@xleddyl/nuxt-cms 0.1.27 → 0.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/dist/module.d.mts +37 -30
- package/dist/module.json +1 -1
- package/dist/module.mjs +123 -35
- package/dist/runtime/app/components/cms/Alert.d.vue.ts +1 -2
- package/dist/runtime/app/components/cms/Alert.vue +2 -3
- package/dist/runtime/app/components/cms/Alert.vue.d.ts +1 -2
- package/dist/runtime/app/components/cms/BlocksField.vue +2 -2
- package/dist/runtime/app/components/cms/Button.d.vue.ts +1 -1
- package/dist/runtime/app/components/cms/Button.vue.d.ts +1 -1
- package/dist/runtime/app/components/cms/ConfirmModal.vue +1 -1
- package/dist/runtime/app/components/cms/EntryDrawer.vue +1 -1
- package/dist/runtime/app/components/cms/MediaField.vue +2 -2
- package/dist/runtime/app/components/cms/MediaGallery.vue +3 -3
- package/dist/runtime/app/components/cms/MediaUpload.vue +9 -4
- package/dist/runtime/app/components/cms/Modal.d.vue.ts +4 -4
- package/dist/runtime/app/components/cms/Modal.vue +4 -4
- package/dist/runtime/app/components/cms/Modal.vue.d.ts +4 -4
- package/dist/runtime/app/components/cms/PageHeader.d.vue.ts +0 -1
- package/dist/runtime/app/components/cms/PageHeader.vue +0 -4
- package/dist/runtime/app/components/cms/PageHeader.vue.d.ts +0 -1
- package/dist/runtime/app/components/cms/RichTextField.vue +1 -1
- package/dist/runtime/app/components/cms/Toaster.vue +6 -0
- package/dist/runtime/app/components/cms/TranslatableField.vue +19 -8
- package/dist/runtime/app/composables/cms-runtime.d.ts +1 -0
- package/dist/runtime/app/layouts/cms-admin.vue +2 -2
- package/dist/runtime/app/pages/admin-collection.vue +12 -7
- package/dist/runtime/app/pages/admin-entry.vue +2 -2
- package/dist/runtime/app/pages/admin-login.vue +5 -5
- package/dist/runtime/assets/main.css +1 -1
- package/dist/runtime/server/api/collection.get.d.ts +1 -3
- package/dist/runtime/server/api/collection.get.js +3 -2
- package/dist/runtime/server/api/collection.post.js +3 -3
- package/dist/runtime/server/api/collection.put.js +13 -3
- package/dist/runtime/server/api/item.get.js +2 -2
- package/dist/runtime/server/api/item.put.js +7 -2
- package/dist/runtime/server/api/media-presign.post.js +3 -9
- package/dist/runtime/server/utils/graphql.js +13 -1
- package/dist/runtime/server/utils/media.d.ts +2 -0
- package/dist/runtime/server/utils/media.js +9 -1
- package/dist/runtime/server/utils/registry.d.ts +2 -0
- package/dist/runtime/server/utils/registry.js +7 -0
- package/dist/runtime/shared/index.d.ts +11 -1
- package/dist/runtime/shared/index.js +70 -1
- package/dist/runtime/shared/validation.js +1 -1
- package/dist/types.d.mts +1 -1
- package/package.json +4 -2
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
}"
|
|
13
13
|
>
|
|
14
14
|
<div class="cms-toast-row">
|
|
15
|
+
<CmsIcon :name="iconFor(toast.color)" class="cms-toast-icon size-4" />
|
|
15
16
|
<div class="min-w-0 flex-1">
|
|
16
17
|
<p class="cms-toast-title">
|
|
17
18
|
{{ toast.title }}
|
|
@@ -39,4 +40,9 @@
|
|
|
39
40
|
import { useCmsToast, useCmsToastState } from "../../composables/cms-toast";
|
|
40
41
|
const toasts = useCmsToastState();
|
|
41
42
|
const { remove } = useCmsToast();
|
|
43
|
+
function iconFor(color) {
|
|
44
|
+
if (color === "success") return "check-circle";
|
|
45
|
+
if (color === "error") return "exclamation-circle";
|
|
46
|
+
return "information-circle";
|
|
47
|
+
}
|
|
42
48
|
</script>
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<CmsMediaField
|
|
3
|
+
v-if="props.field.type === 'media'"
|
|
4
|
+
v-model="currentMedia"
|
|
5
|
+
:media-type="props.field.mediaType"
|
|
6
|
+
:accept="props.field.accept"
|
|
7
|
+
/>
|
|
8
|
+
<CmsRichTextField v-else-if="props.field.type === 'richtext'" v-model="current" />
|
|
3
9
|
<CmsTextarea v-else-if="props.field.textarea" v-model="current" :rows="8" />
|
|
4
10
|
<CmsInput v-else v-model="current" />
|
|
5
11
|
</template>
|
|
@@ -14,14 +20,19 @@ const props = defineProps({
|
|
|
14
20
|
const model = defineModel({ type: [Object, null], ...{ required: true } });
|
|
15
21
|
const { i18n } = useCmsRuntime();
|
|
16
22
|
const active = computed(() => props.locale ?? i18n.defaultLocale);
|
|
23
|
+
function write(value) {
|
|
24
|
+
const next = Object.fromEntries(
|
|
25
|
+
Object.entries(model.value ?? {}).filter(([locale]) => locale !== active.value)
|
|
26
|
+
);
|
|
27
|
+
if (value != null && value !== "") next[active.value] = value;
|
|
28
|
+
model.value = Object.keys(next).length ? next : null;
|
|
29
|
+
}
|
|
17
30
|
const current = computed({
|
|
18
31
|
get: () => model.value?.[active.value] ?? "",
|
|
19
|
-
set:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
model.value = Object.keys(next).length ? next : null;
|
|
25
|
-
}
|
|
32
|
+
set: write
|
|
33
|
+
});
|
|
34
|
+
const currentMedia = computed({
|
|
35
|
+
get: () => model.value?.[active.value] ?? null,
|
|
36
|
+
set: write
|
|
26
37
|
});
|
|
27
38
|
</script>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="cms-scope cms-canvas
|
|
2
|
+
<div class="cms-scope cms-canvas min-h-screen">
|
|
3
3
|
<div class="cms-shell">
|
|
4
4
|
<aside class="cms-sidebar">
|
|
5
5
|
<div class="cms-sidebar-brand">nuxt<span class="cms-accent">·</span>cms</div>
|
|
6
6
|
|
|
7
7
|
<nav class="cms-sidebar-nav">
|
|
8
8
|
<div v-for="group in groups" :key="group.title" class="cms-sidebar-group">
|
|
9
|
-
<div class="cms-
|
|
9
|
+
<div class="cms-label">
|
|
10
10
|
{{ group.title }}
|
|
11
11
|
</div>
|
|
12
12
|
<NuxtLink
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="cms-page" :class="{ 'is-fill': config.kind === 'collection' && !error }">
|
|
3
|
-
<CmsPageHeader :
|
|
3
|
+
<CmsPageHeader :title="config.label">
|
|
4
4
|
<CmsButton
|
|
5
5
|
v-if="config.kind === 'single'"
|
|
6
6
|
type="submit"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<CmsButton
|
|
15
15
|
label="Retry"
|
|
16
16
|
icon="arrow-path"
|
|
17
|
-
variant="
|
|
17
|
+
variant="soft"
|
|
18
18
|
:loading="status === 'pending'"
|
|
19
19
|
@click="reload"
|
|
20
20
|
/>
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
/>
|
|
42
42
|
<div class="cms-toolbar-actions">
|
|
43
43
|
<CmsDropdownMenu v-if="rows.length" :items="columnItems" :content="{ align: 'end' }">
|
|
44
|
-
<CmsButton label="Columns" icon="view-columns" variant="
|
|
44
|
+
<CmsButton label="Columns" icon="view-columns" variant="soft" />
|
|
45
45
|
</CmsDropdownMenu>
|
|
46
46
|
<CmsButton label="New entry" icon="plus" @click="openCreate" />
|
|
47
47
|
</div>
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
@reorder="reorderColumns"
|
|
58
58
|
>
|
|
59
59
|
<template v-for="key in mediaKeys" #[`${key}-cell`]="{ row }" :key="key">
|
|
60
|
-
<CmsMediaThumb :value="row.original[key]" />
|
|
60
|
+
<CmsMediaThumb :value="mediaThumbValue(key, row.original[key])" />
|
|
61
61
|
</template>
|
|
62
62
|
<template v-if="drafts" #status-cell="{ row }">
|
|
63
63
|
<CmsStatusBadge :published="row.original.status === 'published'" />
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
body="Entries you create will show up here."
|
|
92
92
|
fill
|
|
93
93
|
>
|
|
94
|
-
<CmsButton label="New entry" icon="plus" variant="
|
|
94
|
+
<CmsButton label="New entry" icon="plus" variant="soft" @click="openCreate" />
|
|
95
95
|
</CmsEmptyState>
|
|
96
96
|
|
|
97
97
|
<CmsPagination v-model:page="page" :total="total" :items-per-page="PAGE_SIZE" />
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
</template>
|
|
110
110
|
|
|
111
111
|
<script setup>
|
|
112
|
-
import { isTranslatableField } from "#nuxt-cms";
|
|
112
|
+
import { isTranslatableField, isTranslatableMediaField, pickTranslatedMedia } from "#nuxt-cms";
|
|
113
113
|
import {
|
|
114
114
|
computed,
|
|
115
115
|
createError,
|
|
@@ -226,6 +226,12 @@ function localized(value) {
|
|
|
226
226
|
const record = value;
|
|
227
227
|
return record[contentI18n.defaultLocale] ?? Object.values(record)[0] ?? "";
|
|
228
228
|
}
|
|
229
|
+
function mediaThumbValue(key, value) {
|
|
230
|
+
const field = config?.fields[key];
|
|
231
|
+
if (!field || !isTranslatableMediaField(field)) return value ?? null;
|
|
232
|
+
const locale = contentI18n.defaultLocale;
|
|
233
|
+
return pickTranslatedMedia(value, locale, locale);
|
|
234
|
+
}
|
|
229
235
|
function relationLabel(field, key, id) {
|
|
230
236
|
const title = relations.value[key]?.[String(id)];
|
|
231
237
|
if (title == null || title === "") return `#${id}`;
|
|
@@ -335,7 +341,6 @@ const columnItems = computed(
|
|
|
335
341
|
}
|
|
336
342
|
}))
|
|
337
343
|
);
|
|
338
|
-
const kicker = config.kind === "single" ? "single document" : void 0;
|
|
339
344
|
const drawerOpen = ref(false);
|
|
340
345
|
const drawerEntryId = ref(null);
|
|
341
346
|
function onSelect(_event, row) {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="cms-page">
|
|
3
|
-
<CmsPageHeader :
|
|
3
|
+
<CmsPageHeader :title="isNew ? 'New entry' : 'Edit entry'">
|
|
4
4
|
<template v-if="drafts" #badge>
|
|
5
5
|
<CmsStatusBadge :published="published" />
|
|
6
6
|
</template>
|
|
7
7
|
<div class="cms-actions">
|
|
8
|
-
<CmsButton label="Back" icon="arrow-left" variant="
|
|
8
|
+
<CmsButton label="Back" icon="arrow-left" variant="soft" @click="goBack" />
|
|
9
9
|
<CmsButton type="submit" :form="FORM_ID" label="Save" :loading="saving" />
|
|
10
10
|
<CmsButton
|
|
11
11
|
v-if="drafts"
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="cms-scope cms-canvas cms-
|
|
3
|
-
<div class="cms-auth-panel">
|
|
4
|
-
<header class="cms-auth-header
|
|
2
|
+
<div class="cms-scope cms-canvas cms-auth">
|
|
3
|
+
<div class="cms-auth-panel cms-rise">
|
|
4
|
+
<header class="cms-auth-header">
|
|
5
5
|
<h1 class="cms-title cms-title-lg">Welcome back<span class="cms-accent">.</span></h1>
|
|
6
6
|
<p class="cms-subtitle">Sign in to manage your content.</p>
|
|
7
7
|
</header>
|
|
8
8
|
|
|
9
|
-
<div class="cms-card cms-auth-card
|
|
9
|
+
<div class="cms-card cms-auth-card">
|
|
10
10
|
<CmsForm :state="state" @submit="login">
|
|
11
|
-
<CmsAlert v-if="error" color="error"
|
|
11
|
+
<CmsAlert v-if="error" color="error" :title="error" />
|
|
12
12
|
<CmsFormField label="Email" name="email" required>
|
|
13
13
|
<CmsInput
|
|
14
14
|
v-model="state.email"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600&family=Hanken+Grotesk:ital,wght@0,300..800;1,300..800&family=Fragment+Mono:ital@0;1&display=swap");@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-paper:#f5f5f5;--cms-surface:#fff;--cms-field:#f0f0f0;--cms-ink:#171717;--cms-line:#e5e5e5;--cms-line-strong:#d4d4d4;--cms-fern:#171717;--cms-sage:#ebebeb;--cms-sage-ink:#171717;--cms-shadow-soft:none;color-scheme:light;--ui-text-dimmed:#a3a3a3;--ui-text-muted:#737373;--ui-text-toned:#525252;--ui-text:#404040;--ui-text-highlighted:var(--cms-ink);--ui-text-inverted:#fff;--ui-bg:var(--cms-surface);--ui-bg-muted:var(--cms-paper);--ui-bg-elevated:var(--cms-field);--ui-bg-accented:#e0e0e0;--ui-bg-inverted:var(--cms-ink);--ui-border:var(--cms-line);--ui-border-muted:var(--cms-line);--ui-border-accented:var(--cms-line-strong);--ui-border-inverted:var(--cms-ink);--ui-primary:var(--cms-ink);--ui-error:#dc2626;--ui-radius:0.4rem;color:var(--ui-text);font-family:Hanken Grotesk,ui-sans-serif,system-ui,sans-serif;margin:0;padding:0;-webkit-font-smoothing:antialiased}body:has(.cms-scope) ::-moz-selection{background:var(--cms-sage);color:var(--cms-sage-ink)}body:has(.cms-scope) ::selection{background:var(--cms-sage);color:var(--cms-sage-ink)}.cms-display,.cms-title{font-family:Poppins,ui-sans-serif,system-ui,sans-serif;letter-spacing:-.02em}.cms-title{color:var(--ui-text-highlighted);font-weight:500}.cms-title-sm{font-size:1.25rem;line-height:1.4}.cms-title-md{font-size:2rem;line-height:1}.cms-title-lg{font-size:2.25rem;line-height:1.2}.cms-subtitle{color:var(--ui-text-muted);font-size:.875rem}.cms-accent{color:var(--cms-fern)}.cms-kicker{color:var(--ui-text-dimmed);font-family:Fragment Mono,ui-monospace,monospace;font-size:.875rem;letter-spacing:.16em;line-height:1.4;text-transform:uppercase}body:has(.cms-scope) :is(button,a,[role=button],[role=menuitem],[role=option],.__clickable):not(:disabled,[aria-disabled=true]){@apply cursor-pointer transition-all duration-200 select-none}body:has(.cms-scope) :is(button,a,[role=button],[role=menuitem],[role=option],.__clickable):not(:disabled,[aria-disabled=true]):active{@apply transition-none;scale:.99}.cms-canvas{background-color:var(--cms-paper)}.cms-grain:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='a'%3E%3CfeTurbulence baseFrequency='.8' numOctaves='2' stitchTiles='stitch' type='fractalNoise'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23a)'/%3E%3C/svg%3E");content:"";inset:0;mix-blend-mode:multiply;opacity:.035;pointer-events:none;position:fixed;z-index:50}.cms-card{background:var(--cms-surface);border:1px solid var(--cms-line);border-radius:1rem;box-shadow:var(--cms-shadow-soft)}.cms-panel{padding:1.75rem}.cms-shell{display:flex;min-height:100vh}.cms-sidebar{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:Poppins,ui-sans-serif,system-ui,sans-serif;font-size:1.375rem;font-weight:600;letter-spacing:-.02em;padding-inline:.75rem}.cms-sidebar-nav{display:flex;flex:1;flex-direction:column;gap:1.5rem;overflow-y:auto}.cms-sidebar-group{display:flex;flex-direction:column;gap:.25rem}.cms-sidebar-group>.cms-kicker{margin-bottom:.5rem;padding-inline:.75rem}.cms-sidebar-footer{border-top:1px solid var(--cms-line);display:flex;flex-direction:column;gap:.75rem;padding:1.25rem .75rem 0}.cms-main{flex:1;height:100vh;min-width:0;overflow-y:auto;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-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:rgba(253,252,250,.5);border:1.5px dashed var(--cms-line-strong);border-radius:1rem;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:.75rem;color:var(--ui-text-toned);display:flex;font-size:.875rem;font-weight:500;gap:.625rem;padding:.5rem .75rem;transition:background-color .15s ease,color .15s ease}.cms-navlink:hover{background:rgba(38,33,25,.05);color:var(--ui-text-highlighted)}.cms-navlink.is-active{background:var(--cms-sage);color:var(--cms-sage-ink);font-weight:600}.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:9999px;display:inline-flex;font-family:Fragment Mono,ui-monospace,monospace;font-size:.6875rem;letter-spacing:.1em;padding:.1875rem .5rem;pointer-events:none;text-transform:uppercase;transition:background-color .15s ease,border-color .15s ease;white-space:nowrap}.cms-badge.is-published{background:var(--cms-sage);color:var(--cms-sage-ink)}.cms-badge.is-muted{background:var(--cms-field);border-color:var(--cms-line);color:var(--ui-text-muted)}.cms-badge.is-draft{background:transparent;border:1px dashed var(--cms-line-strong);color:var(--ui-text-muted)}.cms-dropzone{align-items:center;background:rgba(253,252,250,.5);border:1.5px dashed var(--cms-line-strong);border-radius:1rem;color:var(--ui-text-muted);cursor:pointer;display:flex;flex-direction:column;gap:.5rem;justify-content:center;padding:2rem 1.5rem;transition:border-color .15s ease,background-color .15s ease,color .15s ease;width:100%}.cms-dropzone.is-over,.cms-dropzone:hover{background:var(--cms-sage);border-color:var(--cms-fern);color:var(--cms-sage-ink)}.cms-dropzone.is-dense{border-radius:.75rem;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-dimmed);cursor:pointer;display:inline-flex;flex:none;font-family:Fragment Mono,ui-monospace,monospace;font-size:.875rem;gap:.35rem;letter-spacing:.12em;padding:.3rem .8rem;transition:background-color .15s ease,color .15s ease;white-space:nowrap}.cms-pill:hover{background:rgba(38,33,25,.05);color:var(--ui-text)}.cms-pill.is-active{background:var(--cms-sage);color:var(--cms-sage-ink)}.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 .15s 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;display:inline-flex;font-family:Fragment Mono,ui-monospace,monospace;font-size:.75rem;gap:.3rem;letter-spacing:.08em;opacity:.7;text-transform:uppercase}@keyframes cms-rise{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}.cms-rise{animation:cms-rise .55s cubic-bezier(.22,1,.36,1) both}.cms-rise.is-delayed{animation-delay:90ms}@media (prefers-reduced-motion:reduce){.cms-rise{animation:none}}.cms-richtext{border:1px solid var(--cms-line);border-radius:.75rem;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{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);color:var(--ui-text-muted);padding-left:.75rem}.cms-btn{align-items:center;border:1px solid transparent;border-radius:9999px;display:inline-flex;font-weight:500;gap:.375rem;justify-content:space-between;line-height:1;min-height:var(--cms-btn-size);transition:background-color .15s ease,border-color .15s ease,color .15s ease,opacity .15s ease;white-space:nowrap}.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 .75rem}.cms-btn-sm{--cms-btn-size:2rem;font-size:.875rem;padding:.25rem .875rem}.cms-btn-md{--cms-btn-size:2.25rem;font-size:.875rem;padding:.25rem 1.125rem}.cms-btn-lg{--cms-btn-size:2.625rem;font-size:.9375rem;padding:.375rem 1.375rem}.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) 88%,#000)}.cms-btn-primary.cms-btn-subtle{background:var(--cms-sage);color:var(--cms-sage-ink)}.cms-btn-primary.cms-btn-subtle:hover{background:#e0e0e0}.cms-btn-primary.cms-btn-soft{background:var(--cms-sage);color:var(--cms-sage-ink)}.cms-btn-neutral.cms-btn-solid{background:var(--ui-bg-inverted);color:var(--ui-text-inverted)}.cms-btn-neutral.cms-btn-subtle{background:var(--ui-bg-elevated);color:var(--ui-text-highlighted)}.cms-btn-neutral.cms-btn-subtle:hover{background:var(--ui-bg-accented)}.cms-btn-neutral.cms-btn-soft{background:var(--ui-bg-elevated);color:var(--ui-text-highlighted)}.cms-btn-neutral.cms-btn-ghost{color:var(--ui-text-toned)}.cms-btn-neutral.cms-btn-ghost:hover{background:rgba(38,33,25,.06);color:var(--ui-text-highlighted)}.cms-btn-error.cms-btn-solid{background:var(--ui-error);color:#fff}.cms-btn-error.cms-btn-solid:hover{background:color-mix(in oklab,var(--ui-error) 88%,#000)}.cms-btn-error.cms-btn-subtle{background:color-mix(in oklab,var(--ui-error) 12%,transparent);color:var(--ui-error)}.cms-btn-error.cms-btn-ghost{color:var(--ui-error)}.cms-btn-error.cms-btn-ghost:hover{background:color-mix(in oklab,var(--ui-error) 10%,transparent)}.cms-btn-success.cms-btn-solid{background:var(--cms-fern);color:var(--ui-text-inverted)}.cms-form{gap:1.25rem}.cms-form,.cms-form-field{display:flex;flex-direction:column}.cms-form-field{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(--ui-bg);border:1px solid var(--cms-line-strong);border-radius:var(--ui-radius);color:var(--ui-text-highlighted);line-height:1.25;transition:border-color .15s ease,box-shadow .15s 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:focus,.cms-field:focus-within{border-color:var(--ui-primary);box-shadow:0 0 0 3px color-mix(in oklab,var(--ui-primary) 18%,transparent);outline:none}.cms-field.is-error{border-color:var(--ui-error)}.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 .18s ease,color .15s 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-paper);border:1px solid var(--cms-line);border-radius:999px;display:inline-flex;gap:2px;padding:2px}.cms-locale-option{border-radius:999px;color:var(--ui-text-muted);cursor:pointer;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.6875rem;letter-spacing:.06em;line-height:1.4;padding:.125rem .5rem;text-transform:uppercase;transition:background-color .15s ease,color .15s 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(--ui-bg);box-shadow:0 1px 2px rgba(38,33,25,.1);color:var(--ui-text-highlighted)}.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 .15s ease;width:2.25rem}.cms-switch.is-on{background:var(--ui-primary)}.cms-switch:after{background:#fff;border-radius:9999px;content:"";height:.875rem;left:.1875rem;position:absolute;transition:transform .15s ease;width:.875rem}.cms-switch.is-on:after{transform:translateX(1rem)}.cms-overlay{align-items:center;backdrop-filter:blur(2px);background:rgba(38,33,25,.32);display:flex;inset:0;justify-content:center;overflow-y:auto;padding:2rem 1rem;position:fixed;z-index:60}.cms-modal{background:var(--ui-bg);border:1px solid var(--cms-line);border-radius:1rem;box-shadow:var(--cms-shadow-soft);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-overlay-drawer{align-items:stretch;justify-content:flex-end;overflow:hidden;padding:0;z-index:55}.cms-drawer{background:var(--ui-bg);border-left:1px solid var(--cms-line);box-shadow:-18px 0 48px rgba(38,33,25,.12);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 .3s ease}.cms-drawer-enter-active .cms-drawer,.cms-drawer-leave-active .cms-drawer{transition:transform .36s cubic-bezier(.32,.72,0,1);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{transition-duration:.01ms}}.cms-menu{background:var(--ui-bg);border:1px solid var(--cms-line);border-radius:.75rem;box-shadow:var(--cms-shadow-soft);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:.5rem;color:var(--ui-text);cursor:pointer;display:flex;font-size:.875rem;gap:.5rem;padding:.4rem .6rem;text-align:left;width:100%}.cms-menu-item:hover{background:var(--ui-bg-elevated)}.cms-menu-check{color:var(--ui-primary);margin-left:auto}.cms-popover{background:var(--ui-bg);border:1px solid var(--cms-line);border-radius:.75rem;box-shadow:var(--cms-shadow-soft);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(--ui-bg);border:1px solid var(--cms-line);border-radius:.75rem;box-shadow:var(--cms-shadow-soft);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(--ui-bg-elevated);border-radius:9999px;display:inline-flex;font-size:.875rem;gap:.25rem;padding:.125rem .5rem}.cms-media-gallery{display:flex;flex-direction:column;gap:1.25rem}.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-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}.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 .15s 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,.7),transparent);bottom:0;color:#fff;font-family:Fragment Mono,ui-monospace,monospace;font-size:.75rem;left:0;letter-spacing:.1em;line-height:1.4;opacity:0;padding:1.5rem .75rem .5rem;pointer-events:none;position:absolute;right:0;text-transform:uppercase;transition:opacity .15s 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{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-field{overflow:hidden}.cms-media-field-preview{align-items:center;background:repeating-conic-gradient(from 0deg,rgba(38,33,25,.04) 0 25%,transparent 0 50%) 50% /16px 16px;color:var(--ui-text-dimmed);display:flex;justify-content:center;max-height:16rem;min-height:8rem;padding:.75rem}.cms-media-field-preview>img,.cms-media-field-preview>video{max-height:14.5rem;max-width:100%;-o-object-fit:contain;object-fit:contain}.cms-media-field-bar{align-items:center;background:var(--ui-bg);border-top:1px solid var(--cms-line);display:flex;gap:.5rem;padding:.625rem .75rem}.cms-media-bar-icon{color:var(--ui-text-dimmed);flex-shrink:0}.cms-media-bar-name{flex:1;font-size:.875rem;font-weight:500;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cms-media-thumb{background:var(--cms-field);border-radius:.375rem;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{box-shadow:inset 0 -1px 0 var(--cms-line);color:var(--ui-text-dimmed);font-family:Fragment Mono,ui-monospace,monospace;font-size:.6875rem;font-weight:400;letter-spacing:.1em;line-height:1.4;padding:.75rem 1.25rem;text-transform:uppercase}.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)}.cms-table tbody tr:last-child{border-bottom:0}.cms-table tbody tr:hover{background:color-mix(in oklab,var(--cms-paper) 50%,transparent)}.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-paper);box-shadow:inset 0 -1px 0 var(--cms-line),inset 0 0 0 2px var(--cms-line-strong)}.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)}.cms-th-sort{flex:none;opacity:0;transition:opacity .15s ease}.cms-table th.is-sortable:hover .cms-th-sort,.cms-table th.is-sorted .cms-th-sort{opacity:1}.cms-alert{border-radius:.75rem;font-size:.875rem;padding:.75rem 1rem}.cms-alert-error{background:color-mix(in oklab,var(--ui-error) 10%,transparent);color:var(--ui-error)}.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(--ui-bg);border:1px solid var(--cms-line);border-radius:.75rem;box-shadow:var(--cms-shadow-soft);padding:.75rem .875rem}.cms-toast-row{align-items:flex-start;display:flex;gap:.75rem}.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}.cms-toast-dismiss:hover{color:var(--ui-text)}.cms-toast.is-success{border-left:3px solid var(--cms-fern)}.cms-toast.is-error{border-left:3px solid var(--ui-error)}.cms-pagination{align-items:center;display:flex;gap:.25rem;justify-content:center}.cms-page-btn{border-radius:.5rem;color:var(--ui-text-toned);font-size:.875rem;height:2rem;min-width:2rem;padding:0 .5rem}.cms-page-btn:hover:not(:disabled){background:var(--ui-bg-elevated)}.cms-page-btn.is-active{background:var(--cms-sage);color:var(--cms-sage-ink);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-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{gap:1.25rem}.cms-form,.cms-form-field{display:flex;flex-direction:column}.cms-form-field{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-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-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-field{overflow:hidden}.cms-media-field-preview{align-items:center;background-color:var(--cms-canvas);background-image:repeating-conic-gradient(from 0deg,hsla(0,0%,100%,.045) 0 25%,hsla(0,0%,100%,0) 0 50%);background-position:50%;background-size:16px 16px;color:var(--ui-text-dimmed);display:flex;justify-content:center;max-height:16rem;min-height:8rem;padding:.75rem}.cms-media-field-preview>img,.cms-media-field-preview>video{max-height:14.5rem;max-width:100%;-o-object-fit:contain;object-fit:contain}.cms-media-field-bar{align-items:center;background:var(--cms-surface);border-top:1px solid var(--cms-line);display:flex;gap:.5rem;padding:.625rem .75rem}.cms-media-bar-icon{color:var(--ui-text-dimmed);flex-shrink:0}.cms-media-bar-name{color:var(--ui-text-highlighted);flex:1;font-size:.875rem;font-weight:500;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.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,4 +1,2 @@
|
|
|
1
|
-
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<
|
|
2
|
-
[x: string]: any;
|
|
3
|
-
} | null>>;
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Record<string, unknown> | null>>;
|
|
4
2
|
export default _default;
|
|
@@ -2,7 +2,7 @@ import { asc, count, desc, sql } from "drizzle-orm";
|
|
|
2
2
|
import { defineEventHandler, getValidatedQuery } from "h3";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { useDb } from "#cms-db";
|
|
5
|
-
import { getRegistryEntry, idColumn, tableColumns } from "../utils/registry.js";
|
|
5
|
+
import { decodeRows, getRegistryEntry, idColumn, tableColumns } from "../utils/registry.js";
|
|
6
6
|
import { attachManyToMany, relationTitles } from "../utils/relations.js";
|
|
7
7
|
import { requireAdmin } from "../utils/require-admin.js";
|
|
8
8
|
const querySchema = z.object({
|
|
@@ -23,7 +23,7 @@ export default defineEventHandler(async (event) => {
|
|
|
23
23
|
if (entry.kind === "single") {
|
|
24
24
|
const rows = await db.select().from(table).limit(1);
|
|
25
25
|
await attachManyToMany(db, name, entry, rows);
|
|
26
|
-
return rows[0] ?? null;
|
|
26
|
+
return decodeRows(entry, rows)[0] ?? null;
|
|
27
27
|
}
|
|
28
28
|
const { limit, offset, search, sort, order, light } = await getValidatedQuery(
|
|
29
29
|
event,
|
|
@@ -52,6 +52,7 @@ export default defineEventHandler(async (event) => {
|
|
|
52
52
|
const [counted] = await (where ? db.select({ total: count() }).from(table).where(where) : db.select({ total: count() }).from(table));
|
|
53
53
|
if (light) return { items, total: counted?.total ?? 0, relations: {} };
|
|
54
54
|
await attachManyToMany(db, name, entry, items);
|
|
55
|
+
decodeRows(entry, items);
|
|
55
56
|
return {
|
|
56
57
|
items,
|
|
57
58
|
total: counted?.total ?? 0,
|
|
@@ -2,7 +2,7 @@ import { createError, defineEventHandler, readValidatedBody } from "h3";
|
|
|
2
2
|
import { withTransaction } from "#cms-db";
|
|
3
3
|
import { customId } from "../utils/custom-id.js";
|
|
4
4
|
import { mapConstraintErrors } from "../utils/db-errors.js";
|
|
5
|
-
import { buildValidator, getRegistryEntry } from "../utils/registry.js";
|
|
5
|
+
import { buildValidator, decodeRows, encodeColumnValues, getRegistryEntry } from "../utils/registry.js";
|
|
6
6
|
import {
|
|
7
7
|
assertRelationTargets,
|
|
8
8
|
attachManyToMany,
|
|
@@ -17,7 +17,7 @@ export default defineEventHandler(async (event) => {
|
|
|
17
17
|
throw createError({ statusCode: 405, statusMessage: "Single objects are updated with PUT" });
|
|
18
18
|
}
|
|
19
19
|
const body = await readValidatedBody(event, buildValidator(entry).parse);
|
|
20
|
-
const { values, lists } = splitRelationValues(entry, body);
|
|
20
|
+
const { values, lists } = splitRelationValues(entry, encodeColumnValues(entry, body));
|
|
21
21
|
await assertRelationTargets(entry, lists);
|
|
22
22
|
if (entry.drafts) values.status ??= "draft";
|
|
23
23
|
values.id = customId(entry.id);
|
|
@@ -28,7 +28,7 @@ export default defineEventHandler(async (event) => {
|
|
|
28
28
|
const [attached] = await attachManyToMany(db, name, entry, [
|
|
29
29
|
row
|
|
30
30
|
]);
|
|
31
|
-
return attached;
|
|
31
|
+
return decodeRows(entry, [attached])[0];
|
|
32
32
|
})
|
|
33
33
|
);
|
|
34
34
|
});
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { createError, defineEventHandler, readValidatedBody } from "h3";
|
|
2
2
|
import { withTransaction } from "#cms-db";
|
|
3
3
|
import { mapConstraintErrors } from "../utils/db-errors.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
buildValidator,
|
|
6
|
+
decodeRows,
|
|
7
|
+
encodeColumnValues,
|
|
8
|
+
getRegistryEntry,
|
|
9
|
+
idColumn,
|
|
10
|
+
withUpdatedAt
|
|
11
|
+
} from "../utils/registry.js";
|
|
5
12
|
import {
|
|
6
13
|
assertRelationTargets,
|
|
7
14
|
attachManyToMany,
|
|
@@ -16,7 +23,10 @@ export default defineEventHandler(async (event) => {
|
|
|
16
23
|
throw createError({ statusCode: 405, statusMessage: "Collections are updated with PUT /:id" });
|
|
17
24
|
}
|
|
18
25
|
const body = await readValidatedBody(event, buildValidator(entry).parse);
|
|
19
|
-
const { values, lists } = splitRelationValues(
|
|
26
|
+
const { values, lists } = splitRelationValues(
|
|
27
|
+
entry,
|
|
28
|
+
encodeColumnValues(entry, body)
|
|
29
|
+
);
|
|
20
30
|
await assertRelationTargets(entry, lists);
|
|
21
31
|
const set = withUpdatedAt(table, values);
|
|
22
32
|
return mapConstraintErrors(
|
|
@@ -26,7 +36,7 @@ export default defineEventHandler(async (event) => {
|
|
|
26
36
|
const [attached] = await attachManyToMany(db, name, entry, [
|
|
27
37
|
row
|
|
28
38
|
]);
|
|
29
|
-
return attached;
|
|
39
|
+
return decodeRows(entry, [attached])[0];
|
|
30
40
|
})
|
|
31
41
|
);
|
|
32
42
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { eq } from "drizzle-orm";
|
|
2
2
|
import { createError, defineEventHandler } from "h3";
|
|
3
3
|
import { useDb } from "#cms-db";
|
|
4
|
-
import { getRegistryEntry, idColumn, parseId } from "../utils/registry.js";
|
|
4
|
+
import { decodeRows, getRegistryEntry, idColumn, parseId } from "../utils/registry.js";
|
|
5
5
|
import { attachManyToMany } from "../utils/relations.js";
|
|
6
6
|
import { requireAdmin } from "../utils/require-admin.js";
|
|
7
7
|
export default defineEventHandler(async (event) => {
|
|
@@ -15,5 +15,5 @@ export default defineEventHandler(async (event) => {
|
|
|
15
15
|
const rows = await db.select().from(table).where(eq(idColumn(table), id)).limit(1);
|
|
16
16
|
if (!rows[0]) throw createError({ statusCode: 404, statusMessage: "Row not found" });
|
|
17
17
|
const [attached] = await attachManyToMany(db, name, entry, [rows[0]]);
|
|
18
|
-
return attached;
|
|
18
|
+
return decodeRows(entry, [attached])[0];
|
|
19
19
|
});
|
|
@@ -3,6 +3,8 @@ import { createError, defineEventHandler, readValidatedBody } from "h3";
|
|
|
3
3
|
import { withTransaction } from "#cms-db";
|
|
4
4
|
import {
|
|
5
5
|
buildValidator,
|
|
6
|
+
decodeRows,
|
|
7
|
+
encodeColumnValues,
|
|
6
8
|
getRegistryEntry,
|
|
7
9
|
idColumn,
|
|
8
10
|
parseId,
|
|
@@ -27,7 +29,10 @@ export default defineEventHandler(async (event) => {
|
|
|
27
29
|
}
|
|
28
30
|
const id = parseId(event);
|
|
29
31
|
const body = await readValidatedBody(event, buildValidator(entry).parse);
|
|
30
|
-
const { values, lists } = splitRelationValues(
|
|
32
|
+
const { values, lists } = splitRelationValues(
|
|
33
|
+
entry,
|
|
34
|
+
encodeColumnValues(entry, body)
|
|
35
|
+
);
|
|
31
36
|
await assertRelationTargets(entry, lists);
|
|
32
37
|
const set = withUpdatedAt(table, values);
|
|
33
38
|
return mapConstraintErrors(
|
|
@@ -38,7 +43,7 @@ export default defineEventHandler(async (event) => {
|
|
|
38
43
|
const [attached] = await attachManyToMany(db, name, entry, [
|
|
39
44
|
row
|
|
40
45
|
]);
|
|
41
|
-
return attached;
|
|
46
|
+
return decodeRows(entry, [attached])[0];
|
|
42
47
|
})
|
|
43
48
|
);
|
|
44
49
|
});
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { defineEventHandler, readValidatedBody } from "h3";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { normalizeMediaFolder, slugify } from "../../shared/index.js";
|
|
5
|
-
import { assertUploadContentType, useMediaStorage } from "../utils/media.js";
|
|
5
|
+
import { assertUploadContentType, assertUploadSize, useMediaStorage } from "../utils/media.js";
|
|
6
6
|
import { requireAdmin } from "../utils/require-admin.js";
|
|
7
7
|
const MAX_BASE_LENGTH = 80;
|
|
8
8
|
const MAX_EXT_LENGTH = 10;
|
|
9
|
-
const MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
10
9
|
const bodySchema = z.object({
|
|
11
10
|
filename: z.string().trim().min(1).max(255),
|
|
12
11
|
contentType: z.string().regex(/^[-\w.+]+\/[-\w.+]+$/, "Invalid content type"),
|
|
@@ -24,12 +23,7 @@ export default defineEventHandler(async (event) => {
|
|
|
24
23
|
const { media, client, bucketUrl, publicUrl } = useMediaStorage(event);
|
|
25
24
|
const { filename, contentType, size, folder } = await readValidatedBody(event, bodySchema.parse);
|
|
26
25
|
assertUploadContentType(contentType);
|
|
27
|
-
|
|
28
|
-
throw createError({
|
|
29
|
-
statusCode: 413,
|
|
30
|
-
statusMessage: "File exceeds the maximum size of 10 MB"
|
|
31
|
-
});
|
|
32
|
-
}
|
|
26
|
+
assertUploadSize(size, media.maxFileSize);
|
|
33
27
|
const now = /* @__PURE__ */ new Date();
|
|
34
28
|
const normalizedFolder = normalizeMediaFolder(folder);
|
|
35
29
|
const prefix = normalizedFolder ?? `${now.getUTCFullYear()}/${String(now.getUTCMonth() + 1).padStart(2, "0")}`;
|
|
@@ -19,7 +19,14 @@ import cmsConfig from "#cms-config";
|
|
|
19
19
|
import { useDb } from "#cms-db";
|
|
20
20
|
import * as cmsTables from "#cms-tables";
|
|
21
21
|
import { useRuntimeConfig } from "#imports";
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
decodeTranslatableMedia,
|
|
24
|
+
isTranslatableMediaField,
|
|
25
|
+
mediaPublicUrl,
|
|
26
|
+
mediaTypeFor,
|
|
27
|
+
pickTranslatedMedia,
|
|
28
|
+
translatableFieldKeys
|
|
29
|
+
} from "../../shared/index.js";
|
|
23
30
|
import { blockTypeName, blockUnionName, renderGraphqlSdl, typeName } from "../../shared/graphql-sdl.js";
|
|
24
31
|
import { getContentI18n, resolveTable, tableColumns } from "./registry.js";
|
|
25
32
|
const MAX_LIMIT = 100;
|
|
@@ -87,6 +94,11 @@ function localizeRow(entry, row, locale) {
|
|
|
87
94
|
const { defaultLocale } = getContentI18n();
|
|
88
95
|
const result = { ...row, [LOCALE]: locale };
|
|
89
96
|
for (const key of translatableFieldKeys(entry)) {
|
|
97
|
+
if (isTranslatableMediaField(entry.fields[key])) {
|
|
98
|
+
const media = decodeTranslatableMedia(row[key], defaultLocale);
|
|
99
|
+
result[key] = pickTranslatedMedia(media, locale, defaultLocale);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
90
102
|
const value = row[key];
|
|
91
103
|
result[key] = value?.[locale] ?? value?.[defaultLocale] ?? null;
|
|
92
104
|
}
|
|
@@ -7,10 +7,12 @@ interface MediaConfig {
|
|
|
7
7
|
region: string;
|
|
8
8
|
bucket: string;
|
|
9
9
|
presignExpiry: number;
|
|
10
|
+
maxFileSize: number;
|
|
10
11
|
accessKeyId: string;
|
|
11
12
|
secretAccessKey: string;
|
|
12
13
|
}
|
|
13
14
|
export declare function assertUploadContentType(contentType: string): void;
|
|
15
|
+
export declare function assertUploadSize(size: number, maxFileSize: number): void;
|
|
14
16
|
export declare function encodeKey(key: string): string;
|
|
15
17
|
export declare function assertMediaConfigured(media: MediaConfig): void;
|
|
16
18
|
export declare function assertMediaWritable(media: MediaConfig): void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AwsClient } from "aws4fetch";
|
|
2
2
|
import { createError } from "h3";
|
|
3
3
|
import { useRuntimeConfig } from "#imports";
|
|
4
|
-
import { mediaPublicUrl, mediaTypeFor } from "../../shared/index.js";
|
|
4
|
+
import { formatFileSize, mediaPublicUrl, mediaTypeFor } from "../../shared/index.js";
|
|
5
5
|
const UPLOAD_TYPE_PREFIXES = ["image/", "video/", "audio/", "font/"];
|
|
6
6
|
const UPLOAD_TYPE_BLOCKLIST = /* @__PURE__ */ new Set(["image/svg+xml"]);
|
|
7
7
|
const UPLOAD_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -29,6 +29,14 @@ export function assertUploadContentType(contentType) {
|
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
export function assertUploadSize(size, maxFileSize) {
|
|
33
|
+
if (size > maxFileSize) {
|
|
34
|
+
throw createError({
|
|
35
|
+
statusCode: 413,
|
|
36
|
+
statusMessage: `File exceeds the maximum size of ${formatFileSize(maxFileSize)}`
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
32
40
|
export function encodeKey(key) {
|
|
33
41
|
return key.split("/").map(encodeURIComponent).join("/");
|
|
34
42
|
}
|
|
@@ -17,4 +17,6 @@ export declare function getRegistryEntry(event: H3Event): {
|
|
|
17
17
|
export declare function buildValidator(entry: CmsEntry): z.ZodObject<{
|
|
18
18
|
[x: string]: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
19
19
|
}, z.core.$strip>;
|
|
20
|
+
export declare function encodeColumnValues(entry: CmsEntry, values: Record<string, unknown>): Record<string, unknown>;
|
|
21
|
+
export declare function decodeRows<T extends Record<string, unknown>>(entry: CmsEntry, rows: T[]): T[];
|
|
20
22
|
export declare function parseId(event: H3Event): string;
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import cmsConfig from "#cms-config";
|
|
4
4
|
import * as cmsTables from "#cms-tables";
|
|
5
5
|
import { useRuntimeConfig } from "#imports";
|
|
6
|
+
import { decodeEntryTranslatableMedia, encodeEntryTranslatableMedia } from "../../shared/index.js";
|
|
6
7
|
import { buildEntrySchema } from "../../shared/validation.js";
|
|
7
8
|
let contentI18n;
|
|
8
9
|
export function getContentI18n() {
|
|
@@ -42,6 +43,12 @@ export function getRegistryEntry(event) {
|
|
|
42
43
|
export function buildValidator(entry) {
|
|
43
44
|
return buildEntrySchema(entry, getContentI18n());
|
|
44
45
|
}
|
|
46
|
+
export function encodeColumnValues(entry, values) {
|
|
47
|
+
return encodeEntryTranslatableMedia(entry, values);
|
|
48
|
+
}
|
|
49
|
+
export function decodeRows(entry, rows) {
|
|
50
|
+
return decodeEntryTranslatableMedia(entry, rows, getContentI18n().defaultLocale);
|
|
51
|
+
}
|
|
45
52
|
export function parseId(event) {
|
|
46
53
|
return z.string().min(1).parse(getRouterParam(event, "id"));
|
|
47
54
|
}
|