smoodly 0.0.9 → 0.0.11
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 +167 -18
- package/dist/admin/client-ops.js +1 -0
- package/dist/admin/editor/CanvasOverlay.d.ts +21 -0
- package/dist/admin/editor/CanvasOverlay.js +39 -0
- package/dist/admin/editor/EditorView.js +56 -237
- package/dist/admin/editor/InsertPicker.d.ts +18 -0
- package/dist/admin/editor/InsertPicker.js +18 -0
- package/dist/admin/editor/NoticePane.d.ts +4 -0
- package/dist/admin/editor/NoticePane.js +4 -0
- package/dist/admin/editor/Outline.d.ts +18 -0
- package/dist/admin/editor/Outline.js +12 -0
- package/dist/admin/editor/locale-url.d.ts +12 -0
- package/dist/admin/editor/locale-url.js +32 -0
- package/dist/admin/editor/overlay-geometry.d.ts +14 -0
- package/dist/admin/editor/overlay-geometry.js +27 -0
- package/dist/admin/editor/useCanvasBridge.d.ts +15 -0
- package/dist/admin/editor/useCanvasBridge.js +53 -0
- package/dist/admin/editor/usePageLocale.d.ts +23 -0
- package/dist/admin/editor/usePageLocale.js +88 -0
- package/dist/admin/forms/CropEditor.d.ts +10 -0
- package/dist/admin/forms/CropEditor.js +61 -0
- package/dist/admin/forms/FieldWidget.js +8 -26
- package/dist/admin/forms/MediaGrid.d.ts +11 -0
- package/dist/admin/forms/MediaGrid.js +16 -0
- package/dist/admin/forms/MediaPicker.d.ts +8 -0
- package/dist/admin/forms/MediaPicker.js +44 -0
- package/dist/admin/forms/MediaWidget.d.ts +9 -0
- package/dist/admin/forms/MediaWidget.js +83 -0
- package/dist/admin/forms/RichtextToolbar.d.ts +20 -0
- package/dist/admin/forms/RichtextToolbar.js +37 -0
- package/dist/admin/forms/RichtextWidget.d.ts +5 -0
- package/dist/admin/forms/RichtextWidget.js +123 -0
- package/dist/admin/forms/assets-context.d.ts +19 -0
- package/dist/admin/forms/assets-context.js +35 -0
- package/dist/admin/forms/crop-editor.d.ts +13 -0
- package/dist/admin/forms/crop-editor.js +72 -0
- package/dist/admin/forms/measure.d.ts +2 -0
- package/dist/admin/forms/measure.js +39 -0
- package/dist/admin/forms/richtext-extensions.d.ts +5 -0
- package/dist/admin/forms/richtext-extensions.js +74 -0
- package/dist/admin/forms/richtext-paste.d.ts +22 -0
- package/dist/admin/forms/richtext-paste.js +59 -0
- package/dist/admin/forms/upload.d.ts +17 -0
- package/dist/admin/forms/upload.js +33 -0
- package/dist/admin/index.d.ts +1 -2
- package/dist/admin/index.js +0 -1
- package/dist/admin/next/create-admin.d.ts +3 -7
- package/dist/admin/next/create-admin.js +14 -5
- package/dist/admin/next/op-handler.d.ts +3 -0
- package/dist/admin/next/op-handler.js +1 -0
- package/dist/admin/ops-impl.d.ts +3 -0
- package/dist/admin/ops-impl.js +83 -2
- package/dist/admin/ops.d.ts +27 -0
- package/dist/admin/richtext-doc.d.ts +7 -0
- package/dist/admin/richtext-doc.js +18 -0
- package/dist/admin/serialize.d.ts +5 -0
- package/dist/admin/serialize.js +1 -0
- package/dist/admin/shell/AdminApp.d.ts +1 -2
- package/dist/admin/shell/AdminApp.js +18 -19
- package/dist/admin/shell/EntriesList.js +3 -2
- package/dist/admin/shell/EntryForm.js +8 -24
- package/dist/admin/shell/IconRail.d.ts +7 -0
- package/dist/admin/shell/IconRail.js +73 -0
- package/dist/admin/shell/ListView.js +3 -1
- package/dist/admin/shell/MediaScreen.d.ts +8 -0
- package/dist/admin/shell/MediaScreen.js +86 -0
- package/dist/admin/shell/PagesList.js +3 -2
- package/dist/admin/shell/RouteLink.d.ts +4 -0
- package/dist/admin/shell/RouteLink.js +12 -0
- package/dist/admin/shell/SharedForm.js +3 -21
- package/dist/admin/shell/media-list.d.ts +8 -0
- package/dist/admin/shell/media-list.js +14 -0
- package/dist/admin/shell/router.d.ts +37 -0
- package/dist/admin/shell/router.js +84 -0
- package/dist/admin/shell/session.js +3 -1
- package/dist/admin/ui/Modal.d.ts +8 -0
- package/dist/admin/ui/Modal.js +15 -0
- package/dist/admin/ui/primitives.d.ts +3 -2
- package/dist/admin/ui/primitives.js +8 -6
- package/dist/admin/ui/theme.d.ts +1 -1
- package/dist/admin/ui/theme.js +133 -24
- package/dist/admin/validate.js +16 -0
- package/dist/asset-store.d.ts +86 -0
- package/dist/asset-store.js +141 -0
- package/dist/collections.d.ts +0 -14
- package/dist/collections.js +2 -10
- package/dist/config.d.ts +12 -0
- package/dist/config.js +17 -5
- package/dist/fields.d.ts +10 -11
- package/dist/fields.js +16 -2
- package/dist/image/SmoodlyImage.d.ts +18 -0
- package/dist/image/SmoodlyImage.js +16 -0
- package/dist/image/SmoodlyVideo.d.ts +7 -0
- package/dist/image/SmoodlyVideo.js +5 -0
- package/dist/image/crop-math.d.ts +14 -0
- package/dist/image/crop-math.js +38 -0
- package/dist/image/index.d.ts +6 -0
- package/dist/image/index.js +5 -0
- package/dist/image/loaders.d.ts +15 -0
- package/dist/image/loaders.js +5 -0
- package/dist/image/next-config.d.ts +15 -0
- package/dist/image/next-config.js +27 -0
- package/dist/image-dimensions.d.ts +4 -0
- package/dist/image-dimensions.js +25 -0
- package/dist/index.d.ts +12 -4
- package/dist/index.js +8 -2
- package/dist/media.d.ts +108 -0
- package/dist/media.js +129 -0
- package/dist/refs.d.ts +2 -0
- package/dist/refs.js +25 -4
- package/dist/resolve.d.ts +19 -4
- package/dist/resolve.js +55 -2
- package/dist/site.d.ts +2 -0
- package/dist/site.js +21 -3
- package/dist/sql-space.d.ts +8 -1
- package/dist/sql-space.js +28 -0
- package/dist/sql.js +30 -0
- package/dist/store.js +3 -0
- package/dist/supabase-asset-store.d.ts +21 -0
- package/dist/supabase-asset-store.js +170 -0
- package/dist/toolset.d.ts +30 -0
- package/dist/toolset.js +49 -0
- package/package.json +15 -1
- package/dist/admin/richtext.d.ts +0 -16
- package/dist/admin/richtext.js +0 -98
|
@@ -15,28 +15,9 @@ import { LocaleSwitcher } from "../ui/LocaleSwitcher.js";
|
|
|
15
15
|
import { Banner, Breadcrumb, Button, Divider, SectionLabel, Segmented, TopBar } from "../ui/primitives.js";
|
|
16
16
|
import { entryTitle } from "./entries-list.js";
|
|
17
17
|
import { BASE } from "./base.js";
|
|
18
|
+
import { localeFromUrl, writeLocaleToUrl } from "../editor/locale-url.js";
|
|
19
|
+
import { useLeaveGuard } from "./router.js";
|
|
18
20
|
const STATUSES = ["Draft", "Published"];
|
|
19
|
-
function localeFromUrl(registry) {
|
|
20
|
-
try {
|
|
21
|
-
const l = new URLSearchParams(window.location.search).get("locale");
|
|
22
|
-
if (l && registry.locales.supported.includes(l))
|
|
23
|
-
return l;
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
// no window — the default is fine
|
|
27
|
-
}
|
|
28
|
-
return registry.locales.default;
|
|
29
|
-
}
|
|
30
|
-
function writeLocaleToUrl(next) {
|
|
31
|
-
try {
|
|
32
|
-
const u = new URL(window.location.href);
|
|
33
|
-
u.searchParams.set("locale", next);
|
|
34
|
-
window.history.replaceState(null, "", u);
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
// the URL is cosmetic here
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
21
|
export function SharedForm({ ops, registry, name }) {
|
|
41
22
|
const item = registry.shared.find((s) => s.name === name);
|
|
42
23
|
const multilingual = registry.locales.supported.length > 1;
|
|
@@ -48,6 +29,7 @@ export function SharedForm({ ops, registry, name }) {
|
|
|
48
29
|
const [banner, setBanner] = useState(null);
|
|
49
30
|
const [fieldErrors, setFieldErrors] = useState({});
|
|
50
31
|
const [dirty, setDirty] = useState(false);
|
|
32
|
+
useLeaveGuard(dirty);
|
|
51
33
|
const refTargets = useMemo(() => {
|
|
52
34
|
const out = {};
|
|
53
35
|
for (const [key, d] of Object.entries(item?.fields ?? {})) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AssetKind } from "../../media.ts";
|
|
2
|
+
export declare const KIND_FILTERS: readonly ["All", "Images", "Video", "Files"];
|
|
3
|
+
export type KindFilter = (typeof KIND_FILTERS)[number];
|
|
4
|
+
export declare const kindOfFilter: (f: KindFilter) => AssetKind | undefined;
|
|
5
|
+
export declare function formatBytes(n: number): string;
|
|
6
|
+
export declare const usageLabel: (n: number) => string;
|
|
7
|
+
/** The `accept` attribute for a field: the registry's list narrowed to the field's kinds. */
|
|
8
|
+
export declare function acceptFor(kinds: AssetKind[], accept: string[]): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const KIND_FILTERS = ["All", "Images", "Video", "Files"];
|
|
2
|
+
export const kindOfFilter = (f) => f === "Images" ? "image" : f === "Video" ? "video" : f === "Files" ? "file" : undefined;
|
|
3
|
+
export function formatBytes(n) {
|
|
4
|
+
if (n < 1024)
|
|
5
|
+
return `${n} B`;
|
|
6
|
+
if (n < 1024 * 1024)
|
|
7
|
+
return `${Math.round(n / 1024)} KB`;
|
|
8
|
+
return `${Math.round((n / (1024 * 1024)) * 10) / 10} MB`;
|
|
9
|
+
}
|
|
10
|
+
export const usageLabel = (n) => (n === 0 ? "Not used" : n === 1 ? "Used in 1 place" : `Used in ${n} places`);
|
|
11
|
+
/** The `accept` attribute for a field: the registry's list narrowed to the field's kinds. */
|
|
12
|
+
export function acceptFor(kinds, accept) {
|
|
13
|
+
return accept.filter((m) => kinds.some((k) => (k === "file" ? !m.startsWith("image/") && !m.startsWith("video/") : m.startsWith(`${k}/`)))).join(",");
|
|
14
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type Route = {
|
|
2
|
+
segments: string[];
|
|
3
|
+
search: string;
|
|
4
|
+
};
|
|
5
|
+
/** The segments under `base`: "/admin/articles/x" → ["articles", "x"];
|
|
6
|
+
* the mount itself, or a pathname outside it, → []. Segments stay as
|
|
7
|
+
* written; the shell decodes the ones it uses, as it does for the
|
|
8
|
+
* catch-all's params. */
|
|
9
|
+
export declare function segmentsOf(pathname: string, base?: string): string[];
|
|
10
|
+
/** A click the browser would turn into a same-tab navigation: left
|
|
11
|
+
* button, no modifier, no other target, not already handled. */
|
|
12
|
+
export declare function isPlainLeftClick(e: {
|
|
13
|
+
button: number;
|
|
14
|
+
metaKey: boolean;
|
|
15
|
+
ctrlKey: boolean;
|
|
16
|
+
shiftKey: boolean;
|
|
17
|
+
altKey: boolean;
|
|
18
|
+
defaultPrevented: boolean;
|
|
19
|
+
}, target?: string | null): boolean;
|
|
20
|
+
export declare const LEAVE_MESSAGE = "Discard unsaved changes?";
|
|
21
|
+
/** Leaving is allowed when no guard is dirty, else when the editor
|
|
22
|
+
* confirms — asked once, however many guards are dirty. */
|
|
23
|
+
export declare function mayLeave(guards: Iterable<() => boolean>, confirm: (message: string) => boolean): boolean;
|
|
24
|
+
/** Move to `href` in place; true when the move happened. A push asks the
|
|
25
|
+
* leave guard; a replace never leaves the view, so it does not ask.
|
|
26
|
+
* `force` skips the guard for a view that has just saved or deleted its
|
|
27
|
+
* record — React has not flushed its setDirty(false) yet. */
|
|
28
|
+
export declare function navigate(href: string, opts?: {
|
|
29
|
+
replace?: boolean;
|
|
30
|
+
force?: boolean;
|
|
31
|
+
}): boolean;
|
|
32
|
+
/** The current route: empty until mount, then the URL, re-read on
|
|
33
|
+
* popstate and after navigate(). */
|
|
34
|
+
export declare function useRoute(): Route;
|
|
35
|
+
/** Register the calling view as dirty: navigate() asks before leaving,
|
|
36
|
+
* and beforeunload covers reload, closing the tab and a real link out. */
|
|
37
|
+
export declare function useLeaveGuard(dirty: boolean): void;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
// packages/smoodly/src/admin/shell/router.ts
|
|
3
|
+
// The admin's own router (spec 2026-09-08 client routing): the route is
|
|
4
|
+
// the URL under BASE, held in React state and moved with pushState, so
|
|
5
|
+
// the shell stays mounted and a click is a state change, not a document
|
|
6
|
+
// load. The URL is the only source: the shell's first render is the
|
|
7
|
+
// session gate, so no view exists before the mount effect has read it.
|
|
8
|
+
// Next's router is not involved — native pushState updates its
|
|
9
|
+
// usePathname without a refetch — and the admin core stays
|
|
10
|
+
// framework-agnostic.
|
|
11
|
+
import { useEffect, useState } from "react";
|
|
12
|
+
import { BASE } from "./base.js";
|
|
13
|
+
/** The segments under `base`: "/admin/articles/x" → ["articles", "x"];
|
|
14
|
+
* the mount itself, or a pathname outside it, → []. Segments stay as
|
|
15
|
+
* written; the shell decodes the ones it uses, as it does for the
|
|
16
|
+
* catch-all's params. */
|
|
17
|
+
export function segmentsOf(pathname, base = BASE) {
|
|
18
|
+
if (pathname !== base && !pathname.startsWith(`${base}/`))
|
|
19
|
+
return [];
|
|
20
|
+
return pathname.slice(base.length).split("/").filter((s) => s !== "");
|
|
21
|
+
}
|
|
22
|
+
/** A click the browser would turn into a same-tab navigation: left
|
|
23
|
+
* button, no modifier, no other target, not already handled. */
|
|
24
|
+
export function isPlainLeftClick(e, target) {
|
|
25
|
+
if (e.defaultPrevented || e.button !== 0)
|
|
26
|
+
return false;
|
|
27
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey)
|
|
28
|
+
return false;
|
|
29
|
+
return !target || target === "_self";
|
|
30
|
+
}
|
|
31
|
+
export const LEAVE_MESSAGE = "Discard unsaved changes?";
|
|
32
|
+
/** Leaving is allowed when no guard is dirty, else when the editor
|
|
33
|
+
* confirms — asked once, however many guards are dirty. */
|
|
34
|
+
export function mayLeave(guards, confirm) {
|
|
35
|
+
for (const dirty of guards)
|
|
36
|
+
if (dirty())
|
|
37
|
+
return confirm(LEAVE_MESSAGE);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
// ── the live router ──────────────────────────────────────────────────
|
|
41
|
+
const guards = new Set();
|
|
42
|
+
const listeners = new Set();
|
|
43
|
+
function currentRoute() {
|
|
44
|
+
return { segments: segmentsOf(window.location.pathname), search: window.location.search };
|
|
45
|
+
}
|
|
46
|
+
const sameRoute = (a, b) => a.search === b.search && a.segments.length === b.segments.length && a.segments.every((s, i) => s === b.segments[i]);
|
|
47
|
+
/** Move to `href` in place; true when the move happened. A push asks the
|
|
48
|
+
* leave guard; a replace never leaves the view, so it does not ask.
|
|
49
|
+
* `force` skips the guard for a view that has just saved or deleted its
|
|
50
|
+
* record — React has not flushed its setDirty(false) yet. */
|
|
51
|
+
export function navigate(href, opts = {}) {
|
|
52
|
+
if (!opts.replace && !opts.force && !mayLeave(guards, (m) => window.confirm(m)))
|
|
53
|
+
return false;
|
|
54
|
+
window.history[opts.replace ? "replaceState" : "pushState"](null, "", href);
|
|
55
|
+
for (const listener of listeners)
|
|
56
|
+
listener();
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
/** The current route: empty until mount, then the URL, re-read on
|
|
60
|
+
* popstate and after navigate(). */
|
|
61
|
+
export function useRoute() {
|
|
62
|
+
const [route, setRoute] = useState({ segments: [], search: "" });
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
const sync = () => setRoute((prev) => { const next = currentRoute(); return sameRoute(prev, next) ? prev : next; });
|
|
65
|
+
sync();
|
|
66
|
+
listeners.add(sync);
|
|
67
|
+
window.addEventListener("popstate", sync);
|
|
68
|
+
return () => { listeners.delete(sync); window.removeEventListener("popstate", sync); };
|
|
69
|
+
}, []);
|
|
70
|
+
return route;
|
|
71
|
+
}
|
|
72
|
+
/** Register the calling view as dirty: navigate() asks before leaving,
|
|
73
|
+
* and beforeunload covers reload, closing the tab and a real link out. */
|
|
74
|
+
export function useLeaveGuard(dirty) {
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!dirty)
|
|
77
|
+
return;
|
|
78
|
+
const guard = () => true;
|
|
79
|
+
guards.add(guard);
|
|
80
|
+
const onBeforeUnload = (e) => { e.preventDefault(); e.returnValue = ""; };
|
|
81
|
+
window.addEventListener("beforeunload", onBeforeUnload);
|
|
82
|
+
return () => { guards.delete(guard); window.removeEventListener("beforeunload", onBeforeUnload); };
|
|
83
|
+
}, [dirty]);
|
|
84
|
+
}
|
|
@@ -8,7 +8,9 @@ import { createClient } from "@supabase/supabase-js";
|
|
|
8
8
|
export function useAdminSession(auth) {
|
|
9
9
|
const browser = typeof window !== "undefined";
|
|
10
10
|
const [client] = useState(() => createClient(auth.url, auth.key, {
|
|
11
|
-
|
|
11
|
+
// Own storage key: a self-hoster's own supabase-js client in the same
|
|
12
|
+
// browser must never share the admin's session slot (DESIGN.md §1).
|
|
13
|
+
auth: { autoRefreshToken: browser, persistSession: browser, detectSessionInUrl: browser, storageKey: "smoodly-auth" },
|
|
12
14
|
}));
|
|
13
15
|
const [state, setState] = useState({ status: "loading" });
|
|
14
16
|
const current = useRef(null);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// The one modal: a fixed overlay (click outside or Escape closes) and a
|
|
4
|
+
// centred panel with a title row. Same overlay shape as InsertPicker.
|
|
5
|
+
import { useEffect } from "react";
|
|
6
|
+
import { IconButton } from "./primitives.js";
|
|
7
|
+
export function Modal({ title, width = 720, onClose, children, footer }) {
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const onKey = (e) => { if (e.key === "Escape")
|
|
10
|
+
onClose(); };
|
|
11
|
+
window.addEventListener("keydown", onKey);
|
|
12
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
13
|
+
}, [onClose]);
|
|
14
|
+
return (_jsx("div", { className: "sm-modal", onClick: onClose, children: _jsxs("div", { className: "sm-modal__panel", role: "dialog", "aria-label": title, style: { width }, onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { className: "sm-modal__head", children: [_jsx("span", { className: "sm-title", children: title }), _jsx(IconButton, { label: "Close", onClick: onClose, children: "\u00D7" })] }), _jsx("div", { className: "sm-modal__body", children: children }), footer && _jsx("div", { className: "sm-modal__foot", children: footer })] }) }));
|
|
15
|
+
}
|
|
@@ -5,7 +5,7 @@ import type { SaveState } from "../editor/autosave.ts";
|
|
|
5
5
|
export declare function Logo({ height }: {
|
|
6
6
|
height?: number;
|
|
7
7
|
}): import("react").JSX.Element;
|
|
8
|
-
/** Light ⇄ dark, remembered in localStorage.
|
|
8
|
+
/** Light ⇄ dark, remembered in localStorage; an entry in the rail's user menu.
|
|
9
9
|
* The attribute goes on the ADMIN wrapper, never the host <html>: a customer's
|
|
10
10
|
* site may own `data-theme` there. Which half of the button shows is decided by
|
|
11
11
|
* --when-light/--when-dark in the same token block as the colors, so the icon
|
|
@@ -44,7 +44,8 @@ export declare function TopBar({ left, right }: {
|
|
|
44
44
|
left: ReactNode;
|
|
45
45
|
right?: ReactNode;
|
|
46
46
|
}): import("react").JSX.Element;
|
|
47
|
-
/** `items` are crumbs; every one but the last is a link when given an href.
|
|
47
|
+
/** `items` are crumbs; every one but the last is a link when given an href.
|
|
48
|
+
* Links route in place: the shell never reloads for a crumb. */
|
|
48
49
|
export declare function Breadcrumb({ items, backHref }: {
|
|
49
50
|
items: {
|
|
50
51
|
label: string;
|
|
@@ -7,15 +7,16 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
7
7
|
import { Fragment } from "react";
|
|
8
8
|
import { Moon, Sun } from "lucide-react";
|
|
9
9
|
import { THEME_KEY } from "./theme.js";
|
|
10
|
+
import { RouteLink } from "../shell/RouteLink.js";
|
|
10
11
|
/** Lucide at the design system's size and stroke. */
|
|
11
12
|
const ICON = { size: 14, strokeWidth: 1.5, "aria-hidden": true };
|
|
12
13
|
/* ── brand ───────────────────────────────────────────────────────────── */
|
|
13
14
|
/** The wordmark, inlined: a package can't ship a file into the host app's
|
|
14
15
|
* /public. Dark mode inverts it through --logo-filter, per the handoff. */
|
|
15
16
|
export function Logo({ height = 18 }) {
|
|
16
|
-
return (_jsx("svg", { className: "sm-
|
|
17
|
+
return (_jsx("svg", { className: "sm-wordmark", style: { height }, viewBox: "0 0 183 43", fill: "none", xmlns: "http://www.w3.org/2000/svg", role: "img", "aria-label": "Smoodly", children: _jsx("path", { fill: "#0038B1", d: "M13.294 9.9796C15.9908 9.9796 19.823 10.7144 21.4552 11.5204L20.1069 18.3473C18.1198 17.4228 15.3284 16.688 12.7973 16.688C11.307 16.688 10.3371 17.0198 10.3371 17.8021C10.3371 18.3236 10.8575 18.6318 11.7801 18.8214L13.7198 19.2481C17.7413 20.1251 19.7993 22.2111 19.7993 25.4112C19.7993 31.9063 14.7607 34.3716 8.23173 34.3716C4.9909 34.3716 1.8447 33.5893 0.046875 32.8782L1.48987 25.6009C3.90274 26.8572 7.14357 27.6395 9.22526 27.6395C10.7156 27.6395 11.7328 27.2602 11.7328 26.3357C11.7328 25.8142 11.3543 25.4112 10.2425 25.2216L8.58656 24.8897C5.44036 24.2971 2.74362 22.14 2.74362 18.6792C2.74362 13.2982 6.81239 10.0033 13.3413 10.0033L13.294 9.9796ZM52.8463 9.9796C49.014 9.9796 46.6012 12.0656 45.2528 13.5116C44.1883 11.3308 41.9174 9.9796 39.2916 9.9796C36.6658 9.9796 34.6551 10.9989 33.283 12.3975L33.6142 10.7381H26.0681L21.6208 33.6604H29.2616L32.0766 19.011C32.9992 17.8495 34.2529 16.9724 35.6013 16.9724C37.4464 16.9724 38.2034 18.2288 37.8249 20.22L35.2228 33.6604H42.8636L45.6786 18.9162C46.6012 17.8021 47.8549 16.9724 49.156 16.9724C51.0011 16.9724 51.7344 18.2288 51.3796 20.22L49.9366 27.7817C49.5581 29.7255 49.4635 31.9537 50.1732 33.6604H57.8139C57.1279 31.9537 57.1989 29.7255 57.5774 27.7817L59.3752 18.3236C60.3451 13.3219 57.1043 10.027 52.8463 10.027V9.9796ZM109.596 19.6273C109.596 27.6158 103.304 34.3241 94.7877 34.3241C89.7727 34.3241 86.1061 31.9537 84.6158 28.3743C84.4975 28.1136 84.19 28.0662 84.0244 28.3032C81.3986 31.93 77.0933 34.3241 72.031 34.3241C65.4074 34.3241 61.0548 30.1996 61.0548 24.629C61.0548 16.688 67.2998 9.9796 75.8159 9.9796C80.8545 9.9796 84.5211 12.3501 86.0351 15.882C86.1534 16.1428 86.4609 16.1902 86.6265 15.9531C89.2523 12.3501 93.5103 9.9559 98.5963 9.9559C105.267 9.9559 109.62 14.0805 109.62 19.6036L109.596 19.6273ZM78.7965 20.9785C78.7965 18.7029 77.2825 17.1858 74.8697 17.1858C71.6761 17.1858 69.0267 19.9592 69.0267 23.349C69.0267 25.672 70.5643 27.1417 72.9062 27.1417C76.0997 27.1417 78.7728 24.3682 78.7728 20.9785H78.7965ZM101.577 20.9785C101.577 18.7029 100.039 17.1858 97.65 17.1858C94.4565 17.1858 91.8307 19.9592 91.8307 23.349C91.8307 25.672 93.3447 27.1417 95.7103 27.1417C98.9038 27.1417 101.601 24.3682 101.601 20.9785H101.577ZM134.529 33.6367H126.888C126.652 33.1152 126.51 32.5226 126.463 31.93C124.972 33.0915 122.299 34.3479 119.484 34.3479C114.02 34.3479 110.968 29.9388 110.921 24.7475C110.921 16.7591 116.527 10.0033 123.837 10.0033C126.108 10.0033 128.331 10.6433 129.68 12.0419L132.022 0H139.662L134.293 27.7343C133.914 29.6781 133.819 31.9063 134.529 33.6367ZM128.331 18.9873C127.503 17.8258 126.06 17.1858 124.57 17.1858C121.14 17.1858 118.964 20.0066 118.964 23.1356C118.822 25.6957 120.454 27.1654 122.749 27.1654C124.002 27.1654 125.848 26.4305 127.101 25.3638L128.308 19.011L128.331 18.9873ZM146.002 27.758L151.348 0H143.708L138.338 27.7343C137.959 29.6781 137.865 31.9063 138.574 33.613H146.215C145.529 31.9063 145.624 29.6781 145.978 27.7343L146.002 27.758ZM169.752 10.7381L162.064 23.823L158.776 10.7381H150.71L156.86 32.4041L150.095 43.0237H158.421L178.552 10.7381H169.705H169.752ZM179.167 24.8423C176.258 24.8423 173.608 27.4024 173.608 30.2707C173.608 32.3093 175.099 33.8975 177.417 33.8975C180.421 33.8975 183.023 31.3374 183.023 28.4691C183.023 26.3831 181.486 24.8423 179.191 24.8423H179.167Z" }) }));
|
|
17
18
|
}
|
|
18
|
-
/** Light ⇄ dark, remembered in localStorage.
|
|
19
|
+
/** Light ⇄ dark, remembered in localStorage; an entry in the rail's user menu.
|
|
19
20
|
* The attribute goes on the ADMIN wrapper, never the host <html>: a customer's
|
|
20
21
|
* site may own `data-theme` there. Which half of the button shows is decided by
|
|
21
22
|
* --when-light/--when-dark in the same token block as the colors, so the icon
|
|
@@ -38,7 +39,7 @@ export function ThemeToggle() {
|
|
|
38
39
|
}
|
|
39
40
|
catch { /* not remembered */ }
|
|
40
41
|
};
|
|
41
|
-
return (_jsxs("button", { type: "button", className: "sm-
|
|
42
|
+
return (_jsxs("button", { type: "button", role: "menuitem", className: "sm-rail__menu-item", onClick: toggle, children: [_jsxs("span", { className: "sm-when-light", children: [_jsx(Moon, { ...ICON }), "Dark mode"] }), _jsxs("span", { className: "sm-when-dark", children: [_jsx(Sun, { ...ICON }), "Light mode"] })] }));
|
|
42
43
|
}
|
|
43
44
|
export function Button({ variant = "primary", size = "md", className, ...rest }) {
|
|
44
45
|
return (_jsx("button", { type: "button", className: cx("sm-btn", `sm-btn--${variant}`, size === "lg" && "sm-btn--lg", className), ...rest }));
|
|
@@ -61,14 +62,15 @@ export function Field({ label, counter, hint, error, asDiv, children, style, cla
|
|
|
61
62
|
export function TopBar({ left, right }) {
|
|
62
63
|
return (_jsxs("header", { className: "sm-topbar", children: [_jsx("div", { className: "sm-topbar__left", children: left }), _jsx("div", { className: "sm-topbar__right", children: right })] }));
|
|
63
64
|
}
|
|
64
|
-
/** `items` are crumbs; every one but the last is a link when given an href.
|
|
65
|
+
/** `items` are crumbs; every one but the last is a link when given an href.
|
|
66
|
+
* Links route in place: the shell never reloads for a crumb. */
|
|
65
67
|
export function Breadcrumb({ items, backHref }) {
|
|
66
|
-
return (_jsxs("div", { className: "sm-crumbs", children: [backHref && _jsx(
|
|
68
|
+
return (_jsxs("div", { className: "sm-crumbs", children: [backHref && _jsx(RouteLink, { className: "sm-back", href: backHref, "aria-label": "Back", children: "\u2190" }), items.map((it, i) => {
|
|
67
69
|
const last = i === items.length - 1;
|
|
68
70
|
return (_jsxs(Fragment, { children: [i > 0 && _jsx("span", { className: "sm-crumbs__sep", children: "/" }), last
|
|
69
71
|
? _jsx("span", { className: "sm-crumbs__here", children: it.label })
|
|
70
72
|
: it.href
|
|
71
|
-
? _jsx(
|
|
73
|
+
? _jsx(RouteLink, { href: it.href, style: { color: "inherit" }, children: it.label })
|
|
72
74
|
: _jsx("span", { children: it.label })] }, i));
|
|
73
75
|
})] }));
|
|
74
76
|
}
|
package/dist/admin/ui/theme.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const ADMIN_CSS = "\n@import url(\"https://fonts.googleapis.com/css2?family=Public+Sans:wght@400;500;600;700&display=swap\");\n\n.sm-admin{\n --font-sans:'Public Sans',-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;\n --font-mono:ui-monospace,Menlo,Consolas,monospace;\n --text-2xs:9px; --text-xs:10px; --text-sm:11px; --text-md:12px;\n --text-lg:13px; --text-xl:14px; --text-2xl:15px; --text-3xl:20px; --text-4xl:30px;\n --tracking-tight:-0.02em; --tracking-label:0.1em; --tracking-tag:0.08em;\n\n --radius-xs:1px; --radius-sm:3px; --radius-md:4px; --radius-round:999px;\n --control-h:28px; --control-h-lg:36px; --row-h:38px; --topbar-h:48px;\n --nav-w:200px; --inspector-w:300px;\n --border:1px solid var(--line);\n --outline-selection:1.5px solid var(--selection);\n\n --brand-600:#0038B1; --brand-500:#2554D6; --brand-300:#6E96FF;\n --brand-200:#A9C0FF; --brand-100:#DCE4F9; --brand-50:#E3EAFA;\n --gray-0:#FFFFFF; --gray-50:#F1F2F6; --gray-100:#E1E4EC; --gray-300:#C9D0E2;\n --gray-500:#8B93A7; --gray-600:#6B7386; --gray-700:#4A5165; --gray-900:#0F1420;\n --green-700:#1F7A46; --green-50:#E3F3EA; --green-300:#6FD39A; --green-500:#2E9E5B;\n --amber-700:#8A5A00; --amber-50:#FBF1DC; --amber-300:#E8B85A;\n --red-700:#B42318; --red-50:#FDECEC; --red-300:#F5A5A0;\n\n --bg:var(--gray-50); --surface:var(--gray-0); --surface-inset:var(--gray-50);\n --input-bg:var(--gray-0); --line:var(--gray-100); --line-dashed:var(--gray-300);\n --text:var(--gray-900); --text-muted:var(--gray-600); --text-subtle:var(--gray-500);\n --accent:var(--brand-600); --on-accent:#FFFFFF; --accent-soft:var(--brand-50);\n --selection:var(--brand-600); --nav-active-bg:var(--brand-100); --nav-active-fg:var(--brand-600);\n --hover:rgba(15,20,32,.045); --press:rgba(15,20,32,.08);\n --control-hover:var(--gray-100);\n\n --status-published-fg:var(--green-700); --status-published-bg:var(--green-50);\n --status-draft-fg:var(--amber-700); --status-draft-bg:var(--amber-50);\n --status-scheduled-fg:var(--accent); --status-scheduled-bg:var(--accent-soft);\n --status-saved-dot:var(--green-500);\n --warn:var(--amber-700); --warn-soft:var(--amber-50);\n --danger:var(--red-700); --danger-soft:var(--red-50);\n\n --page-bg:#FFFFFF; --page-text:var(--gray-900);\n --shadow-page:0 1px 2px rgba(0,0,0,.08), 0 8px 24px rgba(0,0,0,.06);\n --shadow-float:0 2px 8px rgba(0,0,0,.25);\n --shadow-seg:0 1px 2px rgba(0,0,0,.08);\n --focus-ring:0 0 0 3px var(--accent-soft);\n --shadow-pop:0 8px 32px rgba(0,0,0,.18);\n --logo-filter:none;\n\n /* Which half of the theme toggle is visible. A token, so the icon can never\n disagree with the colors \u2014 both follow the one cascade below. */\n --when-light:inline-flex; --when-dark:none;\n}\nhtml[data-theme=\"dark\"] .sm-admin,.sm-admin[data-theme=\"dark\"]{\n --bg:#0D0F14; --surface:#15181F; --surface-inset:#0D0F14; --input-bg:#1B1F28;\n --line:#2A2F3B; --line-dashed:#2A2F3B;\n --text:#EEF0F5; --text-muted:#8C94A8; --text-subtle:#6B7386;\n --accent:var(--brand-300); --on-accent:#06122F; --accent-soft:rgba(110,150,255,.16);\n --nav-active-bg:rgba(110,150,255,.14); --nav-active-fg:var(--brand-200);\n --hover:rgba(238,240,245,.05); --press:rgba(238,240,245,.09);\n --control-hover:var(--input-bg);\n\n --status-published-fg:var(--green-300); --status-published-bg:rgba(111,211,154,.14);\n --status-draft-fg:var(--amber-300); --status-draft-bg:rgba(232,184,90,.14);\n --warn:var(--amber-300); --warn-soft:rgba(232,184,90,.14);\n --danger:var(--red-300); --danger-soft:rgba(244,102,92,.14);\n\n --shadow-page:0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.35);\n --shadow-pop:0 8px 32px rgba(0,0,0,.5);\n --logo-filter:brightness(0) invert(1);\n --when-light:none; --when-dark:inline-flex;\n}\n@media (prefers-color-scheme:dark){\n html:not([data-theme=\"light\"]) .sm-admin:not([data-theme=\"light\"]){\n --bg:#0D0F14; --surface:#15181F; --surface-inset:#0D0F14; --input-bg:#1B1F28;\n --line:#2A2F3B; --line-dashed:#2A2F3B;\n --text:#EEF0F5; --text-muted:#8C94A8; --text-subtle:#6B7386;\n --accent:var(--brand-300); --on-accent:#06122F; --accent-soft:rgba(110,150,255,.16);\n --nav-active-bg:rgba(110,150,255,.14); --nav-active-fg:var(--brand-200);\n --hover:rgba(238,240,245,.05); --press:rgba(238,240,245,.09);\n --control-hover:var(--input-bg);\n\n --status-published-fg:var(--green-300); --status-published-bg:rgba(111,211,154,.14);\n --status-draft-fg:var(--amber-300); --status-draft-bg:rgba(232,184,90,.14);\n --warn:var(--amber-300); --warn-soft:rgba(232,184,90,.14);\n --danger:var(--red-300); --danger-soft:rgba(244,102,92,.14);\n\n --shadow-page:0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.35);\n --shadow-pop:0 8px 32px rgba(0,0,0,.5);\n --logo-filter:brightness(0) invert(1);\n --when-light:none; --when-dark:inline-flex;\n}\n}\n/* Last, and the same specificity as the host-attribute rule: an explicit\n choice from the theme toggle wins over a dark <html> and over the OS. */\n.sm-admin[data-theme=\"light\"]{\n --brand-600:#0038B1; --brand-500:#2554D6; --brand-300:#6E96FF;\n --brand-200:#A9C0FF; --brand-100:#DCE4F9; --brand-50:#E3EAFA;\n --gray-0:#FFFFFF; --gray-50:#F1F2F6; --gray-100:#E1E4EC; --gray-300:#C9D0E2;\n --gray-500:#8B93A7; --gray-600:#6B7386; --gray-700:#4A5165; --gray-900:#0F1420;\n --green-700:#1F7A46; --green-50:#E3F3EA; --green-300:#6FD39A; --green-500:#2E9E5B;\n --amber-700:#8A5A00; --amber-50:#FBF1DC; --amber-300:#E8B85A;\n --red-700:#B42318; --red-50:#FDECEC; --red-300:#F5A5A0;\n\n --bg:var(--gray-50); --surface:var(--gray-0); --surface-inset:var(--gray-50);\n --input-bg:var(--gray-0); --line:var(--gray-100); --line-dashed:var(--gray-300);\n --text:var(--gray-900); --text-muted:var(--gray-600); --text-subtle:var(--gray-500);\n --accent:var(--brand-600); --on-accent:#FFFFFF; --accent-soft:var(--brand-50);\n --selection:var(--brand-600); --nav-active-bg:var(--brand-100); --nav-active-fg:var(--brand-600);\n --hover:rgba(15,20,32,.045); --press:rgba(15,20,32,.08);\n --control-hover:var(--gray-100);\n\n --status-published-fg:var(--green-700); --status-published-bg:var(--green-50);\n --status-draft-fg:var(--amber-700); --status-draft-bg:var(--amber-50);\n --status-scheduled-fg:var(--accent); --status-scheduled-bg:var(--accent-soft);\n --status-saved-dot:var(--green-500);\n --warn:var(--amber-700); --warn-soft:var(--amber-50);\n --danger:var(--red-700); --danger-soft:var(--red-50);\n\n --page-bg:#FFFFFF; --page-text:var(--gray-900);\n --shadow-page:0 1px 2px rgba(0,0,0,.08), 0 8px 24px rgba(0,0,0,.06);\n --shadow-float:0 2px 8px rgba(0,0,0,.25);\n --shadow-seg:0 1px 2px rgba(0,0,0,.08);\n --focus-ring:0 0 0 3px var(--accent-soft);\n --shadow-pop:0 8px 32px rgba(0,0,0,.18);\n --logo-filter:none;\n\n /* Which half of the theme toggle is visible. A token, so the icon can never\n disagree with the colors \u2014 both follow the one cascade below. */\n --when-light:inline-flex; --when-dark:none;\n}\n\n/* The admin owns the viewport; a host <body> margin would push it into a\n page scrollbar. Scoped by :has, so only a page holding the admin is touched. */\nbody:has(.sm-admin){margin:0}\n.sm-admin,.sm-admin *,.sm-admin *::before,.sm-admin *::after{box-sizing:border-box}\n.sm-admin{\n font-family:var(--font-sans);font-size:var(--text-md);line-height:1.4;\n color:var(--text);background:var(--surface);\n -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;\n}\n.sm-admin h1,.sm-admin h2,.sm-admin h3,.sm-admin p,.sm-admin ul{margin:0}\n/* :where() keeps this at zero specificity \u2014 a bare <a> is accent, but any\n component class on it (.sm-nav-item, .sm-btn, .sm-back) decides its own\n colour. Without this the element selector silently beat every one of them. */\n:where(.sm-admin a){color:var(--accent);text-decoration:none}\n:where(.sm-admin a:hover){color:var(--brand-500)}\n.sm-admin button,.sm-admin input,.sm-admin select,.sm-admin textarea{font-family:inherit}\n.sm-admin ::placeholder{color:var(--text-subtle)}\n.sm-admin ::selection{background:var(--accent-soft);color:var(--text)}\n.sm-mono{font-family:var(--font-mono)}\n\n/* \u2500\u2500 shell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.sm-shell{display:grid;grid-template-columns:var(--nav-w) 1fr;height:100vh;height:100dvh;overflow:hidden}\n.sm-shell>.sm-main{min-width:0;min-height:0;display:flex;flex-direction:column;overflow:hidden}\n.sm-nav{\n display:flex;flex-direction:column;gap:24px;padding:16px 12px;\n border-right:var(--border);background:var(--surface);min-width:0;overflow:auto;\n}\n.sm-nav__logo{height:18px;width:auto;align-self:flex-start;filter:var(--logo-filter);flex:none}\n.sm-nav>.sm-nav__logo{margin-left:10px}\n.sm-nav__list{display:flex;flex-direction:column;gap:1px}\n.sm-nav-item{\n display:block;padding:6px 10px;border-radius:var(--radius-md);line-height:1.3;\n color:var(--text);font-weight:500;text-decoration:none;cursor:pointer;\n background:transparent;border:0;width:100%;text-align:left;font-size:var(--text-md);\n overflow:hidden;text-overflow:ellipsis;white-space:nowrap;\n}\n.sm-nav-item:hover{background:var(--surface-inset);color:var(--text)}\n.sm-nav-item[aria-current=\"page\"]{background:var(--nav-active-bg);color:var(--nav-active-fg);font-weight:600}\n.sm-nav-item[aria-current=\"page\"]:hover{background:var(--nav-active-bg);color:var(--nav-active-fg)}\n/* Not greyed: the reference renders every inactive item at --text and marks\n the unavailable ones only by being inert (no pointer, no hover). */\n.sm-nav-item[aria-disabled=\"true\"]{cursor:default}\n.sm-nav-item[aria-disabled=\"true\"]:hover{background:transparent}\n.sm-nav__foot{margin-top:auto;display:flex;flex-direction:column;gap:2px}\n.sm-nav__user{display:flex;align-items:center;gap:10px;padding:6px 10px;min-width:0}\n.sm-nav-item--icon{display:flex;align-items:center;gap:8px}\n.sm-when-light{display:var(--when-light);align-items:center;gap:8px}\n.sm-when-dark{display:var(--when-dark);align-items:center;gap:8px}\n.sm-avatar{\n width:24px;height:24px;flex:none;border-radius:var(--radius-round);\n background:var(--accent-soft);color:var(--accent);\n display:grid;place-items:center;font-size:var(--text-sm);font-weight:600;\n}\n\n/* \u2500\u2500 top bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.sm-topbar{\n height:var(--topbar-h);flex:none;display:flex;align-items:center;justify-content:space-between;\n gap:16px;padding:0 16px;border-bottom:var(--border);background:var(--surface);\n}\n.sm-topbar__left{display:flex;align-items:center;gap:10px;min-width:0}\n.sm-topbar__right{display:flex;align-items:center;gap:8px;flex:none}\n.sm-crumbs{display:flex;align-items:center;gap:8px;color:var(--text-muted);min-width:0}\n.sm-crumbs__sep{opacity:.5}\n.sm-crumbs__here{color:var(--text);font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.sm-back{border:0;background:transparent;padding:0;color:var(--text);font:inherit;cursor:pointer;line-height:1}\n.sm-back:hover{color:var(--accent)}\n.sm-title{font-size:var(--text-xl);font-weight:600}\n.sm-count{color:var(--text-muted)}\n\n/* \u2500\u2500 buttons \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.sm-btn{\n display:inline-flex;align-items:center;justify-content:center;gap:6px;\n height:var(--control-h);padding:0 12px;border-radius:var(--radius-md);border:0;\n font-size:var(--text-md);font-weight:500;line-height:1;white-space:nowrap;cursor:pointer;\n text-decoration:none;\n}\n.sm-btn--lg{height:var(--control-h-lg);padding:0 16px}\n.sm-btn--primary{background:var(--accent);color:var(--on-accent);font-weight:600;padding:0 14px}\n.sm-btn--primary:hover{background:var(--brand-500);color:var(--on-accent)}\n.sm-btn--secondary{background:var(--surface-inset);color:var(--text)}\n.sm-btn--secondary:hover{background:var(--control-hover);color:var(--text)}\n.sm-btn--ghost{background:transparent;color:var(--accent);font-weight:600;padding:0 8px}\n.sm-btn--ghost:hover{background:var(--accent-soft);color:var(--accent)}\n.sm-btn--danger{background:var(--surface-inset);color:var(--danger)}\n.sm-btn--danger:hover{background:var(--danger-soft);color:var(--danger)}\n.sm-btn--ok{background:var(--status-published-bg);color:var(--status-published-fg);font-weight:600;padding:0 14px}\n.sm-btn:disabled,.sm-btn[aria-disabled=\"true\"]{opacity:.5;cursor:default;pointer-events:none}\n.sm-btn:focus-visible,.sm-iconbtn:focus-visible,.sm-nav-item:focus-visible,\n.sm-tab:focus-visible,.sm-seg__opt:focus-visible,.sm-row:focus-visible{\n outline:2px solid var(--accent);outline-offset:1px\n}\n\n.sm-iconbtn{\n width:24px;height:24px;flex:none;display:grid;place-items:center;padding:0;\n border:0;border-radius:var(--radius-sm);background:transparent;color:var(--text-muted);\n font-size:12px;line-height:1;cursor:pointer;\n}\n.sm-iconbtn:hover{background:var(--hover);color:var(--text)}\n.sm-iconbtn--inverse{color:#EEF0F5}\n.sm-iconbtn--inverse:hover{background:rgba(255,255,255,.14);color:#fff}\n.sm-iconbtn:disabled{opacity:.3;cursor:default}\n.sm-iconbtn:disabled:hover{background:transparent}\n\n/* \u2500\u2500 fields \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.sm-field{display:flex;flex-direction:column;gap:6px;min-width:0}\n.sm-field__head{display:flex;align-items:baseline;justify-content:space-between;gap:8px}\n.sm-label{\n font-size:var(--text-2xs);font-weight:600;letter-spacing:var(--tracking-label);\n text-transform:uppercase;color:var(--text-muted);\n}\n.sm-counter,.sm-hint{font-size:var(--text-sm);color:var(--text-muted)}\n.sm-input,.sm-textarea,.sm-select{\n width:100%;min-width:0;border:1.5px solid transparent;border-radius:var(--radius-md);\n background:var(--surface-inset);color:var(--text);font-size:var(--text-md);\n outline:0;box-shadow:none;\n}\n.sm-input,.sm-select{height:var(--control-h);padding:0 10px}\n.sm-textarea{padding:8px 10px;line-height:1.5;resize:vertical;min-height:60px}\n.sm-input--lg{height:var(--control-h-lg);font-size:var(--text-2xl);font-weight:600}\n.sm-input--mono{font-family:var(--font-mono);font-size:var(--text-sm)}\n.sm-input:hover,.sm-textarea:hover,.sm-select:hover{background:var(--control-hover)}\n.sm-input:focus,.sm-textarea:focus,.sm-select:focus,\n.sm-inputwrap:focus-within{\n background:var(--input-bg);border-color:var(--selection);box-shadow:var(--focus-ring);\n}\n.sm-input:disabled,.sm-textarea:disabled,.sm-select:disabled{opacity:.6;cursor:default}\n.sm-select{\n appearance:none;-webkit-appearance:none;cursor:pointer;padding-right:26px;font-weight:500;\n background-image:linear-gradient(45deg,transparent 50%,var(--text-muted) 50%),\n linear-gradient(135deg,var(--text-muted) 50%,transparent 50%);\n background-position:calc(100% - 14px) 12px,calc(100% - 10px) 12px;\n background-size:4px 4px,4px 4px;background-repeat:no-repeat;\n}\n.sm-inputwrap{\n display:flex;align-items:center;gap:0;height:var(--control-h);padding:0 10px;\n border:1.5px solid transparent;border-radius:var(--radius-md);background:var(--surface-inset);\n}\n.sm-inputwrap>.sm-input{height:auto;padding:0;background:transparent;border:0;box-shadow:none}\n.sm-inputwrap>.sm-input:hover,.sm-inputwrap>.sm-input:focus{background:transparent;box-shadow:none}\n.sm-prefix{color:var(--text-muted);font-family:var(--font-mono);font-size:var(--text-sm);flex:none}\n.sm-error{color:var(--danger);font-size:var(--text-sm)}\n\n.sm-group{display:flex;flex-direction:column;gap:10px;padding:2px 0 0 10px;border-left:2px solid var(--line)}\n.sm-list{display:flex;flex-direction:column;gap:6px;align-items:stretch}\n.sm-listrow{border:1px solid var(--line);border-radius:var(--radius-md);min-width:0}\n.sm-listrow__head{display:flex;align-items:center;gap:2px;height:var(--control-h);padding:0 4px 0 4px}\n.sm-listrow__toggle{\n flex:1;min-width:0;display:flex;align-items:center;gap:6px;height:100%;padding:0 4px;\n border:0;background:transparent;color:var(--text);font:inherit;font-size:var(--text-md);font-weight:500;\n text-align:left;cursor:pointer;\n}\n.sm-listrow__toggle:hover{color:var(--accent)}\n.sm-listrow__summary{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}\n.sm-listrow__body{display:flex;flex-direction:column;gap:10px;padding:8px 10px 10px;border-top:1px solid var(--line)}\n\n/* \u2500\u2500 data \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.sm-table{display:flex;flex-direction:column;padding:0 16px 16px}\n.sm-table__head,.sm-row{display:grid;gap:12px;align-items:center;padding:0 8px}\n.sm-table__head{\n height:34px;border-bottom:var(--border);font-family:var(--font-mono);\n font-size:var(--text-2xs);letter-spacing:var(--tracking-tag);color:var(--text-muted);\n text-transform:uppercase;position:sticky;top:0;background:var(--surface);z-index:1;\n}\n.sm-table__body{display:flex;flex-direction:column;padding-top:4px}\n.sm-row{\n height:var(--row-h);border-radius:var(--radius-md);background:transparent;\n border:0;font:inherit;color:inherit;text-align:left;width:100%;\n}\n.sm-row--clickable{cursor:pointer}\n.sm-row--clickable:hover{background:var(--surface-inset)}\n.sm-cell{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}\n.sm-cell--muted{color:var(--text-muted)}\n.sm-cell--mono{font-family:var(--font-mono);font-size:var(--text-sm);color:var(--text-muted)}\n.sm-cell--title{font-weight:500}\n.sm-empty{padding:28px 8px;color:var(--text-muted)}\n.sm-chevron{\n width:12px;flex:none;border:0;background:none;padding:0;font:inherit;font-size:10px;\n color:var(--text-muted);cursor:pointer;line-height:1;\n}\n.sm-chevron:hover{color:var(--text)}\n.sm-cell--tree{display:flex;align-items:center;gap:6px;font-weight:500}\n.sm-cell--tree.sm-cell--parent{font-weight:600}\n.sm-rowmenu{\n border:0;background:none;padding:0 6px;font:inherit;color:var(--text-muted);cursor:pointer;\n border-radius:var(--radius-md);line-height:1;height:24px;\n}\n.sm-rowmenu:hover{background:var(--surface-inset);color:var(--text)}\n\n.sm-badge{\n display:inline-flex;align-items:center;height:20px;padding:0 8px;border-radius:var(--radius-md);\n font-size:var(--text-xs);font-weight:600;letter-spacing:.04em;white-space:nowrap;\n}\n.sm-savestate{\n display:inline-flex;align-items:center;gap:6px;padding:0 6px;\n font-size:var(--text-xs);font-weight:600;letter-spacing:.06em;text-transform:uppercase;\n}\n.sm-dot{width:5px;height:5px;border-radius:var(--radius-round);flex:none}\n\n/* \u2500\u2500 navigation bits \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.sm-tabs{display:flex;gap:18px;padding:0 14px;height:36px;align-items:stretch;border-bottom:var(--border);flex:none}\n.sm-tab{\n display:flex;align-items:center;border:0;background:transparent;padding:0;font:inherit;\n cursor:pointer;color:var(--text-muted);border-bottom:2px solid transparent;margin-bottom:-1px;\n}\n.sm-tab:hover{color:var(--text)}\n.sm-tab[aria-selected=\"true\"]{color:var(--accent);font-weight:600;border-bottom-color:var(--accent)}\n.sm-seg{display:flex;gap:2px;padding:2px;background:var(--surface-inset);border-radius:var(--radius-md)}\n.sm-seg--stretch{width:100%}\n.sm-seg--disabled{opacity:.6}\n.sm-seg__opt:disabled{cursor:default}\n.sm-seg__opt{\n flex:1;padding:3px 10px;border:0;border-radius:var(--radius-sm);background:transparent;\n color:var(--text-muted);font:inherit;cursor:pointer;white-space:nowrap;\n}\n.sm-seg__opt:hover{color:var(--text)}\n.sm-seg__opt[aria-pressed=\"true\"]{background:var(--surface);color:var(--text);font-weight:600;box-shadow:var(--shadow-seg)}\n.sm-divider{height:1px;background:var(--line);flex:none}\n.sm-sectionlabel{display:flex;align-items:center;justify-content:space-between;gap:8px}\n.sm-sectionlabel--mono>.sm-label{\n font-family:var(--font-mono);font-weight:400;letter-spacing:var(--tracking-tag);\n}\n.sm-banner{\n display:flex;gap:14px;align-items:flex-start;padding:10px 14px;border-radius:var(--radius-md);\n font-size:var(--text-md);\n}\n.sm-banner--error{background:var(--danger-soft);color:var(--danger)}\n.sm-banner--ok{background:var(--status-published-bg);color:var(--status-published-fg)}\n.sm-banner--warn{background:var(--warn-soft);color:var(--warn)}\n\n/* \u2500\u2500 editor \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.sm-editor{flex:1;min-height:0;display:grid;grid-template-columns:1fr var(--inspector-w)}\n.sm-canvas{display:flex;flex-direction:column;background:var(--bg);min-width:0;min-height:0;overflow:hidden}\n.sm-canvas__meta{\n display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 20px;flex:none;\n font-family:var(--font-mono);font-size:var(--text-xs);letter-spacing:.04em;color:var(--text-muted);\n}\n.sm-canvas__scroll{flex:1;min-height:0;overflow:auto;padding:0 20px 20px}\n.sm-page{\n position:relative;background:var(--page-bg);border-radius:var(--radius-md);\n box-shadow:var(--shadow-page);min-height:100%;\n}\n.sm-inspector{\n display:flex;flex-direction:column;min-height:0;overflow:hidden;\n border-left:var(--border);background:var(--surface);\n}\n.sm-inspector__head{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:12px 14px 10px;flex:none}\n.sm-inspector__body{display:flex;flex-direction:column;gap:14px;padding:14px;overflow:auto}\n.sm-blockglyph{width:14px;height:10px;border:1.5px solid var(--text-muted);border-radius:var(--radius-xs);flex:none}\n.sm-blocktag{\n position:absolute;top:0;left:0;background:var(--selection);color:#fff;\n font-family:var(--font-mono);font-size:var(--text-2xs);font-weight:700;\n letter-spacing:var(--tracking-tag);text-transform:uppercase;padding:3px 7px;\n border-radius:var(--radius-sm) 0 var(--radius-sm) 0;white-space:nowrap;\n}\n.sm-blocktoolbar{\n display:flex;gap:2px;padding:2px;background:#15181F;border-radius:var(--radius-md);\n box-shadow:var(--shadow-float);\n}\n.sm-plus{\n width:24px;height:24px;border-radius:var(--radius-round);border:0;background:var(--selection);\n color:#fff;display:grid;place-items:center;font-size:15px;line-height:1;cursor:pointer;\n box-shadow:0 2px 6px rgba(0,56,177,.35);padding:0;\n}\n.sm-plus:hover{background:var(--brand-500)}\n.sm-guide{height:1px;background:color-mix(in srgb,var(--selection) 35%,transparent)}\n.sm-tooltip{\n background:#15181F;color:#EEF0F5;font-size:var(--text-sm);padding:4px 8px;\n border-radius:var(--radius-sm);white-space:nowrap;\n}\n.sm-outline-row{\n display:flex;align-items:center;justify-content:space-between;gap:4px;\n height:var(--control-h);border-radius:var(--radius-md);padding:0 4px 0 10px;\n color:var(--text);font-weight:500;background:transparent;\n border:1px solid var(--line);\n}\n.sm-outline-row--clickable{cursor:pointer}\n.sm-outline-row--clickable:hover{background:var(--surface-inset)}\n/* Selected and locked rows are read by their fill, so they drop the hairline \u2014\n border-color, not border, so every row keeps the same 28px box. */\n.sm-outline-row--selected,.sm-outline-row--selected:hover{\n background:var(--nav-active-bg);color:var(--nav-active-fg);font-weight:600;border-color:transparent;\n}\n.sm-outline-row--empty{border:1px dashed var(--line-dashed);color:var(--text-muted);padding:0 10px}\n.sm-outline-row--locked{background:var(--surface-inset);color:var(--text-muted);padding:0 10px;border-color:transparent}\n.sm-outline-row__label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}\n\n.sm-picker{\n width:300px;background:var(--surface);border:var(--border);border-radius:var(--radius-md);\n padding:6px;box-shadow:var(--shadow-pop);\n}\n.sm-picker__item{\n display:flex;gap:10px;align-items:center;width:100%;text-align:left;background:transparent;\n border:0;border-radius:var(--radius-md);padding:8px 8px;cursor:pointer;color:var(--text);font:inherit;\n}\n.sm-picker__item:hover{background:var(--nav-active-bg)}\n.sm-picker__icon{\n width:26px;height:26px;flex:none;border-radius:var(--radius-sm);background:var(--accent-soft);\n color:var(--accent);display:grid;place-items:center;font-size:13px;\n}\n.sm-dashed{\n border:1px dashed var(--line-dashed);border-radius:var(--radius-md);\n display:flex;align-items:center;justify-content:center;gap:8px;color:var(--text-muted);\n}\n";
|
|
1
|
+
export declare const ADMIN_CSS = "\n@import url(\"https://fonts.googleapis.com/css2?family=Public+Sans:wght@400;500;600;700&display=swap\");\n\n.sm-admin{\n --font-sans:'Public Sans',-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;\n --font-mono:ui-monospace,Menlo,Consolas,monospace;\n --text-2xs:9px; --text-xs:10px; --text-sm:11px; --text-md:12px;\n --text-lg:13px; --text-xl:14px; --text-2xl:15px; --text-3xl:20px; --text-4xl:30px;\n --tracking-tight:-0.02em; --tracking-label:0.1em; --tracking-tag:0.08em;\n\n --radius-xs:1px; --radius-sm:3px; --radius-md:4px; --radius-round:999px;\n --control-h:28px; --control-h-lg:36px; --row-h:38px; --topbar-h:48px;\n --nav-w:72px; --inspector-w:380px;\n --border:1px solid var(--line);\n --outline-selection:1.5px solid var(--selection);\n\n --brand-600:#0038B1; --brand-500:#2554D6; --brand-300:#6E96FF;\n --brand-200:#A9C0FF; --brand-100:#DCE4F9; --brand-50:#E3EAFA;\n --gray-0:#FFFFFF; --gray-50:#F1F2F6; --gray-100:#E1E4EC; --gray-300:#C9D0E2;\n --gray-500:#8B93A7; --gray-600:#6B7386; --gray-700:#4A5165; --gray-900:#0F1420;\n --green-700:#1F7A46; --green-50:#E3F3EA; --green-300:#6FD39A; --green-500:#2E9E5B;\n --amber-700:#8A5A00; --amber-50:#FBF1DC; --amber-300:#E8B85A;\n --red-700:#B42318; --red-50:#FDECEC; --red-300:#F5A5A0;\n\n --bg:var(--gray-50); --surface:var(--gray-0); --surface-inset:var(--gray-50);\n --input-bg:var(--gray-0); --line:var(--gray-100); --line-dashed:var(--gray-300);\n --text:var(--gray-900); --text-muted:var(--gray-600); --text-subtle:var(--gray-500);\n --accent:var(--brand-600); --on-accent:#FFFFFF; --accent-soft:var(--brand-50);\n --selection:var(--brand-600); --nav-active-bg:var(--brand-100); --nav-active-fg:var(--brand-600);\n --hover:rgba(15,20,32,.045); --press:rgba(15,20,32,.08);\n --control-hover:var(--gray-100);\n\n --status-published-fg:var(--green-700); --status-published-bg:var(--green-50);\n --status-draft-fg:var(--amber-700); --status-draft-bg:var(--amber-50);\n --status-scheduled-fg:var(--accent); --status-scheduled-bg:var(--accent-soft);\n --status-saved-dot:var(--green-500);\n --warn:var(--amber-700); --warn-soft:var(--amber-50);\n --danger:var(--red-700); --danger-soft:var(--red-50);\n\n --page-bg:#FFFFFF; --page-text:var(--gray-900);\n --shadow-page:0 1px 2px rgba(0,0,0,.08), 0 8px 24px rgba(0,0,0,.06);\n --shadow-float:0 2px 8px rgba(0,0,0,.25);\n --shadow-seg:0 1px 2px rgba(0,0,0,.08);\n --focus-ring:0 0 0 3px var(--accent-soft);\n --shadow-pop:0 8px 32px rgba(0,0,0,.18);\n --logo-filter:none;\n\n /* Which half of the theme toggle is visible. A token, so the icon can never\n disagree with the colors \u2014 both follow the one cascade below. */\n --when-light:inline-flex; --when-dark:none;\n}\nhtml[data-theme=\"dark\"] .sm-admin,.sm-admin[data-theme=\"dark\"]{\n --bg:#0D0F14; --surface:#15181F; --surface-inset:#0D0F14; --input-bg:#1B1F28;\n --line:#2A2F3B; --line-dashed:#2A2F3B;\n --text:#EEF0F5; --text-muted:#8C94A8; --text-subtle:#6B7386;\n --accent:var(--brand-300); --on-accent:#06122F; --accent-soft:rgba(110,150,255,.16);\n --nav-active-bg:rgba(110,150,255,.14); --nav-active-fg:var(--brand-200);\n --hover:rgba(238,240,245,.05); --press:rgba(238,240,245,.09);\n --control-hover:var(--input-bg);\n\n --status-published-fg:var(--green-300); --status-published-bg:rgba(111,211,154,.14);\n --status-draft-fg:var(--amber-300); --status-draft-bg:rgba(232,184,90,.14);\n --warn:var(--amber-300); --warn-soft:rgba(232,184,90,.14);\n --danger:var(--red-300); --danger-soft:rgba(244,102,92,.14);\n\n --shadow-page:0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.35);\n --shadow-pop:0 8px 32px rgba(0,0,0,.5);\n --logo-filter:brightness(0) invert(1);\n --when-light:none; --when-dark:inline-flex;\n}\n@media (prefers-color-scheme:dark){\n html:not([data-theme=\"light\"]) .sm-admin:not([data-theme=\"light\"]){\n --bg:#0D0F14; --surface:#15181F; --surface-inset:#0D0F14; --input-bg:#1B1F28;\n --line:#2A2F3B; --line-dashed:#2A2F3B;\n --text:#EEF0F5; --text-muted:#8C94A8; --text-subtle:#6B7386;\n --accent:var(--brand-300); --on-accent:#06122F; --accent-soft:rgba(110,150,255,.16);\n --nav-active-bg:rgba(110,150,255,.14); --nav-active-fg:var(--brand-200);\n --hover:rgba(238,240,245,.05); --press:rgba(238,240,245,.09);\n --control-hover:var(--input-bg);\n\n --status-published-fg:var(--green-300); --status-published-bg:rgba(111,211,154,.14);\n --status-draft-fg:var(--amber-300); --status-draft-bg:rgba(232,184,90,.14);\n --warn:var(--amber-300); --warn-soft:rgba(232,184,90,.14);\n --danger:var(--red-300); --danger-soft:rgba(244,102,92,.14);\n\n --shadow-page:0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.35);\n --shadow-pop:0 8px 32px rgba(0,0,0,.5);\n --logo-filter:brightness(0) invert(1);\n --when-light:none; --when-dark:inline-flex;\n}\n}\n/* Last, and the same specificity as the host-attribute rule: an explicit\n choice from the theme toggle wins over a dark <html> and over the OS. */\n.sm-admin[data-theme=\"light\"]{\n --brand-600:#0038B1; --brand-500:#2554D6; --brand-300:#6E96FF;\n --brand-200:#A9C0FF; --brand-100:#DCE4F9; --brand-50:#E3EAFA;\n --gray-0:#FFFFFF; --gray-50:#F1F2F6; --gray-100:#E1E4EC; --gray-300:#C9D0E2;\n --gray-500:#8B93A7; --gray-600:#6B7386; --gray-700:#4A5165; --gray-900:#0F1420;\n --green-700:#1F7A46; --green-50:#E3F3EA; --green-300:#6FD39A; --green-500:#2E9E5B;\n --amber-700:#8A5A00; --amber-50:#FBF1DC; --amber-300:#E8B85A;\n --red-700:#B42318; --red-50:#FDECEC; --red-300:#F5A5A0;\n\n --bg:var(--gray-50); --surface:var(--gray-0); --surface-inset:var(--gray-50);\n --input-bg:var(--gray-0); --line:var(--gray-100); --line-dashed:var(--gray-300);\n --text:var(--gray-900); --text-muted:var(--gray-600); --text-subtle:var(--gray-500);\n --accent:var(--brand-600); --on-accent:#FFFFFF; --accent-soft:var(--brand-50);\n --selection:var(--brand-600); --nav-active-bg:var(--brand-100); --nav-active-fg:var(--brand-600);\n --hover:rgba(15,20,32,.045); --press:rgba(15,20,32,.08);\n --control-hover:var(--gray-100);\n\n --status-published-fg:var(--green-700); --status-published-bg:var(--green-50);\n --status-draft-fg:var(--amber-700); --status-draft-bg:var(--amber-50);\n --status-scheduled-fg:var(--accent); --status-scheduled-bg:var(--accent-soft);\n --status-saved-dot:var(--green-500);\n --warn:var(--amber-700); --warn-soft:var(--amber-50);\n --danger:var(--red-700); --danger-soft:var(--red-50);\n\n --page-bg:#FFFFFF; --page-text:var(--gray-900);\n --shadow-page:0 1px 2px rgba(0,0,0,.08), 0 8px 24px rgba(0,0,0,.06);\n --shadow-float:0 2px 8px rgba(0,0,0,.25);\n --shadow-seg:0 1px 2px rgba(0,0,0,.08);\n --focus-ring:0 0 0 3px var(--accent-soft);\n --shadow-pop:0 8px 32px rgba(0,0,0,.18);\n --logo-filter:none;\n\n /* Which half of the theme toggle is visible. A token, so the icon can never\n disagree with the colors \u2014 both follow the one cascade below. */\n --when-light:inline-flex; --when-dark:none;\n}\n\n/* The admin owns the viewport; a host <body> margin would push it into a\n page scrollbar. Scoped by :has, so only a page holding the admin is touched. */\nbody:has(.sm-admin){margin:0}\n.sm-admin,.sm-admin *,.sm-admin *::before,.sm-admin *::after{box-sizing:border-box}\n.sm-admin{\n font-family:var(--font-sans);font-size:var(--text-md);line-height:1.4;\n color:var(--text);background:var(--surface);\n -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;\n}\n.sm-admin h1,.sm-admin h2,.sm-admin h3,.sm-admin p,.sm-admin ul{margin:0}\n/* :where() keeps this at zero specificity \u2014 a bare <a> is accent, but any\n component class on it (.sm-nav-item, .sm-btn, .sm-back) decides its own\n colour. Without this the element selector silently beat every one of them. */\n:where(.sm-admin a){color:var(--accent);text-decoration:none}\n:where(.sm-admin a:hover){color:var(--brand-500)}\n.sm-admin button,.sm-admin input,.sm-admin select,.sm-admin textarea{font-family:inherit}\n.sm-admin ::placeholder{color:var(--text-subtle)}\n.sm-admin ::selection{background:var(--accent-soft);color:var(--text)}\n.sm-mono{font-family:var(--font-mono)}\n\n/* \u2500\u2500 shell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.sm-shell{display:grid;grid-template-columns:var(--nav-w) 1fr;height:100vh;height:100dvh;overflow:hidden}\n.sm-shell>.sm-main{min-width:0;min-height:0;display:flex;flex-direction:column;overflow:hidden}\n.sm-wordmark{height:18px;width:auto;filter:var(--logo-filter);flex:none}\n/* The design system's IconRail (components/navigation/IconRail.html),\n ported as-is: a 72px rail, 18px Lucide icons over a 10px label, a\n 40\u00D732 active tile, the app icon on top and the avatar at the bottom. */\n.sm-rail{\n width:var(--nav-w);height:100%;display:flex;flex-direction:column;align-items:center;gap:14px;\n padding:14px 6px 12px;background:var(--surface);border-right:var(--border);color:var(--text);\n font:500 10px/1 var(--font-sans);position:relative;\n}\n.sm-rail__logo{width:28px;height:28px;display:block;flex:none}\n.sm-rail__list{display:flex;flex-direction:column;gap:6px;width:100%;margin:0;padding:0;list-style:none}\n.sm-rail__item{position:relative}\n.sm-rail__btn{\n appearance:none;background:none;border:0;margin:0;padding:2px 0;width:100%;display:flex;flex-direction:column;\n align-items:center;gap:5px;color:inherit;font:inherit;cursor:pointer;border-radius:var(--radius-md);text-decoration:none;\n}\n.sm-rail__btn:hover{color:inherit}\n.sm-rail__btn:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;box-shadow:none}\n.sm-rail__tile{position:relative;width:40px;height:32px;border-radius:var(--radius-md);display:grid;place-items:center;transition:background .12s}\n.sm-rail__btn:hover .sm-rail__tile{background:var(--surface-inset)}\n.sm-rail__btn[aria-current=\"page\"],.sm-rail__btn[aria-expanded=\"true\"]{color:var(--nav-active-fg);font-weight:600}\n.sm-rail__btn[aria-current=\"page\"]:hover,.sm-rail__btn[aria-expanded=\"true\"]:hover{color:var(--nav-active-fg)}\n.sm-rail__btn[aria-current=\"page\"] .sm-rail__tile,.sm-rail__btn[aria-expanded=\"true\"] .sm-rail__tile{background:var(--nav-active-bg)}\n.sm-rail__tile svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round}\n.sm-rail__chev{position:absolute;right:3px;bottom:2px;width:8px;height:8px}\n.sm-rail__chev svg{width:8px;height:8px;stroke-width:3}\n.sm-rail__label{letter-spacing:.01em;white-space:nowrap}\n/* Not greyed: the reference renders every inactive item at --text and marks\n the unavailable ones only by being inert (no pointer, no hover). */\n.sm-rail__btn[aria-disabled=\"true\"]{cursor:default}\n.sm-rail__btn[aria-disabled=\"true\"]:hover .sm-rail__tile{background:transparent}\n.sm-rail__foot{margin-top:auto;display:flex;flex-direction:column;align-items:center}\n.sm-rail__user{\n width:26px;height:26px;border-radius:var(--radius-round);background:var(--accent-soft);color:var(--accent);\n display:grid;place-items:center;font-size:var(--text-sm);font-weight:600;border:0;cursor:pointer;padding:0;\n}\n.sm-rail__user:focus-visible{outline:2px solid var(--accent);outline-offset:1px;box-shadow:none}\n/* Flyout: 6px right of the rail, aligned with its item (the user menu aligns to the bottom). */\n.sm-rail__menu{\n position:absolute;left:calc(100% + 6px);top:0;z-index:20;width:168px;padding:6px;margin:0;list-style:none;\n background:var(--surface);border:var(--border);border-radius:var(--radius-md);box-shadow:0 4px 20px rgba(0,0,0,.14);\n display:none;flex-direction:column;gap:1px;font-size:var(--text-md);font-weight:500;color:var(--text);\n}\n.sm-rail__menu[data-open=\"true\"]{display:flex}\n.sm-rail__menu--up{top:auto;bottom:0}\n.sm-rail__menu-title{font-size:var(--text-2xs);font-weight:600;letter-spacing:var(--tracking-label);text-transform:uppercase;color:var(--text-muted);padding:6px 8px 4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.sm-rail__menu-item{\n appearance:none;background:none;border:0;width:100%;display:flex;align-items:center;justify-content:space-between;\n padding:6px 8px;border-radius:var(--radius-sm);color:inherit;font:inherit;cursor:pointer;text-align:left;text-decoration:none;\n}\n.sm-rail__menu-item:hover,.sm-rail__menu-item:focus-visible{background:var(--surface-inset);color:inherit;outline:0;box-shadow:none}\n.sm-rail__menu-item[aria-current=\"page\"],.sm-rail__menu-item[aria-current=\"page\"]:hover{background:var(--nav-active-bg);color:var(--nav-active-fg);font-weight:600}\n.sm-when-light{display:var(--when-light);align-items:center;gap:8px}\n.sm-when-dark{display:var(--when-dark);align-items:center;gap:8px}\n/* .sm-nav-item survives as the inspector's link style (EditorView). */\n.sm-nav-item{\n display:block;padding:6px 10px;border-radius:var(--radius-md);line-height:1.3;\n color:var(--text);font-weight:500;text-decoration:none;cursor:pointer;\n background:transparent;border:0;width:100%;text-align:left;font-size:var(--text-md);\n overflow:hidden;text-overflow:ellipsis;white-space:nowrap;\n}\n.sm-nav-item:hover{background:var(--surface-inset);color:var(--text)}\n\n/* \u2500\u2500 top bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.sm-topbar{\n height:var(--topbar-h);flex:none;display:flex;align-items:center;justify-content:space-between;\n gap:16px;padding:0 16px;border-bottom:var(--border);background:var(--surface);\n}\n.sm-topbar__left{display:flex;align-items:center;gap:10px;min-width:0}\n.sm-topbar__right{display:flex;align-items:center;gap:8px;flex:none}\n.sm-crumbs{display:flex;align-items:center;gap:8px;color:var(--text-muted);min-width:0}\n.sm-crumbs__sep{opacity:.5}\n.sm-crumbs__here{color:var(--text);font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.sm-back{border:0;background:transparent;padding:0;color:var(--text);font:inherit;cursor:pointer;line-height:1}\n.sm-back:hover{color:var(--accent)}\n.sm-title{font-size:var(--text-xl);font-weight:600}\n.sm-count{color:var(--text-muted)}\n\n/* \u2500\u2500 buttons \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.sm-btn{\n display:inline-flex;align-items:center;justify-content:center;gap:6px;\n height:var(--control-h);padding:0 12px;border-radius:var(--radius-md);border:0;\n font-size:var(--text-md);font-weight:500;line-height:1;white-space:nowrap;cursor:pointer;\n text-decoration:none;\n}\n.sm-btn--lg{height:var(--control-h-lg);padding:0 16px}\n.sm-btn--primary{background:var(--accent);color:var(--on-accent);font-weight:600;padding:0 14px}\n.sm-btn--primary:hover{background:var(--brand-500);color:var(--on-accent)}\n.sm-btn--secondary{background:var(--surface-inset);color:var(--text)}\n.sm-btn--secondary:hover{background:var(--control-hover);color:var(--text)}\n.sm-btn--ghost{background:transparent;color:var(--accent);font-weight:600;padding:0 8px}\n.sm-btn--ghost:hover{background:var(--accent-soft);color:var(--accent)}\n.sm-btn--danger{background:var(--surface-inset);color:var(--danger)}\n.sm-btn--danger:hover{background:var(--danger-soft);color:var(--danger)}\n.sm-btn--ok{background:var(--status-published-bg);color:var(--status-published-fg);font-weight:600;padding:0 14px}\n.sm-btn:disabled,.sm-btn[aria-disabled=\"true\"]{opacity:.5;cursor:default;pointer-events:none}\n.sm-btn:focus-visible,.sm-iconbtn:focus-visible,.sm-nav-item:focus-visible,\n.sm-tab:focus-visible,.sm-seg__opt:focus-visible,.sm-row:focus-visible{\n outline:2px solid var(--accent);outline-offset:1px\n}\n\n.sm-iconbtn{\n width:24px;height:24px;flex:none;display:grid;place-items:center;padding:0;\n border:0;border-radius:var(--radius-sm);background:transparent;color:var(--text-muted);\n font-size:12px;line-height:1;cursor:pointer;\n}\n.sm-iconbtn:hover{background:var(--hover);color:var(--text)}\n.sm-iconbtn--inverse{color:#EEF0F5}\n.sm-iconbtn--inverse:hover{background:rgba(255,255,255,.14);color:#fff}\n.sm-iconbtn:disabled{opacity:.3;cursor:default}\n.sm-iconbtn:disabled:hover{background:transparent}\n\n/* \u2500\u2500 fields \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.sm-field{display:flex;flex-direction:column;gap:6px;min-width:0}\n.sm-field__head{display:flex;align-items:baseline;justify-content:space-between;gap:8px}\n.sm-label{\n font-size:var(--text-2xs);font-weight:600;letter-spacing:var(--tracking-label);\n text-transform:uppercase;color:var(--text-muted);\n}\n.sm-counter,.sm-hint{font-size:var(--text-sm);color:var(--text-muted)}\n.sm-input,.sm-textarea,.sm-select{\n width:100%;min-width:0;border:1.5px solid transparent;border-radius:var(--radius-md);\n background:var(--surface-inset);color:var(--text);font-size:var(--text-md);\n outline:0;box-shadow:none;\n}\n.sm-input,.sm-select{height:var(--control-h);padding:0 10px}\n.sm-textarea{padding:8px 10px;line-height:1.5;resize:vertical;min-height:60px}\n.sm-input--lg{height:var(--control-h-lg);font-size:var(--text-2xl);font-weight:600}\n.sm-input--mono{font-family:var(--font-mono);font-size:var(--text-sm)}\n.sm-input:hover,.sm-textarea:hover,.sm-select:hover{background:var(--control-hover)}\n.sm-input:focus,.sm-textarea:focus,.sm-select:focus,\n.sm-inputwrap:focus-within{\n background:var(--input-bg);border-color:var(--selection);box-shadow:var(--focus-ring);\n}\n.sm-input:disabled,.sm-textarea:disabled,.sm-select:disabled{opacity:.6;cursor:default}\n.sm-select{\n appearance:none;-webkit-appearance:none;cursor:pointer;padding-right:26px;font-weight:500;\n background-image:linear-gradient(45deg,transparent 50%,var(--text-muted) 50%),\n linear-gradient(135deg,var(--text-muted) 50%,transparent 50%);\n background-position:calc(100% - 14px) 12px,calc(100% - 10px) 12px;\n background-size:4px 4px,4px 4px;background-repeat:no-repeat;\n}\n.sm-inputwrap{\n display:flex;align-items:center;gap:0;height:var(--control-h);padding:0 10px;\n border:1.5px solid transparent;border-radius:var(--radius-md);background:var(--surface-inset);\n}\n.sm-inputwrap>.sm-input{height:auto;padding:0;background:transparent;border:0;box-shadow:none}\n.sm-inputwrap>.sm-input:hover,.sm-inputwrap>.sm-input:focus{background:transparent;box-shadow:none}\n.sm-prefix{color:var(--text-muted);font-family:var(--font-mono);font-size:var(--text-sm);flex:none}\n.sm-error{color:var(--danger);font-size:var(--text-sm)}\n/* \u2500\u2500 richtext \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.sm-richtext{display:flex;flex-direction:column;gap:6px;min-width:0}\n.sm-richtext__bar{display:flex;flex-wrap:wrap;align-items:center;gap:2px}\n.sm-richtext__sep{width:1px;height:16px;margin:0 4px;background:var(--line)}\n.sm-richtext__bar .sm-iconbtn[aria-pressed=\"true\"]{background:var(--press);color:var(--text)}\n.sm-richtext__link{display:flex;align-items:center;gap:6px}\n.sm-richtext__content{\n min-height:110px;padding:8px 10px;border:1.5px solid transparent;border-radius:var(--radius-md);\n background:var(--surface-inset);color:var(--text);font-size:var(--text-md);line-height:1.5;outline:0;\n}\n.sm-richtext__content:hover{background:var(--control-hover)}\n.sm-richtext__content:focus{background:var(--input-bg);border-color:var(--selection);box-shadow:var(--focus-ring)}\n.sm-richtext__content>*+*{margin-top:.5em}\n.sm-richtext__content p,.sm-richtext__content h1,.sm-richtext__content h2,.sm-richtext__content h3,\n.sm-richtext__content h4,.sm-richtext__content h5,.sm-richtext__content h6,\n.sm-richtext__content ul,.sm-richtext__content ol,.sm-richtext__content blockquote{margin:0}\n.sm-richtext__content h1,.sm-richtext__content h2,.sm-richtext__content h3,\n.sm-richtext__content h4,.sm-richtext__content h5,.sm-richtext__content h6{font-weight:600;line-height:1.25}\n.sm-richtext__content h1{font-size:var(--text-3xl)}\n.sm-richtext__content h2{font-size:var(--text-2xl)}\n.sm-richtext__content h3{font-size:var(--text-xl)}\n.sm-richtext__content h4,.sm-richtext__content h5,.sm-richtext__content h6{font-size:var(--text-lg)}\n.sm-richtext__content ul,.sm-richtext__content ol{padding-left:18px}\n.sm-richtext__content li+li{margin-top:.25em}\n.sm-richtext__content blockquote{padding-left:10px;border-left:2px solid var(--line);color:var(--text-muted)}\n.sm-richtext__content a{color:var(--accent);text-decoration:underline}\n\n.sm-group{display:flex;flex-direction:column;gap:10px;padding:2px 0 0 10px;border-left:2px solid var(--line)}\n.sm-list{display:flex;flex-direction:column;gap:6px;align-items:stretch}\n.sm-listrow{border:1px solid var(--line);border-radius:var(--radius-md);min-width:0}\n.sm-listrow__head{display:flex;align-items:center;gap:2px;height:var(--control-h);padding:0 4px 0 4px}\n.sm-listrow__toggle{\n flex:1;min-width:0;display:flex;align-items:center;gap:6px;height:100%;padding:0 4px;\n border:0;background:transparent;color:var(--text);font:inherit;font-size:var(--text-md);font-weight:500;\n text-align:left;cursor:pointer;\n}\n.sm-listrow__toggle:hover{color:var(--accent)}\n.sm-listrow__summary{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}\n.sm-listrow__body{display:flex;flex-direction:column;gap:10px;padding:8px 10px 10px;border-top:1px solid var(--line)}\n\n/* \u2500\u2500 data \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.sm-table{display:flex;flex-direction:column;padding:0 16px 16px}\n.sm-table__head,.sm-row{display:grid;gap:12px;align-items:center;padding:0 8px}\n.sm-table__head{\n height:34px;border-bottom:var(--border);font-family:var(--font-mono);\n font-size:var(--text-2xs);letter-spacing:var(--tracking-tag);color:var(--text-muted);\n text-transform:uppercase;position:sticky;top:0;background:var(--surface);z-index:1;\n}\n.sm-table__body{display:flex;flex-direction:column;padding-top:4px}\n.sm-row{\n height:var(--row-h);border-radius:var(--radius-md);background:transparent;\n border:0;font:inherit;color:inherit;text-align:left;width:100%;\n}\n.sm-row--clickable{cursor:pointer}\n.sm-row--clickable:hover{background:var(--surface-inset)}\n.sm-cell{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}\n.sm-cell--muted{color:var(--text-muted)}\n.sm-cell--mono{font-family:var(--font-mono);font-size:var(--text-sm);color:var(--text-muted)}\n.sm-cell--title{font-weight:500}\n.sm-empty{padding:28px 8px;color:var(--text-muted)}\n.sm-chevron{\n width:12px;flex:none;border:0;background:none;padding:0;font:inherit;font-size:10px;\n color:var(--text-muted);cursor:pointer;line-height:1;\n}\n.sm-chevron:hover{color:var(--text)}\n.sm-cell--tree{display:flex;align-items:center;gap:6px;font-weight:500}\n.sm-cell--tree.sm-cell--parent{font-weight:600}\n.sm-rowmenu{\n border:0;background:none;padding:0 6px;font:inherit;color:var(--text-muted);cursor:pointer;\n border-radius:var(--radius-md);line-height:1;height:24px;\n}\n.sm-rowmenu:hover{background:var(--surface-inset);color:var(--text)}\n\n.sm-badge{\n display:inline-flex;align-items:center;height:20px;padding:0 8px;border-radius:var(--radius-md);\n font-size:var(--text-xs);font-weight:600;letter-spacing:.04em;white-space:nowrap;\n}\n.sm-savestate{\n display:inline-flex;align-items:center;gap:6px;padding:0 6px;\n font-size:var(--text-xs);font-weight:600;letter-spacing:.06em;text-transform:uppercase;\n}\n.sm-dot{width:5px;height:5px;border-radius:var(--radius-round);flex:none}\n\n/* \u2500\u2500 navigation bits \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.sm-tabs{display:flex;gap:18px;padding:0 14px;height:36px;align-items:stretch;border-bottom:var(--border);flex:none}\n.sm-tab{\n display:flex;align-items:center;border:0;background:transparent;padding:0;font:inherit;\n cursor:pointer;color:var(--text-muted);border-bottom:2px solid transparent;margin-bottom:-1px;\n}\n.sm-tab:hover{color:var(--text)}\n.sm-tab[aria-selected=\"true\"]{color:var(--accent);font-weight:600;border-bottom-color:var(--accent)}\n.sm-seg{display:flex;gap:2px;padding:2px;background:var(--surface-inset);border-radius:var(--radius-md)}\n.sm-seg--stretch{width:100%}\n.sm-seg--disabled{opacity:.6}\n.sm-seg__opt:disabled{cursor:default}\n.sm-seg__opt{\n flex:1;padding:3px 10px;border:0;border-radius:var(--radius-sm);background:transparent;\n color:var(--text-muted);font:inherit;cursor:pointer;white-space:nowrap;\n}\n.sm-seg__opt:hover{color:var(--text)}\n.sm-seg__opt[aria-pressed=\"true\"]{background:var(--surface);color:var(--text);font-weight:600;box-shadow:var(--shadow-seg)}\n.sm-divider{height:1px;background:var(--line);flex:none}\n.sm-sectionlabel{display:flex;align-items:center;justify-content:space-between;gap:8px}\n.sm-sectionlabel--mono>.sm-label{\n font-family:var(--font-mono);font-weight:400;letter-spacing:var(--tracking-tag);\n}\n.sm-banner{\n display:flex;gap:14px;align-items:flex-start;padding:10px 14px;border-radius:var(--radius-md);\n font-size:var(--text-md);\n}\n.sm-banner--error{background:var(--danger-soft);color:var(--danger)}\n.sm-banner--ok{background:var(--status-published-bg);color:var(--status-published-fg)}\n.sm-banner--warn{background:var(--warn-soft);color:var(--warn)}\n\n/* \u2500\u2500 editor \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.sm-editor{flex:1;min-height:0;display:grid;grid-template-columns:1fr var(--inspector-w)}\n.sm-canvas{display:flex;flex-direction:column;background:var(--bg);min-width:0;min-height:0;overflow:hidden}\n.sm-canvas__meta{\n display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 20px;flex:none;\n font-family:var(--font-mono);font-size:var(--text-xs);letter-spacing:.04em;color:var(--text-muted);\n}\n.sm-canvas__scroll{flex:1;min-height:0;overflow:auto;padding:0 20px 20px}\n.sm-page{\n position:relative;background:var(--page-bg);border-radius:var(--radius-md);\n box-shadow:var(--shadow-page);min-height:100%;\n}\n.sm-inspector{\n display:flex;flex-direction:column;min-height:0;overflow:hidden;\n border-left:var(--border);background:var(--surface);\n}\n.sm-inspector__head{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:12px 14px 10px;flex:none}\n.sm-inspector__body{display:flex;flex-direction:column;gap:14px;padding:14px;overflow:auto}\n.sm-blockglyph{width:14px;height:10px;border:1.5px solid var(--text-muted);border-radius:var(--radius-xs);flex:none}\n.sm-blocktag{\n position:absolute;top:0;left:0;background:var(--selection);color:#fff;\n font-family:var(--font-mono);font-size:var(--text-2xs);font-weight:700;\n letter-spacing:var(--tracking-tag);text-transform:uppercase;padding:3px 7px;\n border-radius:var(--radius-sm) 0 var(--radius-sm) 0;white-space:nowrap;\n}\n.sm-blocktoolbar{\n display:flex;gap:2px;padding:2px;background:#15181F;border-radius:var(--radius-md);\n box-shadow:var(--shadow-float);\n}\n.sm-plus{\n width:24px;height:24px;border-radius:var(--radius-round);border:0;background:var(--selection);\n color:#fff;display:grid;place-items:center;font-size:15px;line-height:1;cursor:pointer;\n box-shadow:0 2px 6px rgba(0,56,177,.35);padding:0;\n}\n.sm-plus:hover{background:var(--brand-500)}\n.sm-guide{height:1px;background:color-mix(in srgb,var(--selection) 35%,transparent)}\n.sm-tooltip{\n background:#15181F;color:#EEF0F5;font-size:var(--text-sm);padding:4px 8px;\n border-radius:var(--radius-sm);white-space:nowrap;\n}\n.sm-outline-row{\n display:flex;align-items:center;justify-content:space-between;gap:4px;\n height:var(--control-h);border-radius:var(--radius-md);padding:0 4px 0 10px;\n color:var(--text);font-weight:500;background:transparent;\n border:1px solid var(--line);\n}\n.sm-outline-row--clickable{cursor:pointer}\n.sm-outline-row--clickable:hover{background:var(--surface-inset)}\n/* Selected and locked rows are read by their fill, so they drop the hairline \u2014\n border-color, not border, so every row keeps the same 28px box. */\n.sm-outline-row--selected,.sm-outline-row--selected:hover{\n background:var(--nav-active-bg);color:var(--nav-active-fg);font-weight:600;border-color:transparent;\n}\n.sm-outline-row--empty{border:1px dashed var(--line-dashed);color:var(--text-muted);padding:0 10px}\n.sm-outline-row--locked{background:var(--surface-inset);color:var(--text-muted);padding:0 10px;border-color:transparent}\n.sm-outline-row__label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}\n\n.sm-picker{\n width:300px;background:var(--surface);border:var(--border);border-radius:var(--radius-md);\n padding:6px;box-shadow:var(--shadow-pop);\n}\n.sm-picker__item{\n display:flex;gap:10px;align-items:center;width:100%;text-align:left;background:transparent;\n border:0;border-radius:var(--radius-md);padding:8px 8px;cursor:pointer;color:var(--text);font:inherit;\n}\n.sm-picker__item:hover{background:var(--nav-active-bg)}\n.sm-picker__icon{\n width:26px;height:26px;flex:none;border-radius:var(--radius-sm);background:var(--accent-soft);\n color:var(--accent);display:grid;place-items:center;font-size:13px;\n}\n.sm-dashed{\n border:1px dashed var(--line-dashed);border-radius:var(--radius-md);\n display:flex;align-items:center;justify-content:center;gap:8px;color:var(--text-muted);\n}\n\n/* \u2500\u2500 media \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.sm-modal{position:fixed;inset:0;z-index:70;background:rgba(0,0,0,.45);display:grid;place-items:center;padding:24px}\n.sm-modal__panel{\n max-width:100%;max-height:100%;display:flex;flex-direction:column;background:var(--surface);\n border:var(--border);border-radius:var(--radius-md);box-shadow:var(--shadow-pop);overflow:hidden;\n}\n.sm-modal__head{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:var(--border)}\n.sm-modal__body{padding:12px;overflow:auto;display:flex;flex-direction:column;gap:10px;min-height:0}\n.sm-modal__foot{display:flex;justify-content:flex-end;gap:8px;padding:10px 12px;border-top:var(--border)}\n.sm-picker-bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.sm-dropzone{\n border:1px dashed var(--line-dashed);border-radius:var(--radius-md);color:var(--text-muted);\n display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:16px;text-align:center;\n}\n.sm-dropzone--bar{padding:8px;font-size:var(--text-sm)}\n.sm-mediagrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(132px,1fr));gap:10px}\n.sm-mediacard{\n display:flex;flex-direction:column;gap:4px;padding:6px;background:transparent;border:1.5px solid transparent;\n border-radius:var(--radius-md);cursor:pointer;text-align:left;color:var(--text);font:inherit;min-width:0;\n}\n.sm-mediacard:hover{background:var(--control-hover)}\n.sm-mediacard[aria-pressed=\"true\"]{border-color:var(--selection);background:var(--accent-soft)}\n.sm-mediacard__thumb{display:block;aspect-ratio:4/3;border-radius:var(--radius-sm);overflow:hidden;background:var(--surface-inset)}\n.sm-mediacard__img{width:100%;height:100%;object-fit:cover;display:block}\n.sm-thumb--chip{display:grid;place-items:center;width:100%;height:100%;font-weight:600;color:var(--text-muted);font-size:var(--text-sm)}\n.sm-mediacard__name{font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.sm-mediacard__meta{font-size:var(--text-sm);color:var(--text-muted)}\n.sm-media{display:flex;flex-direction:column;gap:6px}\n.sm-media__thumb{position:relative;overflow:hidden;border-radius:var(--radius-md);background:var(--surface-inset)}\n.sm-media__thumb>img,.sm-media__thumb>video{position:absolute;max-width:none;display:block}\n.sm-media__alt{font-size:var(--text-sm);color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.sm-media__actions{display:flex;gap:6px}\n.sm-media__chip{display:flex;align-items:center;gap:8px;padding:6px 10px;background:var(--surface-inset);border-radius:var(--radius-md)}\n.sm-crop{display:grid;grid-template-columns:1fr 240px;gap:14px;min-height:0}\n.sm-crop__stage{position:relative;user-select:none;touch-action:none;background:var(--surface-inset);border-radius:var(--radius-md);overflow:hidden}\n.sm-crop__stage>img{display:block;width:100%;height:auto;pointer-events:none}\n.sm-crop__shade{position:absolute;inset:0;background:rgba(0,0,0,.45);pointer-events:none}\n.sm-crop__rect{position:absolute;outline:1.5px solid #fff;box-shadow:0 0 0 1px rgba(0,0,0,.4);cursor:move;background:transparent}\n.sm-crop__handle{position:absolute;width:12px;height:12px;background:#fff;border:1px solid rgba(0,0,0,.4);border-radius:2px}\n.sm-crop__handle--nw{left:-6px;top:-6px;cursor:nwse-resize}.sm-crop__handle--ne{right:-6px;top:-6px;cursor:nesw-resize}\n.sm-crop__handle--sw{left:-6px;bottom:-6px;cursor:nesw-resize}.sm-crop__handle--se{right:-6px;bottom:-6px;cursor:nwse-resize}\n.sm-crop__hotspot{position:absolute;width:22px;height:22px;margin:-11px 0 0 -11px;border-radius:50%;border:2px solid #fff;background:rgba(0,56,177,.55);cursor:grab;box-shadow:0 0 0 1px rgba(0,0,0,.4)}\n.sm-crop__side{display:flex;flex-direction:column;gap:10px}\n.sm-crop__preview{position:relative;overflow:hidden;border-radius:var(--radius-md);background:var(--surface-inset)}\n.sm-crop__preview>img{position:absolute;max-width:none;display:block}\n.sm-mediascreen{display:grid;grid-template-columns:1fr 300px;flex:1;min-height:0}\n.sm-mediascreen__grid{padding:12px;overflow:auto}\n.sm-mediascreen__inspector{border-left:var(--border);padding:14px;overflow:auto;display:flex;flex-direction:column;gap:10px}\n.sm-facts{display:grid;grid-template-columns:auto 1fr;gap:4px 10px;font-size:var(--text-sm);color:var(--text-muted)}\n.sm-usage{display:flex;flex-direction:column;gap:4px}\n";
|
|
2
2
|
/** Where the theme toggle's choice is remembered, browser-side only. */
|
|
3
3
|
export declare const THEME_KEY = "smoodly-theme";
|
|
4
4
|
/** One <style> element and the theme boot, rendered once by AdminLayout. */
|