smoodly 0.0.10 → 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.
Files changed (80) hide show
  1. package/README.md +73 -0
  2. package/dist/admin/client-ops.js +1 -0
  3. package/dist/admin/forms/CropEditor.d.ts +10 -0
  4. package/dist/admin/forms/CropEditor.js +61 -0
  5. package/dist/admin/forms/FieldWidget.js +4 -4
  6. package/dist/admin/forms/MediaGrid.d.ts +11 -0
  7. package/dist/admin/forms/MediaGrid.js +16 -0
  8. package/dist/admin/forms/MediaPicker.d.ts +8 -0
  9. package/dist/admin/forms/MediaPicker.js +44 -0
  10. package/dist/admin/forms/MediaWidget.d.ts +9 -0
  11. package/dist/admin/forms/MediaWidget.js +83 -0
  12. package/dist/admin/forms/assets-context.d.ts +19 -0
  13. package/dist/admin/forms/assets-context.js +35 -0
  14. package/dist/admin/forms/crop-editor.d.ts +13 -0
  15. package/dist/admin/forms/crop-editor.js +72 -0
  16. package/dist/admin/forms/measure.d.ts +2 -0
  17. package/dist/admin/forms/measure.js +39 -0
  18. package/dist/admin/forms/upload.d.ts +17 -0
  19. package/dist/admin/forms/upload.js +33 -0
  20. package/dist/admin/index.d.ts +1 -1
  21. package/dist/admin/next/op-handler.d.ts +3 -0
  22. package/dist/admin/next/op-handler.js +1 -0
  23. package/dist/admin/ops-impl.d.ts +3 -0
  24. package/dist/admin/ops-impl.js +83 -2
  25. package/dist/admin/ops.d.ts +27 -0
  26. package/dist/admin/serialize.d.ts +5 -0
  27. package/dist/admin/serialize.js +1 -0
  28. package/dist/admin/shell/AdminApp.d.ts +0 -6
  29. package/dist/admin/shell/AdminApp.js +10 -17
  30. package/dist/admin/shell/IconRail.d.ts +7 -0
  31. package/dist/admin/shell/IconRail.js +73 -0
  32. package/dist/admin/shell/MediaScreen.d.ts +8 -0
  33. package/dist/admin/shell/MediaScreen.js +86 -0
  34. package/dist/admin/shell/media-list.d.ts +8 -0
  35. package/dist/admin/shell/media-list.js +14 -0
  36. package/dist/admin/ui/Modal.d.ts +8 -0
  37. package/dist/admin/ui/Modal.js +15 -0
  38. package/dist/admin/ui/primitives.d.ts +1 -1
  39. package/dist/admin/ui/primitives.js +3 -3
  40. package/dist/admin/ui/theme.d.ts +1 -1
  41. package/dist/admin/ui/theme.js +107 -24
  42. package/dist/admin/validate.js +6 -0
  43. package/dist/asset-store.d.ts +86 -0
  44. package/dist/asset-store.js +141 -0
  45. package/dist/collections.js +2 -0
  46. package/dist/config.d.ts +12 -0
  47. package/dist/config.js +17 -5
  48. package/dist/fields.d.ts +8 -10
  49. package/dist/fields.js +14 -1
  50. package/dist/image/SmoodlyImage.d.ts +18 -0
  51. package/dist/image/SmoodlyImage.js +16 -0
  52. package/dist/image/SmoodlyVideo.d.ts +7 -0
  53. package/dist/image/SmoodlyVideo.js +5 -0
  54. package/dist/image/crop-math.d.ts +14 -0
  55. package/dist/image/crop-math.js +38 -0
  56. package/dist/image/index.d.ts +6 -0
  57. package/dist/image/index.js +5 -0
  58. package/dist/image/loaders.d.ts +15 -0
  59. package/dist/image/loaders.js +5 -0
  60. package/dist/image/next-config.d.ts +15 -0
  61. package/dist/image/next-config.js +27 -0
  62. package/dist/image-dimensions.d.ts +4 -0
  63. package/dist/image-dimensions.js +25 -0
  64. package/dist/index.d.ts +8 -2
  65. package/dist/index.js +5 -1
  66. package/dist/media.d.ts +108 -0
  67. package/dist/media.js +129 -0
  68. package/dist/refs.d.ts +2 -0
  69. package/dist/refs.js +25 -4
  70. package/dist/resolve.d.ts +19 -4
  71. package/dist/resolve.js +55 -2
  72. package/dist/site.d.ts +2 -0
  73. package/dist/site.js +21 -3
  74. package/dist/sql-space.d.ts +8 -1
  75. package/dist/sql-space.js +28 -0
  76. package/dist/sql.js +30 -0
  77. package/dist/store.js +3 -0
  78. package/dist/supabase-asset-store.d.ts +21 -0
  79. package/dist/supabase-asset-store.js +170 -0
  80. package/package.json +5 -1
@@ -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: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-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/* \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";
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. */
@@ -74,7 +74,7 @@ const SCALE = `
74
74
 
75
75
  --radius-xs:1px; --radius-sm:3px; --radius-md:4px; --radius-round:999px;
76
76
  --control-h:28px; --control-h-lg:36px; --row-h:38px; --topbar-h:48px;
77
- --nav-w:200px; --inspector-w:380px;
77
+ --nav-w:72px; --inspector-w:380px;
78
78
  --border:1px solid var(--line);
79
79
  --outline-selection:1.5px solid var(--selection);
80
80
  `;
@@ -113,13 +113,61 @@ body:has(.sm-admin){margin:0}
113
113
  /* ── shell ─────────────────────────────────────────────────────────── */
114
114
  .sm-shell{display:grid;grid-template-columns:var(--nav-w) 1fr;height:100vh;height:100dvh;overflow:hidden}
115
115
  .sm-shell>.sm-main{min-width:0;min-height:0;display:flex;flex-direction:column;overflow:hidden}
116
- .sm-nav{
117
- display:flex;flex-direction:column;gap:24px;padding:16px 12px;
118
- border-right:var(--border);background:var(--surface);min-width:0;overflow:auto;
119
- }
120
- .sm-nav__logo{height:18px;width:auto;align-self:flex-start;filter:var(--logo-filter);flex:none}
121
- .sm-nav>.sm-nav__logo{margin-left:10px}
122
- .sm-nav__list{display:flex;flex-direction:column;gap:1px}
116
+ .sm-wordmark{height:18px;width:auto;filter:var(--logo-filter);flex:none}
117
+ /* The design system's IconRail (components/navigation/IconRail.html),
118
+ ported as-is: a 72px rail, 18px Lucide icons over a 10px label, a
119
+ 40×32 active tile, the app icon on top and the avatar at the bottom. */
120
+ .sm-rail{
121
+ width:var(--nav-w);height:100%;display:flex;flex-direction:column;align-items:center;gap:14px;
122
+ padding:14px 6px 12px;background:var(--surface);border-right:var(--border);color:var(--text);
123
+ font:500 10px/1 var(--font-sans);position:relative;
124
+ }
125
+ .sm-rail__logo{width:28px;height:28px;display:block;flex:none}
126
+ .sm-rail__list{display:flex;flex-direction:column;gap:6px;width:100%;margin:0;padding:0;list-style:none}
127
+ .sm-rail__item{position:relative}
128
+ .sm-rail__btn{
129
+ appearance:none;background:none;border:0;margin:0;padding:2px 0;width:100%;display:flex;flex-direction:column;
130
+ align-items:center;gap:5px;color:inherit;font:inherit;cursor:pointer;border-radius:var(--radius-md);text-decoration:none;
131
+ }
132
+ .sm-rail__btn:hover{color:inherit}
133
+ .sm-rail__btn:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;box-shadow:none}
134
+ .sm-rail__tile{position:relative;width:40px;height:32px;border-radius:var(--radius-md);display:grid;place-items:center;transition:background .12s}
135
+ .sm-rail__btn:hover .sm-rail__tile{background:var(--surface-inset)}
136
+ .sm-rail__btn[aria-current="page"],.sm-rail__btn[aria-expanded="true"]{color:var(--nav-active-fg);font-weight:600}
137
+ .sm-rail__btn[aria-current="page"]:hover,.sm-rail__btn[aria-expanded="true"]:hover{color:var(--nav-active-fg)}
138
+ .sm-rail__btn[aria-current="page"] .sm-rail__tile,.sm-rail__btn[aria-expanded="true"] .sm-rail__tile{background:var(--nav-active-bg)}
139
+ .sm-rail__tile svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round}
140
+ .sm-rail__chev{position:absolute;right:3px;bottom:2px;width:8px;height:8px}
141
+ .sm-rail__chev svg{width:8px;height:8px;stroke-width:3}
142
+ .sm-rail__label{letter-spacing:.01em;white-space:nowrap}
143
+ /* Not greyed: the reference renders every inactive item at --text and marks
144
+ the unavailable ones only by being inert (no pointer, no hover). */
145
+ .sm-rail__btn[aria-disabled="true"]{cursor:default}
146
+ .sm-rail__btn[aria-disabled="true"]:hover .sm-rail__tile{background:transparent}
147
+ .sm-rail__foot{margin-top:auto;display:flex;flex-direction:column;align-items:center}
148
+ .sm-rail__user{
149
+ width:26px;height:26px;border-radius:var(--radius-round);background:var(--accent-soft);color:var(--accent);
150
+ display:grid;place-items:center;font-size:var(--text-sm);font-weight:600;border:0;cursor:pointer;padding:0;
151
+ }
152
+ .sm-rail__user:focus-visible{outline:2px solid var(--accent);outline-offset:1px;box-shadow:none}
153
+ /* Flyout: 6px right of the rail, aligned with its item (the user menu aligns to the bottom). */
154
+ .sm-rail__menu{
155
+ position:absolute;left:calc(100% + 6px);top:0;z-index:20;width:168px;padding:6px;margin:0;list-style:none;
156
+ background:var(--surface);border:var(--border);border-radius:var(--radius-md);box-shadow:0 4px 20px rgba(0,0,0,.14);
157
+ display:none;flex-direction:column;gap:1px;font-size:var(--text-md);font-weight:500;color:var(--text);
158
+ }
159
+ .sm-rail__menu[data-open="true"]{display:flex}
160
+ .sm-rail__menu--up{top:auto;bottom:0}
161
+ .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}
162
+ .sm-rail__menu-item{
163
+ appearance:none;background:none;border:0;width:100%;display:flex;align-items:center;justify-content:space-between;
164
+ padding:6px 8px;border-radius:var(--radius-sm);color:inherit;font:inherit;cursor:pointer;text-align:left;text-decoration:none;
165
+ }
166
+ .sm-rail__menu-item:hover,.sm-rail__menu-item:focus-visible{background:var(--surface-inset);color:inherit;outline:0;box-shadow:none}
167
+ .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}
168
+ .sm-when-light{display:var(--when-light);align-items:center;gap:8px}
169
+ .sm-when-dark{display:var(--when-dark);align-items:center;gap:8px}
170
+ /* .sm-nav-item survives as the inspector's link style (EditorView). */
123
171
  .sm-nav-item{
124
172
  display:block;padding:6px 10px;border-radius:var(--radius-md);line-height:1.3;
125
173
  color:var(--text);font-weight:500;text-decoration:none;cursor:pointer;
@@ -127,22 +175,6 @@ body:has(.sm-admin){margin:0}
127
175
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
128
176
  }
129
177
  .sm-nav-item:hover{background:var(--surface-inset);color:var(--text)}
130
- .sm-nav-item[aria-current="page"]{background:var(--nav-active-bg);color:var(--nav-active-fg);font-weight:600}
131
- .sm-nav-item[aria-current="page"]:hover{background:var(--nav-active-bg);color:var(--nav-active-fg)}
132
- /* Not greyed: the reference renders every inactive item at --text and marks
133
- the unavailable ones only by being inert (no pointer, no hover). */
134
- .sm-nav-item[aria-disabled="true"]{cursor:default}
135
- .sm-nav-item[aria-disabled="true"]:hover{background:transparent}
136
- .sm-nav__foot{margin-top:auto;display:flex;flex-direction:column;gap:2px}
137
- .sm-nav__user{display:flex;align-items:center;gap:10px;padding:6px 10px;min-width:0}
138
- .sm-nav-item--icon{display:flex;align-items:center;gap:8px}
139
- .sm-when-light{display:var(--when-light);align-items:center;gap:8px}
140
- .sm-when-dark{display:var(--when-dark);align-items:center;gap:8px}
141
- .sm-avatar{
142
- width:24px;height:24px;flex:none;border-radius:var(--radius-round);
143
- background:var(--accent-soft);color:var(--accent);
144
- display:grid;place-items:center;font-size:var(--text-sm);font-weight:600;
145
- }
146
178
 
147
179
  /* ── top bar ───────────────────────────────────────────────────────── */
148
180
  .sm-topbar{
@@ -419,6 +451,57 @@ body:has(.sm-admin){margin:0}
419
451
  border:1px dashed var(--line-dashed);border-radius:var(--radius-md);
420
452
  display:flex;align-items:center;justify-content:center;gap:8px;color:var(--text-muted);
421
453
  }
454
+
455
+ /* ── media ─────────────────────────────────────────────────────────── */
456
+ .sm-modal{position:fixed;inset:0;z-index:70;background:rgba(0,0,0,.45);display:grid;place-items:center;padding:24px}
457
+ .sm-modal__panel{
458
+ max-width:100%;max-height:100%;display:flex;flex-direction:column;background:var(--surface);
459
+ border:var(--border);border-radius:var(--radius-md);box-shadow:var(--shadow-pop);overflow:hidden;
460
+ }
461
+ .sm-modal__head{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:var(--border)}
462
+ .sm-modal__body{padding:12px;overflow:auto;display:flex;flex-direction:column;gap:10px;min-height:0}
463
+ .sm-modal__foot{display:flex;justify-content:flex-end;gap:8px;padding:10px 12px;border-top:var(--border)}
464
+ .sm-picker-bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
465
+ .sm-dropzone{
466
+ border:1px dashed var(--line-dashed);border-radius:var(--radius-md);color:var(--text-muted);
467
+ display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:16px;text-align:center;
468
+ }
469
+ .sm-dropzone--bar{padding:8px;font-size:var(--text-sm)}
470
+ .sm-mediagrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(132px,1fr));gap:10px}
471
+ .sm-mediacard{
472
+ display:flex;flex-direction:column;gap:4px;padding:6px;background:transparent;border:1.5px solid transparent;
473
+ border-radius:var(--radius-md);cursor:pointer;text-align:left;color:var(--text);font:inherit;min-width:0;
474
+ }
475
+ .sm-mediacard:hover{background:var(--control-hover)}
476
+ .sm-mediacard[aria-pressed="true"]{border-color:var(--selection);background:var(--accent-soft)}
477
+ .sm-mediacard__thumb{display:block;aspect-ratio:4/3;border-radius:var(--radius-sm);overflow:hidden;background:var(--surface-inset)}
478
+ .sm-mediacard__img{width:100%;height:100%;object-fit:cover;display:block}
479
+ .sm-thumb--chip{display:grid;place-items:center;width:100%;height:100%;font-weight:600;color:var(--text-muted);font-size:var(--text-sm)}
480
+ .sm-mediacard__name{font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
481
+ .sm-mediacard__meta{font-size:var(--text-sm);color:var(--text-muted)}
482
+ .sm-media{display:flex;flex-direction:column;gap:6px}
483
+ .sm-media__thumb{position:relative;overflow:hidden;border-radius:var(--radius-md);background:var(--surface-inset)}
484
+ .sm-media__thumb>img,.sm-media__thumb>video{position:absolute;max-width:none;display:block}
485
+ .sm-media__alt{font-size:var(--text-sm);color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
486
+ .sm-media__actions{display:flex;gap:6px}
487
+ .sm-media__chip{display:flex;align-items:center;gap:8px;padding:6px 10px;background:var(--surface-inset);border-radius:var(--radius-md)}
488
+ .sm-crop{display:grid;grid-template-columns:1fr 240px;gap:14px;min-height:0}
489
+ .sm-crop__stage{position:relative;user-select:none;touch-action:none;background:var(--surface-inset);border-radius:var(--radius-md);overflow:hidden}
490
+ .sm-crop__stage>img{display:block;width:100%;height:auto;pointer-events:none}
491
+ .sm-crop__shade{position:absolute;inset:0;background:rgba(0,0,0,.45);pointer-events:none}
492
+ .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}
493
+ .sm-crop__handle{position:absolute;width:12px;height:12px;background:#fff;border:1px solid rgba(0,0,0,.4);border-radius:2px}
494
+ .sm-crop__handle--nw{left:-6px;top:-6px;cursor:nwse-resize}.sm-crop__handle--ne{right:-6px;top:-6px;cursor:nesw-resize}
495
+ .sm-crop__handle--sw{left:-6px;bottom:-6px;cursor:nesw-resize}.sm-crop__handle--se{right:-6px;bottom:-6px;cursor:nwse-resize}
496
+ .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)}
497
+ .sm-crop__side{display:flex;flex-direction:column;gap:10px}
498
+ .sm-crop__preview{position:relative;overflow:hidden;border-radius:var(--radius-md);background:var(--surface-inset)}
499
+ .sm-crop__preview>img{position:absolute;max-width:none;display:block}
500
+ .sm-mediascreen{display:grid;grid-template-columns:1fr 300px;flex:1;min-height:0}
501
+ .sm-mediascreen__grid{padding:12px;overflow:auto}
502
+ .sm-mediascreen__inspector{border-left:var(--border);padding:14px;overflow:auto;display:flex;flex-direction:column;gap:10px}
503
+ .sm-facts{display:grid;grid-template-columns:auto 1fr;gap:4px 10px;font-size:var(--text-sm);color:var(--text-muted)}
504
+ .sm-usage{display:flex;flex-direction:column;gap:4px}
422
505
  `;
423
506
  /** Where the theme toggle's choice is remembered, browser-side only. */
424
507
  export const THEME_KEY = "smoodly-theme";
@@ -21,6 +21,12 @@ function validateValue(path, value, d, errors) {
21
21
  errors.push({ field: path, message: "Required." });
22
22
  return;
23
23
  }
24
+ if (d.type === "image" || d.type === "video" || d.type === "file") {
25
+ if (typeof value !== "object" || Array.isArray(value) || typeof value.asset !== "string") {
26
+ errors.push({ field: path, message: "Must be an uploaded file." });
27
+ }
28
+ return;
29
+ }
24
30
  if (d.type === "list") {
25
31
  if (!Array.isArray(value)) {
26
32
  errors.push({ field: path, message: "Must be a list." });
@@ -0,0 +1,86 @@
1
+ import type { AssetKind, AssetMeta, AssetRecord } from "./media.ts";
2
+ import { MemoryRefIndex } from "./ref-index.ts";
3
+ export type UploadTicket = {
4
+ id: string;
5
+ uploadUrl: string;
6
+ token: string;
7
+ };
8
+ export type FinishInput = {
9
+ width?: number;
10
+ height?: number;
11
+ duration?: number;
12
+ posterId?: string;
13
+ };
14
+ export type AssetQuery = {
15
+ kind?: AssetKind;
16
+ search?: string;
17
+ cursor?: string;
18
+ limit?: number;
19
+ };
20
+ export type AssetPage = {
21
+ items: AssetRecord[];
22
+ nextCursor: string | null;
23
+ };
24
+ export type BeginUploadInput = {
25
+ filename: string;
26
+ mime: string;
27
+ size: number;
28
+ createdBy?: string;
29
+ };
30
+ export type CreateAssetInput = BeginUploadInput & AssetMeta & {
31
+ bytes: Uint8Array;
32
+ width?: number;
33
+ height?: number;
34
+ duration?: number;
35
+ posterId?: string;
36
+ };
37
+ export declare const PAGE_SIZE = 60;
38
+ export interface AssetStore {
39
+ /** A pending row and a signed upload URL for `<space>/<id>.<ext>`. */
40
+ beginUpload(input: BeginUploadInput): Promise<UploadTicket>;
41
+ /** Marks the row ready with what the browser measured. */
42
+ finishUpload(id: string, dims: FinishInput): Promise<AssetRecord>;
43
+ /** Server-side: bytes in, a ready row out (the seed, scripts). */
44
+ create(input: CreateAssetInput): Promise<AssetRecord>;
45
+ /** Any status — the widget shows a pending row as "uploading". */
46
+ get(id: string): Promise<AssetRecord | null>;
47
+ /** Ready rows only, keyed by id — the resolver's fetcher. */
48
+ getMany(ids: string[]): Promise<Record<string, AssetRecord>>;
49
+ /** Ready rows, newest first, `PAGE_SIZE` per page; `cursor` is the last row's createdAt. */
50
+ list(query?: AssetQuery): Promise<AssetPage>;
51
+ update(id: string, meta: AssetMeta): Promise<AssetRecord>;
52
+ /** Refused while any refs row points at the asset; a video's poster goes with it. Idempotent. */
53
+ delete(id: string): Promise<void>;
54
+ }
55
+ export declare const assetErrors: {
56
+ notFound: (id: string) => Error;
57
+ notPending: (id: string) => Error;
58
+ referenced: (id: string, n: number) => Error;
59
+ };
60
+ export declare const kindOf: (mime: string) => AssetKind;
61
+ /** The object's extension: the filename's when it has a sane one, else the mime's, else "bin". */
62
+ export declare function extensionFor(filename: string, mime: string): string;
63
+ export declare const objectPath: (space: string, id: string, filename: string, mime: string) => string;
64
+ /** The public URL; an SVG carries ?download= so the bucket origin never renders it. */
65
+ export declare const withDownload: (url: string, mime: string) => string;
66
+ export declare class MemoryAssetStore implements AssetStore {
67
+ private refs;
68
+ private rows;
69
+ private objects;
70
+ private baseUrl;
71
+ private space;
72
+ constructor(refs?: MemoryRefIndex, options?: {
73
+ baseUrl?: string;
74
+ space?: string;
75
+ });
76
+ private url;
77
+ private row;
78
+ beginUpload(input: BeginUploadInput): Promise<UploadTicket>;
79
+ finishUpload(id: string, dims: FinishInput): Promise<AssetRecord>;
80
+ create(input: CreateAssetInput): Promise<AssetRecord>;
81
+ get(id: string): Promise<AssetRecord | null>;
82
+ getMany(ids: string[]): Promise<Record<string, AssetRecord>>;
83
+ list(query?: AssetQuery): Promise<AssetPage>;
84
+ update(id: string, meta: AssetMeta): Promise<AssetRecord>;
85
+ delete(id: string): Promise<void>;
86
+ }
@@ -0,0 +1,141 @@
1
+ import { MemoryRefIndex } from "./ref-index.js";
2
+ import { DEFAULT_SPACE_ID } from "./sql-space.js";
3
+ import { ASSET_COLLECTION } from "./media.js";
4
+ export const PAGE_SIZE = 60;
5
+ export const assetErrors = {
6
+ notFound: (id) => new Error(`smoodly: no asset "${id}".`),
7
+ notPending: (id) => new Error(`smoodly: asset "${id}" is not pending.`),
8
+ referenced: (id, n) => new Error(`smoodly: "asset/${id}" is referenced by ${n} document(s) — remove those references first.`),
9
+ };
10
+ export const kindOf = (mime) => mime.startsWith("image/") ? "image" : mime.startsWith("video/") ? "video" : "file";
11
+ const BY_MIME = {
12
+ "image/jpeg": "jpg", "image/png": "png", "image/webp": "webp", "image/avif": "avif", "image/gif": "gif",
13
+ "image/svg+xml": "svg", "video/mp4": "mp4", "video/webm": "webm", "application/pdf": "pdf",
14
+ };
15
+ /** The object's extension: the filename's when it has a sane one, else the mime's, else "bin". */
16
+ export function extensionFor(filename, mime) {
17
+ const m = /\.([a-z0-9]{1,8})$/i.exec(filename);
18
+ if (m)
19
+ return m[1].toLowerCase();
20
+ return BY_MIME[mime] ?? "bin";
21
+ }
22
+ export const objectPath = (space, id, filename, mime) => `${space}/${id}.${extensionFor(filename, mime)}`;
23
+ /** The public URL; an SVG carries ?download= so the bucket origin never renders it. */
24
+ export const withDownload = (url, mime) => (mime === "image/svg+xml" ? `${url}?download=` : url);
25
+ const lower = (s) => (s ?? "").toLowerCase();
26
+ export class MemoryAssetStore {
27
+ constructor(refs = new MemoryRefIndex(), options = {}) {
28
+ this.refs = refs;
29
+ this.rows = new Map();
30
+ this.objects = new Map();
31
+ this.baseUrl = options.baseUrl ?? "http://localhost/media";
32
+ this.space = options.space ?? DEFAULT_SPACE_ID;
33
+ }
34
+ url(path, mime) {
35
+ return withDownload(`${this.baseUrl}/${path}`, mime);
36
+ }
37
+ row(input, status) {
38
+ const id = crypto.randomUUID();
39
+ const path = objectPath(this.space, id, input.filename, input.mime);
40
+ const now = Date.now();
41
+ return {
42
+ id, kind: kindOf(input.mime), status, path, mime: input.mime, size: input.size,
43
+ width: null, height: null, duration: null, posterId: null, filename: input.filename,
44
+ title: null, alt: null, caption: null, credit: null, url: this.url(path, input.mime), createdAt: now, updatedAt: now,
45
+ };
46
+ }
47
+ async beginUpload(input) {
48
+ const row = this.row(input, "pending");
49
+ this.rows.set(row.id, row);
50
+ return { id: row.id, uploadUrl: `memory://upload/${row.path}`, token: "memory" };
51
+ }
52
+ async finishUpload(id, dims) {
53
+ const row = this.rows.get(id);
54
+ if (!row)
55
+ throw assetErrors.notFound(id);
56
+ if (row.status !== "pending")
57
+ throw assetErrors.notPending(id);
58
+ const next = {
59
+ ...row, status: "ready", width: dims.width ?? null, height: dims.height ?? null,
60
+ duration: dims.duration ?? null, posterId: dims.posterId ?? null, updatedAt: Date.now(),
61
+ };
62
+ this.rows.set(id, next);
63
+ return structuredClone(next);
64
+ }
65
+ async create(input) {
66
+ const row = {
67
+ ...this.row(input, "ready"),
68
+ width: input.width ?? null, height: input.height ?? null, duration: input.duration ?? null,
69
+ posterId: input.posterId ?? null, title: input.title ?? null, alt: input.alt ?? null,
70
+ caption: input.caption ?? null, credit: input.credit ?? null,
71
+ };
72
+ // Distinct createdAt per row keeps "newest first" and the cursor honest
73
+ // when two creates land in one millisecond.
74
+ const last = [...this.rows.values()].reduce((m, r) => Math.max(m, r.createdAt), 0);
75
+ if (row.createdAt <= last) {
76
+ row.createdAt = last + 1;
77
+ row.updatedAt = row.createdAt;
78
+ }
79
+ this.rows.set(row.id, row);
80
+ this.objects.set(row.path, input.bytes);
81
+ return structuredClone(row);
82
+ }
83
+ async get(id) {
84
+ const row = this.rows.get(id);
85
+ return row ? structuredClone(row) : null;
86
+ }
87
+ async getMany(ids) {
88
+ const out = {};
89
+ for (const id of ids) {
90
+ const row = this.rows.get(id);
91
+ if (row?.status === "ready")
92
+ out[id] = structuredClone(row);
93
+ }
94
+ return out;
95
+ }
96
+ async list(query = {}) {
97
+ const limit = query.limit ?? PAGE_SIZE;
98
+ const q = lower(query.search);
99
+ const cursor = query.cursor ? Number(query.cursor) : Infinity;
100
+ const all = [...this.rows.values()]
101
+ .filter((r) => r.status === "ready")
102
+ .filter((r) => !query.kind || r.kind === query.kind)
103
+ .filter((r) => !q || lower(r.title).includes(q) || lower(r.filename).includes(q) || lower(r.alt).includes(q))
104
+ .filter((r) => r.createdAt < cursor)
105
+ .sort((a, b) => b.createdAt - a.createdAt);
106
+ const items = all.slice(0, limit).map((r) => structuredClone(r));
107
+ return { items, nextCursor: all.length > limit ? String(items[items.length - 1].createdAt) : null };
108
+ }
109
+ async update(id, meta) {
110
+ const row = this.rows.get(id);
111
+ if (!row)
112
+ throw assetErrors.notFound(id);
113
+ const next = {
114
+ ...row,
115
+ ...(meta.title !== undefined ? { title: meta.title } : {}),
116
+ ...(meta.alt !== undefined ? { alt: meta.alt } : {}),
117
+ ...(meta.caption !== undefined ? { caption: meta.caption } : {}),
118
+ ...(meta.credit !== undefined ? { credit: meta.credit } : {}),
119
+ updatedAt: Math.max(Date.now(), row.updatedAt),
120
+ };
121
+ this.rows.set(id, next);
122
+ return structuredClone(next);
123
+ }
124
+ async delete(id) {
125
+ const row = this.rows.get(id);
126
+ if (!row)
127
+ return;
128
+ const usage = this.refs.to(ASSET_COLLECTION, id);
129
+ if (usage.length > 0)
130
+ throw assetErrors.referenced(id, usage.length);
131
+ if (row.posterId) {
132
+ const poster = this.rows.get(row.posterId);
133
+ if (poster) {
134
+ this.rows.delete(poster.id);
135
+ this.objects.delete(poster.path);
136
+ }
137
+ }
138
+ this.rows.delete(id);
139
+ this.objects.delete(row.path);
140
+ }
141
+ }
@@ -5,6 +5,8 @@ export const collectionDepth = (c) => c.tree?.depth ?? 1;
5
5
  export function collection(input) {
6
6
  if (!input?.name)
7
7
  throw new Error("smoodly: a collection requires a name.");
8
+ if (["shared", "asset", "media"].includes(input.name))
9
+ throw new Error(`smoodly: "${input.name}" is a reserved name.`);
8
10
  if (input.tree !== undefined && (!Number.isInteger(input.tree.depth) || input.tree.depth < 1)) {
9
11
  throw new Error(`smoodly: collection "${input.name}" declares an invalid tree depth.`);
10
12
  }
package/dist/config.d.ts CHANGED
@@ -50,6 +50,11 @@ export type SmoodlyConfig = {
50
50
  database?: {
51
51
  provider: string;
52
52
  };
53
+ /** Upload limits (spec 2026-09-08 media §10): bytes per file and the accepted mime types. */
54
+ media?: {
55
+ maxSize?: number;
56
+ accept?: string[];
57
+ };
53
58
  };
54
59
  export type ResolvedConfig = SmoodlyConfig & {
55
60
  homePageSlug: string;
@@ -57,6 +62,13 @@ export type ResolvedConfig = SmoodlyConfig & {
57
62
  pageDepth: number;
58
63
  /** The section's styles with the config standard set merged in (section keys win). */
59
64
  resolvedStyles(sectionName: string): Record<string, Descriptor>;
65
+ media: {
66
+ maxSize: number;
67
+ accept: string[];
68
+ };
60
69
  };
70
+ /** `shared`: the placement node type and an admin route; `asset`: the refs
71
+ * target of every media edge; `media`: the admin's Media screen route. */
72
+ export declare const RESERVED_NAMES: readonly string[];
61
73
  export declare function defineConfig(config: SmoodlyConfig): ResolvedConfig;
62
74
  export {};