elysia-rack 0.5.2 → 0.5.4

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.
Files changed (3) hide show
  1. package/README.md +166 -433
  2. package/dist/panel.css +1 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,13 +1,22 @@
1
1
  # elysia-rack
2
2
 
3
- [![Ko-fi Donate](https://img.shields.io/badge/Ko--fi-Donate-ff5e5b?logo=ko-fi&logoColor=white)](https://ko-fi.com/notoofly_manu) [![npm](https://img.shields.io/npm/v/elysia-rack)](https://www.npmjs.com/package/elysia-rack)
3
+ [![npm version](https://img.shields.io/npm/v/elysia-rack)](https://www.npmjs.com/package/elysia-rack) [![license: MIT](https://img.shields.io/npm/l/elysia-rack)](LICENSE) [![Ko-fi Donate](https://img.shields.io/badge/Ko--fi-Donate-ff5e5b?logo=ko-fi&logoColor=white)](https://ko-fi.com/notoofly_manu)
4
4
 
5
- Model-driven CRUD rack for [Elysia](https://elysiajs.com): declare a resource once, get REST + `QUERY` routes, validation, authorization, query engine, OpenAPI metadata, and a React panel — running on [Bun](https://bun.com) or Node.js 20+.
6
-
7
- ## Documentation
5
+ Model-driven CRUD rack for [Elysia](https://elysiajs.com): declare a database model once and get REST routes, a `QUERY` read API, validation, authorization, OpenAPI metadata, and a React admin panel — running on [Bun](https://bun.com) or Node.js 20+.
8
6
 
9
7
  End-user documentation covers installation, configuration, daily usage, and troubleshooting: [docs/index.md](docs/index.md).
10
8
 
9
+ ## What it does
10
+
11
+ elysia-rack turns a single database table or model into a complete admin resource. You describe the model once with `rack()` and you get:
12
+
13
+ - **Routes**: `GET /` (panel), `QUERY /data` (list), `QUERY /data/:id` (detail), `POST /` (create), `PUT /:id` (replace), `PATCH /:id` (update), `DELETE /:id` (delete).
14
+ - **A React panel**: table, create/edit/detail dialogs, soft-delete restore, bulk delete and field updates, search, filtering, sorting, and pagination.
15
+ - **Safety**: per-operation validation (TypeBox via Elysia), per-operation authorization, idempotent creates, and OpenAPI metadata for generated routes.
16
+ - **ORM support**: Drizzle (Postgres, SQLite, …) or Prisma. Panel assets are served under `/__rack/*` after `bun run build`.
17
+
18
+ The panel reads and writes through the custom `QUERY` HTTP method, so reverse proxies, load balancers, and CDNs must allow it. There is no built-in login system — you add authentication and authorization rules yourself.
19
+
11
20
  ## Install
12
21
 
13
22
  ```bash
@@ -16,503 +25,229 @@ bun add elysia-rack elysia react react-dom
16
25
  npm i elysia-rack elysia react react-dom
17
26
  ```
18
27
 
19
- `elysia`, `react`, and `react-dom` are peer dependencies.
28
+ `elysia`, `react`, and `react-dom` are peer dependencies. `drizzle-orm` is an optional peer (only needed for the Drizzle adapter).
20
29
 
21
- ## Usage
30
+ Supported runtimes: Bun >= 1.0.0, or Node.js >= 20.0.0. Elysia >= 1.4.0 and React >= 18 are required.
31
+
32
+ ## Quick start
22
33
 
23
34
  ```ts
24
35
  import { Elysia } from "elysia";
25
36
  import { rack } from "elysia-rack";
37
+ import { reactPlugin } from "elysia-rack/react";
26
38
 
27
- const app = new Elysia().use(
28
- rack("/catalog/products", {
29
- model: { drizzle: { db, table: products } },
30
-
31
- validation: {
32
- create: CreateProductSchema,
33
- replace: ReplaceProductSchema,
34
- update: UpdateProductSchema,
35
- params: ProductParamsSchema,
36
- query: ProductQuerySchema,
37
- },
38
-
39
- authorization: {
40
- list: "products.view",
41
- create: "products.create",
42
- delete: async ({ request }) => canDelete(request),
43
- },
44
-
45
- query: {
46
- searchable: ["name", "sku"],
47
- filterable: ["status", "categoryId"],
48
- sortable: ["name", "price", "createdAt"],
49
- defaultSort: { field: "createdAt", direction: "desc" },
50
- pagination: { default: 20, max: 100 },
51
- },
52
-
53
- operations: { create: true, delete: false },
54
-
55
- metadata: {
56
- id: "products",
57
- label: "Product",
58
- pluralLabel: "Products",
59
- group: "Catalog",
60
- icon: "package",
61
- fields: {
62
- name: { label: "Product name" },
63
- price: { format: "currency", currency: "USD" },
64
- categoryId: {
65
- label: "Category",
66
- relation: { load: () => loadCategoryOptions() },
67
- },
39
+ // `db` is your Drizzle instance; `products` is your drizzle-orm table.
40
+ const app = new Elysia()
41
+ .use(reactPlugin())
42
+ .use(
43
+ rack("/catalog/products", {
44
+ model: { drizzle: { db, table: products } },
45
+ metadata: {
46
+ id: "products",
47
+ label: "Product",
48
+ pluralLabel: "Products",
49
+ group: "Catalog",
50
+ icon: "package",
68
51
  },
69
- },
70
-
71
- actions: [
72
- { label: "Duplicate", href: "/catalog/products/:id/duplicate", method: "POST" },
73
- { label: "Export CSV", href: "/catalog/products/export", scope: "toolbar" },
74
- ],
75
-
76
- openapi: { tags: ["Products"] },
77
- settings: { primaryKey: "id", returning: true },
78
- }),
79
- );
80
-
81
- app.listen(3000);
52
+ settings: { primaryKey: "id", returning: true },
53
+ }),
54
+ )
55
+ .listen(3000);
82
56
  ```
83
57
 
84
- Generated routes (all gated by `operations`, default on):
85
-
86
- | Operation | Method | Path |
87
- |---|---|---|
88
- | display panel | `GET` | `/` |
89
- | list data | `QUERY` | `/data` |
90
- | detail data | `QUERY` | `/data/:id` |
91
- | create | `POST` | `/` |
92
- | replace | `PUT` | `/:id` |
93
- | update | `PATCH` | `/:id` |
94
- | delete | `DELETE` | `/:id` |
95
-
96
- `GET /` renders the React panel via `page("/panel")` (customize with the
97
- `page` option, disable with `page: { enabled: false }`). All data reads go
98
- through the `QUERY` method under `/data`. The panel UI calls the same
99
- endpoint live via fetch (progressive enhancement — links and forms still
100
- work through plain `GET` navigation without JavaScript).
101
-
102
- ## Panel actions
103
-
104
- The panel ships create / edit / delete UI driven by the same API:
105
-
106
- - `New record` opens a create dialog (fields derived from row columns,
107
- primary key optional). Submit sends `POST /` with an auto-generated
108
- `Idempotency-Key`. Tables without known columns fall back to a raw
109
- JSON body field.
110
- - Row `Edit` loads the row via `QUERY /data/:id` into a dialog and saves
111
- with `PATCH /:id`. Empty inputs are skipped (partial update).
112
- - Row `Delete` confirms natively, then `DELETE /:id`.
113
- - Checkbox selection enables the bulk bar: `Delete selected` loops
114
- `DELETE /:id`, and the field applier loops `PATCH /:id` with the
115
- chosen field/value (coerced to the field kind). `Clear` resets the selection.
116
- - Row `View` loads the record through `QUERY /data/:id` into a read-only dialog.
117
- - With `settings.softDelete`, the toolbar adds `Show deleted`. Deleted rows
118
- show a `Restore` button that clears `deletedAt` via `PATCH /:id`.
119
-
120
- Mutations report success and failures with toast messages and disable the
121
- submit button while a request is in flight.
122
-
123
- Action UI follows `operations`: disable `create`/`update`/`delete` and
124
- the corresponding buttons, dialogs and bulk controls disappear.
58
+ That gives you `GET /catalog/products` (panel), `QUERY /catalog/products/data` (list), `POST /catalog/products` (create), and `PUT/PATCH/DELETE /catalog/products/:id`.
125
59
 
126
- ### Custom actions
60
+ ## Routes
127
61
 
128
- Add extra buttons with the `actions` option. `:id` in `href` is replaced
129
- with the row primary key. Row actions (default) sit next to
130
- Edit/Delete; `scope: "toolbar"` actions sit in the page header.
62
+ | Operation | Method | Path | Gated by |
63
+ |---|---|---|---|
64
+ | Panel | `GET` | `/` | `operations.list` + `page.enabled` |
65
+ | List | `QUERY` | `/data` | `operations.list` |
66
+ | Detail | `QUERY` | `/data/:id` | `operations.detail` |
67
+ | Create | `POST` | `/` | `operations.create` |
68
+ | Replace | `PUT` | `/:id` | `operations.replace` |
69
+ | Update | `PATCH` | `/:id` | `operations.update` |
70
+ | Delete | `DELETE` | `/:id` | `operations.delete` |
131
71
 
132
- ```ts
133
- rack("/catalog/products", {
134
- model,
135
- actions: [
136
- { label: "Duplicate", href: "/catalog/products/:id/duplicate", method: "POST" },
137
- { label: "Export CSV", href: "/catalog/products/export", scope: "toolbar", icon: "download" }
138
- ]
139
- });
140
- ```
72
+ All operations default to `true`. Turn any off with `operations: { create: false }`, etc.
141
73
 
142
- Without `method`, the button is a plain link. With `method`, the panel
143
- runs it via `fetch` and refreshes the table; `confirm` shows a native
144
- confirmation first.
74
+ ## Configuration
145
75
 
146
- Custom action endpoints are yours to protect. The panel does not apply the
147
- rack's `authorization` rules to them, so call `authorize()` (or your own
148
- guard) inside the route that handles the action URL.
76
+ `rack(path, options)` accepts:
149
77
 
150
- ## Idempotency (POST)
78
+ | Option | Purpose |
79
+ |---|---|
80
+ | `model` | `{ drizzle: { db, table } }` or `{ prisma: { client, model } }` |
81
+ | `validation` | TypeBox schemas for `create`, `replace`, `update`, `params`, `query` |
82
+ | `authorization` | Per-operation rule: `true`/`false`/permission string/function |
83
+ | `query` | `searchable`, `filterable`, `sortable`, `defaultSort`, `pagination` |
84
+ | `operations` | Which CRUD operations to enable (all on by default) |
85
+ | `metadata` | `id`, `label`, `pluralLabel`, `group`, `icon`, `order`, `parent`, `hidden`, `hiddenFields`, `fields` |
86
+ | `actions` | Custom row/toolbar buttons (`label`, `href` with `:id`, `method`, `confirm`, `icon`, `scope`) |
87
+ | `settings` | `primaryKey` (default `"id"`), `softDelete`, `deletedAtField` (default `"deletedAt"`), `returning` |
88
+ | `page` | Panel route: `{ enabled?: boolean, path?: string }` (default path `"/panel"`) |
89
+ | `openapi` | `tags`, `description`, per-operation summaries |
90
+ | `idempotency` | `required` (default `true`), `header` (default `"Idempotency-Key"`), `ttl` seconds (default `86400`), `store` |
151
91
 
152
- `POST /` requires an idempotency key by default. Send a client-generated
153
- key per create attempt:
92
+ ### Fields and relations
154
93
 
155
- ```bash
156
- curl -X POST localhost:3000/catalog/products \
157
- -H 'Content-Type: application/json' \
158
- -H 'Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000' \
159
- -d '{"name":"Keyboard"}'
160
- ```
161
-
162
- Replays with the same key return the stored `201` response with an
163
- `Idempotent-Replayed: true` header instead of re-executing. Missing keys
164
- are rejected with `400`. Tune via the `idempotency` option:
94
+ `metadata.fields` configures per-field display:
165
95
 
166
96
  ```ts
167
- rack("/catalog/products", {
168
- model: { drizzle: { db, table: products } },
169
- idempotency: {
170
- required: false, // allow keyless POST (default true)
171
- header: "X-Idempotency-Key",
172
- ttl: 3600, // replay window in seconds (default 86400)
173
- store: myRedisStore, // shared store for multi-instance deploys
97
+ metadata: {
98
+ fields: {
99
+ name: { label: "Product name", order: 1 },
100
+ price: { format: "currency", currency: "USD", order: 2 },
101
+ status: { format: "badge", order: 3 },
102
+ categoryId: {
103
+ label: "Category",
104
+ order: 4,
105
+ relation: {
106
+ load: async () => loadCategories(), // or static options: [...]
107
+ },
108
+ },
109
+ cost: { hideInTable: true }, // editable in forms, not listed
174
110
  },
175
- });
176
- ```
177
-
178
- ## ORM adapters
179
-
180
- `model` selects the adapter. Drizzle needs `drizzle-orm` installed
181
- (optional peer); Prisma needs no extra dependency — pass any client
182
- delegate (e.g. `prisma.product`):
183
-
184
- ```ts
185
- // Drizzle (Postgres, SQLite, ...)
186
- import { drizzle } from "drizzle-orm/node-postgres";
187
- model: { drizzle: { db: drizzle(client), table: products } },
188
-
189
- // Prisma
190
- model: { prisma: { client: prisma, model: prisma.product } },
111
+ }
191
112
  ```
192
113
 
193
- Conventions:
194
-
195
- - String route ids are coerced to numbers for integer columns
196
- (e.g. `serial`); anything else passes through. Malformed ids resolve
197
- to `404`, missing rows to `404`.
198
- - `searchable` fields use SQL `ILIKE` through the Drizzle adapter, so search requires a database that supports `ILIKE` (for example PostgreSQL). SQLite search fails with a SQL syntax error.
199
- - `settings.softDelete: true` writes a timestamp to `deletedAt`
200
- (override with `settings.deletedAtField`) instead of deleting, and
201
- reads skip soft-deleted rows.
202
- - `settings.returning: false` omits `data` from mutation responses.
203
-
204
- ## Dashboard
114
+ Supported `format` values: `text`, `number`, `currency`, `date`, `datetime`, `boolean`, `badge`, `json`. A `relation` renders a `<select>` in forms and shows the related label in the table and detail view. `hiddenFields` hides a field from table columns, forms, and the bulk picker.
205
115
 
206
- `dashboard()` renders a navigation overview of every registered resource, with a sidebar and a resource table. Each `rack()` panel also shows a sidebar with the entire tree.
207
-
208
- Recommended setup:
209
-
210
- ```ts
211
- import { dashboard, rack } from "elysia-rack";
212
- import { reactPlugin } from "elysia-rack/react";
213
-
214
- const app = new Elysia()
215
- .use(reactPlugin()) // pages are optional — defaults to dashboard + panel
216
- .use(dashboard({ title: "My Panel", path: "/" })) // → Dashboard at /
217
- .use(rack("/catalog/products", { model, metadata: { id: "products", group: "Catalog", order: 1 } }))
218
- .use(rack("/catalog/variants", { model, metadata: { id: "variants", group: "Catalog", order: 2, parent: "products" } }));
219
- // → Panel at /catalog/products now shows sidebar with Catalog > Products > Variants
220
- ```
221
-
222
- ### Tree metadata
223
-
224
- `metadata.parent` builds a hierarchy. Children are rendered nested under their parent in `panel`’s sidebar and kept ordered by `order` then label.
225
-
226
- ```ts
227
- rack("/catalog/products", { metadata: { id: "products", group: "Catalog", order: 1 } });
228
- rack("/catalog/variants", { metadata: { id: "variants", parent: "products", group: "Catalog", order: 2 } });
229
- ```
230
-
231
- Helpers: `listRacks()`, `getRack(id)`, `getRackTree()`, `flatRackTree()`, `buildRackTree(racks)`, `clearRacks()` from `elysia-rack`.
232
-
233
- ### Donate — disable on website
234
- Donate link (Ko-fi) tampil di Dashboard & Panel footer. Disable via `dashboard({ donate: false })` atau global `reactPlugin({ donate: false })` (default `true`).
116
+ ### Custom actions
235
117
 
236
- You can still render the dashboard manually:
118
+ Add extra buttons to the panel:
237
119
 
238
120
  ```ts
239
- import { page, reactPlugin } from "elysia-rack/react";
240
-
241
- const app = new Elysia()
242
- .use(reactPlugin())
243
- .use(rack("/catalog/products", { model, metadata: { ... } }))
244
- .get("/", () => page("/dashboard", { name: "My Shop" }));
121
+ actions: [
122
+ {
123
+ label: "Duplicate",
124
+ href: "/catalog/products/:id/duplicate",
125
+ method: "POST",
126
+ confirm: "Duplicate this product?",
127
+ },
128
+ { label: "Export CSV", href: "/catalog/products/export", scope: "toolbar", icon: "download" },
129
+ ]
245
130
  ```
246
131
 
247
- `panel` now handles the sidebar + breadcrumb. Each panel loads the full tree via `getRackTree()`, highlights the active resource (`props.resource`), and links via `href: node.path`. No iframe needed.
248
-
249
- ## Tutorial — make it yours
132
+ `:id` in `href` is replaced with the row primary key. Row actions (`scope: "row"`, the default) sit next to Edit/Delete; `scope: "toolbar"` actions sit in the page header. Without `method` the button is a plain link; with `method` the panel calls it via `fetch` and refreshes the table. `confirm` shows a native confirmation first.
250
133
 
251
- This guide walks you through `rack()`, `dashboard()` and `react()` the way you would build a real app. Copy, run, tweak.
134
+ Custom action endpoints are yours to protect — the rack's `authorization` rules do not apply to them. Guard the route that handles the action URL yourself.
252
135
 
253
- ### 1) `rack()` — describe a resource, get an API + panel
136
+ ### Idempotency (create)
254
137
 
255
- Start with the only required thing: which table/model to use.
256
-
257
- ```ts
258
- import { rack } from "elysia-rack";
138
+ `POST /` requires an idempotency key by default:
259
139
 
260
- rack("/catalog/products", {
261
- model: { drizzle: { db, table: products } } // or { prisma: { client: prisma, model: prisma.product } }
262
- });
140
+ ```bash
141
+ curl -X POST localhost:3000/catalog/products \
142
+ -H 'Content-Type: application/json' \
143
+ -H 'Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000' \
144
+ -d '{"name":"Keyboard"}'
263
145
  ```
264
146
 
265
- You already get `GET /`, `QUERY /data`, `POST /`, `PUT/PATCH/DELETE /:id` and a panel. Now make it yours step by step.
266
-
267
- **Give it a name and a place in the menu.** `metadata` is what the dashboard reads from memory.
268
-
269
- ```ts
270
- rack("/catalog/products", {
271
- model: { drizzle: { db, table: products } },
272
- metadata: {
273
- id: "products", // unique id, defaults to path
274
- label: "Product", // singular
275
- pluralLabel: "Products", // shown in sidebar
276
- group: "Catalog", // sidebar group
277
- order: 1, // smaller = higher
278
- icon: "package", // Lucide icon name (kebab-case or PascalCase)
279
- parent: "catalog-root", // nest under another id
280
- hidden: false,
281
- hiddenFields: ["cost"], // fields hidden from table columns and forms
282
- fields: {
283
- name: { label: "Product name", order: 1 },
284
- price: { format: "currency", currency: "USD" },
285
- cost: { hideInTable: true }, // editable in forms, not listed
286
- categoryId: { // relation select in forms
287
- label: "Category",
288
- relation: { load: async () => loadCategories() }
289
- }
290
- }
291
- }
292
- });
293
- ```
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.
294
148
 
295
- `metadata.icon` also becomes the page favicon (an inline SVG in `<head>`). Per-field config supports `label`, `format` (`text`, `number`, `currency`, `date`, `datetime`, `boolean`, `badge`, `json`), `readOnly`, `hideInTable`, `hideInForm`, `order`, and `relation`. A relation renders a `<select>`; provide static `options` or a `load` function, and the label is used for table/detail display.
149
+ ### Soft delete
296
150
 
297
- **Decide what users can do.** Turn operations on/off. If you disable `create`, the “New record” button disappears automatically.
151
+ `settings: { softDelete: true }` writes a timestamp to `deletedAt` (override `deletedAtField`) instead of removing rows, and reads skip soft-deleted rows. The panel adds a `Show deleted` toggle and a `Restore` button (`PATCH` clearing `deletedAt`), available when `operations.update` is not disabled.
298
152
 
299
- ```ts
300
- rack("/catalog/products", {
301
- model,
302
- operations: { list: true, detail: true, create: true, replace: true, update: true, delete: false }
303
- });
304
- ```
305
-
306
- **Shape the list experience.** Which fields can be searched, filtered, sorted? What’s the default?
307
-
308
- ```ts
309
- rack("/catalog/products", {
310
- model,
311
- query: {
312
- searchable: ["name", "sku"],
313
- filterable: ["status", "categoryId"],
314
- sortable: ["name", "price", "createdAt"],
315
- defaultSort: { field: "createdAt", direction: "desc" },
316
- pagination: { default: 20, max: 100 }
317
- }
318
- });
319
- ```
153
+ ## ORM adapters
320
154
 
321
- Filter controls adapt to the field kind: enum and boolean become selects, dates get a date picker, numbers get numeric inputs, and relation fields become selects. `metadata.hiddenFields` / `hideInTable` keep a field out of the toolbar too.
155
+ - **Drizzle** (Postgres, SQLite, …): `model: { drizzle: { db, table } }`. Requires `drizzle-orm` installed. Search uses SQL `ILIKE`, so it works on databases that support it (e.g. PostgreSQL) and fails on SQLite.
156
+ - **Prisma**: `model: { prisma: { client, model: prisma.product } }`. No extra dependency. Prisma does not expose schema info at runtime, so panels fall back to row columns and a raw JSON form body.
322
157
 
323
- **Add safety.** Validation and authorization are per-operation. A string like `"products.delete"` checks the `x-permissions` header; a function gives you the full `Request`.
158
+ ## Authorization
324
159
 
325
- ```ts
326
- import { t } from "elysia";
327
-
328
- rack("/catalog/products", {
329
- model,
330
- validation: {
331
- create: t.Object({ name: t.String({ minLength: 3 }) }),
332
- update: t.Partial(t.Object({ name: t.String() })),
333
- params: t.Object({ id: t.String() }),
334
- query: t.Object({ search: t.Optional(t.String()) })
335
- },
336
- authorization: {
337
- list: true,
338
- delete: async ({ request }) => request.headers.get("x-api-key") === "secret"
339
- // or: delete: "products.delete"
340
- }
341
- });
342
- ```
160
+ `authorization` is per operation. Rules:
343
161
 
344
- **Tweak the small things** when you need them:
162
+ - `true` / `undefined` — allow
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
165
+ - A function receiving `{ operation, request, id, resource }` — return truthy to allow, falsy for 403
345
166
 
346
167
  ```ts
347
- rack("/catalog/products", {
348
- model,
349
- page: { enabled: true, path: "/panel" }, // change or disable GET / panel
350
- settings: { primaryKey: "slug", softDelete: true, deletedAtField: "deletedAt", returning: true },
351
- openapi: { tags: ["Catalog"], description: "Products", operations: { list: { summary: "List products" } } },
352
- idempotency: { required: true, header: "Idempotency-Key", ttl: 3600, store: myRedisStore }
353
- });
168
+ authorization: {
169
+ list: true,
170
+ delete: async ({ request }) =>
171
+ request.headers.get("x-api-key") === "secret",
172
+ // or: delete: "products.delete",
173
+ }
354
174
  ```
355
175
 
356
- That’s the whole `rack()` surface. Start with `model`, then add `metadata` → `query` → `validation/authorization` as your app grows.
176
+ ## Dashboard and panels
357
177
 
358
- ### 2) `dashboard()`: one line, navigation overview
359
-
360
- `dashboard()` mounts `GET /` and renders a sidebar plus a table of every resource registered with `rack()`.
178
+ `dashboard()` renders a navigation overview of every resource registered with `rack()`:
361
179
 
362
180
  ```ts
363
181
  import { dashboard, rack } from "elysia-rack";
364
182
  import { reactPlugin } from "elysia-rack/react";
365
183
 
366
184
  const app = new Elysia()
367
- .use(reactPlugin()) // 1) UI engine — pages optional
368
- .use(dashboard({ title: "My Shop", path: "/" })) // 2) Dashboard at /
369
- .use(rack("/catalog/products", { model, metadata: { id: "products", group: "Catalog", order: 1 } }))
370
- .use(rack("/catalog/variants", { model, metadata: { id: "variants", parent: "products", group: "Catalog", order: 2 } }));
371
- // visit /catalog/products → panel shows sidebar with Catalog > Products > Variants
185
+ .use(reactPlugin())
186
+ .use(dashboard({ title: "My Shop", path: "/" }))
187
+ .use(
188
+ rack("/catalog/products", {
189
+ model,
190
+ metadata: { id: "products", group: "Catalog", order: 1 },
191
+ }),
192
+ )
193
+ .use(
194
+ rack("/catalog/variants", {
195
+ model,
196
+ metadata: {
197
+ id: "variants",
198
+ parent: "products",
199
+ group: "Catalog",
200
+ order: 2,
201
+ },
202
+ }),
203
+ )
204
+ .listen(3000);
372
205
  ```
373
206
 
374
- Options — all optional:
375
-
376
- - `path` (default `"/"`) — where the dashboard lives
377
- - `title` (default `"Panel"`) — header in the sidebar
378
- - `pagePath` (default `"/dashboard"`) — which React page to render
207
+ `metadata.parent` nests resources under another id; children render nested in the sidebar, ordered by `order` then label. The panel also shows the full tree sidebar and breadcrumb.
379
208
 
380
- Need it without the helper? The helper is just sugar for:
381
-
382
- ```ts
383
- import { page } from "elysia-rack/react";
384
- .get("/", ({ query }) => page("/dashboard", { name: "My Shop", resource: query.resource as string }))
385
- ```
209
+ Dashboard options: `path` (default `"/"`), `title` (default `"Panel"`), `pagePath` (default `"/dashboard"`), `donate` (default `true` — shows the Ko-fi link in the footer; set `false` to hide).
386
210
 
387
- Helpers to inspect the tree yourself: `listRacks()`, `getRack(id)`, `getRackTree()`, `flatRackTree()`, `buildRackTree(racks)`, `clearRacks()`.
211
+ Tree helpers exported from `elysia-rack`: `listRacks()`, `getRack(id)`, `getRackTree()`, `flatRackTree()`, `buildRackTree(racks)`, `clearRacks()`.
388
212
 
389
- ### 3) `react()` — pages and look
213
+ ## Pages and theming (`elysia-rack/react`)
390
214
 
391
- `reactPlugin` is the bridge between Elysia and React. `pages` is now optional and mergeable — defaults already include `"/dashboard"` and `"/panel"`, and any custom pages you pass are merged in. You can pass a single registry or an array.
215
+ `reactPlugin()` bridges Elysia and React and serves panel assets under `/__rack/*` (`panel.css`, `panel-theme.js`, `panel-app.js`). Default pages are `"/dashboard"` and `"/panel"`; custom pages are merged in.
392
216
 
393
217
  ```ts
394
218
  import { lazyPage, pages, reactPlugin, page } from "elysia-rack/react";
395
219
 
396
- // Option A: just use defaults
397
- new Elysia().use(reactPlugin());
220
+ new Elysia().use(reactPlugin()); // defaults only
398
221
 
399
- // Option B: extend defaults with your own page
222
+ // extend defaults with your own page
400
223
  const myPages = { "/hello": lazyPage(() => import("./Hello")) };
401
- new Elysia().use(reactPlugin({ pages: myPages })); // merged with defaults
224
+ new Elysia().use(reactPlugin({ pages: myPages }));
402
225
 
403
- // Option C: pass a component directly (no import wrapper)
404
- import Hello from "./Hello";
405
- new Elysia().use(reactPlugin({ pages: { "/hello": Hello } }));
406
-
407
- // Option D: auto-mount a view — no page() call needed
226
+ // auto-mount a component at a path (no page() call needed)
408
227
  new Elysia().use(
409
228
  reactPlugin({
410
229
  views: [{ path: "/hello", component: Hello, props: { name: "Ada" } }],
411
230
  }),
412
231
  );
413
232
 
414
- // Option E: merge multiple registries — array will be merged
415
- const extra = { "/admin": lazyPage(() => import("./Admin")) };
416
- new Elysia().use(reactPlugin({ pages: [pages, myPages, extra] }));
417
-
418
- // With a style override
419
- new Elysia().use(reactPlugin({
420
- pages: myPages, // or [pages, myPages]
421
- css: "./my-panel.css", // file path
422
- // css: { path: "./my-panel.css" },
423
- // css: { content: ":root{--color-primary:red}" },
424
- // panelCss: "body{...}" // alias
425
- })).get("/hello", () => page("/hello", { name: "Ada" }));
426
- ```
427
-
428
- What each option does:
429
-
430
- - `pages` *(optional, `PageRegistry | PageRegistry[]`)* — registry of page components or loaders wrapped with `lazyPage(() => import(...))`. If omitted, defaults are used. If you pass one or an array, they are merged with defaults so `"/dashboard"` + `"/panel"` stay available. Later entries win on key collision.
431
- - `views` *(optional, `ReactView[]`)* — components auto-mounted at their `path` with fixed `props`. Equivalent to registering a page and returning `page(path, props)` yourself.
432
- - `css` / `panelCss` — replace `dist/panel.css` served at `/__rack/panel.css`. If the string points to an existing file it’s used, otherwise it’s treated as raw CSS. Useful for quick theming without forking.
433
-
434
- Under the hood `reactPlugin` also serves `/__rack/panel-theme.js` and `/__rack/panel-app.js` and turns any `page()` return value into streamed HTML using `src/react/app.html` (`{{title}}` + `<!--app-->`).
435
-
436
- ## HTML template
437
-
438
- SSR uses `src/react/app.html` as the shell. It is read at runtime, cached,
439
- and streamed with the React output.
440
-
441
- ```html
442
- <!doctype html>
443
- <html><head>
444
- <title>{{title}}</title>
445
- <link rel="stylesheet" href="/__rack/panel.css" />
446
- <!--app-head-->
447
- </head><body>
448
- <!--app-->
449
- </body></html>
233
+ // override panel.css (file path, raw CSS string, or { path } / { content })
234
+ new Elysia().use(reactPlugin({ css: "./my-panel.css" }));
450
235
  ```
451
236
 
452
- - `{{title}}` is replaced (escaped) per page.
453
- - `<!--app-->` (also `{{body}}`, `<!--app-html-->`) marks where the React stream is injected.
454
- - Fallback shell is used if the file is missing. `dist/app.html` is a static copy of `src/react/app.html` included in the package.
455
-
456
- Override the template by editing `src/react/app.html` (and `dist/app.html` for the published package).
457
-
458
- ## Panel CSS override
459
-
460
- Override `panel.css` at `react()` config without forking:
461
-
462
- ```ts
463
- import { reactPlugin } from "elysia-rack/react";
464
-
465
- // file path (resolved from cwd/dist), raw string, or object — pages optional
466
- reactPlugin({ css: "./my-panel.css" });
467
- reactPlugin({ css: { path: "./my-panel.css" } });
468
- reactPlugin({ css: { content: ":root{--color-primary:red}" } });
469
- reactPlugin({ panelCss: "body{...}" }); // alias
470
- // with custom pages: reactPlugin({ pages: myPages, css: "./my.css" })
471
- // or array: reactPlugin({ pages: [pages, myPages], css: "./my.css" })
472
- ```
237
+ Use `page(path, props)` to return a rendered page from a route, e.g. `.get("/", () => page("/dashboard", { name: "My Shop" }))`.
473
238
 
474
- If a file path exists it is served; otherwise a raw CSS string is served.
475
- Fallback is `dist/panel.css`.
239
+ Dark mode follows `localStorage` or `prefers-color-scheme`, with a theme toggle in the masthead. Components use semantic utilities (`bg-background`, `text-foreground`, …) so themes swap without touching React code.
476
240
 
477
- ## Panel assets
478
-
479
- Static assets are served under `/__rack/*`:
480
-
481
- - `dist/panel.css` — panel stylesheet (built with Tailwind CSS)
482
- - `dist/panel-theme.js` — dark/light toggle
483
- - `dist/panel-app.js` — live query + actions (imports `components/href.ts`, `components/panelForm.ts`)
484
-
485
- Pages reference `/__rack/panel.css`, `/__rack/panel-theme.js` and
486
- `/__rack/panel-app.js` via `reactPlugin`. Run `bun run build` to produce
487
- `dist/` (`build:css` compiles the stylesheet, `build:js` bundles the clients).
488
-
489
- ## Panel theme
490
-
491
- `src/react/page/panel.css` is the stylesheet entry:
492
-
493
- - `theme/tokens.css` — full semantic tokens (surface, text, status,
494
- chart, selection, focus, overlay, navigation, table, form, sidebar),
495
- shadcn/Tailwind compatible, mapped via `@theme inline`.
496
- - `theme/dark.css` — `.dark` overrides.
497
- - `theme/rack.css` — default theme (black, white, dark blue, gold;
498
- Poppins for UI, Chocolate Classical Sans for display headings).
499
- Remove its import for the neutral look.
500
- - `theme/components.css` — base styles, dialog reset, reduced motion.
501
-
502
- Dark mode flips automatically: stored choice (`localStorage`), else
503
- `prefers-color-scheme`. The `◐ Theme` button in the masthead toggles it.
504
- Components must only use semantic utilities (`bg-background`,
505
- `text-foreground`, `bg-table-row-hover`, …) so themes swap without
506
- touching React code.
507
-
508
- ## Release (maintainer)
241
+ ## Documentation
509
242
 
510
- 1. One-time setup: npmjs.com → package → Settings → Trusted Publisher → GitHub Actions (`notoofly/elysia-rack`, workflow `publish.yml`).
511
- 2. Bump `version` in `package.json`.
512
- 3. Create a GitHub Release with tag `v<version>` (e.g. `v0.1.0`).
513
- 4. The `publish` workflow runs automatically: install, typecheck, test, build, tag-vs-version check, `npm publish --provenance`.
243
+ - [docs/index.md](docs/index.md) — documentation index
244
+ - [docs/configuration/configuration-reference.md](docs/configuration/configuration-reference.md) — every user-facing option with defaults
245
+ - [docs/getting-started.md](docs/getting-started.md) — install, configure, first operation
246
+ - [docs/guides/basic-usage.md](docs/guides/basic-usage.md) and [docs/guides/common-workflows.md](docs/guides/common-workflows.md) — daily usage
247
+ - [docs/installation/index.md](docs/installation/index.md) — install and remove with Bun or npm
248
+ - [docs/faq.md](docs/faq.md) and [docs/troubleshooting/common-issues.md](docs/troubleshooting/common-issues.md) — common issues
514
249
 
515
- ## Scripts
250
+ ## Contributing
516
251
 
517
252
  ```bash
518
253
  bun install # install dependencies
@@ -522,16 +257,14 @@ bun run typecheck # tsc --noEmit
522
257
  bun run build # dist/ (js via bun build + .d.ts via tsc)
523
258
  ```
524
259
 
525
- ## Donate
526
-
527
- Support pengembangan elysia-rack — traktir kopi via Ko-fi:
528
-
529
- [![Ko-fi](https://img.shields.io/badge/Ko--fi-Donate-ff5e5b?logo=ko-fi&logoColor=white)](https://ko-fi.com/notoofly_manu)
530
-
531
- > https://ko-fi.com/notoofly_manu
532
-
533
- Terima kasih sudah mendukung open-source!
260
+ Open an issue or pull request at [github.com/notoofly/elysia-rack](https://github.com/notoofly/elysia-rack).
534
261
 
535
262
  ## License
536
263
 
537
264
  MIT © notoofly
265
+
266
+ ## Additional resources
267
+
268
+ - Package: [npmjs.com/package/elysia-rack](https://www.npmjs.com/package/elysia-rack)
269
+ - Issues: [github.com/notoofly/elysia-rack/issues](https://github.com/notoofly/elysia-rack/issues)
270
+ - Support: [ko-fi.com/notoofly_manu](https://ko-fi.com/notoofly_manu)
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-table-row-hover{background-color:var(--table-row-hover)}.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)}.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}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "elysia-rack",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
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",