elysia-rack 0.5.5 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -144,7 +144,7 @@ curl -X POST localhost:3000/catalog/products \
144
144
  -d '{"name":"Keyboard"}'
145
145
  ```
146
146
 
147
- Replays with the same key return the stored `201` with an `Idempotent-Replayed: true` header instead of re-executing. Missing keys are rejected with `400`. Tune with `idempotency: { required, header, ttl, store }` — pass a shared store (e.g. Redis) for multi-instance deploys; the default is an in-memory store bounded to 1,000 entries.
147
+ Replays with the same key return the stored `201` with an `Idempotent-Replayed: true` header instead of re-executing. Missing keys are rejected with `400`. Tune with `idempotency: { required, header, ttl, store, scope }` — pass a shared store (e.g. Redis) for multi-instance deploys, and set `scope` to namespace keys per caller. The default is an in-memory store that keeps live entries for their full TTL (it does not survive a restart).
148
148
 
149
149
  ### Soft delete
150
150
 
@@ -161,7 +161,7 @@ Replays with the same key return the stored `201` with an `Idempotent-Replayed:
161
161
 
162
162
  - `true` / `undefined` — allow
163
163
  - `false` — 403
164
- - A permission string such as `"products.delete"` — checked against `x-permissions` (comma-separated header) or context permissions; allowed when no auth source is wired yet
164
+ - A permission string such as `"products.delete"` — checked against context `permissions` (`app.decorate`/`app.state`); fails closed when no server-trusted source is wired. The `x-permissions` header is not trusted.
165
165
  - A function receiving `{ operation, request, id, resource }` — return truthy to allow, falsy for 403
166
166
 
167
167
  ```ts
package/dist/panel-app.js CHANGED
@@ -233,25 +233,64 @@ function actionButton(action, id) {
233
233
  const target = esc(action.href.replaceAll(":id", encodeURIComponent(id)));
234
234
  const cls = "text-link hover:text-link-hover ml-3 px-1 text-sm font-medium hover:underline";
235
235
  if (action.method) {
236
- const confirm = action.confirm ? ` data-rack-confirm="${esc(action.confirm)}"` : "";
237
- return `<button type="button" data-rack-href="${target}" data-rack-method="${esc(action.method)}"${confirm} class="${cls}">${esc(action.label)}</button>`;
236
+ const btn = document.createElement("button");
237
+ btn.type = "button";
238
+ btn.dataset.rackHref = target;
239
+ btn.dataset.rackMethod = esc(action.method);
240
+ btn.className = cls;
241
+ if (action.confirm) {
242
+ btn.dataset.rackConfirm = esc(action.confirm);
243
+ }
244
+ btn.textContent = esc(action.label);
245
+ return btn;
238
246
  }
239
- return `<a href="${target}" class="${cls}">${esc(action.label)}</a>`;
247
+ const a = document.createElement("a");
248
+ a.href = target;
249
+ a.className = cls;
250
+ a.textContent = esc(action.label);
251
+ return a;
240
252
  }
241
253
  function actionCell(id, cfg, deleted) {
242
- let inner = "";
254
+ const td = document.createElement("td");
255
+ td.className = "border-table-border border-t px-3 py-2.5 whitespace-nowrap text-right";
256
+ const cls = "text-link hover:text-link-hover px-1 text-sm font-medium hover:underline";
243
257
  if (deleted) {
244
- if (cfg.restorable)
245
- inner = `<button type="button" data-restore-id="${esc(id)}" class="text-link hover:text-link-hover px-1 text-sm font-medium hover:underline">Restore</button>`;
258
+ if (cfg.restorable) {
259
+ const btn = document.createElement("button");
260
+ btn.type = "button";
261
+ btn.dataset.restoreId = esc(id);
262
+ btn.className = cls;
263
+ btn.textContent = "Restore";
264
+ td.appendChild(btn);
265
+ }
246
266
  } else {
247
- if (cfg.viewable)
248
- inner += `<button type="button" data-view-id="${esc(id)}" class="text-link hover:text-link-hover px-1 text-sm font-medium hover:underline">View</button>`;
249
- if (cfg.editable)
250
- inner += `<button type="button" data-edit-id="${esc(id)}" class="text-link hover:text-link-hover ${cfg.viewable ? "ml-3 " : ""}px-1 text-sm font-medium hover:underline">Edit</button>` + `<button type="button" data-delete-id="${esc(id)}" class="text-destructive ml-3 px-1 text-sm font-medium hover:underline">Delete</button>`;
251
- for (const action of cfg.actions)
252
- inner += actionButton(action, id);
267
+ if (cfg.viewable) {
268
+ const btn = document.createElement("button");
269
+ btn.type = "button";
270
+ btn.dataset.viewId = esc(id);
271
+ btn.className = cls;
272
+ btn.textContent = "View";
273
+ td.appendChild(btn);
274
+ }
275
+ if (cfg.editable) {
276
+ const editBtn = document.createElement("button");
277
+ editBtn.type = "button";
278
+ editBtn.dataset.editId = esc(id);
279
+ editBtn.className = `text-link hover:text-link-hover${cfg.viewable ? " ml-3" : ""} px-1 text-sm font-medium hover:underline`;
280
+ editBtn.textContent = "Edit";
281
+ td.appendChild(editBtn);
282
+ const delBtn = document.createElement("button");
283
+ delBtn.type = "button";
284
+ delBtn.dataset.deleteId = esc(id);
285
+ delBtn.className = "text-destructive ml-3 px-1 text-sm font-medium hover:underline";
286
+ delBtn.textContent = "Delete";
287
+ td.appendChild(delBtn);
288
+ }
289
+ for (const action of cfg.actions) {
290
+ td.appendChild(actionButton(action, id));
291
+ }
253
292
  }
254
- return `<td class="border-table-border border-t px-3 py-2.5 whitespace-nowrap text-right">${inner}</td>`;
293
+ return td;
255
294
  }
256
295
  function numericCols(rows, cols) {
257
296
  const out = new Set;
@@ -263,37 +302,68 @@ function numericCols(rows, cols) {
263
302
  }
264
303
  return out;
265
304
  }
266
- function cellHtml(field, value) {
305
+ function cellText(field, value) {
267
306
  if (value === null || value === undefined)
268
307
  return "";
269
- const text = esc(displayValue(field, value));
270
- if (field?.format === "badge" || field?.name === "status")
271
- return `<span class="rounded-sm px-1.5 py-0.5 text-xs font-medium ${badgeClass(value)}">${text}</span>`;
272
- return text;
308
+ return esc(displayValue(field, value));
273
309
  }
274
310
  function paintRows(tbody, cols, rows, cfg) {
275
311
  const showActions = cfg.editable || cfg.viewable || cfg.softDelete || cfg.actions.length > 0;
276
312
  const span = cols.length + (cfg.selectable ? 1 : 0) + (showActions ? 1 : 0);
277
- if (!rows.length) {
278
- tbody.innerHTML = `<tr><td colspan="${span || 1}" class="text-text-muted px-4 py-10 text-center">No records found.</td></tr>`;
279
- return;
280
- }
281
313
  const numeric = numericCols(rows, cols);
282
- tbody.innerHTML = rows.map((row) => {
314
+ if (!rows.length) {
315
+ tbody.replaceChildren();
316
+ const tr = document.createElement("tr");
317
+ const td = document.createElement("td");
318
+ td.colSpan = span || 1;
319
+ td.className = "text-text-muted px-4 py-10 text-center";
320
+ td.textContent = "No records found.";
321
+ tr.appendChild(td);
322
+ tbody.appendChild(tr);
323
+ return;
324
+ }
325
+ tbody.replaceChildren();
326
+ for (const row of rows) {
283
327
  const id = row[cfg.pk] == null ? "" : String(row[cfg.pk]);
284
328
  const deleted = cfg.softDelete && row[cfg.deletedAt] != null;
285
- let html = `<tr class="hover:bg-table-row-hover">`;
286
- if (cfg.selectable)
287
- html += `<td class="border-table-border border-t px-3 py-2.5"><input type="checkbox" data-select-row value="${esc(id)}" aria-label="Select row ${esc(id)}" class="accent-primary size-4"></td>`;
288
- html += cols.map((col) => {
329
+ const tr = document.createElement("tr");
330
+ tr.className = "hover:bg-table-row-hover";
331
+ if (cfg.selectable) {
332
+ const td = document.createElement("td");
333
+ td.className = "border-table-border border-t px-3 py-2.5";
334
+ const input = document.createElement("input");
335
+ input.type = "checkbox";
336
+ input.dataset.selectRow = "";
337
+ input.value = id;
338
+ input.setAttribute("aria-label", `Select row ${esc(id)}`);
339
+ input.className = "accent-primary size-4";
340
+ td.appendChild(input);
341
+ tr.appendChild(td);
342
+ }
343
+ for (const col of cols) {
289
344
  const field = cfg.byName.get(col);
290
345
  const align = numeric.has(col) || field && isNumericField(field) ? " text-right tabular-nums" : "";
291
- return `<td class="border-table-border border-t px-3 py-2.5${align}">${cellHtml(field, row[col])}</td>`;
292
- }).join("");
293
- if (showActions)
294
- html += actionCell(id, cfg, Boolean(deleted));
295
- return html + "</tr>";
296
- }).join("");
346
+ const td = document.createElement("td");
347
+ td.className = `border-table-border border-t px-3 py-2.5${align}`;
348
+ const value = row[col];
349
+ const badge = field?.format === "badge" || field?.name === "status";
350
+ if (value === null || value === undefined) {
351
+ td.textContent = "";
352
+ } else if (badge) {
353
+ const span2 = document.createElement("span");
354
+ span2.className = `rounded-sm px-1.5 py-0.5 text-xs font-medium ${badgeClass(value)}`;
355
+ span2.textContent = displayValue(field, value);
356
+ td.appendChild(span2);
357
+ } else {
358
+ td.textContent = cellText(field, value);
359
+ }
360
+ tr.appendChild(td);
361
+ }
362
+ if (showActions) {
363
+ tr.appendChild(actionCell(id, cfg, Boolean(deleted)));
364
+ }
365
+ tbody.appendChild(tr);
366
+ }
297
367
  }
298
368
  function paintPages(box, params, page, totalPages) {
299
369
  const linkCls = "border-border rounded-md border px-2.5 py-1 text-xs hover:bg-navigation-hover";
@@ -501,14 +571,36 @@ async function openDetail(root, id) {
501
571
  if (label)
502
572
  label.textContent = `#${id}`;
503
573
  const body = dlg.querySelector("[data-detail-body]");
504
- if (body)
505
- body.innerHTML = renderDetail(root, row);
574
+ if (body) {
575
+ body.replaceChildren(renderDetail(root, row));
576
+ }
506
577
  dlg.showModal?.();
507
578
  }
508
579
  function renderDetail(root, row) {
509
580
  const cfg = tableConfig(root);
510
- const items = cfg.fields.filter((field) => row[field.name] !== undefined && !field.hideInTable).map((field) => `<div class="flex flex-col gap-0.5"><dt class="text-text-muted text-xs font-medium">${esc(field.label)}</dt><dd class="text-foreground text-sm">${esc(displayValue(field, row[field.name])) || "—"}</dd></div>`);
511
- return items.join("") || `<p class="text-text-muted text-sm">No fields</p>`;
581
+ const frag = document.createDocumentFragment();
582
+ const visibleFields = cfg.fields.filter((field) => row[field.name] !== undefined && !field.hideInTable);
583
+ if (!visibleFields.length) {
584
+ const p = document.createElement("p");
585
+ p.className = "text-text-muted text-sm";
586
+ p.textContent = "No fields";
587
+ frag.appendChild(p);
588
+ return frag;
589
+ }
590
+ for (const field of visibleFields) {
591
+ const wrapper = document.createElement("div");
592
+ wrapper.className = "flex flex-col gap-0.5";
593
+ const dt = document.createElement("dt");
594
+ dt.className = "text-text-muted text-xs font-medium";
595
+ dt.textContent = String(field.label ?? "");
596
+ const dd = document.createElement("dd");
597
+ dd.className = "text-foreground text-sm";
598
+ dd.textContent = displayValue(field, row[field.name]) || "—";
599
+ wrapper.appendChild(dt);
600
+ wrapper.appendChild(dd);
601
+ frag.appendChild(wrapper);
602
+ }
603
+ return frag;
512
604
  }
513
605
  async function deleteOne(root, id) {
514
606
  if (!window.confirm(`Delete record ${id}?`))
package/dist/panel.css CHANGED
@@ -1,2 +1,2 @@
1
1
  /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
2
- @import "https://fonts.googleapis.com/css2?family=Chocolate+Classical+Sans&family=Poppins:wght@400;500;600&display=swap";@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:"Poppins", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--container-md:28rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--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);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--leading-tight:1.25;--radius-xs:var(--radius-xs);--radius-sm:var(--radius-sm);--radius-md:var(--radius-md);--radius-lg:var(--radius-lg);--radius-xl:var(--radius-xl);--radius-2xl:var(--radius-2xl);--radius-3xl:var(--radius-3xl);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-primary:var(--primary);--radius-full:var(--radius-full);--font-display:"Chocolate Classical Sans", "Poppins", ui-sans-serif, sans-serif}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-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;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}: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:currentColor}@supports (color:color-mix(in lab, red, red)){::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{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-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]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}body{background-color:var(--background);color:var(--foreground)}::selection{background-color:var(--selection);color:var(--selection-foreground)}:focus-visible{outline:2px solid var(--focus);outline-offset:2px}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.fixed{position:fixed}.relative{position:relative}.static{position:static}.right-4{right:calc(var(--spacing) * 4)}.bottom-4{bottom:calc(var(--spacing) * 4)}.z-50{z-index:50}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mr-1\.5{margin-right:calc(var(--spacing) * 1.5)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-3{margin-left:calc(var(--spacing) * 3)}.contents{display:contents}.flex{display:flex}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.size-3\.5{width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-6{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6)}.size-\[22px\]{width:22px;height:22px}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-auto{height:auto}.min-h-screen{min-height:100vh}.w-10{width:calc(var(--spacing) * 10)}.w-40{width:calc(var(--spacing) * 40)}.w-56{width:calc(var(--spacing) * 56)}.w-full{width:100%}.max-w-5xl{max-width:var(--container-5xl)}.max-w-full{max-width:100%}.max-w-md{max-width:var(--container-md)}.min-w-0{min-width:0}.flex-1{flex:1}.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.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)}.self-end{align-self:flex-end}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-border{border-color:var(--border)}.border-input{border-color:var(--input)}.border-sidebar-border{border-color:var(--sidebar-border)}.border-table-border{border-color:var(--table-border)}.bg-background{background-color:var(--background)}.bg-badge-background{background-color:var(--badge-background)}.bg-card{background-color:var(--card)}.bg-destructive{background-color:var(--destructive)}.bg-gold{background-color:var(--gold)}.bg-input-background{background-color:var(--input-background)}.bg-primary{background-color:var(--primary)}.bg-secondary{background-color:var(--secondary)}.bg-sidebar{background-color:var(--sidebar)}.bg-sidebar-primary{background-color:var(--sidebar-primary)}.bg-success-muted{background-color:var(--success-muted)}.bg-surface-muted{background-color:var(--surface-muted)}.bg-table{background-color:var(--table-background)}.bg-warning-muted{background-color:var(--warning-muted)}.p-0{padding:0}.p-5{padding:calc(var(--spacing) * 5)}.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-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.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-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-10{padding-block:calc(var(--spacing) * 10)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pb-1\.5{padding-bottom:calc(var(--spacing) * 1.5)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pl-2{padding-left:calc(var(--spacing) * 2)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-text-bottom{vertical-align:text-bottom}.font-display{font-family:var(--font-display)}.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-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-\[13px\]{font-size:13px}.text-\[15px\]{font-size:15px}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.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)}.whitespace-nowrap{white-space:nowrap}.text-badge-foreground{color:var(--badge-foreground)}.text-card-foreground{color:var(--card-foreground)}.text-destructive{color:var(--destructive)}.text-destructive-foreground{color:var(--destructive-foreground)}.text-foreground{color:var(--foreground)}.text-form-error{color:var(--form-error)}.text-form-label{color:var(--form-label)}.text-gold-foreground{color:var(--gold-foreground)}.text-link{color:var(--link)}.text-muted-foreground{color:var(--muted-foreground)}.text-primary-foreground{color:var(--primary-foreground)}.text-secondary-foreground{color:var(--secondary-foreground)}.text-sidebar-foreground{color:var(--sidebar-foreground)}.text-sidebar-primary-foreground{color:var(--sidebar-primary-foreground)}.text-success-muted-foreground{color:var(--success-muted-foreground)}.text-text-disabled{color:var(--text-disabled)}.text-text-muted{color:var(--text-muted)}.text-text-subtle{color:var(--text-subtle)}.text-warning-muted-foreground{color:var(--warning-muted-foreground)}.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,)}.underline{text-decoration-line:underline}.underline-offset-4{text-underline-offset:4px}.accent-primary{accent-color:var(--primary)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.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-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))}.group-open\:rotate-180:is(:where(.group):is([open],:popover-open,:open) *){rotate:180deg}.peer-checked\:flex:is(:where(.peer):checked~*){display:flex}.placeholder\:text-form-placeholder::placeholder{color:var(--form-placeholder)}@media (hover:hover){.hover\:bg-navigation-hover:hover{background-color:var(--navigation-hover)}.hover\:bg-primary\/90:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab, var(--primary) 90%, transparent)}}.hover\:bg-sidebar-accent:hover{background-color:var(--sidebar-accent)}.hover\:bg-table-row-hover:hover{background-color:var(--table-row-hover)}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:text-link-hover:hover{color:var(--link-hover)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:border-ring:focus{border-color:var(--ring)}@media (min-width:40rem){.sm\:px-6{padding-inline:calc(var(--spacing) * 6)}}@media (min-width:64rem){.lg\:sticky{position:sticky}.lg\:top-0{top:0}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:h-screen{height:100vh}.lg\:w-60{width:calc(var(--spacing) * 60)}.lg\:flex-row{flex-direction:row}.lg\:overflow-y-auto{overflow-y:auto}.lg\:border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.lg\:border-b-0{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.lg\:py-4{padding-block:calc(var(--spacing) * 4)}}@media (min-width:80rem){.xl\:w-64{width:calc(var(--spacing) * 64)}}.\[\&\:\:-webkit-details-marker\]\:hidden::-webkit-details-marker{display:none}}:root{--background:oklch(100% 0 0);--foreground:oklch(14.5% 0 0);--surface:oklch(100% 0 0);--surface-foreground:oklch(14.5% 0 0);--surface-muted:oklch(98.5% 0 0);--surface-muted-foreground:oklch(55.6% 0 0);--card:oklch(100% 0 0);--card-foreground:oklch(14.5% 0 0);--card-muted:oklch(98.5% 0 0);--popover:oklch(100% 0 0);--popover-foreground:oklch(14.5% 0 0);--overlay:oklch(14.5% 0 0/.5);--overlay-foreground:oklch(100% 0 0);--primary:oklch(20.5% 0 0);--primary-foreground:oklch(98.5% 0 0);--secondary:oklch(97% 0 0);--secondary-foreground:oklch(20.5% 0 0);--muted:oklch(97% 0 0);--muted-foreground:oklch(55.6% 0 0);--accent:oklch(97% 0 0);--accent-foreground:oklch(20.5% 0 0);--destructive:oklch(57.7% .245 27.325);--destructive-foreground:oklch(98.5% 0 0);--success:oklch(59.6% .145 163.225);--success-foreground:oklch(98.5% 0 0);--success-muted:oklch(96.2% .044 156.743);--success-muted-foreground:oklch(44.8% .119 151.328);--warning:oklch(68.1% .162 75.834);--warning-foreground:oklch(20.5% 0 0);--warning-muted:oklch(98.7% .026 102.212);--warning-muted-foreground:oklch(47.6% .114 61.907);--info:oklch(62.3% .214 259.815);--info-foreground:oklch(98.5% 0 0);--info-muted:oklch(97% .014 254.604);--info-muted-foreground:oklch(48.8% .217 264.376);--border:oklch(92.2% 0 0);--border-muted:oklch(96% 0 0);--input:oklch(92.2% 0 0);--input-background:oklch(100% 0 0);--ring:oklch(70.8% 0 0);--focus:oklch(62.3% .214 259.815);--text:oklch(14.5% 0 0);--text-muted:oklch(55.6% 0 0);--text-subtle:oklch(70.8% 0 0);--text-disabled:oklch(70.8% 0 0);--text-inverse:oklch(98.5% 0 0);--selection:oklch(20.5% 0 0);--selection-foreground:oklch(98.5% 0 0);--link:oklch(48.8% .243 264.376);--link-hover:oklch(42.4% .199 265.638);--link-visited:oklch(48.8% .243 264.376);--table-background:oklch(100% 0 0);--table-header:oklch(98.5% 0 0);--table-row:oklch(100% 0 0);--table-row-hover:oklch(98.5% 0 0);--table-row-selected:oklch(97% 0 0);--table-border:oklch(92.2% 0 0);--form-label:oklch(20.5% 0 0);--form-description:oklch(55.6% 0 0);--form-placeholder:oklch(70.8% 0 0);--form-error:oklch(57.7% .245 27.325);--form-success:oklch(59.6% .145 163.225);--navigation-background:oklch(100% 0 0);--navigation-foreground:oklch(14.5% 0 0);--navigation-hover:oklch(97% 0 0);--navigation-active:oklch(97% 0 0);--navigation-active-foreground:oklch(20.5% 0 0);--navigation-border:oklch(92.2% 0 0);--sidebar:oklch(98.5% 0 0);--sidebar-foreground:oklch(14.5% 0 0);--sidebar-primary:oklch(20.5% 0 0);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(97% 0 0);--sidebar-accent-foreground:oklch(20.5% 0 0);--sidebar-muted:oklch(55.6% 0 0);--sidebar-border:oklch(92.2% 0 0);--sidebar-ring:oklch(70.8% 0 0);--header:oklch(100% 0 0);--header-foreground:oklch(14.5% 0 0);--header-border:oklch(92.2% 0 0);--breadcrumb-foreground:oklch(55.6% 0 0);--breadcrumb-current:oklch(14.5% 0 0);--breadcrumb-separator:oklch(70.8% 0 0);--badge-background:oklch(97% 0 0);--badge-foreground:oklch(20.5% 0 0);--code-background:oklch(97% 0 0);--code-foreground:oklch(20.5% 0 0);--chart-1:oklch(64.6% .222 41.116);--chart-2:oklch(60% .118 184.704);--chart-3:oklch(39.8% .07 227.392);--chart-4:oklch(82.8% .189 84.429);--chart-5:oklch(76.9% .188 70.08);--radius:.625rem;--radius-xs:calc(var(--radius) - 6px);--radius-sm:calc(var(--radius) - 4px);--radius-md:calc(var(--radius) - 2px);--radius-lg:var(--radius);--radius-xl:calc(var(--radius) + 4px);--radius-2xl:calc(var(--radius) + 8px);--radius-3xl:calc(var(--radius) + 12px);--radius-full:9999px}.dark{--background:oklch(14.5% 0 0);--foreground:oklch(98.5% 0 0);--surface:oklch(20.5% 0 0);--surface-foreground:oklch(98.5% 0 0);--surface-muted:oklch(18% 0 0);--surface-muted-foreground:oklch(70.8% 0 0);--card:oklch(20.5% 0 0);--card-foreground:oklch(98.5% 0 0);--card-muted:oklch(26.9% 0 0);--popover:oklch(20.5% 0 0);--popover-foreground:oklch(98.5% 0 0);--overlay:oklch(0% 0 0/.65);--overlay-foreground:oklch(98.5% 0 0);--primary:oklch(92.2% 0 0);--primary-foreground:oklch(20.5% 0 0);--secondary:oklch(26.9% 0 0);--secondary-foreground:oklch(98.5% 0 0);--muted:oklch(26.9% 0 0);--muted-foreground:oklch(70.8% 0 0);--accent:oklch(26.9% 0 0);--accent-foreground:oklch(98.5% 0 0);--destructive:oklch(70.4% .191 22.216);--destructive-foreground:oklch(98.5% 0 0);--success:oklch(69.6% .17 162.48);--success-foreground:oklch(14.5% 0 0);--success-muted:oklch(26.9% .07 156.743);--success-muted-foreground:oklch(76.5% .13 152.461);--warning:oklch(76.9% .188 70.08);--warning-foreground:oklch(20.5% 0 0);--warning-muted:oklch(26.9% .06 85);--warning-muted-foreground:oklch(82.8% .15 84);--info:oklch(70.7% .165 254.624);--info-foreground:oklch(14.5% 0 0);--info-muted:oklch(26.9% .06 255);--info-muted-foreground:oklch(70.7% .165 254.624);--border:oklch(100% 0 0/.1);--border-muted:oklch(100% 0 0/.06);--input:oklch(100% 0 0/.15);--input-background:oklch(20.5% 0 0);--ring:oklch(55.6% 0 0);--focus:oklch(70.7% .165 254.624);--text:oklch(98.5% 0 0);--text-muted:oklch(70.8% 0 0);--text-subtle:oklch(55.6% 0 0);--text-disabled:oklch(55.6% 0 0);--text-inverse:oklch(14.5% 0 0);--selection:oklch(92.2% 0 0);--selection-foreground:oklch(20.5% 0 0);--link:oklch(70.7% .165 254.624);--link-hover:oklch(80.9% .105 251.813);--link-visited:oklch(70.7% .165 254.624);--table-background:oklch(20.5% 0 0);--table-header:oklch(18% 0 0);--table-row:oklch(20.5% 0 0);--table-row-hover:oklch(26.9% 0 0);--table-row-selected:oklch(26.9% 0 0);--table-border:oklch(100% 0 0/.1);--form-label:oklch(98.5% 0 0);--form-description:oklch(70.8% 0 0);--form-placeholder:oklch(55.6% 0 0);--form-error:oklch(70.4% .191 22.216);--form-success:oklch(69.6% .17 162.48);--navigation-background:oklch(20.5% 0 0);--navigation-foreground:oklch(98.5% 0 0);--navigation-hover:oklch(26.9% 0 0);--navigation-active:oklch(26.9% 0 0);--navigation-active-foreground:oklch(98.5% 0 0);--navigation-border:oklch(100% 0 0/.1);--sidebar:oklch(20.5% 0 0);--sidebar-foreground:oklch(98.5% 0 0);--sidebar-primary:oklch(48.8% .243 264.376);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(26.9% 0 0);--sidebar-accent-foreground:oklch(98.5% 0 0);--sidebar-muted:oklch(70.8% 0 0);--sidebar-border:oklch(100% 0 0/.1);--sidebar-ring:oklch(55.6% 0 0);--header:oklch(20.5% 0 0);--header-foreground:oklch(98.5% 0 0);--header-border:oklch(100% 0 0/.1);--breadcrumb-foreground:oklch(70.8% 0 0);--breadcrumb-current:oklch(98.5% 0 0);--breadcrumb-separator:oklch(55.6% 0 0);--badge-background:oklch(26.9% 0 0);--badge-foreground:oklch(98.5% 0 0);--code-background:oklch(26.9% 0 0);--code-foreground:oklch(98.5% 0 0);--chart-1:oklch(48.8% .243 264.376);--chart-2:oklch(69.6% .17 162.48);--chart-3:oklch(76.9% .188 70.08);--chart-4:oklch(62.7% .265 303.9);--chart-5:oklch(64.5% .246 16.439)}:root{--background:#f4f5f7;--foreground:#101318;--surface:#fff;--surface-foreground:#101318;--surface-muted:#eef0f3;--surface-muted-foreground:#565d68;--card:#fff;--card-foreground:#101318;--card-muted:#eef0f3;--popover:#fff;--popover-foreground:#101318;--primary:#1a2c50;--primary-foreground:#fff;--secondary:#e8eaee;--secondary-foreground:#101318;--muted:#e8eaee;--muted-foreground:#565d68;--accent:#e8eaee;--accent-foreground:#101318;--destructive:#b3261e;--destructive-foreground:#fff;--success:#1e7a4c;--success-foreground:#fff;--success-muted:#e3f1e9;--success-muted-foreground:#145c39;--warning:#8a6a12;--warning-foreground:#fff;--warning-muted:#f6efdb;--warning-muted-foreground:#6b5311;--info:#1a2c50;--info-foreground:#fff;--info-muted:#e9edf6;--info-muted-foreground:#1a2c50;--gold:#c9a227;--gold-foreground:#101318;--border:#e2e5ea;--border-muted:#edeff2;--input:#c9ced6;--input-background:#fff;--ring:#1a2c50;--focus:#1a2c50;--text:#101318;--text-muted:#565d68;--text-subtle:#767d87;--text-disabled:#9aa1ab;--text-inverse:#fff;--selection:#1a2c50;--selection-foreground:#fff;--link:#1a2c50;--link-hover:#0d1830;--link-visited:#1a2c50;--table-background:#fff;--table-header:#fff;--table-row:#fff;--table-row-hover:#f6f7f9;--table-row-selected:#eef1f6;--table-border:#edeff2;--form-label:#101318;--form-description:#565d68;--form-placeholder:#767d87;--form-error:#b3261e;--navigation-background:#f4f5f7;--navigation-foreground:#101318;--navigation-hover:#e8eaee;--navigation-active:#e8eaee;--navigation-active-foreground:#101318;--navigation-border:#e2e5ea;--sidebar:#f4f5f7;--sidebar-foreground:#101318;--sidebar-primary:#1a2c50;--sidebar-primary-foreground:#fff;--sidebar-accent:#e8eaee;--sidebar-accent-foreground:#101318;--sidebar-muted:#565d68;--sidebar-border:#e2e5ea;--sidebar-ring:#1a2c50;--header:#f4f5f7;--header-foreground:#101318;--header-border:#d5d9e0;--breadcrumb-foreground:#565d68;--breadcrumb-current:#101318;--breadcrumb-separator:#9aa1ab;--badge-background:#eef0f3;--badge-foreground:#101318;--code-background:#eef0f3;--code-foreground:#101318;--radius:6px}.dark{--background:#0b0d12;--foreground:#e9edf4;--surface:#12151c;--surface-foreground:#e9edf4;--surface-muted:#171b23;--surface-muted-foreground:#97a0ad;--card:#12151c;--card-foreground:#e9edf4;--card-muted:#171b23;--popover:#171b23;--popover-foreground:#e9edf4;--primary:#e9edf4;--primary-foreground:#0b0d12;--secondary:#1a1f28;--secondary-foreground:#e9edf4;--muted:#1a1f28;--muted-foreground:#97a0ad;--accent:#1a1f28;--accent-foreground:#e9edf4;--destructive:#ef6b62;--destructive-foreground:#0b0d12;--success:#5cc98f;--success-foreground:#0b0d12;--success-muted:#16281f;--success-muted-foreground:#5cc98f;--warning:#d9ab4a;--warning-foreground:#0b0d12;--warning-muted:#2a2313;--warning-muted-foreground:#d9ab4a;--info:#9db8ea;--info-foreground:#0b0d12;--info-muted:#16203a;--info-muted-foreground:#9db8ea;--gold:#d4a72c;--gold-foreground:#101318;--border:#262c38;--border-muted:#1a1f28;--input:#333a48;--input-background:#12151c;--ring:#97a0ad;--focus:#8fb0e8;--text:#e9edf4;--text-muted:#97a0ad;--text-subtle:#7d8794;--text-disabled:#565f6b;--text-inverse:#0b0d12;--selection:#e9edf4;--selection-foreground:#0b0d12;--link:#9db8ea;--link-hover:#c2d4f5;--link-visited:#9db8ea;--table-background:#12151c;--table-header:#12151c;--table-row:#12151c;--table-row-hover:#181d26;--table-row-selected:#1c2330;--table-border:#1c212b;--form-label:#e9edf4;--form-description:#97a0ad;--form-placeholder:#7d8794;--form-error:#ef6b62;--navigation-background:#0b0d12;--navigation-foreground:#e9edf4;--navigation-hover:#1a1f28;--navigation-active:#1a1f28;--navigation-active-foreground:#e9edf4;--navigation-border:#262c38;--sidebar:#0b0d12;--sidebar-foreground:#e9edf4;--sidebar-primary:#e9edf4;--sidebar-primary-foreground:#0b0d12;--sidebar-accent:#1a1f28;--sidebar-accent-foreground:#e9edf4;--sidebar-muted:#97a0ad;--sidebar-border:#262c38;--sidebar-ring:#8fb0e8;--header:#0b0d12;--header-foreground:#e9edf4;--header-border:#262c38;--breadcrumb-foreground:#97a0ad;--breadcrumb-current:#e9edf4;--breadcrumb-separator:#565f6b;--badge-background:#1a1f28;--badge-foreground:#e9edf4;--code-background:#1a1f28;--code-foreground:#e9edf4}@media (prefers-reduced-motion:reduce){*,:before,:after{transition-duration:.01ms!important;animation-duration:.01ms!important}}dialog{max-width:min(32rem,100vw - 2rem);max-height:calc(100vh - 2rem);margin:auto}dialog::backdrop{background-color:var(--overlay)}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
2
+ @import "https://fonts.googleapis.com/css2?family=Chocolate+Classical+Sans&family=Poppins:wght@400;500;600&display=swap";@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:"Poppins", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--container-md:28rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--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);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--leading-tight:1.25;--radius-xs:var(--radius-xs);--radius-sm:var(--radius-sm);--radius-md:var(--radius-md);--radius-lg:var(--radius-lg);--radius-xl:var(--radius-xl);--radius-2xl:var(--radius-2xl);--radius-3xl:var(--radius-3xl);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-primary:var(--primary);--radius-full:var(--radius-full);--font-display:"Chocolate Classical Sans", "Poppins", ui-sans-serif, sans-serif}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-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;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}: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:currentColor}@supports (color:color-mix(in lab, red, red)){::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{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-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]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}body{background-color:var(--background);color:var(--foreground)}::selection{background-color:var(--selection);color:var(--selection-foreground)}:focus-visible{outline:2px solid var(--focus);outline-offset:2px}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.fixed{position:fixed}.relative{position:relative}.static{position:static}.right-4{right:calc(var(--spacing) * 4)}.bottom-4{bottom:calc(var(--spacing) * 4)}.isolate{isolation:isolate}.z-50{z-index:50}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mr-1\.5{margin-right:calc(var(--spacing) * 1.5)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-3{margin-left:calc(var(--spacing) * 3)}.contents{display:contents}.flex{display:flex}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.size-3\.5{width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-6{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6)}.size-\[22px\]{width:22px;height:22px}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-auto{height:auto}.min-h-screen{min-height:100vh}.w-10{width:calc(var(--spacing) * 10)}.w-40{width:calc(var(--spacing) * 40)}.w-56{width:calc(var(--spacing) * 56)}.w-full{width:100%}.max-w-5xl{max-width:var(--container-5xl)}.max-w-full{max-width:100%}.max-w-md{max-width:var(--container-md)}.min-w-0{min-width:0}.flex-1{flex:1}.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.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)}.self-end{align-self:flex-end}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-border{border-color:var(--border)}.border-input{border-color:var(--input)}.border-sidebar-border{border-color:var(--sidebar-border)}.border-table-border{border-color:var(--table-border)}.bg-background{background-color:var(--background)}.bg-badge-background{background-color:var(--badge-background)}.bg-card{background-color:var(--card)}.bg-destructive{background-color:var(--destructive)}.bg-gold{background-color:var(--gold)}.bg-input-background{background-color:var(--input-background)}.bg-primary{background-color:var(--primary)}.bg-secondary{background-color:var(--secondary)}.bg-sidebar{background-color:var(--sidebar)}.bg-sidebar-primary{background-color:var(--sidebar-primary)}.bg-success-muted{background-color:var(--success-muted)}.bg-surface-muted{background-color:var(--surface-muted)}.bg-table{background-color:var(--table-background)}.bg-warning-muted{background-color:var(--warning-muted)}.p-0{padding:0}.p-5{padding:calc(var(--spacing) * 5)}.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-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.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-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-10{padding-block:calc(var(--spacing) * 10)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pb-1\.5{padding-bottom:calc(var(--spacing) * 1.5)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pl-2{padding-left:calc(var(--spacing) * 2)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-text-bottom{vertical-align:text-bottom}.font-display{font-family:var(--font-display)}.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-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-\[13px\]{font-size:13px}.text-\[15px\]{font-size:15px}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.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)}.whitespace-nowrap{white-space:nowrap}.text-badge-foreground{color:var(--badge-foreground)}.text-card-foreground{color:var(--card-foreground)}.text-destructive{color:var(--destructive)}.text-destructive-foreground{color:var(--destructive-foreground)}.text-foreground{color:var(--foreground)}.text-form-error{color:var(--form-error)}.text-form-label{color:var(--form-label)}.text-gold-foreground{color:var(--gold-foreground)}.text-link{color:var(--link)}.text-muted-foreground{color:var(--muted-foreground)}.text-primary-foreground{color:var(--primary-foreground)}.text-secondary-foreground{color:var(--secondary-foreground)}.text-sidebar-foreground{color:var(--sidebar-foreground)}.text-sidebar-primary-foreground{color:var(--sidebar-primary-foreground)}.text-success-muted-foreground{color:var(--success-muted-foreground)}.text-text-disabled{color:var(--text-disabled)}.text-text-muted{color:var(--text-muted)}.text-text-subtle{color:var(--text-subtle)}.text-warning-muted-foreground{color:var(--warning-muted-foreground)}.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,)}.underline{text-decoration-line:underline}.underline-offset-4{text-underline-offset:4px}.accent-primary{accent-color:var(--primary)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.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-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))}.group-open\:rotate-180:is(:where(.group):is([open],:popover-open,:open) *){rotate:180deg}.peer-checked\:flex:is(:where(.peer):checked~*){display:flex}.placeholder\:text-form-placeholder::placeholder{color:var(--form-placeholder)}@media (hover:hover){.hover\:bg-navigation-hover:hover{background-color:var(--navigation-hover)}.hover\:bg-primary\/90:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab, var(--primary) 90%, transparent)}}.hover\:bg-sidebar-accent:hover{background-color:var(--sidebar-accent)}.hover\:bg-table-row-hover:hover{background-color:var(--table-row-hover)}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:text-link-hover:hover{color:var(--link-hover)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:border-ring:focus{border-color:var(--ring)}@media (min-width:40rem){.sm\:px-6{padding-inline:calc(var(--spacing) * 6)}}@media (min-width:64rem){.lg\:sticky{position:sticky}.lg\:top-0{top:0}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:h-screen{height:100vh}.lg\:w-60{width:calc(var(--spacing) * 60)}.lg\:flex-row{flex-direction:row}.lg\:overflow-y-auto{overflow-y:auto}.lg\:border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.lg\:border-b-0{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.lg\:py-4{padding-block:calc(var(--spacing) * 4)}}@media (min-width:80rem){.xl\:w-64{width:calc(var(--spacing) * 64)}}.\[\&\:\:-webkit-details-marker\]\:hidden::-webkit-details-marker{display:none}}:root{--background:oklch(100% 0 0);--foreground:oklch(14.5% 0 0);--surface:oklch(100% 0 0);--surface-foreground:oklch(14.5% 0 0);--surface-muted:oklch(98.5% 0 0);--surface-muted-foreground:oklch(55.6% 0 0);--card:oklch(100% 0 0);--card-foreground:oklch(14.5% 0 0);--card-muted:oklch(98.5% 0 0);--popover:oklch(100% 0 0);--popover-foreground:oklch(14.5% 0 0);--overlay:oklch(14.5% 0 0/.5);--overlay-foreground:oklch(100% 0 0);--primary:oklch(20.5% 0 0);--primary-foreground:oklch(98.5% 0 0);--secondary:oklch(97% 0 0);--secondary-foreground:oklch(20.5% 0 0);--muted:oklch(97% 0 0);--muted-foreground:oklch(55.6% 0 0);--accent:oklch(97% 0 0);--accent-foreground:oklch(20.5% 0 0);--destructive:oklch(57.7% .245 27.325);--destructive-foreground:oklch(98.5% 0 0);--success:oklch(59.6% .145 163.225);--success-foreground:oklch(98.5% 0 0);--success-muted:oklch(96.2% .044 156.743);--success-muted-foreground:oklch(44.8% .119 151.328);--warning:oklch(68.1% .162 75.834);--warning-foreground:oklch(20.5% 0 0);--warning-muted:oklch(98.7% .026 102.212);--warning-muted-foreground:oklch(47.6% .114 61.907);--info:oklch(62.3% .214 259.815);--info-foreground:oklch(98.5% 0 0);--info-muted:oklch(97% .014 254.604);--info-muted-foreground:oklch(48.8% .217 264.376);--border:oklch(92.2% 0 0);--border-muted:oklch(96% 0 0);--input:oklch(92.2% 0 0);--input-background:oklch(100% 0 0);--ring:oklch(70.8% 0 0);--focus:oklch(62.3% .214 259.815);--text:oklch(14.5% 0 0);--text-muted:oklch(55.6% 0 0);--text-subtle:oklch(70.8% 0 0);--text-disabled:oklch(70.8% 0 0);--text-inverse:oklch(98.5% 0 0);--selection:oklch(20.5% 0 0);--selection-foreground:oklch(98.5% 0 0);--link:oklch(48.8% .243 264.376);--link-hover:oklch(42.4% .199 265.638);--link-visited:oklch(48.8% .243 264.376);--table-background:oklch(100% 0 0);--table-header:oklch(98.5% 0 0);--table-row:oklch(100% 0 0);--table-row-hover:oklch(98.5% 0 0);--table-row-selected:oklch(97% 0 0);--table-border:oklch(92.2% 0 0);--form-label:oklch(20.5% 0 0);--form-description:oklch(55.6% 0 0);--form-placeholder:oklch(70.8% 0 0);--form-error:oklch(57.7% .245 27.325);--form-success:oklch(59.6% .145 163.225);--navigation-background:oklch(100% 0 0);--navigation-foreground:oklch(14.5% 0 0);--navigation-hover:oklch(97% 0 0);--navigation-active:oklch(97% 0 0);--navigation-active-foreground:oklch(20.5% 0 0);--navigation-border:oklch(92.2% 0 0);--sidebar:oklch(98.5% 0 0);--sidebar-foreground:oklch(14.5% 0 0);--sidebar-primary:oklch(20.5% 0 0);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(97% 0 0);--sidebar-accent-foreground:oklch(20.5% 0 0);--sidebar-muted:oklch(55.6% 0 0);--sidebar-border:oklch(92.2% 0 0);--sidebar-ring:oklch(70.8% 0 0);--header:oklch(100% 0 0);--header-foreground:oklch(14.5% 0 0);--header-border:oklch(92.2% 0 0);--breadcrumb-foreground:oklch(55.6% 0 0);--breadcrumb-current:oklch(14.5% 0 0);--breadcrumb-separator:oklch(70.8% 0 0);--badge-background:oklch(97% 0 0);--badge-foreground:oklch(20.5% 0 0);--code-background:oklch(97% 0 0);--code-foreground:oklch(20.5% 0 0);--chart-1:oklch(64.6% .222 41.116);--chart-2:oklch(60% .118 184.704);--chart-3:oklch(39.8% .07 227.392);--chart-4:oklch(82.8% .189 84.429);--chart-5:oklch(76.9% .188 70.08);--radius:.625rem;--radius-xs:calc(var(--radius) - 6px);--radius-sm:calc(var(--radius) - 4px);--radius-md:calc(var(--radius) - 2px);--radius-lg:var(--radius);--radius-xl:calc(var(--radius) + 4px);--radius-2xl:calc(var(--radius) + 8px);--radius-3xl:calc(var(--radius) + 12px);--radius-full:9999px}.dark{--background:oklch(14.5% 0 0);--foreground:oklch(98.5% 0 0);--surface:oklch(20.5% 0 0);--surface-foreground:oklch(98.5% 0 0);--surface-muted:oklch(18% 0 0);--surface-muted-foreground:oklch(70.8% 0 0);--card:oklch(20.5% 0 0);--card-foreground:oklch(98.5% 0 0);--card-muted:oklch(26.9% 0 0);--popover:oklch(20.5% 0 0);--popover-foreground:oklch(98.5% 0 0);--overlay:oklch(0% 0 0/.65);--overlay-foreground:oklch(98.5% 0 0);--primary:oklch(92.2% 0 0);--primary-foreground:oklch(20.5% 0 0);--secondary:oklch(26.9% 0 0);--secondary-foreground:oklch(98.5% 0 0);--muted:oklch(26.9% 0 0);--muted-foreground:oklch(70.8% 0 0);--accent:oklch(26.9% 0 0);--accent-foreground:oklch(98.5% 0 0);--destructive:oklch(70.4% .191 22.216);--destructive-foreground:oklch(98.5% 0 0);--success:oklch(69.6% .17 162.48);--success-foreground:oklch(14.5% 0 0);--success-muted:oklch(26.9% .07 156.743);--success-muted-foreground:oklch(76.5% .13 152.461);--warning:oklch(76.9% .188 70.08);--warning-foreground:oklch(20.5% 0 0);--warning-muted:oklch(26.9% .06 85);--warning-muted-foreground:oklch(82.8% .15 84);--info:oklch(70.7% .165 254.624);--info-foreground:oklch(14.5% 0 0);--info-muted:oklch(26.9% .06 255);--info-muted-foreground:oklch(70.7% .165 254.624);--border:oklch(100% 0 0/.1);--border-muted:oklch(100% 0 0/.06);--input:oklch(100% 0 0/.15);--input-background:oklch(20.5% 0 0);--ring:oklch(55.6% 0 0);--focus:oklch(70.7% .165 254.624);--text:oklch(98.5% 0 0);--text-muted:oklch(70.8% 0 0);--text-subtle:oklch(55.6% 0 0);--text-disabled:oklch(55.6% 0 0);--text-inverse:oklch(14.5% 0 0);--selection:oklch(92.2% 0 0);--selection-foreground:oklch(20.5% 0 0);--link:oklch(70.7% .165 254.624);--link-hover:oklch(80.9% .105 251.813);--link-visited:oklch(70.7% .165 254.624);--table-background:oklch(20.5% 0 0);--table-header:oklch(18% 0 0);--table-row:oklch(20.5% 0 0);--table-row-hover:oklch(26.9% 0 0);--table-row-selected:oklch(26.9% 0 0);--table-border:oklch(100% 0 0/.1);--form-label:oklch(98.5% 0 0);--form-description:oklch(70.8% 0 0);--form-placeholder:oklch(55.6% 0 0);--form-error:oklch(70.4% .191 22.216);--form-success:oklch(69.6% .17 162.48);--navigation-background:oklch(20.5% 0 0);--navigation-foreground:oklch(98.5% 0 0);--navigation-hover:oklch(26.9% 0 0);--navigation-active:oklch(26.9% 0 0);--navigation-active-foreground:oklch(98.5% 0 0);--navigation-border:oklch(100% 0 0/.1);--sidebar:oklch(20.5% 0 0);--sidebar-foreground:oklch(98.5% 0 0);--sidebar-primary:oklch(48.8% .243 264.376);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(26.9% 0 0);--sidebar-accent-foreground:oklch(98.5% 0 0);--sidebar-muted:oklch(70.8% 0 0);--sidebar-border:oklch(100% 0 0/.1);--sidebar-ring:oklch(55.6% 0 0);--header:oklch(20.5% 0 0);--header-foreground:oklch(98.5% 0 0);--header-border:oklch(100% 0 0/.1);--breadcrumb-foreground:oklch(70.8% 0 0);--breadcrumb-current:oklch(98.5% 0 0);--breadcrumb-separator:oklch(55.6% 0 0);--badge-background:oklch(26.9% 0 0);--badge-foreground:oklch(98.5% 0 0);--code-background:oklch(26.9% 0 0);--code-foreground:oklch(98.5% 0 0);--chart-1:oklch(48.8% .243 264.376);--chart-2:oklch(69.6% .17 162.48);--chart-3:oklch(76.9% .188 70.08);--chart-4:oklch(62.7% .265 303.9);--chart-5:oklch(64.5% .246 16.439)}:root{--background:#f4f5f7;--foreground:#101318;--surface:#fff;--surface-foreground:#101318;--surface-muted:#eef0f3;--surface-muted-foreground:#565d68;--card:#fff;--card-foreground:#101318;--card-muted:#eef0f3;--popover:#fff;--popover-foreground:#101318;--primary:#1a2c50;--primary-foreground:#fff;--secondary:#e8eaee;--secondary-foreground:#101318;--muted:#e8eaee;--muted-foreground:#565d68;--accent:#e8eaee;--accent-foreground:#101318;--destructive:#b3261e;--destructive-foreground:#fff;--success:#1e7a4c;--success-foreground:#fff;--success-muted:#e3f1e9;--success-muted-foreground:#145c39;--warning:#8a6a12;--warning-foreground:#fff;--warning-muted:#f6efdb;--warning-muted-foreground:#6b5311;--info:#1a2c50;--info-foreground:#fff;--info-muted:#e9edf6;--info-muted-foreground:#1a2c50;--gold:#c9a227;--gold-foreground:#101318;--border:#e2e5ea;--border-muted:#edeff2;--input:#c9ced6;--input-background:#fff;--ring:#1a2c50;--focus:#1a2c50;--text:#101318;--text-muted:#565d68;--text-subtle:#767d87;--text-disabled:#9aa1ab;--text-inverse:#fff;--selection:#1a2c50;--selection-foreground:#fff;--link:#1a2c50;--link-hover:#0d1830;--link-visited:#1a2c50;--table-background:#fff;--table-header:#fff;--table-row:#fff;--table-row-hover:#f6f7f9;--table-row-selected:#eef1f6;--table-border:#edeff2;--form-label:#101318;--form-description:#565d68;--form-placeholder:#767d87;--form-error:#b3261e;--navigation-background:#f4f5f7;--navigation-foreground:#101318;--navigation-hover:#e8eaee;--navigation-active:#e8eaee;--navigation-active-foreground:#101318;--navigation-border:#e2e5ea;--sidebar:#f4f5f7;--sidebar-foreground:#101318;--sidebar-primary:#1a2c50;--sidebar-primary-foreground:#fff;--sidebar-accent:#e8eaee;--sidebar-accent-foreground:#101318;--sidebar-muted:#565d68;--sidebar-border:#e2e5ea;--sidebar-ring:#1a2c50;--header:#f4f5f7;--header-foreground:#101318;--header-border:#d5d9e0;--breadcrumb-foreground:#565d68;--breadcrumb-current:#101318;--breadcrumb-separator:#9aa1ab;--badge-background:#eef0f3;--badge-foreground:#101318;--code-background:#eef0f3;--code-foreground:#101318;--radius:6px}.dark{--background:#0b0d12;--foreground:#e9edf4;--surface:#12151c;--surface-foreground:#e9edf4;--surface-muted:#171b23;--surface-muted-foreground:#97a0ad;--card:#12151c;--card-foreground:#e9edf4;--card-muted:#171b23;--popover:#171b23;--popover-foreground:#e9edf4;--primary:#e9edf4;--primary-foreground:#0b0d12;--secondary:#1a1f28;--secondary-foreground:#e9edf4;--muted:#1a1f28;--muted-foreground:#97a0ad;--accent:#1a1f28;--accent-foreground:#e9edf4;--destructive:#ef6b62;--destructive-foreground:#0b0d12;--success:#5cc98f;--success-foreground:#0b0d12;--success-muted:#16281f;--success-muted-foreground:#5cc98f;--warning:#d9ab4a;--warning-foreground:#0b0d12;--warning-muted:#2a2313;--warning-muted-foreground:#d9ab4a;--info:#9db8ea;--info-foreground:#0b0d12;--info-muted:#16203a;--info-muted-foreground:#9db8ea;--gold:#d4a72c;--gold-foreground:#101318;--border:#262c38;--border-muted:#1a1f28;--input:#333a48;--input-background:#12151c;--ring:#97a0ad;--focus:#8fb0e8;--text:#e9edf4;--text-muted:#97a0ad;--text-subtle:#7d8794;--text-disabled:#565f6b;--text-inverse:#0b0d12;--selection:#e9edf4;--selection-foreground:#0b0d12;--link:#9db8ea;--link-hover:#c2d4f5;--link-visited:#9db8ea;--table-background:#12151c;--table-header:#12151c;--table-row:#12151c;--table-row-hover:#181d26;--table-row-selected:#1c2330;--table-border:#1c212b;--form-label:#e9edf4;--form-description:#97a0ad;--form-placeholder:#7d8794;--form-error:#ef6b62;--navigation-background:#0b0d12;--navigation-foreground:#e9edf4;--navigation-hover:#1a1f28;--navigation-active:#1a1f28;--navigation-active-foreground:#e9edf4;--navigation-border:#262c38;--sidebar:#0b0d12;--sidebar-foreground:#e9edf4;--sidebar-primary:#e9edf4;--sidebar-primary-foreground:#0b0d12;--sidebar-accent:#1a1f28;--sidebar-accent-foreground:#e9edf4;--sidebar-muted:#97a0ad;--sidebar-border:#262c38;--sidebar-ring:#8fb0e8;--header:#0b0d12;--header-foreground:#e9edf4;--header-border:#262c38;--breadcrumb-foreground:#97a0ad;--breadcrumb-current:#e9edf4;--breadcrumb-separator:#565f6b;--badge-background:#1a1f28;--badge-foreground:#e9edf4;--code-background:#1a1f28;--code-foreground:#e9edf4}@media (prefers-reduced-motion:reduce){*,:before,:after{transition-duration:.01ms!important;animation-duration:.01ms!important}}dialog{max-width:min(32rem,100vw - 2rem);max-height:calc(100vh - 2rem);margin:auto}dialog::backdrop{background-color:var(--overlay)}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
@@ -11,10 +11,10 @@ export interface AuthCheck {
11
11
  *
12
12
  * - `undefined` / `true` → allow
13
13
  * - `false` → 403
14
- * - `string` → treated as required permission. Checked against
15
- * `permissions` decorated on context (e.g. by an auth plugin) or the
16
- * `x-permissions` header (comma-separated). Allowed when no auth source
17
- * is wired yet (TODO: strict mode once auth plugin lands).
14
+ * - `string` → required permission. Satisfied only by a server-trusted
15
+ * source: `permissions` decorated on context (e.g. by an auth plugin) or
16
+ * `permissions` in `app.state`. The caller-controlled `x-permissions`
17
+ * header is never trusted. No source wired → deny (fail closed).
18
18
  * - `function` → awaited, `false` → 403
19
19
  */
20
20
  export declare function authorize(operation: Rack.CrudOperation, rule: Rack.CrudAuthorizationRule | undefined, ctx: AuthCheck): Promise<import("elysia").ElysiaCustomStatusResponse<403, {
@@ -1,9 +1,11 @@
1
1
  import type { Rack } from "./types";
2
2
  /**
3
- * In-memory idempotency store with lazy expiry and a bounded size
4
- * (oldest entries are evicted first).
3
+ * In-memory idempotency store with lazy expiry.
5
4
  *
6
- * Scoped per rack instance. Use a shared store (e.g. Redis) via
7
- * `idempotency.store` when running multiple instances.
5
+ * Live entries (within their TTL) are never evicted, so the execute-once
6
+ * guarantee holds for the life of the process. `maxEntries` is a soft
7
+ * ceiling that triggers an expired-entry purge; memory grows with the number
8
+ * of unique keys issued within the TTL window. Use a durable shared store
9
+ * (e.g. Redis) for strict execute-once across restarts or multiple instances.
8
10
  */
9
11
  export declare function createMemoryIdempotencyStore(maxEntries?: number): Rack.CrudIdempotencyStore;
@@ -796,6 +796,12 @@ async function firstRow(promise) {
796
796
  async function applyUpdate(db, table, cond, body) {
797
797
  return firstRow(db.update(table).set(body).where(cond).returning());
798
798
  }
799
+ function keyCondition(table, opts, key, body) {
800
+ const cond = eq(table[opts.primaryKey], key);
801
+ const restoring = opts.softDelete === true && typeof body === "object" && body !== null && body[opts.deletedAtField ?? "deletedAt"] === null;
802
+ const alive = restoring ? undefined : aliveCondition(table, opts, opts.softDelete ?? false);
803
+ return alive ? and(cond, alive) : cond;
804
+ }
799
805
  async function findByKey(db, table, opts, key) {
800
806
  const cond = eq(table[opts.primaryKey], key);
801
807
  const alive = aliveCondition(table, opts, opts.softDelete ?? false);
@@ -869,7 +875,7 @@ var drizzleAdapter = {
869
875
  return null;
870
876
  if (isEmptyBody(body))
871
877
  return findByKey(db, table, opts, key);
872
- const row = await applyUpdate(db, table, eq(table[opts.primaryKey], key), body);
878
+ const row = await applyUpdate(db, table, keyCondition(table, opts, key, body), body);
873
879
  if (row === null)
874
880
  return null;
875
881
  return opts.returning === false ? undefined : row;
@@ -882,7 +888,7 @@ var drizzleAdapter = {
882
888
  return null;
883
889
  if (isEmptyBody(body))
884
890
  return findByKey(db, table, opts, key);
885
- const row = await applyUpdate(db, table, eq(table[opts.primaryKey], key), body);
891
+ const row = await applyUpdate(db, table, keyCondition(table, opts, key, body), body);
886
892
  if (row === null)
887
893
  return null;
888
894
  return opts.returning === false ? undefined : row;
@@ -909,10 +915,14 @@ function deletedAtFilter(opts, includeDeleted = false) {
909
915
  return opts.softDelete && !includeDeleted ? { [opts.deletedAtField ?? "deletedAt"]: null } : {};
910
916
  }
911
917
  function whereFor2(query, opts) {
918
+ const hideDeleted = opts.softDelete === true && !query.includeDeleted;
919
+ const deletedField = opts.deletedAtField ?? "deletedAt";
912
920
  const where = {
913
921
  ...deletedAtFilter(opts, query.includeDeleted)
914
922
  };
915
923
  for (const [key, value] of Object.entries(query.filters)) {
924
+ if (hideDeleted && key === deletedField)
925
+ continue;
916
926
  where[key] = Array.isArray(value) ? { in: value } : value;
917
927
  }
918
928
  if (query.search && query.search.fields.length > 0) {
@@ -1023,15 +1033,8 @@ async function authorize(operation, rule, ctx) {
1023
1033
  if (rule === false)
1024
1034
  return forbidden();
1025
1035
  if (typeof rule === "string") {
1026
- const fromContext = Array.isArray(ctx.permissions) ? ctx.permissions : Array.isArray(ctx.store?.["permissions"]) ? ctx.store?.["permissions"] : null;
1027
- if (fromContext !== null)
1028
- return fromContext.includes(rule) ? null : forbidden({ requiredPermission: rule });
1029
- const header = ctx.request.headers.get("x-permissions");
1030
- if (typeof header === "string" && header.length > 0) {
1031
- const granted = header.split(",").map((s) => s.trim());
1032
- return granted.includes(rule) ? null : forbidden({ requiredPermission: rule });
1033
- }
1034
- return null;
1036
+ const granted = Array.isArray(ctx.permissions) ? ctx.permissions : Array.isArray(ctx.store?.["permissions"]) ? ctx.store?.["permissions"] : null;
1037
+ return granted !== null && granted.includes(rule) ? null : forbidden({ requiredPermission: rule });
1035
1038
  }
1036
1039
  const allowed = await rule({
1037
1040
  operation,
@@ -1057,14 +1060,16 @@ function createMemoryIdempotencyStore(maxEntries = 1000) {
1057
1060
  return entry.record;
1058
1061
  },
1059
1062
  set(key, record, ttlSeconds = 86400) {
1060
- if (entries.size >= maxEntries && !entries.has(key)) {
1061
- const oldest = entries.keys().next();
1062
- if (!oldest.done)
1063
- entries.delete(oldest.value);
1063
+ const now = Date.now();
1064
+ if (entries.size >= maxEntries) {
1065
+ for (const [k, entry] of entries) {
1066
+ if (now >= entry.expiresAt)
1067
+ entries.delete(k);
1068
+ }
1064
1069
  }
1065
1070
  entries.set(key, {
1066
1071
  record,
1067
- expiresAt: Date.now() + ttlSeconds * 1000
1072
+ expiresAt: now + ttlSeconds * 1000
1068
1073
  });
1069
1074
  }
1070
1075
  };
@@ -1269,7 +1274,8 @@ function rack(path, options) {
1269
1274
  operation: "create"
1270
1275
  });
1271
1276
  } else {
1272
- const scoped = `${resource}::${clientKey}`;
1277
+ const scope = idem?.scope?.(authContext(context, "create")) ?? "";
1278
+ const scoped = `${resource}::${scope !== "" ? `${scope}::` : ""}${clientKey}`;
1273
1279
  const stored = await idemStore.get(scoped);
1274
1280
  if (stored !== undefined)
1275
1281
  return replay(context, stored);
@@ -6,7 +6,8 @@ export interface CrudIdempotencyRecord {
6
6
  /**
7
7
  * Pluggable idempotency store.
8
8
  *
9
- * Keys are already scoped per resource (`<resource>::<key>`).
9
+ * Keys are already scoped per resource and (when `idempotency.scope` is set)
10
+ * per caller: `<resource>::[<scope>::]<key>`.
10
11
  * Implementations must honor `ttlSeconds` when provided.
11
12
  */
12
13
  export interface CrudIdempotencyStore {
@@ -43,4 +44,19 @@ export interface CrudIdempotencyOptions {
43
44
  * Use a shared store (e.g. Redis) when running multiple instances.
44
45
  */
45
46
  store?: CrudIdempotencyStore;
47
+ /**
48
+ * Derive a server-trusted caller scope (for example a principal or tenant
49
+ * id) from the request context. Keys become
50
+ * `<resource>::<scope>::<key>`, so two callers that send the same key do
51
+ * not observe each other's stored response.
52
+ *
53
+ * When omitted, keys share one namespace per rack instance. That is safe
54
+ * only when idempotency keys are unguessable and never reused across
55
+ * callers; otherwise provide this to isolate callers.
56
+ */
57
+ scope?: (context: {
58
+ request: Request;
59
+ permissions?: unknown;
60
+ store?: Record<string, unknown>;
61
+ }) => string | undefined;
46
62
  }
@@ -1,5 +1,6 @@
1
1
  import Elysia from "elysia";
2
2
  import { ReactRack } from "./types";
3
+ export declare const lazyPage: typeof ReactRack.lazyPage;
3
4
  export declare function page<Props = Record<string, never>>(path: string, props?: Props): ReactRack.PageDescriptor<Props>;
4
5
  export declare const pages: ReactRack.PageRegistry;
5
6
  export declare function reactPlugin(options?: ReactRack.ReactPluginOptions): Elysia<"", {
@@ -149,6 +149,7 @@ function createRenderer(pages) {
149
149
  }
150
150
 
151
151
  // src/react/index.ts
152
+ var lazyPage = ReactRack.lazyPage;
152
153
  var PANEL_ASSETS = {
153
154
  "panel.css": "text/css; charset=utf-8",
154
155
  "panel-theme.js": "text/javascript; charset=utf-8",
@@ -318,5 +319,6 @@ ${custom}` : custom;
318
319
  export {
319
320
  reactPlugin,
320
321
  pages,
321
- page
322
+ page,
323
+ lazyPage
322
324
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "elysia-rack",
3
- "version": "0.5.5",
3
+ "version": "0.6.0",
4
4
  "description": "Model-driven CRUD rack for Elysia: REST + QUERY routes, validation, authorization, OpenAPI metadata, and React panel rendering.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -59,7 +59,8 @@
59
59
  "build:js": "bun build ./src/rack/index.ts ./src/react/index.ts --outdir ./dist --target node --format esm --splitting --external elysia --external react --external react-dom --external lucide-react",
60
60
  "build:client": "bun build src/react/page/client/panel.ts --outfile dist/panel-app.js --target browser --format esm && bun build src/react/page/client/theme.ts --outfile dist/panel-theme.js --target browser --format esm && mkdir -p dist && cp src/react/app.html dist/app.html 2>/dev/null || true",
61
61
  "build:dts": "tsc -p tsconfig.build.json",
62
- "prepublishOnly": "bun run typecheck && bun run build"
62
+ "prepublishOnly": "bun run typecheck && bun run build",
63
+ "docs:build": "vitepress build docs"
63
64
  },
64
65
  "peerDependencies": {
65
66
  "elysia": "^1.4.0",
@@ -77,6 +78,7 @@
77
78
  "@tailwindcss/cli": "4.3.3",
78
79
  "@types/bun": "latest",
79
80
  "@types/react": "^19.2.18",
81
+ "vitepress": "1",
80
82
  "@types/react-dom": "^19.2.5",
81
83
  "drizzle-orm": "^0.45.2",
82
84
  "elysia": "^1.4.30",