From 5230bd6702f9e88e91bd181decb76fefe2d10ff0 Mon Sep 17 00:00:00 2001 From: Max Amundsen Date: Mon, 13 Jul 2026 15:21:42 -0400 Subject: [PATCH] fix multiselect combobox --- go/cmd/examples/go-wasm-web/app/kit.go | 34 +++- go/cmd/examples/go-wasm-web/wwwroot/app.css | 2 +- go/vdom/vnode.go | 80 +++++++- go/vdom/vnode_test.go | 46 +++++ go/wasmruntime/host_native.go | 5 + go/wasmruntime/host_wasm.go | 24 +++ go/wasmruntime/props_test.go | 120 +++++++++++ go/wasmruntime/reconcile.go | 29 ++- go/wasmruntime/testdata/domexec.js | 5 + go/webui/forms.go | 208 +++++++++++++++----- go/webui/forms_dropdown_test.go | 169 ++++++++++++++++ 11 files changed, 664 insertions(+), 58 deletions(-) create mode 100644 go/vdom/vnode_test.go create mode 100644 go/wasmruntime/props_test.go diff --git a/go/cmd/examples/go-wasm-web/app/kit.go b/go/cmd/examples/go-wasm-web/app/kit.go index ade59706..8992c488 100644 --- a/go/cmd/examples/go-wasm-web/app/kit.go +++ b/go/cmd/examples/go-wasm-web/app/kit.go @@ -1,6 +1,8 @@ package app import ( + "strings" + . "kjol/vdom" ui "kjol/webui" ) @@ -31,6 +33,20 @@ func ptRow(name, plan string, status *VNode) *VNode { ) } +// languageOptions is deliberately longer than the pill limit, so the multi-select +// demonstrates both ways it collapses: past 3 selections it says "N items selected" +// outright, and below that it still collapses if the pills are too wide for the field. +func languageOptions() []ui.FormSelectOption { + return []ui.FormSelectOption{ + {Value: "go", Label: "Go"}, + {Value: "rust", Label: "Rust"}, + {Value: "ts", Label: "TypeScript"}, + {Value: "python", Label: "Python"}, + {Value: "kotlin", Label: "Kotlin"}, + {Value: "swift", Label: "Swift"}, + } +} + //gowasm:page /kit layout=app func KitPage(d Deps) func() *VNode { // Interactive demos own their state via signals (a write re-renders). @@ -41,6 +57,7 @@ func KitPage(d Deps) func() *VNode { name := NewSignal("") email := NewSignal("") plan := NewSignal("pro") + langs := NewSignal([]string{"go"}) // Floating components are CONTROLLERS: they own refs, timers and open state, so // they are built once here — never inside the render closure below, which would @@ -48,6 +65,7 @@ func KitPage(d Deps) func() *VNode { modal := ui.NewModal(ui.ModalOptions{Size: ui.ModalMedium}) menu := ui.NewMenu(ui.MenuOptions{Placement: ui.PlacementBottomStart}) tip := ui.NewHoverTooltip(ui.PlacementTop, "") + skills := ui.NewMultiSelect(ui.DropdownOptions{}) return func() *VNode { return Div(Attr("class", "space-y-8"), @@ -134,9 +152,23 @@ func KitPage(d Deps) func() *VNode { ui.FormOption("free", "Free", false), ui.FormOption("pro", "Pro", false), ui.FormOption("enterprise", "Enterprise", false))), + + // A multi-select. Its rows carry checkboxes, and the field shows the + // selection as removable pills — until they stop fitting, at which point + // it collapses to "N items selected". Tick a few and watch it flip. + row("flex flex-col gap-1", ui.FormLabel(ui.FormLabelProps{}, Text("Languages")), + skills.Render(ui.FormMultiSelectProps{ + Options: languageOptions(), + Value: langs.Get(), + Placeholder: "Pick a few", + Searchable: true, + ShowSelectAll: true, + OnChange: func(v []string) { langs.Set(v) }, + })), ), P(Attr("class", "text-xs text-neutral-500"), - Text("Live: name=\""+name.Get()+"\" email=\""+email.Get()+"\" plan=\""+plan.Get()+"\"")), + Text("Live: name=\""+name.Get()+"\" email=\""+email.Get()+"\" plan=\""+plan.Get()+ + "\" languages="+strings.Join(langs.Get(), ","))), ), kitSection("Table", diff --git a/go/cmd/examples/go-wasm-web/wwwroot/app.css b/go/cmd/examples/go-wasm-web/wwwroot/app.css index c0f9c1a2..a0c82a62 100644 --- a/go/cmd/examples/go-wasm-web/wwwroot/app.css +++ b/go/cmd/examples/go-wasm-web/wwwroot/app.css @@ -1,5 +1,5 @@ @layer theme,base,components,utilities;@layer theme{:root,:host{--font-sans:"Lora", ui-serif, Georgia, Cambria, "Times New Roman", serif;--font-serif:"Lora", ui-serif, Georgia, Cambria, "Times New Roman", serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', - monospace;--color-red-50:oklch(97.1% 0.013 17.38);--color-red-100:oklch(93.6% 0.032 17.717);--color-red-200:oklch(88.5% 0.062 18.334);--color-red-300:oklch(80.8% 0.114 19.571);--color-red-400:oklch(70.4% 0.191 22.216);--color-red-500:oklch(63.7% 0.237 25.331);--color-red-600:oklch(57.7% 0.245 27.325);--color-red-700:oklch(50.5% 0.213 27.518);--color-red-800:oklch(44.4% 0.177 26.899);--color-red-900:oklch(39.6% 0.141 25.723);--color-red-950:oklch(25.8% 0.092 26.042);--color-orange-50:oklch(98% 0.016 73.684);--color-orange-100:oklch(95.4% 0.038 75.164);--color-orange-200:oklch(90.1% 0.076 70.697);--color-orange-300:oklch(83.7% 0.128 66.29);--color-orange-400:oklch(75% 0.183 55.934);--color-orange-500:oklch(70.5% 0.213 47.604);--color-orange-600:oklch(64.6% 0.222 41.116);--color-orange-700:oklch(55.3% 0.195 38.402);--color-orange-800:oklch(47% 0.157 37.304);--color-orange-900:oklch(40.8% 0.123 38.172);--color-orange-950:oklch(26.6% 0.079 36.259);--color-amber-50:oklch(98.7% 0.022 95.277);--color-amber-100:oklch(96.2% 0.059 95.617);--color-amber-200:oklch(92.4% 0.12 95.746);--color-amber-300:oklch(87.9% 0.169 91.605);--color-amber-400:oklch(82.8% 0.189 84.429);--color-amber-500:oklch(76.9% 0.188 70.08);--color-amber-600:oklch(66.6% 0.179 58.318);--color-amber-700:oklch(55.5% 0.163 48.998);--color-amber-800:oklch(47.3% 0.137 46.201);--color-amber-900:oklch(41.4% 0.112 45.904);--color-amber-950:oklch(27.9% 0.077 45.635);--color-yellow-50:oklch(98.7% 0.026 102.212);--color-yellow-100:oklch(97.3% 0.071 103.193);--color-yellow-200:oklch(94.5% 0.129 101.54);--color-yellow-300:oklch(90.5% 0.182 98.111);--color-yellow-400:oklch(85.2% 0.199 91.936);--color-yellow-500:oklch(79.5% 0.184 86.047);--color-yellow-600:oklch(68.1% 0.162 75.834);--color-yellow-700:oklch(55.4% 0.135 66.442);--color-yellow-800:oklch(47.6% 0.114 61.907);--color-yellow-900:oklch(42.1% 0.095 57.708);--color-yellow-950:oklch(28.6% 0.066 53.813);--color-lime-50:oklch(98.6% 0.031 120.757);--color-lime-100:oklch(96.7% 0.067 122.328);--color-lime-200:oklch(93.8% 0.127 124.321);--color-lime-300:oklch(89.7% 0.196 126.665);--color-lime-400:oklch(84.1% 0.238 128.85);--color-lime-500:oklch(76.8% 0.233 130.85);--color-lime-600:oklch(64.8% 0.2 131.684);--color-lime-700:oklch(53.2% 0.157 131.589);--color-lime-800:oklch(45.3% 0.124 130.933);--color-lime-900:oklch(40.5% 0.101 131.063);--color-lime-950:oklch(27.4% 0.072 132.109);--color-green-50:oklch(98.2% 0.018 155.826);--color-green-100:oklch(96.2% 0.044 156.743);--color-green-200:oklch(92.5% 0.084 155.995);--color-green-300:oklch(87.1% 0.15 154.449);--color-green-400:oklch(79.2% 0.209 151.711);--color-green-500:oklch(72.3% 0.219 149.579);--color-green-600:oklch(62.7% 0.194 149.214);--color-green-700:oklch(52.7% 0.154 150.069);--color-green-800:oklch(44.8% 0.119 151.328);--color-green-900:oklch(39.3% 0.095 152.535);--color-green-950:oklch(26.6% 0.065 152.934);--color-emerald-50:oklch(97.9% 0.021 166.113);--color-emerald-100:oklch(95% 0.052 163.051);--color-emerald-200:oklch(90.5% 0.093 164.15);--color-emerald-300:oklch(84.5% 0.143 164.978);--color-emerald-400:oklch(76.5% 0.177 163.223);--color-emerald-500:oklch(69.6% 0.17 162.48);--color-emerald-600:oklch(59.6% 0.145 163.225);--color-emerald-700:oklch(50.8% 0.118 165.612);--color-emerald-800:oklch(43.2% 0.095 166.913);--color-emerald-900:oklch(37.8% 0.077 168.94);--color-emerald-950:oklch(26.2% 0.051 172.552);--color-teal-50:oklch(98.4% 0.014 180.72);--color-teal-100:oklch(95.3% 0.051 180.801);--color-teal-200:oklch(91% 0.096 180.426);--color-teal-300:oklch(85.5% 0.138 181.071);--color-teal-400:oklch(77.7% 0.152 181.912);--color-teal-500:oklch(70.4% 0.14 182.503);--color-teal-600:oklch(60% 0.118 184.704);--color-teal-700:oklch(51.1% 0.096 186.391);--color-teal-800:oklch(43.7% 0.078 188.216);--color-teal-900:oklch(38.6% 0.063 188.416);--color-teal-950:oklch(27.7% 0.046 192.524);--color-cyan-50:oklch(98.4% 0.019 200.873);--color-cyan-100:oklch(95.6% 0.045 203.388);--color-cyan-200:oklch(91.7% 0.08 205.041);--color-cyan-300:oklch(86.5% 0.127 207.078);--color-cyan-400:oklch(78.9% 0.154 211.53);--color-cyan-500:oklch(71.5% 0.143 215.221);--color-cyan-600:oklch(60.9% 0.126 221.723);--color-cyan-700:oklch(52% 0.105 223.128);--color-cyan-800:oklch(45% 0.085 224.283);--color-cyan-900:oklch(39.8% 0.07 227.392);--color-cyan-950:oklch(30.2% 0.056 229.695);--color-sky-50:oklch(97.7% 0.013 236.62);--color-sky-100:oklch(95.1% 0.026 236.824);--color-sky-200:oklch(90.1% 0.058 230.902);--color-sky-300:oklch(82.8% 0.111 230.318);--color-sky-400:oklch(74.6% 0.16 232.661);--color-sky-500:oklch(68.5% 0.169 237.323);--color-sky-600:oklch(58.8% 0.158 241.966);--color-sky-700:oklch(50% 0.134 242.749);--color-sky-800:oklch(44.3% 0.11 240.79);--color-sky-900:oklch(39.1% 0.09 240.876);--color-sky-950:oklch(29.3% 0.066 243.157);--color-blue-50:oklch(97% 0.014 254.604);--color-blue-100:oklch(93.2% 0.032 255.585);--color-blue-200:oklch(88.2% 0.059 254.128);--color-blue-300:oklch(80.9% 0.105 251.813);--color-blue-400:oklch(70.7% 0.165 254.624);--color-blue-500:oklch(62.3% 0.214 259.815);--color-blue-600:oklch(54.6% 0.245 262.881);--color-blue-700:oklch(48.8% 0.243 264.376);--color-blue-800:oklch(42.4% 0.199 265.638);--color-blue-900:oklch(37.9% 0.146 265.522);--color-blue-950:oklch(28.2% 0.091 267.935);--color-indigo-50:oklch(96.2% 0.018 272.314);--color-indigo-100:oklch(93% 0.034 272.788);--color-indigo-200:oklch(87% 0.065 274.039);--color-indigo-300:oklch(78.5% 0.115 274.713);--color-indigo-400:oklch(67.3% 0.182 276.935);--color-indigo-500:oklch(58.5% 0.233 277.117);--color-indigo-600:oklch(51.1% 0.262 276.966);--color-indigo-700:oklch(45.7% 0.24 277.023);--color-indigo-800:oklch(39.8% 0.195 277.366);--color-indigo-900:oklch(35.9% 0.144 278.697);--color-indigo-950:oklch(25.7% 0.09 281.288);--color-violet-50:oklch(96.9% 0.016 293.756);--color-violet-100:oklch(94.3% 0.029 294.588);--color-violet-200:oklch(89.4% 0.057 293.283);--color-violet-300:oklch(81.1% 0.111 293.571);--color-violet-400:oklch(70.2% 0.183 293.541);--color-violet-500:oklch(60.6% 0.25 292.717);--color-violet-600:oklch(54.1% 0.281 293.009);--color-violet-700:oklch(49.1% 0.27 292.581);--color-violet-800:oklch(43.2% 0.232 292.759);--color-violet-900:oklch(38% 0.189 293.745);--color-violet-950:oklch(28.3% 0.141 291.089);--color-purple-50:oklch(97.7% 0.014 308.299);--color-purple-100:oklch(94.6% 0.033 307.174);--color-purple-200:oklch(90.2% 0.063 306.703);--color-purple-300:oklch(82.7% 0.119 306.383);--color-purple-400:oklch(71.4% 0.203 305.504);--color-purple-500:oklch(62.7% 0.265 303.9);--color-purple-600:oklch(55.8% 0.288 302.321);--color-purple-700:oklch(49.6% 0.265 301.924);--color-purple-800:oklch(43.8% 0.218 303.724);--color-purple-900:oklch(38.1% 0.176 304.987);--color-purple-950:oklch(29.1% 0.149 302.717);--color-fuchsia-50:oklch(97.7% 0.017 320.058);--color-fuchsia-100:oklch(95.2% 0.037 318.852);--color-fuchsia-200:oklch(90.3% 0.076 319.62);--color-fuchsia-300:oklch(83.3% 0.145 321.434);--color-fuchsia-400:oklch(74% 0.238 322.16);--color-fuchsia-500:oklch(66.7% 0.295 322.15);--color-fuchsia-600:oklch(59.1% 0.293 322.896);--color-fuchsia-700:oklch(51.8% 0.253 323.949);--color-fuchsia-800:oklch(45.2% 0.211 324.591);--color-fuchsia-900:oklch(40.1% 0.17 325.612);--color-fuchsia-950:oklch(29.3% 0.136 325.661);--color-pink-50:oklch(97.1% 0.014 343.198);--color-pink-100:oklch(94.8% 0.028 342.258);--color-pink-200:oklch(89.9% 0.061 343.231);--color-pink-300:oklch(82.3% 0.12 346.018);--color-pink-400:oklch(71.8% 0.202 349.761);--color-pink-500:oklch(65.6% 0.241 354.308);--color-pink-600:oklch(59.2% 0.249 0.584);--color-pink-700:oklch(52.5% 0.223 3.958);--color-pink-800:oklch(45.9% 0.187 3.815);--color-pink-900:oklch(40.8% 0.153 2.432);--color-pink-950:oklch(28.4% 0.109 3.907);--color-rose-50:oklch(96.9% 0.015 12.422);--color-rose-100:oklch(94.1% 0.03 12.58);--color-rose-200:oklch(89.2% 0.058 10.001);--color-rose-300:oklch(81% 0.117 11.638);--color-rose-400:oklch(71.2% 0.194 13.428);--color-rose-500:oklch(64.5% 0.246 16.439);--color-rose-600:oklch(58.6% 0.253 17.585);--color-rose-700:oklch(51.4% 0.222 16.935);--color-rose-800:oklch(45.5% 0.188 13.697);--color-rose-900:oklch(41% 0.159 10.272);--color-rose-950:oklch(27.1% 0.105 12.094);--color-slate-50:oklch(98.4% 0.003 247.858);--color-slate-100:oklch(96.8% 0.007 247.896);--color-slate-200:oklch(92.9% 0.013 255.508);--color-slate-300:oklch(86.9% 0.022 252.894);--color-slate-400:oklch(70.4% 0.04 256.788);--color-slate-500:oklch(55.4% 0.046 257.417);--color-slate-600:oklch(44.6% 0.043 257.281);--color-slate-700:oklch(37.2% 0.044 257.287);--color-slate-800:oklch(27.9% 0.041 260.031);--color-slate-900:oklch(20.8% 0.042 265.755);--color-slate-950:oklch(12.9% 0.042 264.695);--color-gray-50:oklch(98.5% 0.002 247.839);--color-gray-100:oklch(96.7% 0.003 264.542);--color-gray-200:oklch(92.8% 0.006 264.531);--color-gray-300:oklch(87.2% 0.01 258.338);--color-gray-400:oklch(70.7% 0.022 261.325);--color-gray-500:oklch(55.1% 0.027 264.364);--color-gray-600:oklch(44.6% 0.03 256.802);--color-gray-700:oklch(37.3% 0.034 259.733);--color-gray-800:oklch(27.8% 0.033 256.848);--color-gray-900:oklch(21% 0.034 264.665);--color-gray-950:oklch(13% 0.028 261.692);--color-zinc-50:oklch(98.5% 0 0);--color-zinc-100:oklch(96.7% 0.001 286.375);--color-zinc-200:oklch(92% 0.004 286.32);--color-zinc-300:oklch(87.1% 0.006 286.286);--color-zinc-400:oklch(70.5% 0.015 286.067);--color-zinc-500:oklch(55.2% 0.016 285.938);--color-zinc-600:oklch(44.2% 0.017 285.786);--color-zinc-700:oklch(37% 0.013 285.805);--color-zinc-800:oklch(27.4% 0.006 286.033);--color-zinc-900:oklch(21% 0.006 285.885);--color-zinc-950:oklch(14.1% 0.005 285.823);--color-neutral-50:oklch(98.5% 0 0);--color-neutral-100:oklch(97% 0 0);--color-neutral-200:oklch(92.2% 0 0);--color-neutral-300:oklch(87% 0 0);--color-neutral-400:oklch(70.8% 0 0);--color-neutral-500:oklch(55.6% 0 0);--color-neutral-600:oklch(43.9% 0 0);--color-neutral-700:oklch(37.1% 0 0);--color-neutral-800:oklch(26.9% 0 0);--color-neutral-900:oklch(20.5% 0 0);--color-neutral-950:oklch(14.5% 0 0);--color-stone-50:oklch(98.5% 0.001 106.423);--color-stone-100:oklch(97% 0.001 106.424);--color-stone-200:oklch(92.3% 0.003 48.717);--color-stone-300:oklch(86.9% 0.005 56.366);--color-stone-400:oklch(70.9% 0.01 56.259);--color-stone-500:oklch(55.3% 0.013 58.071);--color-stone-600:oklch(44.4% 0.011 73.639);--color-stone-700:oklch(37.4% 0.01 67.558);--color-stone-800:oklch(26.8% 0.007 34.298);--color-stone-900:oklch(21.6% 0.006 56.043);--color-stone-950:oklch(14.7% 0.004 49.25);--color-mauve-50:oklch(98.5% 0 0);--color-mauve-100:oklch(96% 0.003 325.6);--color-mauve-200:oklch(92.2% 0.005 325.62);--color-mauve-300:oklch(86.5% 0.012 325.68);--color-mauve-400:oklch(71.1% 0.019 323.02);--color-mauve-500:oklch(54.2% 0.034 322.5);--color-mauve-600:oklch(43.5% 0.029 321.78);--color-mauve-700:oklch(36.4% 0.029 323.89);--color-mauve-800:oklch(26.3% 0.024 320.12);--color-mauve-900:oklch(21.2% 0.019 322.12);--color-mauve-950:oklch(14.5% 0.008 326);--color-olive-50:oklch(98.8% 0.003 106.5);--color-olive-100:oklch(96.6% 0.005 106.5);--color-olive-200:oklch(93% 0.007 106.5);--color-olive-300:oklch(88% 0.011 106.6);--color-olive-400:oklch(73.7% 0.021 106.9);--color-olive-500:oklch(58% 0.031 107.3);--color-olive-600:oklch(46.6% 0.025 107.3);--color-olive-700:oklch(39.4% 0.023 107.4);--color-olive-800:oklch(28.6% 0.016 107.4);--color-olive-900:oklch(22.8% 0.013 107.4);--color-olive-950:oklch(15.3% 0.006 107.1);--color-mist-50:oklch(98.7% 0.002 197.1);--color-mist-100:oklch(96.3% 0.002 197.1);--color-mist-200:oklch(92.5% 0.005 214.3);--color-mist-300:oklch(87.2% 0.007 219.6);--color-mist-400:oklch(72.3% 0.014 214.4);--color-mist-500:oklch(56% 0.021 213.5);--color-mist-600:oklch(45% 0.017 213.2);--color-mist-700:oklch(37.8% 0.015 216);--color-mist-800:oklch(27.5% 0.011 216.9);--color-mist-900:oklch(21.8% 0.008 223.9);--color-mist-950:oklch(14.8% 0.004 228.8);--color-taupe-50:oklch(98.6% 0.002 67.8);--color-taupe-100:oklch(96% 0.002 17.2);--color-taupe-200:oklch(92.2% 0.005 34.3);--color-taupe-300:oklch(86.8% 0.007 39.5);--color-taupe-400:oklch(71.4% 0.014 41.2);--color-taupe-500:oklch(54.7% 0.021 43.1);--color-taupe-600:oklch(43.8% 0.017 39.3);--color-taupe-700:oklch(36.7% 0.016 35.7);--color-taupe-800:oklch(26.8% 0.011 36.5);--color-taupe-900:oklch(21.4% 0.009 43.1);--color-taupe-950:oklch(14.7% 0.004 49.3);--color-black:#000;--color-white:#fff;--spacing:0.25rem;--breakpoint-sm:40rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--breakpoint-2xl:96rem;--container-3xs:16rem;--container-2xs:18rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--text-xs:0.75rem;--text-xs--line-height:calc(1 / 0.75);--text-sm:0.875rem;--text-sm--line-height:calc(1.25 / 0.875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--text-7xl:4.5rem;--text-7xl--line-height:1;--text-8xl:6rem;--text-8xl--line-height:1;--text-9xl:8rem;--text-9xl--line-height:1;--font-weight-thin:100;--font-weight-extralight:200;--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-tighter:-0.05em;--tracking-tight:-0.025em;--tracking-normal:0em;--tracking-wide:0.025em;--tracking-wider:0.05em;--tracking-widest:0.1em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--leading-loose:2;--radius-xs:0.125rem;--radius-sm:0.25rem;--radius-md:0.375rem;--radius-lg:0.5rem;--radius-xl:0.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--radius-4xl:2rem;--shadow-2xs:0 1px rgb(0 0 0 / 0.05);--shadow-xs:0 1px 2px 0 rgb(0 0 0 / 0.05);--shadow-sm:0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);--shadow-md:0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);--shadow-lg:0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);--shadow-xl:0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);--shadow-2xl:0 25px 50px -12px rgb(0 0 0 / 0.25);--inset-shadow-2xs:inset 0 1px rgb(0 0 0 / 0.05);--inset-shadow-xs:inset 0 1px 1px rgb(0 0 0 / 0.05);--inset-shadow-sm:inset 0 2px 4px rgb(0 0 0 / 0.05);--drop-shadow-xs:0 1px 1px rgb(0 0 0 / 0.05);--drop-shadow-sm:0 1px 2px rgb(0 0 0 / 0.15);--drop-shadow-md:0 3px 3px rgb(0 0 0 / 0.12);--drop-shadow-lg:0 4px 4px rgb(0 0 0 / 0.15);--drop-shadow-xl:0 9px 7px rgb(0 0 0 / 0.1);--drop-shadow-2xl:0 25px 25px rgb(0 0 0 / 0.15);--text-shadow-2xs:0px 1px 0px rgb(0 0 0 / 0.15);--text-shadow-xs:0px 1px 1px rgb(0 0 0 / 0.2);--text-shadow-sm:0px 1px 0px rgb(0 0 0 / 0.075), 0px 1px 1px rgb(0 0 0 / 0.075), 0px 2px 2px rgb(0 0 0 / 0.075);--text-shadow-md:0px 1px 1px rgb(0 0 0 / 0.1), 0px 1px 2px rgb(0 0 0 / 0.1), 0px 2px 4px rgb(0 0 0 / 0.1);--text-shadow-lg:0px 1px 2px rgb(0 0 0 / 0.1), 0px 3px 2px rgb(0 0 0 / 0.1), 0px 4px 8px rgb(0 0 0 / 0.1);--ease-in:cubic-bezier(0.4, 0, 1, 1);--ease-out:cubic-bezier(0, 0, 0.2, 1);--ease-in-out:cubic-bezier(0.4, 0, 0.2, 1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;--animate-pulse:pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;--animate-bounce:bounce 1s infinite;--blur-xs:4px;--blur-sm:8px;--blur-md:12px;--blur-lg:16px;--blur-xl:24px;--blur-2xl:40px;--blur-3xl:64px;--perspective-dramatic:100px;--perspective-near:300px;--perspective-normal:500px;--perspective-midrange:800px;--perspective-distant:1200px;--aspect-video:16 / 9;--default-transition-duration:150ms;--default-transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);--default-font-family:var(--font-sans);--default-font-feature-settings:initial;--default-font-variation-settings:initial;--default-mono-font-family:var(--font-mono);--default-mono-font-feature-settings:initial;--default-mono-font-variation-settings:initial;--radius-default:0.375rem;--color-primary:#4f46e5;--color-primary-hover:#4338ca;--color-text-heading:#111827;--color-text-on-dark:#f9fafb;--color-text-on-dark-muted:#9ca3af}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,100%{transform:scale(2);opacity:0}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,100%{transform:translateY(-25%);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;animation-timing-function:cubic-bezier(0,0,.2,1)}}@layer base{*,::after,::before,::backdrop,::file-selector-button{box-sizing:border-box;margin:0;padding:0;border:0 solid}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;tab-size:4;font-family:var(--default-font-family);font-feature-settings:var(--default-font-feature-settings);font-variation-settings:var(--default-font-variation-settings);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family);font-feature-settings:var(--default-mono-font-feature-settings);font-variation-settings:var(--default-mono-font-variation-settings);font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea,::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;border-radius:0;background-color:initial;opacity:1}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports(not (-webkit-appearance:-apple-pay-button)) or (contain-intrinsic-size:1px){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]),::file-selector-button{appearance:button}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@font-face{font-family:lora;font-style:italic;font-weight:400 700;font-display:swap;src:url(/fonts/lora-latin-ext-italic.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:lora;font-style:italic;font-weight:400 700;font-display:swap;src:url(/fonts/lora-latin-italic.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:lora;font-style:normal;font-weight:400 700;font-display:swap;src:url(/fonts/lora-latin-ext-normal.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:lora;font-style:normal;font-weight:400 700;font-display:swap;src:url(/fonts/lora-latin-normal.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:0}.inset-y-0{inset-block:0}.top-0{top:0}.top-1\/2{top:calc(1/2 * 100%)}.top-20{top:calc(var(--spacing) * 20)}.top-4{top:calc(var(--spacing) * 4)}.top-full{top:100%}.-right-2{right:calc(var(--spacing) * -2)}.right-0{right:0}.right-0\.5{right:calc(var(--spacing) * .5)}.right-4{right:calc(var(--spacing) * 4)}.right-8{right:calc(var(--spacing) * 8)}.bottom-4{bottom:calc(var(--spacing) * 4)}.left-0{left:0}.left-1\/2{left:calc(1/2 * 100%)}.left-4{left:calc(var(--spacing) * 4)}.isolate{isolation:isolate}.-z-10{z-index:calc(10 * -1)}.z-10{z-index:10}.z-150{z-index:150}.z-200{z-index:200}.z-\[100\]{z-index:100}.z-\[110\]{z-index:110}.z-\[1\]{z-index:1}.z-\[200\]{z-index:200}.col-span-1{grid-column:span 1/span 1}.-m-1\.5{margin:calc(var(--spacing) * -1.5)}.m-0{margin:0}.-mx-1\.5{margin-inline:calc(var(--spacing) * -1.5)}.mx-3{margin-inline:calc(var(--spacing) * 3)}.mx-4{margin-inline:calc(var(--spacing) * 4)}.mx-\[0\.15rem\]{margin-inline:.15rem}.mx-auto{margin-inline:auto}.my-1{margin-block:var(--spacing)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-12{margin-top:calc(var(--spacing) * 12)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mr-2{margin-right:calc(var(--spacing) * 2)}.mr-5{margin-right:calc(var(--spacing) * 5)}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.ml-1{margin-left:var(--spacing)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-3{margin-left:calc(var(--spacing) * 3)}.ml-4{margin-left:calc(var(--spacing) * 4)}.ml-auto{margin-left:auto}.box-border{box-sizing:border-box}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.aspect-square{aspect-ratio:1/1}.h-0{height:0}.h-0\.5{height:calc(var(--spacing) * .5)}.h-1{height:var(--spacing)}.h-2{height:calc(var(--spacing) * 2)}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-72{height:calc(var(--spacing) * 72)}.h-8{height:calc(var(--spacing) * 8)}.h-\[30px\]{height:30px}.h-\[38px\]{height:38px}.h-dvh{height:100dvh}.h-full{height:100%}.h-screen{height:100vh}.max-h-60{max-height:calc(var(--spacing) * 60)}.max-h-64{max-height:calc(var(--spacing) * 64)}.max-h-96{max-height:calc(var(--spacing) * 96)}.max-h-\[34rem\]{max-height:34rem}.max-h-\[calc\(100dvh_-_5rem\)\]{max-height:calc(100dvh - 5rem)}.max-h-\[calc\(100vh_-_7rem\)\]{max-height:calc(100vh - 7rem)}.max-h-dvh{max-height:100dvh}.min-h-0{min-height:0}.min-h-9{min-height:calc(var(--spacing) * 9)}.min-h-\[6\.5rem\]{min-height:6.5rem}.min-h-screen{min-height:100vh}.w-0{width:0}.w-1{width:var(--spacing)}.w-10{width:calc(var(--spacing) * 10)}.w-2{width:calc(var(--spacing) * 2)}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-4{width:calc(var(--spacing) * 4)}.w-40{width:calc(var(--spacing) * 40)}.w-52{width:calc(var(--spacing) * 52)}.w-56{width:calc(var(--spacing) * 56)}.w-6{width:calc(var(--spacing) * 6)}.w-64{width:calc(var(--spacing) * 64)}.w-7{width:calc(var(--spacing) * 7)}.w-8{width:calc(var(--spacing) * 8)}.w-9{width:calc(var(--spacing) * 9)}.w-\[26rem\]{width:26rem}.w-auto{width:auto}.w-full{width:100%}.w-max{width:max-content}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-64{max-width:calc(var(--spacing) * 64)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-7xl{max-width:var(--container-7xl)}.max-w-80{max-width:calc(var(--spacing) * 80)}.max-w-\[100rem\]{max-width:100rem}.max-w-\[11rem\]{max-width:11rem}.max-w-\[14rem\]{max-width:14rem}.max-w-\[16rem\]{max-width:16rem}.max-w-\[20rem\]{max-width:20rem}.max-w-\[90rem\]{max-width:90rem}.max-w-full{max-width:100%}.max-w-md{max-width:var(--container-md)}.max-w-none{max-width:none}.max-w-screen{max-width:100vw}.max-w-sm{max-width:var(--container-sm)}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:0}.min-w-48{min-width:calc(var(--spacing) * 48)}.min-w-5{min-width:calc(var(--spacing) * 5)}.min-w-72{min-width:calc(var(--spacing) * 72)}.min-w-8{min-width:calc(var(--spacing) * 8)}.min-w-\[10rem\]{min-width:10rem}.min-w-\[16rem\]{min-width:16rem}.min-w-\[240px\]{min-width:240px}.min-w-\[7rem\]{min-width:7rem}.min-w-\[8rem\]{min-width:8rem}.min-w-\[9rem\]{min-width:9rem}.min-w-full{min-width:100%}.flex-1{flex:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.table-fixed{table-layout:fixed}.border-collapse{border-collapse:collapse}.-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-0\.5{--tw-translate-x:calc(var(--spacing) * 0.5);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-\[18px\]{--tw-translate-x:18px;translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x)var(--tw-translate-y)}.scale-110{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x)var(--tw-scale-y)}.scale-95{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.rotate-180{rotate:180deg}.rotate-90{rotate:90deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-col-resize{cursor:col-resize}.cursor-default{cursor:default}.cursor-grab{cursor:grab}.cursor-pointer{cursor:pointer}.resize{resize:both}.resize-none{resize:none}.list-none{list-style-type:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-8{gap:calc(var(--spacing) * 8)}.gap-\[2px\]{gap:2px}.space-y-6{ :where(& > :not(:last-child)) { --tw-space-y-reverse: 0; margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse)); margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse))); }}.space-y-8{ :where(& > :not(:last-child)) { --tw-space-y-reverse: 0; margin-block-start: calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse)); margin-block-end: calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse))); }}.self-end{align-self:flex-end}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-y-hidden{overflow-y:hidden}.rounded{border-radius:.25rem}.rounded-default{border-radius:var(--radius-default)}.rounded-full{border-radius:calc(infinity * 1px)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-l-default{border-top-left-radius:var(--radius-default);border-bottom-left-radius:var(--radius-default)}.rounded-l-none{border-top-left-radius:0;border-bottom-left-radius:0}.rounded-b-default{border-bottom-right-radius:var(--radius-default);border-bottom-left-radius:var(--radius-default)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-4{border-style:var(--tw-border-style);border-width:4px}.border-x{border-inline-style:var(--tw-border-style);border-inline-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-t-2{border-top-style:var(--tw-border-style);border-top-width:2px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-r-0{border-right-style:var(--tw-border-style);border-right-width:0}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-none{--tw-border-style:none;border-style:none}.border-emerald-300{border-color:var(--color-emerald-300)}.border-green-200{border-color:var(--color-green-200)}.border-green-500{border-color:var(--color-green-500)}.border-neutral-100{border-color:var(--color-neutral-100)}.border-neutral-200{border-color:var(--color-neutral-200)}.border-neutral-300{border-color:var(--color-neutral-300)}.border-neutral-700{border-color:var(--color-neutral-700)}.border-neutral-800{border-color:var(--color-neutral-800)}.border-primary{border-color:var(--color-primary)}.border-red-200{border-color:var(--color-red-200)}.border-red-500{border-color:var(--color-red-500)}.border-sky-200{border-color:var(--color-sky-200)}.border-sky-700{border-color:var(--color-sky-700)}.border-transparent{border-color:transparent}.border-yellow-200{border-color:var(--color-yellow-200)}.border-t-sky-700{border-top-color:var(--color-sky-700)}.border-t-transparent{border-top-color:transparent}.border-l-green-700{border-left-color:var(--color-green-700)}.border-l-neutral-300{border-left-color:var(--color-neutral-300)}.border-l-neutral-400{border-left-color:var(--color-neutral-400)}.border-l-red-700{border-left-color:var(--color-red-700)}.border-l-sky-800{border-left-color:var(--color-sky-800)}.border-l-yellow-500{border-left-color:var(--color-yellow-500)}.\!bg-primary{background-color:var(--color-primary)!important}.bg-amber-100{background-color:var(--color-amber-100)}.bg-amber-100\!{background-color:var(--color-amber-100)!important}.bg-amber-700{background-color:var(--color-amber-700)}.bg-black\/10{background-color:color-mix(in oklab,var(--color-black) 10%,transparent)}.bg-black\/30{background-color:color-mix(in oklab,var(--color-black) 30%,transparent)}.bg-black\/5{background-color:color-mix(in oklab,var(--color-black) 5%,transparent)}.bg-black\/50{background-color:color-mix(in oklab,var(--color-black) 50%,transparent)}.bg-emerald-100{background-color:var(--color-emerald-100)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-700{background-color:var(--color-green-700)}.bg-green-900{background-color:var(--color-green-900)}.bg-neutral-100{background-color:var(--color-neutral-100)}.bg-neutral-200{background-color:var(--color-neutral-200)}.bg-neutral-300{background-color:var(--color-neutral-300)}.bg-neutral-50{background-color:var(--color-neutral-50)}.bg-neutral-500{background-color:var(--color-neutral-500)}.bg-neutral-600{background-color:var(--color-neutral-600)}.bg-neutral-700{background-color:var(--color-neutral-700)}.bg-neutral-800{background-color:var(--color-neutral-800)}.bg-neutral-900{background-color:var(--color-neutral-900)}.bg-orange-500{background-color:var(--color-orange-500)}.bg-orange-600{background-color:var(--color-orange-600)}.bg-primary{background-color:var(--color-primary)}.bg-primary\/10{background-color:color-mix(in oklab,var(--color-primary) 10%,transparent)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-600{background-color:var(--color-red-600)}.bg-red-700{background-color:var(--color-red-700)}.bg-red-900{background-color:var(--color-red-900)}.bg-sky-50{background-color:var(--color-sky-50)}.bg-sky-600{background-color:var(--color-sky-600)}.bg-sky-700{background-color:var(--color-sky-700)}.bg-sky-800{background-color:var(--color-sky-800)}.bg-sky-900{background-color:var(--color-sky-900)}.bg-transparent{background-color:initial}.bg-white{background-color:var(--color-white)}.bg-white\/10{background-color:color-mix(in oklab,var(--color-white) 10%,transparent)}.bg-yellow-400{background-color:var(--color-yellow-400)}.bg-yellow-50{background-color:var(--color-yellow-50)}.bg-yellow-700{background-color:var(--color-yellow-700)}.fill-current{fill:currentcolor}.p-0{padding:0}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:var(--spacing)}.p-1\.5{padding:calc(var(--spacing) * 1.5)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-8{padding:calc(var(--spacing) * 8)}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-7{padding-inline:calc(var(--spacing) * 7)}.px-8{padding-inline:calc(var(--spacing) * 8)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-10{padding-block:calc(var(--spacing) * 10)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-px{padding-block:1px}.pt-1{padding-top:var(--spacing)}.pt-1\.5{padding-top:calc(var(--spacing) * 1.5)}.pt-10{padding-top:calc(var(--spacing) * 10)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pr-14{padding-right:calc(var(--spacing) * 14)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pr-9{padding-right:calc(var(--spacing) * 9)}.pb-0\.5{padding-bottom:calc(var(--spacing) * .5)}.pb-1{padding-bottom:var(--spacing)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pb-\[calc\(1rem_\+_env\(safe-area-inset-bottom\,0px\)\)\]{padding-bottom:calc(1rem + env(safe-area-inset-bottom,0px))}.pl-4{padding-left:calc(var(--spacing) * 4)}.pl-8{padding-left:calc(var(--spacing) * 8)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-\[inherit\]{font-family:inherit}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.5rem\]{font-size:.5rem}.text-\[10px\]{font-size:10px}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.\!text-white{color:var(--color-white)!important}.text-amber-600{color:var(--color-amber-600)}.text-black{color:var(--color-black)}.text-current{color:currentcolor}.text-emerald-700{color:var(--color-emerald-700)}.text-gray-900{color:var(--color-gray-900)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-900{color:var(--color-green-900)}.text-neutral-100{color:var(--color-neutral-100)}.text-neutral-200{color:var(--color-neutral-200)}.text-neutral-300{color:var(--color-neutral-300)}.text-neutral-400{color:var(--color-neutral-400)}.text-neutral-500{color:var(--color-neutral-500)}.text-neutral-600{color:var(--color-neutral-600)}.text-neutral-700{color:var(--color-neutral-700)}.text-neutral-800{color:var(--color-neutral-800)}.text-neutral-900{color:var(--color-neutral-900)}.text-orange-600{color:var(--color-orange-600)}.text-primary{color:var(--color-primary)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-900{color:var(--color-red-900)}.text-sky-600{color:var(--color-sky-600)}.text-sky-700{color:var(--color-sky-700)}.text-sky-900{color:var(--color-sky-900)}.text-text-heading{color:var(--color-text-heading)}.text-text-on-dark{color:var(--color-text-on-dark)}.text-text-on-dark-muted{color:var(--color-text-on-dark-muted)}.text-transparent{color:transparent}.text-violet-600{color:var(--color-violet-600)}.text-white{color:var(--color-white)}.text-yellow-600{color:var(--color-yellow-600)}.text-yellow-700{color:var(--color-yellow-700)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.no-underline{text-decoration-line:none}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.caret-neutral-800{caret-color:var(--color-neutral-800)}.opacity-0{opacity:0%}.opacity-50{opacity:50%}.opacity-60{opacity:60%}.shadow-\[0_4px_12px_rgba\(0\,0\,0\,0\.15\)\]{--tw-shadow:0 4px 12px var(--tw-shadow-color, rgba(0,0,0,0.15));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[inset_0_0_0_1px_currentColor\]{--tw-shadow:inset 0 0 0 1px var(--tw-shadow-color, currentColor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[inset_3px_0_0_0_theme\(colors\.amber\.500\)\]{--tw-shadow:inset 3px 0 0 0 var(--tw-shadow-color, theme(colors.amber.500));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline-hidden{--tw-outline-style:none;outline-style:none;@media(forced-colors:active){outline: 2px solid transparent; outline-offset: 2px;}}.outline-2{outline-style:var(--tw-outline-style);outline-width:2px}.outline-sky-500{outline-color:var(--color-sky-500)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[color\,border-color\]{transition-property:color,border-color;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[opacity\,transform\]{transition-property:opacity,transform;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[width\]{transition-property:width;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:150ms;transition-duration:150ms}.duration-200{--tw-duration:200ms;transition-duration:200ms}.duration-300{--tw-duration:300ms;transition-duration:300ms}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.group-hover\/th\:opacity-50{&:is(:where(.group\/th):hover *){@media(hover:hover){opacity: 50%;}}}.file\:mr-2{&::file-selector-button{margin-right:calc(var(--spacing) * 2)}}.file\:ml-1{&::file-selector-button{margin-left:var(--spacing)}}.file\:cursor-pointer{&::file-selector-button{cursor:pointer}}.file\:rounded-default{&::file-selector-button{border-radius:var(--radius-default)}}.file\:border{&::file-selector-button{border-style:var(--tw-border-style);border-width:1px}}.file\:border-neutral-300{&::file-selector-button{border-color:var(--color-neutral-300)}}.file\:bg-neutral-100{&::file-selector-button{background-color:var(--color-neutral-100)}}.file\:px-3{&::file-selector-button{padding-inline:calc(var(--spacing) * 3)}}.file\:px-4{&::file-selector-button{padding-inline:calc(var(--spacing) * 4)}}.file\:py-\[2px\]{&::file-selector-button{padding-block:2px}}.file\:py-\[3px\]{&::file-selector-button{padding-block:3px}}.file\:text-sm{&::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}.file\:shadow-xs{&::file-selector-button{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.placeholder\:text-neutral-400{&::placeholder{color:var(--color-neutral-400)}}.before\:absolute{&::before{content:var(--tw-content);position:absolute}}.before\:inset-0{&::before{content:var(--tw-content);inset:0}}.before\:-z-20{&::before{content:var(--tw-content);z-index:calc(20 * -1)}}.before\:bg-neutral-300{&::before{content:var(--tw-content);background-color:var(--color-neutral-300)}}.before\:content-\[\'\'\]{&::before{--tw-content:'';content:var(--tw-content)}}.before\:\[clip-path\:polygon\(16px_0\,100\%_0\,100\%_calc\(100\%_-_16px\)\,calc\(100\%_-_16px\)_100\%\,0_100\%\,0_16px\)\]{&::before{content:var(--tw-content);clip-path:polygon(16px 0,100% 0,100% calc(100% - 16px),calc(100% - 16px) 100%,0 100%,0 16px)}}.after\:absolute{&::after{content:var(--tw-content);position:absolute}}.after\:inset-\[1px\]{&::after{content:var(--tw-content);inset:1px}}.after\:-z-10{&::after{content:var(--tw-content);z-index:calc(10 * -1)}}.after\:bg-white{&::after{content:var(--tw-content);background-color:var(--color-white)}}.after\:content-\[\'\'\]{&::after{--tw-content:'';content:var(--tw-content)}}.after\:\[clip-path\:polygon\(15px_0\,100\%_0\,100\%_calc\(100\%_-_15px\)\,calc\(100\%_-_15px\)_100\%\,0_100\%\,0_15px\)\]{&::after{content:var(--tw-content);clip-path:polygon(15px 0,100% 0,100% calc(100% - 15px),calc(100% - 15px) 100%,0 100%,0 15px)}}.last\:border-r-0{&:last-child{border-right-style:var(--tw-border-style);border-right-width:0}}.last\:border-b-0{&:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}}.odd\:bg-white{&:nth-child(odd){background-color:var(--color-white)}}.even\:bg-neutral-100{&:nth-child(even){background-color:var(--color-neutral-100)}}.hover\:bg-green-100{&:hover{@media(hover:hover){background-color: var(--color-green-100);}}}.hover\:bg-green-800{&:hover{@media(hover:hover){background-color: var(--color-green-800);}}}.hover\:bg-green-950{&:hover{@media(hover:hover){background-color: var(--color-green-950);}}}.hover\:bg-neutral-100{&:hover{@media(hover:hover){background-color: var(--color-neutral-100);}}}.hover\:bg-neutral-200{&:hover{@media(hover:hover){background-color: var(--color-neutral-200);}}}.hover\:bg-neutral-300{&:hover{@media(hover:hover){background-color: var(--color-neutral-300);}}}.hover\:bg-neutral-50{&:hover{@media(hover:hover){background-color: var(--color-neutral-50);}}}.hover\:bg-neutral-500{&:hover{@media(hover:hover){background-color: var(--color-neutral-500);}}}.hover\:bg-neutral-800{&:hover{@media(hover:hover){background-color: var(--color-neutral-800);}}}.hover\:bg-orange-700{&:hover{@media(hover:hover){background-color: var(--color-orange-700);}}}.hover\:bg-primary-hover{&:hover{@media(hover:hover){background-color: var(--color-primary-hover);}}}.hover\:bg-red-700{&:hover{@media(hover:hover){background-color: var(--color-red-700);}}}.hover\:bg-red-800{&:hover{@media(hover:hover){background-color: var(--color-red-800);}}}.hover\:bg-red-950{&:hover{@media(hover:hover){background-color: var(--color-red-950);}}}.hover\:bg-sky-100{&:hover{@media(hover:hover){background-color: var(--color-sky-100);}}}.hover\:bg-sky-500{&:hover{@media(hover:hover){background-color: var(--color-sky-500);}}}.hover\:bg-sky-500\/50{&:hover{@media(hover:hover){background-color: color-mix(in oklab,var(--color-sky-500) 50%,transparent);}}}.hover\:bg-sky-800{&:hover{@media(hover:hover){background-color: var(--color-sky-800);}}}.hover\:bg-sky-950{&:hover{@media(hover:hover){background-color: var(--color-sky-950);}}}.hover\:bg-white\/5{&:hover{@media(hover:hover){background-color: color-mix(in oklab,var(--color-white) 5%,transparent);}}}.hover\:bg-yellow-800{&:hover{@media(hover:hover){background-color: var(--color-yellow-800);}}}.hover\:text-neutral-600{&:hover{@media(hover:hover){color: var(--color-neutral-600);}}}.hover\:text-neutral-700{&:hover{@media(hover:hover){color: var(--color-neutral-700);}}}.hover\:text-neutral-800{&:hover{@media(hover:hover){color: var(--color-neutral-800);}}}.hover\:text-neutral-900{&:hover{@media(hover:hover){color: var(--color-neutral-900);}}}.hover\:text-sky-800{&:hover{@media(hover:hover){color: var(--color-sky-800);}}}.hover\:text-text-on-dark{&:hover{@media(hover:hover){color: var(--color-text-on-dark);}}}.hover\:text-white{&:hover{@media(hover:hover){color: var(--color-white);}}}.hover\:underline{&:hover{@media(hover:hover){text-decoration-line: underline;}}}.hover\:decoration-1{&:hover{@media(hover:hover){text-decoration-thickness: 1px;}}}.hover\:shadow-\[inset_0_0_0_2px_currentColor\]{&:hover{@media(hover:hover){--tw-shadow: inset 0 0 0 2px var(--tw-shadow-color,currentColor); box-shadow: var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);}}}.file\:hover\:bg-neutral-200{&::file-selector-button{&:hover{@media(hover:hover){background-color: var(--color-neutral-200);}}}}.focus\:border-primary{&:focus{border-color:var(--color-primary)}}.focus\:border-sky-500{&:focus{border-color:var(--color-sky-500)}}.focus\:bg-neutral-100{&:focus{background-color:var(--color-neutral-100)}}.focus\:bg-red-50{&:focus{background-color:var(--color-red-50)}}.focus\:shadow-\[inset_0_0_0_2px_var\(--color-red-500\)\]{&:focus{--tw-shadow:inset 0 0 0 2px var(--tw-shadow-color, var(--color-red-500));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.focus\:outline-hidden{&:focus{--tw-outline-style:none;outline-style:none;@media(forced-colors:active){outline: 2px solid transparent; outline-offset: 2px;}}}.focus\:outline-2{&:focus{outline-style:var(--tw-outline-style);outline-width:2px}}.focus\:outline-offset-1{&:focus{outline-offset:1px}}.focus\:outline-green-500{&:focus{outline-color:var(--color-green-500)}}.focus\:outline-red-500{&:focus{outline-color:var(--color-red-500)}}.focus\:outline-sky-500{&:focus{outline-color:var(--color-sky-500)}}.focus-visible\:outline-2{&:focus-visible{outline-style:var(--tw-outline-style);outline-width:2px}}.focus-visible\:outline-offset-2{&:focus-visible{outline-offset:2px}}.focus-visible\:outline-current{&:focus-visible{outline-color:currentcolor}}.focus-visible\:outline-primary{&:focus-visible{outline-color:var(--color-primary)}}.active\:bg-neutral-200{&:active{background-color:var(--color-neutral-200)}}.enabled\:hover\:bg-neutral-50{&:enabled{&:hover{@media(hover:hover){background-color: var(--color-neutral-50);}}}}.enabled\:hover\:bg-neutral-900{&:enabled{&:hover{@media(hover:hover){background-color: var(--color-neutral-900);}}}}.enabled\:hover\:bg-red-700{&:enabled{&:hover{@media(hover:hover){background-color: var(--color-red-700);}}}}.disabled\:cursor-not-allowed{&:disabled{cursor:not-allowed}}.disabled\:bg-neutral-100{&:disabled{background-color:var(--color-neutral-100)}}.disabled\:bg-neutral-50{&:disabled{background-color:var(--color-neutral-50)}}.disabled\:text-neutral-300{&:disabled{color:var(--color-neutral-300)}}.disabled\:text-neutral-400{&:disabled{color:var(--color-neutral-400)}}.disabled\:opacity-40{&:disabled{opacity:40%}}.disabled\:opacity-50{&:disabled{opacity:50%}}.disabled\:hover\:bg-transparent{&:disabled{&:hover{@media(hover:hover){background-color: transparent;}}}}.sm\:block{@media(width >= 40rem){display: block;}}.sm\:flex{@media(width >= 40rem){display: flex;}}.sm\:hidden{@media(width >= 40rem){display: none;}}.sm\:w-48{@media(width >= 40rem){width: calc(var(--spacing) * 48);}}.sm\:w-64{@media(width >= 40rem){width: calc(var(--spacing) * 64);}}.sm\:grow-0{@media(width >= 40rem){flex-grow: 0;}}.sm\:grid-cols-2{@media(width >= 40rem){grid-template-columns: repeat(2,minmax(0,1fr));}}.sm\:grid-cols-3{@media(width >= 40rem){grid-template-columns: repeat(3,minmax(0,1fr));}}.sm\:flex-row{@media(width >= 40rem){flex-direction: row;}}.md\:flex-initial{@media(width >= 48rem){flex: 0 auto;}}.md\:justify-start{@media(width >= 48rem){justify-content: flex-start;}}.lg\:col-span-10{@media(width >= 64rem){grid-column: span 10 / span 10;}}.lg\:col-span-2{@media(width >= 64rem){grid-column: span 2 / span 2;}}.lg\:col-span-5{@media(width >= 64rem){grid-column: span 5 / span 5;}}.lg\:col-span-7{@media(width >= 64rem){grid-column: span 7 / span 7;}}.lg\:block{@media(width >= 64rem){display: block;}}.lg\:h-full{@media(width >= 64rem){height: 100%;}}.lg\:grid-cols-12{@media(width >= 64rem){grid-template-columns: repeat(12,minmax(0,1fr));}}.lg\:self-start{@media(width >= 64rem){align-self: flex-start;}}.lg\:pr-8{@media(width >= 64rem){padding-right: calc(var(--spacing) * 8);}}.\[\&_\.ui-form\]\:m-0{& .ui-form{margin:0}}.\[\&_input\]\:cursor-text{& input{cursor:text}}.\[\&_td\]\:p-3{& td{padding:calc(var(--spacing) * 3)}}.\[\&_td\]\:p-4{& td{padding:calc(var(--spacing) * 4)}}.\[\&_td\]\:px-2{& td{padding-inline:calc(var(--spacing) * 2)}}.\[\&_td\]\:py-0\.5{& td{padding-block:calc(var(--spacing) * .5)}}.\[\&_td\]\:py-1{& td{padding-block:var(--spacing)}}.\[\&_td\+td\]\:border-l{& td+td{border-left-style:var(--tw-border-style);border-left-width:1px}}.\[\&_td\+td\]\:border-neutral-300{& td+td{border-color:var(--color-neutral-300)}}.\[\&_th\]\:border-b{& th{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}}.\[\&_th\]\:border-neutral-300{& th{border-color:var(--color-neutral-300)}}.\[\&_tr\:hover\]\:\!bg-green-100{& tr:hover{background-color:var(--color-green-100)!important}}.\[\&_tr\:hover\]\:\!bg-neutral-200{& tr:hover{background-color:var(--color-neutral-200)!important}}.\[\&_tr\:hover\]\:\!bg-sky-100{& tr:hover{background-color:var(--color-sky-100)!important}}.\[\&_tr\:not\(\:last-child\)\]\:border-b{& tr:not(:last-child){border-bottom-style:var(--tw-border-style);border-bottom-width:1px}}.\[\&_tr\:not\(\:last-child\)\]\:border-neutral-300{& tr:not(:last-child){border-color:var(--color-neutral-300)}}.\[\&_tr\:nth-child\(even\)\]\:bg-neutral-100{& tr:nth-child(even){background-color:var(--color-neutral-100)}}}@property --tw-translate-x{syntax: "*"; + monospace;--color-red-50:oklch(97.1% 0.013 17.38);--color-red-100:oklch(93.6% 0.032 17.717);--color-red-200:oklch(88.5% 0.062 18.334);--color-red-300:oklch(80.8% 0.114 19.571);--color-red-400:oklch(70.4% 0.191 22.216);--color-red-500:oklch(63.7% 0.237 25.331);--color-red-600:oklch(57.7% 0.245 27.325);--color-red-700:oklch(50.5% 0.213 27.518);--color-red-800:oklch(44.4% 0.177 26.899);--color-red-900:oklch(39.6% 0.141 25.723);--color-red-950:oklch(25.8% 0.092 26.042);--color-orange-50:oklch(98% 0.016 73.684);--color-orange-100:oklch(95.4% 0.038 75.164);--color-orange-200:oklch(90.1% 0.076 70.697);--color-orange-300:oklch(83.7% 0.128 66.29);--color-orange-400:oklch(75% 0.183 55.934);--color-orange-500:oklch(70.5% 0.213 47.604);--color-orange-600:oklch(64.6% 0.222 41.116);--color-orange-700:oklch(55.3% 0.195 38.402);--color-orange-800:oklch(47% 0.157 37.304);--color-orange-900:oklch(40.8% 0.123 38.172);--color-orange-950:oklch(26.6% 0.079 36.259);--color-amber-50:oklch(98.7% 0.022 95.277);--color-amber-100:oklch(96.2% 0.059 95.617);--color-amber-200:oklch(92.4% 0.12 95.746);--color-amber-300:oklch(87.9% 0.169 91.605);--color-amber-400:oklch(82.8% 0.189 84.429);--color-amber-500:oklch(76.9% 0.188 70.08);--color-amber-600:oklch(66.6% 0.179 58.318);--color-amber-700:oklch(55.5% 0.163 48.998);--color-amber-800:oklch(47.3% 0.137 46.201);--color-amber-900:oklch(41.4% 0.112 45.904);--color-amber-950:oklch(27.9% 0.077 45.635);--color-yellow-50:oklch(98.7% 0.026 102.212);--color-yellow-100:oklch(97.3% 0.071 103.193);--color-yellow-200:oklch(94.5% 0.129 101.54);--color-yellow-300:oklch(90.5% 0.182 98.111);--color-yellow-400:oklch(85.2% 0.199 91.936);--color-yellow-500:oklch(79.5% 0.184 86.047);--color-yellow-600:oklch(68.1% 0.162 75.834);--color-yellow-700:oklch(55.4% 0.135 66.442);--color-yellow-800:oklch(47.6% 0.114 61.907);--color-yellow-900:oklch(42.1% 0.095 57.708);--color-yellow-950:oklch(28.6% 0.066 53.813);--color-lime-50:oklch(98.6% 0.031 120.757);--color-lime-100:oklch(96.7% 0.067 122.328);--color-lime-200:oklch(93.8% 0.127 124.321);--color-lime-300:oklch(89.7% 0.196 126.665);--color-lime-400:oklch(84.1% 0.238 128.85);--color-lime-500:oklch(76.8% 0.233 130.85);--color-lime-600:oklch(64.8% 0.2 131.684);--color-lime-700:oklch(53.2% 0.157 131.589);--color-lime-800:oklch(45.3% 0.124 130.933);--color-lime-900:oklch(40.5% 0.101 131.063);--color-lime-950:oklch(27.4% 0.072 132.109);--color-green-50:oklch(98.2% 0.018 155.826);--color-green-100:oklch(96.2% 0.044 156.743);--color-green-200:oklch(92.5% 0.084 155.995);--color-green-300:oklch(87.1% 0.15 154.449);--color-green-400:oklch(79.2% 0.209 151.711);--color-green-500:oklch(72.3% 0.219 149.579);--color-green-600:oklch(62.7% 0.194 149.214);--color-green-700:oklch(52.7% 0.154 150.069);--color-green-800:oklch(44.8% 0.119 151.328);--color-green-900:oklch(39.3% 0.095 152.535);--color-green-950:oklch(26.6% 0.065 152.934);--color-emerald-50:oklch(97.9% 0.021 166.113);--color-emerald-100:oklch(95% 0.052 163.051);--color-emerald-200:oklch(90.5% 0.093 164.15);--color-emerald-300:oklch(84.5% 0.143 164.978);--color-emerald-400:oklch(76.5% 0.177 163.223);--color-emerald-500:oklch(69.6% 0.17 162.48);--color-emerald-600:oklch(59.6% 0.145 163.225);--color-emerald-700:oklch(50.8% 0.118 165.612);--color-emerald-800:oklch(43.2% 0.095 166.913);--color-emerald-900:oklch(37.8% 0.077 168.94);--color-emerald-950:oklch(26.2% 0.051 172.552);--color-teal-50:oklch(98.4% 0.014 180.72);--color-teal-100:oklch(95.3% 0.051 180.801);--color-teal-200:oklch(91% 0.096 180.426);--color-teal-300:oklch(85.5% 0.138 181.071);--color-teal-400:oklch(77.7% 0.152 181.912);--color-teal-500:oklch(70.4% 0.14 182.503);--color-teal-600:oklch(60% 0.118 184.704);--color-teal-700:oklch(51.1% 0.096 186.391);--color-teal-800:oklch(43.7% 0.078 188.216);--color-teal-900:oklch(38.6% 0.063 188.416);--color-teal-950:oklch(27.7% 0.046 192.524);--color-cyan-50:oklch(98.4% 0.019 200.873);--color-cyan-100:oklch(95.6% 0.045 203.388);--color-cyan-200:oklch(91.7% 0.08 205.041);--color-cyan-300:oklch(86.5% 0.127 207.078);--color-cyan-400:oklch(78.9% 0.154 211.53);--color-cyan-500:oklch(71.5% 0.143 215.221);--color-cyan-600:oklch(60.9% 0.126 221.723);--color-cyan-700:oklch(52% 0.105 223.128);--color-cyan-800:oklch(45% 0.085 224.283);--color-cyan-900:oklch(39.8% 0.07 227.392);--color-cyan-950:oklch(30.2% 0.056 229.695);--color-sky-50:oklch(97.7% 0.013 236.62);--color-sky-100:oklch(95.1% 0.026 236.824);--color-sky-200:oklch(90.1% 0.058 230.902);--color-sky-300:oklch(82.8% 0.111 230.318);--color-sky-400:oklch(74.6% 0.16 232.661);--color-sky-500:oklch(68.5% 0.169 237.323);--color-sky-600:oklch(58.8% 0.158 241.966);--color-sky-700:oklch(50% 0.134 242.749);--color-sky-800:oklch(44.3% 0.11 240.79);--color-sky-900:oklch(39.1% 0.09 240.876);--color-sky-950:oklch(29.3% 0.066 243.157);--color-blue-50:oklch(97% 0.014 254.604);--color-blue-100:oklch(93.2% 0.032 255.585);--color-blue-200:oklch(88.2% 0.059 254.128);--color-blue-300:oklch(80.9% 0.105 251.813);--color-blue-400:oklch(70.7% 0.165 254.624);--color-blue-500:oklch(62.3% 0.214 259.815);--color-blue-600:oklch(54.6% 0.245 262.881);--color-blue-700:oklch(48.8% 0.243 264.376);--color-blue-800:oklch(42.4% 0.199 265.638);--color-blue-900:oklch(37.9% 0.146 265.522);--color-blue-950:oklch(28.2% 0.091 267.935);--color-indigo-50:oklch(96.2% 0.018 272.314);--color-indigo-100:oklch(93% 0.034 272.788);--color-indigo-200:oklch(87% 0.065 274.039);--color-indigo-300:oklch(78.5% 0.115 274.713);--color-indigo-400:oklch(67.3% 0.182 276.935);--color-indigo-500:oklch(58.5% 0.233 277.117);--color-indigo-600:oklch(51.1% 0.262 276.966);--color-indigo-700:oklch(45.7% 0.24 277.023);--color-indigo-800:oklch(39.8% 0.195 277.366);--color-indigo-900:oklch(35.9% 0.144 278.697);--color-indigo-950:oklch(25.7% 0.09 281.288);--color-violet-50:oklch(96.9% 0.016 293.756);--color-violet-100:oklch(94.3% 0.029 294.588);--color-violet-200:oklch(89.4% 0.057 293.283);--color-violet-300:oklch(81.1% 0.111 293.571);--color-violet-400:oklch(70.2% 0.183 293.541);--color-violet-500:oklch(60.6% 0.25 292.717);--color-violet-600:oklch(54.1% 0.281 293.009);--color-violet-700:oklch(49.1% 0.27 292.581);--color-violet-800:oklch(43.2% 0.232 292.759);--color-violet-900:oklch(38% 0.189 293.745);--color-violet-950:oklch(28.3% 0.141 291.089);--color-purple-50:oklch(97.7% 0.014 308.299);--color-purple-100:oklch(94.6% 0.033 307.174);--color-purple-200:oklch(90.2% 0.063 306.703);--color-purple-300:oklch(82.7% 0.119 306.383);--color-purple-400:oklch(71.4% 0.203 305.504);--color-purple-500:oklch(62.7% 0.265 303.9);--color-purple-600:oklch(55.8% 0.288 302.321);--color-purple-700:oklch(49.6% 0.265 301.924);--color-purple-800:oklch(43.8% 0.218 303.724);--color-purple-900:oklch(38.1% 0.176 304.987);--color-purple-950:oklch(29.1% 0.149 302.717);--color-fuchsia-50:oklch(97.7% 0.017 320.058);--color-fuchsia-100:oklch(95.2% 0.037 318.852);--color-fuchsia-200:oklch(90.3% 0.076 319.62);--color-fuchsia-300:oklch(83.3% 0.145 321.434);--color-fuchsia-400:oklch(74% 0.238 322.16);--color-fuchsia-500:oklch(66.7% 0.295 322.15);--color-fuchsia-600:oklch(59.1% 0.293 322.896);--color-fuchsia-700:oklch(51.8% 0.253 323.949);--color-fuchsia-800:oklch(45.2% 0.211 324.591);--color-fuchsia-900:oklch(40.1% 0.17 325.612);--color-fuchsia-950:oklch(29.3% 0.136 325.661);--color-pink-50:oklch(97.1% 0.014 343.198);--color-pink-100:oklch(94.8% 0.028 342.258);--color-pink-200:oklch(89.9% 0.061 343.231);--color-pink-300:oklch(82.3% 0.12 346.018);--color-pink-400:oklch(71.8% 0.202 349.761);--color-pink-500:oklch(65.6% 0.241 354.308);--color-pink-600:oklch(59.2% 0.249 0.584);--color-pink-700:oklch(52.5% 0.223 3.958);--color-pink-800:oklch(45.9% 0.187 3.815);--color-pink-900:oklch(40.8% 0.153 2.432);--color-pink-950:oklch(28.4% 0.109 3.907);--color-rose-50:oklch(96.9% 0.015 12.422);--color-rose-100:oklch(94.1% 0.03 12.58);--color-rose-200:oklch(89.2% 0.058 10.001);--color-rose-300:oklch(81% 0.117 11.638);--color-rose-400:oklch(71.2% 0.194 13.428);--color-rose-500:oklch(64.5% 0.246 16.439);--color-rose-600:oklch(58.6% 0.253 17.585);--color-rose-700:oklch(51.4% 0.222 16.935);--color-rose-800:oklch(45.5% 0.188 13.697);--color-rose-900:oklch(41% 0.159 10.272);--color-rose-950:oklch(27.1% 0.105 12.094);--color-slate-50:oklch(98.4% 0.003 247.858);--color-slate-100:oklch(96.8% 0.007 247.896);--color-slate-200:oklch(92.9% 0.013 255.508);--color-slate-300:oklch(86.9% 0.022 252.894);--color-slate-400:oklch(70.4% 0.04 256.788);--color-slate-500:oklch(55.4% 0.046 257.417);--color-slate-600:oklch(44.6% 0.043 257.281);--color-slate-700:oklch(37.2% 0.044 257.287);--color-slate-800:oklch(27.9% 0.041 260.031);--color-slate-900:oklch(20.8% 0.042 265.755);--color-slate-950:oklch(12.9% 0.042 264.695);--color-gray-50:oklch(98.5% 0.002 247.839);--color-gray-100:oklch(96.7% 0.003 264.542);--color-gray-200:oklch(92.8% 0.006 264.531);--color-gray-300:oklch(87.2% 0.01 258.338);--color-gray-400:oklch(70.7% 0.022 261.325);--color-gray-500:oklch(55.1% 0.027 264.364);--color-gray-600:oklch(44.6% 0.03 256.802);--color-gray-700:oklch(37.3% 0.034 259.733);--color-gray-800:oklch(27.8% 0.033 256.848);--color-gray-900:oklch(21% 0.034 264.665);--color-gray-950:oklch(13% 0.028 261.692);--color-zinc-50:oklch(98.5% 0 0);--color-zinc-100:oklch(96.7% 0.001 286.375);--color-zinc-200:oklch(92% 0.004 286.32);--color-zinc-300:oklch(87.1% 0.006 286.286);--color-zinc-400:oklch(70.5% 0.015 286.067);--color-zinc-500:oklch(55.2% 0.016 285.938);--color-zinc-600:oklch(44.2% 0.017 285.786);--color-zinc-700:oklch(37% 0.013 285.805);--color-zinc-800:oklch(27.4% 0.006 286.033);--color-zinc-900:oklch(21% 0.006 285.885);--color-zinc-950:oklch(14.1% 0.005 285.823);--color-neutral-50:oklch(98.5% 0 0);--color-neutral-100:oklch(97% 0 0);--color-neutral-200:oklch(92.2% 0 0);--color-neutral-300:oklch(87% 0 0);--color-neutral-400:oklch(70.8% 0 0);--color-neutral-500:oklch(55.6% 0 0);--color-neutral-600:oklch(43.9% 0 0);--color-neutral-700:oklch(37.1% 0 0);--color-neutral-800:oklch(26.9% 0 0);--color-neutral-900:oklch(20.5% 0 0);--color-neutral-950:oklch(14.5% 0 0);--color-stone-50:oklch(98.5% 0.001 106.423);--color-stone-100:oklch(97% 0.001 106.424);--color-stone-200:oklch(92.3% 0.003 48.717);--color-stone-300:oklch(86.9% 0.005 56.366);--color-stone-400:oklch(70.9% 0.01 56.259);--color-stone-500:oklch(55.3% 0.013 58.071);--color-stone-600:oklch(44.4% 0.011 73.639);--color-stone-700:oklch(37.4% 0.01 67.558);--color-stone-800:oklch(26.8% 0.007 34.298);--color-stone-900:oklch(21.6% 0.006 56.043);--color-stone-950:oklch(14.7% 0.004 49.25);--color-mauve-50:oklch(98.5% 0 0);--color-mauve-100:oklch(96% 0.003 325.6);--color-mauve-200:oklch(92.2% 0.005 325.62);--color-mauve-300:oklch(86.5% 0.012 325.68);--color-mauve-400:oklch(71.1% 0.019 323.02);--color-mauve-500:oklch(54.2% 0.034 322.5);--color-mauve-600:oklch(43.5% 0.029 321.78);--color-mauve-700:oklch(36.4% 0.029 323.89);--color-mauve-800:oklch(26.3% 0.024 320.12);--color-mauve-900:oklch(21.2% 0.019 322.12);--color-mauve-950:oklch(14.5% 0.008 326);--color-olive-50:oklch(98.8% 0.003 106.5);--color-olive-100:oklch(96.6% 0.005 106.5);--color-olive-200:oklch(93% 0.007 106.5);--color-olive-300:oklch(88% 0.011 106.6);--color-olive-400:oklch(73.7% 0.021 106.9);--color-olive-500:oklch(58% 0.031 107.3);--color-olive-600:oklch(46.6% 0.025 107.3);--color-olive-700:oklch(39.4% 0.023 107.4);--color-olive-800:oklch(28.6% 0.016 107.4);--color-olive-900:oklch(22.8% 0.013 107.4);--color-olive-950:oklch(15.3% 0.006 107.1);--color-mist-50:oklch(98.7% 0.002 197.1);--color-mist-100:oklch(96.3% 0.002 197.1);--color-mist-200:oklch(92.5% 0.005 214.3);--color-mist-300:oklch(87.2% 0.007 219.6);--color-mist-400:oklch(72.3% 0.014 214.4);--color-mist-500:oklch(56% 0.021 213.5);--color-mist-600:oklch(45% 0.017 213.2);--color-mist-700:oklch(37.8% 0.015 216);--color-mist-800:oklch(27.5% 0.011 216.9);--color-mist-900:oklch(21.8% 0.008 223.9);--color-mist-950:oklch(14.8% 0.004 228.8);--color-taupe-50:oklch(98.6% 0.002 67.8);--color-taupe-100:oklch(96% 0.002 17.2);--color-taupe-200:oklch(92.2% 0.005 34.3);--color-taupe-300:oklch(86.8% 0.007 39.5);--color-taupe-400:oklch(71.4% 0.014 41.2);--color-taupe-500:oklch(54.7% 0.021 43.1);--color-taupe-600:oklch(43.8% 0.017 39.3);--color-taupe-700:oklch(36.7% 0.016 35.7);--color-taupe-800:oklch(26.8% 0.011 36.5);--color-taupe-900:oklch(21.4% 0.009 43.1);--color-taupe-950:oklch(14.7% 0.004 49.3);--color-black:#000;--color-white:#fff;--spacing:0.25rem;--breakpoint-sm:40rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--breakpoint-2xl:96rem;--container-3xs:16rem;--container-2xs:18rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--text-xs:0.75rem;--text-xs--line-height:calc(1 / 0.75);--text-sm:0.875rem;--text-sm--line-height:calc(1.25 / 0.875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--text-7xl:4.5rem;--text-7xl--line-height:1;--text-8xl:6rem;--text-8xl--line-height:1;--text-9xl:8rem;--text-9xl--line-height:1;--font-weight-thin:100;--font-weight-extralight:200;--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-tighter:-0.05em;--tracking-tight:-0.025em;--tracking-normal:0em;--tracking-wide:0.025em;--tracking-wider:0.05em;--tracking-widest:0.1em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--leading-loose:2;--radius-xs:0.125rem;--radius-sm:0.25rem;--radius-md:0.375rem;--radius-lg:0.5rem;--radius-xl:0.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--radius-4xl:2rem;--shadow-2xs:0 1px rgb(0 0 0 / 0.05);--shadow-xs:0 1px 2px 0 rgb(0 0 0 / 0.05);--shadow-sm:0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);--shadow-md:0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);--shadow-lg:0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);--shadow-xl:0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);--shadow-2xl:0 25px 50px -12px rgb(0 0 0 / 0.25);--inset-shadow-2xs:inset 0 1px rgb(0 0 0 / 0.05);--inset-shadow-xs:inset 0 1px 1px rgb(0 0 0 / 0.05);--inset-shadow-sm:inset 0 2px 4px rgb(0 0 0 / 0.05);--drop-shadow-xs:0 1px 1px rgb(0 0 0 / 0.05);--drop-shadow-sm:0 1px 2px rgb(0 0 0 / 0.15);--drop-shadow-md:0 3px 3px rgb(0 0 0 / 0.12);--drop-shadow-lg:0 4px 4px rgb(0 0 0 / 0.15);--drop-shadow-xl:0 9px 7px rgb(0 0 0 / 0.1);--drop-shadow-2xl:0 25px 25px rgb(0 0 0 / 0.15);--text-shadow-2xs:0px 1px 0px rgb(0 0 0 / 0.15);--text-shadow-xs:0px 1px 1px rgb(0 0 0 / 0.2);--text-shadow-sm:0px 1px 0px rgb(0 0 0 / 0.075), 0px 1px 1px rgb(0 0 0 / 0.075), 0px 2px 2px rgb(0 0 0 / 0.075);--text-shadow-md:0px 1px 1px rgb(0 0 0 / 0.1), 0px 1px 2px rgb(0 0 0 / 0.1), 0px 2px 4px rgb(0 0 0 / 0.1);--text-shadow-lg:0px 1px 2px rgb(0 0 0 / 0.1), 0px 3px 2px rgb(0 0 0 / 0.1), 0px 4px 8px rgb(0 0 0 / 0.1);--ease-in:cubic-bezier(0.4, 0, 1, 1);--ease-out:cubic-bezier(0, 0, 0.2, 1);--ease-in-out:cubic-bezier(0.4, 0, 0.2, 1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;--animate-pulse:pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;--animate-bounce:bounce 1s infinite;--blur-xs:4px;--blur-sm:8px;--blur-md:12px;--blur-lg:16px;--blur-xl:24px;--blur-2xl:40px;--blur-3xl:64px;--perspective-dramatic:100px;--perspective-near:300px;--perspective-normal:500px;--perspective-midrange:800px;--perspective-distant:1200px;--aspect-video:16 / 9;--default-transition-duration:150ms;--default-transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);--default-font-family:var(--font-sans);--default-font-feature-settings:initial;--default-font-variation-settings:initial;--default-mono-font-family:var(--font-mono);--default-mono-font-feature-settings:initial;--default-mono-font-variation-settings:initial;--radius-default:0.375rem;--color-primary:#4f46e5;--color-primary-hover:#4338ca;--color-text-heading:#111827;--color-text-on-dark:#f9fafb;--color-text-on-dark-muted:#9ca3af}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,100%{transform:scale(2);opacity:0}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,100%{transform:translateY(-25%);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;animation-timing-function:cubic-bezier(0,0,.2,1)}}@layer base{*,::after,::before,::backdrop,::file-selector-button{box-sizing:border-box;margin:0;padding:0;border:0 solid}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;tab-size:4;font-family:var(--default-font-family);font-feature-settings:var(--default-font-feature-settings);font-variation-settings:var(--default-font-variation-settings);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family);font-feature-settings:var(--default-mono-font-feature-settings);font-variation-settings:var(--default-mono-font-variation-settings);font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea,::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;border-radius:0;background-color:initial;opacity:1}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports(not (-webkit-appearance:-apple-pay-button)) or (contain-intrinsic-size:1px){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]),::file-selector-button{appearance:button}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@font-face{font-family:lora;font-style:italic;font-weight:400 700;font-display:swap;src:url(/fonts/lora-latin-ext-italic.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:lora;font-style:italic;font-weight:400 700;font-display:swap;src:url(/fonts/lora-latin-italic.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:lora;font-style:normal;font-weight:400 700;font-display:swap;src:url(/fonts/lora-latin-ext-normal.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:lora;font-style:normal;font-weight:400 700;font-display:swap;src:url(/fonts/lora-latin-normal.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:0}.inset-y-0{inset-block:0}.top-0{top:0}.top-1\/2{top:calc(1/2 * 100%)}.top-20{top:calc(var(--spacing) * 20)}.top-4{top:calc(var(--spacing) * 4)}.top-full{top:100%}.-right-2{right:calc(var(--spacing) * -2)}.right-0{right:0}.right-0\.5{right:calc(var(--spacing) * .5)}.right-4{right:calc(var(--spacing) * 4)}.right-8{right:calc(var(--spacing) * 8)}.bottom-4{bottom:calc(var(--spacing) * 4)}.left-0{left:0}.left-1\/2{left:calc(1/2 * 100%)}.left-4{left:calc(var(--spacing) * 4)}.isolate{isolation:isolate}.-z-10{z-index:calc(10 * -1)}.z-10{z-index:10}.z-150{z-index:150}.z-200{z-index:200}.z-\[100\]{z-index:100}.z-\[110\]{z-index:110}.z-\[1\]{z-index:1}.z-\[200\]{z-index:200}.col-span-1{grid-column:span 1/span 1}.-m-1\.5{margin:calc(var(--spacing) * -1.5)}.m-0{margin:0}.-mx-1\.5{margin-inline:calc(var(--spacing) * -1.5)}.mx-3{margin-inline:calc(var(--spacing) * 3)}.mx-4{margin-inline:calc(var(--spacing) * 4)}.mx-\[0\.15rem\]{margin-inline:.15rem}.mx-auto{margin-inline:auto}.my-1{margin-block:var(--spacing)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-12{margin-top:calc(var(--spacing) * 12)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mr-2{margin-right:calc(var(--spacing) * 2)}.mr-5{margin-right:calc(var(--spacing) * 5)}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.ml-1{margin-left:var(--spacing)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-3{margin-left:calc(var(--spacing) * 3)}.ml-4{margin-left:calc(var(--spacing) * 4)}.ml-auto{margin-left:auto}.box-border{box-sizing:border-box}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.aspect-square{aspect-ratio:1/1}.h-0{height:0}.h-0\.5{height:calc(var(--spacing) * .5)}.h-1{height:var(--spacing)}.h-2{height:calc(var(--spacing) * 2)}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-72{height:calc(var(--spacing) * 72)}.h-8{height:calc(var(--spacing) * 8)}.h-\[30px\]{height:30px}.h-\[38px\]{height:38px}.h-dvh{height:100dvh}.h-full{height:100%}.h-screen{height:100vh}.max-h-60{max-height:calc(var(--spacing) * 60)}.max-h-64{max-height:calc(var(--spacing) * 64)}.max-h-96{max-height:calc(var(--spacing) * 96)}.max-h-\[34rem\]{max-height:34rem}.max-h-\[calc\(100dvh_-_5rem\)\]{max-height:calc(100dvh - 5rem)}.max-h-\[calc\(100vh_-_7rem\)\]{max-height:calc(100vh - 7rem)}.max-h-dvh{max-height:100dvh}.min-h-0{min-height:0}.min-h-9{min-height:calc(var(--spacing) * 9)}.min-h-\[6\.5rem\]{min-height:6.5rem}.min-h-screen{min-height:100vh}.w-0{width:0}.w-1{width:var(--spacing)}.w-10{width:calc(var(--spacing) * 10)}.w-2{width:calc(var(--spacing) * 2)}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-4{width:calc(var(--spacing) * 4)}.w-40{width:calc(var(--spacing) * 40)}.w-52{width:calc(var(--spacing) * 52)}.w-56{width:calc(var(--spacing) * 56)}.w-6{width:calc(var(--spacing) * 6)}.w-64{width:calc(var(--spacing) * 64)}.w-7{width:calc(var(--spacing) * 7)}.w-8{width:calc(var(--spacing) * 8)}.w-9{width:calc(var(--spacing) * 9)}.w-\[26rem\]{width:26rem}.w-auto{width:auto}.w-full{width:100%}.w-max{width:max-content}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-64{max-width:calc(var(--spacing) * 64)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-7xl{max-width:var(--container-7xl)}.max-w-80{max-width:calc(var(--spacing) * 80)}.max-w-\[100rem\]{max-width:100rem}.max-w-\[11rem\]{max-width:11rem}.max-w-\[14rem\]{max-width:14rem}.max-w-\[16rem\]{max-width:16rem}.max-w-\[20rem\]{max-width:20rem}.max-w-\[90rem\]{max-width:90rem}.max-w-full{max-width:100%}.max-w-md{max-width:var(--container-md)}.max-w-none{max-width:none}.max-w-screen{max-width:100vw}.max-w-sm{max-width:var(--container-sm)}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:0}.min-w-48{min-width:calc(var(--spacing) * 48)}.min-w-5{min-width:calc(var(--spacing) * 5)}.min-w-72{min-width:calc(var(--spacing) * 72)}.min-w-8{min-width:calc(var(--spacing) * 8)}.min-w-\[10rem\]{min-width:10rem}.min-w-\[16rem\]{min-width:16rem}.min-w-\[240px\]{min-width:240px}.min-w-\[7rem\]{min-width:7rem}.min-w-\[8rem\]{min-width:8rem}.min-w-\[9rem\]{min-width:9rem}.min-w-full{min-width:100%}.flex-1{flex:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.table-fixed{table-layout:fixed}.border-collapse{border-collapse:collapse}.-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-0\.5{--tw-translate-x:calc(var(--spacing) * 0.5);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-\[18px\]{--tw-translate-x:18px;translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x)var(--tw-translate-y)}.scale-110{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x)var(--tw-scale-y)}.scale-95{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.rotate-180{rotate:180deg}.rotate-90{rotate:90deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-col-resize{cursor:col-resize}.cursor-default{cursor:default}.cursor-grab{cursor:grab}.cursor-pointer{cursor:pointer}.resize{resize:both}.resize-none{resize:none}.list-none{list-style-type:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-nowrap{flex-wrap:nowrap}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-8{gap:calc(var(--spacing) * 8)}.gap-\[2px\]{gap:2px}.space-y-6{ :where(& > :not(:last-child)) { --tw-space-y-reverse: 0; margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse)); margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse))); }}.space-y-8{ :where(& > :not(:last-child)) { --tw-space-y-reverse: 0; margin-block-start: calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse)); margin-block-end: calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse))); }}.self-end{align-self:flex-end}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-y-hidden{overflow-y:hidden}.rounded{border-radius:.25rem}.rounded-default{border-radius:var(--radius-default)}.rounded-full{border-radius:calc(infinity * 1px)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-l-default{border-top-left-radius:var(--radius-default);border-bottom-left-radius:var(--radius-default)}.rounded-l-none{border-top-left-radius:0;border-bottom-left-radius:0}.rounded-b-default{border-bottom-right-radius:var(--radius-default);border-bottom-left-radius:var(--radius-default)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-4{border-style:var(--tw-border-style);border-width:4px}.border-x{border-inline-style:var(--tw-border-style);border-inline-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-t-2{border-top-style:var(--tw-border-style);border-top-width:2px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-r-0{border-right-style:var(--tw-border-style);border-right-width:0}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-none{--tw-border-style:none;border-style:none}.border-emerald-300{border-color:var(--color-emerald-300)}.border-green-200{border-color:var(--color-green-200)}.border-green-500{border-color:var(--color-green-500)}.border-neutral-100{border-color:var(--color-neutral-100)}.border-neutral-200{border-color:var(--color-neutral-200)}.border-neutral-300{border-color:var(--color-neutral-300)}.border-neutral-700{border-color:var(--color-neutral-700)}.border-neutral-800{border-color:var(--color-neutral-800)}.border-primary{border-color:var(--color-primary)}.border-red-200{border-color:var(--color-red-200)}.border-red-500{border-color:var(--color-red-500)}.border-sky-200{border-color:var(--color-sky-200)}.border-sky-700{border-color:var(--color-sky-700)}.border-transparent{border-color:transparent}.border-yellow-200{border-color:var(--color-yellow-200)}.border-t-sky-700{border-top-color:var(--color-sky-700)}.border-t-transparent{border-top-color:transparent}.border-l-green-700{border-left-color:var(--color-green-700)}.border-l-neutral-300{border-left-color:var(--color-neutral-300)}.border-l-neutral-400{border-left-color:var(--color-neutral-400)}.border-l-red-700{border-left-color:var(--color-red-700)}.border-l-sky-800{border-left-color:var(--color-sky-800)}.border-l-yellow-500{border-left-color:var(--color-yellow-500)}.\!bg-primary{background-color:var(--color-primary)!important}.bg-amber-100{background-color:var(--color-amber-100)}.bg-amber-100\!{background-color:var(--color-amber-100)!important}.bg-amber-700{background-color:var(--color-amber-700)}.bg-black\/10{background-color:color-mix(in oklab,var(--color-black) 10%,transparent)}.bg-black\/30{background-color:color-mix(in oklab,var(--color-black) 30%,transparent)}.bg-black\/5{background-color:color-mix(in oklab,var(--color-black) 5%,transparent)}.bg-black\/50{background-color:color-mix(in oklab,var(--color-black) 50%,transparent)}.bg-emerald-100{background-color:var(--color-emerald-100)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-700{background-color:var(--color-green-700)}.bg-green-900{background-color:var(--color-green-900)}.bg-neutral-100{background-color:var(--color-neutral-100)}.bg-neutral-200{background-color:var(--color-neutral-200)}.bg-neutral-300{background-color:var(--color-neutral-300)}.bg-neutral-50{background-color:var(--color-neutral-50)}.bg-neutral-500{background-color:var(--color-neutral-500)}.bg-neutral-600{background-color:var(--color-neutral-600)}.bg-neutral-700{background-color:var(--color-neutral-700)}.bg-neutral-800{background-color:var(--color-neutral-800)}.bg-neutral-900{background-color:var(--color-neutral-900)}.bg-orange-500{background-color:var(--color-orange-500)}.bg-orange-600{background-color:var(--color-orange-600)}.bg-primary{background-color:var(--color-primary)}.bg-primary\/10{background-color:color-mix(in oklab,var(--color-primary) 10%,transparent)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-600{background-color:var(--color-red-600)}.bg-red-700{background-color:var(--color-red-700)}.bg-red-900{background-color:var(--color-red-900)}.bg-sky-50{background-color:var(--color-sky-50)}.bg-sky-600{background-color:var(--color-sky-600)}.bg-sky-700{background-color:var(--color-sky-700)}.bg-sky-800{background-color:var(--color-sky-800)}.bg-sky-900{background-color:var(--color-sky-900)}.bg-transparent{background-color:initial}.bg-white{background-color:var(--color-white)}.bg-white\/10{background-color:color-mix(in oklab,var(--color-white) 10%,transparent)}.bg-yellow-400{background-color:var(--color-yellow-400)}.bg-yellow-50{background-color:var(--color-yellow-50)}.bg-yellow-700{background-color:var(--color-yellow-700)}.fill-current{fill:currentcolor}.p-0{padding:0}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:var(--spacing)}.p-1\.5{padding:calc(var(--spacing) * 1.5)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-8{padding:calc(var(--spacing) * 8)}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-7{padding-inline:calc(var(--spacing) * 7)}.px-8{padding-inline:calc(var(--spacing) * 8)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-10{padding-block:calc(var(--spacing) * 10)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-px{padding-block:1px}.pt-1{padding-top:var(--spacing)}.pt-1\.5{padding-top:calc(var(--spacing) * 1.5)}.pt-10{padding-top:calc(var(--spacing) * 10)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pr-14{padding-right:calc(var(--spacing) * 14)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pr-9{padding-right:calc(var(--spacing) * 9)}.pb-0\.5{padding-bottom:calc(var(--spacing) * .5)}.pb-1{padding-bottom:var(--spacing)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pb-\[calc\(1rem_\+_env\(safe-area-inset-bottom\,0px\)\)\]{padding-bottom:calc(1rem + env(safe-area-inset-bottom,0px))}.pl-4{padding-left:calc(var(--spacing) * 4)}.pl-8{padding-left:calc(var(--spacing) * 8)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-\[inherit\]{font-family:inherit}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.5rem\]{font-size:.5rem}.text-\[10px\]{font-size:10px}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.\!text-white{color:var(--color-white)!important}.text-amber-600{color:var(--color-amber-600)}.text-black{color:var(--color-black)}.text-current{color:currentcolor}.text-emerald-700{color:var(--color-emerald-700)}.text-gray-900{color:var(--color-gray-900)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-900{color:var(--color-green-900)}.text-neutral-100{color:var(--color-neutral-100)}.text-neutral-200{color:var(--color-neutral-200)}.text-neutral-300{color:var(--color-neutral-300)}.text-neutral-400{color:var(--color-neutral-400)}.text-neutral-500{color:var(--color-neutral-500)}.text-neutral-600{color:var(--color-neutral-600)}.text-neutral-700{color:var(--color-neutral-700)}.text-neutral-800{color:var(--color-neutral-800)}.text-neutral-900{color:var(--color-neutral-900)}.text-orange-600{color:var(--color-orange-600)}.text-primary{color:var(--color-primary)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-900{color:var(--color-red-900)}.text-sky-600{color:var(--color-sky-600)}.text-sky-700{color:var(--color-sky-700)}.text-sky-900{color:var(--color-sky-900)}.text-text-heading{color:var(--color-text-heading)}.text-text-on-dark{color:var(--color-text-on-dark)}.text-text-on-dark-muted{color:var(--color-text-on-dark-muted)}.text-transparent{color:transparent}.text-violet-600{color:var(--color-violet-600)}.text-white{color:var(--color-white)}.text-yellow-600{color:var(--color-yellow-600)}.text-yellow-700{color:var(--color-yellow-700)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.no-underline{text-decoration-line:none}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.caret-neutral-800{caret-color:var(--color-neutral-800)}.opacity-0{opacity:0%}.opacity-50{opacity:50%}.opacity-60{opacity:60%}.shadow-\[0_4px_12px_rgba\(0\,0\,0\,0\.15\)\]{--tw-shadow:0 4px 12px var(--tw-shadow-color, rgba(0,0,0,0.15));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[inset_0_0_0_1px_currentColor\]{--tw-shadow:inset 0 0 0 1px var(--tw-shadow-color, currentColor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[inset_3px_0_0_0_theme\(colors\.amber\.500\)\]{--tw-shadow:inset 3px 0 0 0 var(--tw-shadow-color, theme(colors.amber.500));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline-hidden{--tw-outline-style:none;outline-style:none;@media(forced-colors:active){outline: 2px solid transparent; outline-offset: 2px;}}.outline-2{outline-style:var(--tw-outline-style);outline-width:2px}.outline-sky-500{outline-color:var(--color-sky-500)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[color\,border-color\]{transition-property:color,border-color;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[opacity\,transform\]{transition-property:opacity,transform;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[width\]{transition-property:width;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:150ms;transition-duration:150ms}.duration-200{--tw-duration:200ms;transition-duration:200ms}.duration-300{--tw-duration:300ms;transition-duration:300ms}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.group-hover\/th\:opacity-50{&:is(:where(.group\/th):hover *){@media(hover:hover){opacity: 50%;}}}.file\:mr-2{&::file-selector-button{margin-right:calc(var(--spacing) * 2)}}.file\:ml-1{&::file-selector-button{margin-left:var(--spacing)}}.file\:cursor-pointer{&::file-selector-button{cursor:pointer}}.file\:rounded-default{&::file-selector-button{border-radius:var(--radius-default)}}.file\:border{&::file-selector-button{border-style:var(--tw-border-style);border-width:1px}}.file\:border-neutral-300{&::file-selector-button{border-color:var(--color-neutral-300)}}.file\:bg-neutral-100{&::file-selector-button{background-color:var(--color-neutral-100)}}.file\:px-3{&::file-selector-button{padding-inline:calc(var(--spacing) * 3)}}.file\:px-4{&::file-selector-button{padding-inline:calc(var(--spacing) * 4)}}.file\:py-\[2px\]{&::file-selector-button{padding-block:2px}}.file\:py-\[3px\]{&::file-selector-button{padding-block:3px}}.file\:text-sm{&::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}.file\:shadow-xs{&::file-selector-button{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.placeholder\:text-neutral-400{&::placeholder{color:var(--color-neutral-400)}}.before\:absolute{&::before{content:var(--tw-content);position:absolute}}.before\:inset-0{&::before{content:var(--tw-content);inset:0}}.before\:-z-20{&::before{content:var(--tw-content);z-index:calc(20 * -1)}}.before\:bg-neutral-300{&::before{content:var(--tw-content);background-color:var(--color-neutral-300)}}.before\:content-\[\'\'\]{&::before{--tw-content:'';content:var(--tw-content)}}.before\:\[clip-path\:polygon\(16px_0\,100\%_0\,100\%_calc\(100\%_-_16px\)\,calc\(100\%_-_16px\)_100\%\,0_100\%\,0_16px\)\]{&::before{content:var(--tw-content);clip-path:polygon(16px 0,100% 0,100% calc(100% - 16px),calc(100% - 16px) 100%,0 100%,0 16px)}}.after\:absolute{&::after{content:var(--tw-content);position:absolute}}.after\:inset-\[1px\]{&::after{content:var(--tw-content);inset:1px}}.after\:-z-10{&::after{content:var(--tw-content);z-index:calc(10 * -1)}}.after\:bg-white{&::after{content:var(--tw-content);background-color:var(--color-white)}}.after\:content-\[\'\'\]{&::after{--tw-content:'';content:var(--tw-content)}}.after\:\[clip-path\:polygon\(15px_0\,100\%_0\,100\%_calc\(100\%_-_15px\)\,calc\(100\%_-_15px\)_100\%\,0_100\%\,0_15px\)\]{&::after{content:var(--tw-content);clip-path:polygon(15px 0,100% 0,100% calc(100% - 15px),calc(100% - 15px) 100%,0 100%,0 15px)}}.last\:border-r-0{&:last-child{border-right-style:var(--tw-border-style);border-right-width:0}}.last\:border-b-0{&:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}}.odd\:bg-white{&:nth-child(odd){background-color:var(--color-white)}}.even\:bg-neutral-100{&:nth-child(even){background-color:var(--color-neutral-100)}}.hover\:bg-green-100{&:hover{@media(hover:hover){background-color: var(--color-green-100);}}}.hover\:bg-green-800{&:hover{@media(hover:hover){background-color: var(--color-green-800);}}}.hover\:bg-green-950{&:hover{@media(hover:hover){background-color: var(--color-green-950);}}}.hover\:bg-neutral-100{&:hover{@media(hover:hover){background-color: var(--color-neutral-100);}}}.hover\:bg-neutral-200{&:hover{@media(hover:hover){background-color: var(--color-neutral-200);}}}.hover\:bg-neutral-300{&:hover{@media(hover:hover){background-color: var(--color-neutral-300);}}}.hover\:bg-neutral-50{&:hover{@media(hover:hover){background-color: var(--color-neutral-50);}}}.hover\:bg-neutral-500{&:hover{@media(hover:hover){background-color: var(--color-neutral-500);}}}.hover\:bg-neutral-800{&:hover{@media(hover:hover){background-color: var(--color-neutral-800);}}}.hover\:bg-orange-700{&:hover{@media(hover:hover){background-color: var(--color-orange-700);}}}.hover\:bg-primary-hover{&:hover{@media(hover:hover){background-color: var(--color-primary-hover);}}}.hover\:bg-red-700{&:hover{@media(hover:hover){background-color: var(--color-red-700);}}}.hover\:bg-red-800{&:hover{@media(hover:hover){background-color: var(--color-red-800);}}}.hover\:bg-red-950{&:hover{@media(hover:hover){background-color: var(--color-red-950);}}}.hover\:bg-sky-100{&:hover{@media(hover:hover){background-color: var(--color-sky-100);}}}.hover\:bg-sky-500{&:hover{@media(hover:hover){background-color: var(--color-sky-500);}}}.hover\:bg-sky-500\/50{&:hover{@media(hover:hover){background-color: color-mix(in oklab,var(--color-sky-500) 50%,transparent);}}}.hover\:bg-sky-800{&:hover{@media(hover:hover){background-color: var(--color-sky-800);}}}.hover\:bg-sky-950{&:hover{@media(hover:hover){background-color: var(--color-sky-950);}}}.hover\:bg-white\/5{&:hover{@media(hover:hover){background-color: color-mix(in oklab,var(--color-white) 5%,transparent);}}}.hover\:bg-yellow-800{&:hover{@media(hover:hover){background-color: var(--color-yellow-800);}}}.hover\:text-neutral-600{&:hover{@media(hover:hover){color: var(--color-neutral-600);}}}.hover\:text-neutral-700{&:hover{@media(hover:hover){color: var(--color-neutral-700);}}}.hover\:text-neutral-800{&:hover{@media(hover:hover){color: var(--color-neutral-800);}}}.hover\:text-neutral-900{&:hover{@media(hover:hover){color: var(--color-neutral-900);}}}.hover\:text-sky-800{&:hover{@media(hover:hover){color: var(--color-sky-800);}}}.hover\:text-text-on-dark{&:hover{@media(hover:hover){color: var(--color-text-on-dark);}}}.hover\:text-white{&:hover{@media(hover:hover){color: var(--color-white);}}}.hover\:underline{&:hover{@media(hover:hover){text-decoration-line: underline;}}}.hover\:decoration-1{&:hover{@media(hover:hover){text-decoration-thickness: 1px;}}}.hover\:shadow-\[inset_0_0_0_2px_currentColor\]{&:hover{@media(hover:hover){--tw-shadow: inset 0 0 0 2px var(--tw-shadow-color,currentColor); box-shadow: var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);}}}.file\:hover\:bg-neutral-200{&::file-selector-button{&:hover{@media(hover:hover){background-color: var(--color-neutral-200);}}}}.focus\:border-primary{&:focus{border-color:var(--color-primary)}}.focus\:border-sky-500{&:focus{border-color:var(--color-sky-500)}}.focus\:bg-neutral-100{&:focus{background-color:var(--color-neutral-100)}}.focus\:bg-red-50{&:focus{background-color:var(--color-red-50)}}.focus\:shadow-\[inset_0_0_0_2px_var\(--color-red-500\)\]{&:focus{--tw-shadow:inset 0 0 0 2px var(--tw-shadow-color, var(--color-red-500));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.focus\:outline-hidden{&:focus{--tw-outline-style:none;outline-style:none;@media(forced-colors:active){outline: 2px solid transparent; outline-offset: 2px;}}}.focus\:outline-2{&:focus{outline-style:var(--tw-outline-style);outline-width:2px}}.focus\:outline-offset-1{&:focus{outline-offset:1px}}.focus\:outline-green-500{&:focus{outline-color:var(--color-green-500)}}.focus\:outline-red-500{&:focus{outline-color:var(--color-red-500)}}.focus\:outline-sky-500{&:focus{outline-color:var(--color-sky-500)}}.focus-visible\:outline-2{&:focus-visible{outline-style:var(--tw-outline-style);outline-width:2px}}.focus-visible\:outline-offset-2{&:focus-visible{outline-offset:2px}}.focus-visible\:outline-current{&:focus-visible{outline-color:currentcolor}}.focus-visible\:outline-primary{&:focus-visible{outline-color:var(--color-primary)}}.active\:bg-neutral-200{&:active{background-color:var(--color-neutral-200)}}.enabled\:hover\:bg-neutral-50{&:enabled{&:hover{@media(hover:hover){background-color: var(--color-neutral-50);}}}}.enabled\:hover\:bg-neutral-900{&:enabled{&:hover{@media(hover:hover){background-color: var(--color-neutral-900);}}}}.enabled\:hover\:bg-red-700{&:enabled{&:hover{@media(hover:hover){background-color: var(--color-red-700);}}}}.disabled\:cursor-not-allowed{&:disabled{cursor:not-allowed}}.disabled\:bg-neutral-100{&:disabled{background-color:var(--color-neutral-100)}}.disabled\:bg-neutral-50{&:disabled{background-color:var(--color-neutral-50)}}.disabled\:text-neutral-300{&:disabled{color:var(--color-neutral-300)}}.disabled\:text-neutral-400{&:disabled{color:var(--color-neutral-400)}}.disabled\:opacity-40{&:disabled{opacity:40%}}.disabled\:opacity-50{&:disabled{opacity:50%}}.disabled\:hover\:bg-transparent{&:disabled{&:hover{@media(hover:hover){background-color: transparent;}}}}.sm\:block{@media(width >= 40rem){display: block;}}.sm\:flex{@media(width >= 40rem){display: flex;}}.sm\:hidden{@media(width >= 40rem){display: none;}}.sm\:w-48{@media(width >= 40rem){width: calc(var(--spacing) * 48);}}.sm\:w-64{@media(width >= 40rem){width: calc(var(--spacing) * 64);}}.sm\:grow-0{@media(width >= 40rem){flex-grow: 0;}}.sm\:grid-cols-2{@media(width >= 40rem){grid-template-columns: repeat(2,minmax(0,1fr));}}.sm\:grid-cols-3{@media(width >= 40rem){grid-template-columns: repeat(3,minmax(0,1fr));}}.sm\:flex-row{@media(width >= 40rem){flex-direction: row;}}.md\:flex-initial{@media(width >= 48rem){flex: 0 auto;}}.md\:justify-start{@media(width >= 48rem){justify-content: flex-start;}}.lg\:col-span-10{@media(width >= 64rem){grid-column: span 10 / span 10;}}.lg\:col-span-2{@media(width >= 64rem){grid-column: span 2 / span 2;}}.lg\:col-span-5{@media(width >= 64rem){grid-column: span 5 / span 5;}}.lg\:col-span-7{@media(width >= 64rem){grid-column: span 7 / span 7;}}.lg\:block{@media(width >= 64rem){display: block;}}.lg\:h-full{@media(width >= 64rem){height: 100%;}}.lg\:grid-cols-12{@media(width >= 64rem){grid-template-columns: repeat(12,minmax(0,1fr));}}.lg\:self-start{@media(width >= 64rem){align-self: flex-start;}}.lg\:pr-8{@media(width >= 64rem){padding-right: calc(var(--spacing) * 8);}}.\[\&_\.ui-form\]\:m-0{& .ui-form{margin:0}}.\[\&_input\]\:cursor-text{& input{cursor:text}}.\[\&_td\]\:p-3{& td{padding:calc(var(--spacing) * 3)}}.\[\&_td\]\:p-4{& td{padding:calc(var(--spacing) * 4)}}.\[\&_td\]\:px-2{& td{padding-inline:calc(var(--spacing) * 2)}}.\[\&_td\]\:py-0\.5{& td{padding-block:calc(var(--spacing) * .5)}}.\[\&_td\]\:py-1{& td{padding-block:var(--spacing)}}.\[\&_td\+td\]\:border-l{& td+td{border-left-style:var(--tw-border-style);border-left-width:1px}}.\[\&_td\+td\]\:border-neutral-300{& td+td{border-color:var(--color-neutral-300)}}.\[\&_th\]\:border-b{& th{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}}.\[\&_th\]\:border-neutral-300{& th{border-color:var(--color-neutral-300)}}.\[\&_tr\:hover\]\:\!bg-green-100{& tr:hover{background-color:var(--color-green-100)!important}}.\[\&_tr\:hover\]\:\!bg-neutral-200{& tr:hover{background-color:var(--color-neutral-200)!important}}.\[\&_tr\:hover\]\:\!bg-sky-100{& tr:hover{background-color:var(--color-sky-100)!important}}.\[\&_tr\:not\(\:last-child\)\]\:border-b{& tr:not(:last-child){border-bottom-style:var(--tw-border-style);border-bottom-width:1px}}.\[\&_tr\:not\(\:last-child\)\]\:border-neutral-300{& tr:not(:last-child){border-color:var(--color-neutral-300)}}.\[\&_tr\:nth-child\(even\)\]\:bg-neutral-100{& tr:nth-child(even){background-color:var(--color-neutral-100)}}}@property --tw-translate-x{syntax: "*"; inherits: false; initial-value: 0; }@property --tw-translate-y{syntax: "*"; diff --git a/go/vdom/vnode.go b/go/vdom/vnode.go index c891a49b..e8b56091 100644 --- a/go/vdom/vnode.go +++ b/go/vdom/vnode.go @@ -7,6 +7,7 @@ package vdom import ( "html" "sort" + "strconv" "strings" ) @@ -100,6 +101,41 @@ func (p propMod) apply(n *VNode) { n.Props[p.k] = p.v } // Prop sets a live DOM property (e.g. an input's value). func Prop(k, v string) Mod { return propMod{k, v} } +// BoolProp sets a boolean DOM property: checked, disabled, open, and the rest of +// boolProps below. +// +// Use this rather than Prop for those — a boolean written as a string is wrong in BOTH +// directions, and wrong in the same direction both times, which is what makes it such a +// good hiding place for a bug. In HTML a boolean attribute is presence-based, so +// `checked="false"` renders a TICKED box. In JS every non-empty string is truthy, so +// `el.checked = "false"` also ticks it. A checkbox bound with Prop is therefore ticked +// forever, and looks fine until you try to untick it. +func BoolProp(k string, on bool) Mod { return propMod{k, strconv.FormatBool(on)} } + +// boolProps are the DOM properties whose type is boolean, so both the reconciler and +// SSR can special-case them (see BoolProp for why they must). +// +// This is a name table because the DOM is a name table: there is no way to ask, of a +// VNode alone, whether `checked` on this tag is a boolean. Every framework carries the +// same list. +var boolProps = map[string]bool{ + "checked": true, + "disabled": true, + "readOnly": true, + "required": true, + "selected": true, + "multiple": true, + "hidden": true, + "open": true, + "autofocus": true, + "indeterminate": true, + "defaultChecked": true, +} + +// IsBoolProp reports whether a prop name is a boolean DOM property. The reconciler +// needs it to write a real bool instead of a string. +func IsBoolProp(k string) bool { return boolProps[k] } + type htmlMod struct{ html string } func (h htmlMod) apply(n *VNode) { n.HTML = h.html } @@ -147,7 +183,7 @@ func writeNode(b *strings.Builder, n *VNode) { b.WriteByte('<') b.WriteString(n.Tag) writeAttrs(b, n.Attrs) - writeAttrs(b, n.Props) // props like input value show up as attributes in SSR + writeProps(b, n.Props) // props like an input's value show up as attributes in SSR b.WriteByte('>') if voidTags[n.Tag] { return @@ -165,16 +201,44 @@ func writeNode(b *strings.Builder, n *VNode) { } func writeAttrs(b *strings.Builder, m map[string]string) { + for _, k := range sortedKeys(m) { + writeAttr(b, k, m[k]) + } +} + +// writeProps serializes live DOM properties as HTML attributes for SSR, so the +// server's markup shows what the client's props will hold. +// +// A boolean prop is emitted as a BARE attribute when true and omitted entirely when +// false — that is what the HTML boolean-attribute rule means. Writing checked="false" +// would render a ticked checkbox, which is the exact opposite of what was asked for. +func writeProps(b *strings.Builder, m map[string]string) { + for _, k := range sortedKeys(m) { + v := m[k] + if boolProps[k] { + if v == "true" { + b.WriteByte(' ') + b.WriteString(strings.ToLower(k)) + } + continue + } + writeAttr(b, k, v) + } +} + +func writeAttr(b *strings.Builder, k, v string) { + b.WriteByte(' ') + b.WriteString(k) + b.WriteString(`="`) + b.WriteString(html.EscapeString(v)) + b.WriteByte('"') +} + +func sortedKeys(m map[string]string) []string { keys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) } sort.Strings(keys) - for _, k := range keys { - b.WriteByte(' ') - b.WriteString(k) - b.WriteString(`="`) - b.WriteString(html.EscapeString(m[k])) - b.WriteByte('"') - } + return keys } diff --git a/go/vdom/vnode_test.go b/go/vdom/vnode_test.go new file mode 100644 index 00000000..247395c0 --- /dev/null +++ b/go/vdom/vnode_test.go @@ -0,0 +1,46 @@ +package vdom + +import ( + "strings" + "testing" +) + +// A boolean DOM property is a trap in HTML: the attribute's PRESENCE is what means +// true. checked="false" is a ticked checkbox — the value is not even read. So a false +// boolean prop must be omitted entirely, and a true one written bare. +func TestBoolPropSSR(t *testing.T) { + ticked := RenderHTML(Input(Attr("type", "checkbox"), BoolProp("checked", true))) + if !strings.Contains(ticked, " checked") { + t.Errorf("a checked box did not render the attribute: %s", ticked) + } + if strings.Contains(ticked, `checked="`) { + t.Errorf("a boolean attribute must be bare, not valued: %s", ticked) + } + + unticked := RenderHTML(Input(Attr("type", "checkbox"), BoolProp("checked", false))) + if strings.Contains(unticked, "checked") { + t.Errorf(`an unchecked box must omit the attribute entirely — checked="false" renders as TICKED: %s`, unticked) + } +} + +// Non-boolean props keep their values: an input's value is a string, and dropping it +// when empty would be just as wrong as writing checked="false". +func TestValuePropSSRKeepsItsValue(t *testing.T) { + got := RenderHTML(Input(Prop("value", "false"))) + if !strings.Contains(got, `value="false"`) { + t.Errorf(`value="false" is a string, not a boolean, and must survive: %s`, got) + } +} + +func TestIsBoolProp(t *testing.T) { + for _, k := range []string{"checked", "disabled", "selected", "open"} { + if !IsBoolProp(k) { + t.Errorf("%q should be known as a boolean property — the reconciler writes it as a string otherwise", k) + } + } + for _, k := range []string{"value", "className", "id"} { + if IsBoolProp(k) { + t.Errorf("%q is not a boolean property", k) + } + } +} diff --git a/go/wasmruntime/host_native.go b/go/wasmruntime/host_native.go index d11a58c9..5c0685df 100644 --- a/go/wasmruntime/host_native.go +++ b/go/wasmruntime/host_native.go @@ -30,6 +30,11 @@ func ScrollIntoView(*vdom.Ref, bool, string) {} func ScrollLeft(*vdom.Ref) float64 { return 0 } func SetScrollLeft(*vdom.Ref, float64) {} +// OverflowsX is false on the server: with no layout, nothing can overflow. A +// component that collapses overflowing content therefore SSRs its uncollapsed form, +// and the client collapses it on the first commit — before paint, so it is not seen. +func OverflowsX(*vdom.Ref) bool { return false } + func Contains(*vdom.Ref, any) bool { return false } func ClosestAttr(any, string, string) (string, bool) { return "", false } func QuerySelector(string) *vdom.Ref { return vdom.NewRef() } diff --git a/go/wasmruntime/host_wasm.go b/go/wasmruntime/host_wasm.go index d7a66a09..76e7297a 100644 --- a/go/wasmruntime/host_wasm.go +++ b/go/wasmruntime/host_wasm.go @@ -208,6 +208,30 @@ func SetScrollLeft(r *vdom.Ref, x float64) { } } +// OverflowsX reports whether an element's content is wider than the box it is +// clipped to — i.e. something is hidden. +// +// It compares scrollWidth (the full content) against clientWidth (the visible content +// box). Measure/getBoundingClientRect is the WRONG comparison here: it reports the +// element's own border box, which is by definition the size it was clipped to, so it +// can never reveal an overflow. +// +// A detached or display:none element has no layout and reports 0/0; that is not an +// overflow, so it answers false rather than a misleading true. +func OverflowsX(r *vdom.Ref) bool { + n, ok := node(r) + if !ok { + return false + } + client := n.Get("clientWidth").Float() + if client == 0 { + return false + } + // Sub-pixel layout means scrollWidth can exceed clientWidth by a hair on content + // that visually fits. Round up to whole pixels before believing it. + return n.Get("scrollWidth").Float() > client+1 +} + // ---- hit testing (outside-click) ---- // Contains reports whether target lies inside r's subtree. target is an diff --git a/go/wasmruntime/props_test.go b/go/wasmruntime/props_test.go new file mode 100644 index 00000000..a9d37284 --- /dev/null +++ b/go/wasmruntime/props_test.go @@ -0,0 +1,120 @@ +//go:build js && wasm + +package wasmruntime + +import ( + "syscall/js" + "testing" + + "kjol/vdom" +) + +// Run from kjol/go: +// +// GOOS=js GOARCH=wasm go test -exec="node testdata/domexec.js" ./wasmruntime + +// Props travel through the vdom as strings, but `checked` is a BOOLEAN property, and in +// JavaScript every non-empty string is truthy — so `el.checked = "false"` ticks the box. +// A multi-select rendered that way shows every option as selected, forever. +// +// The reconciler must write a real boolean. +func TestBoolPropIsWrittenAsABoolean(t *testing.T) { + root := document.Call("createElement", "div") + box := vdom.NewRef() + + unticked := vdom.Input(vdom.WithRef(box), vdom.BoolProp("checked", false)) + patchChildren(root, nil, one(unticked)) + + el, _ := box.Node().(js.Value) + if got := el.Get("checked"); got.Type() != js.TypeBoolean { + t.Fatalf(`checked is a %v, not a boolean — the string "false" is truthy in JS`, got.Type()) + } + if el.Get("checked").Bool() { + t.Error("BoolProp(false) produced a TICKED checkbox") + } +} + +// ...and it must keep tracking the signal. A tick that renders once and then never +// changes is the same bug wearing a different hat: the row would look right until you +// clicked it. +func TestBoolPropUpdatesOnRerender(t *testing.T) { + root := document.Call("createElement", "div") + box := vdom.NewRef() + + off := vdom.Input(vdom.WithRef(box), vdom.BoolProp("checked", false)) + patchChildren(root, nil, one(off)) + el, _ := box.Node().(js.Value) + + on := vdom.Input(vdom.WithRef(box), vdom.BoolProp("checked", true)) + patchChildren(root, one(off), one(on)) + if !el.Get("checked").Bool() { + t.Fatal("selecting an option did not tick its checkbox") + } + + back := vdom.Input(vdom.WithRef(box), vdom.BoolProp("checked", false)) + patchChildren(root, one(on), one(back)) + if el.Get("checked").Bool() { + t.Error("deselecting an option did not UNTICK its checkbox") + } +} + +// A string prop is still a string — the boolean handling must not swallow an input's +// value, least of all the literal value "false". +func TestStringPropIsUntouched(t *testing.T) { + root := document.Call("createElement", "div") + r := vdom.NewRef() + + patchChildren(root, nil, one(vdom.Input(vdom.WithRef(r), vdom.Prop("value", "false")))) + el, _ := r.Node().(js.Value) + if got := el.Get("value").String(); got != "false" { + t.Errorf("value = %q, want the string \"false\"", got) + } +} + +// OverflowsX is how the multi-select decides its pills no longer fit. It compares +// scrollWidth (all the content) against clientWidth (what is visible) — NOT +// getBoundingClientRect, which reports the clipped box and so can never reveal an +// overflow. +func TestOverflowsX(t *testing.T) { + root := document.Call("createElement", "div") + + cases := []struct { + name string + client, scroll, want float64 + overflows bool + }{ + {name: "content fits exactly", client: 200, scroll: 200}, + {name: "content is wider than the box", client: 200, scroll: 340, overflows: true}, + // Sub-pixel layout puts scrollWidth a hair over clientWidth on content that + // visually fits; believing that would collapse a field showing two pills. + {name: "sub-pixel noise is not an overflow", client: 200, scroll: 200.6}, + // A hidden element has no layout at all. That is not an overflow — and reading it + // as one is exactly how a collapsed field would get stuck collapsed. + {name: "display:none reports nothing", client: 0, scroll: 0}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + r := vdom.NewRef() + n := vdom.Div(vdom.WithRef(r)) + patchChildren(root, nil, one(n)) + + el, _ := r.Node().(js.Value) + el.Set("clientWidth", tc.client) + el.Set("scrollWidth", tc.scroll) + + if got := OverflowsX(r); got != tc.overflows { + t.Errorf("OverflowsX(client=%v scroll=%v) = %v, want %v", tc.client, tc.scroll, got, tc.overflows) + } + patchChildren(root, one(n), nil) + }) + } +} + +// An unmounted ref measures nothing rather than panicking — components call the host +// API unconditionally, including on the render before their element exists. +func TestOverflowsXOnUnmountedRef(t *testing.T) { + if OverflowsX(vdom.NewRef()) { + t.Error("an unmounted ref should not report an overflow") + } +} diff --git a/go/wasmruntime/reconcile.go b/go/wasmruntime/reconcile.go index b3633c95..7e103961 100644 --- a/go/wasmruntime/reconcile.go +++ b/go/wasmruntime/reconcile.go @@ -163,7 +163,7 @@ func createDOM(n *vdom.VNode, ns string) js.Value { el.Call("setAttribute", k, v) } for k, v := range n.Props { - el.Set(k, v) + setProp(el, k, v) } for name, h := range n.Events { addListener(n, name, h) @@ -316,12 +316,33 @@ func updateAttrs(o, x *vdom.VNode) { func updateProps(o, x *vdom.VNode) { dom := rt(x).dom for k, v := range x.Props { - if o.Props[k] != v && dom.Get(k).String() != v { - dom.Set(k, v) + if o.Props[k] != v && !propEquals(dom, k, v) { + setProp(dom, k, v) } } } +// setProp writes a live DOM property. Props travel as strings, but the property they +// land on may be a boolean — and JS reads the string "false" as TRUE, so writing +// el.checked = "false" ticks the box. Boolean props are converted before the write. +func setProp(el js.Value, k, v string) { + if vdom.IsBoolProp(k) { + el.Set(k, v == "true") + return + } + el.Set(k, v) +} + +// propEquals compares against what the DOM currently holds, in the property's own +// type. It is what lets a re-render skip touching an the user is typing in. +func propEquals(el js.Value, k, v string) bool { + cur := el.Get(k) + if vdom.IsBoolProp(k) { + return cur.Truthy() == (v == "true") + } + return cur.String() == v +} + func updateEvents(o, x *vdom.VNode) { r := rt(x) // same nodeRT as o (adopted above) for name, fn := range r.jsFuncs { @@ -441,7 +462,7 @@ func hydrateNode(dom js.Value, n *vdom.VNode) { } } for k, v := range n.Props { - dom.Set(k, v) + setProp(dom, k, v) } if n.HTML != "" { return // trust server-rendered HTML diff --git a/go/wasmruntime/testdata/domexec.js b/go/wasmruntime/testdata/domexec.js index a1d215d8..d54a420b 100644 --- a/go/wasmruntime/testdata/domexec.js +++ b/go/wasmruntime/testdata/domexec.js @@ -42,6 +42,11 @@ class DNode { // Tests set .rect to control what getBoundingClientRect reports; there is no // layout engine here, so geometry is whatever the test declares. this.rect = { left: 0, top: 0, width: 0, height: 0 }; + // Likewise for overflow. clientWidth is the visible content box, scrollWidth the + // full content — content overflows exactly when the second exceeds the first, and + // a test declares both rather than a layout engine deriving them. + this.clientWidth = 0; + this.scrollWidth = 0; } get nodeType() { return this.tag === "#text" ? 3 : 1; } get firstChild() { return this.childNodes[0] ?? null; } diff --git a/go/webui/forms.go b/go/webui/forms.go index 380b231b..516c5694 100644 --- a/go/webui/forms.go +++ b/go/webui/forms.go @@ -815,6 +815,11 @@ func FormTimezoneSelector(p FormSelectProps) *vdom.VNode { type dropdown struct { f *Floating + // multi drives the parts of a row that differ between picking ONE thing and picking + // SEVERAL: a multi-select's rows carry a checkbox, because the row's job there is to + // show a state you toggle, not an item you pick. + multi bool + search *vdom.Signal[string] searchRef *vdom.Ref // active is the keyboard-highlighted option, as an index into the FILTERED list. @@ -830,8 +835,9 @@ type DropdownOptions struct { OnOpenChange func(bool) } -func newDropdown(o DropdownOptions, closeOnSelect bool) *dropdown { +func newDropdown(o DropdownOptions, multi bool) *dropdown { d := &dropdown{ + multi: multi, search: vdom.NewSignal(""), searchRef: vdom.NewRef(), active: vdom.NewSignal(-1), @@ -858,7 +864,6 @@ func newDropdown(o DropdownOptions, closeOnSelect bool) *dropdown { } }, }) - _ = closeOnSelect return d } @@ -963,9 +968,32 @@ func (d *dropdown) optionButton(opt FormSelectOption, idx int, selected, onDark, } else if onClick != nil { mods = append(mods, vdom.On(vdom.EVENT_CLICK, onClick)) } + + if d.multi { + mods = append(mods, optionCheckbox(selected)) + } return vdom.Button(append(mods, vdom.Text(opt.Label))...) } +// optionCheckbox is the tick on a multi-select row. +// +// It is inert: pointer-events are off and it has no change handler, so the click always +// lands on the row BUTTON that owns it. A checkbox that could be clicked independently +// of its row would let the two disagree about what is selected. +// +// `checked` is a PROP, not an attribute. The attribute is only the DOM's initial value; +// once the browser owns the property, writing the attribute again does nothing — so an +// attribute-driven tick would be right on the first paint and then never change again. +func optionCheckbox(selected bool) vdom.Mod { + return vdom.Input( + vdom.Attr("type", "checkbox"), + vdom.Attr("tabindex", "-1"), + vdom.Attr("aria-hidden", "true"), + vdom.Attr("class", "pointer-events-none shrink-0"), + vdom.BoolProp("checked", selected), + ) +} + func (d *dropdown) noResults(onDark bool) *vdom.VNode { cls := formDropdownNoResults if onDark { @@ -1015,7 +1043,7 @@ type Combobox struct{ *dropdown } // NewCombobox creates a single-select dropdown. func NewCombobox(o DropdownOptions) *Combobox { - return &Combobox{dropdown: newDropdown(o, true)} + return &Combobox{dropdown: newDropdown(o, false)} // picks one — no checkboxes } // FormComboboxProps configures Combobox.Render. @@ -1117,11 +1145,23 @@ func NewSearchableSelect(o DropdownOptions) *Combobox { return NewCombobox(o) } // click, on Escape, or when its trigger is clicked again. // // Create it once, alongside your signals — never inside a render function. -type MultiSelect struct{ *dropdown } +type MultiSelect struct { + *dropdown + + // tagsRef / countRef are the two faces of the trigger: the row of pills, and the + // "N items selected" summary that replaces them when they do not fit. Both are + // always rendered; which one is visible is decided by measurement, not by a signal. + tagsRef *vdom.Ref + countRef *vdom.Ref +} // NewMultiSelect creates a multi-select dropdown. func NewMultiSelect(o DropdownOptions) *MultiSelect { - return &MultiSelect{dropdown: newDropdown(o, false)} + return &MultiSelect{ + dropdown: newDropdown(o, true), // picks several — every row gets a checkbox + tagsRef: vdom.NewRef(), + countRef: vdom.NewRef(), + } } // FormMultiSelectProps configures MultiSelect.Render. @@ -1154,43 +1194,6 @@ func (m *MultiSelect) Render(p FormMultiSelectProps) *vdom.VNode { } } - // The trigger's face: a placeholder, tags, or "N selected" once there are too many - // to show without the field growing unboundedly. - var face *vdom.VNode - switch { - case len(selected) == 0: - face = vdom.Span(vdom.Attr("class", "text-neutral-500 truncate"), - vdom.Text(pick(p.Placeholder, "Select options"))) - case len(selected) > maxTags: - face = vdom.Span(vdom.Attr("class", "truncate"), - vdom.Text(strconv.Itoa(len(selected))+" selected")) - default: - tags := []vdom.Mod{vdom.Attr("class", "flex flex-wrap items-center gap-1 min-w-0")} - for _, opt := range selected { - ov := opt.Value - remove := []vdom.Mod{ - vdom.Attr("type", "button"), - vdom.Attr("class", "bg-transparent border-none p-0 cursor-pointer flex items-center hover:text-neutral-900"), - vdom.Attr("aria-label", "Remove "+opt.Label), - IconInline("xmark", 10, ""), - } - if p.OnChange != nil { - oc, cur := p.OnChange, p.Value - remove = append(remove, vdom.OnEvent(vdom.EVENT_CLICK, func(e vdom.Event) { - // The tag sits INSIDE the trigger, so without this the click would - // bubble up and toggle the panel open as it removed the tag. - e.StopPropagation() - oc(formRemoveStr(cur, ov)) - })) - } - tags = append(tags, vdom.Span(vdom.Attr("class", formMultiSelectTag), - vdom.Text(opt.Label), - vdom.Button(remove...), - )) - } - face = vdom.Div(tags...) - } - pad := "p-2" if p.Small { pad = "p-1" @@ -1200,11 +1203,14 @@ func (m *MultiSelect) Render(p FormMultiSelectProps) *vdom.VNode { chevron = "chevron-up" } + face := append([]vdom.Mod{vdom.Attr("class", "flex-1 overflow-hidden flex items-center min-w-0")}, + m.triggerFace(p, selected, maxTags)...) + trigger := m.f.Trigger(FloatingTriggerProps{ Class: cx(formTriggerBase, "overflow-hidden", formControlH(p.Small), pad), AriaHasPopup: "listbox", }, - vdom.Div(vdom.Attr("class", "flex-1 overflow-hidden flex items-center min-w-0"), face), + vdom.Div(face...), IconInline(chevron, 16, "text-neutral-400"), ) if p.Disabled { @@ -1270,7 +1276,121 @@ func (m *MultiSelect) Render(p FormMultiSelectProps) *vdom.VNode { ) } -const formMultiSelectTag = "inline-flex items-center gap-1 rounded-default bg-neutral-100 px-1.5 py-0.5 text-xs text-neutral-700 max-w-full" +// triggerFace is what the closed field shows: a placeholder, a row of removable pills, +// or "N items selected" when the pills will not fit. +// +// It renders the pills AND the summary every time, and lets a measurement decide which +// one is displayed — because "will not fit" is a question only the browser can answer, +// and answering it by writing a signal would re-render the tree that the answer depends +// on. See collapseFace. +func (m *MultiSelect) triggerFace(p FormMultiSelectProps, selected []FormSelectOption, maxTags int) []vdom.Mod { + if len(selected) == 0 { + return []vdom.Mod{vdom.Span( + vdom.Attr("class", "text-neutral-500 truncate"), + vdom.Text(pick(p.Placeholder, "Select options")), + )} + } + + // Too many to be worth showing as pills at all. This needs no measurement, so it is + // decided here in the markup and holds on the SERVER too — SSR gets the summary + // rather than a row of clipped pills it would then have to swap out. + tooMany := len(selected) > maxTags + + pillCls := formMultiSelectTag + if p.Small { + pillCls = cx(pillCls, "px-1.5 text-xs") + } else { + pillCls = cx(pillCls, "px-2 text-sm") + } + + // flex-nowrap + overflow-hidden is what makes the overflow MEASURABLE: the pills lay + // out on one line and are clipped, so scrollWidth exceeds clientWidth by exactly the + // amount that does not fit. With flex-wrap they would simply wrap and never overflow + // horizontally, and nothing would ever collapse. + tags := []vdom.Mod{ + vdom.WithRef(m.tagsRef), + vdom.Attr("class", faceCls("flex flex-nowrap items-center gap-1 min-w-0 overflow-hidden", tooMany)), + } + for _, opt := range selected { + ov, label := opt.Value, opt.Label + remove := []vdom.Mod{ + vdom.Attr("type", "button"), + vdom.Attr("class", "bg-transparent border-none p-0 cursor-pointer flex items-center hover:text-neutral-900"), + vdom.Attr("aria-label", "Remove "+label), + IconInline("xmark", 10, ""), + } + if p.OnChange != nil { + oc, cur := p.OnChange, p.Value + remove = append(remove, vdom.OnEvent(vdom.EVENT_CLICK, func(e vdom.Event) { + // The pill sits INSIDE the trigger, so without this the click would bubble + // up and open the panel as it removed the pill. + e.StopPropagation() + oc(formRemoveStr(cur, ov)) + })) + } + tags = append(tags, vdom.Span(vdom.Attr("class", pillCls), + vdom.Text(label), + vdom.Button(remove...), + )) + } + + count := vdom.Span( + vdom.WithRef(m.countRef), + vdom.Attr("class", faceCls("truncate", !tooMany)), + vdom.Text(itemsSelected(len(selected))), + ) + + m.collapseFace(tooMany) + return []vdom.Mod{vdom.Div(tags...), count} +} + +// collapseFace picks which face is displayed, after the DOM exists. +// +// The choice is written with SetStyle, not a signal: a signal write re-renders the +// whole tree, and this decision is DERIVED from that tree's layout — the render would +// feed the measurement that triggers the render. Doing it imperatively also means the +// pills and the summary can both be in the DOM at once without ever both being seen. +// +// AfterRender runs after the commit but before the browser paints, so every write here +// — including the deliberate flash of the pills back into view to measure them — lands +// in the same frame. Nothing is visible mid-decision. +func (m *MultiSelect) collapseFace(tooMany bool) { + wasmruntime.AfterRender(func() { + collapse := tooMany + if !collapse { + // Measure from a known state. A previous render may have left the pills + // display:none, and a hidden element has no layout — it would report no + // overflow and we would wrongly un-collapse. + wasmruntime.SetStyle(m.tagsRef, "display", "flex") + wasmruntime.SetStyle(m.countRef, "display", "none") + if !wasmruntime.OverflowsX(m.tagsRef) { + return + } + } + wasmruntime.SetStyle(m.tagsRef, "display", "none") + wasmruntime.SetStyle(m.countRef, "display", "block") + }) +} + +// faceCls hides one of the two faces by class, which is what the SERVER renders (it +// cannot measure, so it ships the unmeasured choice). On the client the inline styles +// written by collapseFace take over, and they win over a class. +func faceCls(cls string, hidden bool) string { + if hidden { + return cx(cls, "hidden") + } + return cls +} + +// itemsSelected is the collapsed summary: "1 item selected", "4 items selected". +func itemsSelected(n int) string { + if n == 1 { + return "1 item selected" + } + return strconv.Itoa(n) + " items selected" +} + +const formMultiSelectTag = "inline-flex items-center gap-0.5 rounded-default bg-neutral-200 py-0.5 text-neutral-700 whitespace-nowrap leading-none" func truncateRunes(s string, max int) string { if max <= 0 { diff --git a/go/webui/forms_dropdown_test.go b/go/webui/forms_dropdown_test.go index acaeabd8..3f451436 100644 --- a/go/webui/forms_dropdown_test.go +++ b/go/webui/forms_dropdown_test.go @@ -1,6 +1,7 @@ package webui import ( + "strconv" "strings" "testing" @@ -183,6 +184,174 @@ func TestDropdownsAreStandalone(t *testing.T) { } } +// ---- the panel rows ---- + +// A multi-select row shows a CHECKBOX. Its job is to show a state you toggle, not an +// item you pick, and the tick is the only thing that says which is which. +// +// The unified dropdown dropped it: both kinds of row rendered as a bare label, so a +// multi-select gave no indication of what was already selected. +func TestMultiSelectOptionsHaveCheckboxes(t *testing.T) { + m := NewMultiSelect(DropdownOptions{}) + m.Open() + + node := m.Render(FormMultiSelectProps{ + Options: dropdownOptions(), + Value: []string{"res"}, // Research is selected; the other two are not + }) + + for _, want := range []struct { + label string + checked bool + }{ + {"Engineering", false}, + {"Research", true}, + {"Networking", false}, + } { + box := findCheckboxIn(optionRow(t, node, want.label)) + if box == nil { + t.Errorf("option %q has no checkbox", want.label) + continue + } + if got := box.Props["checked"]; got != strconv.FormatBool(want.checked) { + t.Errorf("option %q: checked=%q, want %v", want.label, got, want.checked) + } + } +} + +// ...and a combobox row does NOT. It picks one thing; a checkbox would promise a +// multiple selection it will not honour. +func TestComboboxOptionsHaveNoCheckboxes(t *testing.T) { + c := NewCombobox(DropdownOptions{}) + c.Open() + + node := c.Render(FormComboboxProps{Options: dropdownOptions(), Value: "res"}) + if findCheckboxIn(optionRow(t, node, "Research")) != nil { + t.Error("a single-select option must not render a checkbox") + } +} + +// ---- the collapsed trigger ---- + +// Past maxTagsBeforeCollapse the field stops showing pills and says how many are +// selected. Without this the trigger either grows without bound or silently clips the +// selection, and the user cannot tell how much they picked. +func TestMultiSelectCollapsesToACount(t *testing.T) { + m := NewMultiSelect(DropdownOptions{}) + html := vdom.RenderHTML(m.Render(FormMultiSelectProps{ + Options: manyOptions(6), + Value: []string{"o1", "o2", "o3", "o4"}, // 4 > the default max of 3 + MaxTagsBeforeCollapse: 3, + })) + + if !strings.Contains(html, "4 items selected") { + t.Errorf("collapsed trigger does not say how many are selected:\n%s", html) + } + // The count is the SERVER's choice too — this needs no measurement, so SSR must not + // ship a row of clipped pills that the client then has to swap out. + if !strings.Contains(html, `class="flex flex-nowrap items-center gap-1 min-w-0 overflow-hidden hidden"`) { + t.Errorf("the pills were not hidden server-side:\n%s", html) + } +} + +// One is one ITEM, not one items. +func TestCollapsedCountIsPluralised(t *testing.T) { + if got := itemsSelected(1); got != "1 item selected" { + t.Errorf("itemsSelected(1) = %q", got) + } + if got := itemsSelected(4); got != "4 items selected" { + t.Errorf("itemsSelected(4) = %q", got) + } +} + +// Under the limit, the pills show and the summary is the hidden one. Both are always in +// the DOM: which is visible is settled by a measurement after render (collapseFace), +// and that measurement needs something to measure. +func TestMultiSelectShowsPillsWhenTheyFit(t *testing.T) { + m := NewMultiSelect(DropdownOptions{}) + html := vdom.RenderHTML(m.Render(FormMultiSelectProps{ + Options: dropdownOptions(), + Value: []string{"eng", "res"}, + MaxTagsBeforeCollapse: 3, + })) + + if !strings.Contains(html, "Engineering") || !strings.Contains(html, "Research") { + t.Errorf("the selected pills are missing:\n%s", html) + } + if !strings.Contains(html, `class="truncate hidden"`) { + t.Errorf("the count summary should be the hidden face here:\n%s", html) + } + // It must be RENDERED though, or there is nothing for collapseFace to reveal. + if !strings.Contains(html, "2 items selected") { + t.Errorf("the count summary was not rendered at all:\n%s", html) + } +} + +// The pills must lay out on ONE line and clip. With flex-wrap they wrap instead, so +// scrollWidth never exceeds clientWidth, and the overflow collapse can never fire. +func TestPillsAreNowrapSoOverflowIsMeasurable(t *testing.T) { + m := NewMultiSelect(DropdownOptions{}) + html := vdom.RenderHTML(m.Render(FormMultiSelectProps{ + Options: dropdownOptions(), + Value: []string{"eng"}, + })) + if strings.Contains(html, "flex-wrap") { + t.Error("the pill row wraps; overflow would never be detected") + } + if !strings.Contains(html, "flex-nowrap") || !strings.Contains(html, "overflow-hidden") { + t.Errorf("the pill row must be flex-nowrap + overflow-hidden:\n%s", html) + } +} + +func manyOptions(n int) []FormSelectOption { + out := make([]FormSelectOption, n) + for i := range out { + id := strconv.Itoa(i + 1) + out[i] = FormSelectOption{Value: "o" + id, Label: "Option " + id} + } + return out +} + +// optionRow returns the panel row whose label is label. +func optionRow(t *testing.T, n *vdom.VNode, label string) *vdom.VNode { + t.Helper() + if row := findRow(n, label); row != nil { + return row + } + t.Fatalf("no option row labelled %q", label) + return nil +} + +func findRow(n *vdom.VNode, label string) *vdom.VNode { + if n == nil { + return nil + } + if n.Tag == "button" && n.Attrs["role"] == "option" && nodeText(n) == label { + return n + } + for _, c := range n.Children { + if row := findRow(c, label); row != nil { + return row + } + } + return nil +} + +func findCheckboxIn(n *vdom.VNode) *vdom.VNode { + if n == nil { + return nil + } + if n.Tag == "input" && n.Attrs["type"] == "checkbox" { + return n + } + for _, c := range n.Children { + if box := findCheckboxIn(c); box != nil { + return box + } + } + return nil +} + // clickOption finds an option button by its label and invokes its click handler. func clickOption(t *testing.T, n *vdom.VNode, label string) { t.Helper()