@xleddyl/nuxt-cms 0.1.56 → 0.1.58
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/dist/module.json +1 -1
- package/dist/module.mjs +57 -5
- package/dist/runtime/app/components/cms/BlocksField.vue +19 -1
- package/dist/runtime/app/components/cms/MediaField.vue +14 -1
- package/dist/runtime/app/composables/cms-media-keys.d.ts +4 -0
- package/dist/runtime/app/composables/cms-media-keys.js +21 -0
- package/dist/runtime/app/composables/cms-query.js +57 -4
- package/dist/runtime/assets/main.css +1 -1
- package/dist/runtime/seed.js +9 -2
- package/dist/runtime/server/api/collection.get.js +2 -1
- package/dist/runtime/server/api/item.get.js +6 -4
- package/dist/runtime/server/api/item.put.js +5 -4
- package/dist/runtime/server/plugins/migrate-libsql.js +2 -2
- package/dist/runtime/server/routes/graphql.js +2 -0
- package/dist/runtime/server/utils/db-d1.d.ts +2 -0
- package/dist/runtime/server/utils/db-d1.js +5 -0
- package/dist/runtime/server/utils/db-libsql.d.ts +2 -0
- package/dist/runtime/server/utils/db-libsql.js +5 -0
- package/dist/runtime/server/utils/db-postgres.d.ts +2 -0
- package/dist/runtime/server/utils/db-postgres.js +8 -0
- package/dist/runtime/server/utils/db-sqlite.d.ts +2 -0
- package/dist/runtime/server/utils/db-sqlite.js +8 -0
- package/dist/runtime/server/utils/graphql.js +3 -4
- package/dist/runtime/server/utils/libsql-migrations.d.ts +16 -0
- package/dist/runtime/server/utils/libsql-migrations.js +35 -0
- package/dist/runtime/server/utils/media-check.d.ts +2 -0
- package/dist/runtime/server/utils/media-check.js +20 -0
- package/dist/runtime/server/utils/media-references.d.ts +10 -0
- package/dist/runtime/server/utils/media-references.js +50 -0
- package/dist/runtime/server/utils/migrate.d.ts +1 -0
- package/dist/runtime/server/utils/migrate.js +7 -0
- package/dist/runtime/server/utils/page-rows.d.ts +51 -0
- package/dist/runtime/server/utils/page-rows.js +223 -0
- package/dist/runtime/server/utils/page-storage.d.ts +11 -0
- package/dist/runtime/server/utils/page-storage.js +42 -0
- package/dist/runtime/shared/index.d.ts +7 -0
- package/dist/runtime/shared/index.js +21 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { minifyIntrospection, outputIntrospectionFile } from 'gql.tada/internal'
|
|
|
11
11
|
import { createJiti } from 'jiti';
|
|
12
12
|
import { typeName, blockTypeName, blocksFieldTypeName, renderGraphqlSdl } from '../dist/runtime/shared/graphql-sdl.js';
|
|
13
13
|
import { migrationsDirFor } from '../dist/runtime/shared/migrations-dir.js';
|
|
14
|
-
import { isTranslatableField, isTranslatableMediaField, isMultiSelect, pageAllFields, PAGE_PATH_FIELD, pageRoutes, fieldConditions, isRequiredField, pageFields, isPrivateField, entryFieldsFor, mediaTypeFilter, DEFAULT_MEDIA_MAX_FILE_SIZE } from '../dist/runtime/shared/index.js';
|
|
14
|
+
import { pageColumnFields, isTranslatableField, isTranslatableMediaField, isMultiSelect, pageAllFields, PAGE_PATH_FIELD, pageRoutes, fieldConditions, pageFieldsTableName, pageMediaTableName, isRequiredField, pageFields, isPrivateField, entryFieldsFor, mediaTypeFilter, DEFAULT_MEDIA_MAX_FILE_SIZE } from '../dist/runtime/shared/index.js';
|
|
15
15
|
import { scanMediaDirectory, readMediaFileMeta } from '../dist/runtime/server/utils/media-sync.js';
|
|
16
16
|
import { createHash } from 'node:crypto';
|
|
17
17
|
import { readFile } from 'node:fs/promises';
|
|
@@ -181,9 +181,11 @@ function validateConfig(config, i18n) {
|
|
|
181
181
|
declared.set(key, field.type);
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
|
+
errors.push(...pageColumnsErrors(at, entry));
|
|
184
185
|
} else if (entry.overrides || entry.routes || entry.include || entry.exclude) {
|
|
185
186
|
errors.push(`${at}: routes, include, exclude and overrides need kind 'page'`);
|
|
186
187
|
}
|
|
188
|
+
if (entry.kind !== "page" && entry.columns) errors.push(`${at}: columns need kind 'page'`);
|
|
187
189
|
if (entry.drafts && entry.kind !== "collection")
|
|
188
190
|
errors.push(`${at}: drafts are only supported on collections`);
|
|
189
191
|
const tabIds = /* @__PURE__ */ new Set();
|
|
@@ -386,6 +388,10 @@ function validateConfig(config, i18n) {
|
|
|
386
388
|
}
|
|
387
389
|
};
|
|
388
390
|
registerTable(name, `entry '${name}'`);
|
|
391
|
+
if (entry.kind === "page") {
|
|
392
|
+
registerTable(pageFieldsTableName(name), `the field rows table of '${name}'`);
|
|
393
|
+
registerTable(pageMediaTableName(name), `the media rows table of '${name}'`);
|
|
394
|
+
}
|
|
389
395
|
for (const [key, field] of Object.entries(entry.fields ?? {})) {
|
|
390
396
|
if (isManyToMany(field))
|
|
391
397
|
registerTable(`${name}_${snakeCase(key)}`, `the join table of '${name}.${key}'`);
|
|
@@ -393,6 +399,35 @@ function validateConfig(config, i18n) {
|
|
|
393
399
|
}
|
|
394
400
|
return errors;
|
|
395
401
|
}
|
|
402
|
+
function pageColumnsErrors(at, entry) {
|
|
403
|
+
const errors = [];
|
|
404
|
+
if (entry.table) errors.push(`${at}: page entries cannot use a custom table`);
|
|
405
|
+
const columns = /* @__PURE__ */ new Set();
|
|
406
|
+
for (const key of entry.columns ?? []) {
|
|
407
|
+
const cat = `${at}, column '${key}'`;
|
|
408
|
+
if (columns.has(key)) errors.push(`${cat}: listed more than once`);
|
|
409
|
+
columns.add(key);
|
|
410
|
+
if (!Object.hasOwn(entry.fields ?? {}, key)) {
|
|
411
|
+
errors.push(`${cat}: not a field declared for every page in fields`);
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
for (const [path, override] of Object.entries(entry.overrides ?? {})) {
|
|
415
|
+
if (Object.hasOwn(override, key) && !override[key])
|
|
416
|
+
errors.push(`${cat}: removed from '${path}', a column must exist on every page`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
for (const [key, field] of Object.entries(pageAllFields(entry))) {
|
|
420
|
+
const fat = `${at}, field '${key}'`;
|
|
421
|
+
if (isManyToMany(field)) {
|
|
422
|
+
errors.push(`${fat}: many-to-many relations are not supported on pages`);
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (columns.has(key)) continue;
|
|
426
|
+
if (field.type === "relation" || field.type === "slug")
|
|
427
|
+
errors.push(`${fat}: ${field.type} fields on pages need to be listed in columns`);
|
|
428
|
+
}
|
|
429
|
+
return errors;
|
|
430
|
+
}
|
|
396
431
|
function snakeCase(key) {
|
|
397
432
|
return key.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
|
|
398
433
|
}
|
|
@@ -455,7 +490,7 @@ function tableExpr(name, entry, dialect) {
|
|
|
455
490
|
lines.push(` id: text('id').primaryKey(),`);
|
|
456
491
|
if (entry.kind === "page") lines.push(` path: text('path').notNull().unique(),`);
|
|
457
492
|
for (const [key, field] of Object.entries(
|
|
458
|
-
entry.kind === "page" ?
|
|
493
|
+
entry.kind === "page" ? pageColumnFields(entry) : entry.fields
|
|
459
494
|
)) {
|
|
460
495
|
if (isManyToMany(field)) continue;
|
|
461
496
|
lines.push(columnExpr(key, field, dialect));
|
|
@@ -478,6 +513,19 @@ function joinTableExpr(name, key, field, dialect) {
|
|
|
478
513
|
position: integer('position').notNull().default(0),
|
|
479
514
|
}, table => [primaryKey({ columns: [table.sourceId, table.targetId] })])`;
|
|
480
515
|
}
|
|
516
|
+
function pageRowsTableExprs(name, dialect) {
|
|
517
|
+
const tableFn = dialect === "postgres" ? "pgTable" : "sqliteTable";
|
|
518
|
+
const rowsTable = (table, valueLine) => `export const ${table} = ${tableFn}('${table}', {
|
|
519
|
+
pageId: text('page_id').notNull().references(() => ${name}.id, { onDelete: 'cascade' }),
|
|
520
|
+
key: text('key').notNull(),
|
|
521
|
+
position: integer('position').notNull().default(0),
|
|
522
|
+
${valueLine},
|
|
523
|
+
}, table => [primaryKey({ columns: [table.pageId, table.key, table.position] })])`;
|
|
524
|
+
return [
|
|
525
|
+
rowsTable(pageFieldsTableName(name), `value: text('value').notNull()`),
|
|
526
|
+
rowsTable(pageMediaTableName(name), `mediaKey: text('media_key').notNull()`)
|
|
527
|
+
];
|
|
528
|
+
}
|
|
481
529
|
function mediaTableExpr(dialect) {
|
|
482
530
|
const pg = dialect === "postgres";
|
|
483
531
|
const tableFn = pg ? "pgTable" : "sqliteTable";
|
|
@@ -495,7 +543,10 @@ function mediaTableExpr(dialect) {
|
|
|
495
543
|
}
|
|
496
544
|
function renderSchemaFile(config, dialect, resolveImport = (s) => s) {
|
|
497
545
|
const derived = Object.entries(config).filter(([, entry]) => !entry.table);
|
|
498
|
-
const fields = derived.flatMap(
|
|
546
|
+
const fields = derived.flatMap(
|
|
547
|
+
([, entry]) => Object.values(entry.kind === "page" ? pageColumnFields(entry) : entry.fields)
|
|
548
|
+
);
|
|
549
|
+
const rowsEntries = derived.filter(([, entry]) => entry.kind === "page");
|
|
499
550
|
const pg = dialect === "postgres";
|
|
500
551
|
const core = /* @__PURE__ */ new Set(["integer", "text", pg ? "pgTable" : "sqliteTable"]);
|
|
501
552
|
if (fields.some((f) => f.type === "number" && !f.integer))
|
|
@@ -507,7 +558,7 @@ function renderSchemaFile(config, dialect, resolveImport = (s) => s) {
|
|
|
507
558
|
))
|
|
508
559
|
core.add("jsonb");
|
|
509
560
|
if (pg) core.add("timestamp");
|
|
510
|
-
if (fields.some(isManyToMany)) core.add("primaryKey");
|
|
561
|
+
if (fields.some(isManyToMany) || rowsEntries.length) core.add("primaryKey");
|
|
511
562
|
const imports = [
|
|
512
563
|
...pg ? [] : [`import { sql } from '${resolveImport("drizzle-orm")}'`],
|
|
513
564
|
`import { ${[...core].sort().join(", ")} } from '${resolveImport(
|
|
@@ -518,9 +569,10 @@ function renderSchemaFile(config, dialect, resolveImport = (s) => s) {
|
|
|
518
569
|
const joins = derived.flatMap(
|
|
519
570
|
([name, entry]) => Object.entries(entry.kind === "page" ? pageAllFields(entry) : entry.fields).filter(([, field]) => isManyToMany(field)).map(([key, field]) => joinTableExpr(name, key, field, dialect))
|
|
520
571
|
);
|
|
572
|
+
const rows = rowsEntries.flatMap(([name]) => pageRowsTableExprs(name, dialect));
|
|
521
573
|
return `${imports.join("\n")}
|
|
522
574
|
|
|
523
|
-
${[mediaTableExpr(dialect), ...tables, ...joins].join(
|
|
575
|
+
${[mediaTableExpr(dialect), ...tables, ...joins, ...rows].join(
|
|
524
576
|
"\n\n"
|
|
525
577
|
)}
|
|
526
578
|
`;
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
:class="{
|
|
9
9
|
'is-muted': isHidden(item),
|
|
10
10
|
'is-dragging': dragIndex === index,
|
|
11
|
-
'is-drop-target': dropIndex === index && dragIndex !== index
|
|
11
|
+
'is-drop-target': dropIndex === index && dragIndex !== index,
|
|
12
|
+
'is-missing': hasMissingMedia(item)
|
|
12
13
|
}"
|
|
13
14
|
draggable="true"
|
|
14
15
|
@dragstart="onDragStart(index, $event)"
|
|
@@ -55,6 +56,13 @@
|
|
|
55
56
|
<CmsIcon name="bars-2" class="size-4" />
|
|
56
57
|
</button>
|
|
57
58
|
<span class="cms-block-bar-name" :title="barTitle(item)">{{ barLabel(item) }}</span>
|
|
59
|
+
<span
|
|
60
|
+
v-if="hasMissingMedia(item)"
|
|
61
|
+
class="cms-media-missing-icon"
|
|
62
|
+
title="Not found in the media library"
|
|
63
|
+
>
|
|
64
|
+
<CmsIcon name="exclamation-triangle" class="size-4" />
|
|
65
|
+
</span>
|
|
58
66
|
<div class="cms-block-bar-actions">
|
|
59
67
|
<CmsButton
|
|
60
68
|
icon="trash"
|
|
@@ -150,6 +158,7 @@ import {
|
|
|
150
158
|
pickTranslatedMedia
|
|
151
159
|
} from "#nuxt-cms";
|
|
152
160
|
import { computed, ref } from "#imports";
|
|
161
|
+
import { useCmsMediaKeys } from "../../composables/cms-media-keys";
|
|
153
162
|
import { useCmsRuntime } from "../../composables/cms-runtime";
|
|
154
163
|
const props = defineProps({
|
|
155
164
|
field: { type: Object, required: true },
|
|
@@ -157,6 +166,7 @@ const props = defineProps({
|
|
|
157
166
|
});
|
|
158
167
|
const model = defineModel({ type: [Array, null], ...{ required: true } });
|
|
159
168
|
const { mediaBaseUrl, i18n } = useCmsRuntime();
|
|
169
|
+
const mediaKeys = useCmsMediaKeys();
|
|
160
170
|
const blocks = computed(() => props.field.blocks ?? {});
|
|
161
171
|
const items = computed(() => Array.isArray(model.value) ? model.value : []);
|
|
162
172
|
let uidCounter = 0;
|
|
@@ -192,6 +202,14 @@ function mediaKeyOf(item) {
|
|
|
192
202
|
}
|
|
193
203
|
return null;
|
|
194
204
|
}
|
|
205
|
+
function hasMissingMedia(item) {
|
|
206
|
+
return Object.entries(blockOf(item)?.fields ?? {}).some(([key, field]) => {
|
|
207
|
+
if (field.type !== "media") return false;
|
|
208
|
+
const raw = item[key];
|
|
209
|
+
const values = raw && typeof raw === "object" ? Object.values(raw) : [raw];
|
|
210
|
+
return values.some((value) => mediaKeys.isMissing(value));
|
|
211
|
+
});
|
|
212
|
+
}
|
|
195
213
|
function previewOf(item) {
|
|
196
214
|
const key = mediaKeyOf(item);
|
|
197
215
|
if (!key) return null;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
|
+
v-if="model"
|
|
4
|
+
class="cms-card cms-block-tile cms-media-single"
|
|
5
|
+
:class="{ 'is-missing': missing }"
|
|
6
|
+
>
|
|
3
7
|
<button
|
|
4
8
|
type="button"
|
|
5
9
|
class="cms-block-preview"
|
|
@@ -44,6 +48,11 @@
|
|
|
44
48
|
<span>Choose</span>
|
|
45
49
|
</button>
|
|
46
50
|
|
|
51
|
+
<p v-if="missing" class="cms-media-missing">
|
|
52
|
+
<CmsIcon name="exclamation-triangle" class="size-4" />
|
|
53
|
+
<span>Not found in the media library</span>
|
|
54
|
+
</p>
|
|
55
|
+
|
|
47
56
|
<CmsModal v-model:open="galleryOpen" title="Media" size="lg">
|
|
48
57
|
<template #body>
|
|
49
58
|
<CmsMediaGallery selectable :media-type="mediaType" :accept="accept" @select="onSelect" />
|
|
@@ -54,6 +63,7 @@
|
|
|
54
63
|
<script setup>
|
|
55
64
|
import { computed, ref } from "#imports";
|
|
56
65
|
import { mediaFilename, mediaIconFor, mediaPublicUrl, mediaTypeForKey } from "#nuxt-cms";
|
|
66
|
+
import { useCmsMediaKeys } from "../../composables/cms-media-keys";
|
|
57
67
|
import { useCmsRuntime } from "../../composables/cms-runtime";
|
|
58
68
|
defineProps({
|
|
59
69
|
mediaType: { type: null, required: false },
|
|
@@ -61,8 +71,10 @@ defineProps({
|
|
|
61
71
|
});
|
|
62
72
|
const model = defineModel({ type: [String, null], ...{ required: true } });
|
|
63
73
|
const { mediaBaseUrl } = useCmsRuntime();
|
|
74
|
+
const mediaKeys = useCmsMediaKeys();
|
|
64
75
|
const galleryOpen = ref(false);
|
|
65
76
|
function onSelect(item) {
|
|
77
|
+
mediaKeys.remember(item.key);
|
|
66
78
|
model.value = item.key;
|
|
67
79
|
galleryOpen.value = false;
|
|
68
80
|
}
|
|
@@ -72,6 +84,7 @@ function openGallery() {
|
|
|
72
84
|
function clear() {
|
|
73
85
|
model.value = null;
|
|
74
86
|
}
|
|
87
|
+
const missing = computed(() => mediaKeys.isMissing(model.value));
|
|
75
88
|
const url = computed(() => model.value ? mediaPublicUrl(mediaBaseUrl, model.value) : null);
|
|
76
89
|
const kind = computed(() => model.value ? mediaTypeForKey(model.value) : "file");
|
|
77
90
|
const icon = computed(() => mediaIconFor(kind.value));
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ref } from "#imports";
|
|
2
|
+
const known = ref(null);
|
|
3
|
+
let loading = null;
|
|
4
|
+
function load() {
|
|
5
|
+
loading ??= $fetch("/api/cms/admin/media").then((result) => {
|
|
6
|
+
known.value = result.source.kind === "none" ? null : new Set(result.items.map((item) => item.key));
|
|
7
|
+
}).catch(() => {
|
|
8
|
+
known.value = null;
|
|
9
|
+
});
|
|
10
|
+
return loading;
|
|
11
|
+
}
|
|
12
|
+
export function useCmsMediaKeys() {
|
|
13
|
+
if (import.meta.client) void load();
|
|
14
|
+
function isMissing(key) {
|
|
15
|
+
return typeof key === "string" && key !== "" && !!known.value && !known.value.has(key);
|
|
16
|
+
}
|
|
17
|
+
function remember(key) {
|
|
18
|
+
if (known.value) known.value = /* @__PURE__ */ new Set([...known.value, key]);
|
|
19
|
+
}
|
|
20
|
+
return { isMissing, remember };
|
|
21
|
+
}
|
|
@@ -1,13 +1,66 @@
|
|
|
1
1
|
import { useAsyncData } from "#imports";
|
|
2
|
+
import { CMS_GRAPHQL_BATCH_LIMIT } from "../../shared/index.js";
|
|
2
3
|
const ENDPOINT = "/api/cms/graphql";
|
|
4
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
5
|
+
let queue = [];
|
|
6
|
+
let scheduled = false;
|
|
7
|
+
function responseError(response) {
|
|
8
|
+
return new Error(
|
|
9
|
+
response?.errors?.map((e) => e.message).join("; ") || "Invalid batched GraphQL response"
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
async function sendBatch(batch) {
|
|
13
|
+
try {
|
|
14
|
+
if (batch.length === 1) {
|
|
15
|
+
batch[0].resolve(
|
|
16
|
+
await $fetch(ENDPOINT, {
|
|
17
|
+
method: "POST",
|
|
18
|
+
body: batch[0].body
|
|
19
|
+
})
|
|
20
|
+
);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const responses = await $fetch(
|
|
24
|
+
ENDPOINT,
|
|
25
|
+
{ method: "POST", body: batch.map((pending) => pending.body) }
|
|
26
|
+
);
|
|
27
|
+
if (!Array.isArray(responses)) throw responseError(responses);
|
|
28
|
+
batch.forEach((pending, index) => {
|
|
29
|
+
const response = responses[index];
|
|
30
|
+
if (response) pending.resolve(response);
|
|
31
|
+
else pending.reject(responseError(response));
|
|
32
|
+
});
|
|
33
|
+
} catch (error) {
|
|
34
|
+
for (const pending of batch) pending.reject(error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function flushQueue() {
|
|
38
|
+
const pending = queue;
|
|
39
|
+
queue = [];
|
|
40
|
+
scheduled = false;
|
|
41
|
+
for (let index = 0; index < pending.length; index += CMS_GRAPHQL_BATCH_LIMIT) {
|
|
42
|
+
void sendBatch(pending.slice(index, index + CMS_GRAPHQL_BATCH_LIMIT));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function requestGraphql(body) {
|
|
46
|
+
const key = JSON.stringify(body);
|
|
47
|
+
const existing = inFlight.get(key);
|
|
48
|
+
if (existing) return existing;
|
|
49
|
+
const request = new Promise((resolve, reject) => {
|
|
50
|
+
queue.push({ body, resolve, reject });
|
|
51
|
+
if (!scheduled) {
|
|
52
|
+
scheduled = true;
|
|
53
|
+
setTimeout(flushQueue, 0);
|
|
54
|
+
}
|
|
55
|
+
}).finally(() => inFlight.delete(key));
|
|
56
|
+
inFlight.set(key, request);
|
|
57
|
+
return request;
|
|
58
|
+
}
|
|
3
59
|
export function cmsQueryKey(query, variables) {
|
|
4
60
|
return `cms-gql:${query}:${JSON.stringify(variables ?? {})}`;
|
|
5
61
|
}
|
|
6
62
|
export async function $cmsQuery(query, variables) {
|
|
7
|
-
const res = await
|
|
8
|
-
method: "POST",
|
|
9
|
-
body: { query, variables }
|
|
10
|
-
});
|
|
63
|
+
const res = await requestGraphql({ query, variables });
|
|
11
64
|
if (res.errors?.length) {
|
|
12
65
|
throw new Error(res.errors.map((e) => e.message).join("; "));
|
|
13
66
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
@layer theme, base, components, utilities;@import "tailwindcss/theme.css" layer(theme) theme(inline);.cms-scope{@import "tailwindcss/preflight.css" layer(base);@import "tailwindcss/utilities.css" layer(utilities) source(none)}@source "../app";.cms-scope,body:has(.cms-scope){--cms-canvas:#1b1b1e;--cms-surface:#222226;--cms-field:#2a2a2f;--cms-elevated:#2f2f35;--cms-line:#35353b;--cms-line-strong:#46464e;--cms-text:#ededef;--cms-text-toned:#c2c2c8;--cms-text-muted:#9a9aa3;--cms-text-dimmed:#6e6e78;--cms-fern:#8fae8b;--cms-error:#f0857d;--cms-success:#7fb083;--cms-warning:#d4ad76;--cms-font-ui:"Hanken Grotesk Variable","Hanken Grotesk",ui-sans-serif,system-ui,sans-serif;--cms-font-mono:"Fragment Mono",ui-monospace,SFMono-Regular,Menlo,monospace;--cms-radius-sm:6px;--cms-radius-md:8px;--cms-radius-lg:12px;--cms-shadow-overlay:0 8px 24px rgba(0,0,0,.35);--cms-scrim:rgba(0,0,0,.5);--cms-duration:120ms;--cms-duration-lg:250ms;--cms-ease:cubic-bezier(0.2,0,0,1);color-scheme:dark;--ui-text-dimmed:var(--cms-text-dimmed);--ui-text-muted:var(--cms-text-muted);--ui-text-toned:var(--cms-text-toned);--ui-text:var(--cms-text-toned);--ui-text-highlighted:var(--cms-text);--ui-text-inverted:var(--cms-canvas);--ui-bg:var(--cms-surface);--ui-bg-muted:var(--cms-canvas);--ui-bg-elevated:var(--cms-field);--ui-bg-accented:var(--cms-elevated);--ui-bg-inverted:var(--cms-text);--ui-border:var(--cms-line);--ui-border-muted:var(--cms-line);--ui-border-accented:var(--cms-line-strong);--ui-border-inverted:var(--cms-text);--ui-primary:var(--cms-text);--ui-accent:var(--cms-fern);--ui-error:var(--cms-error);--ui-success:var(--cms-success);--ui-warning:var(--cms-warning);--ui-radius:var(--cms-radius-md);color:var(--ui-text);font-family:var(--cms-font-ui);margin:0;padding:0;-webkit-font-smoothing:antialiased}body:has(.cms-scope) ::-moz-selection{background:color-mix(in oklab,var(--cms-fern) 35%,transparent);color:var(--ui-text-highlighted)}body:has(.cms-scope) ::selection{background:color-mix(in oklab,var(--cms-fern) 35%,transparent);color:var(--ui-text-highlighted)}body:has(.cms-scope) :is(.cms-scope,.cms-scope *){scrollbar-color:var(--cms-line-strong) transparent;scrollbar-width:thin}body:has(.cms-scope) :is(.cms-scope,.cms-scope *)::-webkit-scrollbar{height:10px;width:10px}body:has(.cms-scope) :is(.cms-scope,.cms-scope *)::-webkit-scrollbar-track{background:transparent}body:has(.cms-scope) :is(.cms-scope,.cms-scope *)::-webkit-scrollbar-thumb{background:var(--cms-line-strong);background-clip:content-box;border:3px solid transparent;border-radius:9999px}body:has(.cms-scope) :is(.cms-scope,.cms-scope *)::-webkit-scrollbar-thumb:hover{background:color-mix(in oklab,var(--cms-line-strong) 70%,var(--cms-text));background-clip:content-box}.cms-display,.cms-title{font-family:var(--cms-font-ui);font-weight:650;letter-spacing:-.01em}.cms-title{color:var(--ui-text-highlighted)}.cms-title-sm{font-size:1.125rem;line-height:1.4}.cms-title-md{font-size:1.5rem;line-height:1.25}.cms-title-lg{font-size:1.875rem;line-height:1.2}.cms-subtitle{color:var(--ui-text-muted);font-size:.875rem}.cms-accent{color:var(--cms-fern)}.cms-label{color:var(--ui-text-muted);font-size:.75rem;font-weight:500;line-height:1.4}body:has(.cms-scope) :is(button,a,[role=button],[role=menuitem],[role=option],.__clickable):not(:disabled,[aria-disabled=true]){@apply cursor-pointer select-none}body:has(.cms-scope) :is(button,a,[role=button],[role=menuitem],[role=option]):focus-visible{outline:2px solid var(--cms-fern);outline-offset:2px}.cms-canvas{background-color:var(--cms-canvas)}.cms-card{background:var(--cms-surface);border:1px solid var(--cms-line);border-radius:var(--cms-radius-lg)}.cms-panel{padding:1.75rem}body:has(.cms-scope),html:has(.cms-scope){overscroll-behavior-y:none}.cms-shell{display:flex;min-height:100vh}.cms-sidebar{background:var(--cms-surface);border-right:1px solid var(--cms-line);display:flex;flex-direction:column;flex-shrink:0;gap:1.75rem;height:100vh;padding:1.75rem 1rem;position:sticky;top:0;width:16rem}.cms-sidebar-brand{color:var(--ui-text-highlighted);font-family:var(--cms-font-ui);font-size:1.25rem;font-weight:650;letter-spacing:-.01em;padding-inline:.75rem}.cms-sidebar-nav{display:flex;flex:1;flex-direction:column;gap:1.5rem;overflow-y:auto;overscroll-behavior-y:none}.cms-sidebar-group{display:flex;flex-direction:column;gap:.25rem}.cms-sidebar-group>.cms-label{margin-bottom:.5rem;padding-inline:.75rem}.cms-sidebar-footer{border-top:1px solid var(--cms-line);display:flex;flex-direction:column;gap:.75rem;padding-top:1rem}.cms-sidebar-footer .cms-btn.is-block{color:var(--ui-text-muted);justify-content:space-between;padding-inline:.75rem}.cms-sidebar-footer .cms-btn.is-block:hover{background:var(--cms-field);color:var(--ui-text)}.cms-main{flex:1;height:100vh;min-width:0;overflow-y:auto;overscroll-behavior-y:none;padding:2.5rem}.cms-main-inner{margin-inline:auto;max-width:64rem;min-height:100%;width:100%}.cms-main-inner,.cms-page{display:flex;flex-direction:column}.cms-page{gap:1.75rem}.cms-page.is-fill{flex:1;min-height:0}.cms-page-header{align-items:flex-end;display:flex;gap:1rem;justify-content:space-between}.cms-page-heading{display:flex;flex-direction:column;gap:.5rem}.cms-breadcrumb{align-items:center;color:var(--ui-text-muted);display:flex;font-size:.75rem;font-weight:500;gap:.375rem;line-height:1.4}.cms-breadcrumb-link{border-radius:.25rem;padding:.0625rem .125rem;transition:color .15s ease}.cms-breadcrumb-link:hover{color:var(--ui-text-highlighted)}.cms-breadcrumb-separator{color:var(--ui-text-dimmed)}.cms-tree-row{align-items:center;display:flex;gap:.75rem;padding:.625rem 1rem;transition:background-color .15s ease}.cms-tree-row:hover{background-color:var(--ui-bg-elevated)}.cms-tree-branch{color:var(--ui-text-dimmed);flex-shrink:0;transform:scaleY(-1) rotate(90deg);width:1rem}.cms-toolbar{align-items:center;display:flex;gap:1.5rem}.cms-toolbar-actions{align-items:center;display:flex;flex-shrink:0;gap:.5rem;margin-left:auto}.cms-actions{align-items:center;display:flex;gap:.75rem}.cms-actions.is-end{justify-content:flex-end}.cms-spinner{color:var(--ui-text-dimmed);flex:1;justify-content:center;padding-block:2.5rem}.cms-empty,.cms-spinner{align-items:center;display:flex}.cms-empty{background:color-mix(in oklab,var(--cms-surface) 55%,transparent);border:1px dashed var(--cms-line-strong);border-radius:var(--cms-radius-lg);flex-direction:column;gap:.5rem;padding:3.5rem;text-align:center}.cms-empty.is-fill{flex:1;justify-content:center}.cms-empty-icon{color:var(--cms-fern);margin-bottom:.25rem}.cms-empty>.cms-btn{margin-top:.75rem}.cms-auth{align-items:center;display:flex;justify-content:center;min-height:100vh;padding:1.5rem}.cms-auth-panel{gap:2rem;max-width:24rem;width:100%}.cms-auth-header,.cms-auth-panel{display:flex;flex-direction:column}.cms-auth-header{align-items:center;gap:.75rem;text-align:center}.cms-auth-card{padding:1.75rem}.cms-navlink{align-items:center;border-radius:var(--cms-radius-md);color:var(--ui-text-toned);display:flex;font-size:.875rem;font-weight:500;gap:.625rem;padding:.5rem .75rem;transition:background-color var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease)}.cms-navlink:hover{background:var(--cms-field);color:var(--ui-text-highlighted)}.cms-navlink.is-active{background:var(--cms-field);color:var(--cms-fern)}.cms-navlink-icon{color:var(--ui-text-dimmed)}.cms-navlink.is-active .cms-navlink-icon{color:var(--cms-fern)}.cms-badge{align-items:center;border:1px solid transparent;border-radius:var(--cms-radius-md);display:inline-flex;font-family:var(--cms-font-mono);font-size:.6875rem;letter-spacing:.08em;line-height:1.4;padding:.1875rem .5rem;pointer-events:none;text-transform:uppercase;transition:background-color var(--cms-duration) var(--cms-ease),border-color var(--cms-duration) var(--cms-ease);white-space:nowrap}.cms-badge.is-published{background:color-mix(in oklab,var(--cms-success) 18%,transparent);border-color:color-mix(in oklab,var(--cms-success) 32%,transparent);color:var(--cms-success)}.cms-badge.is-muted{background:var(--cms-field);border-color:var(--cms-line);color:var(--ui-text-muted)}.cms-badge.is-draft{background:transparent}.cms-badge.is-draft,.cms-dropzone{border:1px dashed var(--cms-line-strong);color:var(--ui-text-muted)}.cms-dropzone{align-items:center;background:color-mix(in oklab,var(--cms-surface) 55%,transparent);border-radius:var(--cms-radius-lg);cursor:pointer;display:flex;flex-direction:column;gap:.5rem;justify-content:center;padding:2rem 1.5rem;transition:border-color var(--cms-duration) var(--cms-ease),background-color var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease);width:100%}.cms-dropzone.is-over,.cms-dropzone:hover{background:var(--cms-field);border-color:var(--cms-fern);color:var(--ui-text-highlighted)}.cms-dropzone.is-dense{flex-direction:row;gap:.625rem;padding:.875rem 1.25rem}.cms-dropzone.is-busy{opacity:.7;pointer-events:none}.cms-pill{align-items:center;background:var(--cms-surface);border:1px solid var(--cms-line);border-radius:9999px;color:var(--ui-text-muted);cursor:pointer;display:inline-flex;flex:none;font-size:.8125rem;gap:.35rem;line-height:1.4;padding:.3rem .8rem;transition:background-color var(--cms-duration) var(--cms-ease),border-color var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease);white-space:nowrap}.cms-pill:hover{background:var(--cms-field);color:var(--ui-text-highlighted)}.cms-pill.is-active{background:var(--cms-field);border-color:color-mix(in oklab,var(--cms-fern) 45%,transparent);color:var(--cms-fern)}.cms-pill.is-empty{border-style:dashed}.cms-pill-group{align-items:center;display:inline-flex;gap:.125rem}.cms-pill-group .cms-pill{padding-right:.6rem}.cms-pill-discard{align-items:center;border-radius:9999px;color:var(--ui-text-dimmed);cursor:pointer;display:inline-flex;height:1.25rem;justify-content:center;transition:color var(--cms-duration) var(--cms-ease);width:1.25rem}.cms-pill-discard:hover{color:var(--ui-error)}.cms-folder-picker{align-items:center;display:flex;flex-wrap:wrap;gap:.375rem}.cms-folder-picker-input{width:11rem}.cms-form-hint{color:var(--ui-text-dimmed);font-size:.8125rem}.cms-dropzone-hint{align-items:center;color:var(--ui-text-dimmed);display:inline-flex;font-size:.75rem;gap:.3rem}.cms-block-label{color:var(--ui-text-toned);font-size:.875rem;font-weight:550;line-height:1.4;transition:color var(--cms-duration) var(--cms-ease)}.cms-block-label:hover{color:var(--ui-text-highlighted)}@keyframes cms-rise{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}.cms-rise{animation:cms-rise .3s var(--cms-ease) both}.cms-rise.is-delayed{animation-delay:90ms}@media (prefers-reduced-motion:reduce){.cms-rise{animation:none}}.cms-richtext{background:var(--cms-field);border:1px solid var(--cms-line);border-radius:var(--cms-radius-lg);overflow:hidden}.cms-richtext-toolbar{align-items:center;border-bottom:1px solid var(--cms-line);display:flex;flex-wrap:wrap;gap:2px;padding:4px}.cms-richtext-body{color:var(--ui-text-highlighted);font-size:.875rem;min-height:9rem;outline:none;padding:.75rem 1rem}.cms-richtext-body h2{font-size:1.25rem;font-weight:600;margin:.75em 0 .35em}.cms-richtext-body h3{font-size:1.1rem;font-weight:600;margin:.75em 0 .35em}.cms-richtext-body p{margin:.35em 0}.cms-richtext-body ul{list-style:disc;padding-left:1.25rem}.cms-richtext-body ol{list-style:decimal;padding-left:1.25rem}.cms-richtext-body blockquote{border-left:3px solid var(--cms-line-strong);color:var(--ui-text-muted);padding-left:.75rem}.cms-btn{align-items:center;border:1px solid transparent;border-radius:var(--cms-radius-md);display:inline-flex;font-weight:500;gap:.375rem;justify-content:center;line-height:1;min-height:var(--cms-btn-size);transition:background-color var(--cms-duration) var(--cms-ease),border-color var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease),opacity var(--cms-duration) var(--cms-ease);white-space:nowrap}.cms-btn:active:not(:disabled,[aria-disabled=true]){scale:.99}.cms-btn:disabled,.cms-btn[aria-disabled=true]{opacity:.5;pointer-events:none}.cms-btn.is-block{width:100%}.cms-btn.is-icon{justify-content:center;padding-inline:0;width:var(--cms-btn-size)}.cms-btn-xs{--cms-btn-size:1.75rem;font-size:.875rem;padding:.25rem .625rem}.cms-btn-sm{--cms-btn-size:2rem;font-size:.875rem;padding:.25rem .75rem}.cms-btn-md{--cms-btn-size:2.25rem;font-size:.875rem;padding:.25rem .875rem}.cms-btn-lg{--cms-btn-size:2.625rem;font-size:.9375rem;padding:.375rem 1.125rem}.cms-btn-primary.cms-btn-solid{background:var(--ui-primary);color:var(--ui-text-inverted)}.cms-btn-primary.cms-btn-solid:hover{background:color-mix(in oklab,var(--ui-primary) 82%,#fff)}.cms-btn-primary.cms-btn-solid:active{background:color-mix(in oklab,var(--ui-primary) 88%,#000)}.cms-btn-primary.cms-btn-soft{background:var(--cms-field);border-color:var(--cms-line);color:var(--ui-text-highlighted)}.cms-btn-primary.cms-btn-soft:hover{background:var(--cms-elevated);border-color:var(--cms-line-strong)}.cms-btn-primary.cms-btn-soft:active{background:var(--cms-field)}.cms-btn-primary.cms-btn-ghost{background:transparent;color:var(--ui-text-toned)}.cms-btn-primary.cms-btn-ghost:hover{background:var(--cms-field);color:var(--ui-text-highlighted)}.cms-btn-primary.cms-btn-ghost:active{background:var(--cms-elevated)}.cms-btn-neutral.cms-btn-solid{background:var(--cms-line-strong);color:var(--ui-text-highlighted)}.cms-btn-neutral.cms-btn-solid:hover{background:color-mix(in oklab,var(--cms-line-strong) 80%,var(--cms-text))}.cms-btn-neutral.cms-btn-solid:active{background:color-mix(in oklab,var(--cms-line-strong) 90%,#000)}.cms-btn-neutral.cms-btn-soft{background:var(--cms-field);border-color:var(--cms-line);color:var(--ui-text-highlighted)}.cms-btn-neutral.cms-btn-soft:hover{background:var(--cms-elevated);border-color:var(--cms-line-strong)}.cms-btn-neutral.cms-btn-soft:active{background:var(--cms-field)}.cms-btn-neutral.cms-btn-ghost{background:transparent;color:var(--ui-text-toned)}.cms-btn-neutral.cms-btn-ghost:hover{background:var(--cms-field);color:var(--ui-text-highlighted)}.cms-btn-neutral.cms-btn-ghost:active{background:var(--cms-elevated)}.cms-btn-error.cms-btn-solid{background:var(--ui-error);color:var(--cms-canvas)}.cms-btn-error.cms-btn-solid:hover{background:color-mix(in oklab,var(--ui-error) 85%,#fff)}.cms-btn-error.cms-btn-solid:active{background:color-mix(in oklab,var(--ui-error) 88%,#000)}.cms-btn-error.cms-btn-soft{background:color-mix(in oklab,var(--ui-error) 16%,transparent);border-color:color-mix(in oklab,var(--ui-error) 28%,transparent);color:var(--ui-error)}.cms-btn-error.cms-btn-soft:hover{background:color-mix(in oklab,var(--ui-error) 24%,transparent);border-color:color-mix(in oklab,var(--ui-error) 40%,transparent)}.cms-btn-error.cms-btn-soft:active{background:color-mix(in oklab,var(--ui-error) 16%,transparent)}.cms-btn-error.cms-btn-ghost{background:transparent;color:var(--ui-error)}.cms-btn-error.cms-btn-ghost:hover{background:color-mix(in oklab,var(--ui-error) 14%,transparent)}.cms-btn-error.cms-btn-ghost:active{background:color-mix(in oklab,var(--ui-error) 22%,transparent)}.cms-btn-success.cms-btn-solid{background:var(--ui-success);color:var(--cms-canvas)}.cms-btn-success.cms-btn-solid:hover{background:color-mix(in oklab,var(--ui-success) 85%,#fff)}.cms-btn-success.cms-btn-solid:active{background:color-mix(in oklab,var(--ui-success) 88%,#000)}.cms-btn-success.cms-btn-soft{background:color-mix(in oklab,var(--ui-success) 16%,transparent);border-color:color-mix(in oklab,var(--ui-success) 28%,transparent);color:var(--ui-success)}.cms-btn-success.cms-btn-soft:hover{background:color-mix(in oklab,var(--ui-success) 24%,transparent);border-color:color-mix(in oklab,var(--ui-success) 40%,transparent)}.cms-btn-success.cms-btn-soft:active{background:color-mix(in oklab,var(--ui-success) 16%,transparent)}.cms-btn-success.cms-btn-ghost{background:transparent;color:var(--ui-success)}.cms-btn-success.cms-btn-ghost:hover{background:color-mix(in oklab,var(--ui-success) 14%,transparent)}.cms-btn-success.cms-btn-ghost:active{background:color-mix(in oklab,var(--ui-success) 22%,transparent)}.cms-form{display:flex;flex-direction:column;gap:1.25rem}.cms-tabs{align-items:center;border-bottom:1px solid var(--ui-border);display:flex;gap:.25rem;margin-bottom:.25rem;overflow-x:auto}.cms-tab{border-bottom:2px solid transparent;color:var(--ui-text-toned);cursor:pointer;font-size:.875rem;font-weight:500;margin-bottom:-1px;padding:.5rem .875rem;transition:color var(--cms-duration) var(--cms-ease),border-color var(--cms-duration) var(--cms-ease);white-space:nowrap}.cms-tab:hover{color:var(--ui-text-highlighted)}.cms-tab.is-active{border-bottom-color:var(--cms-fern);color:var(--cms-fern)}.cms-form-field{display:flex;flex-direction:column;gap:.375rem}.cms-form-label-row{align-items:center;display:flex;gap:.75rem;justify-content:space-between;min-height:1.75rem}.cms-form-label{color:var(--ui-text-toned);font-size:.875rem;font-weight:500}.cms-form-error,.cms-form-required{color:var(--ui-error)}.cms-form-error{font-size:.875rem}.cms-form-actions{align-items:center;display:flex;gap:.75rem;justify-content:flex-end;padding-top:.25rem}.cms-field{background:var(--cms-field);border:1px solid var(--cms-line);border-radius:var(--ui-radius);color:var(--ui-text-highlighted);line-height:1.25;transition:border-color var(--cms-duration) var(--cms-ease),box-shadow var(--cms-duration) var(--cms-ease);width:100%}textarea.cms-field{line-height:1.5;min-height:0;padding-bottom:.625rem;padding-top:.625rem;resize:vertical}.cms-field::-moz-placeholder{color:var(--ui-text-dimmed)}.cms-field::placeholder{color:var(--ui-text-dimmed)}.cms-field:hover:not(:focus,:focus-within){border-color:var(--cms-line-strong)}.cms-field:focus,.cms-field:focus-within{border-color:var(--cms-fern);box-shadow:0 0 0 3px color-mix(in oklab,var(--cms-fern) 25%,transparent);outline:none}.cms-field.is-error{border-color:var(--ui-error)}.cms-field.is-error:focus,.cms-field.is-error:focus-within{border-color:var(--ui-error);box-shadow:0 0 0 3px color-mix(in oklab,var(--ui-error) 25%,transparent)}.cms-field-sm{font-size:.875rem;min-height:2rem;padding:.25rem .625rem}.cms-field-md{font-size:.875rem;min-height:2.25rem;padding:.25rem .75rem}.cms-field-lg{font-size:.9375rem;min-height:2.625rem;padding:.375rem .875rem}.cms-select-chevron{align-items:center;cursor:pointer;display:inline-flex;transition:transform var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease)}.cms-select-chevron:hover{color:var(--ui-text-muted)}.cms-select-chevron.is-open{transform:rotate(180deg)}.cms-locale-switch{align-items:center;background:var(--cms-field);border:1px solid var(--cms-line);border-radius:var(--cms-radius-md);display:inline-flex;gap:2px;padding:2px}.cms-locale-option{border-radius:var(--cms-radius-sm);color:var(--ui-text-muted);cursor:pointer;font-family:var(--cms-font-mono);font-size:.6875rem;letter-spacing:.08em;line-height:1.4;padding:.125rem .5rem;text-transform:uppercase;transition:background-color var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease)}.cms-locale-option:hover:not(.is-active){color:var(--ui-text-highlighted)}.cms-locale-option.is-empty{color:var(--ui-text-dimmed)}.cms-locale-option.is-active{background:var(--cms-elevated);color:var(--cms-fern)}.cms-input-wrap{align-items:center;display:flex;position:relative;width:100%}.cms-input-wrap .cms-field{width:100%}.cms-input-lead,.cms-input-trail{align-items:center;color:var(--ui-text-dimmed);display:flex;position:absolute}.cms-input-lead{left:.625rem}.cms-input-trail{right:.375rem}.cms-input-wrap.has-lead .cms-field{padding-left:2rem}.cms-switch{align-items:center;background:var(--cms-line-strong);border-radius:9999px;display:inline-flex;flex-shrink:0;height:1.25rem;position:relative;transition:background-color var(--cms-duration) var(--cms-ease);width:2.25rem}.cms-switch.is-on{background:var(--cms-fern)}.cms-switch:after{background:var(--cms-text);border-radius:9999px;content:"";height:.875rem;left:.1875rem;position:absolute;transition:transform var(--cms-duration) var(--cms-ease);width:.875rem}.cms-switch.is-on:after{background:var(--cms-canvas);transform:translateX(1rem)}.cms-overlay{align-items:center;background:var(--cms-scrim);display:flex;inset:0;justify-content:center;overflow-y:auto;padding:2rem 1rem;position:fixed;z-index:60}.cms-modal{background:var(--cms-surface);border:1px solid var(--cms-line);border-radius:var(--cms-radius-lg);box-shadow:var(--cms-shadow-overlay);display:flex;flex-direction:column;max-height:calc(100dvh - 4rem);max-width:32rem;width:100%}.cms-modal.is-sm{max-width:28rem}.cms-modal.is-lg{max-width:48rem}.cms-modal-header{padding:1.25rem 1.5rem 0}.cms-modal-body{flex:1;min-height:0;overflow-y:auto;padding:1.25rem 1.5rem 1.5rem}.cms-modal-enter-active,.cms-modal-leave-active{transition:opacity var(--cms-duration-lg) var(--cms-ease)}.cms-modal-enter-active .cms-modal,.cms-modal-leave-active .cms-modal{transition:transform var(--cms-duration-lg) var(--cms-ease),opacity var(--cms-duration-lg) var(--cms-ease);will-change:transform}.cms-modal-enter-from,.cms-modal-leave-to{opacity:0}.cms-modal-enter-from .cms-modal,.cms-modal-leave-to .cms-modal{opacity:0;transform:translateY(8px)}.cms-overlay-drawer{align-items:stretch;justify-content:flex-end;overflow:hidden;padding:0;z-index:55}.cms-drawer{background:var(--cms-surface);border-left:1px solid var(--cms-line);border-radius:var(--cms-radius-lg) 0 0 var(--cms-radius-lg);box-shadow:var(--cms-shadow-overlay);display:flex;flex-direction:column;height:100%;max-width:46rem;width:100%}.cms-drawer-header{align-items:center;border-bottom:1px solid var(--cms-line);display:flex;gap:1rem;justify-content:space-between;padding:1.25rem 1.75rem}.cms-drawer-body{flex:1;min-height:0;overflow-y:auto;padding:1.75rem}.cms-drawer-footer{align-items:center;border-top:1px solid var(--cms-line);display:flex;gap:1rem;justify-content:space-between;padding:1.25rem 1.75rem}.cms-drawer-enter-active,.cms-drawer-leave-active{transition:opacity var(--cms-duration-lg) var(--cms-ease)}.cms-drawer-enter-active .cms-drawer,.cms-drawer-leave-active .cms-drawer{transition:transform var(--cms-duration-lg) var(--cms-ease);will-change:transform}.cms-drawer-enter-from,.cms-drawer-leave-to{opacity:0}.cms-drawer-enter-from .cms-drawer,.cms-drawer-leave-to .cms-drawer{transform:translateX(100%)}@media (prefers-reduced-motion:reduce){.cms-drawer-enter-active,.cms-drawer-enter-active .cms-drawer,.cms-drawer-leave-active,.cms-drawer-leave-active .cms-drawer,.cms-modal-enter-active,.cms-modal-enter-active .cms-modal,.cms-modal-leave-active,.cms-modal-leave-active .cms-modal{transition-duration:.01ms}}.cms-menu{background:var(--cms-elevated);border:1px solid var(--cms-line);border-radius:var(--cms-radius-lg);box-shadow:var(--cms-shadow-overlay);margin-top:.375rem;min-width:12rem;padding:.25rem;position:absolute;z-index:50}.cms-menu.align-end{right:0}.cms-menu-item{align-items:center;border-radius:var(--cms-radius-sm);color:var(--ui-text);cursor:pointer;display:flex;font-size:.875rem;gap:.5rem;padding:.4rem .6rem;text-align:left;transition:background-color var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease);width:100%}.cms-menu-item:hover{background:color-mix(in oklab,var(--cms-text) 8%,transparent);color:var(--ui-text-highlighted)}.cms-menu-check{color:var(--cms-fern);margin-left:auto}.cms-popover{background:var(--cms-elevated);border:1px solid var(--cms-line);border-radius:var(--cms-radius-lg);box-shadow:var(--cms-shadow-overlay);margin-top:.375rem;position:absolute;z-index:50}.cms-selectmenu-input{background:transparent;color:var(--ui-text-highlighted);flex:1;min-width:4rem;outline:none}.cms-selectmenu-affix{color:var(--ui-text-dimmed);flex-shrink:0}.cms-selectmenu-panel{background:var(--cms-elevated);border:1px solid var(--cms-line);border-radius:var(--cms-radius-lg);box-shadow:var(--cms-shadow-overlay);left:0;margin-top:.375rem;max-height:15rem;overflow-y:auto;padding:.25rem;position:absolute;right:0;z-index:50}.cms-tag{align-items:center;background:var(--cms-elevated);border:1px solid var(--cms-line);border-radius:9999px;color:var(--ui-text-highlighted);display:inline-flex;font-size:.8125rem;gap:.25rem;padding:.125rem .5rem}.cms-media-gallery{display:flex;flex-direction:column;gap:1.25rem}.cms-media-source{color:var(--ui-text-muted);font-size:.8125rem;margin-top:-.75rem}.cms-media-filters{align-items:center;display:flex;flex-wrap:wrap;gap:.25rem}.cms-media-filters-actions{align-items:center;display:flex;flex-shrink:0;gap:.5rem;margin-left:auto}.cms-media-filter-separator{background:var(--cms-line);flex:none;height:1rem;margin-inline:.25rem;width:1px}.cms-pill-count{color:var(--ui-text-dimmed);font-family:var(--cms-font-mono);font-size:.6875rem;margin-left:.25rem}.cms-blocks-grid{display:grid;gap:.75rem;grid-template-columns:repeat(2,minmax(0,1fr))}@media (min-width:640px){.cms-blocks-grid{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width:1024px){.cms-blocks-grid{grid-template-columns:repeat(4,minmax(0,1fr))}}.cms-block-tile{overflow:hidden;transition:opacity var(--cms-duration) var(--cms-ease)}.cms-block-tile.is-muted{opacity:.45}.cms-block-preview{align-items:center;aspect-ratio:4/3;background:var(--cms-field);color:var(--ui-text-dimmed);display:flex;justify-content:center;position:relative;width:100%}.cms-block-preview>img,.cms-block-preview>video{height:100%;inset:0;-o-object-fit:cover;object-fit:cover;position:absolute;width:100%}.cms-block-summary{color:var(--ui-text-muted);font-size:.75rem;line-height:1.5;padding:.75rem;text-align:left}.cms-block-hidden{color:var(--cms-text);filter:drop-shadow(0 1px 2px rgb(0 0 0/.6));left:.5rem;position:absolute;top:.5rem}.cms-block-tile.is-dragging{opacity:.4}.cms-block-tile.is-drop-target{outline:2px solid var(--cms-fern);outline-offset:2px}.cms-block-bar{gap:.25rem;padding:.375rem}.cms-block-bar,.cms-block-grip{align-items:center;display:flex}.cms-block-grip{color:var(--ui-text-dimmed);cursor:grab;justify-content:center;padding:.25rem;transition:color var(--cms-duration) var(--cms-ease)}.cms-block-grip:hover{color:var(--ui-text-muted)}.cms-block-tile.is-dragging .cms-block-grip{cursor:grabbing}.cms-block-bar-name{color:var(--ui-text-muted);flex:1;font-size:.75rem;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cms-block-bar-actions{align-items:center;display:flex;gap:.125rem}.cms-block-add{align-items:center;border:1px dashed var(--cms-line-strong);border-radius:var(--cms-radius-md);color:var(--ui-text-muted);display:flex;flex-direction:column;font-size:.8125rem;font-weight:500;gap:.375rem;justify-content:center;min-height:6rem;transition:border-color var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease)}.cms-block-add:hover{border-color:var(--cms-fern);color:var(--ui-text-highlighted)}.cms-media-grid{display:grid;gap:1rem;grid-template-columns:repeat(2,minmax(0,1fr))}@media (min-width:640px){.cms-media-grid{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width:1024px){.cms-media-grid:not(.is-compact){grid-template-columns:repeat(4,minmax(0,1fr))}}.cms-media-tile{overflow:hidden;text-align:left;transition:border-color var(--cms-duration) var(--cms-ease)}.cms-media-tile.is-selectable{cursor:pointer}.cms-media-tile.is-selectable:hover{border-color:var(--cms-line-strong)}.cms-media-preview{align-items:center;aspect-ratio:1;background:var(--cms-field);color:var(--ui-text-dimmed);display:flex;justify-content:center;position:relative}.cms-media-preview>img,.cms-media-preview>video{height:100%;inset:0;-o-object-fit:cover;object-fit:cover;position:absolute;width:100%}.cms-media-actions{display:flex;gap:.25rem;opacity:0;position:absolute;right:.5rem;top:.5rem;transition:opacity var(--cms-duration) var(--cms-ease)}.cms-media-actions:focus-within,.cms-media-tile:hover .cms-media-actions{opacity:1}.cms-media-info{background:linear-gradient(0deg,rgba(0,0,0,.75),transparent);bottom:0;color:var(--cms-text);font-family:var(--cms-font-mono);font-size:.75rem;left:0;letter-spacing:.08em;line-height:1.4;opacity:0;padding:1.5rem .75rem .5rem;pointer-events:none;position:absolute;right:0;text-transform:uppercase;transition:opacity var(--cms-duration) var(--cms-ease)}.cms-media-tile:focus-visible .cms-media-info,.cms-media-tile:hover .cms-media-info{opacity:1}.cms-media-meta{display:flex;flex-direction:column;gap:.25rem;padding:.625rem .875rem}.cms-media-name{color:var(--ui-text-highlighted);font-size:.875rem;font-weight:500}.cms-media-folder,.cms-media-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cms-media-folder{align-items:center;color:var(--ui-text-dimmed);display:inline-flex;font-size:.75rem;gap:.25rem}.cms-media-single{max-width:15rem;width:100%}.cms-media-thumb{background:var(--cms-field);border-radius:var(--cms-radius-sm);height:2.25rem;-o-object-fit:cover;object-fit:cover;width:2.25rem}.cms-media-thumb.is-fallback{align-items:center;color:var(--ui-text-dimmed);display:flex;justify-content:center}.cms-media-thumb-empty{color:var(--ui-text-dimmed)}.cms-table-shell{display:flex;flex:1;flex-direction:column;min-height:0;overflow:hidden}.cms-table-scroll{flex:1;min-height:0;overflow:auto}.cms-table{border-collapse:collapse;min-width:-moz-max-content;min-width:max-content;text-align:left;width:100%}.cms-table td,.cms-table th{white-space:nowrap}.cms-table thead{background:var(--cms-surface);position:sticky;top:0;z-index:10}.cms-table th{background:var(--cms-surface);box-shadow:inset 0 -1px 0 var(--cms-line);color:var(--ui-text-muted);font-family:var(--cms-font-mono);font-size:.6875rem;font-weight:400;letter-spacing:.08em;line-height:1.4;padding:.75rem 1.25rem;text-transform:uppercase;transition:background-color var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease)}.cms-table td{color:var(--ui-text);font-size:.875rem;padding:.875rem 1.25rem}.cms-table tbody tr{border-bottom:1px solid var(--cms-line);transition:background-color var(--cms-duration) var(--cms-ease)}.cms-table tbody tr:last-child{border-bottom:0}.cms-table tbody tr:hover{background:var(--cms-field)}.cms-table-cell-actions{display:flex;justify-content:flex-end}.cms-table th[draggable=true]{cursor:grab;-webkit-user-select:none;-moz-user-select:none;user-select:none}.cms-table th[draggable=true]:active{cursor:grabbing}.cms-table th.is-dragging{opacity:.4}.cms-table th.is-drop-target{background:var(--cms-field);box-shadow:inset 0 -1px 0 var(--cms-line),inset 0 0 0 2px color-mix(in oklab,var(--cms-fern) 55%,transparent)}.cms-th-label{align-items:center;display:inline-flex;gap:.35rem}.cms-table th.is-sortable{cursor:pointer}.cms-table th.is-sortable:hover,.cms-table th.is-sorted{color:var(--ui-text-highlighted)}.cms-table th.is-sorted{background:var(--cms-field)}.cms-th-sort{flex:none;opacity:0;transition:opacity var(--cms-duration) var(--cms-ease)}.cms-table th.is-sortable:hover .cms-th-sort,.cms-table th.is-sorted .cms-th-sort{opacity:1}.cms-alert{border:1px solid transparent;border-radius:var(--cms-radius-md);display:flex;flex-direction:column;font-size:.875rem;gap:.125rem;padding:.75rem 1rem}.cms-alert-title{font-weight:550}.cms-alert-error{background:color-mix(in oklab,var(--ui-error) 14%,transparent);border-color:color-mix(in oklab,var(--ui-error) 26%,transparent);color:var(--ui-error)}.cms-alert-success{background:color-mix(in oklab,var(--ui-success) 14%,transparent);border-color:color-mix(in oklab,var(--ui-success) 26%,transparent);color:var(--ui-success)}.cms-alert-warning{background:color-mix(in oklab,var(--ui-warning) 14%,transparent);border-color:color-mix(in oklab,var(--ui-warning) 26%,transparent);color:var(--ui-warning)}.cms-alert-neutral{background:var(--cms-field);border-color:var(--cms-line);color:var(--ui-text-toned)}.cms-alert-neutral .cms-alert-title{color:var(--ui-text-highlighted)}.cms-toaster{bottom:1rem;display:flex;flex-direction:column;gap:.5rem;max-width:calc(100vw - 2rem);position:fixed;right:1rem;width:20rem;z-index:80}.cms-toast{background:var(--cms-elevated);border:1px solid var(--cms-line);border-radius:var(--cms-radius-lg);box-shadow:var(--cms-shadow-overlay);padding:.75rem .875rem}.cms-toast-row{align-items:flex-start;display:flex;gap:.625rem}.cms-toast-icon{color:var(--ui-text-muted);flex-shrink:0;margin-top:.0625rem}.cms-toast.is-success .cms-toast-icon{color:var(--ui-success)}.cms-toast.is-error .cms-toast-icon{color:var(--ui-error)}.cms-toast-title{color:var(--ui-text-highlighted);font-size:.875rem;font-weight:500}.cms-toast-description{color:var(--ui-text-muted);font-size:.875rem;margin-top:.125rem}.cms-toast-dismiss{color:var(--ui-text-dimmed);flex-shrink:0;transition:color var(--cms-duration) var(--cms-ease)}.cms-toast-dismiss:hover{color:var(--ui-text-highlighted)}.cms-pagination{align-items:center;display:flex;gap:.25rem;justify-content:center}.cms-page-btn{border-radius:var(--cms-radius-sm);color:var(--ui-text-toned);font-size:.875rem;height:2rem;min-width:2rem;padding:0 .5rem;transition:background-color var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease)}.cms-page-btn:hover:not(:disabled){background:var(--cms-field);color:var(--ui-text-highlighted)}.cms-page-btn.is-active{background:var(--cms-field);color:var(--cms-fern);font-weight:600}.cms-page-btn:disabled{opacity:.4}
|
|
1
|
+
@layer theme, base, components, utilities;@import "tailwindcss/theme.css" layer(theme) theme(inline);.cms-scope{@import "tailwindcss/preflight.css" layer(base);@import "tailwindcss/utilities.css" layer(utilities) source(none)}@source "../app";.cms-scope,body:has(.cms-scope){--cms-canvas:#1b1b1e;--cms-surface:#222226;--cms-field:#2a2a2f;--cms-elevated:#2f2f35;--cms-line:#35353b;--cms-line-strong:#46464e;--cms-text:#ededef;--cms-text-toned:#c2c2c8;--cms-text-muted:#9a9aa3;--cms-text-dimmed:#6e6e78;--cms-fern:#8fae8b;--cms-error:#f0857d;--cms-success:#7fb083;--cms-warning:#d4ad76;--cms-font-ui:"Hanken Grotesk Variable","Hanken Grotesk",ui-sans-serif,system-ui,sans-serif;--cms-font-mono:"Fragment Mono",ui-monospace,SFMono-Regular,Menlo,monospace;--cms-radius-sm:6px;--cms-radius-md:8px;--cms-radius-lg:12px;--cms-shadow-overlay:0 8px 24px rgba(0,0,0,.35);--cms-scrim:rgba(0,0,0,.5);--cms-duration:120ms;--cms-duration-lg:250ms;--cms-ease:cubic-bezier(0.2,0,0,1);color-scheme:dark;--ui-text-dimmed:var(--cms-text-dimmed);--ui-text-muted:var(--cms-text-muted);--ui-text-toned:var(--cms-text-toned);--ui-text:var(--cms-text-toned);--ui-text-highlighted:var(--cms-text);--ui-text-inverted:var(--cms-canvas);--ui-bg:var(--cms-surface);--ui-bg-muted:var(--cms-canvas);--ui-bg-elevated:var(--cms-field);--ui-bg-accented:var(--cms-elevated);--ui-bg-inverted:var(--cms-text);--ui-border:var(--cms-line);--ui-border-muted:var(--cms-line);--ui-border-accented:var(--cms-line-strong);--ui-border-inverted:var(--cms-text);--ui-primary:var(--cms-text);--ui-accent:var(--cms-fern);--ui-error:var(--cms-error);--ui-success:var(--cms-success);--ui-warning:var(--cms-warning);--ui-radius:var(--cms-radius-md);color:var(--ui-text);font-family:var(--cms-font-ui);margin:0;padding:0;-webkit-font-smoothing:antialiased}body:has(.cms-scope) ::-moz-selection{background:color-mix(in oklab,var(--cms-fern) 35%,transparent);color:var(--ui-text-highlighted)}body:has(.cms-scope) ::selection{background:color-mix(in oklab,var(--cms-fern) 35%,transparent);color:var(--ui-text-highlighted)}body:has(.cms-scope) :is(.cms-scope,.cms-scope *){scrollbar-color:var(--cms-line-strong) transparent;scrollbar-width:thin}body:has(.cms-scope) :is(.cms-scope,.cms-scope *)::-webkit-scrollbar{height:10px;width:10px}body:has(.cms-scope) :is(.cms-scope,.cms-scope *)::-webkit-scrollbar-track{background:transparent}body:has(.cms-scope) :is(.cms-scope,.cms-scope *)::-webkit-scrollbar-thumb{background:var(--cms-line-strong);background-clip:content-box;border:3px solid transparent;border-radius:9999px}body:has(.cms-scope) :is(.cms-scope,.cms-scope *)::-webkit-scrollbar-thumb:hover{background:color-mix(in oklab,var(--cms-line-strong) 70%,var(--cms-text));background-clip:content-box}.cms-display,.cms-title{font-family:var(--cms-font-ui);font-weight:650;letter-spacing:-.01em}.cms-title{color:var(--ui-text-highlighted)}.cms-title-sm{font-size:1.125rem;line-height:1.4}.cms-title-md{font-size:1.5rem;line-height:1.25}.cms-title-lg{font-size:1.875rem;line-height:1.2}.cms-subtitle{color:var(--ui-text-muted);font-size:.875rem}.cms-accent{color:var(--cms-fern)}.cms-label{color:var(--ui-text-muted);font-size:.75rem;font-weight:500;line-height:1.4}body:has(.cms-scope) :is(button,a,[role=button],[role=menuitem],[role=option],.__clickable):not(:disabled,[aria-disabled=true]){@apply cursor-pointer select-none}body:has(.cms-scope) :is(button,a,[role=button],[role=menuitem],[role=option]):focus-visible{outline:2px solid var(--cms-fern);outline-offset:2px}.cms-canvas{background-color:var(--cms-canvas)}.cms-card{background:var(--cms-surface);border:1px solid var(--cms-line);border-radius:var(--cms-radius-lg)}.cms-panel{padding:1.75rem}body:has(.cms-scope),html:has(.cms-scope){overscroll-behavior-y:none}.cms-shell{display:flex;min-height:100vh}.cms-sidebar{background:var(--cms-surface);border-right:1px solid var(--cms-line);display:flex;flex-direction:column;flex-shrink:0;gap:1.75rem;height:100vh;padding:1.75rem 1rem;position:sticky;top:0;width:16rem}.cms-sidebar-brand{color:var(--ui-text-highlighted);font-family:var(--cms-font-ui);font-size:1.25rem;font-weight:650;letter-spacing:-.01em;padding-inline:.75rem}.cms-sidebar-nav{display:flex;flex:1;flex-direction:column;gap:1.5rem;overflow-y:auto;overscroll-behavior-y:none}.cms-sidebar-group{display:flex;flex-direction:column;gap:.25rem}.cms-sidebar-group>.cms-label{margin-bottom:.5rem;padding-inline:.75rem}.cms-sidebar-footer{border-top:1px solid var(--cms-line);display:flex;flex-direction:column;gap:.75rem;padding-top:1rem}.cms-sidebar-footer .cms-btn.is-block{color:var(--ui-text-muted);justify-content:space-between;padding-inline:.75rem}.cms-sidebar-footer .cms-btn.is-block:hover{background:var(--cms-field);color:var(--ui-text)}.cms-main{flex:1;height:100vh;min-width:0;overflow-y:auto;overscroll-behavior-y:none;padding:2.5rem}.cms-main-inner{margin-inline:auto;max-width:64rem;min-height:100%;width:100%}.cms-main-inner,.cms-page{display:flex;flex-direction:column}.cms-page{gap:1.75rem}.cms-page.is-fill{flex:1;min-height:0}.cms-page-header{align-items:flex-end;display:flex;gap:1rem;justify-content:space-between}.cms-page-heading{display:flex;flex-direction:column;gap:.5rem}.cms-breadcrumb{align-items:center;color:var(--ui-text-muted);display:flex;font-size:.75rem;font-weight:500;gap:.375rem;line-height:1.4}.cms-breadcrumb-link{border-radius:.25rem;padding:.0625rem .125rem;transition:color .15s ease}.cms-breadcrumb-link:hover{color:var(--ui-text-highlighted)}.cms-breadcrumb-separator{color:var(--ui-text-dimmed)}.cms-tree-row{align-items:center;display:flex;gap:.75rem;padding:.625rem 1rem;transition:background-color .15s ease}.cms-tree-row:hover{background-color:var(--ui-bg-elevated)}.cms-tree-branch{color:var(--ui-text-dimmed);flex-shrink:0;transform:scaleY(-1) rotate(90deg);width:1rem}.cms-toolbar{align-items:center;display:flex;gap:1.5rem}.cms-toolbar-actions{align-items:center;display:flex;flex-shrink:0;gap:.5rem;margin-left:auto}.cms-actions{align-items:center;display:flex;gap:.75rem}.cms-actions.is-end{justify-content:flex-end}.cms-spinner{color:var(--ui-text-dimmed);flex:1;justify-content:center;padding-block:2.5rem}.cms-empty,.cms-spinner{align-items:center;display:flex}.cms-empty{background:color-mix(in oklab,var(--cms-surface) 55%,transparent);border:1px dashed var(--cms-line-strong);border-radius:var(--cms-radius-lg);flex-direction:column;gap:.5rem;padding:3.5rem;text-align:center}.cms-empty.is-fill{flex:1;justify-content:center}.cms-empty-icon{color:var(--cms-fern);margin-bottom:.25rem}.cms-empty>.cms-btn{margin-top:.75rem}.cms-auth{align-items:center;display:flex;justify-content:center;min-height:100vh;padding:1.5rem}.cms-auth-panel{gap:2rem;max-width:24rem;width:100%}.cms-auth-header,.cms-auth-panel{display:flex;flex-direction:column}.cms-auth-header{align-items:center;gap:.75rem;text-align:center}.cms-auth-card{padding:1.75rem}.cms-navlink{align-items:center;border-radius:var(--cms-radius-md);color:var(--ui-text-toned);display:flex;font-size:.875rem;font-weight:500;gap:.625rem;padding:.5rem .75rem;transition:background-color var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease)}.cms-navlink:hover{background:var(--cms-field);color:var(--ui-text-highlighted)}.cms-navlink.is-active{background:var(--cms-field);color:var(--cms-fern)}.cms-navlink-icon{color:var(--ui-text-dimmed)}.cms-navlink.is-active .cms-navlink-icon{color:var(--cms-fern)}.cms-badge{align-items:center;border:1px solid transparent;border-radius:var(--cms-radius-md);display:inline-flex;font-family:var(--cms-font-mono);font-size:.6875rem;letter-spacing:.08em;line-height:1.4;padding:.1875rem .5rem;pointer-events:none;text-transform:uppercase;transition:background-color var(--cms-duration) var(--cms-ease),border-color var(--cms-duration) var(--cms-ease);white-space:nowrap}.cms-badge.is-published{background:color-mix(in oklab,var(--cms-success) 18%,transparent);border-color:color-mix(in oklab,var(--cms-success) 32%,transparent);color:var(--cms-success)}.cms-badge.is-muted{background:var(--cms-field);border-color:var(--cms-line);color:var(--ui-text-muted)}.cms-badge.is-draft{background:transparent}.cms-badge.is-draft,.cms-dropzone{border:1px dashed var(--cms-line-strong);color:var(--ui-text-muted)}.cms-dropzone{align-items:center;background:color-mix(in oklab,var(--cms-surface) 55%,transparent);border-radius:var(--cms-radius-lg);cursor:pointer;display:flex;flex-direction:column;gap:.5rem;justify-content:center;padding:2rem 1.5rem;transition:border-color var(--cms-duration) var(--cms-ease),background-color var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease);width:100%}.cms-dropzone.is-over,.cms-dropzone:hover{background:var(--cms-field);border-color:var(--cms-fern);color:var(--ui-text-highlighted)}.cms-dropzone.is-dense{flex-direction:row;gap:.625rem;padding:.875rem 1.25rem}.cms-dropzone.is-busy{opacity:.7;pointer-events:none}.cms-pill{align-items:center;background:var(--cms-surface);border:1px solid var(--cms-line);border-radius:9999px;color:var(--ui-text-muted);cursor:pointer;display:inline-flex;flex:none;font-size:.8125rem;gap:.35rem;line-height:1.4;padding:.3rem .8rem;transition:background-color var(--cms-duration) var(--cms-ease),border-color var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease);white-space:nowrap}.cms-pill:hover{background:var(--cms-field);color:var(--ui-text-highlighted)}.cms-pill.is-active{background:var(--cms-field);border-color:color-mix(in oklab,var(--cms-fern) 45%,transparent);color:var(--cms-fern)}.cms-pill.is-empty{border-style:dashed}.cms-pill-group{align-items:center;display:inline-flex;gap:.125rem}.cms-pill-group .cms-pill{padding-right:.6rem}.cms-pill-discard{align-items:center;border-radius:9999px;color:var(--ui-text-dimmed);cursor:pointer;display:inline-flex;height:1.25rem;justify-content:center;transition:color var(--cms-duration) var(--cms-ease);width:1.25rem}.cms-pill-discard:hover{color:var(--ui-error)}.cms-folder-picker{align-items:center;display:flex;flex-wrap:wrap;gap:.375rem}.cms-folder-picker-input{width:11rem}.cms-form-hint{color:var(--ui-text-dimmed);font-size:.8125rem}.cms-dropzone-hint{align-items:center;color:var(--ui-text-dimmed);display:inline-flex;font-size:.75rem;gap:.3rem}.cms-block-label{color:var(--ui-text-toned);font-size:.875rem;font-weight:550;line-height:1.4;transition:color var(--cms-duration) var(--cms-ease)}.cms-block-label:hover{color:var(--ui-text-highlighted)}@keyframes cms-rise{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}.cms-rise{animation:cms-rise .3s var(--cms-ease) both}.cms-rise.is-delayed{animation-delay:90ms}@media (prefers-reduced-motion:reduce){.cms-rise{animation:none}}.cms-richtext{background:var(--cms-field);border:1px solid var(--cms-line);border-radius:var(--cms-radius-lg);overflow:hidden}.cms-richtext-toolbar{align-items:center;border-bottom:1px solid var(--cms-line);display:flex;flex-wrap:wrap;gap:2px;padding:4px}.cms-richtext-body{color:var(--ui-text-highlighted);font-size:.875rem;min-height:9rem;outline:none;padding:.75rem 1rem}.cms-richtext-body h2{font-size:1.25rem;font-weight:600;margin:.75em 0 .35em}.cms-richtext-body h3{font-size:1.1rem;font-weight:600;margin:.75em 0 .35em}.cms-richtext-body p{margin:.35em 0}.cms-richtext-body ul{list-style:disc;padding-left:1.25rem}.cms-richtext-body ol{list-style:decimal;padding-left:1.25rem}.cms-richtext-body blockquote{border-left:3px solid var(--cms-line-strong);color:var(--ui-text-muted);padding-left:.75rem}.cms-btn{align-items:center;border:1px solid transparent;border-radius:var(--cms-radius-md);display:inline-flex;font-weight:500;gap:.375rem;justify-content:center;line-height:1;min-height:var(--cms-btn-size);transition:background-color var(--cms-duration) var(--cms-ease),border-color var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease),opacity var(--cms-duration) var(--cms-ease);white-space:nowrap}.cms-btn:active:not(:disabled,[aria-disabled=true]){scale:.99}.cms-btn:disabled,.cms-btn[aria-disabled=true]{opacity:.5;pointer-events:none}.cms-btn.is-block{width:100%}.cms-btn.is-icon{justify-content:center;padding-inline:0;width:var(--cms-btn-size)}.cms-btn-xs{--cms-btn-size:1.75rem;font-size:.875rem;padding:.25rem .625rem}.cms-btn-sm{--cms-btn-size:2rem;font-size:.875rem;padding:.25rem .75rem}.cms-btn-md{--cms-btn-size:2.25rem;font-size:.875rem;padding:.25rem .875rem}.cms-btn-lg{--cms-btn-size:2.625rem;font-size:.9375rem;padding:.375rem 1.125rem}.cms-btn-primary.cms-btn-solid{background:var(--ui-primary);color:var(--ui-text-inverted)}.cms-btn-primary.cms-btn-solid:hover{background:color-mix(in oklab,var(--ui-primary) 82%,#fff)}.cms-btn-primary.cms-btn-solid:active{background:color-mix(in oklab,var(--ui-primary) 88%,#000)}.cms-btn-primary.cms-btn-soft{background:var(--cms-field);border-color:var(--cms-line);color:var(--ui-text-highlighted)}.cms-btn-primary.cms-btn-soft:hover{background:var(--cms-elevated);border-color:var(--cms-line-strong)}.cms-btn-primary.cms-btn-soft:active{background:var(--cms-field)}.cms-btn-primary.cms-btn-ghost{background:transparent;color:var(--ui-text-toned)}.cms-btn-primary.cms-btn-ghost:hover{background:var(--cms-field);color:var(--ui-text-highlighted)}.cms-btn-primary.cms-btn-ghost:active{background:var(--cms-elevated)}.cms-btn-neutral.cms-btn-solid{background:var(--cms-line-strong);color:var(--ui-text-highlighted)}.cms-btn-neutral.cms-btn-solid:hover{background:color-mix(in oklab,var(--cms-line-strong) 80%,var(--cms-text))}.cms-btn-neutral.cms-btn-solid:active{background:color-mix(in oklab,var(--cms-line-strong) 90%,#000)}.cms-btn-neutral.cms-btn-soft{background:var(--cms-field);border-color:var(--cms-line);color:var(--ui-text-highlighted)}.cms-btn-neutral.cms-btn-soft:hover{background:var(--cms-elevated);border-color:var(--cms-line-strong)}.cms-btn-neutral.cms-btn-soft:active{background:var(--cms-field)}.cms-btn-neutral.cms-btn-ghost{background:transparent;color:var(--ui-text-toned)}.cms-btn-neutral.cms-btn-ghost:hover{background:var(--cms-field);color:var(--ui-text-highlighted)}.cms-btn-neutral.cms-btn-ghost:active{background:var(--cms-elevated)}.cms-btn-error.cms-btn-solid{background:var(--ui-error);color:var(--cms-canvas)}.cms-btn-error.cms-btn-solid:hover{background:color-mix(in oklab,var(--ui-error) 85%,#fff)}.cms-btn-error.cms-btn-solid:active{background:color-mix(in oklab,var(--ui-error) 88%,#000)}.cms-btn-error.cms-btn-soft{background:color-mix(in oklab,var(--ui-error) 16%,transparent);border-color:color-mix(in oklab,var(--ui-error) 28%,transparent);color:var(--ui-error)}.cms-btn-error.cms-btn-soft:hover{background:color-mix(in oklab,var(--ui-error) 24%,transparent);border-color:color-mix(in oklab,var(--ui-error) 40%,transparent)}.cms-btn-error.cms-btn-soft:active{background:color-mix(in oklab,var(--ui-error) 16%,transparent)}.cms-btn-error.cms-btn-ghost{background:transparent;color:var(--ui-error)}.cms-btn-error.cms-btn-ghost:hover{background:color-mix(in oklab,var(--ui-error) 14%,transparent)}.cms-btn-error.cms-btn-ghost:active{background:color-mix(in oklab,var(--ui-error) 22%,transparent)}.cms-btn-success.cms-btn-solid{background:var(--ui-success);color:var(--cms-canvas)}.cms-btn-success.cms-btn-solid:hover{background:color-mix(in oklab,var(--ui-success) 85%,#fff)}.cms-btn-success.cms-btn-solid:active{background:color-mix(in oklab,var(--ui-success) 88%,#000)}.cms-btn-success.cms-btn-soft{background:color-mix(in oklab,var(--ui-success) 16%,transparent);border-color:color-mix(in oklab,var(--ui-success) 28%,transparent);color:var(--ui-success)}.cms-btn-success.cms-btn-soft:hover{background:color-mix(in oklab,var(--ui-success) 24%,transparent);border-color:color-mix(in oklab,var(--ui-success) 40%,transparent)}.cms-btn-success.cms-btn-soft:active{background:color-mix(in oklab,var(--ui-success) 16%,transparent)}.cms-btn-success.cms-btn-ghost{background:transparent;color:var(--ui-success)}.cms-btn-success.cms-btn-ghost:hover{background:color-mix(in oklab,var(--ui-success) 14%,transparent)}.cms-btn-success.cms-btn-ghost:active{background:color-mix(in oklab,var(--ui-success) 22%,transparent)}.cms-form{display:flex;flex-direction:column;gap:1.25rem}.cms-tabs{align-items:center;border-bottom:1px solid var(--ui-border);display:flex;gap:.25rem;margin-bottom:.25rem;overflow-x:auto}.cms-tab{border-bottom:2px solid transparent;color:var(--ui-text-toned);cursor:pointer;font-size:.875rem;font-weight:500;margin-bottom:-1px;padding:.5rem .875rem;transition:color var(--cms-duration) var(--cms-ease),border-color var(--cms-duration) var(--cms-ease);white-space:nowrap}.cms-tab:hover{color:var(--ui-text-highlighted)}.cms-tab.is-active{border-bottom-color:var(--cms-fern);color:var(--cms-fern)}.cms-form-field{display:flex;flex-direction:column;gap:.375rem}.cms-form-label-row{align-items:center;display:flex;gap:.75rem;justify-content:space-between;min-height:1.75rem}.cms-form-label{color:var(--ui-text-toned);font-size:.875rem;font-weight:500}.cms-form-error,.cms-form-required{color:var(--ui-error)}.cms-form-error{font-size:.875rem}.cms-form-actions{align-items:center;display:flex;gap:.75rem;justify-content:flex-end;padding-top:.25rem}.cms-field{background:var(--cms-field);border:1px solid var(--cms-line);border-radius:var(--ui-radius);color:var(--ui-text-highlighted);line-height:1.25;transition:border-color var(--cms-duration) var(--cms-ease),box-shadow var(--cms-duration) var(--cms-ease);width:100%}textarea.cms-field{line-height:1.5;min-height:0;padding-bottom:.625rem;padding-top:.625rem;resize:vertical}.cms-field::-moz-placeholder{color:var(--ui-text-dimmed)}.cms-field::placeholder{color:var(--ui-text-dimmed)}.cms-field:hover:not(:focus,:focus-within){border-color:var(--cms-line-strong)}.cms-field:focus,.cms-field:focus-within{border-color:var(--cms-fern);box-shadow:0 0 0 3px color-mix(in oklab,var(--cms-fern) 25%,transparent);outline:none}.cms-field.is-error{border-color:var(--ui-error)}.cms-field.is-error:focus,.cms-field.is-error:focus-within{border-color:var(--ui-error);box-shadow:0 0 0 3px color-mix(in oklab,var(--ui-error) 25%,transparent)}.cms-field-sm{font-size:.875rem;min-height:2rem;padding:.25rem .625rem}.cms-field-md{font-size:.875rem;min-height:2.25rem;padding:.25rem .75rem}.cms-field-lg{font-size:.9375rem;min-height:2.625rem;padding:.375rem .875rem}.cms-select-chevron{align-items:center;cursor:pointer;display:inline-flex;transition:transform var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease)}.cms-select-chevron:hover{color:var(--ui-text-muted)}.cms-select-chevron.is-open{transform:rotate(180deg)}.cms-locale-switch{align-items:center;background:var(--cms-field);border:1px solid var(--cms-line);border-radius:var(--cms-radius-md);display:inline-flex;gap:2px;padding:2px}.cms-locale-option{border-radius:var(--cms-radius-sm);color:var(--ui-text-muted);cursor:pointer;font-family:var(--cms-font-mono);font-size:.6875rem;letter-spacing:.08em;line-height:1.4;padding:.125rem .5rem;text-transform:uppercase;transition:background-color var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease)}.cms-locale-option:hover:not(.is-active){color:var(--ui-text-highlighted)}.cms-locale-option.is-empty{color:var(--ui-text-dimmed)}.cms-locale-option.is-active{background:var(--cms-elevated);color:var(--cms-fern)}.cms-input-wrap{align-items:center;display:flex;position:relative;width:100%}.cms-input-wrap .cms-field{width:100%}.cms-input-lead,.cms-input-trail{align-items:center;color:var(--ui-text-dimmed);display:flex;position:absolute}.cms-input-lead{left:.625rem}.cms-input-trail{right:.375rem}.cms-input-wrap.has-lead .cms-field{padding-left:2rem}.cms-switch{align-items:center;background:var(--cms-line-strong);border-radius:9999px;display:inline-flex;flex-shrink:0;height:1.25rem;position:relative;transition:background-color var(--cms-duration) var(--cms-ease);width:2.25rem}.cms-switch.is-on{background:var(--cms-fern)}.cms-switch:after{background:var(--cms-text);border-radius:9999px;content:"";height:.875rem;left:.1875rem;position:absolute;transition:transform var(--cms-duration) var(--cms-ease);width:.875rem}.cms-switch.is-on:after{background:var(--cms-canvas);transform:translateX(1rem)}.cms-overlay{align-items:center;background:var(--cms-scrim);display:flex;inset:0;justify-content:center;overflow-y:auto;padding:2rem 1rem;position:fixed;z-index:60}.cms-modal{background:var(--cms-surface);border:1px solid var(--cms-line);border-radius:var(--cms-radius-lg);box-shadow:var(--cms-shadow-overlay);display:flex;flex-direction:column;max-height:calc(100dvh - 4rem);max-width:32rem;width:100%}.cms-modal.is-sm{max-width:28rem}.cms-modal.is-lg{max-width:48rem}.cms-modal-header{padding:1.25rem 1.5rem 0}.cms-modal-body{flex:1;min-height:0;overflow-y:auto;padding:1.25rem 1.5rem 1.5rem}.cms-modal-enter-active,.cms-modal-leave-active{transition:opacity var(--cms-duration-lg) var(--cms-ease)}.cms-modal-enter-active .cms-modal,.cms-modal-leave-active .cms-modal{transition:transform var(--cms-duration-lg) var(--cms-ease),opacity var(--cms-duration-lg) var(--cms-ease);will-change:transform}.cms-modal-enter-from,.cms-modal-leave-to{opacity:0}.cms-modal-enter-from .cms-modal,.cms-modal-leave-to .cms-modal{opacity:0;transform:translateY(8px)}.cms-overlay-drawer{align-items:stretch;justify-content:flex-end;overflow:hidden;padding:0;z-index:55}.cms-drawer{background:var(--cms-surface);border-left:1px solid var(--cms-line);border-radius:var(--cms-radius-lg) 0 0 var(--cms-radius-lg);box-shadow:var(--cms-shadow-overlay);display:flex;flex-direction:column;height:100%;max-width:46rem;width:100%}.cms-drawer-header{align-items:center;border-bottom:1px solid var(--cms-line);display:flex;gap:1rem;justify-content:space-between;padding:1.25rem 1.75rem}.cms-drawer-body{flex:1;min-height:0;overflow-y:auto;padding:1.75rem}.cms-drawer-footer{align-items:center;border-top:1px solid var(--cms-line);display:flex;gap:1rem;justify-content:space-between;padding:1.25rem 1.75rem}.cms-drawer-enter-active,.cms-drawer-leave-active{transition:opacity var(--cms-duration-lg) var(--cms-ease)}.cms-drawer-enter-active .cms-drawer,.cms-drawer-leave-active .cms-drawer{transition:transform var(--cms-duration-lg) var(--cms-ease);will-change:transform}.cms-drawer-enter-from,.cms-drawer-leave-to{opacity:0}.cms-drawer-enter-from .cms-drawer,.cms-drawer-leave-to .cms-drawer{transform:translateX(100%)}@media (prefers-reduced-motion:reduce){.cms-drawer-enter-active,.cms-drawer-enter-active .cms-drawer,.cms-drawer-leave-active,.cms-drawer-leave-active .cms-drawer,.cms-modal-enter-active,.cms-modal-enter-active .cms-modal,.cms-modal-leave-active,.cms-modal-leave-active .cms-modal{transition-duration:.01ms}}.cms-menu{background:var(--cms-elevated);border:1px solid var(--cms-line);border-radius:var(--cms-radius-lg);box-shadow:var(--cms-shadow-overlay);margin-top:.375rem;min-width:12rem;padding:.25rem;position:absolute;z-index:50}.cms-menu.align-end{right:0}.cms-menu-item{align-items:center;border-radius:var(--cms-radius-sm);color:var(--ui-text);cursor:pointer;display:flex;font-size:.875rem;gap:.5rem;padding:.4rem .6rem;text-align:left;transition:background-color var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease);width:100%}.cms-menu-item:hover{background:color-mix(in oklab,var(--cms-text) 8%,transparent);color:var(--ui-text-highlighted)}.cms-menu-check{color:var(--cms-fern);margin-left:auto}.cms-popover{background:var(--cms-elevated);border:1px solid var(--cms-line);border-radius:var(--cms-radius-lg);box-shadow:var(--cms-shadow-overlay);margin-top:.375rem;position:absolute;z-index:50}.cms-selectmenu-input{background:transparent;color:var(--ui-text-highlighted);flex:1;min-width:4rem;outline:none}.cms-selectmenu-affix{color:var(--ui-text-dimmed);flex-shrink:0}.cms-selectmenu-panel{background:var(--cms-elevated);border:1px solid var(--cms-line);border-radius:var(--cms-radius-lg);box-shadow:var(--cms-shadow-overlay);left:0;margin-top:.375rem;max-height:15rem;overflow-y:auto;padding:.25rem;position:absolute;right:0;z-index:50}.cms-tag{align-items:center;background:var(--cms-elevated);border:1px solid var(--cms-line);border-radius:9999px;color:var(--ui-text-highlighted);display:inline-flex;font-size:.8125rem;gap:.25rem;padding:.125rem .5rem}.cms-media-gallery{display:flex;flex-direction:column;gap:1.25rem}.cms-media-source{color:var(--ui-text-muted);font-size:.8125rem;margin-top:-.75rem}.cms-media-filters{align-items:center;display:flex;flex-wrap:wrap;gap:.25rem}.cms-media-filters-actions{align-items:center;display:flex;flex-shrink:0;gap:.5rem;margin-left:auto}.cms-media-filter-separator{background:var(--cms-line);flex:none;height:1rem;margin-inline:.25rem;width:1px}.cms-pill-count{color:var(--ui-text-dimmed);font-family:var(--cms-font-mono);font-size:.6875rem;margin-left:.25rem}.cms-blocks-grid{display:grid;gap:.75rem;grid-template-columns:repeat(2,minmax(0,1fr))}@media (min-width:640px){.cms-blocks-grid{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width:1024px){.cms-blocks-grid{grid-template-columns:repeat(4,minmax(0,1fr))}}.cms-block-tile{overflow:hidden;transition:opacity var(--cms-duration) var(--cms-ease)}.cms-block-tile.is-muted{opacity:.45}.cms-block-preview{align-items:center;aspect-ratio:4/3;background:var(--cms-field);color:var(--ui-text-dimmed);display:flex;justify-content:center;position:relative;width:100%}.cms-block-preview>img,.cms-block-preview>video{height:100%;inset:0;-o-object-fit:cover;object-fit:cover;position:absolute;width:100%}.cms-block-summary{color:var(--ui-text-muted);font-size:.75rem;line-height:1.5;padding:.75rem;text-align:left}.cms-block-hidden{color:var(--cms-text);filter:drop-shadow(0 1px 2px rgb(0 0 0/.6));left:.5rem;position:absolute;top:.5rem}.cms-block-tile.is-dragging{opacity:.4}.cms-block-tile.is-drop-target{outline:2px solid var(--cms-fern);outline-offset:2px}.cms-block-bar{gap:.25rem;padding:.375rem}.cms-block-bar,.cms-block-grip{align-items:center;display:flex}.cms-block-grip{color:var(--ui-text-dimmed);cursor:grab;justify-content:center;padding:.25rem;transition:color var(--cms-duration) var(--cms-ease)}.cms-block-grip:hover{color:var(--ui-text-muted)}.cms-block-tile.is-dragging .cms-block-grip{cursor:grabbing}.cms-block-bar-name{color:var(--ui-text-muted);flex:1;font-size:.75rem;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cms-block-bar-actions{align-items:center;display:flex;gap:.125rem}.cms-block-add{align-items:center;border:1px dashed var(--cms-line-strong);border-radius:var(--cms-radius-md);color:var(--ui-text-muted);display:flex;flex-direction:column;font-size:.8125rem;font-weight:500;gap:.375rem;justify-content:center;min-height:6rem;transition:border-color var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease)}.cms-block-add:hover{border-color:var(--cms-fern);color:var(--ui-text-highlighted)}.cms-media-grid{display:grid;gap:1rem;grid-template-columns:repeat(2,minmax(0,1fr))}@media (min-width:640px){.cms-media-grid{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width:1024px){.cms-media-grid:not(.is-compact){grid-template-columns:repeat(4,minmax(0,1fr))}}.cms-media-tile{overflow:hidden;text-align:left;transition:border-color var(--cms-duration) var(--cms-ease)}.cms-media-tile.is-selectable{cursor:pointer}.cms-media-tile.is-selectable:hover{border-color:var(--cms-line-strong)}.cms-media-preview{align-items:center;aspect-ratio:1;background:var(--cms-field);color:var(--ui-text-dimmed);display:flex;justify-content:center;position:relative}.cms-media-preview>img,.cms-media-preview>video{height:100%;inset:0;-o-object-fit:cover;object-fit:cover;position:absolute;width:100%}.cms-media-actions{display:flex;gap:.25rem;opacity:0;position:absolute;right:.5rem;top:.5rem;transition:opacity var(--cms-duration) var(--cms-ease)}.cms-media-actions:focus-within,.cms-media-tile:hover .cms-media-actions{opacity:1}.cms-media-info{background:linear-gradient(0deg,rgba(0,0,0,.75),transparent);bottom:0;color:var(--cms-text);font-family:var(--cms-font-mono);font-size:.75rem;left:0;letter-spacing:.08em;line-height:1.4;opacity:0;padding:1.5rem .75rem .5rem;pointer-events:none;position:absolute;right:0;text-transform:uppercase;transition:opacity var(--cms-duration) var(--cms-ease)}.cms-media-tile:focus-visible .cms-media-info,.cms-media-tile:hover .cms-media-info{opacity:1}.cms-media-meta{display:flex;flex-direction:column;gap:.25rem;padding:.625rem .875rem}.cms-media-name{color:var(--ui-text-highlighted);font-size:.875rem;font-weight:500}.cms-media-folder,.cms-media-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cms-media-folder{align-items:center;color:var(--ui-text-dimmed);display:inline-flex;font-size:.75rem;gap:.25rem}.cms-media-single{max-width:15rem;width:100%}.cms-block-tile.is-missing{border-color:color-mix(in oklab,var(--ui-warning) 55%,transparent)}.cms-media-missing{align-items:center;font-size:.75rem;gap:.375rem}.cms-media-missing,.cms-media-missing-icon{color:var(--ui-warning);display:flex}.cms-media-thumb{background:var(--cms-field);border-radius:var(--cms-radius-sm);height:2.25rem;-o-object-fit:cover;object-fit:cover;width:2.25rem}.cms-media-thumb.is-fallback{align-items:center;color:var(--ui-text-dimmed);display:flex;justify-content:center}.cms-media-thumb-empty{color:var(--ui-text-dimmed)}.cms-table-shell{display:flex;flex:1;flex-direction:column;min-height:0;overflow:hidden}.cms-table-scroll{flex:1;min-height:0;overflow:auto}.cms-table{border-collapse:collapse;min-width:-moz-max-content;min-width:max-content;text-align:left;width:100%}.cms-table td,.cms-table th{white-space:nowrap}.cms-table thead{background:var(--cms-surface);position:sticky;top:0;z-index:10}.cms-table th{background:var(--cms-surface);box-shadow:inset 0 -1px 0 var(--cms-line);color:var(--ui-text-muted);font-family:var(--cms-font-mono);font-size:.6875rem;font-weight:400;letter-spacing:.08em;line-height:1.4;padding:.75rem 1.25rem;text-transform:uppercase;transition:background-color var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease)}.cms-table td{color:var(--ui-text);font-size:.875rem;padding:.875rem 1.25rem}.cms-table tbody tr{border-bottom:1px solid var(--cms-line);transition:background-color var(--cms-duration) var(--cms-ease)}.cms-table tbody tr:last-child{border-bottom:0}.cms-table tbody tr:hover{background:var(--cms-field)}.cms-table-cell-actions{display:flex;justify-content:flex-end}.cms-table th[draggable=true]{cursor:grab;-webkit-user-select:none;-moz-user-select:none;user-select:none}.cms-table th[draggable=true]:active{cursor:grabbing}.cms-table th.is-dragging{opacity:.4}.cms-table th.is-drop-target{background:var(--cms-field);box-shadow:inset 0 -1px 0 var(--cms-line),inset 0 0 0 2px color-mix(in oklab,var(--cms-fern) 55%,transparent)}.cms-th-label{align-items:center;display:inline-flex;gap:.35rem}.cms-table th.is-sortable{cursor:pointer}.cms-table th.is-sortable:hover,.cms-table th.is-sorted{color:var(--ui-text-highlighted)}.cms-table th.is-sorted{background:var(--cms-field)}.cms-th-sort{flex:none;opacity:0;transition:opacity var(--cms-duration) var(--cms-ease)}.cms-table th.is-sortable:hover .cms-th-sort,.cms-table th.is-sorted .cms-th-sort{opacity:1}.cms-alert{border:1px solid transparent;border-radius:var(--cms-radius-md);display:flex;flex-direction:column;font-size:.875rem;gap:.125rem;padding:.75rem 1rem}.cms-alert-title{font-weight:550}.cms-alert-error{background:color-mix(in oklab,var(--ui-error) 14%,transparent);border-color:color-mix(in oklab,var(--ui-error) 26%,transparent);color:var(--ui-error)}.cms-alert-success{background:color-mix(in oklab,var(--ui-success) 14%,transparent);border-color:color-mix(in oklab,var(--ui-success) 26%,transparent);color:var(--ui-success)}.cms-alert-warning{background:color-mix(in oklab,var(--ui-warning) 14%,transparent);border-color:color-mix(in oklab,var(--ui-warning) 26%,transparent);color:var(--ui-warning)}.cms-alert-neutral{background:var(--cms-field);border-color:var(--cms-line);color:var(--ui-text-toned)}.cms-alert-neutral .cms-alert-title{color:var(--ui-text-highlighted)}.cms-toaster{bottom:1rem;display:flex;flex-direction:column;gap:.5rem;max-width:calc(100vw - 2rem);position:fixed;right:1rem;width:20rem;z-index:80}.cms-toast{background:var(--cms-elevated);border:1px solid var(--cms-line);border-radius:var(--cms-radius-lg);box-shadow:var(--cms-shadow-overlay);padding:.75rem .875rem}.cms-toast-row{align-items:flex-start;display:flex;gap:.625rem}.cms-toast-icon{color:var(--ui-text-muted);flex-shrink:0;margin-top:.0625rem}.cms-toast.is-success .cms-toast-icon{color:var(--ui-success)}.cms-toast.is-error .cms-toast-icon{color:var(--ui-error)}.cms-toast-title{color:var(--ui-text-highlighted);font-size:.875rem;font-weight:500}.cms-toast-description{color:var(--ui-text-muted);font-size:.875rem;margin-top:.125rem}.cms-toast-dismiss{color:var(--ui-text-dimmed);flex-shrink:0;transition:color var(--cms-duration) var(--cms-ease)}.cms-toast-dismiss:hover{color:var(--ui-text-highlighted)}.cms-pagination{align-items:center;display:flex;gap:.25rem;justify-content:center}.cms-page-btn{border-radius:var(--cms-radius-sm);color:var(--ui-text-toned);font-size:.875rem;height:2rem;min-width:2rem;padding:0 .5rem;transition:background-color var(--cms-duration) var(--cms-ease),color var(--cms-duration) var(--cms-ease)}.cms-page-btn:hover:not(:disabled){background:var(--cms-field);color:var(--ui-text-highlighted)}.cms-page-btn.is-active{background:var(--cms-field);color:var(--cms-fern);font-weight:600}.cms-page-btn:disabled{opacity:.4}
|
package/dist/runtime/seed.js
CHANGED
|
@@ -3,6 +3,7 @@ import { dirname, isAbsolute, resolve } from "node:path";
|
|
|
3
3
|
import { migrationsDirFor } from "./shared/migrations-dir.js";
|
|
4
4
|
import { resolvePageRoutes, routePathsFromDir } from "./shared/page-routes.js";
|
|
5
5
|
import { customId, ID_LENGTH } from "./server/utils/custom-id.js";
|
|
6
|
+
import { applyLibsqlMigrations } from "./server/utils/libsql-migrations.js";
|
|
6
7
|
export { customId, ID_LENGTH };
|
|
7
8
|
export function resolveCmsPages(config, options = {}) {
|
|
8
9
|
const root = options.root ?? process.cwd();
|
|
@@ -27,7 +28,7 @@ export async function createCmsSeeder(opts = {}) {
|
|
|
27
28
|
const resolvedDbPath = isAbsolute(dbPath) ? dbPath : resolve(root, dbPath);
|
|
28
29
|
const migrationsFolder = opts.migrationsDir ?? migrationsDirFor(root, driver);
|
|
29
30
|
if (driver === "libsql") {
|
|
30
|
-
const {
|
|
31
|
+
const { readMigrationFiles } = await import("drizzle-orm/migrator");
|
|
31
32
|
const dbUrl = url || `file:${resolvedDbPath}`;
|
|
32
33
|
let db2;
|
|
33
34
|
if (dbUrl.startsWith("file:")) {
|
|
@@ -40,7 +41,13 @@ export async function createCmsSeeder(opts = {}) {
|
|
|
40
41
|
const { drizzle: drizzle2 } = await import("drizzle-orm/libsql/web");
|
|
41
42
|
db2 = drizzle2(createClient({ url: dbUrl, authToken: authToken || void 0 }));
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
+
const client = db2.$client;
|
|
45
|
+
return {
|
|
46
|
+
db: db2,
|
|
47
|
+
migrate: async () => {
|
|
48
|
+
await applyLibsqlMigrations(client, readMigrationFiles({ migrationsFolder }));
|
|
49
|
+
}
|
|
50
|
+
};
|
|
44
51
|
}
|
|
45
52
|
if (driver === "postgres") {
|
|
46
53
|
if (!url) throw new Error("[nuxt-cms] seed: Postgres needs a url (NUXT_CMS_DATABASE_URL)");
|
|
@@ -4,6 +4,7 @@ import { z } from "zod";
|
|
|
4
4
|
import { useDb } from "#cms-db";
|
|
5
5
|
import { pageRoutes } from "../../shared/index.js";
|
|
6
6
|
import { decodeRows, getRegistryEntry, idColumn, tableColumns } from "../utils/registry.js";
|
|
7
|
+
import { readPages } from "../utils/page-storage.js";
|
|
7
8
|
import { attachManyToMany, relationTitles } from "../utils/relations.js";
|
|
8
9
|
import { requireAdmin } from "../utils/require-admin.js";
|
|
9
10
|
const querySchema = z.object({
|
|
@@ -31,7 +32,7 @@ export default defineEventHandler(async (event) => {
|
|
|
31
32
|
querySchema.parse
|
|
32
33
|
);
|
|
33
34
|
if (entry.kind === "page") {
|
|
34
|
-
const rows = await
|
|
35
|
+
const rows = await readPages(name, entry, table);
|
|
35
36
|
decodeRows(entry, rows);
|
|
36
37
|
const saved = new Map(rows.map((row) => [row.id, row]));
|
|
37
38
|
const items2 = pageRoutes(entry).map((route) => {
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
parseId,
|
|
9
9
|
requirePageRoute
|
|
10
10
|
} from "../utils/registry.js";
|
|
11
|
+
import { readPages } from "../utils/page-storage.js";
|
|
11
12
|
import { attachManyToMany } from "../utils/relations.js";
|
|
12
13
|
import { requireAdmin } from "../utils/require-admin.js";
|
|
13
14
|
export default defineEventHandler(async (event) => {
|
|
@@ -17,13 +18,14 @@ export default defineEventHandler(async (event) => {
|
|
|
17
18
|
throw createError({ statusCode: 404, statusMessage: "Single objects have no items" });
|
|
18
19
|
}
|
|
19
20
|
const id = parseId(event);
|
|
20
|
-
const db = useDb();
|
|
21
|
-
const rows = await db.select().from(table).where(eq(idColumn(table), id)).limit(1);
|
|
22
21
|
if (entry.kind === "page") {
|
|
23
22
|
const route = requirePageRoute(entry, id);
|
|
24
|
-
|
|
25
|
-
return
|
|
23
|
+
const [page] = await readPages(name, entry, table, { id });
|
|
24
|
+
if (!page) return { id: route.key, path: route.path };
|
|
25
|
+
return decodeRows(entry, [page])[0];
|
|
26
26
|
}
|
|
27
|
+
const db = useDb();
|
|
28
|
+
const rows = await db.select().from(table).where(eq(idColumn(table), id)).limit(1);
|
|
27
29
|
if (!rows[0]) throw createError({ statusCode: 404, statusMessage: "Row not found" });
|
|
28
30
|
const [attached] = await attachManyToMany(db, name, entry, [rows[0]]);
|
|
29
31
|
return decodeRows(entry, [attached])[0];
|
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
requirePageRoute,
|
|
12
12
|
withUpdatedAt
|
|
13
13
|
} from "../utils/registry.js";
|
|
14
|
+
import { pageFields } from "../../shared/index.js";
|
|
15
|
+
import { assertMediaExists } from "../utils/media-check.js";
|
|
16
|
+
import { writePage } from "../utils/page-storage.js";
|
|
14
17
|
import {
|
|
15
18
|
assertRelationTargets,
|
|
16
19
|
attachManyToMany,
|
|
@@ -32,13 +35,11 @@ export default defineEventHandler(async (event) => {
|
|
|
32
35
|
if (entry.kind === "page") {
|
|
33
36
|
const route = requirePageRoute(entry, id);
|
|
34
37
|
const body2 = await readValidatedBody(event, buildValidator(entry, route.path).parse);
|
|
38
|
+
await assertMediaExists(pageFields(entry, route.path), body2);
|
|
35
39
|
const values2 = encodeColumnValues(entry, body2);
|
|
36
40
|
const set2 = withUpdatedAt(table, values2);
|
|
37
41
|
return mapConstraintErrors(
|
|
38
|
-
() =>
|
|
39
|
-
const [row] = await db.insert(table).values({ id: route.key, path: route.path, ...set2 }).onConflictDoUpdate({ target: idColumn(table), set: set2 }).returning();
|
|
40
|
-
return decodeRows(entry, [row])[0];
|
|
41
|
-
})
|
|
42
|
+
async () => decodeRows(entry, [await writePage(name, entry, table, route, set2)])[0]
|
|
42
43
|
);
|
|
43
44
|
}
|
|
44
45
|
const body = await readValidatedBody(event, buildValidator(entry).parse);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useDb } from "../utils/db-libsql.js";
|
|
2
|
-
import {
|
|
2
|
+
import { runLibsqlMigrations } from "../utils/migrate.js";
|
|
3
3
|
export default async () => {
|
|
4
|
-
await
|
|
4
|
+
await runLibsqlMigrations(useDb());
|
|
5
5
|
};
|
|
@@ -3,6 +3,7 @@ import { defineEventHandler } from "h3";
|
|
|
3
3
|
import { useRuntimeConfig } from "#imports";
|
|
4
4
|
import { buildCmsSchema, createGraphqlContext } from "../utils/graphql.js";
|
|
5
5
|
import { createBreadthRule, createDepthRule, createIntrospectionRule } from "../utils/graphql-depth.js";
|
|
6
|
+
import { CMS_GRAPHQL_BATCH_LIMIT } from "../../shared/index.js";
|
|
6
7
|
function buildYoga(options) {
|
|
7
8
|
return createYoga({
|
|
8
9
|
schema: buildCmsSchema(),
|
|
@@ -10,6 +11,7 @@ function buildYoga(options) {
|
|
|
10
11
|
landingPage: false,
|
|
11
12
|
graphiql: options.graphiql,
|
|
12
13
|
cors: false,
|
|
14
|
+
batching: { limit: CMS_GRAPHQL_BATCH_LIMIT },
|
|
13
15
|
context: () => createGraphqlContext(),
|
|
14
16
|
plugins: [
|
|
15
17
|
{
|
|
@@ -7,5 +7,7 @@ export declare function useDb(): import("drizzle-orm/d1").DrizzleD1Database<Reco
|
|
|
7
7
|
};
|
|
8
8
|
type Db = ReturnType<typeof useDb>;
|
|
9
9
|
export type CmsDb = Db;
|
|
10
|
+
export declare const cmsDialect: 'sqlite' | 'postgres';
|
|
10
11
|
export declare function withTransaction<T>(fn: (db: CmsDb) => Promise<T>): Promise<T>;
|
|
12
|
+
export declare function runBatch(build: (db: CmsDb) => PromiseLike<unknown>[]): Promise<unknown[]>;
|
|
11
13
|
export {};
|