elysia-rack 0.5.3 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +169 -433
- package/dist/panel.css +1 -1
- package/dist/react/index.js +21 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
# elysia-rack
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/elysia-rack) [](LICENSE) [](https://ko-fi.com/notoofly_manu)
|
|
4
4
|
|
|
5
|
-
Model-driven CRUD rack for [Elysia](https://elysiajs.com): declare a
|
|
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,232 @@ 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
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
60
|
+
## Routes
|
|
127
61
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
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
|
-
|
|
143
|
-
runs it via `fetch` and refreshes the table; `confirm` shows a native
|
|
144
|
-
confirmation first.
|
|
74
|
+
## Configuration
|
|
145
75
|
|
|
146
|
-
|
|
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
|
-
|
|
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
|
-
|
|
153
|
-
key per create attempt:
|
|
92
|
+
### Fields and relations
|
|
154
93
|
|
|
155
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
118
|
+
Add extra buttons to the panel:
|
|
237
119
|
|
|
238
120
|
```ts
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
-
###
|
|
136
|
+
### Idempotency (create)
|
|
254
137
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
```ts
|
|
258
|
-
import { rack } from "elysia-rack";
|
|
138
|
+
`POST /` requires an idempotency key by default:
|
|
259
139
|
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
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
|
-
|
|
149
|
+
### Soft delete
|
|
296
150
|
|
|
297
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
158
|
+
## Authorization
|
|
324
159
|
|
|
325
|
-
|
|
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
|
-
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
-
|
|
176
|
+
## Dashboard and panels
|
|
357
177
|
|
|
358
|
-
|
|
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())
|
|
368
|
-
.use(dashboard({ title: "My Shop", path: "/" }))
|
|
369
|
-
.use(
|
|
370
|
-
|
|
371
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
211
|
+
Tree helpers exported from `elysia-rack`: `listRacks()`, `getRack(id)`, `getRackTree()`, `flatRackTree()`, `buildRackTree(racks)`, `clearRacks()`.
|
|
388
212
|
|
|
389
|
-
|
|
213
|
+
## Pages and theming (`elysia-rack/react`)
|
|
390
214
|
|
|
391
|
-
`reactPlugin`
|
|
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
|
-
|
|
397
|
-
new Elysia().use(reactPlugin());
|
|
220
|
+
new Elysia().use(reactPlugin()); // defaults only
|
|
398
221
|
|
|
399
|
-
//
|
|
222
|
+
// extend defaults with your own page
|
|
400
223
|
const myPages = { "/hello": lazyPage(() => import("./Hello")) };
|
|
401
|
-
new Elysia().use(reactPlugin({ pages: myPages }));
|
|
224
|
+
new Elysia().use(reactPlugin({ pages: myPages }));
|
|
402
225
|
|
|
403
|
-
//
|
|
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
|
-
//
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
+
// Custom CSS is appended after the default panel.css, so it cascades over
|
|
235
|
+
// the default instead of replacing it — useful for changing only a few
|
|
236
|
+
// tokens or rules.
|
|
237
|
+
new Elysia().use(reactPlugin({ css: "./my-panel.css" }));
|
|
450
238
|
```
|
|
451
239
|
|
|
452
|
-
|
|
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
|
-
```
|
|
240
|
+
Use `page(path, props)` to return a rendered page from a route, e.g. `.get("/", () => page("/dashboard", { name: "My Shop" }))`.
|
|
473
241
|
|
|
474
|
-
|
|
475
|
-
Fallback is `dist/panel.css`.
|
|
242
|
+
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
243
|
|
|
477
|
-
##
|
|
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)
|
|
244
|
+
## Documentation
|
|
509
245
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
246
|
+
- [docs/index.md](docs/index.md) — documentation index
|
|
247
|
+
- [docs/configuration/configuration-reference.md](docs/configuration/configuration-reference.md) — every user-facing option with defaults
|
|
248
|
+
- [docs/getting-started.md](docs/getting-started.md) — install, configure, first operation
|
|
249
|
+
- [docs/guides/basic-usage.md](docs/guides/basic-usage.md) and [docs/guides/common-workflows.md](docs/guides/common-workflows.md) — daily usage
|
|
250
|
+
- [docs/installation/index.md](docs/installation/index.md) — install and remove with Bun or npm
|
|
251
|
+
- [docs/faq.md](docs/faq.md) and [docs/troubleshooting/common-issues.md](docs/troubleshooting/common-issues.md) — common issues
|
|
514
252
|
|
|
515
|
-
##
|
|
253
|
+
## Contributing
|
|
516
254
|
|
|
517
255
|
```bash
|
|
518
256
|
bun install # install dependencies
|
|
@@ -522,16 +260,14 @@ bun run typecheck # tsc --noEmit
|
|
|
522
260
|
bun run build # dist/ (js via bun build + .d.ts via tsc)
|
|
523
261
|
```
|
|
524
262
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
Support pengembangan elysia-rack — traktir kopi via Ko-fi:
|
|
528
|
-
|
|
529
|
-
[](https://ko-fi.com/notoofly_manu)
|
|
530
|
-
|
|
531
|
-
> https://ko-fi.com/notoofly_manu
|
|
532
|
-
|
|
533
|
-
Terima kasih sudah mendukung open-source!
|
|
263
|
+
Open an issue or pull request at [github.com/notoofly/elysia-rack](https://github.com/notoofly/elysia-rack).
|
|
534
264
|
|
|
535
265
|
## License
|
|
536
266
|
|
|
537
267
|
MIT © notoofly
|
|
268
|
+
|
|
269
|
+
## Additional resources
|
|
270
|
+
|
|
271
|
+
- Package: [npmjs.com/package/elysia-rack](https://www.npmjs.com/package/elysia-rack)
|
|
272
|
+
- Issues: [github.com/notoofly/elysia-rack/issues](https://github.com/notoofly/elysia-rack/issues)
|
|
273
|
+
- 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/dist/react/index.js
CHANGED
|
@@ -260,14 +260,34 @@ function reactPlugin(options = {}) {
|
|
|
260
260
|
return status(404, "Panel asset not found");
|
|
261
261
|
if (params.name === "panel.css") {
|
|
262
262
|
const custom = await loadCustomPanelCss();
|
|
263
|
+
const dir2 = resolveDistDir();
|
|
264
|
+
let defaultCss = null;
|
|
265
|
+
if (dir2) {
|
|
266
|
+
try {
|
|
267
|
+
defaultCss = await readFile2(join2(dir2, "panel.css"), "utf-8");
|
|
268
|
+
} catch {}
|
|
269
|
+
}
|
|
263
270
|
if (custom !== null) {
|
|
264
|
-
|
|
271
|
+
const combined = defaultCss ? `${defaultCss}
|
|
272
|
+
|
|
273
|
+
/* Custom panel.css overrides */
|
|
274
|
+
${custom}` : custom;
|
|
275
|
+
return new Response(combined, {
|
|
265
276
|
headers: {
|
|
266
277
|
"Content-Type": type,
|
|
267
278
|
"Cache-Control": "public, max-age=3600"
|
|
268
279
|
}
|
|
269
280
|
});
|
|
270
281
|
}
|
|
282
|
+
if (defaultCss) {
|
|
283
|
+
return new Response(defaultCss, {
|
|
284
|
+
headers: {
|
|
285
|
+
"Content-Type": type,
|
|
286
|
+
"Cache-Control": "public, max-age=3600"
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
return status(404, "Panel asset not found");
|
|
271
291
|
}
|
|
272
292
|
const dir = resolveDistDir();
|
|
273
293
|
if (!dir)
|
package/package.json
CHANGED