domma-cms 1.8.27 → 1.8.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.8.28] - 2026-10-19
4
+
5
+ - **Structured data on the SEO tab.** The page editor's SEO tab gains a **Structured data (JSON-LD)** box: Schema.org markup for the page - a SoftwareApplication, an Organisation, an Article, a FAQ, a Product, a Local business, an Event - with an example of each to start from, filled in from the page. It is checked as you leave the box and again on save (a page with structured data that does not parse is not saved, and the tab says what is wrong), kept in the page's frontmatter as `seo.schema`, and put into the page's head as `application/ld+json` after the WebPage and breadcrumb blocks every page already gets. One object or a list of them; `@context` is filled in when left out.
6
+ - **Scripts and stylesheets are served from memory, compressed once.** Every static text asset - a script, a stylesheet, an SVG, a JSON file - was read from disk and compressed afresh for every visitor. The served bytes are now kept in memory per encoding (brotli, gzip, or plain for a client that accepts neither), bounded at 64 MB with the least recently served dropped first. The file is still checked on every request, so an edit shows at once; images, fonts and media stream as before. On while the response cache is on. `GET /api/cache/status` gains `assets`.
7
+
3
8
  ## [1.8.27] - 2026-10-19
4
9
 
5
10
  - **Pages come out of the cache about twice as fast.** Every public request decided whether you may see the page before it looked in the response cache, and that decision parsed the whole page - shortcodes, collections and all - so a cached page still cost a full parse on every visit. The parsed page is now kept per file (keyed on the file's time and size, emptied whenever anything leaves the response cache, and only while the cache is on), so a cached page costs a file stat and a lookup. On a 40 KB home page: 230 to 540 pages a second, the typical response from 41 ms to 18 ms under load.
package/README.md CHANGED
@@ -204,6 +204,9 @@ sidebar: false
204
204
  seo:
205
205
  title: My Page | My Site
206
206
  description: A short description for SEO
207
+ schema: # optional JSON-LD, emitted as application/ld+json
208
+ '@type': SoftwareApplication
209
+ name: My App
207
210
  createdAt: '2026-01-01T00:00:00.000Z'
208
211
  updatedAt: '2026-01-01T00:00:00.000Z'
209
212
  ---
@@ -1 +1 @@
1
- .dashboard-layout{display:flex;flex-direction:column;height:100vh;min-height:100vh;overflow:hidden}.dashboard-wrapper{display:flex;flex:1;min-height:0;overflow:hidden}.dashboard-main{flex:1;min-width:0;overflow-y:auto}#admin-sidebar.dashboard-sidebar{display:flex;flex-direction:column;flex-shrink:0;overflow:hidden}#admin-sidebar .dm-admin-sidebar{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--dm-border) transparent}#admin-sidebar .dm-admin-sidebar::-webkit-scrollbar{width:8px}#admin-sidebar .dm-admin-sidebar::-webkit-scrollbar-track{background:transparent}#admin-sidebar .dm-admin-sidebar::-webkit-scrollbar-thumb{background:var(--dm-border);border-radius:4px}#admin-sidebar .dm-admin-sidebar::-webkit-scrollbar-thumb:hover{background:var(--dm-text-muted)}.view-container{padding:1.5rem 2rem;max-width:1200px}.view-container:has(>.dm-plugin-shell){max-width:100%}.view-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;gap:1rem}.view-header h1{font-size:1.5rem;font-weight:700;margin:0;display:flex;align-items:center;gap:.5rem}.view-header-actions{display:flex;gap:.75rem;flex-wrap:wrap}.view-header-actions .btn-primary,.view-header-actions .btn-success,.view-header-actions .btn-danger{margin-left:.5rem}.stat-card .card-body{display:flex;align-items:center;gap:1rem}.stat-icon{width:44px;height:44px;border-radius:8px;background:color-mix(in srgb,var(--dm-primary) 14%,transparent);color:var(--dm-primary, #5b8cff);display:flex;align-items:center;justify-content:center;font-size:1.25rem;flex-shrink:0}.stat-icon--success{background:color-mix(in srgb,var(--dm-success) 15%,transparent);color:var(--dm-success)}.stat-icon--warning{background:color-mix(in srgb,var(--dm-warning) 15%,transparent);color:var(--dm-warning)}.stat-value{font-size:1.75rem;font-weight:700;line-height:1}.stat-label{font-size:.8rem;color:var(--dm-text-muted, #888);margin-top:.25rem}.toolbar{display:flex;align-items:center;gap:.75rem}.form-check-label{display:flex;align-items:center;gap:.5rem;cursor:pointer;font-size:.9rem}.form-hint{display:block;font-size:.8rem;color:var(--dm-text-muted, #888);margin-top:.25rem}.dashboard-main:has(#pe-workspace){overflow:hidden}.view-container:has(#pe-workspace){display:flex;flex-direction:column;height:calc(100dvh - 60px);max-width:100%;padding:.6rem .75rem 0}.view-container:has(#pe-workspace)>.dm-plugin-shell{flex:1;min-height:0;display:flex;flex-direction:column}.view-container:has(#pe-workspace)>.dm-plugin-shell>.dm-plugin-banner{flex:none}.view-container:has(#pe-workspace)>.dm-plugin-shell>.dm-plugin-body{flex:1;min-height:0;display:flex;flex-direction:column}.pe-ws{flex:1;min-height:0;display:grid;grid-template-columns:var(--pe-explorer-w, 15rem) minmax(0,1fr) var(--pe-dock-w, 22rem);border:1px solid var(--dm-border);border-radius:var(--dm-radius-md);background:var(--dm-surface, var(--dm-background));overflow:hidden}.pe-ws:not(.pe-ws--dock){grid-template-columns:var(--pe-explorer-w, 15rem) minmax(0,1fr)}.pe-ws--no-explorer{grid-template-columns:minmax(0,1fr) var(--pe-dock-w, 22rem)}.pe-ws--no-explorer:not(.pe-ws--dock){grid-template-columns:minmax(0,1fr)}.pe-explorer,.pe-main,.pe-dock{display:flex;flex-direction:column;min-height:0;min-width:0}.pe-explorer{border-right:1px solid var(--dm-border);background:var(--dm-background-alt)}.pe-dock{border-left:1px solid var(--dm-border);background:var(--dm-background-alt)}.pe-ws--full{position:fixed;inset:0;z-index:1000;border-radius:0;border:0}@media(max-width:1024px){.pe-ws,.pe-ws:not(.pe-ws--dock),.pe-ws--no-explorer{grid-template-columns:minmax(0,1fr);position:relative}.pe-explorer,.pe-dock{position:absolute;top:0;bottom:0;width:min(22rem,88%);z-index:5;box-shadow:0 0 24px #00000059}.pe-explorer{left:0}.pe-dock{right:0}.pe-ws--full .pe-explorer,.pe-ws--full .pe-dock{position:fixed}}.view-container:has(#ep-editor-tabs){max-width:100%}.ep-editor-grid{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(0,1fr) minmax(0,1.3fr);gap:1rem;align-items:start}.ep-editor-grid>.ep-tryit-col,.ep-editor-grid>.ep-results-col{position:sticky;top:1rem;display:flex;flex-direction:column;gap:1rem;min-width:0}@media(max-width:1500px){.ep-editor-grid{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}.ep-editor-grid>:first-child{grid-column:1 / -1}.ep-editor-grid>.ep-tryit-col,.ep-editor-grid>.ep-results-col{position:static}}@media(max-width:1024px){.ep-editor-grid{grid-template-columns:minmax(0,1fr)}}.live-preview-widths{display:inline-flex;border:1px solid var(--dm-border);border-radius:var(--dm-radius-sm);overflow:hidden}.live-preview-width{display:inline-flex;align-items:center;justify-content:center;padding:.3rem .55rem;border:none;background:transparent;color:inherit;cursor:pointer;line-height:1}.live-preview-width+.live-preview-width{border-left:1px solid var(--dm-border)}.live-preview-width:hover{background:var(--dm-hover-bg)}.live-preview-width.active{background:var(--dm-active-bg)}.live-preview-status{flex:1 1 auto;font-size:.8rem;opacity:.75}.page-live-preview-frame{display:block;width:100%;max-width:100%;height:100%;border:none;border-radius:6px;background:var(--dm-background);transition:max-width .18s ease}.page-live-preview-frame[data-width="1024"]{max-width:1024px}.page-live-preview-frame[data-width="390"]{max-width:390px}.page-live-preview-frame[data-width="1024"],.page-live-preview-frame[data-width="390"]{border-left:1px solid var(--dm-border);border-right:1px solid var(--dm-border)}.editor-toolbar{display:flex;align-items:center;gap:2px;padding:6px 10px;border-bottom:1px solid var(--dm-border, rgba(255, 255, 255, .08));flex-wrap:wrap;flex-shrink:0}.editor-toolbar-btn{background:none;border:none;color:var(--dm-text-muted, #888);padding:6px 8px;border-radius:4px;cursor:pointer;display:flex;align-items:center;transition:color .15s,background .15s}.editor-toolbar-btn:hover{color:var(--dm-text, #eee);background:var(--dm-hover-bg)}.editor-toolbar-sep{width:1px;height:20px;background:var(--dm-border, rgba(255, 255, 255, .08));margin:0 4px;flex-shrink:0}.editor-toolbar-right{margin-left:auto;display:flex;align-items:center;gap:2px}.editor-view-btn{background:none;border:none;color:var(--dm-text-muted, #888);padding:6px 8px;border-radius:4px;cursor:pointer;display:flex;align-items:center;transition:color .15s,background .15s}.editor-view-btn:hover{color:var(--dm-text, #eee);background:var(--dm-hover-bg)}.editor-view-btn.active{color:var(--dm-primary, #5b8cff);background:color-mix(in srgb,var(--dm-primary) 14%,transparent)}.editor-body{display:flex;flex:1;min-height:0;--dm-editor-split: 50%}@media(min-width:769px){.editor-body:not(.editor-mode-write):not(.editor-mode-preview) .editor-pane--write{flex:0 0 var(--dm-editor-split, 50%)}.editor-body:not(.editor-mode-write):not(.editor-mode-preview) .editor-pane--preview{flex:1 1 0}}.editor-pane{flex:1;display:flex;flex-direction:column;min-width:0}.editor-pane--write,.editor-pane--preview{overflow:hidden}.editor-pane--write{flex-direction:row}.editor-line-numbers{padding:1rem .6rem 1rem .75rem;background:var(--dm-background-alt, rgba(0, 0, 0, .15));border-right:1px solid var(--dm-border, rgba(255, 255, 255, .08));color:var(--dm-text-muted, #666);font-family:Fira Code,Courier New,monospace;font-size:.9rem;line-height:1.6;text-align:right;user-select:none;white-space:pre;overflow:hidden;flex-shrink:0;min-width:2.5rem}.editor-line-numbers--foldable{padding:1rem 0;min-width:3rem;white-space:normal;text-align:left;display:flex;flex-direction:column}.editor-line-number-row{display:flex;align-items:center;justify-content:flex-end;padding-right:.6rem;gap:.15rem}.fold-toggle{display:inline-flex;align-items:center;justify-content:center;width:1em;font-size:.6rem;cursor:default;color:transparent}.fold-toggle--open,.fold-toggle--folded{cursor:pointer;color:var(--dm-text-muted, #666);transition:color .1s}.fold-toggle--open:hover,.fold-toggle--folded:hover{color:var(--dm-primary, #4a9eff)}.editor-line-num{min-width:1.5rem;text-align:right}.editor-mode-write .editor-pane--preview,.editor-mode-write .editor-divider,.editor-mode-preview .editor-pane--write,.editor-mode-preview .editor-divider{display:none}.editor-divider{width:7px;background:linear-gradient(to right,transparent 0,transparent 3px,var(--dm-border, rgba(255,255,255,.08)) 3px,var(--dm-border, rgba(255,255,255,.08)) 4px,transparent 4px,transparent 100%);cursor:col-resize;flex-shrink:0;touch-action:none;transition:background .12s ease}.editor-divider:hover,.editor-divider:focus-visible,.editor-divider.is-dragging{background:linear-gradient(to right,transparent 0,transparent 2px,var(--dm-primary, #5b8cff) 2px,var(--dm-primary, #5b8cff) 5px,transparent 5px,transparent 100%);outline:none}.editor-body.is-resizing .editor-pane{pointer-events:none}.editor-body.is-resizing{cursor:col-resize;user-select:none}.editor-textarea{flex:1;resize:none;border:none;border-radius:0;background:transparent;font-family:Fira Code,Courier New,monospace;font-size:.9rem;padding:1rem;outline:none;color:inherit;line-height:1.6}#editor-body .editor-textarea,#editor-body .editor-line-numbers{font-family:var(--pe-font, "Fira Code", "Courier New", monospace);font-size:var(--pe-size, .9rem);line-height:var(--pe-line, 1.6)}#editor-body.pe-nowrap .editor-textarea{white-space:pre;overflow-x:auto}.editor-preview{flex:1;display:block;width:100%;border:none;background:var(--dm-background)}.media-picker-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:.75rem;max-height:400px;overflow-y:auto;padding:.25rem}.media-picker-item{cursor:pointer;border:2px solid transparent;border-radius:6px;overflow:hidden;transition:border-color .15s}.media-picker-item:hover{border-color:var(--dm-primary, #5b8cff)}.media-picker-item img{width:100%;height:80px;object-fit:cover;display:block}.media-picker-item span{display:block;font-size:.75rem;padding:4px 6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.docs-body h3{margin-top:1.25rem;margin-bottom:.5rem}.docs-body p{margin-bottom:.5rem;line-height:1.6}.docs-body ol,.docs-body ul{margin-bottom:.75rem;padding-left:1.25rem}.docs-body li{margin-bottom:.25rem}.docs-body hr{margin:1.25rem 0;border-color:var(--dm-border, rgba(255, 255, 255, .08))}.docs-body .table{margin-bottom:.75rem}.docs-body h2{margin-top:1.75rem;margin-bottom:.6rem;padding-bottom:.3rem;border-bottom:1px solid var(--dm-border)}.docs-body h2:first-child{margin-top:0}.docs-body blockquote{margin:.75rem 0;padding:.5rem .9rem;border-left:3px solid var(--dm-primary);background:var(--dm-surface);border-radius:var(--dm-radius, 6px)}.docs-body pre{margin-bottom:.75rem;overflow-x:auto}.pg-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:1rem;margin-top:1.5rem}.pg-grid>.card.pg-card{display:block;margin:0;text-decoration:none}.pg-card h3{margin-top:0}.pg-card .text-muted,.pg-card-pages{margin-bottom:0}.pg-card-pages{margin-top:.5rem;font-size:.8rem;color:var(--dm-text-muted)}.pg-actions{display:flex;gap:.5rem;flex-wrap:wrap}.pg-meta{margin-bottom:.75rem}.pg-pages{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:1rem}.pg-body{max-width:80ch}.nav-items-header,.nav-item-row{display:flex;gap:.5rem;align-items:center;padding:.35rem .75rem}.nav-items-header{border-bottom:1px solid var(--dm-border, rgba(255,255,255,.08));padding-bottom:.4rem;margin-bottom:.25rem}.nav-item-row{border-bottom:1px solid var(--dm-border, rgba(255,255,255,.04))}.nav-item-row:last-child{border-bottom:none}.nav-col-indent{width:18px;flex-shrink:0;color:var(--dm-text-muted, #888);text-align:center;font-size:.85rem}.nav-col-main{flex:2;min-width:0}.nav-col-icon{flex:0 0 90px}.nav-col-parent{flex:2;min-width:0}.nav-col-action{flex-shrink:0}.nav-item-row--child{background:color-mix(in srgb,var(--dm-text) 3%,transparent)}.nav-col-action{display:flex;align-items:center;gap:.25rem}.nav-item-row--hidden{opacity:.45}.nav-item-row--hidden .item-text,.nav-item-row--hidden .footer-link-text{text-decoration:line-through}.btn-toggle-hidden{color:var(--dm-text-muted, #888)}.btn-toggle-hidden.active{color:var(--dm-warning, #f59e0b)}.fb-drag-grip{flex-shrink:0;display:inline-flex;align-items:center;color:var(--dm-text-muted);cursor:grab}.fb-drag-grip:hover{color:var(--dm-text)}.btn-ghost span[data-icon=trash]{color:var(--dm-danger, #dc2626)}.view-container:has(.fb-fields-layout){max-width:none}.fb-fields-layout{display:grid;grid-template-columns:minmax(0,6fr) minmax(320px,4fr);gap:1.25rem;align-items:start}.fb-preview-pane{position:sticky;top:1rem;min-width:0}@media(max-width:1100px){.fb-fields-layout{grid-template-columns:1fr}.fb-preview-pane{position:static}}.toggle-label{display:flex;align-items:center;gap:.4rem;font-size:.9rem;cursor:pointer;margin-right:1rem}.preset-toggles{display:flex;flex-wrap:wrap;margin-top:.75rem}.presets-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem}@media(max-width:900px){.presets-grid{grid-template-columns:repeat(2,1fr)}}@media(max-width:600px){.presets-grid{grid-template-columns:1fr}}.card-header{padding:.5rem 1rem}.card-header h2{margin:0;font-size:.9rem;font-weight:600;letter-spacing:.02em}.preset-card .card-header{display:flex;align-items:center;justify-content:space-between}.preset-card .card-header h3{margin:0;font-size:1rem}.media-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:1rem}.media-card{background:var(--dm-card-bg, rgba(255,255,255,.04));border:1px solid var(--dm-border, rgba(255,255,255,.08));border-radius:8px;overflow:hidden}.media-preview{height:130px;display:flex;align-items:center;justify-content:center;background:#0003;overflow:hidden}.media-thumb{width:100%;height:100%;object-fit:cover}.media-thumb--file{font-size:2rem;color:var(--dm-text-muted, #888)}.media-info{padding:.5rem .75rem;display:flex;flex-direction:column;gap:.15rem}.media-name{font-size:.8rem;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:default}.media-rename-input{width:100%;font-size:.8rem;font-weight:500;padding:.1rem .25rem;border:1px solid var(--dm-primary, #7c6af7);border-radius:3px;background:var(--dm-input-bg, transparent);color:inherit;outline:none}.media-size{font-size:.75rem;color:var(--dm-text-muted, #888)}.media-actions{padding:.5rem .75rem;display:flex;gap:.4rem;border-top:1px solid var(--dm-border, rgba(255,255,255,.08))}#admin-topbar{height:60px;display:flex;align-items:center;gap:1rem;padding:0 1.25rem;background:var(--dm-navbar-bg, rgba(0,0,0,.3));border-bottom:1px solid var(--dm-border, rgba(255,255,255,.08));position:sticky;top:0;z-index:200;flex-shrink:0}.topbar-brand{display:flex;align-items:center;gap:.5rem;font-weight:700;font-size:.95rem;color:inherit;flex-shrink:0;margin-right:.5rem}.topbar-brand span[data-icon],.topbar-brand svg{color:var(--dm-primary, #5b8cff);width:22px;height:22px}.topbar-brand-text{opacity:.85}.topbar-brand-logo{height:28px;width:auto;display:inline-block;vertical-align:middle;margin-right:.4em;object-fit:contain}.topbar-user{display:flex;align-items:center;gap:.6rem;flex:1}.topbar-user-name{font-size:.875rem;font-weight:500}.topbar-avatar{width:1.75rem;height:1.75rem;border-radius:50%;object-fit:cover;flex-shrink:0}.topbar-role-badge{font-size:.7rem;font-weight:600;letter-spacing:.04em;text-transform:uppercase;padding:.15rem .45rem;border-radius:4px}.topbar-role-badge--admin{background:color-mix(in srgb,var(--dm-primary) 15%,transparent);color:var(--dm-primary)}.topbar-role-badge--manager{background:color-mix(in srgb,var(--dm-success) 15%,transparent);color:var(--dm-success)}.topbar-role-badge--editor{background:color-mix(in srgb,var(--dm-warning) 15%,transparent);color:var(--dm-warning)}.topbar-role-badge--subscriber{background:color-mix(in srgb,var(--dm-text-muted) 15%,transparent);color:var(--dm-text-muted)}.topbar-actions{display:flex;align-items:center;gap:.5rem;flex-shrink:0}.topbar-action-link{display:flex;align-items:center;gap:.35rem;font-size:.875rem;color:var(--dm-text-muted, #888);text-decoration:none;padding:.4rem .65rem;border-radius:6px;transition:color .15s,background .15s}.topbar-action-link:hover{color:var(--dm-text, #eee);background:var(--dm-hover-bg)}.topbar-action-link span[data-icon],.topbar-action-link svg{width:16px;height:16px}.topbar-signout{display:flex;align-items:center;gap:.35rem;font-size:.875rem}.topbar-signout span[data-icon],.topbar-signout svg{width:16px;height:16px}.login-wrap{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;padding:1rem;background:var(--dm-background, #0f1117);z-index:100;overflow-y:auto}.login-card{width:100%;max-width:460px}.ob-done-icon{font-size:3rem;color:var(--dm-success);margin-bottom:1rem;text-align:center}.btn-skip{display:block;text-align:center;margin-top:.75rem;font-size:.85rem;color:var(--dm-text-muted, #888);text-decoration:none}.btn-skip:hover{text-decoration:underline}.theme-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:.6rem}.theme-swatch{border:2px solid var(--dm-border, rgba(255,255,255,.1));border-radius:8px;overflow:hidden;cursor:pointer;transition:border-color .15s,transform .1s}.theme-swatch:hover{border-color:var(--dm-primary, #5b8cff);transform:translateY(-1px)}.theme-swatch.selected{border-color:var(--dm-primary, #5b8cff);box-shadow:0 0 0 2px var(--dm-primary, #5b8cff)}.theme-swatch-preview{height:52px;position:relative;display:flex;align-items:flex-end;padding:.35rem}.theme-swatch-accent{width:28px;height:6px;border-radius:3px}.theme-swatch-label{display:flex;justify-content:space-between;align-items:center;padding:.3rem .45rem;font-size:.7rem;background:var(--dm-card-bg, rgba(255,255,255,.04))}.theme-swatch-mode{color:var(--dm-text-muted, #888);font-size:.65rem}.login-logo{display:flex;align-items:center;gap:.75rem;margin-bottom:1.75rem;font-size:1.5rem}.login-logo span[data-icon]{font-size:2rem;color:var(--dm-primary, #5b8cff)}.login-logo h1{margin:0;font-size:1.5rem;font-weight:700}.login-card [hidden]{display:none!important}.login-heading{font-size:1.1rem;font-weight:600;margin-bottom:1.25rem}.login-rules{margin:.25rem 0 .75rem;padding-left:1.1rem;color:var(--dm-danger);font-size:.85rem}.login-rules li+li{margin-top:.2rem}.login-small{font-size:.8rem;margin-top:.75rem}.login-icon-warn{color:var(--dm-warning)}.pwf{position:relative;display:block}.pwf>input{width:100%;padding-right:2.5rem}.pwf-toggle{position:absolute;top:50%;right:.35rem;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;padding:0;border:0;border-radius:var(--dm-radius-sm);background:transparent;color:var(--dm-text-muted);cursor:pointer}.pwf-toggle:hover,.pwf-toggle:focus-visible{color:var(--dm-text);background:var(--dm-hover-bg)}.pwf-toggle svg{width:16px;height:16px}.pwf-toggle [data-icon=eye-off],.pwf.is-shown .pwf-toggle [data-icon=eye]{display:none}.pwf.is-shown .pwf-toggle [data-icon=eye-off]{display:inline-block}.pwf-caps{display:block;margin-top:.25rem;color:var(--dm-warning);font-size:.8rem}.pwf-caps[hidden]{display:none}.btn-block{width:100%}.plugins-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1rem}.plugin-card{display:flex;flex-direction:column;--plugin-accent: var(--dm-primary, #5b8cff);position:relative;overflow:hidden;background-image:radial-gradient(120% 90% at 100% 0%,color-mix(in srgb,var(--plugin-accent) 13%,transparent) 0%,color-mix(in srgb,var(--plugin-accent) 4%,transparent) 42%,transparent 72%)}.plugin-card .plugin-icon{background:color-mix(in srgb,var(--plugin-accent) 16%,transparent);color:var(--plugin-accent)}.plugin-watermark{position:absolute;top:-34px;right:-30px;width:148px;height:148px;display:block;color:var(--plugin-accent);opacity:.09;pointer-events:none;z-index:0}.plugin-card>.card-body,.plugin-card>.plugin-footer{position:relative;z-index:1}.plugin-card:has(.badge-secondary) .plugin-watermark{opacity:.05}@media(prefers-reduced-motion:no-preference){.plugin-card{transition:background-image .2s ease}}.plugin-card .card-body{flex:1}.plugin-header{display:flex;align-items:flex-start;gap:.75rem;margin-bottom:.75rem;flex-wrap:wrap}.plugin-icon{width:40px;height:40px;border-radius:8px;background:color-mix(in srgb,var(--dm-primary) 14%,transparent);color:var(--dm-primary, #5b8cff);display:flex;align-items:center;justify-content:center;flex-shrink:0}.plugin-meta{flex:1 1 140px;min-width:0}.plugin-name{font-weight:600;margin-bottom:.15rem}.plugin-version{font-size:.75rem;color:var(--dm-text-muted, #888)}.plugin-desc{font-size:.875rem;color:var(--dm-text-muted, #888);margin-bottom:1rem}.plugin-footer{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1rem;border-top:1px solid var(--dm-border, rgba(255,255,255,.08))}.plugin-footer-actions{display:flex;align-items:center;gap:.5rem}#admin-sidebar .sidebar-link{position:relative!important}#admin-sidebar .sidebar-badge{position:absolute!important;right:.75rem!important;top:50%!important;transform:translateY(-50%)!important;margin-left:0!important;padding-left:.45rem!important;padding-right:.45rem!important}#admin-sidebar .sidebar-text{padding-right:2rem!important}#admin-sidebar.sidebar-dark{background:var(--dm-surface);border-color:var(--dm-border)}#admin-sidebar.sidebar-dark .sidebar-header{background:var(--dm-surface-raised);border-color:var(--dm-border)}#admin-sidebar.sidebar-dark .sidebar-header-title{color:var(--dm-text)}#admin-sidebar.sidebar-dark .sidebar-link{color:var(--dm-text-muted)}#admin-sidebar.sidebar-dark .sidebar-link:hover{color:var(--dm-text);background:var(--dm-surface-overlay)}#admin-sidebar.sidebar-dark .sidebar-link.active{color:var(--dm-primary);background:color-mix(in srgb,var(--dm-primary) 14%,transparent);border-left-color:var(--dm-primary)}#admin-sidebar.sidebar-dark .sidebar-heading{color:var(--dm-text-muted)}#admin-sidebar.sidebar-dark .sidebar-divider{background:var(--dm-border)}#admin-sidebar.sidebar-dark .sidebar-footer{background:var(--dm-surface-raised);border-color:var(--dm-border)}.sidebar-heading--collapsible{display:flex!important;align-items:center;justify-content:space-between;cursor:pointer;user-select:none}.sidebar-heading-toggle{display:flex;align-items:center;opacity:.5;flex-shrink:0;transition:transform .2s ease}.sidebar-heading--collapsible.is-collapsed .sidebar-heading-toggle{transform:rotate(-90deg)}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.p-0{padding:0!important}.text-muted{color:var(--dm-text-muted, #888)}@media(max-width:768px){.view-container{padding:1rem}.editor-body{flex-direction:column}.editor-divider{width:auto;height:1px;cursor:default;background:var(--dm-border, rgba(255,255,255,.08))}}.image-editor{display:flex;flex-direction:column;gap:0}.image-editor-toolbar{display:flex;flex-direction:row;align-items:center;gap:.25rem;padding:.5rem .75rem;border-bottom:1px solid var(--dm-border, rgba(255, 255, 255, .1))}.image-editor-sep{display:inline-block;width:1px;height:1.5rem;background:var(--dm-border, rgba(255, 255, 255, .15));margin:0 .25rem;flex-shrink:0}.editor-toolbar-btn.active{color:var(--dm-primary, #7c6af7);background:color-mix(in srgb,var(--dm-primary, #7c6af7) 15%,transparent)}.editor-toolbar-caret{font-size:.6rem;line-height:1;margin-left:2px;opacity:.6}.editor-toolbar-dropdown{position:absolute;z-index:1000;background:var(--dm-surface, #1e1e2e);border:1px solid var(--dm-border, rgba(255, 255, 255, .1));border-radius:8px;box-shadow:0 8px 24px #0006;padding:4px;min-width:160px;max-height:70vh;overflow-y:auto}.editor-toolbar-dropdown-heading{padding:6px 10px 3px;color:var(--dm-text-muted, #8a8a9a);font-size:.66rem;text-transform:uppercase;letter-spacing:.06em}.editor-toolbar-dropdown-item{display:flex;align-items:center;gap:8px;width:100%;text-align:left;background:none;border:none;color:var(--dm-text, #ddd);padding:7px 10px;border-radius:5px;cursor:pointer;font-size:.85rem;transition:background .12s,color .12s}.editor-toolbar-dropdown-item:hover{background:var(--dm-hover-bg);color:var(--dm-text, #fff)}.editor-toolbar-dropdown-item span[data-icon],.editor-toolbar-dropdown-item svg{width:15px;height:15px;flex-shrink:0;opacity:.75}.editor-toolbar{position:relative}.editor-effects-dropdown-menu{position:absolute;top:100%;z-index:1000;background:var(--dm-surface, #1e1e2e);border:1px solid var(--dm-border, rgba(255, 255, 255, .1));border-radius:8px;box-shadow:0 8px 24px #0006;padding:6px;min-width:180px;max-height:340px;overflow-y:auto}.editor-effects-category{padding:6px 8px 3px;font-size:.7rem;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--dm-text-muted, #888)}.editor-effects-item{display:block;width:100%;text-align:left;background:none;border:none;color:var(--dm-text, #ddd);padding:6px 10px;border-radius:5px;cursor:pointer;font-size:.85rem;transition:background .12s,color .12s}.editor-effects-item:hover{background:var(--dm-hover-bg);color:var(--dm-text, #fff)}.editor-icon-picker{position:absolute;z-index:1000;background:var(--dm-surface, #1e1e2e);border:1px solid var(--dm-border, rgba(255, 255, 255, .1));border-radius:8px;box-shadow:0 8px 24px #0006;padding:8px;width:320px}.editor-icon-picker-search{width:100%;margin-bottom:8px;font-size:.8rem}.editor-spacer-picker{position:absolute;z-index:9999;background:var(--dm-surface, #1e1e2e);border:1px solid var(--dm-border, #333);border-radius:8px;padding:12px;width:240px;box-shadow:0 8px 24px #00000073}.editor-spacer-picker-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--dm-text-muted, #888);margin-bottom:10px}.editor-spacer-picker-row{display:flex;align-items:center;gap:10px;margin-bottom:12px}.editor-spacer-slider{flex:1;accent-color:var(--dm-primary, #6366f1);cursor:pointer}.editor-spacer-slider-value{font-size:13px;font-weight:600;min-width:38px;text-align:right;color:var(--dm-text, #cdd6f4)}.editor-spacer-insert-btn{width:100%}.editor-icon-picker-size-row{display:flex;align-items:center;gap:8px;padding:0 8px 8px}.editor-icon-picker-size-row label{font-size:11px;color:var(--dm-text-muted, #888);white-space:nowrap}.editor-icon-picker-size{width:70px;font-size:12px;padding:3px 6px}.editor-icon-picker-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(52px,1fr));gap:2px;max-height:280px;overflow-y:auto}.editor-icon-picker-item{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:6px 2px;border-radius:5px;cursor:pointer;background:none;border:none;color:var(--dm-text, #ddd);font-size:.58rem;text-align:center;gap:4px;overflow:hidden;transition:background .12s,color .12s}.editor-icon-picker-item:hover{background:var(--dm-hover-bg);color:var(--dm-text, #fff)}.editor-icon-picker-item span[data-icon],.editor-icon-picker-item svg{width:16px;height:16px;flex-shrink:0}.editor-icon-picker-item>span:last-child{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.2}.editor-icon-picker-empty{grid-column:1 / -1;text-align:center;color:var(--dm-text-muted, #888);font-size:.8rem;padding:1.5rem}.image-editor-canvas{min-height:320px;max-height:520px;background:#111;overflow:hidden;display:flex;align-items:center;justify-content:center}.image-editor-canvas img{display:block;max-width:100%;max-height:520px}.image-editor-resize{display:flex;flex-direction:row;align-items:center;gap:.5rem;padding:.6rem .75rem;border-top:1px solid var(--dm-border, rgba(255, 255, 255, .1));font-size:.85rem}.image-editor-resize label{font-weight:600;color:var(--dm-text-muted, #888);font-size:.8rem}.image-editor-resize .form-input{width:5rem;padding:.25rem .5rem;font-size:.85rem}.image-editor-footer{display:flex;flex-direction:row;align-items:center;justify-content:space-between;padding:.75rem;border-top:1px solid var(--dm-border, rgba(255, 255, 255, .1));gap:.5rem}.image-editor-effects{border-top:1px solid var(--dm-border, rgba(255, 255, 255, .1))}.image-editor-tab-bar{display:flex;flex-direction:row;overflow-x:auto;border-bottom:1px solid var(--dm-border, rgba(255, 255, 255, .1));scrollbar-width:none}.image-editor-tab-bar::-webkit-scrollbar{display:none}.image-editor-tab-btn{display:inline-flex;align-items:center;gap:.35rem;padding:.5rem .85rem;font-size:.8rem;font-weight:500;border:none;border-bottom:2px solid transparent;background:transparent;cursor:pointer;color:var(--dm-text-muted, #888);white-space:nowrap;transition:color .15s,border-color .15s;flex-shrink:0}.image-editor-tab-btn:hover{color:var(--dm-text, #eee)}.image-editor-tab-btn.active{color:var(--dm-primary, #7c6af7);border-bottom-color:var(--dm-primary, #7c6af7)}.image-editor-tab-panel{padding:.75rem;max-height:200px;overflow-y:auto}.ie-presets-grid{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.5rem}.ie-preset-btn.active{color:var(--dm-primary, #7c6af7);background:color-mix(in srgb,var(--dm-primary, #7c6af7) 15%,transparent);border-color:var(--dm-primary, #7c6af7)}.ie-server-note{font-size:.73rem;color:var(--dm-text-muted, #888);font-style:italic;margin:.25rem 0 0;line-height:1.4}.ie-slider-row{display:flex;align-items:center;gap:.5rem;margin-bottom:.4rem}.ie-slider-label{width:6rem;font-size:.8rem;color:var(--dm-text-muted, #888);font-weight:600;flex-shrink:0}.ie-slider{flex:1;accent-color:var(--dm-primary, #7c6af7);cursor:pointer}.ie-slider-val{width:3rem;font-size:.8rem;text-align:right;color:var(--dm-text, #eee);flex-shrink:0}.ie-select{flex:1;font-size:.85rem;padding:.25rem .5rem}.ie-colour-input{width:40px;height:28px;padding:0;border:1px solid var(--dm-border, rgba(255, 255, 255, .15));border-radius:4px;cursor:pointer;background:transparent;flex-shrink:0}.ie-wm-preview-row{display:flex;align-items:center;gap:.5rem;margin:.4rem 0}.ie-wm-preview-img{width:48px;height:48px;object-fit:contain;border-radius:4px;background:var(--dm-surface-raised, rgba(255, 255, 255, .06));flex-shrink:0}.ie-wm-preview-name{font-size:.8rem;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dm-text, #eee)}.ie-media-picker{padding:.25rem 0}.ie-media-picker-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(80px,1fr));gap:.5rem;max-height:300px;overflow-y:auto}.ie-media-thumb{display:flex;flex-direction:column;align-items:center;gap:.25rem;padding:.4rem;border-radius:6px;cursor:pointer;border:2px solid transparent;transition:border-color .15s,background .15s}.ie-media-thumb:hover{background:var(--dm-surface-raised, rgba(255, 255, 255, .08));border-color:var(--dm-primary, #7c6af7)}.ie-media-thumb img{width:72px;height:72px;object-fit:cover;border-radius:4px}.ie-media-thumb-name{font-size:.7rem;color:var(--dm-text-muted, #888);text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:72px}.dm-slideover-left,.dm-slideover-right{width:min(40rem,95vw)!important;min-width:min(36rem,100vw)!important;max-width:100vw!important}.dm-slideover-resizer{position:absolute;top:0;bottom:0;width:6px;cursor:col-resize;z-index:2;background:transparent}.dm-slideover-right>.dm-slideover-resizer{left:0}.dm-slideover-left>.dm-slideover-resizer{right:0}.dm-slideover-resizer:hover,.is-resizing>.dm-slideover-resizer{background:var(--dm-primary);opacity:.7}.dm-slideover.is-resizing{user-select:none;transition:none!important}.dm-slideover-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;border-bottom:1px solid var(--dm-border, #333);flex-shrink:0}.dm-slideover-title{margin:0;font-size:1rem;font-weight:600}.dm-slideover-close{padding:.2rem .35rem!important;line-height:1;font-size:.75rem}.dm-slideover-body{flex:1;overflow:auto;overscroll-behavior:contain;min-height:0;padding:1.5rem!important;box-sizing:border-box}.dm-slideover-body:empty{flex:0 0 auto;padding:0!important}.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-body):not(.dm-slideover-close){padding:1.5rem!important;box-sizing:border-box;flex:1 1 auto;min-height:0;overflow:auto;overscroll-behavior:contain}.dm-slideover-body,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close){scrollbar-width:thin;scrollbar-color:var(--dm-border) transparent}.dm-slideover-body::-webkit-scrollbar,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close)::-webkit-scrollbar{width:8px;height:8px}.dm-slideover-body::-webkit-scrollbar-track,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close)::-webkit-scrollbar-track{background:transparent}.dm-slideover-body::-webkit-scrollbar-thumb,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close)::-webkit-scrollbar-thumb{background:var(--dm-border);border-radius:4px}.dm-slideover-body::-webkit-scrollbar-thumb:hover,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close)::-webkit-scrollbar-thumb:hover{background:var(--dm-text-muted)}.dm-slideover-body>div{padding-left:0!important;padding-right:0!important}.dm-slideover-body>button.btn:last-child{margin-top:.5rem}.dconfig-textarea{font-family:monospace;min-height:100px;resize:vertical}.dm-editor-line-numbers{white-space:pre}.dm-code-inline{font-family:Fira Code,Courier New,monospace;font-size:.82em;padding:.15em .35em;border-radius:3px;background:var(--dm-background-alt, rgba(0, 0, 0, .18));color:var(--dm-text, inherit);border:1px solid var(--dm-border, rgba(255, 255, 255, .08))}.dm-code-block{font-family:Fira Code,Courier New,monospace;font-size:.82rem;line-height:1.6;padding:.65rem .9rem;border-radius:6px;background:var(--dm-background-alt, rgba(0, 0, 0, .18));color:var(--dm-text, inherit);border:1px solid var(--dm-border, rgba(255, 255, 255, .08));white-space:pre;overflow-x:auto;display:block;margin:.4rem 0}.tabs-centered{text-align:center}.tabs-centered .tab-list{display:inline-flex}.tabs-centered .tab-content{text-align:left}@media(max-width:768px){.view-header{flex-direction:column;align-items:flex-start}.theme-grid{grid-template-columns:repeat(2,1fr)}.nav-item-row{flex-wrap:wrap}.nav-col-icon,.nav-col-parent{display:none}#admin-topbar{padding:0 .75rem}.topbar-brand-text{display:none}.image-editor-toolbar{flex-wrap:wrap}}#topbar-bell{gap:.5rem}#topbar-bell .topbar-bell-badge{position:static;flex-shrink:0}.topbar-action-link[hidden],.topbar-bell-badge[hidden]{display:none}.topbar-bell-badge{position:absolute;top:2px;right:2px;min-width:16px;height:16px;padding:0 4px;background:var(--dm-danger, #dc2626);color:var(--dm-danger-text, #fff);font-size:10px;font-weight:700;line-height:16px;border-radius:9999px;text-align:center;pointer-events:none}.cb-scaffolder{padding:1rem 1.1rem;background:var(--dm-background-alt);border:1px solid var(--dm-primary);border-radius:8px;color:var(--dm-text)}.cb-scaffolder-head{display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem}.cb-scaffolder-head .cb-icon{flex-shrink:0;color:var(--dm-primary)}.cb-scaffolder-head h2{margin:0;font-size:1.05rem;font-weight:700;color:var(--dm-text)}.cb-scaffolder-blurb{margin:0 0 .75rem;font-size:.88rem;line-height:1.5;color:var(--dm-text-muted)}.cb-recipe-list{display:flex;flex-direction:column;gap:.5rem}.cb-recipe-card{display:flex;align-items:flex-start;gap:.75rem;text-align:left;padding:.75rem 1rem;background:var(--dm-surface);border:1px solid var(--dm-border);border-radius:6px;cursor:pointer;transition:border-color .12s;width:100%;color:var(--dm-text)}.cb-recipe-card:hover{border-color:var(--dm-primary)}.cb-recipe-card .cb-icon{flex-shrink:0;font-size:1.5rem;color:var(--dm-primary);margin-top:.1rem}.cb-recipe-meta{flex:1}.cb-recipe-name{font-weight:600;margin-bottom:.15rem;font-size:.95rem;color:var(--dm-text)}.cb-recipe-desc{font-size:.83rem;line-height:1.45;color:var(--dm-text-muted)}.cb-apply-panel{padding:1rem 1.1rem;background:var(--dm-surface);border:1px solid var(--dm-primary);border-radius:8px;color:var(--dm-text)}.cb-apply-head{display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem}.cb-apply-head .cb-icon{color:var(--dm-primary);font-size:1.4rem}.cb-apply-head h3{margin:0;font-size:1rem;font-weight:600;color:var(--dm-text)}.cb-apply-desc,.cb-apply-hint{margin:.25rem 0 .75rem;font-size:.85rem;line-height:1.5;color:var(--dm-text-muted)}.cb-apply-hint{font-size:.75rem;margin-top:.2rem}.cb-apply-field{margin-bottom:.6rem}.cb-apply-field label{display:block;font-size:.82rem;font-weight:600;margin-bottom:.2rem;color:var(--dm-text)}.cb-apply-field input{width:100%;padding:.4rem .55rem;border:1px solid var(--dm-border);border-radius:4px;font-size:.88rem;background:var(--dm-surface);color:var(--dm-text)}.cb-apply-status{margin:.5rem 0;font-size:.85rem;min-height:1.2rem;color:var(--dm-text)}.cb-apply-status.cb-error{color:var(--dm-danger, #dc2626)}.cb-apply-buttons{display:flex;gap:.5rem;justify-content:flex-end;margin-top:.5rem}.cb-success-panel{padding:1rem 1.1rem;background:var(--dm-surface);border:1px solid var(--dm-success, #16a34a);border-radius:8px;color:var(--dm-text)}.cb-success-panel .cb-icon{color:var(--dm-success, #16a34a);font-size:1.4rem}.cb-success-summary{margin:.25rem 0 .5rem;padding-left:1.2rem;font-size:.86rem;line-height:1.6;color:var(--dm-text)}.cb-success-warning{margin:.5rem 0;padding:.5rem .75rem;background:var(--dm-background-alt);border-left:3px solid var(--dm-warning, #eab308);border-radius:0 4px 4px 0;font-size:.82rem;color:var(--dm-text-muted)}.cb-success-snippet-label{margin:0 0 .25rem;font-size:.82rem;font-weight:600;color:var(--dm-text)}.cb-success-actions{display:flex;gap:.5rem;margin-top:.5rem;flex-wrap:wrap}.cb-code{position:relative}.cb-code pre{background:var(--dm-background-alt);color:var(--dm-text);display:block;padding:.6rem 2.4rem .6rem .75rem;white-space:pre;overflow-x:auto;margin:0;border-radius:6px;font-size:.82em;font-family:monospace;line-height:1.45}.cb-code .cb-copy{position:absolute;top:.35rem;right:.35rem;background:none;border:none;cursor:pointer;opacity:.45;padding:.2rem;line-height:1;color:var(--dm-text)}.cb-code .cb-copy:hover{opacity:1}.cb-intro{padding:.75rem 1rem;border-left:3px solid var(--dm-primary);background:var(--dm-background-alt);border-radius:0 6px 6px 0}.cb-intro p{margin:0;font-size:.92rem;line-height:1.5;color:var(--dm-text)}.cb-tutorial-link{display:flex;align-items:center;gap:.6rem;padding:.65rem .85rem;border:1px solid var(--dm-border);border-radius:6px;text-decoration:none;color:var(--dm-text);font-size:.88rem;transition:border-color .12s}.cb-tutorial-link:hover{border-color:var(--dm-primary)}.cb-tutorial-link .cb-icon{flex-shrink:0;color:var(--dm-primary)}.cb-tutorial-link .cb-arrow{color:var(--dm-text-muted);font-size:1.1rem;margin-left:auto}.menu-tree-row{display:grid;grid-template-columns:26px 1.4fr 1.4fr .9fr .7fr .9fr repeat(6,auto);gap:6px;align-items:center;padding:4px 0;transition:opacity .15s ease}.menu-tree-row--hidden{opacity:.5}.menu-tree-row--tool .me-tool-badge{margin-left:.4rem;font-size:.7rem;vertical-align:middle}.menu-tree-row--default .me-row-label{font-style:italic;opacity:.8}.menu-tree-grip{cursor:grab;color:var(--dm-text-muted)}.menu-tree-grip:hover{color:var(--dm-text)}.menu-tree{padding:8px 0}.menu-tree-row--separator{display:grid;grid-template-columns:26px minmax(80px,1fr) repeat(7,auto);gap:6px;align-items:center;padding:2px 0;opacity:.85}.menu-tree-row--separator:hover{opacity:1}.menu-tree-row--separator .me-sep-line{display:flex;align-items:center;gap:10px;width:100%}.menu-tree-row--separator .me-sep-line:before,.menu-tree-row--separator .me-sep-line:after{content:"";flex:1;border-top:1px dashed var(--dm-border, rgba(128, 128, 128, .55))}.menu-tree-row--separator .me-sep-label{font-size:.66rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--dm-text-muted, #8a8a8a);white-space:nowrap}.dm-admin-sidebar .sidebar-divider{border:0;border-top:1px solid var(--dm-border, rgba(128, 128, 128, .5));margin:.5rem .75rem;opacity:.8}.dm-admin-sidebar .sidebar-spacer--xs{height:var(--dm-space-1, .25rem)}.dm-admin-sidebar .sidebar-spacer--sm{height:var(--dm-space-2, .5rem)}.dm-admin-sidebar .sidebar-spacer--md{height:var(--dm-space-4, 1rem)}.dm-admin-sidebar .sidebar-spacer--lg{height:var(--dm-space-6, 1.5rem)}.dm-admin-sidebar .sidebar-spacer--xl{height:var(--dm-space-10, 2.5rem)}.dm-admin-sidebar .sidebar-spacer--flex{flex:1 1 auto;min-height:var(--dm-space-4, 1rem)}.dm-admin-sidebar--has-flex-spacer{display:flex;flex-direction:column;min-height:100%}.menu-tree-row--spacer{display:grid;grid-template-columns:26px minmax(80px,1fr) repeat(7,auto);gap:6px;align-items:center;padding:2px 0;opacity:.85}.menu-tree-row--spacer:hover{opacity:1}.menu-tree-row--spacer .me-spacer-line{display:flex;align-items:center;gap:8px;width:100%}.menu-tree-row--spacer .me-spacer-line:before,.menu-tree-row--spacer .me-spacer-line:after{content:"";flex:1;border-top:1px dotted var(--dm-border, rgba(128, 128, 128, .55))}.menu-tree-row--spacer .me-spacer-label{font-size:.66rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--dm-text-muted, #8a8a8a);white-space:nowrap}.menu-tree-row--spacer .me-spacer-size{width:auto;min-width:5.5rem;height:26px;padding:0 .35rem;font-size:.75rem}.menu-tree-row .me-icon-wrap{display:flex;gap:2px}.menu-tree-row .me-icon-wrap .me-icon{flex:1;min-width:0}.menu-tree-row .me-icon-wrap .me-icon-pick{flex-shrink:0;padding:.2rem .45rem;font-size:.85em}.dm-admin-sidebar{display:flex;flex-direction:column;gap:2px;padding:8px 0;font-size:.875rem}.dm-admin-sidebar details{margin:0}.dm-admin-sidebar details>summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:.5rem;padding:.4rem .75rem;border-radius:4px;user-select:none;font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--dm-text-muted, #888)}.dm-admin-sidebar details details>summary{font-size:.875rem;font-weight:500;text-transform:none;letter-spacing:0;color:inherit}.dm-admin-sidebar .dm-menu-badge{font-size:.7rem;font-weight:600}.dm-admin-sidebar details>summary::-webkit-details-marker{display:none}.dm-admin-sidebar details>summary:after{content:"\25b8";margin-left:auto;transition:transform .15s ease;color:var(--dm-text-muted, #999);font-size:.85em}.dm-admin-sidebar details[open]>summary:after{transform:rotate(90deg)}.dm-admin-sidebar details>div{padding-left:1rem;border-left:1px solid var(--dm-border-light, rgba(0, 0, 0, .08));margin-left:.95rem}.dm-admin-sidebar .sidebar-link{display:flex;align-items:center;gap:.5rem;padding:.35rem .75rem;text-decoration:none;color:var(--dm-text, inherit);border-radius:4px}.dm-admin-sidebar .sidebar-link:hover,.dm-admin-sidebar details>summary:hover{background:var(--dm-hover-bg, rgba(0, 0, 0, .05))}.dm-admin-sidebar .sidebar-link.active{background:var(--dm-active-bg, rgba(0, 0, 0, .08));font-weight:600}.dm-admin-sidebar .sidebar-text{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dm-admin-sidebar [data-icon]{flex-shrink:0}.dm-admin-sidebar-header{position:sticky;top:0;z-index:2;background:var(--dm-surface);display:flex;align-items:center;gap:.5rem;padding:.65rem .85rem;margin:0 0 .5rem;font-weight:600;font-size:.95rem;letter-spacing:.01em;color:var(--dm-text, inherit);border-bottom:1px solid var(--dm-border-light, rgba(0, 0, 0, .08))}.dm-admin-sidebar--dark .dm-admin-sidebar-header{background:#1a1d24}.dm-admin-sidebar--light .dm-admin-sidebar-header{background:#fff}.dm-admin-sidebar-header [data-icon]{color:var(--dm-text-muted, #999)}.dm-admin-sidebar-header .dm-admin-sidebar-arrange{margin-left:auto;padding:.2rem .35rem;color:var(--dm-text-muted, #999);line-height:1}.dm-admin-sidebar-header .dm-admin-sidebar-arrange:hover,.dm-admin-sidebar-header .dm-admin-sidebar-arrange:focus-visible{color:var(--dm-text)}.dm-admin-sidebar.is-arranging>:not(.dm-admin-sidebar-header):not(.sb-arrange):not(style){display:none!important}.dm-admin-sidebar.is-arranging .dm-admin-sidebar-arrange,.dm-admin-sidebar.is-arranging .dm-admin-sidebar-collapse{display:none}.sb-arrange{box-sizing:border-box;max-width:100vw;padding:0 .5rem 1rem;animation:sb-arrange-in .18s cubic-bezier(.2,.8,.2,1)}@keyframes sb-arrange-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}.sb-arrange-bar{position:sticky;top:2.6rem;z-index:2;display:flex;align-items:center;gap:.4rem;padding:.4rem .25rem;background:inherit}.sb-arrange-title{font-weight:600;margin-right:auto}.sb-arrange-hint{margin:0 .25rem .5rem;font-size:.78rem;line-height:1.35;color:var(--dm-text-muted)}.sb-arr-row{display:flex;align-items:center;gap:.45rem;padding:.35rem .4rem .35rem calc(.4rem + var(--sb-arr-depth, 0) * 1rem);border-radius:var(--dm-radius-sm, 4px);cursor:grab;user-select:none;font-size:.9rem}.sb-arr-row:hover{background:color-mix(in srgb,var(--dm-text) 6%,transparent)}.sb-arr-row:focus-visible{outline:2px solid var(--dm-primary);outline-offset:-2px}.sb-arr-row--folder .sb-arr-text{font-weight:600}.sb-arr-row--hidden .sb-arr-text,.sb-arr-row--hidden .sb-arr-icon{opacity:.45;text-decoration:line-through}.sb-arr-row--structural .sb-arr-text{font-size:.75rem;text-transform:uppercase;letter-spacing:.05em;color:var(--dm-text-muted)}.sb-arr-grip{color:var(--dm-text-muted);flex:none}.sb-arr-icon{width:16px;flex:none}.sb-arr-text{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sb-arr-hide{flex:none;display:inline-flex;padding:.15rem;border:0;border-radius:var(--dm-radius-sm, 4px);background:transparent;color:var(--dm-text-muted);cursor:pointer}.sb-arr-hide:hover,.sb-arr-hide:focus-visible{color:var(--dm-text);background:color-mix(in srgb,var(--dm-text) 8%,transparent)}.sb-arr-lock{flex:none;color:var(--dm-text-muted);opacity:.6}.dm-admin-sidebar .sidebar-badge{margin-left:auto;padding:0 .4rem;min-width:1.25rem;height:1.1rem;line-height:1.1rem;text-align:center;font-size:.7rem;font-weight:600;border-radius:999px;background:var(--dm-active-bg, rgba(0, 0, 0, .12));color:var(--dm-text-muted, #888)}.dm-admin-sidebar .sidebar-link.active .sidebar-badge{background:var(--dm-primary, #4a86e8);color:var(--dm-primary-text, #fff)}.dm-admin-sidebar .sidebar-children{overflow:hidden;transition:max-height .22s ease,opacity .18s ease}.dm-admin-sidebar .sidebar-frequent-empty{margin:0;padding:.35rem 1rem .6rem 2.25rem;font-size:.8rem;white-space:normal;overflow-wrap:anywhere;contain:inline-size;line-height:1.4;color:var(--dm-text-muted, inherit);opacity:.85}.dm-admin-sidebar details:not([open])>.sidebar-children{opacity:0}.dm-admin-sidebar{position:relative}#admin-sidebar{position:relative;transition:width .18s ease}#admin-sidebar.is-resizing{transition:none}@media(prefers-reduced-motion:reduce){#admin-sidebar{transition:none}}.dm-admin-sidebar-handle{position:absolute;top:0;right:0;width:8px;height:100%;cursor:col-resize;z-index:10;touch-action:none;outline:none}.dm-admin-sidebar-handle:before,.dm-admin-sidebar-handle:after{content:"";position:absolute;right:2px;transition:background .15s,opacity .15s}.dm-admin-sidebar-handle:before{top:0;bottom:0;width:2px;background:transparent}.dm-admin-sidebar-handle:after{top:50%;width:3px;height:28px;margin-top:-14px;border-radius:2px;background:var(--dm-border, rgba(0, 0, 0, .15));opacity:.8}.dm-admin-sidebar-handle:hover:before,.dm-admin-sidebar-handle:focus-visible:before,.is-resizing>.dm-admin-sidebar-handle:before{background:var(--dm-primary, #4a86e8);opacity:.5}.dm-admin-sidebar-handle:hover:after,.dm-admin-sidebar-handle:focus-visible:after,.is-resizing>.dm-admin-sidebar-handle:after{background:var(--dm-primary, #4a86e8);opacity:1}.dm-admin-sidebar-handle:focus-visible:before{opacity:1}#admin-sidebar:has(.is-arranging)>.dm-admin-sidebar-handle{display:none}body.dm-sidebar-resizing{cursor:col-resize;user-select:none}body.dm-sidebar-resizing iframe{pointer-events:none}.dm-admin-sidebar-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dm-admin-sidebar-header .dm-admin-sidebar-collapse{padding:.2rem .35rem;color:var(--dm-text-muted, #999);line-height:1}.dm-admin-sidebar-header .dm-admin-sidebar-collapse:hover,.dm-admin-sidebar-header .dm-admin-sidebar-collapse:focus-visible{color:var(--dm-text)}.dm-admin-sidebar-collapse [data-icon]{transition:transform .18s ease}.dm-admin-sidebar .sidebar-initial{display:none}#admin-sidebar.is-collapsed{width:56px!important}#admin-sidebar.is-collapsed .dm-admin-sidebar{overflow-x:hidden}#admin-sidebar.is-collapsed .dm-admin-sidebar-header{justify-content:center;padding-left:0;padding-right:0}#admin-sidebar.is-collapsed .dm-admin-sidebar-header>[data-icon],#admin-sidebar.is-collapsed .dm-admin-sidebar-title,#admin-sidebar.is-collapsed .dm-admin-sidebar-arrange{display:none}#admin-sidebar.is-collapsed .dm-admin-sidebar-collapse [data-icon]{transform:rotate(180deg)}#admin-sidebar.is-collapsed .sidebar-text{position:absolute!important;width:1px;height:1px;padding:0!important;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}#admin-sidebar.is-collapsed .sidebar-link,#admin-sidebar.is-collapsed details>summary{justify-content:center;padding:.5rem 0;margin:0 6px;width:auto!important}#admin-sidebar.is-collapsed details>summary:after,#admin-sidebar.is-collapsed .dm-menu-badge,#admin-sidebar.is-collapsed .sidebar-children{display:none!important}#admin-sidebar.is-collapsed .sidebar-initial{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:4px;font-size:.75rem;font-weight:600;background:var(--dm-hover-bg, rgba(0, 0, 0, .06))}#admin-sidebar.is-collapsed details>summary[aria-expanded=true]{background:var(--dm-active-bg, rgba(0, 0, 0, .08))}#admin-sidebar.is-collapsed .sidebar-badge{top:4px!important;right:8px!important;transform:none!important;width:8px;height:8px;min-width:0;padding:0!important;border-radius:50%;font-size:0;line-height:0;overflow:hidden}#admin-sidebar.is-collapsed .sidebar-badge--live{padding:0!important;background:var(--dm-success, #16a34a)}#admin-sidebar.is-collapsed .sidebar-badge--live:before{display:none}#admin-sidebar.is-collapsed summary>.sidebar-badge--sum{position:absolute!important;margin:0!important}#admin-sidebar.is-collapsed details[open]>summary>.sidebar-badge--sum{display:block}#admin-sidebar.is-collapsed .sidebar-divider{margin-left:8px;margin-right:8px}#admin-sidebar.is-collapsed .sidebar-spacer--flex{min-height:var(--dm-space-2, .5rem)}#admin-sidebar.is-collapsed details>summary{position:relative}.dm-admin-sidebar-flyout{position:fixed;z-index:1000;min-width:200px;max-width:320px;overflow-y:auto;background:var(--dm-surface, #fff);color:var(--dm-text, inherit);border:1px solid var(--dm-border, rgba(0, 0, 0, .1));border-radius:var(--dm-radius-md, 8px);box-shadow:var(--dm-shadow-lg, 0 8px 24px rgba(0, 0, 0, .15));animation:dm-sidebar-flyout-in .12s ease-out}.dm-admin-sidebar-flyout>.dm-admin-sidebar{padding:4px 6px 6px}.dm-admin-sidebar-flyout-title{padding:.45rem .75rem .35rem;font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--dm-text-muted, #888)}.dm-admin-sidebar-flyout .sidebar-badge{margin-left:auto}@keyframes dm-sidebar-flyout-in{0%{opacity:0;transform:translate(-4px)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){.dm-admin-sidebar-flyout{animation:none}}.dm-admin-sidebar--dark{background:#1a1d24;color:#e3e6eb}.dm-admin-sidebar--dark .sidebar-link,.dm-admin-sidebar--dark summary{color:#e3e6eb}.dm-admin-sidebar--dark .sidebar-link:hover,.dm-admin-sidebar--dark details>summary:hover{background:#ffffff0f}.dm-admin-sidebar--dark .sidebar-link.active{background:#4a86e840}.dm-admin-sidebar--dark .sidebar-badge{background:#ffffff1f;color:#cbd0d8}.dm-admin-sidebar--light{background:#fff;color:#1a1d24;border-right:1px solid rgba(0,0,0,.08)}.dm-admin-sidebar--light .sidebar-link,.dm-admin-sidebar--light summary{color:#1a1d24}.dm-admin-sidebar--light .sidebar-link:hover,.dm-admin-sidebar--light details>summary:hover{background:#0000000a}.dm-admin-sidebar--light .sidebar-link.active{background:#4a86e826}.dm-admin-sidebar--transparent{background:transparent}.page-header{display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;margin:0 0 1.25rem;padding-bottom:.75rem;border-bottom:1px solid var(--dm-border-light, rgba(0, 0, 0, .08))}.page-header h2{margin:0;display:flex;align-items:center;gap:.5rem;font-size:1.4rem;line-height:1.2}.page-header h2 [data-icon]{color:var(--dm-text-muted, #888)}.header-actions{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.card+.card,.card+p,p+.card,.card+.toolbar,.toolbar+.card,.card+.grid{margin-top:1rem}.toolbar{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;margin:0 0 1rem}.btn+.btn,.btn+a.btn,a.btn+.btn,a.btn+a.btn{margin-left:.35rem}[style*=inline-flex] .btn+.btn,[style*=inline-flex] .btn+a.btn,[style*=inline-flex] a.btn+.btn{margin-left:0}.card-body>[id$=-form],.card-body>form{margin:.25rem 0}.card-header{display:flex;align-items:center;gap:.5rem}.card-header h3{margin:0;font-size:1.05rem}.card-header [data-icon]{color:var(--dm-text-muted, #888)}.page-header+p.text-muted{margin:-.5rem 0 1rem}.dm-pe-grid{gap:.75rem 1rem}.dm-pe-grid .form-group{display:flex;flex-direction:column;gap:.25rem;margin:0}.dm-pe-grid .form-group.col-span-2{grid-column:1 / -1}.dm-pe-grid label{font-size:.85rem;font-weight:600;color:var(--dm-text, inherit)}.dm-pe-grid label .required{color:var(--dm-danger, #c33);margin-left:2px}.dm-pe-grid .form-hint{font-size:.75rem;color:var(--dm-text-muted, #888);margin-top:.1rem}.dm-pe-grid .form-input{width:100%;padding:.4rem .6rem}.dm-pe-grid textarea.form-input{resize:vertical;min-height:2.5rem}.dm-menu-badge{display:inline-block;margin-left:6px;padding:1px 7px;font-size:.7em;font-weight:700;line-height:1.4;border-radius:999px;background:var(--dm-text-muted);color:var(--dm-text-inverse, #fff);vertical-align:middle}.menu-tree-row--slim{display:grid;grid-template-columns:26px 1fr auto repeat(9,auto);gap:6px;align-items:center}.menu-tree-row--slim .me-row-label{font-weight:600}.menu-tree-row--slim .me-row-url{color:var(--dm-text-muted);font-size:.85em}.me-pages-picker{display:flex;gap:6px;align-items:center;margin-bottom:6px}.me-pages-picker .form-input{flex:1;min-width:0}.me-colour-control{display:flex;gap:6px;align-items:center}.me-colour-control .me-colour-hex{width:48px;height:32px;padding:2px;flex:none}.me-bind-summary{padding:.6rem .8rem;border:1px solid var(--dm-border, rgba(128, 128, 128, .35));border-radius:var(--dm-radius-md, 6px);background:color-mix(in srgb,var(--dm-text) 4%,transparent);font-size:.9em}.me-bind-summary code{font-size:.9em}.me-item-form{display:flex;flex-direction:column;gap:10px;padding:4px 2px}.me-item-form .form-field{display:flex;flex-direction:column;gap:4px}.me-item-form .form-label{font-size:.85em;color:var(--dm-text-muted)}.me-form-section{margin:12px 0 2px;padding-top:10px;border-top:1px solid var(--dm-border-light, rgba(0,0,0,.15));font-size:.78em;text-transform:uppercase;letter-spacing:.06em;color:var(--dm-text-muted)}.me-item-form .me-form-section:first-child{margin-top:0;padding-top:0;border-top:0}.me-item-form .me-colour-hex{width:48px;height:32px;padding:2px}.me-item-form .me-field-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}.me-item-form .me-field-grid .form-field{margin:0}.me-item-form .me-icon-field{display:flex;align-items:center;gap:8px}.me-item-form .me-icon-field .form-input{flex:1}.me-item-form .me-icon-preview{width:24px;height:24px;flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;color:var(--dm-text-muted)}#admin-sidebar.dm-menu-floating{border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 8px);box-shadow:var(--dm-shadow-lg);overflow:hidden}@media(max-width:768px){#admin-sidebar.dm-menu-floating{position:static!important;inset:auto!important;max-height:none!important;transform:none!important}.dashboard-wrapper{padding-left:0!important;padding-right:0!important}}[data-logic-section]{border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 6px);padding:.9rem 1rem 1rem;margin-bottom:1rem;background:color-mix(in srgb,var(--dm-text) 3%,transparent)}[data-logic-section] .fb-logic-cond-row,[data-logic-section] .fb-logic-val-rule{padding:.55rem .65rem;border-radius:var(--dm-radius-sm, 4px);border:1px solid var(--dm-border);background:var(--dm-surface, transparent)}[data-logic-section] .fb-logic-cond-row:last-of-type,[data-logic-section] .fb-logic-val-rule:last-of-type{margin-bottom:.75rem}[data-logic-section]>.btn{margin-top:.15rem}[data-logic-section] textarea{min-height:5rem}.project-filter-notice{display:flex;align-items:center;gap:.4rem;flex-wrap:wrap;margin:0 0 1rem;padding:.6rem .85rem;border:1px solid var(--dm-border);border-left:3px solid var(--dm-info, var(--dm-primary));border-radius:var(--dm-radius-md, 6px);background:color-mix(in srgb,var(--dm-info, var(--dm-primary)) 6%,transparent);font-size:.85rem;color:var(--dm-text-muted)}.project-filter-notice a{color:var(--dm-primary);font-weight:600}.ctx-help{display:inline-flex;align-items:center;justify-content:center;width:1.05rem;height:1.05rem;margin-left:.35rem;padding:0;border:1px solid var(--dm-border-dark, var(--dm-border));border-radius:var(--dm-radius-full, 9999px);background:transparent;color:var(--dm-text-muted);font-size:.68rem;font-weight:700;line-height:1;cursor:pointer;vertical-align:middle;flex-shrink:0}.ctx-help:hover,.ctx-help:focus-visible{border-color:var(--dm-primary);color:var(--dm-primary);outline:none}.card-header h3 .ctx-help{align-self:center}.ctx-preview-card{position:sticky;top:1rem}@media(max-width:1100px){.ctx-workspace>.col-8,.ctx-workspace>.col-4{flex:0 0 auto;width:100%}.ctx-preview-card{position:static}}.dm-plugin-shell{border:1px solid var(--dm-border);border-radius:var(--dm-radius-lg, 12px);background:var(--dm-surface);overflow:hidden}.dm-plugin-banner{display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;padding:.7rem 1rem;background:var(--dm-background-alt);border-bottom:1px solid var(--dm-border)}.dm-plugin-banner-identity{display:flex;align-items:center;gap:.55rem;min-width:0;margin-right:auto}.dm-plugin-banner-actions{display:flex;align-items:center;gap:.25rem;flex:0 0 auto;margin-right:.15rem;padding-right:.4rem;border-right:1px solid var(--dm-border)}.dm-plugin-banner-action{padding:.25rem .4rem;line-height:1;color:var(--dm-text-muted)}.dm-plugin-banner-action:hover{color:var(--dm-primary)}.dm-plugin-banner-icon{display:inline-flex;align-items:center;color:var(--dm-primary);flex:0 0 auto}.dm-plugin-banner-name{margin:0;font-size:1.05rem;font-weight:600;letter-spacing:.01em;color:var(--dm-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dm-activity{display:inline-flex;align-items:center;flex:0 0 auto;margin-left:.35rem}.dm-activity domma-loader{display:inline-block;line-height:0}.dm-activity[hidden]{display:none!important}#admin-topbar>.dm-activity{margin:0 .75rem 0 0}.dm-plugin-banner-parent{font-weight:500;color:var(--dm-text-muted)}.dm-plugin-banner-sep{margin:0 .4rem;color:var(--dm-text-muted)}.dm-plugin-banner-facts{display:flex;align-items:center;gap:.4rem;flex-wrap:wrap}.dm-plugin-fact{font-size:.72rem;line-height:1.6;padding:1px 8px;border-radius:999px;border:1px solid var(--dm-border);color:var(--dm-text-muted);background:var(--dm-surface);white-space:nowrap}.dm-plugin-fact--version{font-variant-numeric:tabular-nums;color:var(--dm-text)}.dm-plugin-fact--licence.is-included{color:var(--dm-info);border-color:var(--dm-info)}.dm-plugin-fact--licence.is-licensed{color:var(--dm-success);border-color:var(--dm-success)}.dm-plugin-fact--expiry{font-variant-numeric:tabular-nums}.dm-plugin-fact--expiry.is-warning{color:var(--dm-warning);border-color:var(--dm-warning)}.dm-plugin-fact--expiry.is-danger{color:var(--dm-danger);border-color:var(--dm-danger)}.dm-plugin-body{padding:1rem}.dm-plugin-body .card.dm-plugin-unwrap{border:0;background:none;box-shadow:none;margin:0}.dm-plugin-body .card.dm-plugin-unwrap>.card-body{padding:0}.dm-plugin-body>.p-4{padding:0}@media(max-width:640px){.dm-plugin-banner{align-items:flex-start;flex-direction:column;gap:.5rem}.dm-plugin-body{padding:.75rem}}.analytics-filter{max-width:240px}.dm-help-btn{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;margin-left:.3rem;padding:0;border:0;background:none;line-height:1;color:var(--dm-text-muted);opacity:.75;cursor:pointer}.dm-help-btn:hover,.dm-help-btn:focus-visible{color:var(--dm-primary);opacity:1}.dm-popover.dm-help-popover{--dm-popover-max-width: 300px;font-size:.85rem;line-height:1.45;font-weight:400}.dm-help-pop{position:fixed;z-index:10050;max-width:300px;padding:.6rem .75rem;background:var(--dm-surface, var(--dm-background));color:var(--dm-text);border:1px solid var(--dm-border);border-radius:var(--dm-radius-md);box-shadow:0 8px 24px #00000040;font-size:.85rem;line-height:1.45;text-align:left;font-weight:400}.dm-help-pop strong{display:block;margin-bottom:.2rem;font-weight:600}.dm-help-pop p{margin:0}.dm-admin-sidebar .sidebar-badge[data-badge-label]{cursor:help}.dm-admin-sidebar .sidebar-badge--danger,.dm-admin-sidebar .sidebar-link.active .sidebar-badge--danger{background:var(--dm-danger, #dc2626);color:#fff}.dm-admin-sidebar .sidebar-badge--live{background:color-mix(in srgb,var(--dm-success, #16a34a) 20%,transparent);color:var(--dm-success, #16a34a)}#admin-sidebar .sidebar-badge--live{padding-left:1.05rem!important}#admin-sidebar summary>.sidebar-badge--sum{position:static!important;transform:none!important;margin-left:auto!important;margin-right:.45rem;text-transform:none;letter-spacing:0}#admin-sidebar summary:has(>.sidebar-badge--sum):after{margin-left:0}#admin-sidebar details[open]>summary>.sidebar-badge--sum{display:none}.dm-admin-sidebar .sidebar-badge--live:before{content:"";position:absolute;left:.4rem;top:50%;width:.4rem;height:.4rem;margin-top:-.2rem;border-radius:50%;background:currentColor;animation:dm-badge-pulse 2s ease-in-out infinite}@keyframes dm-badge-pulse{50%{opacity:.35}}@media(prefers-reduced-motion:reduce){.dm-admin-sidebar .sidebar-badge--live:before{animation:none}}.dm-badge-pop{width:290px;max-width:calc(100vw - 16px);font-size:.8rem;pointer-events:none}.dm-badge-pop-title{display:block;font-size:.85rem}.dm-badge-pop-sub{margin:.1rem 0 .5rem;color:var(--dm-text-muted)}.dm-badge-pop-rows{display:grid;grid-template-columns:1fr auto;gap:.2rem .75rem;margin:0 0 .5rem;padding:.45rem 0;border-top:1px solid var(--dm-border);border-bottom:1px solid var(--dm-border)}.dm-badge-pop-rows dt{color:var(--dm-text-muted)}.dm-badge-pop-rows dd{margin:0;text-align:right;font-weight:600;font-variant-numeric:tabular-nums}.dm-badge-pop-items{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.4rem}.dm-badge-pop-items li{display:flex;flex-direction:column;min-width:0}.dm-badge-pop-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dm-badge-pop-meta{font-size:.72rem;color:var(--dm-text-muted)}.dm-badge-pop .is-danger,.dm-badge-pop li.is-danger .dm-badge-pop-meta{color:var(--dm-danger, #dc2626)}.dm-badge-pop-empty{margin:0;color:var(--dm-text-muted)}.dm-plugin-body,.dm-slideover{font-family:var(--dm-font-sans, inherit)}.dm-plugin-body :is(button,input,select,textarea,.btn,.form-input,.form-select,.form-textarea),.dm-slideover :is(button,input,select,textarea,.btn,.form-input,.form-select,.form-textarea){font-family:inherit}.dm-plugin-body :is(input,select,textarea,.form-input,.form-select,.form-textarea):not(.form-input-lg),.dm-slideover :is(input,select,textarea,.form-input,.form-select,.form-textarea):not(.form-input-lg){font-size:var(--dm-text-sm)}.dm-plugin-body .form-input-lg{font-size:var(--dm-text-base)}.dm-plugin-body :is(.font-mono,code,pre,kbd),.dm-slideover :is(.font-mono,code,pre,kbd){font-family:var(--dm-font-mono, monospace)}.dm-plugin-body .form-label,.dm-slideover .form-label{font-size:var(--dm-text-sm)}.related-box{display:flex;flex-direction:column;gap:.6rem;border-top:1px solid var(--dm-border);padding-top:.75rem}.related-box[hidden]{display:none}.related-head{display:flex;align-items:center;gap:.4rem}.related-head strong{flex:1}.related-list{list-style:none;margin:.3rem 0 0;padding:0;display:flex;flex-direction:column;gap:.25rem}.related-list li{display:flex;flex-direction:column}.related-list li.is-past{opacity:.7}.related-meta,.related-note,.related-loading{color:var(--dm-text-muted);font-size:var(--dm-text-xs);margin:0}.login-challenge{display:flex;flex-direction:column;gap:.6rem;margin-bottom:.75rem}.login-challenge #challenge-code{font-size:1.25rem;letter-spacing:.2em;text-align:center}.login-trust{display:flex;align-items:center;gap:.45rem;font-size:.875rem}.login-enrol{display:flex;flex-direction:column;align-items:center;gap:.6rem;margin-bottom:1rem}.login-enrol img{background:#fff;padding:.5rem;border-radius:.5rem}.login-secret{display:flex;flex-direction:column;align-items:center;gap:.25rem;text-align:center}.login-secret code{font-size:.95rem;letter-spacing:.08em;word-break:break-all}.usx-secret{display:flex;flex-direction:column;gap:.75rem;padding:1rem}.usx-secret-link{display:block;padding:.6rem .75rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius-sm);background:var(--dm-background-alt);word-break:break-all;font-size:.8rem;user-select:all}.usx-secret-actions{display:flex;align-items:center;justify-content:flex-end;gap:.5rem}.usx-secret-actions .btn{display:inline-flex;align-items:center;gap:.35rem}.usx-secret-note{margin-right:auto;color:var(--dm-success)}.usx-secret-foot{color:var(--dm-text-muted);font-size:.8rem;margin:0}.uex-reset{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem .5rem;margin-top:.5rem}.uex-reset>small:first-child{flex-basis:100%;color:var(--dm-text-muted)}.uex-reset .btn{display:inline-flex;align-items:center;gap:.35rem}.uex-reset-open{flex-basis:100%;color:var(--dm-info)}.dm-loading-slot>.skeleton-list,.dm-loading-slot>.skeleton-table{border-color:transparent;background:transparent}.dm-loading-slot>.skeleton-list .skeleton-list-item{padding-left:.35rem;padding-right:.35rem}.dm-skeleton-figure{width:3.5rem;height:1em;margin:.1em 0}.view-container:has(.dbp){max-width:none}.dbp-head{display:flex;align-items:center;justify-content:space-between;gap:1rem;margin-bottom:.9rem}.dbp-hint{margin:0;color:var(--dm-text-muted);font-size:var(--dm-text-sm);display:flex;align-items:center;gap:.35rem}.dbp-source{color:var(--dm-text-muted);font-size:var(--dm-text-xs);white-space:nowrap}.dbp-board{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));column-gap:1rem;row-gap:0;grid-auto-rows:4px;grid-auto-flow:row dense;align-items:start}.dbp-board>.dbp-card{align-self:start;margin-bottom:1rem}.dbp-card{grid-column:span 2;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md);background:var(--dm-surface);display:flex;flex-direction:column;min-width:0;min-height:9rem;outline:none}.dbp-card:focus-visible{border-color:var(--dm-primary)}.dbp-size-s{grid-column:span 1}.dbp-size-m{grid-column:span 2}.dbp-size-l{grid-column:span 3}.dbp-size-xl{grid-column:1 / -1}@media(max-width:1100px){.dbp-board{grid-template-columns:repeat(2,minmax(0,1fr))}.dbp-size-l{grid-column:1 / -1}}@media(max-width:640px){.dbp-board{grid-template-columns:minmax(0,1fr)}.dbp-card{grid-column:1 / -1}}.dbp-card-head{display:flex;align-items:center;gap:.5rem;padding:.55rem .7rem;border-bottom:1px solid var(--dm-border);min-width:0}.dbp-grip{cursor:grab;color:var(--dm-text-muted);display:inline-flex;touch-action:none}.dbp-grip:active{cursor:grabbing}.dbp-icon{display:inline-flex;align-items:center;justify-content:center;width:1.8rem;height:1.8rem;flex:0 0 auto;border-radius:var(--dm-radius-md);color:var(--dm-primary);background:color-mix(in srgb,var(--dm-primary) 12%,transparent)}.dbp-title{font-weight:600;color:var(--dm-text);text-decoration:none;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}a.dbp-title:hover,a.dbp-title:focus-visible{color:var(--dm-primary);text-decoration:underline}.dbp-from{margin-left:auto;color:var(--dm-text-muted);font-size:var(--dm-text-xs);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dbp-kebab{border:0;background:none;color:var(--dm-text-muted);cursor:pointer;padding:.1rem .35rem;border-radius:var(--dm-radius-md);line-height:1}.dbp-kebab:hover,.dbp-kebab:focus-visible{background:color-mix(in srgb,var(--dm-text) 8%,transparent);color:var(--dm-text)}.dbp-body{padding:.75rem .9rem;display:flex;flex-direction:column;gap:.6rem;min-width:0;flex:1 1 auto}.dbp-card.is-loading .dbp-body{opacity:.75}.dbp-skeleton{height:3.5rem;border-radius:var(--dm-radius-md);background:color-mix(in srgb,var(--dm-text) 7%,transparent)}.dbp-figure{display:flex;align-items:baseline;gap:.5rem;flex-wrap:wrap}.dbp-figure-value{font-size:var(--dm-text-2xl, 1.5rem);line-height:1.15;font-weight:600;font-variant-numeric:tabular-nums;color:var(--dm-text)}.dbp-figure-label{color:var(--dm-text-muted);font-size:var(--dm-text-sm)}.dbp-figure.is-danger .dbp-figure-value{color:var(--dm-danger)}.dbp-figure.is-attention .dbp-figure-value{color:var(--dm-warning)}.dbp-sub{margin:0;color:var(--dm-text-muted);font-size:var(--dm-text-xs)}.dbp-facts{display:grid;grid-template-columns:auto 1fr;gap:.2rem .8rem;margin:0;font-size:var(--dm-text-sm)}.dbp-facts dt{color:var(--dm-text-muted)}.dbp-facts dd{margin:0;text-align:right;font-variant-numeric:tabular-nums}.dbp-rows{display:flex;flex-direction:column;min-width:0}.dbp-row{display:flex;align-items:center;gap:.6rem;padding:.4rem .3rem;border-bottom:1px solid var(--dm-border);color:var(--dm-text);text-decoration:none;min-width:0}.dbp-row:last-child{border-bottom:0}a.dbp-row:hover,a.dbp-row:focus-visible{background:color-mix(in srgb,var(--dm-text) 6%,transparent);outline:none}.dbp-row-main{display:flex;flex-direction:column;min-width:0;flex:1 1 auto}.dbp-row-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dbp-row-meta{color:var(--dm-text-muted);font-size:var(--dm-text-xs);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dbp-row-value{font-variant-numeric:tabular-nums;color:var(--dm-text-muted);font-size:var(--dm-text-sm);white-space:nowrap}.dbp-row.is-danger{box-shadow:inset 3px 0 0 var(--dm-danger)}.dbp-row.is-attention{box-shadow:inset 3px 0 0 var(--dm-warning)}.dbp-row.is-ok .dbp-row-value{color:var(--dm-success)}.dbp-chart{position:relative;height:240px}.dbp-empty{margin:0;color:var(--dm-text-muted);font-size:var(--dm-text-sm)}.dbp-empty.is-error{color:var(--dm-danger)}.dbp-empty-board{text-align:center;padding:3rem 1rem;border:1px dashed var(--dm-border);border-radius:var(--dm-radius-md);color:var(--dm-text-muted)}.dbp-gallery{display:flex;flex-direction:column;gap:.6rem;padding:.25rem}.dbp-gallery-hint{margin:0;color:var(--dm-text-muted);font-size:var(--dm-text-xs)}.dbp-gallery-group{margin:.6rem 0 .1rem;font-size:var(--dm-text-xs);text-transform:uppercase;letter-spacing:.06em;color:var(--dm-text-muted)}.dbp-gallery-item{display:flex;align-items:center;gap:.6rem;padding:.5rem .3rem;border-bottom:1px solid var(--dm-border)}.dbp-gallery-main{display:flex;flex-direction:column;min-width:0;flex:1 1 auto}.dbp-gallery-main small{color:var(--dm-text-muted);font-size:var(--dm-text-xs)}.dbp-gallery-added{color:var(--dm-success);font-size:var(--dm-text-xs);white-space:nowrap}.dbp-fold{border:0;background:none;color:var(--dm-text-muted);cursor:pointer;padding:.15rem;border-radius:var(--dm-radius-md);display:inline-flex;line-height:1}.dbp-fold:hover,.dbp-fold:focus-visible{background:color-mix(in srgb,var(--dm-text) 8%,transparent);color:var(--dm-text)}.dbp-fold [data-icon]{transition:transform .15s ease}.dbp-card.is-collapsed{min-height:0}.dbp-card.is-collapsed .dbp-card-head{border-bottom:0}.dbp-card.is-collapsed .dbp-fold [data-icon]{transform:rotate(-90deg)}.dbp-from+.dbp-fold{margin-left:0}@media(prefers-reduced-motion:reduce){.dbp-fold [data-icon]{transition:none}}.dbp-body[hidden]{display:none}@media(min-width:1101px){.dbp.dbp-cols-3 .dbp-board{grid-template-columns:repeat(3,minmax(0,1fr))}.dbp.dbp-cols-6 .dbp-board{grid-template-columns:repeat(6,minmax(0,1fr))}}.dbp.is-compact .dbp-card-head{padding:.35rem .55rem}.dbp.is-compact .dbp-body{padding:.5rem .65rem;gap:.4rem}.dbp.is-compact .dbp-figure-value{font-size:var(--dm-text-xl, 1.25rem)}.dbp.is-compact .dbp-row{padding:.25rem .2rem}.dbp.is-compact .dbp-card{min-height:0}.dbp.is-locked .dbp-grip{display:none}.dbp-greeting{margin:0 0 .6rem;font-size:var(--dm-text-lg, 1.1rem);color:var(--dm-text);font-weight:500}.dbp-settings{display:flex;flex-direction:column;gap:.85rem;padding:.25rem}.dbp-settings-section{margin:.4rem 0 0;font-size:var(--dm-text-xs);text-transform:uppercase;letter-spacing:.06em;color:var(--dm-text-muted)}.dbp-field{display:flex;flex-direction:column;gap:.3rem}.dbp-field .form-label{display:flex;align-items:center;gap:.35rem}.dbp-field-row{display:grid;grid-template-columns:1fr 1fr;gap:.8rem}.dbp-switch{flex-direction:row;align-items:center;gap:.6rem;cursor:pointer}.dbp-switch>span{display:flex;align-items:center;gap:.35rem}.dbp-settings-actions{display:flex;justify-content:flex-end;gap:.5rem;margin-top:.4rem}.dbp-card{container-type:inline-size}@container (max-width: 240px){.dbp-card-head .dbp-icon,.dbp-card-head .dbp-from{display:none}.dbp-card-head .dbp-title{white-space:normal;line-height:1.2;overflow-wrap:anywhere;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.dbp-card-head{gap:.35rem}}.dbp-tabs{display:flex;flex-wrap:wrap;gap:.25rem;margin:0 0 .9rem;border-bottom:1px solid var(--dm-border)}.dbp-tab{border:1px solid transparent;border-bottom:0;background:none;color:var(--dm-text-muted);cursor:pointer;padding:.45rem .85rem;border-radius:var(--dm-radius-md) var(--dm-radius-md) 0 0;font-weight:500;margin-bottom:-1px;max-width:16rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dbp-tab:hover,.dbp-tab:focus-visible{color:var(--dm-text);background:color-mix(in srgb,var(--dm-text) 6%,transparent);outline:none}.dbp-tab.is-active{color:var(--dm-text);background:var(--dm-surface);border-color:var(--dm-border)}.dbp-tab-new{display:inline-flex;align-items:center;padding:.45rem .6rem}.dbp-tab-fixed{display:inline-flex;align-items:center;gap:.3rem}.dbp-tab-fixed+.dbp-tab:not(.dbp-tab-new){margin-left:.4rem}.dbs-strip{margin-bottom:1rem}.dm-hk-backdrop{position:fixed;inset:0;z-index:10050;display:flex;align-items:flex-start;justify-content:center;padding:12vh 16px 16px;background:#00000073}.dm-hk-panel{width:min(560px,100%);max-height:72vh;display:flex;flex-direction:column;background:var(--dm-surface-raised, var(--dm-surface, #fff));color:var(--dm-text);border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 10px);box-shadow:var(--dm-shadow-lg, 0 20px 50px rgba(0, 0, 0, .35));overflow:hidden}.dm-hk-panel:focus{outline:none}.dm-hk-keylist{width:min(720px,100%)}.dm-hk-search{display:flex;align-items:center;gap:10px;padding:12px 16px;border-bottom:1px solid var(--dm-border);color:var(--dm-text-muted)}.dm-hk-search input{flex:1;min-width:0;border:0;outline:0;background:transparent;color:var(--dm-text);font-size:1.05rem}.dm-hk-list{list-style:none;margin:0;padding:6px;overflow-y:auto}.dm-hk-item{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:var(--dm-radius-sm, 6px);cursor:pointer}.dm-hk-item.is-active{background:var(--dm-active-bg, var(--dm-hover-bg))}.dm-hk-icon{width:18px;display:inline-flex;justify-content:center;color:var(--dm-text-muted)}.dm-hk-name{flex:1;min-width:0;display:flex;flex-direction:column}.dm-hk-name small{color:var(--dm-text-muted);font-size:var(--dm-text-xs, .75rem)}.dm-hk-keys{display:inline-flex;gap:3px;flex:none}.dm-hk-panel kbd{display:inline-block;min-width:1.5em;padding:1px 5px;font:600 .72rem/1.4 var(--dm-font-mono, ui-monospace, monospace);text-align:center;color:var(--dm-text);background:var(--dm-background-alt, var(--dm-hover-bg));border:1px solid var(--dm-border);border-bottom-width:2px;border-radius:4px}.dm-hk-empty{padding:14px 10px;color:var(--dm-text-muted);list-style:none}.dm-hk-foot{display:flex;flex-wrap:wrap;align-items:center;gap:4px 6px;padding:8px 16px;border-top:1px solid var(--dm-border);color:var(--dm-text-muted);font-size:var(--dm-text-xs, .75rem)}.dm-hk-foot-end{margin-left:auto}.dm-hk-head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--dm-border)}.dm-hk-close{border:0;background:transparent;color:var(--dm-text-muted);cursor:pointer;padding:4px;border-radius:4px}.dm-hk-close:hover{background:var(--dm-hover-bg);color:var(--dm-text)}.dm-hk-cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:8px 24px;padding:12px 16px 16px;overflow-y:auto}.dm-hk-cols h4{margin:8px 0 4px;font-size:var(--dm-text-xs, .75rem);text-transform:uppercase;letter-spacing:.06em;color:var(--dm-text-muted)}.dm-hk-cols ul{list-style:none;margin:0;padding:0}.dm-hk-cols li{display:flex;align-items:center;gap:10px;padding:4px 0}.dm-hk-cols li .dm-hk-keys{width:4.6em}.dm-hk-cols li.dm-hk-empty{display:block;padding:4px 0;font-size:var(--dm-text-xs, .75rem)}.dm-hk-chip{position:fixed;left:50%;bottom:20px;z-index:10040;transform:translate(-50%);display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:6px 12px;width:max-content;max-width:min(920px,calc(100vw - 32px));padding:10px 14px;background:var(--dm-surface-raised, var(--dm-surface, #fff));color:var(--dm-text);border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 10px);box-shadow:var(--dm-shadow-lg, 0 12px 32px rgba(0, 0, 0, .3));font-size:var(--dm-text-sm, .85rem);pointer-events:none;animation:dm-hk-chip-in .12s ease-out}.dm-hk-chip kbd{display:inline-block;min-width:1.5em;padding:1px 5px;margin-right:4px;font:600 .72rem/1.4 var(--dm-font-mono, ui-monospace, monospace);text-align:center;color:var(--dm-text);background:var(--dm-background-alt, var(--dm-hover-bg));border:1px solid var(--dm-border);border-bottom-width:2px;border-radius:4px}.dm-hk-chip-lead{color:var(--dm-text-muted);font-weight:600}.dm-hk-chip-item{display:inline-flex;align-items:center;white-space:nowrap}.dm-hk-chip-sep{width:1px;align-self:stretch;background:var(--dm-border)}.dm-hk-chip-more{color:var(--dm-text-muted);white-space:nowrap}@keyframes dm-hk-chip-in{0%{opacity:0;transform:translate(-50%,6px)}to{opacity:1;transform:translate(-50%)}}@media(prefers-reduced-motion:reduce){.dm-hk-chip{animation:none}}.dm-hk-note{margin:0 0 6px;color:var(--dm-text-muted);font-size:var(--dm-text-xs, .75rem)}.dm-update-notice{position:fixed;left:50%;bottom:1rem;transform:translate(-50%);z-index:1090;display:flex;align-items:center;gap:.75rem;max-width:min(760px,calc(100vw - 2rem));padding:.75rem 1rem;background:var(--dm-surface);color:var(--dm-text);border:1px solid var(--dm-warning, #ffc107);border-left-width:4px;border-radius:var(--dm-radius-md, 8px);box-shadow:var(--dm-shadow-lg)}.dm-update-notice[hidden]{display:none!important}.dm-update-notice__text{flex:1 1 auto;font-size:.9rem}.dm-update-notice .btn{flex:0 0 auto}.topbar-sidebar-toggle{display:none;align-items:center;justify-content:center;flex:0 0 auto;width:2.25rem;height:2.25rem;padding:0;border:0;border-radius:6px;background:transparent;color:var(--dm-text, inherit);cursor:pointer}.topbar-sidebar-toggle:hover{background:var(--dm-hover-bg)}.topbar-sidebar-toggle:focus-visible{outline:2px solid var(--dm-primary);outline-offset:2px}.topbar-sidebar-toggle span[data-icon],.topbar-sidebar-toggle svg{width:20px;height:20px}.admin-sidebar-backdrop{display:none}.topbar-brand-logo{max-width:9rem}.view-header{flex-wrap:wrap}.view-header>*{min-width:0}.view-header h1{overflow-wrap:anywhere}@media(max-width:768px){#admin-topbar{gap:.5rem}.topbar-sidebar-toggle{display:inline-flex}.topbar-brand{margin-right:0}.topbar-user{min-width:0;gap:.4rem}.topbar-user-name{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.topbar-role-badge{flex-shrink:0;white-space:nowrap}.topbar-actions{gap:.15rem}.topbar-action-link{position:relative;padding:.5rem}.topbar-action-label{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}#topbar-bell .topbar-bell-badge{position:absolute;top:0;right:0}body #admin-sidebar.dashboard-sidebar{position:fixed!important;inset:60px auto 0 0!important;max-height:none!important;max-width:min(85vw,20rem);z-index:190;background:var(--dm-surface);box-shadow:var(--dm-shadow-lg);transform:translate(-105%)!important;visibility:hidden;transition:transform .2s ease,visibility 0s linear .2s}body.dm-sidebar-open #admin-sidebar.dashboard-sidebar{transform:none!important;visibility:visible;transition:transform .2s ease,visibility 0s}#admin-sidebar .dm-admin-sidebar-handle,#admin-sidebar .dm-admin-sidebar-collapse{display:none}body.dm-sidebar-open .admin-sidebar-backdrop{display:block;position:fixed;inset:60px 0 0;z-index:180;background:color-mix(in srgb,var(--dm-background, #000) 60%,transparent)}}@media(max-width:768px){.docs-body .table{display:block;max-width:100%;overflow-x:auto}.docs-body :not(pre)>code{overflow-wrap:anywhere}}@media(max-width:400px){.topbar-role-badge{display:none}}@media(prefers-reduced-motion:reduce){body #admin-sidebar.dashboard-sidebar{transition:none!important}}
1
+ .dashboard-layout{display:flex;flex-direction:column;height:100vh;min-height:100vh;overflow:hidden}.dashboard-wrapper{display:flex;flex:1;min-height:0;overflow:hidden}.dashboard-main{flex:1;min-width:0;overflow-y:auto}#admin-sidebar.dashboard-sidebar{display:flex;flex-direction:column;flex-shrink:0;overflow:hidden}#admin-sidebar .dm-admin-sidebar{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--dm-border) transparent}#admin-sidebar .dm-admin-sidebar::-webkit-scrollbar{width:8px}#admin-sidebar .dm-admin-sidebar::-webkit-scrollbar-track{background:transparent}#admin-sidebar .dm-admin-sidebar::-webkit-scrollbar-thumb{background:var(--dm-border);border-radius:4px}#admin-sidebar .dm-admin-sidebar::-webkit-scrollbar-thumb:hover{background:var(--dm-text-muted)}.view-container{padding:1.5rem 2rem;max-width:1200px}.view-container:has(>.dm-plugin-shell){max-width:100%}.view-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;gap:1rem}.view-header h1{font-size:1.5rem;font-weight:700;margin:0;display:flex;align-items:center;gap:.5rem}.view-header-actions{display:flex;gap:.75rem;flex-wrap:wrap}.view-header-actions .btn-primary,.view-header-actions .btn-success,.view-header-actions .btn-danger{margin-left:.5rem}.stat-card .card-body{display:flex;align-items:center;gap:1rem}.stat-icon{width:44px;height:44px;border-radius:8px;background:color-mix(in srgb,var(--dm-primary) 14%,transparent);color:var(--dm-primary, #5b8cff);display:flex;align-items:center;justify-content:center;font-size:1.25rem;flex-shrink:0}.stat-icon--success{background:color-mix(in srgb,var(--dm-success) 15%,transparent);color:var(--dm-success)}.stat-icon--warning{background:color-mix(in srgb,var(--dm-warning) 15%,transparent);color:var(--dm-warning)}.stat-value{font-size:1.75rem;font-weight:700;line-height:1}.stat-label{font-size:.8rem;color:var(--dm-text-muted, #888);margin-top:.25rem}.toolbar{display:flex;align-items:center;gap:.75rem}.form-check-label{display:flex;align-items:center;gap:.5rem;cursor:pointer;font-size:.9rem}.form-hint{display:block;font-size:.8rem;color:var(--dm-text-muted, #888);margin-top:.25rem}.dashboard-main:has(#pe-workspace){overflow:hidden}.view-container:has(#pe-workspace){display:flex;flex-direction:column;height:calc(100dvh - 60px);max-width:100%;padding:.6rem .75rem 0}.view-container:has(#pe-workspace)>.dm-plugin-shell{flex:1;min-height:0;display:flex;flex-direction:column}.view-container:has(#pe-workspace)>.dm-plugin-shell>.dm-plugin-banner{flex:none}.view-container:has(#pe-workspace)>.dm-plugin-shell>.dm-plugin-body{flex:1;min-height:0;display:flex;flex-direction:column}.pe-ws{flex:1;min-height:0;display:grid;grid-template-columns:var(--pe-explorer-w, 15rem) minmax(0,1fr) var(--pe-dock-w, 22rem);border:1px solid var(--dm-border);border-radius:var(--dm-radius-md);background:var(--dm-surface, var(--dm-background));overflow:hidden}.pe-ws:not(.pe-ws--dock){grid-template-columns:var(--pe-explorer-w, 15rem) minmax(0,1fr)}.pe-ws--no-explorer{grid-template-columns:minmax(0,1fr) var(--pe-dock-w, 22rem)}.pe-ws--no-explorer:not(.pe-ws--dock){grid-template-columns:minmax(0,1fr)}.pe-explorer,.pe-main,.pe-dock{display:flex;flex-direction:column;min-height:0;min-width:0}.pe-explorer{border-right:1px solid var(--dm-border);background:var(--dm-background-alt)}.pe-dock{border-left:1px solid var(--dm-border);background:var(--dm-background-alt)}.pe-ws--full{position:fixed;inset:0;z-index:1000;border-radius:0;border:0}@media(max-width:1024px){.pe-ws,.pe-ws:not(.pe-ws--dock),.pe-ws--no-explorer{grid-template-columns:minmax(0,1fr);position:relative}.pe-explorer,.pe-dock{position:absolute;top:0;bottom:0;width:min(22rem,88%);z-index:5;box-shadow:0 0 24px #00000059}.pe-explorer{left:0}.pe-dock{right:0}.pe-ws--full .pe-explorer,.pe-ws--full .pe-dock{position:fixed}}.view-container:has(#ep-editor-tabs){max-width:100%}.ep-editor-grid{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(0,1fr) minmax(0,1.3fr);gap:1rem;align-items:start}.ep-editor-grid>.ep-tryit-col,.ep-editor-grid>.ep-results-col{position:sticky;top:1rem;display:flex;flex-direction:column;gap:1rem;min-width:0}@media(max-width:1500px){.ep-editor-grid{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}.ep-editor-grid>:first-child{grid-column:1 / -1}.ep-editor-grid>.ep-tryit-col,.ep-editor-grid>.ep-results-col{position:static}}@media(max-width:1024px){.ep-editor-grid{grid-template-columns:minmax(0,1fr)}}.live-preview-widths{display:inline-flex;border:1px solid var(--dm-border);border-radius:var(--dm-radius-sm);overflow:hidden}.live-preview-width{display:inline-flex;align-items:center;justify-content:center;padding:.3rem .55rem;border:none;background:transparent;color:inherit;cursor:pointer;line-height:1}.live-preview-width+.live-preview-width{border-left:1px solid var(--dm-border)}.live-preview-width:hover{background:var(--dm-hover-bg)}.live-preview-width.active{background:var(--dm-active-bg)}.live-preview-status{flex:1 1 auto;font-size:.8rem;opacity:.75}.page-live-preview-frame{display:block;width:100%;max-width:100%;height:100%;border:none;border-radius:6px;background:var(--dm-background);transition:max-width .18s ease}.page-live-preview-frame[data-width="1024"]{max-width:1024px}.page-live-preview-frame[data-width="390"]{max-width:390px}.page-live-preview-frame[data-width="1024"],.page-live-preview-frame[data-width="390"]{border-left:1px solid var(--dm-border);border-right:1px solid var(--dm-border)}.editor-toolbar{display:flex;align-items:center;gap:2px;padding:6px 10px;border-bottom:1px solid var(--dm-border, rgba(255, 255, 255, .08));flex-wrap:wrap;flex-shrink:0}.editor-toolbar-btn{background:none;border:none;color:var(--dm-text-muted, #888);padding:6px 8px;border-radius:4px;cursor:pointer;display:flex;align-items:center;transition:color .15s,background .15s}.editor-toolbar-btn:hover{color:var(--dm-text, #eee);background:var(--dm-hover-bg)}.editor-toolbar-sep{width:1px;height:20px;background:var(--dm-border, rgba(255, 255, 255, .08));margin:0 4px;flex-shrink:0}.editor-toolbar-right{margin-left:auto;display:flex;align-items:center;gap:2px}.editor-view-btn{background:none;border:none;color:var(--dm-text-muted, #888);padding:6px 8px;border-radius:4px;cursor:pointer;display:flex;align-items:center;transition:color .15s,background .15s}.editor-view-btn:hover{color:var(--dm-text, #eee);background:var(--dm-hover-bg)}.editor-view-btn.active{color:var(--dm-primary, #5b8cff);background:color-mix(in srgb,var(--dm-primary) 14%,transparent)}.editor-body{display:flex;flex:1;min-height:0;--dm-editor-split: 50%}@media(min-width:769px){.editor-body:not(.editor-mode-write):not(.editor-mode-preview) .editor-pane--write{flex:0 0 var(--dm-editor-split, 50%)}.editor-body:not(.editor-mode-write):not(.editor-mode-preview) .editor-pane--preview{flex:1 1 0}}.editor-pane{flex:1;display:flex;flex-direction:column;min-width:0}.editor-pane--write,.editor-pane--preview{overflow:hidden}.editor-pane--write{flex-direction:row}.editor-line-numbers{padding:1rem .6rem 1rem .75rem;background:var(--dm-background-alt, rgba(0, 0, 0, .15));border-right:1px solid var(--dm-border, rgba(255, 255, 255, .08));color:var(--dm-text-muted, #666);font-family:Fira Code,Courier New,monospace;font-size:.9rem;line-height:1.6;text-align:right;user-select:none;white-space:pre;overflow:hidden;flex-shrink:0;min-width:2.5rem}.editor-line-numbers--foldable{padding:1rem 0;min-width:3rem;white-space:normal;text-align:left;display:flex;flex-direction:column}.editor-line-number-row{display:flex;align-items:center;justify-content:flex-end;padding-right:.6rem;gap:.15rem}.fold-toggle{display:inline-flex;align-items:center;justify-content:center;width:1em;font-size:.6rem;cursor:default;color:transparent}.fold-toggle--open,.fold-toggle--folded{cursor:pointer;color:var(--dm-text-muted, #666);transition:color .1s}.fold-toggle--open:hover,.fold-toggle--folded:hover{color:var(--dm-primary, #4a9eff)}.editor-line-num{min-width:1.5rem;text-align:right}.editor-mode-write .editor-pane--preview,.editor-mode-write .editor-divider,.editor-mode-preview .editor-pane--write,.editor-mode-preview .editor-divider{display:none}.editor-divider{width:7px;background:linear-gradient(to right,transparent 0,transparent 3px,var(--dm-border, rgba(255,255,255,.08)) 3px,var(--dm-border, rgba(255,255,255,.08)) 4px,transparent 4px,transparent 100%);cursor:col-resize;flex-shrink:0;touch-action:none;transition:background .12s ease}.editor-divider:hover,.editor-divider:focus-visible,.editor-divider.is-dragging{background:linear-gradient(to right,transparent 0,transparent 2px,var(--dm-primary, #5b8cff) 2px,var(--dm-primary, #5b8cff) 5px,transparent 5px,transparent 100%);outline:none}.editor-body.is-resizing .editor-pane{pointer-events:none}.editor-body.is-resizing{cursor:col-resize;user-select:none}.editor-textarea{flex:1;resize:none;border:none;border-radius:0;background:transparent;font-family:Fira Code,Courier New,monospace;font-size:.9rem;padding:1rem;outline:none;color:inherit;line-height:1.6}#editor-body .editor-textarea,#editor-body .editor-line-numbers{font-family:var(--pe-font, "Fira Code", "Courier New", monospace);font-size:var(--pe-size, .9rem);line-height:var(--pe-line, 1.6)}#editor-body.pe-nowrap .editor-textarea{white-space:pre;overflow-x:auto}.editor-preview{flex:1;display:block;width:100%;border:none;background:var(--dm-background)}.media-picker-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:.75rem;max-height:400px;overflow-y:auto;padding:.25rem}.media-picker-item{cursor:pointer;border:2px solid transparent;border-radius:6px;overflow:hidden;transition:border-color .15s}.media-picker-item:hover{border-color:var(--dm-primary, #5b8cff)}.media-picker-item img{width:100%;height:80px;object-fit:cover;display:block}.media-picker-item span{display:block;font-size:.75rem;padding:4px 6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.docs-body h3{margin-top:1.25rem;margin-bottom:.5rem}.docs-body p{margin-bottom:.5rem;line-height:1.6}.docs-body ol,.docs-body ul{margin-bottom:.75rem;padding-left:1.25rem}.docs-body li{margin-bottom:.25rem}.docs-body hr{margin:1.25rem 0;border-color:var(--dm-border, rgba(255, 255, 255, .08))}.docs-body .table{margin-bottom:.75rem}.docs-body h2{margin-top:1.75rem;margin-bottom:.6rem;padding-bottom:.3rem;border-bottom:1px solid var(--dm-border)}.docs-body h2:first-child{margin-top:0}.docs-body blockquote{margin:.75rem 0;padding:.5rem .9rem;border-left:3px solid var(--dm-primary);background:var(--dm-surface);border-radius:var(--dm-radius, 6px)}.docs-body pre{margin-bottom:.75rem;overflow-x:auto}.pg-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:1rem;margin-top:1.5rem}.pg-grid>.card.pg-card{display:block;margin:0;text-decoration:none}.pg-card h3{margin-top:0}.pg-card .text-muted,.pg-card-pages{margin-bottom:0}.pg-card-pages{margin-top:.5rem;font-size:.8rem;color:var(--dm-text-muted)}.pg-actions{display:flex;gap:.5rem;flex-wrap:wrap}.pg-meta{margin-bottom:.75rem}.pg-pages{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:1rem}.pg-body{max-width:80ch}.nav-items-header,.nav-item-row{display:flex;gap:.5rem;align-items:center;padding:.35rem .75rem}.nav-items-header{border-bottom:1px solid var(--dm-border, rgba(255,255,255,.08));padding-bottom:.4rem;margin-bottom:.25rem}.nav-item-row{border-bottom:1px solid var(--dm-border, rgba(255,255,255,.04))}.nav-item-row:last-child{border-bottom:none}.nav-col-indent{width:18px;flex-shrink:0;color:var(--dm-text-muted, #888);text-align:center;font-size:.85rem}.nav-col-main{flex:2;min-width:0}.nav-col-icon{flex:0 0 90px}.nav-col-parent{flex:2;min-width:0}.nav-col-action{flex-shrink:0}.nav-item-row--child{background:color-mix(in srgb,var(--dm-text) 3%,transparent)}.nav-col-action{display:flex;align-items:center;gap:.25rem}.nav-item-row--hidden{opacity:.45}.nav-item-row--hidden .item-text,.nav-item-row--hidden .footer-link-text{text-decoration:line-through}.btn-toggle-hidden{color:var(--dm-text-muted, #888)}.btn-toggle-hidden.active{color:var(--dm-warning, #f59e0b)}.fb-drag-grip{flex-shrink:0;display:inline-flex;align-items:center;color:var(--dm-text-muted);cursor:grab}.fb-drag-grip:hover{color:var(--dm-text)}.btn-ghost span[data-icon=trash]{color:var(--dm-danger, #dc2626)}.view-container:has(.fb-fields-layout){max-width:none}.fb-fields-layout{display:grid;grid-template-columns:minmax(0,6fr) minmax(320px,4fr);gap:1.25rem;align-items:start}.fb-preview-pane{position:sticky;top:1rem;min-width:0}@media(max-width:1100px){.fb-fields-layout{grid-template-columns:1fr}.fb-preview-pane{position:static}}.toggle-label{display:flex;align-items:center;gap:.4rem;font-size:.9rem;cursor:pointer;margin-right:1rem}.preset-toggles{display:flex;flex-wrap:wrap;margin-top:.75rem}.presets-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem}@media(max-width:900px){.presets-grid{grid-template-columns:repeat(2,1fr)}}@media(max-width:600px){.presets-grid{grid-template-columns:1fr}}.card-header{padding:.5rem 1rem}.card-header h2{margin:0;font-size:.9rem;font-weight:600;letter-spacing:.02em}.preset-card .card-header{display:flex;align-items:center;justify-content:space-between}.preset-card .card-header h3{margin:0;font-size:1rem}.media-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:1rem}.media-card{background:var(--dm-card-bg, rgba(255,255,255,.04));border:1px solid var(--dm-border, rgba(255,255,255,.08));border-radius:8px;overflow:hidden}.media-preview{height:130px;display:flex;align-items:center;justify-content:center;background:#0003;overflow:hidden}.media-thumb{width:100%;height:100%;object-fit:cover}.media-thumb--file{font-size:2rem;color:var(--dm-text-muted, #888)}.media-info{padding:.5rem .75rem;display:flex;flex-direction:column;gap:.15rem}.media-name{font-size:.8rem;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:default}.media-rename-input{width:100%;font-size:.8rem;font-weight:500;padding:.1rem .25rem;border:1px solid var(--dm-primary, #7c6af7);border-radius:3px;background:var(--dm-input-bg, transparent);color:inherit;outline:none}.media-size{font-size:.75rem;color:var(--dm-text-muted, #888)}.media-actions{padding:.5rem .75rem;display:flex;gap:.4rem;border-top:1px solid var(--dm-border, rgba(255,255,255,.08))}#admin-topbar{height:60px;display:flex;align-items:center;gap:1rem;padding:0 1.25rem;background:var(--dm-navbar-bg, rgba(0,0,0,.3));border-bottom:1px solid var(--dm-border, rgba(255,255,255,.08));position:sticky;top:0;z-index:200;flex-shrink:0}.topbar-brand{display:flex;align-items:center;gap:.5rem;font-weight:700;font-size:.95rem;color:inherit;flex-shrink:0;margin-right:.5rem}.topbar-brand span[data-icon],.topbar-brand svg{color:var(--dm-primary, #5b8cff);width:22px;height:22px}.topbar-brand-text{opacity:.85}.topbar-brand-logo{height:28px;width:auto;display:inline-block;vertical-align:middle;margin-right:.4em;object-fit:contain}.topbar-user{display:flex;align-items:center;gap:.6rem;flex:1}.topbar-user-name{font-size:.875rem;font-weight:500}.topbar-avatar{width:1.75rem;height:1.75rem;border-radius:50%;object-fit:cover;flex-shrink:0}.topbar-role-badge{font-size:.7rem;font-weight:600;letter-spacing:.04em;text-transform:uppercase;padding:.15rem .45rem;border-radius:4px}.topbar-role-badge--admin{background:color-mix(in srgb,var(--dm-primary) 15%,transparent);color:var(--dm-primary)}.topbar-role-badge--manager{background:color-mix(in srgb,var(--dm-success) 15%,transparent);color:var(--dm-success)}.topbar-role-badge--editor{background:color-mix(in srgb,var(--dm-warning) 15%,transparent);color:var(--dm-warning)}.topbar-role-badge--subscriber{background:color-mix(in srgb,var(--dm-text-muted) 15%,transparent);color:var(--dm-text-muted)}.topbar-actions{display:flex;align-items:center;gap:.5rem;flex-shrink:0}.topbar-action-link{display:flex;align-items:center;gap:.35rem;font-size:.875rem;color:var(--dm-text-muted, #888);text-decoration:none;padding:.4rem .65rem;border-radius:6px;transition:color .15s,background .15s}.topbar-action-link:hover{color:var(--dm-text, #eee);background:var(--dm-hover-bg)}.topbar-action-link span[data-icon],.topbar-action-link svg{width:16px;height:16px}.topbar-signout{display:flex;align-items:center;gap:.35rem;font-size:.875rem}.topbar-signout span[data-icon],.topbar-signout svg{width:16px;height:16px}.login-wrap{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;padding:1rem;background:var(--dm-background, #0f1117);z-index:100;overflow-y:auto}.login-card{width:100%;max-width:460px}.ob-done-icon{font-size:3rem;color:var(--dm-success);margin-bottom:1rem;text-align:center}.btn-skip{display:block;text-align:center;margin-top:.75rem;font-size:.85rem;color:var(--dm-text-muted, #888);text-decoration:none}.btn-skip:hover{text-decoration:underline}.theme-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:.6rem}.theme-swatch{border:2px solid var(--dm-border, rgba(255,255,255,.1));border-radius:8px;overflow:hidden;cursor:pointer;transition:border-color .15s,transform .1s}.theme-swatch:hover{border-color:var(--dm-primary, #5b8cff);transform:translateY(-1px)}.theme-swatch.selected{border-color:var(--dm-primary, #5b8cff);box-shadow:0 0 0 2px var(--dm-primary, #5b8cff)}.theme-swatch-preview{height:52px;position:relative;display:flex;align-items:flex-end;padding:.35rem}.theme-swatch-accent{width:28px;height:6px;border-radius:3px}.theme-swatch-label{display:flex;justify-content:space-between;align-items:center;padding:.3rem .45rem;font-size:.7rem;background:var(--dm-card-bg, rgba(255,255,255,.04))}.theme-swatch-mode{color:var(--dm-text-muted, #888);font-size:.65rem}.login-logo{display:flex;align-items:center;gap:.75rem;margin-bottom:1.75rem;font-size:1.5rem}.login-logo span[data-icon]{font-size:2rem;color:var(--dm-primary, #5b8cff)}.login-logo h1{margin:0;font-size:1.5rem;font-weight:700}.login-card [hidden]{display:none!important}.login-heading{font-size:1.1rem;font-weight:600;margin-bottom:1.25rem}.login-rules{margin:.25rem 0 .75rem;padding-left:1.1rem;color:var(--dm-danger);font-size:.85rem}.login-rules li+li{margin-top:.2rem}.login-small{font-size:.8rem;margin-top:.75rem}.login-icon-warn{color:var(--dm-warning)}.pwf{position:relative;display:block}.pwf>input{width:100%;padding-right:2.5rem}.pwf-toggle{position:absolute;top:50%;right:.35rem;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;padding:0;border:0;border-radius:var(--dm-radius-sm);background:transparent;color:var(--dm-text-muted);cursor:pointer}.pwf-toggle:hover,.pwf-toggle:focus-visible{color:var(--dm-text);background:var(--dm-hover-bg)}.pwf-toggle svg{width:16px;height:16px}.pwf-toggle [data-icon=eye-off],.pwf.is-shown .pwf-toggle [data-icon=eye]{display:none}.pwf.is-shown .pwf-toggle [data-icon=eye-off]{display:inline-block}.pwf-caps{display:block;margin-top:.25rem;color:var(--dm-warning);font-size:.8rem}.pwf-caps[hidden]{display:none}.btn-block{width:100%}.plugins-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1rem}.plugin-card{display:flex;flex-direction:column;--plugin-accent: var(--dm-primary, #5b8cff);position:relative;overflow:hidden;background-image:radial-gradient(120% 90% at 100% 0%,color-mix(in srgb,var(--plugin-accent) 13%,transparent) 0%,color-mix(in srgb,var(--plugin-accent) 4%,transparent) 42%,transparent 72%)}.plugin-card .plugin-icon{background:color-mix(in srgb,var(--plugin-accent) 16%,transparent);color:var(--plugin-accent)}.plugin-watermark{position:absolute;top:-34px;right:-30px;width:148px;height:148px;display:block;color:var(--plugin-accent);opacity:.09;pointer-events:none;z-index:0}.plugin-card>.card-body,.plugin-card>.plugin-footer{position:relative;z-index:1}.plugin-card:has(.badge-secondary) .plugin-watermark{opacity:.05}@media(prefers-reduced-motion:no-preference){.plugin-card{transition:background-image .2s ease}}.plugin-card .card-body{flex:1}.plugin-header{display:flex;align-items:flex-start;gap:.75rem;margin-bottom:.75rem;flex-wrap:wrap}.plugin-icon{width:40px;height:40px;border-radius:8px;background:color-mix(in srgb,var(--dm-primary) 14%,transparent);color:var(--dm-primary, #5b8cff);display:flex;align-items:center;justify-content:center;flex-shrink:0}.plugin-meta{flex:1 1 140px;min-width:0}.plugin-name{font-weight:600;margin-bottom:.15rem}.plugin-version{font-size:.75rem;color:var(--dm-text-muted, #888)}.plugin-desc{font-size:.875rem;color:var(--dm-text-muted, #888);margin-bottom:1rem}.plugin-footer{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1rem;border-top:1px solid var(--dm-border, rgba(255,255,255,.08))}.plugin-footer-actions{display:flex;align-items:center;gap:.5rem}#admin-sidebar .sidebar-link{position:relative!important}#admin-sidebar .sidebar-badge{position:absolute!important;right:.75rem!important;top:50%!important;transform:translateY(-50%)!important;margin-left:0!important;padding-left:.45rem!important;padding-right:.45rem!important}#admin-sidebar .sidebar-text{padding-right:2rem!important}#admin-sidebar.sidebar-dark{background:var(--dm-surface);border-color:var(--dm-border)}#admin-sidebar.sidebar-dark .sidebar-header{background:var(--dm-surface-raised);border-color:var(--dm-border)}#admin-sidebar.sidebar-dark .sidebar-header-title{color:var(--dm-text)}#admin-sidebar.sidebar-dark .sidebar-link{color:var(--dm-text-muted)}#admin-sidebar.sidebar-dark .sidebar-link:hover{color:var(--dm-text);background:var(--dm-surface-overlay)}#admin-sidebar.sidebar-dark .sidebar-link.active{color:var(--dm-primary);background:color-mix(in srgb,var(--dm-primary) 14%,transparent);border-left-color:var(--dm-primary)}#admin-sidebar.sidebar-dark .sidebar-heading{color:var(--dm-text-muted)}#admin-sidebar.sidebar-dark .sidebar-divider{background:var(--dm-border)}#admin-sidebar.sidebar-dark .sidebar-footer{background:var(--dm-surface-raised);border-color:var(--dm-border)}.sidebar-heading--collapsible{display:flex!important;align-items:center;justify-content:space-between;cursor:pointer;user-select:none}.sidebar-heading-toggle{display:flex;align-items:center;opacity:.5;flex-shrink:0;transition:transform .2s ease}.sidebar-heading--collapsible.is-collapsed .sidebar-heading-toggle{transform:rotate(-90deg)}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.p-0{padding:0!important}.text-muted{color:var(--dm-text-muted, #888)}@media(max-width:768px){.view-container{padding:1rem}.editor-body{flex-direction:column}.editor-divider{width:auto;height:1px;cursor:default;background:var(--dm-border, rgba(255,255,255,.08))}}.image-editor{display:flex;flex-direction:column;gap:0}.image-editor-toolbar{display:flex;flex-direction:row;align-items:center;gap:.25rem;padding:.5rem .75rem;border-bottom:1px solid var(--dm-border, rgba(255, 255, 255, .1))}.image-editor-sep{display:inline-block;width:1px;height:1.5rem;background:var(--dm-border, rgba(255, 255, 255, .15));margin:0 .25rem;flex-shrink:0}.editor-toolbar-btn.active{color:var(--dm-primary, #7c6af7);background:color-mix(in srgb,var(--dm-primary, #7c6af7) 15%,transparent)}.editor-toolbar-caret{font-size:.6rem;line-height:1;margin-left:2px;opacity:.6}.editor-toolbar-dropdown{position:absolute;z-index:1000;background:var(--dm-surface, #1e1e2e);border:1px solid var(--dm-border, rgba(255, 255, 255, .1));border-radius:8px;box-shadow:0 8px 24px #0006;padding:4px;min-width:160px;max-height:70vh;overflow-y:auto}.editor-toolbar-dropdown-heading{padding:6px 10px 3px;color:var(--dm-text-muted, #8a8a9a);font-size:.66rem;text-transform:uppercase;letter-spacing:.06em}.editor-toolbar-dropdown-item{display:flex;align-items:center;gap:8px;width:100%;text-align:left;background:none;border:none;color:var(--dm-text, #ddd);padding:7px 10px;border-radius:5px;cursor:pointer;font-size:.85rem;transition:background .12s,color .12s}.editor-toolbar-dropdown-item:hover{background:var(--dm-hover-bg);color:var(--dm-text, #fff)}.editor-toolbar-dropdown-item span[data-icon],.editor-toolbar-dropdown-item svg{width:15px;height:15px;flex-shrink:0;opacity:.75}.editor-toolbar{position:relative}.editor-effects-dropdown-menu{position:absolute;top:100%;z-index:1000;background:var(--dm-surface, #1e1e2e);border:1px solid var(--dm-border, rgba(255, 255, 255, .1));border-radius:8px;box-shadow:0 8px 24px #0006;padding:6px;min-width:180px;max-height:340px;overflow-y:auto}.editor-effects-category{padding:6px 8px 3px;font-size:.7rem;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--dm-text-muted, #888)}.editor-effects-item{display:block;width:100%;text-align:left;background:none;border:none;color:var(--dm-text, #ddd);padding:6px 10px;border-radius:5px;cursor:pointer;font-size:.85rem;transition:background .12s,color .12s}.editor-effects-item:hover{background:var(--dm-hover-bg);color:var(--dm-text, #fff)}.editor-icon-picker{position:absolute;z-index:1000;background:var(--dm-surface, #1e1e2e);border:1px solid var(--dm-border, rgba(255, 255, 255, .1));border-radius:8px;box-shadow:0 8px 24px #0006;padding:8px;width:320px}.editor-icon-picker-search{width:100%;margin-bottom:8px;font-size:.8rem}.editor-spacer-picker{position:absolute;z-index:9999;background:var(--dm-surface, #1e1e2e);border:1px solid var(--dm-border, #333);border-radius:8px;padding:12px;width:240px;box-shadow:0 8px 24px #00000073}.editor-spacer-picker-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--dm-text-muted, #888);margin-bottom:10px}.editor-spacer-picker-row{display:flex;align-items:center;gap:10px;margin-bottom:12px}.editor-spacer-slider{flex:1;accent-color:var(--dm-primary, #6366f1);cursor:pointer}.editor-spacer-slider-value{font-size:13px;font-weight:600;min-width:38px;text-align:right;color:var(--dm-text, #cdd6f4)}.editor-spacer-insert-btn{width:100%}.editor-icon-picker-size-row{display:flex;align-items:center;gap:8px;padding:0 8px 8px}.editor-icon-picker-size-row label{font-size:11px;color:var(--dm-text-muted, #888);white-space:nowrap}.editor-icon-picker-size{width:70px;font-size:12px;padding:3px 6px}.editor-icon-picker-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(52px,1fr));gap:2px;max-height:280px;overflow-y:auto}.editor-icon-picker-item{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:6px 2px;border-radius:5px;cursor:pointer;background:none;border:none;color:var(--dm-text, #ddd);font-size:.58rem;text-align:center;gap:4px;overflow:hidden;transition:background .12s,color .12s}.editor-icon-picker-item:hover{background:var(--dm-hover-bg);color:var(--dm-text, #fff)}.editor-icon-picker-item span[data-icon],.editor-icon-picker-item svg{width:16px;height:16px;flex-shrink:0}.editor-icon-picker-item>span:last-child{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.2}.editor-icon-picker-empty{grid-column:1 / -1;text-align:center;color:var(--dm-text-muted, #888);font-size:.8rem;padding:1.5rem}.image-editor-canvas{min-height:320px;max-height:520px;background:#111;overflow:hidden;display:flex;align-items:center;justify-content:center}.image-editor-canvas img{display:block;max-width:100%;max-height:520px}.image-editor-resize{display:flex;flex-direction:row;align-items:center;gap:.5rem;padding:.6rem .75rem;border-top:1px solid var(--dm-border, rgba(255, 255, 255, .1));font-size:.85rem}.image-editor-resize label{font-weight:600;color:var(--dm-text-muted, #888);font-size:.8rem}.image-editor-resize .form-input{width:5rem;padding:.25rem .5rem;font-size:.85rem}.image-editor-footer{display:flex;flex-direction:row;align-items:center;justify-content:space-between;padding:.75rem;border-top:1px solid var(--dm-border, rgba(255, 255, 255, .1));gap:.5rem}.image-editor-effects{border-top:1px solid var(--dm-border, rgba(255, 255, 255, .1))}.image-editor-tab-bar{display:flex;flex-direction:row;overflow-x:auto;border-bottom:1px solid var(--dm-border, rgba(255, 255, 255, .1));scrollbar-width:none}.image-editor-tab-bar::-webkit-scrollbar{display:none}.image-editor-tab-btn{display:inline-flex;align-items:center;gap:.35rem;padding:.5rem .85rem;font-size:.8rem;font-weight:500;border:none;border-bottom:2px solid transparent;background:transparent;cursor:pointer;color:var(--dm-text-muted, #888);white-space:nowrap;transition:color .15s,border-color .15s;flex-shrink:0}.image-editor-tab-btn:hover{color:var(--dm-text, #eee)}.image-editor-tab-btn.active{color:var(--dm-primary, #7c6af7);border-bottom-color:var(--dm-primary, #7c6af7)}.image-editor-tab-panel{padding:.75rem;max-height:200px;overflow-y:auto}.ie-presets-grid{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.5rem}.ie-preset-btn.active{color:var(--dm-primary, #7c6af7);background:color-mix(in srgb,var(--dm-primary, #7c6af7) 15%,transparent);border-color:var(--dm-primary, #7c6af7)}.ie-server-note{font-size:.73rem;color:var(--dm-text-muted, #888);font-style:italic;margin:.25rem 0 0;line-height:1.4}.ie-slider-row{display:flex;align-items:center;gap:.5rem;margin-bottom:.4rem}.ie-slider-label{width:6rem;font-size:.8rem;color:var(--dm-text-muted, #888);font-weight:600;flex-shrink:0}.ie-slider{flex:1;accent-color:var(--dm-primary, #7c6af7);cursor:pointer}.ie-slider-val{width:3rem;font-size:.8rem;text-align:right;color:var(--dm-text, #eee);flex-shrink:0}.ie-select{flex:1;font-size:.85rem;padding:.25rem .5rem}.ie-colour-input{width:40px;height:28px;padding:0;border:1px solid var(--dm-border, rgba(255, 255, 255, .15));border-radius:4px;cursor:pointer;background:transparent;flex-shrink:0}.ie-wm-preview-row{display:flex;align-items:center;gap:.5rem;margin:.4rem 0}.ie-wm-preview-img{width:48px;height:48px;object-fit:contain;border-radius:4px;background:var(--dm-surface-raised, rgba(255, 255, 255, .06));flex-shrink:0}.ie-wm-preview-name{font-size:.8rem;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dm-text, #eee)}.ie-media-picker{padding:.25rem 0}.ie-media-picker-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(80px,1fr));gap:.5rem;max-height:300px;overflow-y:auto}.ie-media-thumb{display:flex;flex-direction:column;align-items:center;gap:.25rem;padding:.4rem;border-radius:6px;cursor:pointer;border:2px solid transparent;transition:border-color .15s,background .15s}.ie-media-thumb:hover{background:var(--dm-surface-raised, rgba(255, 255, 255, .08));border-color:var(--dm-primary, #7c6af7)}.ie-media-thumb img{width:72px;height:72px;object-fit:cover;border-radius:4px}.ie-media-thumb-name{font-size:.7rem;color:var(--dm-text-muted, #888);text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:72px}.dm-slideover-left,.dm-slideover-right{width:min(40rem,95vw)!important;min-width:min(36rem,100vw)!important;max-width:100vw!important}.dm-slideover-resizer{position:absolute;top:0;bottom:0;width:6px;cursor:col-resize;z-index:2;background:transparent}.dm-slideover-right>.dm-slideover-resizer{left:0}.dm-slideover-left>.dm-slideover-resizer{right:0}.dm-slideover-resizer:hover,.is-resizing>.dm-slideover-resizer{background:var(--dm-primary);opacity:.7}.dm-slideover.is-resizing{user-select:none;transition:none!important}.dm-slideover-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;border-bottom:1px solid var(--dm-border, #333);flex-shrink:0}.dm-slideover-title{margin:0;font-size:1rem;font-weight:600}.dm-slideover-close{padding:.2rem .35rem!important;line-height:1;font-size:.75rem}.dm-slideover-body{flex:1;overflow:auto;overscroll-behavior:contain;min-height:0;padding:1.5rem!important;box-sizing:border-box}.dm-slideover-body:empty{flex:0 0 auto;padding:0!important}.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-body):not(.dm-slideover-close){padding:1.5rem!important;box-sizing:border-box;flex:1 1 auto;min-height:0;overflow:auto;overscroll-behavior:contain}.dm-slideover-body,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close){scrollbar-width:thin;scrollbar-color:var(--dm-border) transparent}.dm-slideover-body::-webkit-scrollbar,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close)::-webkit-scrollbar{width:8px;height:8px}.dm-slideover-body::-webkit-scrollbar-track,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close)::-webkit-scrollbar-track{background:transparent}.dm-slideover-body::-webkit-scrollbar-thumb,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close)::-webkit-scrollbar-thumb{background:var(--dm-border);border-radius:4px}.dm-slideover-body::-webkit-scrollbar-thumb:hover,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close)::-webkit-scrollbar-thumb:hover{background:var(--dm-text-muted)}.dm-slideover-body>div{padding-left:0!important;padding-right:0!important}.dm-slideover-body>button.btn:last-child{margin-top:.5rem}.pe-schema-example{margin-bottom:.5rem}.pe-schema-note{color:var(--dm-danger, #b91c1c)}.dconfig-textarea{font-family:monospace;min-height:100px;resize:vertical}.dm-editor-line-numbers{white-space:pre}.dm-code-inline{font-family:Fira Code,Courier New,monospace;font-size:.82em;padding:.15em .35em;border-radius:3px;background:var(--dm-background-alt, rgba(0, 0, 0, .18));color:var(--dm-text, inherit);border:1px solid var(--dm-border, rgba(255, 255, 255, .08))}.dm-code-block{font-family:Fira Code,Courier New,monospace;font-size:.82rem;line-height:1.6;padding:.65rem .9rem;border-radius:6px;background:var(--dm-background-alt, rgba(0, 0, 0, .18));color:var(--dm-text, inherit);border:1px solid var(--dm-border, rgba(255, 255, 255, .08));white-space:pre;overflow-x:auto;display:block;margin:.4rem 0}.tabs-centered{text-align:center}.tabs-centered .tab-list{display:inline-flex}.tabs-centered .tab-content{text-align:left}@media(max-width:768px){.view-header{flex-direction:column;align-items:flex-start}.theme-grid{grid-template-columns:repeat(2,1fr)}.nav-item-row{flex-wrap:wrap}.nav-col-icon,.nav-col-parent{display:none}#admin-topbar{padding:0 .75rem}.topbar-brand-text{display:none}.image-editor-toolbar{flex-wrap:wrap}}#topbar-bell{gap:.5rem}#topbar-bell .topbar-bell-badge{position:static;flex-shrink:0}.topbar-action-link[hidden],.topbar-bell-badge[hidden]{display:none}.topbar-bell-badge{position:absolute;top:2px;right:2px;min-width:16px;height:16px;padding:0 4px;background:var(--dm-danger, #dc2626);color:var(--dm-danger-text, #fff);font-size:10px;font-weight:700;line-height:16px;border-radius:9999px;text-align:center;pointer-events:none}.cb-scaffolder{padding:1rem 1.1rem;background:var(--dm-background-alt);border:1px solid var(--dm-primary);border-radius:8px;color:var(--dm-text)}.cb-scaffolder-head{display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem}.cb-scaffolder-head .cb-icon{flex-shrink:0;color:var(--dm-primary)}.cb-scaffolder-head h2{margin:0;font-size:1.05rem;font-weight:700;color:var(--dm-text)}.cb-scaffolder-blurb{margin:0 0 .75rem;font-size:.88rem;line-height:1.5;color:var(--dm-text-muted)}.cb-recipe-list{display:flex;flex-direction:column;gap:.5rem}.cb-recipe-card{display:flex;align-items:flex-start;gap:.75rem;text-align:left;padding:.75rem 1rem;background:var(--dm-surface);border:1px solid var(--dm-border);border-radius:6px;cursor:pointer;transition:border-color .12s;width:100%;color:var(--dm-text)}.cb-recipe-card:hover{border-color:var(--dm-primary)}.cb-recipe-card .cb-icon{flex-shrink:0;font-size:1.5rem;color:var(--dm-primary);margin-top:.1rem}.cb-recipe-meta{flex:1}.cb-recipe-name{font-weight:600;margin-bottom:.15rem;font-size:.95rem;color:var(--dm-text)}.cb-recipe-desc{font-size:.83rem;line-height:1.45;color:var(--dm-text-muted)}.cb-apply-panel{padding:1rem 1.1rem;background:var(--dm-surface);border:1px solid var(--dm-primary);border-radius:8px;color:var(--dm-text)}.cb-apply-head{display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem}.cb-apply-head .cb-icon{color:var(--dm-primary);font-size:1.4rem}.cb-apply-head h3{margin:0;font-size:1rem;font-weight:600;color:var(--dm-text)}.cb-apply-desc,.cb-apply-hint{margin:.25rem 0 .75rem;font-size:.85rem;line-height:1.5;color:var(--dm-text-muted)}.cb-apply-hint{font-size:.75rem;margin-top:.2rem}.cb-apply-field{margin-bottom:.6rem}.cb-apply-field label{display:block;font-size:.82rem;font-weight:600;margin-bottom:.2rem;color:var(--dm-text)}.cb-apply-field input{width:100%;padding:.4rem .55rem;border:1px solid var(--dm-border);border-radius:4px;font-size:.88rem;background:var(--dm-surface);color:var(--dm-text)}.cb-apply-status{margin:.5rem 0;font-size:.85rem;min-height:1.2rem;color:var(--dm-text)}.cb-apply-status.cb-error{color:var(--dm-danger, #dc2626)}.cb-apply-buttons{display:flex;gap:.5rem;justify-content:flex-end;margin-top:.5rem}.cb-success-panel{padding:1rem 1.1rem;background:var(--dm-surface);border:1px solid var(--dm-success, #16a34a);border-radius:8px;color:var(--dm-text)}.cb-success-panel .cb-icon{color:var(--dm-success, #16a34a);font-size:1.4rem}.cb-success-summary{margin:.25rem 0 .5rem;padding-left:1.2rem;font-size:.86rem;line-height:1.6;color:var(--dm-text)}.cb-success-warning{margin:.5rem 0;padding:.5rem .75rem;background:var(--dm-background-alt);border-left:3px solid var(--dm-warning, #eab308);border-radius:0 4px 4px 0;font-size:.82rem;color:var(--dm-text-muted)}.cb-success-snippet-label{margin:0 0 .25rem;font-size:.82rem;font-weight:600;color:var(--dm-text)}.cb-success-actions{display:flex;gap:.5rem;margin-top:.5rem;flex-wrap:wrap}.cb-code{position:relative}.cb-code pre{background:var(--dm-background-alt);color:var(--dm-text);display:block;padding:.6rem 2.4rem .6rem .75rem;white-space:pre;overflow-x:auto;margin:0;border-radius:6px;font-size:.82em;font-family:monospace;line-height:1.45}.cb-code .cb-copy{position:absolute;top:.35rem;right:.35rem;background:none;border:none;cursor:pointer;opacity:.45;padding:.2rem;line-height:1;color:var(--dm-text)}.cb-code .cb-copy:hover{opacity:1}.cb-intro{padding:.75rem 1rem;border-left:3px solid var(--dm-primary);background:var(--dm-background-alt);border-radius:0 6px 6px 0}.cb-intro p{margin:0;font-size:.92rem;line-height:1.5;color:var(--dm-text)}.cb-tutorial-link{display:flex;align-items:center;gap:.6rem;padding:.65rem .85rem;border:1px solid var(--dm-border);border-radius:6px;text-decoration:none;color:var(--dm-text);font-size:.88rem;transition:border-color .12s}.cb-tutorial-link:hover{border-color:var(--dm-primary)}.cb-tutorial-link .cb-icon{flex-shrink:0;color:var(--dm-primary)}.cb-tutorial-link .cb-arrow{color:var(--dm-text-muted);font-size:1.1rem;margin-left:auto}.menu-tree-row{display:grid;grid-template-columns:26px 1.4fr 1.4fr .9fr .7fr .9fr repeat(6,auto);gap:6px;align-items:center;padding:4px 0;transition:opacity .15s ease}.menu-tree-row--hidden{opacity:.5}.menu-tree-row--tool .me-tool-badge{margin-left:.4rem;font-size:.7rem;vertical-align:middle}.menu-tree-row--default .me-row-label{font-style:italic;opacity:.8}.menu-tree-grip{cursor:grab;color:var(--dm-text-muted)}.menu-tree-grip:hover{color:var(--dm-text)}.menu-tree{padding:8px 0}.menu-tree-row--separator{display:grid;grid-template-columns:26px minmax(80px,1fr) repeat(7,auto);gap:6px;align-items:center;padding:2px 0;opacity:.85}.menu-tree-row--separator:hover{opacity:1}.menu-tree-row--separator .me-sep-line{display:flex;align-items:center;gap:10px;width:100%}.menu-tree-row--separator .me-sep-line:before,.menu-tree-row--separator .me-sep-line:after{content:"";flex:1;border-top:1px dashed var(--dm-border, rgba(128, 128, 128, .55))}.menu-tree-row--separator .me-sep-label{font-size:.66rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--dm-text-muted, #8a8a8a);white-space:nowrap}.dm-admin-sidebar .sidebar-divider{border:0;border-top:1px solid var(--dm-border, rgba(128, 128, 128, .5));margin:.5rem .75rem;opacity:.8}.dm-admin-sidebar .sidebar-spacer--xs{height:var(--dm-space-1, .25rem)}.dm-admin-sidebar .sidebar-spacer--sm{height:var(--dm-space-2, .5rem)}.dm-admin-sidebar .sidebar-spacer--md{height:var(--dm-space-4, 1rem)}.dm-admin-sidebar .sidebar-spacer--lg{height:var(--dm-space-6, 1.5rem)}.dm-admin-sidebar .sidebar-spacer--xl{height:var(--dm-space-10, 2.5rem)}.dm-admin-sidebar .sidebar-spacer--flex{flex:1 1 auto;min-height:var(--dm-space-4, 1rem)}.dm-admin-sidebar--has-flex-spacer{display:flex;flex-direction:column;min-height:100%}.menu-tree-row--spacer{display:grid;grid-template-columns:26px minmax(80px,1fr) repeat(7,auto);gap:6px;align-items:center;padding:2px 0;opacity:.85}.menu-tree-row--spacer:hover{opacity:1}.menu-tree-row--spacer .me-spacer-line{display:flex;align-items:center;gap:8px;width:100%}.menu-tree-row--spacer .me-spacer-line:before,.menu-tree-row--spacer .me-spacer-line:after{content:"";flex:1;border-top:1px dotted var(--dm-border, rgba(128, 128, 128, .55))}.menu-tree-row--spacer .me-spacer-label{font-size:.66rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--dm-text-muted, #8a8a8a);white-space:nowrap}.menu-tree-row--spacer .me-spacer-size{width:auto;min-width:5.5rem;height:26px;padding:0 .35rem;font-size:.75rem}.menu-tree-row .me-icon-wrap{display:flex;gap:2px}.menu-tree-row .me-icon-wrap .me-icon{flex:1;min-width:0}.menu-tree-row .me-icon-wrap .me-icon-pick{flex-shrink:0;padding:.2rem .45rem;font-size:.85em}.dm-admin-sidebar{display:flex;flex-direction:column;gap:2px;padding:8px 0;font-size:.875rem}.dm-admin-sidebar details{margin:0}.dm-admin-sidebar details>summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:.5rem;padding:.4rem .75rem;border-radius:4px;user-select:none;font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--dm-text-muted, #888)}.dm-admin-sidebar details details>summary{font-size:.875rem;font-weight:500;text-transform:none;letter-spacing:0;color:inherit}.dm-admin-sidebar .dm-menu-badge{font-size:.7rem;font-weight:600}.dm-admin-sidebar details>summary::-webkit-details-marker{display:none}.dm-admin-sidebar details>summary:after{content:"\25b8";margin-left:auto;transition:transform .15s ease;color:var(--dm-text-muted, #999);font-size:.85em}.dm-admin-sidebar details[open]>summary:after{transform:rotate(90deg)}.dm-admin-sidebar details>div{padding-left:1rem;border-left:1px solid var(--dm-border-light, rgba(0, 0, 0, .08));margin-left:.95rem}.dm-admin-sidebar .sidebar-link{display:flex;align-items:center;gap:.5rem;padding:.35rem .75rem;text-decoration:none;color:var(--dm-text, inherit);border-radius:4px}.dm-admin-sidebar .sidebar-link:hover,.dm-admin-sidebar details>summary:hover{background:var(--dm-hover-bg, rgba(0, 0, 0, .05))}.dm-admin-sidebar .sidebar-link.active{background:var(--dm-active-bg, rgba(0, 0, 0, .08));font-weight:600}.dm-admin-sidebar .sidebar-text{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dm-admin-sidebar [data-icon]{flex-shrink:0}.dm-admin-sidebar-header{position:sticky;top:0;z-index:2;background:var(--dm-surface);display:flex;align-items:center;gap:.5rem;padding:.65rem .85rem;margin:0 0 .5rem;font-weight:600;font-size:.95rem;letter-spacing:.01em;color:var(--dm-text, inherit);border-bottom:1px solid var(--dm-border-light, rgba(0, 0, 0, .08))}.dm-admin-sidebar--dark .dm-admin-sidebar-header{background:#1a1d24}.dm-admin-sidebar--light .dm-admin-sidebar-header{background:#fff}.dm-admin-sidebar-header [data-icon]{color:var(--dm-text-muted, #999)}.dm-admin-sidebar-header .dm-admin-sidebar-arrange{margin-left:auto;padding:.2rem .35rem;color:var(--dm-text-muted, #999);line-height:1}.dm-admin-sidebar-header .dm-admin-sidebar-arrange:hover,.dm-admin-sidebar-header .dm-admin-sidebar-arrange:focus-visible{color:var(--dm-text)}.dm-admin-sidebar.is-arranging>:not(.dm-admin-sidebar-header):not(.sb-arrange):not(style){display:none!important}.dm-admin-sidebar.is-arranging .dm-admin-sidebar-arrange,.dm-admin-sidebar.is-arranging .dm-admin-sidebar-collapse{display:none}.sb-arrange{box-sizing:border-box;max-width:100vw;padding:0 .5rem 1rem;animation:sb-arrange-in .18s cubic-bezier(.2,.8,.2,1)}@keyframes sb-arrange-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}.sb-arrange-bar{position:sticky;top:2.6rem;z-index:2;display:flex;align-items:center;gap:.4rem;padding:.4rem .25rem;background:inherit}.sb-arrange-title{font-weight:600;margin-right:auto}.sb-arrange-hint{margin:0 .25rem .5rem;font-size:.78rem;line-height:1.35;color:var(--dm-text-muted)}.sb-arr-row{display:flex;align-items:center;gap:.45rem;padding:.35rem .4rem .35rem calc(.4rem + var(--sb-arr-depth, 0) * 1rem);border-radius:var(--dm-radius-sm, 4px);cursor:grab;user-select:none;font-size:.9rem}.sb-arr-row:hover{background:color-mix(in srgb,var(--dm-text) 6%,transparent)}.sb-arr-row:focus-visible{outline:2px solid var(--dm-primary);outline-offset:-2px}.sb-arr-row--folder .sb-arr-text{font-weight:600}.sb-arr-row--hidden .sb-arr-text,.sb-arr-row--hidden .sb-arr-icon{opacity:.45;text-decoration:line-through}.sb-arr-row--structural .sb-arr-text{font-size:.75rem;text-transform:uppercase;letter-spacing:.05em;color:var(--dm-text-muted)}.sb-arr-grip{color:var(--dm-text-muted);flex:none}.sb-arr-icon{width:16px;flex:none}.sb-arr-text{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sb-arr-hide{flex:none;display:inline-flex;padding:.15rem;border:0;border-radius:var(--dm-radius-sm, 4px);background:transparent;color:var(--dm-text-muted);cursor:pointer}.sb-arr-hide:hover,.sb-arr-hide:focus-visible{color:var(--dm-text);background:color-mix(in srgb,var(--dm-text) 8%,transparent)}.sb-arr-lock{flex:none;color:var(--dm-text-muted);opacity:.6}.dm-admin-sidebar .sidebar-badge{margin-left:auto;padding:0 .4rem;min-width:1.25rem;height:1.1rem;line-height:1.1rem;text-align:center;font-size:.7rem;font-weight:600;border-radius:999px;background:var(--dm-active-bg, rgba(0, 0, 0, .12));color:var(--dm-text-muted, #888)}.dm-admin-sidebar .sidebar-link.active .sidebar-badge{background:var(--dm-primary, #4a86e8);color:var(--dm-primary-text, #fff)}.dm-admin-sidebar .sidebar-children{overflow:hidden;transition:max-height .22s ease,opacity .18s ease}.dm-admin-sidebar .sidebar-frequent-empty{margin:0;padding:.35rem 1rem .6rem 2.25rem;font-size:.8rem;white-space:normal;overflow-wrap:anywhere;contain:inline-size;line-height:1.4;color:var(--dm-text-muted, inherit);opacity:.85}.dm-admin-sidebar details:not([open])>.sidebar-children{opacity:0}.dm-admin-sidebar{position:relative}#admin-sidebar{position:relative;transition:width .18s ease}#admin-sidebar.is-resizing{transition:none}@media(prefers-reduced-motion:reduce){#admin-sidebar{transition:none}}.dm-admin-sidebar-handle{position:absolute;top:0;right:0;width:8px;height:100%;cursor:col-resize;z-index:10;touch-action:none;outline:none}.dm-admin-sidebar-handle:before,.dm-admin-sidebar-handle:after{content:"";position:absolute;right:2px;transition:background .15s,opacity .15s}.dm-admin-sidebar-handle:before{top:0;bottom:0;width:2px;background:transparent}.dm-admin-sidebar-handle:after{top:50%;width:3px;height:28px;margin-top:-14px;border-radius:2px;background:var(--dm-border, rgba(0, 0, 0, .15));opacity:.8}.dm-admin-sidebar-handle:hover:before,.dm-admin-sidebar-handle:focus-visible:before,.is-resizing>.dm-admin-sidebar-handle:before{background:var(--dm-primary, #4a86e8);opacity:.5}.dm-admin-sidebar-handle:hover:after,.dm-admin-sidebar-handle:focus-visible:after,.is-resizing>.dm-admin-sidebar-handle:after{background:var(--dm-primary, #4a86e8);opacity:1}.dm-admin-sidebar-handle:focus-visible:before{opacity:1}#admin-sidebar:has(.is-arranging)>.dm-admin-sidebar-handle{display:none}body.dm-sidebar-resizing{cursor:col-resize;user-select:none}body.dm-sidebar-resizing iframe{pointer-events:none}.dm-admin-sidebar-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dm-admin-sidebar-header .dm-admin-sidebar-collapse{padding:.2rem .35rem;color:var(--dm-text-muted, #999);line-height:1}.dm-admin-sidebar-header .dm-admin-sidebar-collapse:hover,.dm-admin-sidebar-header .dm-admin-sidebar-collapse:focus-visible{color:var(--dm-text)}.dm-admin-sidebar-collapse [data-icon]{transition:transform .18s ease}.dm-admin-sidebar .sidebar-initial{display:none}#admin-sidebar.is-collapsed{width:56px!important}#admin-sidebar.is-collapsed .dm-admin-sidebar{overflow-x:hidden}#admin-sidebar.is-collapsed .dm-admin-sidebar-header{justify-content:center;padding-left:0;padding-right:0}#admin-sidebar.is-collapsed .dm-admin-sidebar-header>[data-icon],#admin-sidebar.is-collapsed .dm-admin-sidebar-title,#admin-sidebar.is-collapsed .dm-admin-sidebar-arrange{display:none}#admin-sidebar.is-collapsed .dm-admin-sidebar-collapse [data-icon]{transform:rotate(180deg)}#admin-sidebar.is-collapsed .sidebar-text{position:absolute!important;width:1px;height:1px;padding:0!important;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}#admin-sidebar.is-collapsed .sidebar-link,#admin-sidebar.is-collapsed details>summary{justify-content:center;padding:.5rem 0;margin:0 6px;width:auto!important}#admin-sidebar.is-collapsed details>summary:after,#admin-sidebar.is-collapsed .dm-menu-badge,#admin-sidebar.is-collapsed .sidebar-children{display:none!important}#admin-sidebar.is-collapsed .sidebar-initial{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:4px;font-size:.75rem;font-weight:600;background:var(--dm-hover-bg, rgba(0, 0, 0, .06))}#admin-sidebar.is-collapsed details>summary[aria-expanded=true]{background:var(--dm-active-bg, rgba(0, 0, 0, .08))}#admin-sidebar.is-collapsed .sidebar-badge{top:4px!important;right:8px!important;transform:none!important;width:8px;height:8px;min-width:0;padding:0!important;border-radius:50%;font-size:0;line-height:0;overflow:hidden}#admin-sidebar.is-collapsed .sidebar-badge--live{padding:0!important;background:var(--dm-success, #16a34a)}#admin-sidebar.is-collapsed .sidebar-badge--live:before{display:none}#admin-sidebar.is-collapsed summary>.sidebar-badge--sum{position:absolute!important;margin:0!important}#admin-sidebar.is-collapsed details[open]>summary>.sidebar-badge--sum{display:block}#admin-sidebar.is-collapsed .sidebar-divider{margin-left:8px;margin-right:8px}#admin-sidebar.is-collapsed .sidebar-spacer--flex{min-height:var(--dm-space-2, .5rem)}#admin-sidebar.is-collapsed details>summary{position:relative}.dm-admin-sidebar-flyout{position:fixed;z-index:1000;min-width:200px;max-width:320px;overflow-y:auto;background:var(--dm-surface, #fff);color:var(--dm-text, inherit);border:1px solid var(--dm-border, rgba(0, 0, 0, .1));border-radius:var(--dm-radius-md, 8px);box-shadow:var(--dm-shadow-lg, 0 8px 24px rgba(0, 0, 0, .15));animation:dm-sidebar-flyout-in .12s ease-out}.dm-admin-sidebar-flyout>.dm-admin-sidebar{padding:4px 6px 6px}.dm-admin-sidebar-flyout-title{padding:.45rem .75rem .35rem;font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--dm-text-muted, #888)}.dm-admin-sidebar-flyout .sidebar-badge{margin-left:auto}@keyframes dm-sidebar-flyout-in{0%{opacity:0;transform:translate(-4px)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){.dm-admin-sidebar-flyout{animation:none}}.dm-admin-sidebar--dark{background:#1a1d24;color:#e3e6eb}.dm-admin-sidebar--dark .sidebar-link,.dm-admin-sidebar--dark summary{color:#e3e6eb}.dm-admin-sidebar--dark .sidebar-link:hover,.dm-admin-sidebar--dark details>summary:hover{background:#ffffff0f}.dm-admin-sidebar--dark .sidebar-link.active{background:#4a86e840}.dm-admin-sidebar--dark .sidebar-badge{background:#ffffff1f;color:#cbd0d8}.dm-admin-sidebar--light{background:#fff;color:#1a1d24;border-right:1px solid rgba(0,0,0,.08)}.dm-admin-sidebar--light .sidebar-link,.dm-admin-sidebar--light summary{color:#1a1d24}.dm-admin-sidebar--light .sidebar-link:hover,.dm-admin-sidebar--light details>summary:hover{background:#0000000a}.dm-admin-sidebar--light .sidebar-link.active{background:#4a86e826}.dm-admin-sidebar--transparent{background:transparent}.page-header{display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;margin:0 0 1.25rem;padding-bottom:.75rem;border-bottom:1px solid var(--dm-border-light, rgba(0, 0, 0, .08))}.page-header h2{margin:0;display:flex;align-items:center;gap:.5rem;font-size:1.4rem;line-height:1.2}.page-header h2 [data-icon]{color:var(--dm-text-muted, #888)}.header-actions{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.card+.card,.card+p,p+.card,.card+.toolbar,.toolbar+.card,.card+.grid{margin-top:1rem}.toolbar{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;margin:0 0 1rem}.btn+.btn,.btn+a.btn,a.btn+.btn,a.btn+a.btn{margin-left:.35rem}[style*=inline-flex] .btn+.btn,[style*=inline-flex] .btn+a.btn,[style*=inline-flex] a.btn+.btn{margin-left:0}.card-body>[id$=-form],.card-body>form{margin:.25rem 0}.card-header{display:flex;align-items:center;gap:.5rem}.card-header h3{margin:0;font-size:1.05rem}.card-header [data-icon]{color:var(--dm-text-muted, #888)}.page-header+p.text-muted{margin:-.5rem 0 1rem}.dm-pe-grid{gap:.75rem 1rem}.dm-pe-grid .form-group{display:flex;flex-direction:column;gap:.25rem;margin:0}.dm-pe-grid .form-group.col-span-2{grid-column:1 / -1}.dm-pe-grid label{font-size:.85rem;font-weight:600;color:var(--dm-text, inherit)}.dm-pe-grid label .required{color:var(--dm-danger, #c33);margin-left:2px}.dm-pe-grid .form-hint{font-size:.75rem;color:var(--dm-text-muted, #888);margin-top:.1rem}.dm-pe-grid .form-input{width:100%;padding:.4rem .6rem}.dm-pe-grid textarea.form-input{resize:vertical;min-height:2.5rem}.dm-menu-badge{display:inline-block;margin-left:6px;padding:1px 7px;font-size:.7em;font-weight:700;line-height:1.4;border-radius:999px;background:var(--dm-text-muted);color:var(--dm-text-inverse, #fff);vertical-align:middle}.menu-tree-row--slim{display:grid;grid-template-columns:26px 1fr auto repeat(9,auto);gap:6px;align-items:center}.menu-tree-row--slim .me-row-label{font-weight:600}.menu-tree-row--slim .me-row-url{color:var(--dm-text-muted);font-size:.85em}.me-pages-picker{display:flex;gap:6px;align-items:center;margin-bottom:6px}.me-pages-picker .form-input{flex:1;min-width:0}.me-colour-control{display:flex;gap:6px;align-items:center}.me-colour-control .me-colour-hex{width:48px;height:32px;padding:2px;flex:none}.me-bind-summary{padding:.6rem .8rem;border:1px solid var(--dm-border, rgba(128, 128, 128, .35));border-radius:var(--dm-radius-md, 6px);background:color-mix(in srgb,var(--dm-text) 4%,transparent);font-size:.9em}.me-bind-summary code{font-size:.9em}.me-item-form{display:flex;flex-direction:column;gap:10px;padding:4px 2px}.me-item-form .form-field{display:flex;flex-direction:column;gap:4px}.me-item-form .form-label{font-size:.85em;color:var(--dm-text-muted)}.me-form-section{margin:12px 0 2px;padding-top:10px;border-top:1px solid var(--dm-border-light, rgba(0,0,0,.15));font-size:.78em;text-transform:uppercase;letter-spacing:.06em;color:var(--dm-text-muted)}.me-item-form .me-form-section:first-child{margin-top:0;padding-top:0;border-top:0}.me-item-form .me-colour-hex{width:48px;height:32px;padding:2px}.me-item-form .me-field-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}.me-item-form .me-field-grid .form-field{margin:0}.me-item-form .me-icon-field{display:flex;align-items:center;gap:8px}.me-item-form .me-icon-field .form-input{flex:1}.me-item-form .me-icon-preview{width:24px;height:24px;flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;color:var(--dm-text-muted)}#admin-sidebar.dm-menu-floating{border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 8px);box-shadow:var(--dm-shadow-lg);overflow:hidden}@media(max-width:768px){#admin-sidebar.dm-menu-floating{position:static!important;inset:auto!important;max-height:none!important;transform:none!important}.dashboard-wrapper{padding-left:0!important;padding-right:0!important}}[data-logic-section]{border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 6px);padding:.9rem 1rem 1rem;margin-bottom:1rem;background:color-mix(in srgb,var(--dm-text) 3%,transparent)}[data-logic-section] .fb-logic-cond-row,[data-logic-section] .fb-logic-val-rule{padding:.55rem .65rem;border-radius:var(--dm-radius-sm, 4px);border:1px solid var(--dm-border);background:var(--dm-surface, transparent)}[data-logic-section] .fb-logic-cond-row:last-of-type,[data-logic-section] .fb-logic-val-rule:last-of-type{margin-bottom:.75rem}[data-logic-section]>.btn{margin-top:.15rem}[data-logic-section] textarea{min-height:5rem}.project-filter-notice{display:flex;align-items:center;gap:.4rem;flex-wrap:wrap;margin:0 0 1rem;padding:.6rem .85rem;border:1px solid var(--dm-border);border-left:3px solid var(--dm-info, var(--dm-primary));border-radius:var(--dm-radius-md, 6px);background:color-mix(in srgb,var(--dm-info, var(--dm-primary)) 6%,transparent);font-size:.85rem;color:var(--dm-text-muted)}.project-filter-notice a{color:var(--dm-primary);font-weight:600}.ctx-help{display:inline-flex;align-items:center;justify-content:center;width:1.05rem;height:1.05rem;margin-left:.35rem;padding:0;border:1px solid var(--dm-border-dark, var(--dm-border));border-radius:var(--dm-radius-full, 9999px);background:transparent;color:var(--dm-text-muted);font-size:.68rem;font-weight:700;line-height:1;cursor:pointer;vertical-align:middle;flex-shrink:0}.ctx-help:hover,.ctx-help:focus-visible{border-color:var(--dm-primary);color:var(--dm-primary);outline:none}.card-header h3 .ctx-help{align-self:center}.ctx-preview-card{position:sticky;top:1rem}@media(max-width:1100px){.ctx-workspace>.col-8,.ctx-workspace>.col-4{flex:0 0 auto;width:100%}.ctx-preview-card{position:static}}.dm-plugin-shell{border:1px solid var(--dm-border);border-radius:var(--dm-radius-lg, 12px);background:var(--dm-surface);overflow:hidden}.dm-plugin-banner{display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;padding:.7rem 1rem;background:var(--dm-background-alt);border-bottom:1px solid var(--dm-border)}.dm-plugin-banner-identity{display:flex;align-items:center;gap:.55rem;min-width:0;margin-right:auto}.dm-plugin-banner-actions{display:flex;align-items:center;gap:.25rem;flex:0 0 auto;margin-right:.15rem;padding-right:.4rem;border-right:1px solid var(--dm-border)}.dm-plugin-banner-action{padding:.25rem .4rem;line-height:1;color:var(--dm-text-muted)}.dm-plugin-banner-action:hover{color:var(--dm-primary)}.dm-plugin-banner-icon{display:inline-flex;align-items:center;color:var(--dm-primary);flex:0 0 auto}.dm-plugin-banner-name{margin:0;font-size:1.05rem;font-weight:600;letter-spacing:.01em;color:var(--dm-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dm-activity{display:inline-flex;align-items:center;flex:0 0 auto;margin-left:.35rem}.dm-activity domma-loader{display:inline-block;line-height:0}.dm-activity[hidden]{display:none!important}#admin-topbar>.dm-activity{margin:0 .75rem 0 0}.dm-plugin-banner-parent{font-weight:500;color:var(--dm-text-muted)}.dm-plugin-banner-sep{margin:0 .4rem;color:var(--dm-text-muted)}.dm-plugin-banner-facts{display:flex;align-items:center;gap:.4rem;flex-wrap:wrap}.dm-plugin-fact{font-size:.72rem;line-height:1.6;padding:1px 8px;border-radius:999px;border:1px solid var(--dm-border);color:var(--dm-text-muted);background:var(--dm-surface);white-space:nowrap}.dm-plugin-fact--version{font-variant-numeric:tabular-nums;color:var(--dm-text)}.dm-plugin-fact--licence.is-included{color:var(--dm-info);border-color:var(--dm-info)}.dm-plugin-fact--licence.is-licensed{color:var(--dm-success);border-color:var(--dm-success)}.dm-plugin-fact--expiry{font-variant-numeric:tabular-nums}.dm-plugin-fact--expiry.is-warning{color:var(--dm-warning);border-color:var(--dm-warning)}.dm-plugin-fact--expiry.is-danger{color:var(--dm-danger);border-color:var(--dm-danger)}.dm-plugin-body{padding:1rem}.dm-plugin-body .card.dm-plugin-unwrap{border:0;background:none;box-shadow:none;margin:0}.dm-plugin-body .card.dm-plugin-unwrap>.card-body{padding:0}.dm-plugin-body>.p-4{padding:0}@media(max-width:640px){.dm-plugin-banner{align-items:flex-start;flex-direction:column;gap:.5rem}.dm-plugin-body{padding:.75rem}}.analytics-filter{max-width:240px}.dm-help-btn{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;margin-left:.3rem;padding:0;border:0;background:none;line-height:1;color:var(--dm-text-muted);opacity:.75;cursor:pointer}.dm-help-btn:hover,.dm-help-btn:focus-visible{color:var(--dm-primary);opacity:1}.dm-popover.dm-help-popover{--dm-popover-max-width: 300px;font-size:.85rem;line-height:1.45;font-weight:400}.dm-help-pop{position:fixed;z-index:10050;max-width:300px;padding:.6rem .75rem;background:var(--dm-surface, var(--dm-background));color:var(--dm-text);border:1px solid var(--dm-border);border-radius:var(--dm-radius-md);box-shadow:0 8px 24px #00000040;font-size:.85rem;line-height:1.45;text-align:left;font-weight:400}.dm-help-pop strong{display:block;margin-bottom:.2rem;font-weight:600}.dm-help-pop p{margin:0}.dm-admin-sidebar .sidebar-badge[data-badge-label]{cursor:help}.dm-admin-sidebar .sidebar-badge--danger,.dm-admin-sidebar .sidebar-link.active .sidebar-badge--danger{background:var(--dm-danger, #dc2626);color:#fff}.dm-admin-sidebar .sidebar-badge--live{background:color-mix(in srgb,var(--dm-success, #16a34a) 20%,transparent);color:var(--dm-success, #16a34a)}#admin-sidebar .sidebar-badge--live{padding-left:1.05rem!important}#admin-sidebar summary>.sidebar-badge--sum{position:static!important;transform:none!important;margin-left:auto!important;margin-right:.45rem;text-transform:none;letter-spacing:0}#admin-sidebar summary:has(>.sidebar-badge--sum):after{margin-left:0}#admin-sidebar details[open]>summary>.sidebar-badge--sum{display:none}.dm-admin-sidebar .sidebar-badge--live:before{content:"";position:absolute;left:.4rem;top:50%;width:.4rem;height:.4rem;margin-top:-.2rem;border-radius:50%;background:currentColor;animation:dm-badge-pulse 2s ease-in-out infinite}@keyframes dm-badge-pulse{50%{opacity:.35}}@media(prefers-reduced-motion:reduce){.dm-admin-sidebar .sidebar-badge--live:before{animation:none}}.dm-badge-pop{width:290px;max-width:calc(100vw - 16px);font-size:.8rem;pointer-events:none}.dm-badge-pop-title{display:block;font-size:.85rem}.dm-badge-pop-sub{margin:.1rem 0 .5rem;color:var(--dm-text-muted)}.dm-badge-pop-rows{display:grid;grid-template-columns:1fr auto;gap:.2rem .75rem;margin:0 0 .5rem;padding:.45rem 0;border-top:1px solid var(--dm-border);border-bottom:1px solid var(--dm-border)}.dm-badge-pop-rows dt{color:var(--dm-text-muted)}.dm-badge-pop-rows dd{margin:0;text-align:right;font-weight:600;font-variant-numeric:tabular-nums}.dm-badge-pop-items{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.4rem}.dm-badge-pop-items li{display:flex;flex-direction:column;min-width:0}.dm-badge-pop-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dm-badge-pop-meta{font-size:.72rem;color:var(--dm-text-muted)}.dm-badge-pop .is-danger,.dm-badge-pop li.is-danger .dm-badge-pop-meta{color:var(--dm-danger, #dc2626)}.dm-badge-pop-empty{margin:0;color:var(--dm-text-muted)}.dm-plugin-body,.dm-slideover{font-family:var(--dm-font-sans, inherit)}.dm-plugin-body :is(button,input,select,textarea,.btn,.form-input,.form-select,.form-textarea),.dm-slideover :is(button,input,select,textarea,.btn,.form-input,.form-select,.form-textarea){font-family:inherit}.dm-plugin-body :is(input,select,textarea,.form-input,.form-select,.form-textarea):not(.form-input-lg),.dm-slideover :is(input,select,textarea,.form-input,.form-select,.form-textarea):not(.form-input-lg){font-size:var(--dm-text-sm)}.dm-plugin-body .form-input-lg{font-size:var(--dm-text-base)}.dm-plugin-body :is(.font-mono,code,pre,kbd),.dm-slideover :is(.font-mono,code,pre,kbd){font-family:var(--dm-font-mono, monospace)}.dm-plugin-body .form-label,.dm-slideover .form-label{font-size:var(--dm-text-sm)}.related-box{display:flex;flex-direction:column;gap:.6rem;border-top:1px solid var(--dm-border);padding-top:.75rem}.related-box[hidden]{display:none}.related-head{display:flex;align-items:center;gap:.4rem}.related-head strong{flex:1}.related-list{list-style:none;margin:.3rem 0 0;padding:0;display:flex;flex-direction:column;gap:.25rem}.related-list li{display:flex;flex-direction:column}.related-list li.is-past{opacity:.7}.related-meta,.related-note,.related-loading{color:var(--dm-text-muted);font-size:var(--dm-text-xs);margin:0}.login-challenge{display:flex;flex-direction:column;gap:.6rem;margin-bottom:.75rem}.login-challenge #challenge-code{font-size:1.25rem;letter-spacing:.2em;text-align:center}.login-trust{display:flex;align-items:center;gap:.45rem;font-size:.875rem}.login-enrol{display:flex;flex-direction:column;align-items:center;gap:.6rem;margin-bottom:1rem}.login-enrol img{background:#fff;padding:.5rem;border-radius:.5rem}.login-secret{display:flex;flex-direction:column;align-items:center;gap:.25rem;text-align:center}.login-secret code{font-size:.95rem;letter-spacing:.08em;word-break:break-all}.usx-secret{display:flex;flex-direction:column;gap:.75rem;padding:1rem}.usx-secret-link{display:block;padding:.6rem .75rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius-sm);background:var(--dm-background-alt);word-break:break-all;font-size:.8rem;user-select:all}.usx-secret-actions{display:flex;align-items:center;justify-content:flex-end;gap:.5rem}.usx-secret-actions .btn{display:inline-flex;align-items:center;gap:.35rem}.usx-secret-note{margin-right:auto;color:var(--dm-success)}.usx-secret-foot{color:var(--dm-text-muted);font-size:.8rem;margin:0}.uex-reset{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem .5rem;margin-top:.5rem}.uex-reset>small:first-child{flex-basis:100%;color:var(--dm-text-muted)}.uex-reset .btn{display:inline-flex;align-items:center;gap:.35rem}.uex-reset-open{flex-basis:100%;color:var(--dm-info)}.dm-loading-slot>.skeleton-list,.dm-loading-slot>.skeleton-table{border-color:transparent;background:transparent}.dm-loading-slot>.skeleton-list .skeleton-list-item{padding-left:.35rem;padding-right:.35rem}.dm-skeleton-figure{width:3.5rem;height:1em;margin:.1em 0}.view-container:has(.dbp){max-width:none}.dbp-head{display:flex;align-items:center;justify-content:space-between;gap:1rem;margin-bottom:.9rem}.dbp-hint{margin:0;color:var(--dm-text-muted);font-size:var(--dm-text-sm);display:flex;align-items:center;gap:.35rem}.dbp-source{color:var(--dm-text-muted);font-size:var(--dm-text-xs);white-space:nowrap}.dbp-board{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));column-gap:1rem;row-gap:0;grid-auto-rows:4px;grid-auto-flow:row dense;align-items:start}.dbp-board>.dbp-card{align-self:start;margin-bottom:1rem}.dbp-card{grid-column:span 2;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md);background:var(--dm-surface);display:flex;flex-direction:column;min-width:0;min-height:9rem;outline:none}.dbp-card:focus-visible{border-color:var(--dm-primary)}.dbp-size-s{grid-column:span 1}.dbp-size-m{grid-column:span 2}.dbp-size-l{grid-column:span 3}.dbp-size-xl{grid-column:1 / -1}@media(max-width:1100px){.dbp-board{grid-template-columns:repeat(2,minmax(0,1fr))}.dbp-size-l{grid-column:1 / -1}}@media(max-width:640px){.dbp-board{grid-template-columns:minmax(0,1fr)}.dbp-card{grid-column:1 / -1}}.dbp-card-head{display:flex;align-items:center;gap:.5rem;padding:.55rem .7rem;border-bottom:1px solid var(--dm-border);min-width:0}.dbp-grip{cursor:grab;color:var(--dm-text-muted);display:inline-flex;touch-action:none}.dbp-grip:active{cursor:grabbing}.dbp-icon{display:inline-flex;align-items:center;justify-content:center;width:1.8rem;height:1.8rem;flex:0 0 auto;border-radius:var(--dm-radius-md);color:var(--dm-primary);background:color-mix(in srgb,var(--dm-primary) 12%,transparent)}.dbp-title{font-weight:600;color:var(--dm-text);text-decoration:none;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}a.dbp-title:hover,a.dbp-title:focus-visible{color:var(--dm-primary);text-decoration:underline}.dbp-from{margin-left:auto;color:var(--dm-text-muted);font-size:var(--dm-text-xs);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dbp-kebab{border:0;background:none;color:var(--dm-text-muted);cursor:pointer;padding:.1rem .35rem;border-radius:var(--dm-radius-md);line-height:1}.dbp-kebab:hover,.dbp-kebab:focus-visible{background:color-mix(in srgb,var(--dm-text) 8%,transparent);color:var(--dm-text)}.dbp-body{padding:.75rem .9rem;display:flex;flex-direction:column;gap:.6rem;min-width:0;flex:1 1 auto}.dbp-card.is-loading .dbp-body{opacity:.75}.dbp-skeleton{height:3.5rem;border-radius:var(--dm-radius-md);background:color-mix(in srgb,var(--dm-text) 7%,transparent)}.dbp-figure{display:flex;align-items:baseline;gap:.5rem;flex-wrap:wrap}.dbp-figure-value{font-size:var(--dm-text-2xl, 1.5rem);line-height:1.15;font-weight:600;font-variant-numeric:tabular-nums;color:var(--dm-text)}.dbp-figure-label{color:var(--dm-text-muted);font-size:var(--dm-text-sm)}.dbp-figure.is-danger .dbp-figure-value{color:var(--dm-danger)}.dbp-figure.is-attention .dbp-figure-value{color:var(--dm-warning)}.dbp-sub{margin:0;color:var(--dm-text-muted);font-size:var(--dm-text-xs)}.dbp-facts{display:grid;grid-template-columns:auto 1fr;gap:.2rem .8rem;margin:0;font-size:var(--dm-text-sm)}.dbp-facts dt{color:var(--dm-text-muted)}.dbp-facts dd{margin:0;text-align:right;font-variant-numeric:tabular-nums}.dbp-rows{display:flex;flex-direction:column;min-width:0}.dbp-row{display:flex;align-items:center;gap:.6rem;padding:.4rem .3rem;border-bottom:1px solid var(--dm-border);color:var(--dm-text);text-decoration:none;min-width:0}.dbp-row:last-child{border-bottom:0}a.dbp-row:hover,a.dbp-row:focus-visible{background:color-mix(in srgb,var(--dm-text) 6%,transparent);outline:none}.dbp-row-main{display:flex;flex-direction:column;min-width:0;flex:1 1 auto}.dbp-row-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dbp-row-meta{color:var(--dm-text-muted);font-size:var(--dm-text-xs);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dbp-row-value{font-variant-numeric:tabular-nums;color:var(--dm-text-muted);font-size:var(--dm-text-sm);white-space:nowrap}.dbp-row.is-danger{box-shadow:inset 3px 0 0 var(--dm-danger)}.dbp-row.is-attention{box-shadow:inset 3px 0 0 var(--dm-warning)}.dbp-row.is-ok .dbp-row-value{color:var(--dm-success)}.dbp-chart{position:relative;height:240px}.dbp-empty{margin:0;color:var(--dm-text-muted);font-size:var(--dm-text-sm)}.dbp-empty.is-error{color:var(--dm-danger)}.dbp-empty-board{text-align:center;padding:3rem 1rem;border:1px dashed var(--dm-border);border-radius:var(--dm-radius-md);color:var(--dm-text-muted)}.dbp-gallery{display:flex;flex-direction:column;gap:.6rem;padding:.25rem}.dbp-gallery-hint{margin:0;color:var(--dm-text-muted);font-size:var(--dm-text-xs)}.dbp-gallery-group{margin:.6rem 0 .1rem;font-size:var(--dm-text-xs);text-transform:uppercase;letter-spacing:.06em;color:var(--dm-text-muted)}.dbp-gallery-item{display:flex;align-items:center;gap:.6rem;padding:.5rem .3rem;border-bottom:1px solid var(--dm-border)}.dbp-gallery-main{display:flex;flex-direction:column;min-width:0;flex:1 1 auto}.dbp-gallery-main small{color:var(--dm-text-muted);font-size:var(--dm-text-xs)}.dbp-gallery-added{color:var(--dm-success);font-size:var(--dm-text-xs);white-space:nowrap}.dbp-fold{border:0;background:none;color:var(--dm-text-muted);cursor:pointer;padding:.15rem;border-radius:var(--dm-radius-md);display:inline-flex;line-height:1}.dbp-fold:hover,.dbp-fold:focus-visible{background:color-mix(in srgb,var(--dm-text) 8%,transparent);color:var(--dm-text)}.dbp-fold [data-icon]{transition:transform .15s ease}.dbp-card.is-collapsed{min-height:0}.dbp-card.is-collapsed .dbp-card-head{border-bottom:0}.dbp-card.is-collapsed .dbp-fold [data-icon]{transform:rotate(-90deg)}.dbp-from+.dbp-fold{margin-left:0}@media(prefers-reduced-motion:reduce){.dbp-fold [data-icon]{transition:none}}.dbp-body[hidden]{display:none}@media(min-width:1101px){.dbp.dbp-cols-3 .dbp-board{grid-template-columns:repeat(3,minmax(0,1fr))}.dbp.dbp-cols-6 .dbp-board{grid-template-columns:repeat(6,minmax(0,1fr))}}.dbp.is-compact .dbp-card-head{padding:.35rem .55rem}.dbp.is-compact .dbp-body{padding:.5rem .65rem;gap:.4rem}.dbp.is-compact .dbp-figure-value{font-size:var(--dm-text-xl, 1.25rem)}.dbp.is-compact .dbp-row{padding:.25rem .2rem}.dbp.is-compact .dbp-card{min-height:0}.dbp.is-locked .dbp-grip{display:none}.dbp-greeting{margin:0 0 .6rem;font-size:var(--dm-text-lg, 1.1rem);color:var(--dm-text);font-weight:500}.dbp-settings{display:flex;flex-direction:column;gap:.85rem;padding:.25rem}.dbp-settings-section{margin:.4rem 0 0;font-size:var(--dm-text-xs);text-transform:uppercase;letter-spacing:.06em;color:var(--dm-text-muted)}.dbp-field{display:flex;flex-direction:column;gap:.3rem}.dbp-field .form-label{display:flex;align-items:center;gap:.35rem}.dbp-field-row{display:grid;grid-template-columns:1fr 1fr;gap:.8rem}.dbp-switch{flex-direction:row;align-items:center;gap:.6rem;cursor:pointer}.dbp-switch>span{display:flex;align-items:center;gap:.35rem}.dbp-settings-actions{display:flex;justify-content:flex-end;gap:.5rem;margin-top:.4rem}.dbp-card{container-type:inline-size}@container (max-width: 240px){.dbp-card-head .dbp-icon,.dbp-card-head .dbp-from{display:none}.dbp-card-head .dbp-title{white-space:normal;line-height:1.2;overflow-wrap:anywhere;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.dbp-card-head{gap:.35rem}}.dbp-tabs{display:flex;flex-wrap:wrap;gap:.25rem;margin:0 0 .9rem;border-bottom:1px solid var(--dm-border)}.dbp-tab{border:1px solid transparent;border-bottom:0;background:none;color:var(--dm-text-muted);cursor:pointer;padding:.45rem .85rem;border-radius:var(--dm-radius-md) var(--dm-radius-md) 0 0;font-weight:500;margin-bottom:-1px;max-width:16rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dbp-tab:hover,.dbp-tab:focus-visible{color:var(--dm-text);background:color-mix(in srgb,var(--dm-text) 6%,transparent);outline:none}.dbp-tab.is-active{color:var(--dm-text);background:var(--dm-surface);border-color:var(--dm-border)}.dbp-tab-new{display:inline-flex;align-items:center;padding:.45rem .6rem}.dbp-tab-fixed{display:inline-flex;align-items:center;gap:.3rem}.dbp-tab-fixed+.dbp-tab:not(.dbp-tab-new){margin-left:.4rem}.dbs-strip{margin-bottom:1rem}.dm-hk-backdrop{position:fixed;inset:0;z-index:10050;display:flex;align-items:flex-start;justify-content:center;padding:12vh 16px 16px;background:#00000073}.dm-hk-panel{width:min(560px,100%);max-height:72vh;display:flex;flex-direction:column;background:var(--dm-surface-raised, var(--dm-surface, #fff));color:var(--dm-text);border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 10px);box-shadow:var(--dm-shadow-lg, 0 20px 50px rgba(0, 0, 0, .35));overflow:hidden}.dm-hk-panel:focus{outline:none}.dm-hk-keylist{width:min(720px,100%)}.dm-hk-search{display:flex;align-items:center;gap:10px;padding:12px 16px;border-bottom:1px solid var(--dm-border);color:var(--dm-text-muted)}.dm-hk-search input{flex:1;min-width:0;border:0;outline:0;background:transparent;color:var(--dm-text);font-size:1.05rem}.dm-hk-list{list-style:none;margin:0;padding:6px;overflow-y:auto}.dm-hk-item{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:var(--dm-radius-sm, 6px);cursor:pointer}.dm-hk-item.is-active{background:var(--dm-active-bg, var(--dm-hover-bg))}.dm-hk-icon{width:18px;display:inline-flex;justify-content:center;color:var(--dm-text-muted)}.dm-hk-name{flex:1;min-width:0;display:flex;flex-direction:column}.dm-hk-name small{color:var(--dm-text-muted);font-size:var(--dm-text-xs, .75rem)}.dm-hk-keys{display:inline-flex;gap:3px;flex:none}.dm-hk-panel kbd{display:inline-block;min-width:1.5em;padding:1px 5px;font:600 .72rem/1.4 var(--dm-font-mono, ui-monospace, monospace);text-align:center;color:var(--dm-text);background:var(--dm-background-alt, var(--dm-hover-bg));border:1px solid var(--dm-border);border-bottom-width:2px;border-radius:4px}.dm-hk-empty{padding:14px 10px;color:var(--dm-text-muted);list-style:none}.dm-hk-foot{display:flex;flex-wrap:wrap;align-items:center;gap:4px 6px;padding:8px 16px;border-top:1px solid var(--dm-border);color:var(--dm-text-muted);font-size:var(--dm-text-xs, .75rem)}.dm-hk-foot-end{margin-left:auto}.dm-hk-head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--dm-border)}.dm-hk-close{border:0;background:transparent;color:var(--dm-text-muted);cursor:pointer;padding:4px;border-radius:4px}.dm-hk-close:hover{background:var(--dm-hover-bg);color:var(--dm-text)}.dm-hk-cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:8px 24px;padding:12px 16px 16px;overflow-y:auto}.dm-hk-cols h4{margin:8px 0 4px;font-size:var(--dm-text-xs, .75rem);text-transform:uppercase;letter-spacing:.06em;color:var(--dm-text-muted)}.dm-hk-cols ul{list-style:none;margin:0;padding:0}.dm-hk-cols li{display:flex;align-items:center;gap:10px;padding:4px 0}.dm-hk-cols li .dm-hk-keys{width:4.6em}.dm-hk-cols li.dm-hk-empty{display:block;padding:4px 0;font-size:var(--dm-text-xs, .75rem)}.dm-hk-chip{position:fixed;left:50%;bottom:20px;z-index:10040;transform:translate(-50%);display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:6px 12px;width:max-content;max-width:min(920px,calc(100vw - 32px));padding:10px 14px;background:var(--dm-surface-raised, var(--dm-surface, #fff));color:var(--dm-text);border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 10px);box-shadow:var(--dm-shadow-lg, 0 12px 32px rgba(0, 0, 0, .3));font-size:var(--dm-text-sm, .85rem);pointer-events:none;animation:dm-hk-chip-in .12s ease-out}.dm-hk-chip kbd{display:inline-block;min-width:1.5em;padding:1px 5px;margin-right:4px;font:600 .72rem/1.4 var(--dm-font-mono, ui-monospace, monospace);text-align:center;color:var(--dm-text);background:var(--dm-background-alt, var(--dm-hover-bg));border:1px solid var(--dm-border);border-bottom-width:2px;border-radius:4px}.dm-hk-chip-lead{color:var(--dm-text-muted);font-weight:600}.dm-hk-chip-item{display:inline-flex;align-items:center;white-space:nowrap}.dm-hk-chip-sep{width:1px;align-self:stretch;background:var(--dm-border)}.dm-hk-chip-more{color:var(--dm-text-muted);white-space:nowrap}@keyframes dm-hk-chip-in{0%{opacity:0;transform:translate(-50%,6px)}to{opacity:1;transform:translate(-50%)}}@media(prefers-reduced-motion:reduce){.dm-hk-chip{animation:none}}.dm-hk-note{margin:0 0 6px;color:var(--dm-text-muted);font-size:var(--dm-text-xs, .75rem)}.dm-update-notice{position:fixed;left:50%;bottom:1rem;transform:translate(-50%);z-index:1090;display:flex;align-items:center;gap:.75rem;max-width:min(760px,calc(100vw - 2rem));padding:.75rem 1rem;background:var(--dm-surface);color:var(--dm-text);border:1px solid var(--dm-warning, #ffc107);border-left-width:4px;border-radius:var(--dm-radius-md, 8px);box-shadow:var(--dm-shadow-lg)}.dm-update-notice[hidden]{display:none!important}.dm-update-notice__text{flex:1 1 auto;font-size:.9rem}.dm-update-notice .btn{flex:0 0 auto}.topbar-sidebar-toggle{display:none;align-items:center;justify-content:center;flex:0 0 auto;width:2.25rem;height:2.25rem;padding:0;border:0;border-radius:6px;background:transparent;color:var(--dm-text, inherit);cursor:pointer}.topbar-sidebar-toggle:hover{background:var(--dm-hover-bg)}.topbar-sidebar-toggle:focus-visible{outline:2px solid var(--dm-primary);outline-offset:2px}.topbar-sidebar-toggle span[data-icon],.topbar-sidebar-toggle svg{width:20px;height:20px}.admin-sidebar-backdrop{display:none}.topbar-brand-logo{max-width:9rem}.view-header{flex-wrap:wrap}.view-header>*{min-width:0}.view-header h1{overflow-wrap:anywhere}@media(max-width:768px){#admin-topbar{gap:.5rem}.topbar-sidebar-toggle{display:inline-flex}.topbar-brand{margin-right:0}.topbar-user{min-width:0;gap:.4rem}.topbar-user-name{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.topbar-role-badge{flex-shrink:0;white-space:nowrap}.topbar-actions{gap:.15rem}.topbar-action-link{position:relative;padding:.5rem}.topbar-action-label{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}#topbar-bell .topbar-bell-badge{position:absolute;top:0;right:0}body #admin-sidebar.dashboard-sidebar{position:fixed!important;inset:60px auto 0 0!important;max-height:none!important;max-width:min(85vw,20rem);z-index:190;background:var(--dm-surface);box-shadow:var(--dm-shadow-lg);transform:translate(-105%)!important;visibility:hidden;transition:transform .2s ease,visibility 0s linear .2s}body.dm-sidebar-open #admin-sidebar.dashboard-sidebar{transform:none!important;visibility:visible;transition:transform .2s ease,visibility 0s}#admin-sidebar .dm-admin-sidebar-handle,#admin-sidebar .dm-admin-sidebar-collapse{display:none}body.dm-sidebar-open .admin-sidebar-backdrop{display:block;position:fixed;inset:60px 0 0;z-index:180;background:color-mix(in srgb,var(--dm-background, #000) 60%,transparent)}}@media(max-width:768px){.docs-body .table{display:block;max-width:100%;overflow-x:auto}.docs-body :not(pre)>code{overflow-wrap:anywhere}}@media(max-width:400px){.topbar-role-badge{display:none}}@media(prefers-reduced-motion:reduce){body #admin-sidebar.dashboard-sidebar{transition:none!important}}
@@ -1 +1 @@
1
- export const SCREEN=/^\/(pages\/(new|edit\/.*?)|(blocks|components)\/(new|edit\/[^/?#]+\/?))(\?.*)?$/;export function pageOfPath(t){const e=String(t||"").match(/^\/pages\/edit(\/[^?#]*)/);return e?e[1]:""}export const draftKey=t=>`page-editor.draft.${t||"new"}`,FIELDS=["urlPath","title","description","layout","status","sortOrder","showInNav","sidebar","breadcrumbs","bundled","tags","category","visibility","project","theme","seoTitle","seoDesc","dconfig","body","css"];export function snapshot(t={}){const e=t||{},o={};for(const n of FIELDS){const r=e[n];n==="tags"?o[n]=Array.isArray(r)?r.map(String):[]:typeof r=="boolean"?o[n]=r:n==="css"?o[n]=r==null?null:String(r):o[n]=r==null?"":String(r)}return JSON.stringify(o)}export function makeDraft(t,e,o=new Date().toISOString()){return{state:JSON.parse(snapshot(t)),base:e,at:o}}export function readDraft(t){return!t||typeof t!="object"||!t.state||typeof t.state!="object"?null:{state:JSON.parse(snapshot(t.state)),base:typeof t.base=="string"?t.base:"",at:typeof t.at=="string"?t.at:""}}export const draftIsStale=(t,e)=>!!(t?.base&&e&&t.base!==e);export function problems(t={}){const e=[],o=String(t.urlPath||"").trim();o?o.startsWith("/")?/\s/.test(o)&&e.push({field:"urlPath",text:"The address cannot contain spaces."}):e.push({field:"urlPath",text:"The address starts with / - for example /about."}):e.push({field:"urlPath",text:"The page needs an address - its URL path."});const n=String(t.dconfig||"").trim();if(n)try{const r=JSON.parse(n);(!r||typeof r!="object"||Array.isArray(r))&&e.push({field:"dconfig",text:"DConfig must be a JSON object ({ \u2026 })."})}catch{e.push({field:"dconfig",text:"DConfig is not valid JSON."})}return e}export function bannerSection(t,e,o){return t?String(e||"").trim()||o||"Untitled":"New page"}export function initialProject({isEdit:t,page:e=null,projects:o=[],fromHash:n="",urlPath:r=""}){if(!t&&n)return n;if(!e||e.project===null)return"";if(typeof e.project=="string"&&e.project)return e.project;if(!r)return"";let i="",a=-1;for(const s of o||[])s?.rootUrl&&(r===s.rootUrl||r.startsWith(s.rootUrl+"/"))&&s.rootUrl.length>a&&(i=s.slug,a=s.rootUrl.length);return i}
1
+ export const SCREEN=/^\/(pages\/(new|edit\/.*?)|(blocks|components)\/(new|edit\/[^/?#]+\/?))(\?.*)?$/;export function pageOfPath(t){const e=String(t||"").match(/^\/pages\/edit(\/[^?#]*)/);return e?e[1]:""}export const draftKey=t=>`page-editor.draft.${t||"new"}`,FIELDS=["urlPath","title","description","layout","status","sortOrder","showInNav","sidebar","breadcrumbs","bundled","tags","category","visibility","project","theme","seoTitle","seoDesc","seoSchema","dconfig","body","css"];export function snapshot(t={}){const e=t||{},o={};for(const n of FIELDS){const r=e[n];n==="tags"?o[n]=Array.isArray(r)?r.map(String):[]:typeof r=="boolean"?o[n]=r:n==="css"?o[n]=r==null?null:String(r):o[n]=r==null?"":String(r)}return JSON.stringify(o)}export function makeDraft(t,e,o=new Date().toISOString()){return{state:JSON.parse(snapshot(t)),base:e,at:o}}export function readDraft(t){return!t||typeof t!="object"||!t.state||typeof t.state!="object"?null:{state:JSON.parse(snapshot(t.state)),base:typeof t.base=="string"?t.base:"",at:typeof t.at=="string"?t.at:""}}export const draftIsStale=(t,e)=>!!(t?.base&&e&&t.base!==e);export function problems(t={}){const e=[],o=String(t.urlPath||"").trim();o?o.startsWith("/")?/\s/.test(o)&&e.push({field:"urlPath",text:"The address cannot contain spaces."}):e.push({field:"urlPath",text:"The address starts with / - for example /about."}):e.push({field:"urlPath",text:"The page needs an address - its URL path."});const n=String(t.dconfig||"").trim();if(n)try{const r=JSON.parse(n);(!r||typeof r!="object"||Array.isArray(r))&&e.push({field:"dconfig",text:"DConfig must be a JSON object ({ \u2026 })."})}catch{e.push({field:"dconfig",text:"DConfig is not valid JSON."})}return e}export function bannerSection(t,e,o){return t?String(e||"").trim()||o||"Untitled":"New page"}export function initialProject({isEdit:t,page:e=null,projects:o=[],fromHash:n="",urlPath:r=""}){if(!t&&n)return n;if(!e||e.project===null)return"";if(typeof e.project=="string"&&e.project)return e.project;if(!r)return"";let i="",a=-1;for(const s of o||[])s?.rootUrl&&(r===s.rootUrl||r.startsWith(s.rootUrl+"/"))&&s.rootUrl.length>a&&(i=s.slug,a=s.rootUrl.length);return i}
@@ -0,0 +1 @@
1
+ export const SCHEMA_EXAMPLES=[{id:"SoftwareApplication",label:"Software application",make:({title:e,description:t,url:r})=>({"@context":"https://schema.org","@type":"SoftwareApplication",name:e||"My App",description:t||"",url:r||"",applicationCategory:"DeveloperApplication",operatingSystem:"Web",offers:{"@type":"Offer",price:"0",priceCurrency:"GBP"}})},{id:"Organization",label:"Organisation",make:({siteName:e,url:t})=>({"@context":"https://schema.org","@type":"Organization",name:e||"My Company",url:t||"",logo:"",sameAs:[]})},{id:"Article",label:"Article",make:({title:e,description:t,url:r,siteName:a})=>({"@context":"https://schema.org","@type":"Article",headline:e||"",description:t||"",url:r||"",author:{"@type":"Person",name:""},publisher:{"@type":"Organization",name:a||""},datePublished:new Date().toISOString().slice(0,10)})},{id:"FAQPage",label:"FAQ page",make:()=>({"@context":"https://schema.org","@type":"FAQPage",mainEntity:[{"@type":"Question",name:"A question?",acceptedAnswer:{"@type":"Answer",text:"The answer."}}]})},{id:"Product",label:"Product",make:({title:e,description:t})=>({"@context":"https://schema.org","@type":"Product",name:e||"",description:t||"",image:"",offers:{"@type":"Offer",price:"0.00",priceCurrency:"GBP",availability:"https://schema.org/InStock"}})},{id:"LocalBusiness",label:"Local business",make:({siteName:e,url:t})=>({"@context":"https://schema.org","@type":"LocalBusiness",name:e||"",url:t||"",telephone:"",address:{"@type":"PostalAddress",streetAddress:"",addressLocality:"",postalCode:"",addressCountry:"GB"}})},{id:"Event",label:"Event",make:({title:e,description:t,url:r})=>({"@context":"https://schema.org","@type":"Event",name:e||"",description:t||"",url:r||"",startDate:"",location:{"@type":"Place",name:"",address:""}})}];export function exampleJson(e,t={}){const r=SCHEMA_EXAMPLES.find(a=>a.id===e);return r?JSON.stringify(r.make(t),null,2):""}export function parseSchema(e){const t=String(e??"").trim();if(!t)return{value:null,error:""};let r;try{r=JSON.parse(t)}catch(o){return{value:null,error:`Not valid JSON: ${String(o.message||o).replace(/^JSON\.parse: /,"")}`}}const a=Array.isArray(r)?r:[r];if(!a.length)return{value:null,error:"The list is empty - add a block or clear the box."};for(const[o,n]of a.entries()){const i=a.length>1?` (item ${o+1})`:"";if(!n||typeof n!="object"||Array.isArray(n))return{value:null,error:`Each block must be an object${i}.`};if(typeof n["@type"]!="string"||!n["@type"].trim())return{value:null,error:`Each block needs an "@type", such as "SoftwareApplication"${i}.`}}return{value:r,error:""}}export function schemaText(e){if(e==null||e==="")return"";if(typeof e=="string")return e;try{return JSON.stringify(e,null,2)}catch{return""}}
@@ -381,6 +381,22 @@
381
381
  <label class="form-label" for="field-seo-desc">SEO Description <span data-help="The summary search engines show under the title. Blank uses the page description."></span></label>
382
382
  <input id="field-seo-desc" type="text" data-model="seoDesc.value" class="form-input" placeholder="Overrides default description">
383
383
  </div>
384
+ <div class="pe-field">
385
+ <label class="form-label" for="field-seo-schema">Structured data (JSON-LD) <span data-help-title="Structured data" data-help="Schema.org JSON-LD for this page - a SoftwareApplication, an Organization, an Article, a FAQ… Search engines read it for rich results. Pick an example to start from, then fill it in. One object, or a list of them; each needs an &quot;@type&quot;. It goes into the page's head as a script of type application/ld+json, after the WebPage and breadcrumb blocks every page already gets."></span></label>
386
+ <select class="form-select pe-schema-example" aria-label="Insert an example" data-on-change="insertSchemaExample">
387
+ <option value="">Insert an example…</option>
388
+ <option value="SoftwareApplication">Software application</option>
389
+ <option value="Organization">Organisation</option>
390
+ <option value="Article">Article</option>
391
+ <option value="FAQPage">FAQ page</option>
392
+ <option value="Product">Product</option>
393
+ <option value="LocalBusiness">Local business</option>
394
+ <option value="Event">Event</option>
395
+ </select>
396
+ <textarea id="field-seo-schema" class="form-input dconfig-textarea" data-model="seoSchema.value" data-on-change="schemaChanged"
397
+ placeholder='{ "@context": "https://schema.org", "@type": "SoftwareApplication", "name": "…" }'></textarea>
398
+ <small class="form-hint pe-schema-note" data-bind-text="seoSchemaNote.value" data-bind-hidden="!seoSchemaNote.value"></small>
399
+ </div>
384
400
  </div>
385
401
 
386
402
  <!-- DConfig -->
@@ -1,4 +1,4 @@
1
- import{api as k}from"../api.js";import{CardBuilder as Us}from"../lib/card-builder.js";import{TimelineBuilder as _s}from"../lib/timeline-builder.js";import{attachEditorKeybindings as Ws,createToolbar as Ys,insertAtCursor as Js}from"../lib/markdown-toolbar.js";import{EffectsBuilder as Gs}from"../lib/effects-builder.js";import{attachContextMenu as Xs}from"../lib/shortcode-context-menu.js";import{registerCustomInserters as Qs}from"../lib/shortcode-insert.js";import{populateThemeSelect as Zs}from"../lib/themes.js";import{createFoldingManager as en}from"../lib/editor-folding.js";import{openScribeComposer as tn}from"../lib/scribe-composer.js";import{openCrudTutorial as on}from"../lib/crud-tutorial.js";import{addBannerAction as rt}from"../lib/plugin-chrome.js";import{helpPopovers as Da}from"../lib/help-popover.js";import{onceClosed as Lt,openMenuAt as Me,sidebarCount as lt}from"../lib/tool-kit.js";import{pickMedia as an}from"../lib/media-picker.js";import{openPageClone as sn}from"../lib/page-clone.js";import{openEditorReference as La}from"../lib/page-editor-reference.js";import{cleanType as Nt,DEFAULT_TYPE as Bt,EDITOR_FONTS as nn,fontHref as rn,isDefaultType as ln,LINE_HEIGHTS as cn,SIZES as dn,typeVars as Na}from"../lib/editor-type.js";import{getProjectFromHash as jt}from"../lib/project-context.js";import{attachSmoothCaret as Ba,getCaretPreference as pn,setCaretPreference as un}from"../lib/smooth-caret.js";import{SCREEN as Eo,bannerSection as fn,draftIsStale as mn,draftKey as je,initialProject as bn,makeDraft as ja,problems as Mo,readDraft as vn,snapshot as Y}from"../lib/page-editor-arrange.js";import{fmtDateTime as Ie}from"../lib/dates.js";import{hasDraft as Te,heldBlockEdits as gn,hold as To,takeHeld as So,wireHeldRouter as kn}from"../lib/editor-hop.js";import{hopTarget as Ia}from"../lib/editor-hop-arrange.js";import{checkNewName as yn,copyName as Co,usageText as hn}from"../lib/blocks-arrange.js";import{isCollapsed as Fa,setCollapsed as qa}from"../lib/sidebar-size.js";import{blockTagDefs as za,blocksInBody as wn}from"../lib/block-tags.js";import{draftKey as Fe,draftOffer as En,extractPlaceholders as Va,formatHtml as Mn,formFromBlock as Ha,payloadFromForm as Ka,rawPlaceholders as xo,shortcodeFor as Tn,snapshot as Po}from"../lib/block-editor-arrange.js";import{blockTabs as Sn,notHereLine as Cn,otherUses as Ua,outlineBlock as xn,previewBlocks as Pn,saveQuestion as On,savedMessage as Rn,usesLine as An}from"../lib/page-blocks-arrange.js";import{addressFor as _a,blockAloneBody as $n,blockRows as Dn,cleanLayout as Oo,closeTab as Ln,componentRows as Nn,componentStatusText as Bn,componentsInBody as jn,moveTab as In,nextLayout as Fn,openTab as Ro,pageRows as qn,pageStatusText as zn,rememberedTabs as Vn,renameTab as Hn,sampleRows as Wa,saveAllLabel as Kn,startingTabs as Un,tabId as Q,tabItem as I,tabsToClose as Ao,workspaceKey as _n}from"../lib/editor-workspace-arrange.js";import{PANES as It,STARTER_SOURCE as Wn,assembleSource as Ya,attributeFor as Yn,draftKey as qe,draftStatus as Jn,nameProblem as Gn,previewRows as Ja,readValue as Xn,snapshot as $o,splitSource as it,writeValue as Ga}from"../lib/component-editor-arrange.js";import{attachColourLayer as Xa,createPlainEditor as Do}from"../lib/plain-editor.js";function ct(d){const u=document.createElement("template");return u.innerHTML=d.trim(),u.content.firstElementChild}const Qn=Ie,Qa=d=>Ie(d.createdAt)+(d.label?` - ${d.label}`:""),Zn=`
1
+ import{api as k}from"../api.js";import{CardBuilder as Ws}from"../lib/card-builder.js";import{TimelineBuilder as Ys}from"../lib/timeline-builder.js";import{attachEditorKeybindings as Js,createToolbar as Gs,insertAtCursor as Xs}from"../lib/markdown-toolbar.js";import{EffectsBuilder as Qs}from"../lib/effects-builder.js";import{attachContextMenu as Zs}from"../lib/shortcode-context-menu.js";import{registerCustomInserters as en}from"../lib/shortcode-insert.js";import{populateThemeSelect as tn}from"../lib/themes.js";import{createFoldingManager as on}from"../lib/editor-folding.js";import{openScribeComposer as an}from"../lib/scribe-composer.js";import{openCrudTutorial as sn}from"../lib/crud-tutorial.js";import{addBannerAction as rt}from"../lib/plugin-chrome.js";import{exampleJson as nn,parseSchema as Eo,schemaText as rn}from"../lib/seo-structured-data.js";import{helpPopovers as La}from"../lib/help-popover.js";import{onceClosed as Nt,openMenuAt as Se,sidebarCount as lt}from"../lib/tool-kit.js";import{pickMedia as ln}from"../lib/media-picker.js";import{openPageClone as cn}from"../lib/page-clone.js";import{openEditorReference as Ba}from"../lib/page-editor-reference.js";import{cleanType as Lt,DEFAULT_TYPE as Bt,EDITOR_FONTS as dn,fontHref as pn,isDefaultType as un,LINE_HEIGHTS as mn,SIZES as fn,typeVars as ja}from"../lib/editor-type.js";import{getProjectFromHash as jt}from"../lib/project-context.js";import{attachSmoothCaret as Ia,getCaretPreference as bn,setCaretPreference as vn}from"../lib/smooth-caret.js";import{SCREEN as So,bannerSection as gn,draftIsStale as kn,draftKey as je,initialProject as hn,makeDraft as Fa,problems as Mo,readDraft as yn,snapshot as Y}from"../lib/page-editor-arrange.js";import{fmtDateTime as Ie}from"../lib/dates.js";import{hasDraft as Me,heldBlockEdits as wn,hold as To,takeHeld as Co,wireHeldRouter as En}from"../lib/editor-hop.js";import{hopTarget as qa}from"../lib/editor-hop-arrange.js";import{checkNewName as Sn,copyName as xo,usageText as Mn}from"../lib/blocks-arrange.js";import{isCollapsed as za,setCollapsed as Va}from"../lib/sidebar-size.js";import{blockTagDefs as Ha,blocksInBody as Tn}from"../lib/block-tags.js";import{draftKey as Fe,draftOffer as Cn,extractPlaceholders as Ka,formatHtml as xn,formFromBlock as Ua,payloadFromForm as _a,rawPlaceholders as Po,shortcodeFor as Pn,snapshot as Oo}from"../lib/block-editor-arrange.js";import{blockTabs as On,notHereLine as Rn,otherUses as Wa,outlineBlock as $n,previewBlocks as An,saveQuestion as Dn,savedMessage as Nn,usesLine as Ln}from"../lib/page-blocks-arrange.js";import{addressFor as Ya,blockAloneBody as Bn,blockRows as jn,cleanLayout as Ro,closeTab as In,componentRows as Fn,componentStatusText as qn,componentsInBody as zn,moveTab as Vn,nextLayout as Hn,openTab as $o,pageRows as Kn,pageStatusText as Un,rememberedTabs as _n,renameTab as Wn,sampleRows as Ja,saveAllLabel as Yn,startingTabs as Jn,tabId as Q,tabItem as I,tabsToClose as Ao,workspaceKey as Gn}from"../lib/editor-workspace-arrange.js";import{PANES as It,STARTER_SOURCE as Xn,assembleSource as Ga,attributeFor as Qn,draftKey as qe,draftStatus as Zn,nameProblem as er,previewRows as Xa,readValue as tr,snapshot as Do,splitSource as it,writeValue as Qa}from"../lib/component-editor-arrange.js";import{attachColourLayer as Za,createPlainEditor as No}from"../lib/plain-editor.js";function ct(d){const m=document.createElement("template");return m.innerHTML=d.trim(),m.content.firstElementChild}const or=Ie,es=d=>Ie(d.createdAt)+(d.label?` - ${d.label}`:""),ar=`
2
2
  <div class="pe-share">
3
3
  <p class="form-hint" style="margin-top:0">Anyone with the link can view this page even while it is unpublished, without
4
4
  signing in. The link works for this page only, expires on its own, and can be revoked at any time.</p>
@@ -30,7 +30,7 @@ import{api as k}from"../api.js";import{CardBuilder as Us}from"../lib/card-builde
30
30
  .pe-share [hidden] { display: none !important; }
31
31
  .pe-share-row { display: flex; align-items: center; gap: .6rem; padding: .5rem 0; border-bottom: 1px solid var(--dm-border-light); }
32
32
  .pe-share-text { flex: 1 1 auto; min-width: 0; font-size: .82rem; }
33
- </style>`;function Za(d){const u=rn(d);if(!u||document.querySelector(`link[data-pe-font="${CSS.escape(d)}"]`))return;const v=Object.assign(document.createElement("link"),{rel:"stylesheet",href:u});v.dataset.peFont=d,document.head.appendChild(v)}const er=`
33
+ </style>`;function ts(d){const m=pn(d);if(!m||document.querySelector(`link[data-pe-font="${CSS.escape(d)}"]`))return;const v=Object.assign(document.createElement("link"),{rel:"stylesheet",href:m});v.dataset.peFont=d,document.head.appendChild(v)}const sr=`
34
34
  <div class="pe-settings">
35
35
  <p class="pe-settings-note">How the editor looks to you - kept in this browser, for every page.</p>
36
36
  <label class="form-label" for="pe-set-font">Font <span data-help="The lettering in the editor (not on the site). Monospace keeps every character the same width, so shortcodes and Markdown line up."></span></label>
@@ -70,17 +70,17 @@ The quick brown fox - 0O 1lI {} [] () =&gt; ==
70
70
  .pe-settings-sample { white-space: pre-wrap; padding: .75rem 1rem; border-radius: var(--dm-radius-md); border: 1px solid var(--dm-border);
71
71
  background: var(--dm-background-alt); font-family: var(--pe-font); font-size: var(--pe-size); line-height: var(--pe-line); }
72
72
  .pe-settings-actions { display: flex; gap: .5rem; border-top: 1px solid var(--dm-border); padding-top: .75rem; }
73
- </style>`;function tr(d){const u=E.slideover({title:"Editor settings",size:"sm",position:"right"}),v=ct(`<div>${er}</div>`),l=Nt(d.peek()),b={font:M.observable(l.font),size:M.observable(String(l.size)),line:M.observable(String(l.line)),wrap:M.observable(l.wrap),caret:M.observable(pn()),sidebar:M.observable(Se().closeSidebar===!0)},h=M.effect(()=>{const L=b.sidebar.value;L!==(Se().closeSidebar===!0)&&dt("closeSidebar",L)}),w=M.computed(()=>Nt({font:b.font.value,size:b.size.value,line:b.line.value,wrap:b.wrap.value}));b.isDefault=M.computed(()=>ln(w.value)&&b.caret.value==="auto"&&!b.sidebar.value);const y=M.effect(()=>un(b.caret.value)),C=v.querySelector("#pe-set-font");for(const[L,$]of[["mono","Monospace"],["sans","Other"],["serif",null]]){const J=nn.filter(Ce=>Ce.kind===L),ut=$?Object.assign(document.createElement("optgroup"),{label:$}):C.querySelector("optgroup:last-of-type");ut.append(...J.map(Ce=>new Option(Ce.label,Ce.id))),$&&C.appendChild(ut)}v.querySelector("#pe-set-size").append(...dn.map(L=>new Option(`${L}px`,String(L)))),v.querySelector("#pe-set-line").append(...cn.map(L=>new Option({1.3:"Tight",1.45:"Snug",1.6:"Normal",1.8:"Relaxed",2:"Double"}[L]||String(L),String(L))));const N=v.querySelector(".pe-settings-sample"),c=M.effect(()=>{const L=w.value;Za(L.font);for(const[$,J]of Object.entries(Na(L)))N.style.setProperty($,J);N.style.whiteSpace=L.wrap?"pre-wrap":"pre",N.style.overflowX=L.wrap?"":"auto",JSON.stringify(L)!==JSON.stringify(Nt(d.peek()))&&(d.value=L,dt("type",L))});b.reset=()=>{b.font.value=Bt.font,b.size.value=String(Bt.size),b.line.value=String(Bt.line),b.wrap.value=Bt.wrap,b.caret.value="auto",b.sidebar.value=!1},b.done=()=>u.close(),u.element.appendChild(v);const P=M.applyBindings(b,v),H=Da(v);return Lt(u,()=>{P.dispose(),H(),c?.(),y?.(),h?.()}),Domma.icons?.scan?.(v),u.open(),u}async function or(d){const u=E.slideover({title:"Share preview link",size:"md",position:"right"}),v=ct(`<div>${Zn}</div>`),l={expiry:M.observable("7d"),note:M.observable(""),creating:M.observable(!1),url:M.observable(""),links:M.observable([]),loaded:M.observable(!1),listError:M.observable("")},b=async()=>{try{const w=await k.pages.previewLinks.list(d);l.links.value=w.filter(y=>!y.revokedAt&&!y.expired).map(y=>({id:y.id,title:y.label||"Untitled link",meta:`Expires ${Qn(y.expiresAt)} - ${y.useCount===1?"1 view":`${y.useCount||0} views`}`})),l.listError.value=""}catch(w){l.listError.value="Could not load existing links: "+(w.message||"unknown error")}finally{l.loaded.value=!0}};l.create=async()=>{l.creating.value=!0;try{const w=await k.pages.previewLinks.create(d,l.expiry.peek(),l.note.peek().trim());l.url.value=w.url,l.note.value="",E.toast("Share link created.",{type:"success"}),await b(),v.querySelector(".pe-share-url")?.select()}catch(w){E.toast("Could not create link: "+(w.message||"unknown error"),{type:"error"})}finally{l.creating.value=!1}},l.copy=async()=>{try{await navigator.clipboard.writeText(l.url.peek()),E.toast("Link copied to clipboard.",{type:"success"})}catch{v.querySelector(".pe-share-url")?.select(),E.toast("Press Ctrl+C to copy the selected link.",{type:"info"})}},l.revoke=async w=>{if(await E.confirm("Revoke this link? Anyone holding it loses access immediately."))try{await k.pages.previewLinks.revoke(w),E.toast("Link revoked.",{type:"success"}),await b()}catch(y){E.toast("Could not revoke: "+(y.message||"unknown error"),{type:"error"})}};const h=M.applyBindings(l,v);Lt(u,()=>h.dispose()),u.element.appendChild(v),u.open(),await b()}function ar(d){return new Promise(u=>{const v=E.slideover({title:"Save Named Version",size:"sm",position:"right"}),l=ct(`
73
+ </style>`;function nr(d){const m=E.slideover({title:"Editor settings",size:"sm",position:"right"}),v=ct(`<div>${sr}</div>`),l=Lt(d.peek()),b={font:M.observable(l.font),size:M.observable(String(l.size)),line:M.observable(String(l.line)),wrap:M.observable(l.wrap),caret:M.observable(bn()),sidebar:M.observable(Te().closeSidebar===!0)},y=M.effect(()=>{const A=b.sidebar.value;A!==(Te().closeSidebar===!0)&&dt("closeSidebar",A)}),w=M.computed(()=>Lt({font:b.font.value,size:b.size.value,line:b.line.value,wrap:b.wrap.value}));b.isDefault=M.computed(()=>un(w.value)&&b.caret.value==="auto"&&!b.sidebar.value);const h=M.effect(()=>vn(b.caret.value)),C=v.querySelector("#pe-set-font");for(const[A,D]of[["mono","Monospace"],["sans","Other"],["serif",null]]){const J=dn.filter(Ce=>Ce.kind===A),ut=D?Object.assign(document.createElement("optgroup"),{label:D}):C.querySelector("optgroup:last-of-type");ut.append(...J.map(Ce=>new Option(Ce.label,Ce.id))),D&&C.appendChild(ut)}v.querySelector("#pe-set-size").append(...fn.map(A=>new Option(`${A}px`,String(A)))),v.querySelector("#pe-set-line").append(...mn.map(A=>new Option({1.3:"Tight",1.45:"Snug",1.6:"Normal",1.8:"Relaxed",2:"Double"}[A]||String(A),String(A))));const L=v.querySelector(".pe-settings-sample"),c=M.effect(()=>{const A=w.value;ts(A.font);for(const[D,J]of Object.entries(ja(A)))L.style.setProperty(D,J);L.style.whiteSpace=A.wrap?"pre-wrap":"pre",L.style.overflowX=A.wrap?"":"auto",JSON.stringify(A)!==JSON.stringify(Lt(d.peek()))&&(d.value=A,dt("type",A))});b.reset=()=>{b.font.value=Bt.font,b.size.value=String(Bt.size),b.line.value=String(Bt.line),b.wrap.value=Bt.wrap,b.caret.value="auto",b.sidebar.value=!1},b.done=()=>m.close(),m.element.appendChild(v);const P=M.applyBindings(b,v),H=La(v);return Nt(m,()=>{P.dispose(),H(),c?.(),h?.(),y?.()}),Domma.icons?.scan?.(v),m.open(),m}async function rr(d){const m=E.slideover({title:"Share preview link",size:"md",position:"right"}),v=ct(`<div>${ar}</div>`),l={expiry:M.observable("7d"),note:M.observable(""),creating:M.observable(!1),url:M.observable(""),links:M.observable([]),loaded:M.observable(!1),listError:M.observable("")},b=async()=>{try{const w=await k.pages.previewLinks.list(d);l.links.value=w.filter(h=>!h.revokedAt&&!h.expired).map(h=>({id:h.id,title:h.label||"Untitled link",meta:`Expires ${or(h.expiresAt)} - ${h.useCount===1?"1 view":`${h.useCount||0} views`}`})),l.listError.value=""}catch(w){l.listError.value="Could not load existing links: "+(w.message||"unknown error")}finally{l.loaded.value=!0}};l.create=async()=>{l.creating.value=!0;try{const w=await k.pages.previewLinks.create(d,l.expiry.peek(),l.note.peek().trim());l.url.value=w.url,l.note.value="",E.toast("Share link created.",{type:"success"}),await b(),v.querySelector(".pe-share-url")?.select()}catch(w){E.toast("Could not create link: "+(w.message||"unknown error"),{type:"error"})}finally{l.creating.value=!1}},l.copy=async()=>{try{await navigator.clipboard.writeText(l.url.peek()),E.toast("Link copied to clipboard.",{type:"success"})}catch{v.querySelector(".pe-share-url")?.select(),E.toast("Press Ctrl+C to copy the selected link.",{type:"info"})}},l.revoke=async w=>{if(await E.confirm("Revoke this link? Anyone holding it loses access immediately."))try{await k.pages.previewLinks.revoke(w),E.toast("Link revoked.",{type:"success"}),await b()}catch(h){E.toast("Could not revoke: "+(h.message||"unknown error"),{type:"error"})}};const y=M.applyBindings(l,v);Nt(m,()=>y.dispose()),m.element.appendChild(v),m.open(),await b()}function lr(d){return new Promise(m=>{const v=E.slideover({title:"Save Named Version",size:"sm",position:"right"}),l=ct(`
74
74
  <div style="padding:1rem">
75
75
  <label class="form-label" for="pe-version-label">Version Label</label>
76
76
  <input id="pe-version-label" type="text" class="form-input" style="margin-bottom:.75rem"
77
77
  placeholder="e.g. Initial design, Before restructure" data-model="label.value" data-on-keydown="key">
78
78
  <button type="button" class="btn btn-primary" data-on-click="save" data-bind-disabled="saving.value">Save Version</button>
79
- </div>`);let b=!1;const h={label:M.observable(""),saving:M.observable(!1)};h.save=async()=>{h.saving.value=!0;try{await k.versions.create(d,h.label.peek().trim()||null),E.toast("Named version saved.",{type:"success"}),b=!0,v.close()}catch(y){E.toast("Failed to save version: "+y.message,{type:"error"})}finally{h.saving.value=!1}},h.key=y=>{y.key==="Enter"&&(y.preventDefault(),h.save())};const w=M.applyBindings(h,l);Lt(v,()=>{w.dispose(),u(b)}),v.element.appendChild(l),v.open(),setTimeout(()=>l.querySelector("input")?.focus(),100)})}const sr=`<div class="card">
79
+ </div>`);let b=!1;const y={label:M.observable(""),saving:M.observable(!1)};y.save=async()=>{y.saving.value=!0;try{await k.versions.create(d,y.label.peek().trim()||null),E.toast("Named version saved.",{type:"success"}),b=!0,v.close()}catch(h){E.toast("Failed to save version: "+h.message,{type:"error"})}finally{y.saving.value=!1}},y.key=h=>{h.key==="Enter"&&(h.preventDefault(),y.save())};const w=M.applyBindings(y,l);Nt(v,()=>{w.dispose(),m(b)}),v.element.appendChild(l),v.open(),setTimeout(()=>l.querySelector("input")?.focus(),100)})}const ir=`<div class="card">
80
80
  <h3>{{title}}</h3>
81
81
  <p>{{description}}</p>
82
82
  </div>
83
- `,nr=`
83
+ `,cr=`
84
84
  <div class="pe-new">
85
85
  <p class="form-hint" style="margin-top:0" data-bind-text="hint.value"></p>
86
86
  <label class="form-label" for="pe-new-name">Name</label>
@@ -100,11 +100,11 @@ The quick brown fox - 0O 1lI {} [] () =&gt; ==
100
100
  .pe-new [hidden] { display: none !important; }
101
101
  .pe-new-err { display: block; margin-top: .3rem; color: var(--dm-danger); font-size: var(--dm-text-xs); }
102
102
  .pe-new-actions { display: flex; justify-content: flex-end; gap: .5rem; margin-top: 1.25rem; }
103
- </style>`;function es({kind:d,projects:u,taken:v,project:l="",name:b="",check:h,create:w,register:y=()=>{}}){return new Promise(C=>{const N=E.slideover({title:d==="block"?"New block":"New component",size:"sm",position:"right"});y(()=>{try{N.close()}catch{}});const c=ct(`<div>${nr}</div>`);for(const $ of u)c.querySelector("#pe-new-project").append(new Option($.name||$.slug,$.slug));const P={name:M.observable(b),project:M.observable(l),busy:M.observable(!1),hint:M.observable(d==="block"?"A block is a reusable piece of HTML with {{placeholders}}, used on pages with its short tag or by a collection display. Its name is its tag - fixed once created.":"A component is a custom element - <dm-name> - with a template, props, a script and a style. Its name is its tag - fixed once created."),placeholder:M.observable(d==="block"?"e.g. team-card":"e.g. pricing-table")};P.error=M.computed(()=>P.name.value.trim()?h(P.name.value.trim(),v):""),P.canCreate=M.computed(()=>!P.busy.value&&!!P.name.value.trim()&&!h(P.name.value.trim(),v));let H="";P.create=async()=>{const $=P.name.peek().trim();if(!(!$||P.error.peek()||P.busy.peek())){P.busy.value=!0;try{await w($,P.project.peek()),H=$,N.close()}catch(J){E.toast(`Not created: ${J.message||J}`,{type:"error"})}finally{P.busy.value=!1}}},P.cancel=()=>N.close(),P.key=$=>{$.key==="Enter"&&($.preventDefault(),P.create())};const L=M.applyBindings(P,c);Lt(N,()=>{L.dispose(),C(H)}),N.element.appendChild(c),c.querySelector("#pe-new-project").value=l,N.open(),setTimeout(()=>{const $=c.querySelector("#pe-new-name");$?.focus(),$?.select()},100)})}let q=null,ts=!1;function rr(){ts||!window.R?.use||(ts=!0,kn(),R.use((d,u,v)=>{if(q&&!q.alive()){const h=q;q=null,h.dispose()}const l=u?.path||"",b=d?.path||"";if(!q||!Eo.test(l))return v();if(Eo.test(b)){const h=Ia(b);if(h){q.open(h,b);return}return v()}return q.dirty()?E.confirm("You have unsaved changes. Leave anyway? They are kept as drafts for your next visit.").then(h=>{h?(q?.keepDraft(),v()):q&&history.replaceState({pe:1},"","#"+q.address)}):v()}),M.subscribe("router:afterChange",({to:d}={})=>{if(q&&!Eo.test(d?.path||"")){const u=q;q=null,u.dispose()}}))}function lr(d){return d.split(`
104
- `).map(u=>u.trimEnd()).join(`
103
+ </style>`;function os({kind:d,projects:m,taken:v,project:l="",name:b="",check:y,create:w,register:h=()=>{}}){return new Promise(C=>{const L=E.slideover({title:d==="block"?"New block":"New component",size:"sm",position:"right"});h(()=>{try{L.close()}catch{}});const c=ct(`<div>${cr}</div>`);for(const D of m)c.querySelector("#pe-new-project").append(new Option(D.name||D.slug,D.slug));const P={name:M.observable(b),project:M.observable(l),busy:M.observable(!1),hint:M.observable(d==="block"?"A block is a reusable piece of HTML with {{placeholders}}, used on pages with its short tag or by a collection display. Its name is its tag - fixed once created.":"A component is a custom element - <dm-name> - with a template, props, a script and a style. Its name is its tag - fixed once created."),placeholder:M.observable(d==="block"?"e.g. team-card":"e.g. pricing-table")};P.error=M.computed(()=>P.name.value.trim()?y(P.name.value.trim(),v):""),P.canCreate=M.computed(()=>!P.busy.value&&!!P.name.value.trim()&&!y(P.name.value.trim(),v));let H="";P.create=async()=>{const D=P.name.peek().trim();if(!(!D||P.error.peek()||P.busy.peek())){P.busy.value=!0;try{await w(D,P.project.peek()),H=D,L.close()}catch(J){E.toast(`Not created: ${J.message||J}`,{type:"error"})}finally{P.busy.value=!1}}},P.cancel=()=>L.close(),P.key=D=>{D.key==="Enter"&&(D.preventDefault(),P.create())};const A=M.applyBindings(P,c);Nt(L,()=>{A.dispose(),C(H)}),L.element.appendChild(c),c.querySelector("#pe-new-project").value=l,L.open(),setTimeout(()=>{const D=c.querySelector("#pe-new-name");D?.focus(),D?.select()},100)})}let q=null,as=!1;function dr(){as||!window.R?.use||(as=!0,En(),R.use((d,m,v)=>{if(q&&!q.alive()){const y=q;q=null,y.dispose()}const l=m?.path||"",b=d?.path||"";if(!q||!So.test(l))return v();if(So.test(b)){const y=qa(b);if(y){q.open(y,b);return}return v()}return q.dirty()?E.confirm("You have unsaved changes. Leave anyway? They are kept as drafts for your next visit.").then(y=>{y?(q?.keepDraft(),v()):q&&history.replaceState({pe:1},"","#"+q.address)}):v()}),M.subscribe("router:afterChange",({to:d}={})=>{if(q&&!So.test(d?.path||"")){const m=q;q=null,m.dispose()}}))}function pr(d){return d.split(`
104
+ `).map(m=>m.trimEnd()).join(`
105
105
  `).replace(/\n{3,}/g,`
106
106
 
107
- `)}const os="editor_prefs";function Se(){return S.get(os)||{viewMode:"split",fullscreen:!1}}const ir=()=>{try{return Se()||{}}catch{return{}}};function dt(d,u){const v=Se();v[d]=u,S.set(os,v)}const as="editor-workspace.tabs",ss="editor-workspace.folded",pt="editor-workspace.css",cr="/__block-preview",dr=d=>d.replace(/<\/head>/i,"<style>body.dm-cloaked{opacity:1 !important;transition:none !important}</style></head>");function pr(d,u,v=()=>{}){let l=0;try{l=d.contentWindow.scrollY||0}catch{}d.addEventListener("load",()=>{try{d.contentWindow.scrollTo(0,l)}catch{}v()},{once:!0}),d.srcdoc=u}const ur=["urlPath","title","description","layout","status","sortOrder","category","visibility","project","theme","seoTitle","seoDesc","dconfig"],fr=["showInNav","sidebar","breadcrumbs","bundled"],ns=()=>({urlPath:"",title:"",description:"",layout:"",status:"draft",sortOrder:"99",showInNav:!1,sidebar:!1,breadcrumbs:!0,bundled:!1,tags:[],adminTags:[],category:"",visibility:"public",project:"",theme:"",seoTitle:"",seoDesc:"",dconfig:"",body:"",css:null});export const pageEditorView={templateUrl:"/admin/js/templates/page-editor.html",async onMount(d){if(rr(),q){const e=q;q=null,e.dispose()}const u=window.location.hash.replace(/^#/,""),v=[],l=e=>(v.push(e),e);let b=()=>!1,h=()=>{},w=()=>{};const y=d.get(0),C=y.querySelector("#pe-workspace"),N={address:u,alive:()=>C?.isConnected===!0,dirty:()=>b(),keepDraft:()=>h(),open:(e,t)=>w(e,t),dispose:()=>{v.splice(0).forEach(e=>{try{e()}catch{}})}};q=N;const c=()=>q===N&&N.alive();if(ir().closeSidebar===!0){const e=document.querySelector("#admin-sidebar");e&&!Fa(e)&&!window.matchMedia("(max-width: 768px)").matches&&(qa(e,!0,!1),l(()=>{e.isConnected&&Fa(e)&&qa(e,!1,!1)}))}const P=E.loader(y,{type:"dots"});let[H,L,$,J,ut,Ce,No,Bo]=await Promise.all([k.layouts.get().catch(()=>({})),k.settings.get().catch(()=>({})),k.pages.list().catch(()=>[]),k.projects.list().catch(()=>[]),k.pages.tags().catch(()=>[]),k.listTags.list("pages").then(e=>e.map(t=>t.name)).catch(()=>[]),k.get("/blocks?usage=1").catch(()=>k.blocks.list()).catch(()=>[]),k.components.list().catch(()=>[])]);if(!c()){P.destroy();return}P.destroy();const D=e=>y.querySelector(e);if($=Array.isArray($)?$:[],Zs(D("#field-theme"),{includeDefault:!0}),await kr(D("#field-visibility")),!c())return;const Ft=Object.keys(H||{});gr(D("#field-layout"),H,Ft[0]||"default");const jo=e=>Ft.includes(e)?e:Ft[0]||e||"default";for(const e of J)for(const t of["#field-project","#block-project","#comp-project"])D(t).append(new Option(e.name||e.slug,e.slug));let ae=Array.isArray(No)?No:[],ft=za(ae);const mt=new Map(ae.map(e=>[e.name,e.updatedAt])),ze=new Map(ae.map(e=>[e.name,e.usage])),pe=new Set(ae.map(e=>e.name)),Io=async()=>{const e=await k.get("/blocks?usage=1").catch(()=>null);if(!(!c()||!Array.isArray(e))){ae=e,ft=za(ae);for(const t of ae)pe.add(t.name),ze.set(t.name,t.usage),mt.has(t.name)||mt.set(t.name,t.updatedAt)}},qt=Array.isArray(L?.footer?.blocks)?L.footer.blocks.filter(e=>typeof e=="string"):[];let Ve=Array.isArray(Bo)?Bo.filter(e=>typeof e?.name=="string"):[];const se=new Set(Ve.map(e=>e.name)),Fo=async()=>{const e=await k.components.list().catch(()=>null);if(!(!c()||!Array.isArray(e))){Ve=e.filter(t=>typeof t?.name=="string");for(const t of Ve)se.add(t.name)}},te=Se(),p={urlPath:M.observable(""),title:M.observable(""),description:M.observable(""),layout:M.observable(jo("default")),status:M.observable("draft"),sortOrder:M.observable("99"),showInNav:M.observable(!1),sidebar:M.observable(!1),breadcrumbs:M.observable(!0),bundled:M.observable(!1),tags:M.observable([]),adminTags:M.observable([]),category:M.observable(""),visibility:M.observable("public"),project:M.observable(""),theme:M.observable(""),seoTitle:M.observable(""),seoDesc:M.observable(""),dconfig:M.observable(""),css:M.observable(""),cssOpened:M.observable(!1)},a={bodyRev:M.observable(0),base:M.observable(""),saving:M.observable(!1),viewMode:M.observable(Oo(te.viewMode)),fullscreen:M.observable(!!te.fullscreen),explorerOn:M.observable(te.explorer??!window.matchMedia("(max-width: 1024px)").matches),dockOn:M.observable(!!te.dock),dockTab:M.observable("details"),cssMode:M.observable(!1),folded:M.observable(!1),previewStatus:M.observable("Showing unsaved changes"),liveWidth:M.observable("full"),versions:M.observable([]),histLoading:M.observable(!1),histError:M.observable(""),compare:M.observable(null),heldNote:M.observable(""),tabs:M.observable([]),active:M.observable(""),curId:M.observable(null),blockTab:M.observable(""),blockPart:M.observable("html"),blockReady:M.observable(!1),blockNote:M.observable(""),blockUses:M.observable(""),blockProject:M.observable(""),blockBundled:M.observable(!1),blockWhere:M.observable(""),compTab:M.observable(""),compPart:M.observable("template"),compReady:M.observable(!1),compNote:M.observable(""),compReadOnly:M.observable(!1),compState:M.observable(""),compErrors:M.observable(""),compProject:M.observable(""),compBundled:M.observable(!1),blockRev:M.observable(0),compRev:M.observable(0),docRev:M.observable(0),filter:M.observable(""),secHere:M.observable(te.secHere!==!1),secPages:M.observable(te.secPages!==!1),secBlocks:M.observable(te.secBlocks!==!1),secComps:M.observable(te.secComps!==!1),thisPage:M.observable([]),pageRows:M.observable([]),blockRows:M.observable([]),compRows:M.observable([])},F=D("#markdown-editor"),ue=D("#css-editor"),rs=Ba(F),ls=Ba(ue);l(()=>rs.destroy()),l(()=>ls.destroy());const ne=document.createElement("div");ne.className="editor-line-numbers editor-line-numbers--foldable",F.parentElement.insertBefore(ne,F);const He=Xa(F,"md"),zt=Xa(ue,"css");l(()=>{He.destroy(),zt.destroy()});const qo=()=>{He.paint(),zt.paint()},G=en(F,ne,{onFoldChange:()=>He.paint()});let xe=!1;G.setGuard(e=>{xe=e});const bt=()=>G.getUnfoldedContent(),zo=M.observable(Nt(te.type));l(M.effect(()=>{const e=zo.value,t=D("#editor-body");if(t){Za(e.font);for(const[o,s]of Object.entries(Na(e)))t.style.setProperty(o,s);t.classList.toggle("pe-nowrap",!e.wrap),F.wrap=e.wrap?"soft":"off",ue.wrap=e.wrap?"soft":"off",requestAnimationFrame(()=>{c()&&qo()})}}));let Vo=null;l(()=>{try{Vo?.close()}catch{}});const K=()=>(a.bodyRev.value,{urlPath:p.urlPath.value.trim(),title:p.title.value,description:p.description.value,layout:p.layout.value,status:p.status.value,sortOrder:p.sortOrder.value,showInNav:p.showInNav.value,sidebar:p.sidebar.value,breadcrumbs:p.breadcrumbs.value,bundled:p.bundled.value,tags:p.tags.value,adminTags:p.adminTags.value,category:p.category.value,visibility:p.visibility.value,project:p.project.value,theme:p.theme.value,seoTitle:p.seoTitle.value,seoDesc:p.seoDesc.value,dconfig:p.dconfig.value,body:bt(),css:null}),Ho=()=>M.untracked?M.untracked(K):K(),Z=M.observable(""),vt=()=>p.cssOpened.peek()&&p.css.peek()!==Z.peek(),_=new Map;let f=null;const A=new Map,B=new Map;let x=[];const re=e=>Po(e.form)!==e.base,fe=()=>[...A.entries()].filter(([,e])=>re(e)).map(([e])=>e),me=e=>!e.readOnly&&$o({name:e.name,...e.form})!==e.base,Ke=()=>[...B.values()].filter(me).map(e=>e.name),Vt=()=>Y(Ho())!==a.base.peek(),Ht=e=>e.loaded&&(e===f?Vt():Y(e.state)!==e.base),Pe=()=>[..._.values()].filter(Ht),is=M.computed(()=>Y(K())!==a.base.value),cs=M.computed(()=>p.cssOpened.value&&p.css.value!==Z.value),ds=M.computed(()=>(a.blockRev.value,a.compRev.value,a.docRev.value,Y(K())!==a.base.value||p.cssOpened.value&&p.css.value!==Z.value||Pe().some(e=>e!==f)||fe().length>0||Ke().length>0)),ps=M.computed(()=>{a.blockRev.value,a.compRev.value;const e=I(a.active.value);if(e?.kind==="block"){const t=A.get(e.id);return!!(t&&re(t))}if(e?.kind==="component"){const t=B.get(e.id);return!!(t&&me(t))}return Y(K())!==a.base.value||p.cssOpened.value&&p.css.value!==Z.value}),us=M.computed(()=>a.curId.value!==null),Ko=M.computed(()=>I(a.active.value)?.kind||""),Uo=M.computed(()=>!!a.curId.value&&I(a.active.value)?.kind==="page"),fs=M.computed(()=>p.urlPath.value.trim()||a.curId.value||""),ms=M.computed(()=>Mo({urlPath:p.urlPath.value,dconfig:p.dconfig.value})[0]?.text||""),bs=M.computed(()=>{a.docRev.value;const e=I(a.active.value);return e?e.kind==="block"?a.blockUses.value:e.kind==="component"?Bn({name:e.id,readOnly:a.compReadOnly.value,state:a.compReady.value?a.compState.value:""}):zn({urlPath:p.urlPath.value,status:p.status.value,isEdit:!!a.curId.value}):""}),_o=M.computed(()=>a.compTab.value?"component":a.blockTab.value?"block":a.curId.value!==null?"page":""),vs=M.computed(()=>a.blockTab.value?`[${a.blockTab.value} /]`:""),gs=M.computed(()=>({compiling:"Compiling\u2026",ok:"Compiles",error:"Does not compile"})[a.compState.value]||""),ks=M.computed(()=>a.compTab.value?`<dm-${a.compTab.value}>`:""),ys=M.computed(()=>a.versions.value.some(e=>e.checked.value)),hs=M.computed(()=>a.versions.value.length>0&&a.versions.value.every(e=>e.checked.value)),ws=M.computed(()=>{const e=a.versions.value.filter(t=>t.checked.value).length;return e>0&&e<a.versions.value.length});b=()=>Pe().length>0||fe().length>0||Ke().length>0||vt();const Kt=(e,t,o)=>({title:String(e.title||"").trim()||"Untitled",description:String(e.description||"").trim(),layout:e.layout,status:e.status,sortOrder:parseInt(String(e.sortOrder),10)||99,showInNav:!!e.showInNav,sidebar:!!e.sidebar,...e.breadcrumbs?{}:{breadcrumbs:!1},...e.bundled?{bundled:!0}:{},tags:Array.isArray(e.tags)?e.tags:[],...t?.isEdit&&JSON.stringify(e.adminTags)===t.savedAdminTags?{}:{adminTags:Array.isArray(e.adminTags)?e.adminTags:[]},category:String(e.category||"").trim()||null,visibility:e.visibility||"public",project:e.project||"",seo:{title:String(e.seoTitle||"").trim(),description:String(e.seoDesc||"").trim()},dconfig:o,...e.theme?{theme:e.theme}:{}}),Ut=e=>{const t=String(e.dconfig||"").trim();if(!t)return null;try{return JSON.parse(t)}catch{return null}},Es=()=>p.urlPath.peek().trim()||f?.id||"/";let gt=[],Wo=[];const kt=e=>gt.includes(e)||qt.includes(e);let Oe=new Set;const yt=()=>{const e=[...new Set([...gt,...qt,...A.keys()])],t=gn(e.filter(s=>!A.has(s)),mt);Oe=new Set(Object.keys(t));const o=Oe.size?`Preview shows unsaved changes to ${[...Oe].join(", ")}`:"";return o!==a.heldNote.peek()&&(a.heldNote.value=o),Pn(t,[...A.entries()].map(([s,n])=>({name:s,content:n.form.content,css:n.form.css,dirty:re(n)})))};let _t="";const Ms=()=>{Yo();const e=_t;if(e){_t="";try{Re.contentDocument?.querySelector(`[data-block="${CSS.escape(e)}"]`)?.scrollIntoView({block:"center"})}catch{}}},Re=D("#markdown-preview");k.settings.getCustomCss().then(({css:e})=>{if(!c())return;Z.value=e||"";let t=null;try{t=S.get(pt)}catch{}typeof t=="string"&&t!==Z.peek()?(Zo(),p.css.value=t,E.toast("Your unsaved custom CSS is back - the CSS button shows it.",{type:"info"})):p.cssOpened.peek()||(p.css.value=Z.peek()),W(pt)}).catch(()=>{});const Yo=()=>{const e=Re?.contentDocument;if(!e?.head)return;const t=p.cssOpened.peek()?p.css.peek():"";if(!t)return;let o=e.getElementById("preview-custom-css");o||(o=e.createElement("style"),o.id="preview-custom-css",e.head.appendChild(o)),o.textContent=t};let Wt=null,Yt=0;const U=(e=!1)=>{clearTimeout(Wt),Wt=setTimeout(async()=>{if(!Re||a.viewMode.peek()==="write"||!a.active.peek()||a.compTab.peek())return;const t=++Yt,o=a.blockTab.peek();a.previewStatus.value="Rendering\u2026";try{let s;if(o&&(!f||!kt(o)))({html:s}=await k.pages.previewFull(cr,{title:o,layout:"blank",breadcrumbs:!1},$n(o,Ls(o)),yt()));else if(f){const n=K();({html:s}=await k.pages.previewFull(Es(),Kt(n,f,Ut(n)),n.body,yt()))}else{Re.srcdoc="";return}if(!c()||t!==Yt)return;pr(Re,xn(dr(s),o),Ms),a.previewStatus.value="Showing unsaved changes"}catch(s){if(!c()||t!==Yt)return;a.previewStatus.value="Preview failed - "+(s?.message||"unknown error"),Re.srcdoc='<!doctype html><meta charset="utf-8"><body style="font:14px system-ui;padding:1rem;color:#b91c1c;">Preview failed: '+String(s?.message||"unknown error").replace(/[<&]/g,n=>n==="<"?"&lt;":"&amp;")+"</body>"}},e?0:500)};l(()=>clearTimeout(Wt));const Jt=Ys(d.find("#markdown-editor"),d.find("#editor-toolbar"),{spacerDefault:L?.layoutOptions?.spacerSize??40});Xs(F,{blocks:()=>ft,openBlock:e=>Le(e)});const Ue=y.querySelector(".editor-toolbar-right");Ue.querySelectorAll(".editor-view-btn[data-mode]").forEach(e=>{const t=e.getAttribute("data-mode");e.classList.remove("active"),e.setAttribute("data-bind-class",`viewMode.value === '${t}' && 'active'`),e.setAttribute("data-on-click",`setMode('${t}')`)}),D("#fullscreen-btn")?.setAttribute("data-on-click","toggleFullscreen");const Ts=ct(`
107
+ `)}const ss="editor_prefs";function Te(){return S.get(ss)||{viewMode:"split",fullscreen:!1}}const ur=()=>{try{return Te()||{}}catch{return{}}};function dt(d,m){const v=Te();v[d]=m,S.set(ss,v)}const ns="editor-workspace.tabs",rs="editor-workspace.folded",pt="editor-workspace.css",mr="/__block-preview",fr=d=>d.replace(/<\/head>/i,"<style>body.dm-cloaked{opacity:1 !important;transition:none !important}</style></head>");function br(d,m,v=()=>{}){let l=0;try{l=d.contentWindow.scrollY||0}catch{}d.addEventListener("load",()=>{try{d.contentWindow.scrollTo(0,l)}catch{}v()},{once:!0}),d.srcdoc=m}const vr=["urlPath","title","description","layout","status","sortOrder","category","visibility","project","theme","seoTitle","seoDesc","seoSchema","dconfig"],gr=["showInNav","sidebar","breadcrumbs","bundled"],ls=()=>({urlPath:"",title:"",description:"",layout:"",status:"draft",sortOrder:"99",showInNav:!1,sidebar:!1,breadcrumbs:!0,bundled:!1,tags:[],adminTags:[],category:"",visibility:"public",project:"",theme:"",seoTitle:"",seoDesc:"",seoSchema:"",dconfig:"",body:"",css:null});export const pageEditorView={templateUrl:"/admin/js/templates/page-editor.html",async onMount(d){if(dr(),q){const e=q;q=null,e.dispose()}const m=window.location.hash.replace(/^#/,""),v=[],l=e=>(v.push(e),e);let b=()=>!1,y=()=>{},w=()=>{};const h=d.get(0),C=h.querySelector("#pe-workspace"),L={address:m,alive:()=>C?.isConnected===!0,dirty:()=>b(),keepDraft:()=>y(),open:(e,t)=>w(e,t),dispose:()=>{v.splice(0).forEach(e=>{try{e()}catch{}})}};q=L;const c=()=>q===L&&L.alive();if(ur().closeSidebar===!0){const e=document.querySelector("#admin-sidebar");e&&!za(e)&&!window.matchMedia("(max-width: 768px)").matches&&(Va(e,!0,!1),l(()=>{e.isConnected&&za(e)&&Va(e,!1,!1)}))}const P=E.loader(h,{type:"dots"});let[H,A,D,J,ut,Ce,Bo,jo]=await Promise.all([k.layouts.get().catch(()=>({})),k.settings.get().catch(()=>({})),k.pages.list().catch(()=>[]),k.projects.list().catch(()=>[]),k.pages.tags().catch(()=>[]),k.listTags.list("pages").then(e=>e.map(t=>t.name)).catch(()=>[]),k.get("/blocks?usage=1").catch(()=>k.blocks.list()).catch(()=>[]),k.components.list().catch(()=>[])]);if(!c()){P.destroy();return}P.destroy();const N=e=>h.querySelector(e);if(D=Array.isArray(D)?D:[],tn(N("#field-theme"),{includeDefault:!0}),await Er(N("#field-visibility")),!c())return;const Ft=Object.keys(H||{});wr(N("#field-layout"),H,Ft[0]||"default");const Io=e=>Ft.includes(e)?e:Ft[0]||e||"default";for(const e of J)for(const t of["#field-project","#block-project","#comp-project"])N(t).append(new Option(e.name||e.slug,e.slug));let ae=Array.isArray(Bo)?Bo:[],mt=Ha(ae);const ft=new Map(ae.map(e=>[e.name,e.updatedAt])),ze=new Map(ae.map(e=>[e.name,e.usage])),pe=new Set(ae.map(e=>e.name)),Fo=async()=>{const e=await k.get("/blocks?usage=1").catch(()=>null);if(!(!c()||!Array.isArray(e))){ae=e,mt=Ha(ae);for(const t of ae)pe.add(t.name),ze.set(t.name,t.usage),ft.has(t.name)||ft.set(t.name,t.updatedAt)}},qt=Array.isArray(A?.footer?.blocks)?A.footer.blocks.filter(e=>typeof e=="string"):[];let Ve=Array.isArray(jo)?jo.filter(e=>typeof e?.name=="string"):[];const se=new Set(Ve.map(e=>e.name)),qo=async()=>{const e=await k.components.list().catch(()=>null);if(!(!c()||!Array.isArray(e))){Ve=e.filter(t=>typeof t?.name=="string");for(const t of Ve)se.add(t.name)}},te=Te(),p={urlPath:M.observable(""),title:M.observable(""),description:M.observable(""),layout:M.observable(Io("default")),status:M.observable("draft"),sortOrder:M.observable("99"),showInNav:M.observable(!1),sidebar:M.observable(!1),breadcrumbs:M.observable(!0),bundled:M.observable(!1),tags:M.observable([]),adminTags:M.observable([]),category:M.observable(""),visibility:M.observable("public"),project:M.observable(""),theme:M.observable(""),seoTitle:M.observable(""),seoDesc:M.observable(""),seoSchema:M.observable(""),dconfig:M.observable(""),css:M.observable(""),cssOpened:M.observable(!1)},o={bodyRev:M.observable(0),base:M.observable(""),saving:M.observable(!1),viewMode:M.observable(Ro(te.viewMode)),fullscreen:M.observable(!!te.fullscreen),explorerOn:M.observable(te.explorer??!window.matchMedia("(max-width: 1024px)").matches),dockOn:M.observable(!!te.dock),dockTab:M.observable("details"),seoSchemaNote:M.observable(""),cssMode:M.observable(!1),folded:M.observable(!1),previewStatus:M.observable("Showing unsaved changes"),liveWidth:M.observable("full"),versions:M.observable([]),histLoading:M.observable(!1),histError:M.observable(""),compare:M.observable(null),heldNote:M.observable(""),tabs:M.observable([]),active:M.observable(""),curId:M.observable(null),blockTab:M.observable(""),blockPart:M.observable("html"),blockReady:M.observable(!1),blockNote:M.observable(""),blockUses:M.observable(""),blockProject:M.observable(""),blockBundled:M.observable(!1),blockWhere:M.observable(""),compTab:M.observable(""),compPart:M.observable("template"),compReady:M.observable(!1),compNote:M.observable(""),compReadOnly:M.observable(!1),compState:M.observable(""),compErrors:M.observable(""),compProject:M.observable(""),compBundled:M.observable(!1),blockRev:M.observable(0),compRev:M.observable(0),docRev:M.observable(0),filter:M.observable(""),secHere:M.observable(te.secHere!==!1),secPages:M.observable(te.secPages!==!1),secBlocks:M.observable(te.secBlocks!==!1),secComps:M.observable(te.secComps!==!1),thisPage:M.observable([]),pageRows:M.observable([]),blockRows:M.observable([]),compRows:M.observable([])},F=N("#markdown-editor"),ue=N("#css-editor"),is=Ia(F),cs=Ia(ue);l(()=>is.destroy()),l(()=>cs.destroy());const ne=document.createElement("div");ne.className="editor-line-numbers editor-line-numbers--foldable",F.parentElement.insertBefore(ne,F);const He=Za(F,"md"),zt=Za(ue,"css");l(()=>{He.destroy(),zt.destroy()});const zo=()=>{He.paint(),zt.paint()},G=on(F,ne,{onFoldChange:()=>He.paint()});let xe=!1;G.setGuard(e=>{xe=e});const bt=()=>G.getUnfoldedContent(),Vo=M.observable(Lt(te.type));l(M.effect(()=>{const e=Vo.value,t=N("#editor-body");if(t){ts(e.font);for(const[a,s]of Object.entries(ja(e)))t.style.setProperty(a,s);t.classList.toggle("pe-nowrap",!e.wrap),F.wrap=e.wrap?"soft":"off",ue.wrap=e.wrap?"soft":"off",requestAnimationFrame(()=>{c()&&zo()})}}));let Ho=null;l(()=>{try{Ho?.close()}catch{}});const K=()=>(o.bodyRev.value,{urlPath:p.urlPath.value.trim(),title:p.title.value,description:p.description.value,layout:p.layout.value,status:p.status.value,sortOrder:p.sortOrder.value,showInNav:p.showInNav.value,sidebar:p.sidebar.value,breadcrumbs:p.breadcrumbs.value,bundled:p.bundled.value,tags:p.tags.value,adminTags:p.adminTags.value,category:p.category.value,visibility:p.visibility.value,project:p.project.value,theme:p.theme.value,seoTitle:p.seoTitle.value,seoDesc:p.seoDesc.value,seoSchema:p.seoSchema.value,dconfig:p.dconfig.value,body:bt(),css:null}),Ko=()=>M.untracked?M.untracked(K):K(),Z=M.observable(""),vt=()=>p.cssOpened.peek()&&p.css.peek()!==Z.peek(),_=new Map;let u=null;const $=new Map,B=new Map;let x=[];const re=e=>Oo(e.form)!==e.base,me=()=>[...$.entries()].filter(([,e])=>re(e)).map(([e])=>e),fe=e=>!e.readOnly&&Do({name:e.name,...e.form})!==e.base,Ke=()=>[...B.values()].filter(fe).map(e=>e.name),Vt=()=>Y(Ko())!==o.base.peek(),Ht=e=>e.loaded&&(e===u?Vt():Y(e.state)!==e.base),Pe=()=>[..._.values()].filter(Ht),ds=M.computed(()=>Y(K())!==o.base.value),ps=M.computed(()=>p.cssOpened.value&&p.css.value!==Z.value),us=M.computed(()=>(o.blockRev.value,o.compRev.value,o.docRev.value,Y(K())!==o.base.value||p.cssOpened.value&&p.css.value!==Z.value||Pe().some(e=>e!==u)||me().length>0||Ke().length>0)),ms=M.computed(()=>{o.blockRev.value,o.compRev.value;const e=I(o.active.value);if(e?.kind==="block"){const t=$.get(e.id);return!!(t&&re(t))}if(e?.kind==="component"){const t=B.get(e.id);return!!(t&&fe(t))}return Y(K())!==o.base.value||p.cssOpened.value&&p.css.value!==Z.value}),fs=M.computed(()=>o.curId.value!==null),Uo=M.computed(()=>I(o.active.value)?.kind||""),_o=M.computed(()=>!!o.curId.value&&I(o.active.value)?.kind==="page"),bs=M.computed(()=>p.urlPath.value.trim()||o.curId.value||""),vs=M.computed(()=>Mo({urlPath:p.urlPath.value,dconfig:p.dconfig.value})[0]?.text||""),gs=M.computed(()=>{o.docRev.value;const e=I(o.active.value);return e?e.kind==="block"?o.blockUses.value:e.kind==="component"?qn({name:e.id,readOnly:o.compReadOnly.value,state:o.compReady.value?o.compState.value:""}):Un({urlPath:p.urlPath.value,status:p.status.value,isEdit:!!o.curId.value}):""}),Wo=M.computed(()=>o.compTab.value?"component":o.blockTab.value?"block":o.curId.value!==null?"page":""),ks=M.computed(()=>o.blockTab.value?`[${o.blockTab.value} /]`:""),hs=M.computed(()=>({compiling:"Compiling\u2026",ok:"Compiles",error:"Does not compile"})[o.compState.value]||""),ys=M.computed(()=>o.compTab.value?`<dm-${o.compTab.value}>`:""),ws=M.computed(()=>o.versions.value.some(e=>e.checked.value)),Es=M.computed(()=>o.versions.value.length>0&&o.versions.value.every(e=>e.checked.value)),Ss=M.computed(()=>{const e=o.versions.value.filter(t=>t.checked.value).length;return e>0&&e<o.versions.value.length});b=()=>Pe().length>0||me().length>0||Ke().length>0||vt();const Kt=(e,t,a)=>({title:String(e.title||"").trim()||"Untitled",description:String(e.description||"").trim(),layout:e.layout,status:e.status,sortOrder:parseInt(String(e.sortOrder),10)||99,showInNav:!!e.showInNav,sidebar:!!e.sidebar,...e.breadcrumbs?{}:{breadcrumbs:!1},...e.bundled?{bundled:!0}:{},tags:Array.isArray(e.tags)?e.tags:[],...t?.isEdit&&JSON.stringify(e.adminTags)===t.savedAdminTags?{}:{adminTags:Array.isArray(e.adminTags)?e.adminTags:[]},category:String(e.category||"").trim()||null,visibility:e.visibility||"public",project:e.project||"",seo:{title:String(e.seoTitle||"").trim(),description:String(e.seoDesc||"").trim(),...Yo(e)?{schema:Yo(e)}:t?.isEdit?{schema:null}:{}},dconfig:a,...e.theme?{theme:e.theme}:{}}),Yo=e=>Eo(e.seoSchema).value,Ut=e=>{const t=String(e.dconfig||"").trim();if(!t)return null;try{return JSON.parse(t)}catch{return null}},Ms=()=>p.urlPath.peek().trim()||u?.id||"/";let gt=[],Jo=[];const kt=e=>gt.includes(e)||qt.includes(e);let Oe=new Set;const ht=()=>{const e=[...new Set([...gt,...qt,...$.keys()])],t=wn(e.filter(s=>!$.has(s)),ft);Oe=new Set(Object.keys(t));const a=Oe.size?`Preview shows unsaved changes to ${[...Oe].join(", ")}`:"";return a!==o.heldNote.peek()&&(o.heldNote.value=a),An(t,[...$.entries()].map(([s,r])=>({name:s,content:r.form.content,css:r.form.css,dirty:re(r)})))};let _t="";const Ts=()=>{Go();const e=_t;if(e){_t="";try{Re.contentDocument?.querySelector(`[data-block="${CSS.escape(e)}"]`)?.scrollIntoView({block:"center"})}catch{}}},Re=N("#markdown-preview");k.settings.getCustomCss().then(({css:e})=>{if(!c())return;Z.value=e||"";let t=null;try{t=S.get(pt)}catch{}typeof t=="string"&&t!==Z.peek()?(ta(),p.css.value=t,E.toast("Your unsaved custom CSS is back - the CSS button shows it.",{type:"info"})):p.cssOpened.peek()||(p.css.value=Z.peek()),W(pt)}).catch(()=>{});const Go=()=>{const e=Re?.contentDocument;if(!e?.head)return;const t=p.cssOpened.peek()?p.css.peek():"";if(!t)return;let a=e.getElementById("preview-custom-css");a||(a=e.createElement("style"),a.id="preview-custom-css",e.head.appendChild(a)),a.textContent=t};let Wt=null,Yt=0;const U=(e=!1)=>{clearTimeout(Wt),Wt=setTimeout(async()=>{if(!Re||o.viewMode.peek()==="write"||!o.active.peek()||o.compTab.peek())return;const t=++Yt,a=o.blockTab.peek();o.previewStatus.value="Rendering\u2026";try{let s;if(a&&(!u||!kt(a)))({html:s}=await k.pages.previewFull(mr,{title:a,layout:"blank",breadcrumbs:!1},Bn(a,Bs(a)),ht()));else if(u){const r=K();({html:s}=await k.pages.previewFull(Ms(),Kt(r,u,Ut(r)),r.body,ht()))}else{Re.srcdoc="";return}if(!c()||t!==Yt)return;br(Re,$n(fr(s),a),Ts),o.previewStatus.value="Showing unsaved changes"}catch(s){if(!c()||t!==Yt)return;o.previewStatus.value="Preview failed - "+(s?.message||"unknown error"),Re.srcdoc='<!doctype html><meta charset="utf-8"><body style="font:14px system-ui;padding:1rem;color:#b91c1c;">Preview failed: '+String(s?.message||"unknown error").replace(/[<&]/g,r=>r==="<"?"&lt;":"&amp;")+"</body>"}},e?0:500)};l(()=>clearTimeout(Wt));const Jt=Gs(d.find("#markdown-editor"),d.find("#editor-toolbar"),{spacerDefault:A?.layoutOptions?.spacerSize??40});Zs(F,{blocks:()=>mt,openBlock:e=>Ne(e)});const Ue=h.querySelector(".editor-toolbar-right");Ue.querySelectorAll(".editor-view-btn[data-mode]").forEach(e=>{const t=e.getAttribute("data-mode");e.classList.remove("active"),e.setAttribute("data-bind-class",`viewMode.value === '${t}' && 'active'`),e.setAttribute("data-on-click",`setMode('${t}')`)}),N("#fullscreen-btn")?.setAttribute("data-on-click","toggleFullscreen");const Cs=ct(`
108
108
  <span class="pe-toolbar-extras" style="display:contents">
109
109
  <button type="button" class="editor-view-btn" data-tip="Edit Custom CSS" data-on-click="toggleCss"
110
110
  data-bind-class="cssMode.value && 'active'"><span>CSS</span></button>
@@ -116,8 +116,8 @@ The quick brown fox - 0O 1lI {} [] () =&gt; ==
116
116
  <span class="pe-fold-icon" hidden data-bind-hidden="!folded.value"><span data-icon="chevrons-right" data-icon-size="10"></span></span>
117
117
  </button>
118
118
  <span class="editor-toolbar-sep"></span>
119
- </span>`);Ue.prepend(Ts),Ue.before(D(".pe-block-bar")),Ue.before(D(".pe-comp-bar"));const Jo=e=>(Array.isArray(e)?e:[]).map(t=>({value:t,label:t})),Go=D("#field-tags");let ht=null;ht=Go?E.pillbox(Go,{data:Jo(ut),value:[],creatable:!0,searchable:!0,placeholder:"Add tag\u2026",onChange:()=>{ht&&(p.tags.value=[...ht.getValue()])}}):null;const Xo=D("#field-admin-tags");let wt=null;wt=Xo?E.pillbox(Xo,{data:Jo(Ce),value:[],creatable:!0,searchable:!0,placeholder:"Add admin tag\u2026",onChange:()=>{wt&&(p.adminTags.value=[...wt.getValue()])}}):null;const Qo=()=>{a.bodyRev.value=a.bodyRev.peek()+1},be=()=>{a.blockRev.value=a.blockRev.peek()+1},ve=()=>{a.compRev.value=a.compRev.peek()+1},_e=()=>{a.docRev.value=a.docRev.peek()+1},Zo=()=>{p.cssOpened.peek()||(p.css.peek()||(p.css.value=Z.peek()),p.cssOpened.value=!0,Ws(ue))},We=e=>{for(const t of ur)p[t].value=e[t]==null?"":String(e[t]);for(const t of fr)p[t].value=!!e[t];p.tags.value=Array.isArray(e.tags)?e.tags:[],ht?.setValue(p.tags.peek()),Array.isArray(e.adminTags)&&(p.adminTags.value=e.adminTags),wt?.setValue(p.adminTags.peek()),Lo(D("#field-visibility"),p.visibility.peek()),G.unfoldAll(),a.folded.value=!1,xe=!0,F.value=e.body==null?"":String(e.body),xe=!1,G.refresh(),qo(),Qo()},ea=(e,t)=>({urlPath:t||"",title:e?.title||"",description:e?.description||"",layout:jo(e?.layout||"default"),status:e?.status||"draft",sortOrder:String(e?.sortOrder??99),showInNav:!!e?.showInNav,sidebar:!!e?.sidebar,breadcrumbs:e?e.breadcrumbs!==!1:!0,bundled:!!e?.bundled,tags:Array.isArray(e?.tags)?e.tags:[],adminTags:Array.isArray(e?.adminTags)?e.adminTags:[],category:e?.category||"",visibility:typeof e?.visibility=="string"&&e.visibility?e.visibility:"public",project:bn({isEdit:!!t,page:e,projects:J,fromHash:t?"":jt()||"",urlPath:t||""}),theme:e?.theme||"",seoTitle:e?.seo?.title||"",seoDesc:e?.seo?.description||"",dconfig:e?.dconfig?JSON.stringify(e.dconfig,null,2):"",body:e?.content||"",css:null}),ta=()=>{f&&(f.state=K(),f.at=Date.now())},oa=e=>{f!==e&&(ta(),f=e,We(e.state),a.base.value=e.base,a.curId.value=e.id,e.at=Date.now(),Mt(),_e())},Ss=()=>{ta(),f=null;const e=ns();We(e),a.base.value=Y(e),a.curId.value=null,Mt(),_e()};We(ns()),a.base.value=Y(K());const W=e=>{try{S.remove?S.remove(e):S.set(e,null)}catch{}},aa=(e,t)=>({name:e.name,...e.form,base:e.base,at:t});h=()=>{try{for(const t of Pe())S.set(je(t.id),ja(t===f?Ho():t.state,t.base));vt()&&S.set(pt,p.css.peek());const e=new Date().toISOString();for(const t of fe()){const o=A.get(t);S.set(Fe(t),{form:{...o.form},base:o.base,at:e})}for(const t of Ke())S.set(qe(t),aa(B.get(t),e))}catch{}};const sa=e=>{b()&&(h(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",sa),l(()=>window.removeEventListener("beforeunload",sa));let Ye=!1,Et=null;const Mt=()=>{Ye=!1,Et=null,a.versions.value=[],a.compare.value=null,a.histError.value="",a.dockOn.peek()&&a.dockTab.peek()==="history"&&(f?.isEdit?le():a.dockTab.value="details")},le=async()=>{const e=f;if(e?.isEdit){Ye=!0,a.histLoading.value=!0,a.histError.value="";try{const t=(await k.versions.list(e.id)).versions||[];if(!c()||f!==e)return;a.versions.value=t.map(o=>({...o,when:Ie(o.createdAt),author:o.author||"",label:o.label||"",who:[o.author,o.label].filter(Boolean).join(" - "),checked:M.observable(!1)}))}catch(t){f===e&&(a.histError.value="Failed to load versions: "+t.message)}finally{f===e&&(a.histLoading.value=!1)}}},Gt=e=>a.versions.peek().find(t=>t.filename===e),Xt=async e=>{const t=f;try{const o=await k.versions.get(t.id,e.filename);if(!c()||f!==t)return;Et=e.filename,a.compare.value={label:Qa(e),old:o.content,current:bt()}}catch(o){E.toast("Failed to load version: "+o.message,{type:"error"})}},Cs=async()=>{const e=f;if(!e?.isEdit)return;const t=await k.pages.get(e.id).catch(()=>null);if(!c()||f!==e||!t)return;e.page=t;const o=ea(t,e.id);e.base=Y(o),e.savedAdminTags=JSON.stringify(o.adminTags),We(o),a.base.value=e.base,Mt(),a.dockOn.peek()&&a.dockTab.peek()==="history"&&le(),U()},na=async e=>{if(!(!e||!await E.confirm("Restore this version? The current page content will be overwritten (a pre-restore snapshot will be saved automatically).")))try{await k.versions.restore(f.id,e),E.toast("Page restored.",{type:"success"}),await Cs()}catch(t){E.toast("Restore failed: "+t.message,{type:"error"})}},xs=async e=>{if(await E.confirm("Delete this version? This cannot be undone."))try{await k.versions.delete(f.id,e.filename),E.toast("Version deleted.",{type:"success"}),await le()}catch(t){E.toast("Failed to delete: "+t.message,{type:"error"})}},Ae=new Map,Ps=e=>{if(!Ae.has(e)){const t=I(e);Ae.set(e,{id:e,kind:t.kind,kindText:t.kind==="page"?"Page":t.kind==="component"?"Component":"Block",label:M.observable(""),title:M.observable(""),active:M.observable(!1),dirty:M.observable(!1)})}return Ae.get(e)},ra=e=>{const t=e===f?p.title.peek():e?.loaded?e.state.title:$.find(o=>o.urlPath===e?.id)?.title;return String(t||"").trim()||e?.id||"New page"};let la=!0,ia="";const Os=()=>{if(la)return;const e={ids:x,active:a.active.peek()},t=JSON.stringify(e);if(t!==ia){ia=t;try{S.set(as,e)}catch{}}},V=()=>{const e=x.map(Ps);for(const t of e){const o=I(t.id);let s,n;if(o.kind==="page"){const i=_.get(o.id);s=ra(i),n=i?.loaded?Ht(i):Te(o),t.title.value=o.id?`${s} (${o.id})`:"New page"}else if(o.kind==="component"){const i=B.get(o.id);s=o.id,n=i?me(i):Te(o),t.title.value=`Component <dm-${o.id}>`}else{const i=A.get(o.id);s=o.id,n=i?re(i):Oe.has(o.id)||Te(o),t.title.value=`Block ${o.id}`}t.label.peek()!==s&&(t.label.value=s),t.dirty.peek()!==n&&(t.dirty.value=n);const r=t.id===a.active.peek();t.active.peek()!==r&&(t.active.value=r)}e.map(t=>t.id).join("|")!==a.tabs.peek().map(t=>t.id).join("|")&&(a.tabs.value=e),Os()};let ge=new Set;try{const e=S.get(ss);Array.isArray(e)&&(ge=new Set(e.filter(t=>typeof t=="string")))}catch{}const ca=new Map,Qt=e=>{let t=ca.get(e.key);return t?(t.label.peek()!==e.label&&(t.label.value=e.label),t.meta.peek()!==(e.meta||"")&&(t.meta.value=e.meta||""),t.status.peek()!==(e.status||"")&&(t.status.value=e.status||""),t.folder.peek()!==!!e.folder&&(t.folder.value=!!e.folder)):(t={...e,label:M.observable(e.label),meta:M.observable(e.meta||""),status:M.observable(e.status||""),folder:M.observable(!!e.folder),active:M.observable(!1),dirty:M.observable(!1),folded:M.observable(!1),title:""},ca.set(e.key,t)),t.title=e.kind==="block"||e.kind==="component"?e.meta?`${e.id} - ${e.meta}`:e.id:e.kind==="folder"?e.meta:`${e.label} (${e.meta})`,t},Zt=new Map,z=()=>{const e=a.filter.peek(),t=I(a.active.peek()),o=t?.kind==="page"?t.id:null,s=t?.kind==="block"?t.id:null,n=t?.kind==="component"?t.id:null,r=new Set(Pe().map(m=>m.id)),i=qn($,{filter:e,folded:ge}).map(Qt);for(const m of i){const T=m.kind==="page"&&m.id===o,j=m.kind==="page"&&(r.has(m.id)||!_.get(m.id)?.loaded&&Te({kind:"page",id:m.id})),de=ge.has(m.id);m.active.peek()!==T&&(m.active.value=T),m.dirty.peek()!==j&&(m.dirty.value=j),m.folded.peek()!==de&&(m.folded.value=de)}const g=Dn(ae,e).map(Qt),O=m=>{const T=A.get(m);return T?re(T):Oe.has(m)||Te({kind:"block",id:m})};for(const m of g){const T=m.id===s,j=O(m.id);m.active.peek()!==T&&(m.active.value=T),m.dirty.peek()!==j&&(m.dirty.value=j)}const X=Nn(Ve,e).map(Qt),Ee=m=>{const T=B.get(m);return T?me(T):Te({kind:"component",id:m})};for(const m of X){const T=m.id===n,j=Ee(m.id);m.active.peek()!==T&&(m.active.value=T),m.dirty.peek()!==j&&(m.dirty.value=j)}const ho=f?Sn(gt,qt,pe):[],$a=(m,T,j)=>(Zt.has(m)||Zt.set(m,{key:m,id:T,label:j,kindText:"",title:"",active:M.observable(!1),dirty:M.observable(!1)}),Zt.get(m)),wo=ho.map(m=>{const T=$a(`block:${m.name}`,m.name,m.name);T.kindText=m.footer?"Footer":"Block",T.title=`${m.name} - ${m.footer?"in the footer, on every page":"a block on this page"}`;const j=m.name===s,de=O(m.name);return T.active.peek()!==j&&(T.active.value=j),T.dirty.peek()!==de&&(T.dirty.value=de),T});for(const m of Wo){const T=$a(`component:${m}`,m,m);T.kindText="Component",T.title=`<dm-${m}> - a component on this page`;const j=m===n,de=Ee(m);T.active.peek()!==j&&(T.active.value=j),T.dirty.peek()!==de&&(T.dirty.value=de),wo.push(T)}const Dt=(m,T)=>m.map(j=>j.key).join("|")===T.map(j=>j.key).join("|");Dt(i,a.pageRows.peek())||(a.pageRows.value=i),Dt(g,a.blockRows.peek())||(a.blockRows.value=g),Dt(X,a.compRows.peek())||(a.compRows.value=X),Dt(wo,a.thisPage.peek())||(a.thisPage.value=wo)},da=async()=>{const e=await k.pages.list().catch(()=>null);!c()||!Array.isArray(e)||($=e,z(),V())},ee=()=>{const e=I(a.active.peek()),t=e?_a(e):"/pages/new",o=window.location.hash.replace(/^#/,"");N.address=o.split("?")[0]===t?o:t,o.split("?")[0]!==t?history.replaceState({pe:1},"","#"+t):history.state?.pe||history.replaceState({pe:1},"",window.location.hash)},Rs=async e=>{if(e.loading)return e.loading;e.loading=(async()=>{let t=null;if(e.isEdit){if(t=await k.pages.get(e.id).catch(()=>null),!c())return!1;if(!t)return E.toast(`Page ${e.id} not found.`,{type:"error"}),!1}e.page=t;const o=ea(t,e.id);e.state=o,e.base=Y(o),e.savedAdminTags=JSON.stringify(o.adminTags),e.loaded=!0;let s=null;try{s=S.get(je(e.id))}catch{}const n=vn(s),r=So({kind:"page",id:e.id});if(n){W(je(e.id));const i=Y(n.state)!==e.base,g=mn(n,e.base),O={...o,...n.state,adminTags:o.adminTags};if(i&&r&&!g)e.state=O,E.toast(`Your unsaved changes to ${e.id||"the new page"} are back.`,{type:"info"});else if(i){const X=n.at?` (${Ie(n.at)})`:"",Ee=g?" The saved page has changed since - restoring puts your version back over it (the saved one stays in History).":"";await E.confirm(`You left unsaved changes to ${e.id||"a new page"}${X}. Restore them?${Ee}`)&&c()&&(e.state=O)}}return!0})();try{return await e.loading}finally{e.loading=null}},pa=e=>(_.has(e)||_.set(e,{kind:"page",id:e,isEdit:!!e,loaded:!1,page:null,state:null,base:"",savedAdminTags:"[]",at:0}),_.get(e)),ie=async e=>{const t=Q({kind:"page",id:e});({ids:x}=Ro(x,t)),a.active.value=t,a.blockTab.value="",co();const o=pa(e);if(V(),!o.loaded){a.previewStatus.value=`Opening ${e||"a new page"}\u2026`;const s=await Rs(o);if(!c()||!x.includes(t)||_.get(e)!==o)return;if(!s){ua(t);return}if(a.active.peek()!==t){V(),z();return}}oa(o),ee(),V(),z(),U(),G.refresh(),F.focus()};w=async(e,t)=>{if(N.address=t,e.kind==="page"){ie(e.id);return}if(!e.id){(e.kind==="block"?At:tt)({project:jt(t)||""});return}const o=e.kind==="block"?pe:se;if(o.has(e.id)||await(e.kind==="block"?Io:Fo)(),!!c()){if(!o.has(e.id)){E.toast(`There is no ${e.kind} called "${e.id}".`,{type:"warning"}),ee(),z();return}e.kind==="block"?Le(e.id):et(e.id)}};const ua=e=>{const t=a.active.peek(),o=Ln(x,e,t);x=o.ids,Ae.delete(e);const s=I(e),n=o.active!==t?I(o.active):null;if(s.kind==="page"){if(_.delete(s.id),f?.id===s.id){const r=[..._.values()].filter(i=>i.loaded&&i.id!==s.id).sort((i,g)=>g.at-i.at)[0];r?oa(r):Ss()}}else s.kind==="component"?(B.delete(s.id),a.compTab.peek()===s.id&&co()):(A.delete(s.id),a.blockTab.peek()===s.id&&(a.blockTab.value="",a.blockReady.value=!1));if(a.active.value=o.active,n?.kind==="page"){ie(n.id);return}if(n?.kind==="block"){Le(n.id);return}if(n?.kind==="component"){et(n.id);return}ee(),_e(),be(),ve(),V(),z(),U()},Je=e=>{const t=I(e);if(!t||!x.includes(e))return;let o="";try{if(t.kind==="page"){const s=_.get(t.id);s&&Ht(s)&&(S.set(je(s.id),ja(s===f?K():s.state,s.base)),To({kind:"page",id:s.id}),o=ra(s))}else if(t.kind==="component"){const s=B.get(t.id);s&&me(s)&&(S.set(qe(t.id),aa(s,new Date().toISOString())),To({kind:"component",id:t.id}),o=t.id)}else{const s=A.get(t.id);s&&re(s)&&(S.set(Fe(t.id),{form:{...s.form},base:s.base,at:new Date().toISOString()}),To({kind:"block",id:t.id}),o=t.id)}}catch{}ua(e),o&&E.toast(`Changes to "${o}" kept as a draft - back when you open it again.`,{type:"info"})},Tt=e=>{const t=I(e);t&&(t.kind==="page"?ie(t.id):t.kind==="component"?et(t.id):Le(t.id))},fa=new Set,As=async e=>{const t=[];for(const o of e){const s=A.get(o);if(!s)continue;const n={...s.form};try{const r=Ka(n,s.block?.meta);await k.blocks.put(o,r),s.block={...s.block,meta:r.meta},s.saved=n,s.base=Po(n),mt.set(o,new Date().toISOString()),c()&&W(Fe(o)),t.push(o)}catch(r){E.toast(`Block "${o}" not saved: ${r.message||"Unknown error"}`,{type:"error"})}}return t.forEach(o=>fa.add(o)),t.length&&(be(),lt("#/blocks")),t},$s=async e=>{const t=e===f?K():e.state,o=Kt(t,e,Ut(t)),s=e.id;try{if(e.isEdit){const n={frontmatter:o,body:t.body};t.urlPath!==e.id&&(n.newUrlPath=t.urlPath),await k.pages.update(e.id,n)}else await k.pages.create({urlPath:t.urlPath,frontmatter:o,body:t.body})}catch(n){return E.toast(`${e.id||"The new page"} not saved: ${n.message||"Unknown error"}`,{type:"error"}),!1}if(!c())return!0;if(e.savedAdminTags=JSON.stringify(t.adminTags),e.base=Y(t),e===f?a.base.value=e.base:e.state=t,W(je(s)),!e.isEdit||t.urlPath!==e.id){_.delete(s),e.id=t.urlPath,e.isEdit=!0,_.set(e.id,e);const n=Q({kind:"page",id:s}),r=Q({kind:"page",id:e.id});Ae.delete(n),{ids:x}=Hn(x,n,r,a.active.peek()),a.active.peek()===n&&(a.active.value=r),e===f&&(a.curId.value=e.id,ee())}return!0},Ds=async e=>{const t=[];for(const o of e){const s=B.get(o);if(!s||s.readOnly)continue;if(Ot===s?(clearTimeout(Pt),Ot=null,ve(),Ze(s)):s.state||Ze(s),s.compiling&&await s.compiling,!c())return t;if(s.state!=="ok"){E.toast(`Component "${o}" not saved: ${s.state==="error"?"it does not compile - fix the errors first.":"it has not compiled yet - try again in a moment."}`,{type:"warning"});continue}const n={...s.form},r={...s.meta||{}};delete r.bundled,n.project?r.project=n.project:delete r.project;try{await k.components.put(o,{source:n.source,bundled:n.bundled,meta:r}),s.meta=r,s.saved=n,s.base=$o({name:o,...n});const i=Ve.find(g=>g.name===o);i&&(i.bundled=n.bundled,i.meta=r),c()&&W(qe(o)),t.push(o)}catch(i){E.toast(`Component "${o}" not saved: ${i.message||"Unknown error"}`,{type:"error"})}}return t.length&&(ve(),lt("#/components")),t};let eo=!1;const to=async(e=!1)=>{if(a.saving.peek()||eo)return;eo=!0;let t,o,s=[];try{if(await ba(),!c())return;const r=I(a.active.peek());if(e)t=Pe(),o=fe(),s=Ke();else if(r?.kind==="block")t=[],o=A.has(r.id)&&re(A.get(r.id))?[r.id]:[];else if(r?.kind==="component")t=[],o=[],s=B.has(r.id)&&me(B.get(r.id))?[r.id]:[];else if(r?.kind==="page"&&f)o=fe().filter(kt),t=!f.isEdit||Vt()||!o.length?[f]:[];else return;if(!t.length&&!o.length&&!s.length){E.toast("Nothing to save.",{type:"info"});return}for(const g of t){const O=Mo(g===f?K():g.state);if(O.length){E.toast(`${g.id||"New page"}: ${O[0].text}`,{type:"warning"}),g!==f&&await ie(g.id),a.dockOn.value=!0,a.dockTab.value=O[0].field==="dconfig"?"dconfig":"details",dt("dock",!0);return}}const i=On(o,Object.fromEntries(o.map(g=>[g,Ua(ze.get(g),f?.id||"")])),fa);if(i&&(!await E.confirm(i)||!c()))return}finally{eo=!1}const n=t.length>0&&vt();a.saving.value=!0;try{let r=0;for(const O of t)await $s(O)&&r++;if(!c())return;if(n){const O=p.css.peek();await k.settings.saveCustomCss(O),Z.value=O,W(pt)}const i=await As(o);if(!c())return;const g=await Ds(s);if((r||i.length||g.length)&&E.toast(Rn({pages:r,css:n,blocks:i,components:g}),{type:"success"}),_e(),!c())return;V(),z(),r&&(lt("#/pages"),Mt(),da())}catch(r){E.toast(`Save failed: ${r.message||"Unknown error"}`,{type:"error"})}finally{a.saving.value=!1}};let oe=null,Ge=null;const $e=new Map,De=new Map,ma=()=>{const e=A.get(a.blockTab.peek());!e||!oe||(a.blockReady.value=!1,oe.setValue(e.form.content),Ge.setValue(e.form.css),a.blockProject.value=e.form.project,Lo(D("#block-project"),e.form.project),a.blockBundled.value=e.form.bundled,a.blockWhere.value=hn({usage:ze.get(e.form.name)})||"Where it is used could not be read.",a.blockNote.value="",a.blockReady.value=!0,no())},Ls=e=>{const t=A.get(e);if(!t)return{};const o=t.form.content;return Object.fromEntries(Wa(Va(o),xo(o),t.samples||{}).map(s=>[s.name,s.value]))};l(M.effect(()=>{const e=a.blockProject.value,t=a.blockBundled.value,o=A.get(a.blockTab.peek());!o||!a.blockReady.peek()||o.form.project===e&&o.form.bundled===t||(o.form.project=e,o.form.bundled=t,be())}));const oo=async(e,{quiet:t=!1}={})=>{for(;$e.has(e);)if(await $e.get(e),A.has(e)||t)return A.get(e)||null;if(A.has(e))return A.get(e);const o=(async()=>{try{const s=await k.blocks.get(e);if(!c()||!t&&!x.includes(Q({kind:"block",id:e})))return null;const n=Ha({...s,name:e}),r={block:s,saved:n,base:Po(n),form:{...n},samples:{}};let i=null;try{i=S.get(Fe(e))}catch{}const g=En(i,r.base);if(t&&g?.stale)return null;if(So({kind:"block",id:e}),g){const O=g.at?` (${Ie(g.at)})`:"",X=!g.stale||await E.confirm(`You left unsaved changes to "${e}"${O}, but the block has been saved since. Restore your version anyway?`);if(!c())return null;X&&(r.form={...r.form,...g.form,name:e},g.stale||E.toast(`Your unsaved changes to "${e}" are back.`,{type:"info"}))}return i&&W(Fe(e)),A.set(e,r),be(),r}catch(s){return c()&&a.blockTab.peek()===e&&(a.blockNote.value=`Could not open "${e}": ${s.message||"unknown error"}`),null}finally{$e.delete(e)}})();return $e.set(e,o),o},ba=async()=>{for(;$e.size||De.size;)await Promise.all([...$e.values(),...De.values()])},Le=async e=>{if(!e||!pe.has(e))return;const t=Q({kind:"block",id:e});({ids:x}=Ro(x,t)),a.active.value=t,co(),a.cssMode.peek()&&(a.cssMode.value=!1),a.viewMode.peek()==="preview"&&(a.viewMode.value="split"),a.blockTab.peek()!==e&&(a.blockReady.value=!1),a.blockTab.value=e,a.blockUses.value=f&&kt(e)?An(Ua(ze.get(e),f.id||"")):Cn(ze.get(e)),ee(),V(),z();let o=A.get(e);o||(a.blockReady.value=!1,a.blockNote.value=`Opening ${e}\u2026`,o=await oo(e)),!(!o||!c()||a.blockTab.peek()!==e)&&(ma(),V(),z(),_t=e,U(),(a.blockPart.peek()==="css"?Ge:oe)?.focus())};let ao=null;l(()=>clearTimeout(ao));const va=(e,t)=>{const o=A.get(a.blockTab.peek());!o||!a.blockReady.peek()||(o.form[e]=t,clearTimeout(ao),ao=setTimeout(()=>{be(),e==="content"&&no()},120),U())},ke=D("#pe-block-samples"),ga=e=>{ke.replaceChildren();const t=document.createElement("p");t.className="pe-dock-none",t.textContent=e,ke.appendChild(t)};let Xe=[],so=null;function no(){const e=A.get(a.blockTab.peek());if(!e){Xe=[],so=null,ga("Open a block to see its placeholders.");return}const t=Wa(Va(e.form.content),xo(e.form.content),e.samples);if(!(so===e&&t.map(o=>o.name).join("|")===Xe.join("|"))){if(Xe=t.map(o=>o.name),so=e,!t.length){ga("No {{placeholders}} in the template yet.");return}ke.replaceChildren();for(const o of t){const s=document.createElement("div");s.className="pe-comp-prop",s.tabIndex=0,s.dataset.sample=o.name;const n=document.createElement("label");n.className="pe-comp-prop-label",n.textContent=o.name,n.title=o.raw?`{{{${o.name}}}} - raw markup`:`{{${o.name}}}`;const r=document.createElement("input");r.type="text",r.className="form-input form-input--sm pe-comp-prop-input",r.value=o.value,r.setAttribute("aria-label",`Sample value for ${o.name}`),r.addEventListener("input",()=>{e.samples[o.name]=r.value,U()});const i=document.createElement("button");i.type="button",i.className="pe-kebab",i.setAttribute("aria-label",`Actions for ${o.name}`),i.title="Actions",i.textContent="\u22EE",s.append(n,r,i),ke.appendChild(s)}}}const ro=(e,t)=>xo(e.form.content).has(t)?`{{{${t}}}}`:`{{${t}}}`,Ns=e=>{const t=A.get(a.blockTab.peek());return!t||!Xe.includes(e)?[]:[{header:ro(t,e)},{label:"Insert into the template",icon:"corner-down-right",action:()=>{const o=oe?.textarea;o&&(a.blockPart.value="html",o.focus(),o.setRangeText(ro(t,e),o.selectionStart,o.selectionEnd,"end"),o.dispatchEvent(new Event("input",{bubbles:!0})))}},{label:"Copy the placeholder",icon:"copy",action:()=>Ne(ro(t,e),"Placeholder")},{type:"divider"},{label:"Reset the sample",icon:"rotate-ccw",action:()=>{delete t.samples[e],Xe=[],no(),U()}}]};{const e=E.contextMenu(ke,{cursor:"glyph",match:".pe-comp-prop",inherit:!1,density:"compact",items:t=>Ns(t.dataset.sample)});l(()=>e?.destroy?.()),ke.addEventListener("click",t=>{const o=t.target.closest(".pe-kebab");o&&Me(o)}),ke.addEventListener("keydown",t=>{const o=t.target.closest(".pe-comp-prop");o&&t.key==="F10"&&t.shiftKey&&(t.preventDefault(),Me(o))})}const ce={},ye=D("#component-preview"),St=e=>{try{ye?.contentWindow?.postMessage(e,"*")}catch{}};let lo=ye.contentDocument?.readyState==="complete"&&/component-preview/.test(ye.contentDocument?.URL||""),Ct=!1,Qe=null;const ka=()=>{lo=!0,Qe&&c()&&(St({type:"mount",payload:Qe}),Ct=!0),Qe=null};ye.addEventListener("load",ka),l(()=>ye.removeEventListener("load",ka));const Bs=e=>{if(!lo){Qe=e;return}if(!Ct){Ct=!0,St({type:"mount",payload:e});return}Qe=e,lo=!1,Ct=!1,ye.src=ye.src};let xt="";const io=(e=!1)=>{const t=B.get(a.compTab.peek());if(!(!t||t.state!=="ok"||!t.js)){if(a.viewMode.peek()==="write"){t.previewStale=!0;return}!e&&xt===t.name&&!t.previewStale||(t.previewStale=!1,xt=t.name,Bs({compiledJs:t.js,tagName:`dm-${t.name}`,props:t.previewProps}))}},Ze=e=>{const t=++e.seq;return e.state="compiling",a.compTab.peek()===e.name&&(a.compState.value="compiling"),e.compiling=(async()=>{let o;try{o=await k.components.compile(e.name,e.form.source)}catch(s){o={errors:[{type:"request",message:s.message||"Compile request failed."}]}}if(!(t!==e.seq||!c())){if(o.errors?.length?(e.state="error",e.errors=o.errors.map(s=>`[${s.type}] ${s.message}`).join(`
120
- `),e.js=null):(e.state="ok",e.errors="",e.js=o.js),a.compTab.peek()!==e.name){e.previewStale=!0;return}a.compState.value=e.state,a.compErrors.value=e.errors,e.state==="ok"&&(mo(),io(!0))}})().finally(()=>{e.compiling&&t===e.seq&&(e.compiling=null)}),e.compiling};let Pt=null,Ot=null;l(()=>clearTimeout(Pt));const ya=()=>{const e=B.get(a.compTab.peek());if(!e||!ce.template)return;a.compReady.value=!1;const t=it(e.form.source);for(const o of It)ce[o].setValue(t[o]),ce[o].textarea.readOnly=e.readOnly;a.compReadOnly.value=e.readOnly,a.compProject.value=e.form.project,Lo(D("#comp-project"),e.form.project),a.compBundled.value=e.form.bundled,a.compState.value=e.state,a.compErrors.value=e.errors,a.compNote.value="",a.compReady.value=!0,mo(),e.state==="ok"?io():e.state||Ze(e)},co=()=>{a.compTab.peek()&&(a.compTab.value="",a.compReady.value=!1)},ha={page:"details",block:"details",component:"details"};let Rt="";l(M.effect(()=>{const e=_o.value;e!==Rt&&(Rt&&(ha[Rt]=a.dockTab.peek()),Rt=e,e&&(a.dockTab.value=ha[e]||"details",e==="page"&&a.dockTab.peek()==="history"&&a.dockOn.peek()&&f?.isEdit&&!Ye&&le()))}));const po=async(e,{quiet:t=!1}={})=>{if(De.has(e))return De.get(e);if(B.has(e))return B.get(e);const o=(async()=>{try{const s=await k.components.get(e);if(!c()||!x.includes(Q({kind:"component",id:e})))return null;const n=s.readOnly===!0||s.origin==="plugin",r={project:s.meta?.project||"",bundled:!!s.bundled,source:Ya(it(s.source))},i={name:e,comp:s,saved:r,base:$o({name:e,...r}),form:{...r},meta:s.meta||{},readOnly:n,state:"",errors:"",js:null,seq:0,compiling:null,previewProps:{},previewStale:!1};let g=null;try{g=S.get(qe(e))}catch{}const O=Jn(g,i.base);if(t&&(!O.ok||O.stale||n))return null;const X=So({kind:"component",id:e});if(t&&!X)return null;if(O.ok&&!n){const Ee=g.at?` (${Ie(g.at)})`:"",ho=X&&!O.stale||await E.confirm(O.stale?`You left unsaved changes to the component "${e}"${Ee}, but it has been saved since. Restore your version anyway?`:`You left unsaved changes to the component "${e}"${Ee}. Restore them?`);if(!c())return null;ho&&(i.form={project:String(g.project??""),bundled:g.bundled===!0,source:String(g.source)},X&&!O.stale&&E.toast(`Your unsaved changes to the component "${e}" are back.`,{type:"info"}))}return g&&W(qe(e)),B.set(e,i),ve(),i}catch(s){return c()&&a.compTab.peek()===e&&(a.compNote.value=`Could not open <dm-${e}>: ${s.message||"unknown error"}`),null}finally{De.delete(e)}})();return De.set(e,o),o},et=async e=>{if(!e||!se.has(e))return;const t=Q({kind:"component",id:e});({ids:x}=Ro(x,t)),a.active.value=t,a.blockTab.value="",a.cssMode.peek()&&(a.cssMode.value=!1),a.viewMode.peek()==="preview"&&(a.viewMode.value="split"),a.compTab.value=e,ee(),V(),z();let o=B.get(e);o||(a.compReady.value=!1,a.compReadOnly.value=!1,a.compState.value="",a.compNote.value=`Opening <dm-${e}>\u2026`,o=await po(e)),!(!o||!c()||a.compTab.peek()!==e)&&(ya(),V(),z(),ce[a.compPart.peek()]?.focus())},js=(e,t)=>{const o=B.get(a.compTab.peek());if(!o||!a.compReady.peek()||o.readOnly)return;const s=it(o.form.source);s[e]=t,o.form.source=Ya(s),clearTimeout(Pt),Ot=o,Pt=setTimeout(()=>{Ot=null,ve(),Ze(o)},300)};l(M.effect(()=>{const e=a.compProject.value,t=a.compBundled.value,o=B.get(a.compTab.peek());!o||!a.compReady.peek()||o.readOnly||o.form.project===e&&o.form.bundled===t||(o.form.project=e,o.form.bundled=t,ve())}));const he=D("#pe-comp-props");let uo=[];const fo=e=>{he.replaceChildren();const t=document.createElement("p");t.className="pe-dock-none",t.textContent=e,he.appendChild(t)};function mo(){const e=B.get(a.compTab.peek());if(!e){uo=[],fo("Open a component to try its props.");return}const{rows:t,error:o}=Ja(it(e.form.source).props,e.previewProps);if(uo=t,o){fo("The <props> block is not valid JSON - fix it to see the props here.");return}if(!t.length){e.previewProps={},fo("No props declared. Add them in the <props> block.");return}e.previewProps=Object.fromEntries(t.map(s=>[s.name,s.value])),he.replaceChildren();for(const s of t){const n=document.createElement("div");n.className="pe-comp-prop",n.tabIndex=0,n.dataset.prop=s.name;const r=document.createElement("label");r.className="pe-comp-prop-label",r.textContent=s.label,r.title=`${s.name} (${s.type})`;const i=s.type==="array"||s.type==="object"?document.createElement("textarea"):document.createElement("input");s.type==="boolean"?(i.type="checkbox",i.className="form-check",i.checked=Ga(s.type,s.value)):(i.tagName==="INPUT"?i.type=s.type==="number"?"number":"text":i.rows=2,i.className="form-input form-input--sm pe-comp-prop-input",i.value=Ga(s.type,s.value)),i.setAttribute("aria-label",s.label);const g=()=>{e.previewProps[s.name]=Xn(s.type,s.type==="boolean"?i.checked:i.value),xt===e.name&&St({type:"update",payload:{props:e.previewProps}})};i.addEventListener("input",g),i.addEventListener("change",g);const O=document.createElement("button");O.type="button",O.className="pe-kebab",O.setAttribute("aria-label",`Actions for ${s.label}`),O.title="Actions",O.textContent="\u22EE",n.append(r,i,O),he.appendChild(n)}}const Ne=async(e,t)=>{try{await navigator.clipboard.writeText(e),E.toast(`${t} copied.`,{type:"success"})}catch{E.toast(e,{type:"info",duration:8e3})}},Is=e=>{const t=B.get(a.compTab.peek()),o=uo.find(r=>r.name===e);if(!t||!o)return[];const s=Yn(o.name,o.type,t.previewProps[o.name]),n=`dm-${t.name}`;return[{header:o.label},{label:"Reset to its default",icon:"rotate-ccw",action:()=>{delete t.previewProps[o.name];const r=Ja(it(t.form.source).props,{}).rows.find(i=>i.name===o.name);t.previewProps[o.name]=r?r.value:null,mo(),xt===t.name&&St({type:"update",payload:{props:t.previewProps}})}},{label:"Copy as an attribute",icon:"copy",disabled:!s,action:()=>Ne(s,"Attribute")},{label:"Copy the tag with this value",icon:"code",action:()=>Ne(`<${n}${s?` ${s}`:""}></${n}>`,"Tag")},{label:"Copy the prop name",icon:"copy",action:()=>Ne(o.name,"Name")},{type:"divider"},{label:"Edit in <props>",icon:"edit",action:()=>{a.compPart.value="props";const r=ce.props?.textarea;if(!r)return;const i=r.value.indexOf(`"${o.name}"`);r.focus(),i>=0&&r.setSelectionRange(i,i+o.name.length+2)}}]};{const e=E.contextMenu(he,{cursor:"glyph",match:".pe-comp-prop",inherit:!1,density:"compact",items:t=>Is(t.dataset.prop)});l(()=>e?.destroy?.()),he.addEventListener("click",t=>{const o=t.target.closest(".pe-kebab");o&&Me(o)}),he.addEventListener("keydown",t=>{const o=t.target.closest(".pe-comp-prop");o&&t.key==="F10"&&t.shiftKey&&(t.preventDefault(),Me(o))})}let Be=!1;const wa=()=>jt()||"",At=async({project:e=wa(),seed:t="",content:o=sr,css:s=""}={})=>{if(!Be){Be=!0;try{const r=await es({kind:"block",projects:J,taken:[...pe],project:e,name:t,check:yn,register:l,create:async(i,g)=>{await k.blocks.put(i,Ka({name:i,project:g,bundled:!1,content:o,css:s}))}});if(!c())return;if(!r){ee();return}if(await Io(),!c())return;lt("#/blocks"),E.toast(`Created the block "${r}".`,{type:"success"}),Le(r)}finally{Be=!1}}},tt=async({name:e="",source:t=Wn,project:o=wa()}={})=>{if(!Be){Be=!0;try{const s=await es({kind:"component",projects:J,taken:[...se],project:o,name:e,register:l,check:(n,r)=>Gn(n,{isNew:!0,taken:r}),create:async(n,r)=>{await k.components.put(n,{source:t,bundled:!1,meta:r?{project:r}:{}})}});if(!c())return;if(!s){ee();return}if(await Fo(),!c())return;lt("#/components"),E.toast(`Created the component "${s}".`,{type:"success"}),et(s)}finally{Be=!1}}},Ea=y.querySelector(".dm-plugin-body")||y,Fs=M.computed(()=>{a.blockRev.value,a.compRev.value,a.docRev.value,Y(K()),a.base.value;const e=I(a.active.value),t=[...Pe().map(s=>Q({kind:"page",id:s.id})),...fe().map(s=>Q({kind:"block",id:s})),...Ke().map(s=>Q({kind:"component",id:s}))];let o=[];return e?.kind==="block"||e?.kind==="component"?o=[a.active.value]:e?.kind==="page"&&f&&(o=[a.active.value,...fe().filter(kt).map(s=>Q({kind:"block",id:s}))]),Kn(t,o)}),qs={...p,...a,dirty:ds,pageDirty:is,activeDirty:ps,cssUnsaved:cs,hasPage:us,activeKind:Ko,canView:Uo,viewHref:fs,problem:ms,statusText:bs,saveAll:Fs,dockKind:_o,blockTagText:vs,compStateText:gs,compTagText:ks,anySelected:ys,allSelected:hs,someSelected:ws},ot=(e,t)=>dt(e,t),bo={save:()=>to(!1),saveAllOpen:()=>to(!0),discard:async()=>{if(await ba(),!c())return;const e=I(a.active.peek());if(e?.kind==="block"){const t=A.get(e.id);if(!t||!re(t)||!await E.confirm(`Discard the changes to "${e.id}" you have not saved?`))return;t.form={...t.saved},W(Fe(e.id)),ma(),be()}else if(e?.kind==="component"){const t=B.get(e.id);if(!t||!me(t)||!await E.confirm(`Discard the changes to the component "${e.id}" you have not saved?`))return;t.form={...t.saved},t.state="",W(qe(e.id)),ya(),ve()}else{if(!f||e?.kind==="page"&&e.id!==f.id||!(Vt()||vt())||!await E.confirm("Discard the changes to this page you have not saved?"))return;We(JSON.parse(a.base.peek())),p.cssOpened.peek()&&(p.css.value=Z.peek()),W(je(f.id)),W(pt),_e()}V(),z(),U()},newPage:()=>ie(""),newBlock:()=>At(),newComponent:()=>tt(),toggleSection:e=>{const t={here:"secHere",pages:"secPages",blocks:"secBlocks",components:"secComps"}[e];t&&(a[t].value=!a[t].peek(),ot(t,a[t].peek()))},toggleExplorer:()=>{a.explorerOn.value=!a.explorerOn.peek(),ot("explorer",a.explorerOn.peek())},toggleDock:()=>{a.dockOn.value=!a.dockOn.peek(),ot("dock",a.dockOn.peek()),a.dockOn.peek()&&a.dockTab.peek()==="history"&&!Ye&&le()},showDock:e=>{a.dockTab.value=e,e==="history"&&f?.isEdit&&!Ye&&le()},setBlockPart:e=>{a.blockPart.value=e==="css"?"css":"html",(e==="css"?Ge:oe)?.focus()},formatBlock:()=>{const e=A.get(a.blockTab.peek());!e||!oe||(e.form.content=Mn(e.form.content),oe.setValue(e.form.content),be(),U())},copyShortcode:async()=>{const e=a.blockTab.peek();e&&await Ne(Tn(e),"Shortcode")},setCompPart:e=>{a.compPart.value=It.includes(e)?e:"template",ce[a.compPart.peek()]?.focus()},copyComponent:()=>{const e=B.get(a.compTab.peek());e&&tt({name:Co(e.name,[...se]),source:e.form.source})},setMode:e=>{a.cssMode.peek()&&(a.cssMode.value=!1),a.viewMode.value=Oo(e),ot("viewMode",a.viewMode.peek()),e!=="write"&&(U(!0),io())},toggleFullscreen:()=>{a.fullscreen.value=!a.fullscreen.peek(),ot("fullscreen",a.fullscreen.peek())},toggleCss:()=>{a.blockTab.peek()||a.compTab.peek()||(a.cssMode.peek()?(a.cssMode.value=!1,a.viewMode.value=Oo(Se().viewMode),G.refresh()):(Zo(),a.cssMode.value=!0,a.viewMode.value="split"))},formatMarkdown:()=>{const e=F.selectionStart,t=lr(bt());G.unfoldAll(),xe=!0,F.value=t,xe=!1,F.selectionStart=F.selectionEnd=Math.min(e,t.length),F.dispatchEvent(new Event("input")),He.paint(),G.refresh(),E.toast("Markdown formatted.",{type:"success"})},toggleFold:()=>{a.folded.peek()?G.unfoldAll():G.foldAll(),a.folded.value=!a.folded.peek(),He.paint()},setWidth:e=>{a.liveWidth.value=e},refreshPreview:()=>{if(a.compTab.peek()){const e=B.get(a.compTab.peek());e&&Ze(e)}else U(!0)},share:async()=>{if(!f?.isEdit){E.toast("Save the page first - a share link needs a page to point at.",{type:"warning"});return}await or(f.id)},toggleAll:e=>{const t=!!e?.target?.checked;a.versions.peek().forEach(o=>{o.checked.value=t})},deleteSelected:async()=>{const e=a.versions.peek().filter(o=>o.checked.peek()).map(o=>o.filename),t=e.length;if(!(!t||!await E.confirm(`Delete ${t} version${t>1?"s":""}? This cannot be undone.`)))try{await k.versions.bulkDelete(f.id,e),E.toast(`${t} version${t>1?"s":""} deleted.`,{type:"success"}),await le()}catch(o){E.toast("Delete failed: "+o.message,{type:"error"})}},saveNamed:async()=>{if(!f?.isEdit)return;const e=f;await ar(e.id)&&c()&&f===e&&await le()},closeCompare:()=>{a.compare.value=null,Et=null},restoreCompared:()=>na(Et)};Ue.querySelectorAll("[data-tip]").forEach(e=>E.tooltip(e,{content:e.getAttribute("data-tip"),position:"top"}));const zs=M.applyBindings(qs,Ea,{methods:bo});l(()=>zs.dispose()),oe=Do(D('.pe-block-edit[data-part="html"]'),{ariaLabel:"Block template HTML",language:"html",onChange:e=>va("content",e)}),Ge=Do(D('.pe-block-edit[data-part="css"]'),{ariaLabel:"Block CSS",language:"css",placeholder:`/* Scoped to this block - e.g.
119
+ </span>`);Ue.prepend(Cs),Ue.before(N(".pe-block-bar")),Ue.before(N(".pe-comp-bar"));const Xo=e=>(Array.isArray(e)?e:[]).map(t=>({value:t,label:t})),Qo=N("#field-tags");let yt=null;yt=Qo?E.pillbox(Qo,{data:Xo(ut),value:[],creatable:!0,searchable:!0,placeholder:"Add tag\u2026",onChange:()=>{yt&&(p.tags.value=[...yt.getValue()])}}):null;const Zo=N("#field-admin-tags");let wt=null;wt=Zo?E.pillbox(Zo,{data:Xo(Ce),value:[],creatable:!0,searchable:!0,placeholder:"Add admin tag\u2026",onChange:()=>{wt&&(p.adminTags.value=[...wt.getValue()])}}):null;const ea=()=>{o.bodyRev.value=o.bodyRev.peek()+1},be=()=>{o.blockRev.value=o.blockRev.peek()+1},ve=()=>{o.compRev.value=o.compRev.peek()+1},_e=()=>{o.docRev.value=o.docRev.peek()+1},ta=()=>{p.cssOpened.peek()||(p.css.peek()||(p.css.value=Z.peek()),p.cssOpened.value=!0,Js(ue))},We=e=>{for(const t of vr)p[t].value=e[t]==null?"":String(e[t]);for(const t of gr)p[t].value=!!e[t];p.tags.value=Array.isArray(e.tags)?e.tags:[],yt?.setValue(p.tags.peek()),Array.isArray(e.adminTags)&&(p.adminTags.value=e.adminTags),wt?.setValue(p.adminTags.peek()),Lo(N("#field-visibility"),p.visibility.peek()),G.unfoldAll(),o.folded.value=!1,xe=!0,F.value=e.body==null?"":String(e.body),xe=!1,G.refresh(),zo(),ea()},oa=(e,t)=>({urlPath:t||"",title:e?.title||"",description:e?.description||"",layout:Io(e?.layout||"default"),status:e?.status||"draft",sortOrder:String(e?.sortOrder??99),showInNav:!!e?.showInNav,sidebar:!!e?.sidebar,breadcrumbs:e?e.breadcrumbs!==!1:!0,bundled:!!e?.bundled,tags:Array.isArray(e?.tags)?e.tags:[],adminTags:Array.isArray(e?.adminTags)?e.adminTags:[],category:e?.category||"",visibility:typeof e?.visibility=="string"&&e.visibility?e.visibility:"public",project:hn({isEdit:!!t,page:e,projects:J,fromHash:t?"":jt()||"",urlPath:t||""}),theme:e?.theme||"",seoTitle:e?.seo?.title||"",seoDesc:e?.seo?.description||"",seoSchema:rn(e?.seo?.schema),dconfig:e?.dconfig?JSON.stringify(e.dconfig,null,2):"",body:e?.content||"",css:null}),aa=()=>{u&&(u.state=K(),u.at=Date.now())},sa=e=>{u!==e&&(aa(),u=e,We(e.state),o.base.value=e.base,o.curId.value=e.id,e.at=Date.now(),St(),_e())},xs=()=>{aa(),u=null;const e=ls();We(e),o.base.value=Y(e),o.curId.value=null,St(),_e()};We(ls()),o.base.value=Y(K());const W=e=>{try{S.remove?S.remove(e):S.set(e,null)}catch{}},na=(e,t)=>({name:e.name,...e.form,base:e.base,at:t});y=()=>{try{for(const t of Pe())S.set(je(t.id),Fa(t===u?Ko():t.state,t.base));vt()&&S.set(pt,p.css.peek());const e=new Date().toISOString();for(const t of me()){const a=$.get(t);S.set(Fe(t),{form:{...a.form},base:a.base,at:e})}for(const t of Ke())S.set(qe(t),na(B.get(t),e))}catch{}};const ra=e=>{b()&&(y(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",ra),l(()=>window.removeEventListener("beforeunload",ra));let Ye=!1,Et=null;const St=()=>{Ye=!1,Et=null,o.versions.value=[],o.compare.value=null,o.histError.value="",o.dockOn.peek()&&o.dockTab.peek()==="history"&&(u?.isEdit?le():o.dockTab.value="details")},le=async()=>{const e=u;if(e?.isEdit){Ye=!0,o.histLoading.value=!0,o.histError.value="";try{const t=(await k.versions.list(e.id)).versions||[];if(!c()||u!==e)return;o.versions.value=t.map(a=>({...a,when:Ie(a.createdAt),author:a.author||"",label:a.label||"",who:[a.author,a.label].filter(Boolean).join(" - "),checked:M.observable(!1)}))}catch(t){u===e&&(o.histError.value="Failed to load versions: "+t.message)}finally{u===e&&(o.histLoading.value=!1)}}},Gt=e=>o.versions.peek().find(t=>t.filename===e),Xt=async e=>{const t=u;try{const a=await k.versions.get(t.id,e.filename);if(!c()||u!==t)return;Et=e.filename,o.compare.value={label:es(e),old:a.content,current:bt()}}catch(a){E.toast("Failed to load version: "+a.message,{type:"error"})}},Ps=async()=>{const e=u;if(!e?.isEdit)return;const t=await k.pages.get(e.id).catch(()=>null);if(!c()||u!==e||!t)return;e.page=t;const a=oa(t,e.id);e.base=Y(a),e.savedAdminTags=JSON.stringify(a.adminTags),We(a),o.base.value=e.base,St(),o.dockOn.peek()&&o.dockTab.peek()==="history"&&le(),U()},la=async e=>{if(!(!e||!await E.confirm("Restore this version? The current page content will be overwritten (a pre-restore snapshot will be saved automatically).")))try{await k.versions.restore(u.id,e),E.toast("Page restored.",{type:"success"}),await Ps()}catch(t){E.toast("Restore failed: "+t.message,{type:"error"})}},Os=async e=>{if(await E.confirm("Delete this version? This cannot be undone."))try{await k.versions.delete(u.id,e.filename),E.toast("Version deleted.",{type:"success"}),await le()}catch(t){E.toast("Failed to delete: "+t.message,{type:"error"})}},$e=new Map,Rs=e=>{if(!$e.has(e)){const t=I(e);$e.set(e,{id:e,kind:t.kind,kindText:t.kind==="page"?"Page":t.kind==="component"?"Component":"Block",label:M.observable(""),title:M.observable(""),active:M.observable(!1),dirty:M.observable(!1)})}return $e.get(e)},ia=e=>{const t=e===u?p.title.peek():e?.loaded?e.state.title:D.find(a=>a.urlPath===e?.id)?.title;return String(t||"").trim()||e?.id||"New page"};let ca=!0,da="";const $s=()=>{if(ca)return;const e={ids:x,active:o.active.peek()},t=JSON.stringify(e);if(t!==da){da=t;try{S.set(ns,e)}catch{}}},V=()=>{const e=x.map(Rs);for(const t of e){const a=I(t.id);let s,r;if(a.kind==="page"){const i=_.get(a.id);s=ia(i),r=i?.loaded?Ht(i):Me(a),t.title.value=a.id?`${s} (${a.id})`:"New page"}else if(a.kind==="component"){const i=B.get(a.id);s=a.id,r=i?fe(i):Me(a),t.title.value=`Component <dm-${a.id}>`}else{const i=$.get(a.id);s=a.id,r=i?re(i):Oe.has(a.id)||Me(a),t.title.value=`Block ${a.id}`}t.label.peek()!==s&&(t.label.value=s),t.dirty.peek()!==r&&(t.dirty.value=r);const n=t.id===o.active.peek();t.active.peek()!==n&&(t.active.value=n)}e.map(t=>t.id).join("|")!==o.tabs.peek().map(t=>t.id).join("|")&&(o.tabs.value=e),$s()};let ge=new Set;try{const e=S.get(rs);Array.isArray(e)&&(ge=new Set(e.filter(t=>typeof t=="string")))}catch{}const pa=new Map,Qt=e=>{let t=pa.get(e.key);return t?(t.label.peek()!==e.label&&(t.label.value=e.label),t.meta.peek()!==(e.meta||"")&&(t.meta.value=e.meta||""),t.status.peek()!==(e.status||"")&&(t.status.value=e.status||""),t.folder.peek()!==!!e.folder&&(t.folder.value=!!e.folder)):(t={...e,label:M.observable(e.label),meta:M.observable(e.meta||""),status:M.observable(e.status||""),folder:M.observable(!!e.folder),active:M.observable(!1),dirty:M.observable(!1),folded:M.observable(!1),title:""},pa.set(e.key,t)),t.title=e.kind==="block"||e.kind==="component"?e.meta?`${e.id} - ${e.meta}`:e.id:e.kind==="folder"?e.meta:`${e.label} (${e.meta})`,t},Zt=new Map,z=()=>{const e=o.filter.peek(),t=I(o.active.peek()),a=t?.kind==="page"?t.id:null,s=t?.kind==="block"?t.id:null,r=t?.kind==="component"?t.id:null,n=new Set(Pe().map(f=>f.id)),i=Kn(D,{filter:e,folded:ge}).map(Qt);for(const f of i){const T=f.kind==="page"&&f.id===a,j=f.kind==="page"&&(n.has(f.id)||!_.get(f.id)?.loaded&&Me({kind:"page",id:f.id})),de=ge.has(f.id);f.active.peek()!==T&&(f.active.value=T),f.dirty.peek()!==j&&(f.dirty.value=j),f.folded.peek()!==de&&(f.folded.value=de)}const g=jn(ae,e).map(Qt),O=f=>{const T=$.get(f);return T?re(T):Oe.has(f)||Me({kind:"block",id:f})};for(const f of g){const T=f.id===s,j=O(f.id);f.active.peek()!==T&&(f.active.value=T),f.dirty.peek()!==j&&(f.dirty.value=j)}const X=Fn(Ve,e).map(Qt),Ee=f=>{const T=B.get(f);return T?fe(T):Me({kind:"component",id:f})};for(const f of X){const T=f.id===r,j=Ee(f.id);f.active.peek()!==T&&(f.active.value=T),f.dirty.peek()!==j&&(f.dirty.value=j)}const yo=u?On(gt,qt,pe):[],Na=(f,T,j)=>(Zt.has(f)||Zt.set(f,{key:f,id:T,label:j,kindText:"",title:"",active:M.observable(!1),dirty:M.observable(!1)}),Zt.get(f)),wo=yo.map(f=>{const T=Na(`block:${f.name}`,f.name,f.name);T.kindText=f.footer?"Footer":"Block",T.title=`${f.name} - ${f.footer?"in the footer, on every page":"a block on this page"}`;const j=f.name===s,de=O(f.name);return T.active.peek()!==j&&(T.active.value=j),T.dirty.peek()!==de&&(T.dirty.value=de),T});for(const f of Jo){const T=Na(`component:${f}`,f,f);T.kindText="Component",T.title=`<dm-${f}> - a component on this page`;const j=f===r,de=Ee(f);T.active.peek()!==j&&(T.active.value=j),T.dirty.peek()!==de&&(T.dirty.value=de),wo.push(T)}const Dt=(f,T)=>f.map(j=>j.key).join("|")===T.map(j=>j.key).join("|");Dt(i,o.pageRows.peek())||(o.pageRows.value=i),Dt(g,o.blockRows.peek())||(o.blockRows.value=g),Dt(X,o.compRows.peek())||(o.compRows.value=X),Dt(wo,o.thisPage.peek())||(o.thisPage.value=wo)},ua=async()=>{const e=await k.pages.list().catch(()=>null);!c()||!Array.isArray(e)||(D=e,z(),V())},ee=()=>{const e=I(o.active.peek()),t=e?Ya(e):"/pages/new",a=window.location.hash.replace(/^#/,"");L.address=a.split("?")[0]===t?a:t,a.split("?")[0]!==t?history.replaceState({pe:1},"","#"+t):history.state?.pe||history.replaceState({pe:1},"",window.location.hash)},As=async e=>{if(e.loading)return e.loading;e.loading=(async()=>{let t=null;if(e.isEdit){if(t=await k.pages.get(e.id).catch(()=>null),!c())return!1;if(!t)return E.toast(`Page ${e.id} not found.`,{type:"error"}),!1}e.page=t;const a=oa(t,e.id);e.state=a,e.base=Y(a),e.savedAdminTags=JSON.stringify(a.adminTags),e.loaded=!0;let s=null;try{s=S.get(je(e.id))}catch{}const r=yn(s),n=Co({kind:"page",id:e.id});if(r){W(je(e.id));const i=Y(r.state)!==e.base,g=kn(r,e.base),O={...a,...r.state,adminTags:a.adminTags};if(i&&n&&!g)e.state=O,E.toast(`Your unsaved changes to ${e.id||"the new page"} are back.`,{type:"info"});else if(i){const X=r.at?` (${Ie(r.at)})`:"",Ee=g?" The saved page has changed since - restoring puts your version back over it (the saved one stays in History).":"";await E.confirm(`You left unsaved changes to ${e.id||"a new page"}${X}. Restore them?${Ee}`)&&c()&&(e.state=O)}}return!0})();try{return await e.loading}finally{e.loading=null}},ma=e=>(_.has(e)||_.set(e,{kind:"page",id:e,isEdit:!!e,loaded:!1,page:null,state:null,base:"",savedAdminTags:"[]",at:0}),_.get(e)),ie=async e=>{const t=Q({kind:"page",id:e});({ids:x}=$o(x,t)),o.active.value=t,o.blockTab.value="",co();const a=ma(e);if(V(),!a.loaded){o.previewStatus.value=`Opening ${e||"a new page"}\u2026`;const s=await As(a);if(!c()||!x.includes(t)||_.get(e)!==a)return;if(!s){fa(t);return}if(o.active.peek()!==t){V(),z();return}}sa(a),ee(),V(),z(),U(),G.refresh(),F.focus()};w=async(e,t)=>{if(L.address=t,e.kind==="page"){ie(e.id);return}if(!e.id){(e.kind==="block"?$t:tt)({project:jt(t)||""});return}const a=e.kind==="block"?pe:se;if(a.has(e.id)||await(e.kind==="block"?Fo:qo)(),!!c()){if(!a.has(e.id)){E.toast(`There is no ${e.kind} called "${e.id}".`,{type:"warning"}),ee(),z();return}e.kind==="block"?Ne(e.id):et(e.id)}};const fa=e=>{const t=o.active.peek(),a=In(x,e,t);x=a.ids,$e.delete(e);const s=I(e),r=a.active!==t?I(a.active):null;if(s.kind==="page"){if(_.delete(s.id),u?.id===s.id){const n=[..._.values()].filter(i=>i.loaded&&i.id!==s.id).sort((i,g)=>g.at-i.at)[0];n?sa(n):xs()}}else s.kind==="component"?(B.delete(s.id),o.compTab.peek()===s.id&&co()):($.delete(s.id),o.blockTab.peek()===s.id&&(o.blockTab.value="",o.blockReady.value=!1));if(o.active.value=a.active,r?.kind==="page"){ie(r.id);return}if(r?.kind==="block"){Ne(r.id);return}if(r?.kind==="component"){et(r.id);return}ee(),_e(),be(),ve(),V(),z(),U()},Je=e=>{const t=I(e);if(!t||!x.includes(e))return;let a="";try{if(t.kind==="page"){const s=_.get(t.id);s&&Ht(s)&&(S.set(je(s.id),Fa(s===u?K():s.state,s.base)),To({kind:"page",id:s.id}),a=ia(s))}else if(t.kind==="component"){const s=B.get(t.id);s&&fe(s)&&(S.set(qe(t.id),na(s,new Date().toISOString())),To({kind:"component",id:t.id}),a=t.id)}else{const s=$.get(t.id);s&&re(s)&&(S.set(Fe(t.id),{form:{...s.form},base:s.base,at:new Date().toISOString()}),To({kind:"block",id:t.id}),a=t.id)}}catch{}fa(e),a&&E.toast(`Changes to "${a}" kept as a draft - back when you open it again.`,{type:"info"})},Mt=e=>{const t=I(e);t&&(t.kind==="page"?ie(t.id):t.kind==="component"?et(t.id):Ne(t.id))},ba=new Set,Ds=async e=>{const t=[];for(const a of e){const s=$.get(a);if(!s)continue;const r={...s.form};try{const n=_a(r,s.block?.meta);await k.blocks.put(a,n),s.block={...s.block,meta:n.meta},s.saved=r,s.base=Oo(r),ft.set(a,new Date().toISOString()),c()&&W(Fe(a)),t.push(a)}catch(n){E.toast(`Block "${a}" not saved: ${n.message||"Unknown error"}`,{type:"error"})}}return t.forEach(a=>ba.add(a)),t.length&&(be(),lt("#/blocks")),t},Ns=async e=>{const t=e===u?K():e.state,a=Eo(t.seoSchema).error;if(a)return E.toast(`${e.id||"The new page"} not saved - structured data: ${a}`,{type:"error"}),e===u&&(o.seoSchemaNote.value=a,o.dockOn.value=!0,o.dockTab.value="seo"),!1;const s=Kt(t,e,Ut(t)),r=e.id;try{if(e.isEdit){const n={frontmatter:s,body:t.body};t.urlPath!==e.id&&(n.newUrlPath=t.urlPath),await k.pages.update(e.id,n)}else await k.pages.create({urlPath:t.urlPath,frontmatter:s,body:t.body})}catch(n){return E.toast(`${e.id||"The new page"} not saved: ${n.message||"Unknown error"}`,{type:"error"}),!1}if(!c())return!0;if(e.savedAdminTags=JSON.stringify(t.adminTags),e.base=Y(t),e===u?o.base.value=e.base:e.state=t,W(je(r)),!e.isEdit||t.urlPath!==e.id){_.delete(r),e.id=t.urlPath,e.isEdit=!0,_.set(e.id,e);const n=Q({kind:"page",id:r}),i=Q({kind:"page",id:e.id});$e.delete(n),{ids:x}=Wn(x,n,i,o.active.peek()),o.active.peek()===n&&(o.active.value=i),e===u&&(o.curId.value=e.id,ee())}return!0},Ls=async e=>{const t=[];for(const a of e){const s=B.get(a);if(!s||s.readOnly)continue;if(Ot===s?(clearTimeout(Pt),Ot=null,ve(),Ze(s)):s.state||Ze(s),s.compiling&&await s.compiling,!c())return t;if(s.state!=="ok"){E.toast(`Component "${a}" not saved: ${s.state==="error"?"it does not compile - fix the errors first.":"it has not compiled yet - try again in a moment."}`,{type:"warning"});continue}const r={...s.form},n={...s.meta||{}};delete n.bundled,r.project?n.project=r.project:delete n.project;try{await k.components.put(a,{source:r.source,bundled:r.bundled,meta:n}),s.meta=n,s.saved=r,s.base=Do({name:a,...r});const i=Ve.find(g=>g.name===a);i&&(i.bundled=r.bundled,i.meta=n),c()&&W(qe(a)),t.push(a)}catch(i){E.toast(`Component "${a}" not saved: ${i.message||"Unknown error"}`,{type:"error"})}}return t.length&&(ve(),lt("#/components")),t};let eo=!1;const to=async(e=!1)=>{if(o.saving.peek()||eo)return;eo=!0;let t,a,s=[];try{if(await ga(),!c())return;const n=I(o.active.peek());if(e)t=Pe(),a=me(),s=Ke();else if(n?.kind==="block")t=[],a=$.has(n.id)&&re($.get(n.id))?[n.id]:[];else if(n?.kind==="component")t=[],a=[],s=B.has(n.id)&&fe(B.get(n.id))?[n.id]:[];else if(n?.kind==="page"&&u)a=me().filter(kt),t=!u.isEdit||Vt()||!a.length?[u]:[];else return;if(!t.length&&!a.length&&!s.length){E.toast("Nothing to save.",{type:"info"});return}for(const g of t){const O=Mo(g===u?K():g.state);if(O.length){E.toast(`${g.id||"New page"}: ${O[0].text}`,{type:"warning"}),g!==u&&await ie(g.id),o.dockOn.value=!0,o.dockTab.value=O[0].field==="dconfig"?"dconfig":"details",dt("dock",!0);return}}const i=Dn(a,Object.fromEntries(a.map(g=>[g,Wa(ze.get(g),u?.id||"")])),ba);if(i&&(!await E.confirm(i)||!c()))return}finally{eo=!1}const r=t.length>0&&vt();o.saving.value=!0;try{let n=0;for(const O of t)await Ns(O)&&n++;if(!c())return;if(r){const O=p.css.peek();await k.settings.saveCustomCss(O),Z.value=O,W(pt)}const i=await Ds(a);if(!c())return;const g=await Ls(s);if((n||i.length||g.length)&&E.toast(Nn({pages:n,css:r,blocks:i,components:g}),{type:"success"}),_e(),!c())return;V(),z(),n&&(lt("#/pages"),St(),ua())}catch(n){E.toast(`Save failed: ${n.message||"Unknown error"}`,{type:"error"})}finally{o.saving.value=!1}};let oe=null,Ge=null;const Ae=new Map,De=new Map,va=()=>{const e=$.get(o.blockTab.peek());!e||!oe||(o.blockReady.value=!1,oe.setValue(e.form.content),Ge.setValue(e.form.css),o.blockProject.value=e.form.project,Lo(N("#block-project"),e.form.project),o.blockBundled.value=e.form.bundled,o.blockWhere.value=Mn({usage:ze.get(e.form.name)})||"Where it is used could not be read.",o.blockNote.value="",o.blockReady.value=!0,no())},Bs=e=>{const t=$.get(e);if(!t)return{};const a=t.form.content;return Object.fromEntries(Ja(Ka(a),Po(a),t.samples||{}).map(s=>[s.name,s.value]))};l(M.effect(()=>{const e=o.blockProject.value,t=o.blockBundled.value,a=$.get(o.blockTab.peek());!a||!o.blockReady.peek()||a.form.project===e&&a.form.bundled===t||(a.form.project=e,a.form.bundled=t,be())}));const oo=async(e,{quiet:t=!1}={})=>{for(;Ae.has(e);)if(await Ae.get(e),$.has(e)||t)return $.get(e)||null;if($.has(e))return $.get(e);const a=(async()=>{try{const s=await k.blocks.get(e);if(!c()||!t&&!x.includes(Q({kind:"block",id:e})))return null;const r=Ua({...s,name:e}),n={block:s,saved:r,base:Oo(r),form:{...r},samples:{}};let i=null;try{i=S.get(Fe(e))}catch{}const g=Cn(i,n.base);if(t&&g?.stale)return null;if(Co({kind:"block",id:e}),g){const O=g.at?` (${Ie(g.at)})`:"",X=!g.stale||await E.confirm(`You left unsaved changes to "${e}"${O}, but the block has been saved since. Restore your version anyway?`);if(!c())return null;X&&(n.form={...n.form,...g.form,name:e},g.stale||E.toast(`Your unsaved changes to "${e}" are back.`,{type:"info"}))}return i&&W(Fe(e)),$.set(e,n),be(),n}catch(s){return c()&&o.blockTab.peek()===e&&(o.blockNote.value=`Could not open "${e}": ${s.message||"unknown error"}`),null}finally{Ae.delete(e)}})();return Ae.set(e,a),a},ga=async()=>{for(;Ae.size||De.size;)await Promise.all([...Ae.values(),...De.values()])},Ne=async e=>{if(!e||!pe.has(e))return;const t=Q({kind:"block",id:e});({ids:x}=$o(x,t)),o.active.value=t,co(),o.cssMode.peek()&&(o.cssMode.value=!1),o.viewMode.peek()==="preview"&&(o.viewMode.value="split"),o.blockTab.peek()!==e&&(o.blockReady.value=!1),o.blockTab.value=e,o.blockUses.value=u&&kt(e)?Ln(Wa(ze.get(e),u.id||"")):Rn(ze.get(e)),ee(),V(),z();let a=$.get(e);a||(o.blockReady.value=!1,o.blockNote.value=`Opening ${e}\u2026`,a=await oo(e)),!(!a||!c()||o.blockTab.peek()!==e)&&(va(),V(),z(),_t=e,U(),(o.blockPart.peek()==="css"?Ge:oe)?.focus())};let ao=null;l(()=>clearTimeout(ao));const ka=(e,t)=>{const a=$.get(o.blockTab.peek());!a||!o.blockReady.peek()||(a.form[e]=t,clearTimeout(ao),ao=setTimeout(()=>{be(),e==="content"&&no()},120),U())},ke=N("#pe-block-samples"),ha=e=>{ke.replaceChildren();const t=document.createElement("p");t.className="pe-dock-none",t.textContent=e,ke.appendChild(t)};let Xe=[],so=null;function no(){const e=$.get(o.blockTab.peek());if(!e){Xe=[],so=null,ha("Open a block to see its placeholders.");return}const t=Ja(Ka(e.form.content),Po(e.form.content),e.samples);if(!(so===e&&t.map(a=>a.name).join("|")===Xe.join("|"))){if(Xe=t.map(a=>a.name),so=e,!t.length){ha("No {{placeholders}} in the template yet.");return}ke.replaceChildren();for(const a of t){const s=document.createElement("div");s.className="pe-comp-prop",s.tabIndex=0,s.dataset.sample=a.name;const r=document.createElement("label");r.className="pe-comp-prop-label",r.textContent=a.name,r.title=a.raw?`{{{${a.name}}}} - raw markup`:`{{${a.name}}}`;const n=document.createElement("input");n.type="text",n.className="form-input form-input--sm pe-comp-prop-input",n.value=a.value,n.setAttribute("aria-label",`Sample value for ${a.name}`),n.addEventListener("input",()=>{e.samples[a.name]=n.value,U()});const i=document.createElement("button");i.type="button",i.className="pe-kebab",i.setAttribute("aria-label",`Actions for ${a.name}`),i.title="Actions",i.textContent="\u22EE",s.append(r,n,i),ke.appendChild(s)}}}const ro=(e,t)=>Po(e.form.content).has(t)?`{{{${t}}}}`:`{{${t}}}`,js=e=>{const t=$.get(o.blockTab.peek());return!t||!Xe.includes(e)?[]:[{header:ro(t,e)},{label:"Insert into the template",icon:"corner-down-right",action:()=>{const a=oe?.textarea;a&&(o.blockPart.value="html",a.focus(),a.setRangeText(ro(t,e),a.selectionStart,a.selectionEnd,"end"),a.dispatchEvent(new Event("input",{bubbles:!0})))}},{label:"Copy the placeholder",icon:"copy",action:()=>Le(ro(t,e),"Placeholder")},{type:"divider"},{label:"Reset the sample",icon:"rotate-ccw",action:()=>{delete t.samples[e],Xe=[],no(),U()}}]};{const e=E.contextMenu(ke,{cursor:"glyph",match:".pe-comp-prop",inherit:!1,density:"compact",items:t=>js(t.dataset.sample)});l(()=>e?.destroy?.()),ke.addEventListener("click",t=>{const a=t.target.closest(".pe-kebab");a&&Se(a)}),ke.addEventListener("keydown",t=>{const a=t.target.closest(".pe-comp-prop");a&&t.key==="F10"&&t.shiftKey&&(t.preventDefault(),Se(a))})}const ce={},he=N("#component-preview"),Tt=e=>{try{he?.contentWindow?.postMessage(e,"*")}catch{}};let lo=he.contentDocument?.readyState==="complete"&&/component-preview/.test(he.contentDocument?.URL||""),Ct=!1,Qe=null;const ya=()=>{lo=!0,Qe&&c()&&(Tt({type:"mount",payload:Qe}),Ct=!0),Qe=null};he.addEventListener("load",ya),l(()=>he.removeEventListener("load",ya));const Is=e=>{if(!lo){Qe=e;return}if(!Ct){Ct=!0,Tt({type:"mount",payload:e});return}Qe=e,lo=!1,Ct=!1,he.src=he.src};let xt="";const io=(e=!1)=>{const t=B.get(o.compTab.peek());if(!(!t||t.state!=="ok"||!t.js)){if(o.viewMode.peek()==="write"){t.previewStale=!0;return}!e&&xt===t.name&&!t.previewStale||(t.previewStale=!1,xt=t.name,Is({compiledJs:t.js,tagName:`dm-${t.name}`,props:t.previewProps}))}},Ze=e=>{const t=++e.seq;return e.state="compiling",o.compTab.peek()===e.name&&(o.compState.value="compiling"),e.compiling=(async()=>{let a;try{a=await k.components.compile(e.name,e.form.source)}catch(s){a={errors:[{type:"request",message:s.message||"Compile request failed."}]}}if(!(t!==e.seq||!c())){if(a.errors?.length?(e.state="error",e.errors=a.errors.map(s=>`[${s.type}] ${s.message}`).join(`
120
+ `),e.js=null):(e.state="ok",e.errors="",e.js=a.js),o.compTab.peek()!==e.name){e.previewStale=!0;return}o.compState.value=e.state,o.compErrors.value=e.errors,e.state==="ok"&&(fo(),io(!0))}})().finally(()=>{e.compiling&&t===e.seq&&(e.compiling=null)}),e.compiling};let Pt=null,Ot=null;l(()=>clearTimeout(Pt));const wa=()=>{const e=B.get(o.compTab.peek());if(!e||!ce.template)return;o.compReady.value=!1;const t=it(e.form.source);for(const a of It)ce[a].setValue(t[a]),ce[a].textarea.readOnly=e.readOnly;o.compReadOnly.value=e.readOnly,o.compProject.value=e.form.project,Lo(N("#comp-project"),e.form.project),o.compBundled.value=e.form.bundled,o.compState.value=e.state,o.compErrors.value=e.errors,o.compNote.value="",o.compReady.value=!0,fo(),e.state==="ok"?io():e.state||Ze(e)},co=()=>{o.compTab.peek()&&(o.compTab.value="",o.compReady.value=!1)},Ea={page:"details",block:"details",component:"details"};let Rt="";l(M.effect(()=>{const e=Wo.value;e!==Rt&&(Rt&&(Ea[Rt]=o.dockTab.peek()),Rt=e,e&&(o.dockTab.value=Ea[e]||"details",e==="page"&&o.dockTab.peek()==="history"&&o.dockOn.peek()&&u?.isEdit&&!Ye&&le()))}));const po=async(e,{quiet:t=!1}={})=>{if(De.has(e))return De.get(e);if(B.has(e))return B.get(e);const a=(async()=>{try{const s=await k.components.get(e);if(!c()||!x.includes(Q({kind:"component",id:e})))return null;const r=s.readOnly===!0||s.origin==="plugin",n={project:s.meta?.project||"",bundled:!!s.bundled,source:Ga(it(s.source))},i={name:e,comp:s,saved:n,base:Do({name:e,...n}),form:{...n},meta:s.meta||{},readOnly:r,state:"",errors:"",js:null,seq:0,compiling:null,previewProps:{},previewStale:!1};let g=null;try{g=S.get(qe(e))}catch{}const O=Zn(g,i.base);if(t&&(!O.ok||O.stale||r))return null;const X=Co({kind:"component",id:e});if(t&&!X)return null;if(O.ok&&!r){const Ee=g.at?` (${Ie(g.at)})`:"",yo=X&&!O.stale||await E.confirm(O.stale?`You left unsaved changes to the component "${e}"${Ee}, but it has been saved since. Restore your version anyway?`:`You left unsaved changes to the component "${e}"${Ee}. Restore them?`);if(!c())return null;yo&&(i.form={project:String(g.project??""),bundled:g.bundled===!0,source:String(g.source)},X&&!O.stale&&E.toast(`Your unsaved changes to the component "${e}" are back.`,{type:"info"}))}return g&&W(qe(e)),B.set(e,i),ve(),i}catch(s){return c()&&o.compTab.peek()===e&&(o.compNote.value=`Could not open <dm-${e}>: ${s.message||"unknown error"}`),null}finally{De.delete(e)}})();return De.set(e,a),a},et=async e=>{if(!e||!se.has(e))return;const t=Q({kind:"component",id:e});({ids:x}=$o(x,t)),o.active.value=t,o.blockTab.value="",o.cssMode.peek()&&(o.cssMode.value=!1),o.viewMode.peek()==="preview"&&(o.viewMode.value="split"),o.compTab.value=e,ee(),V(),z();let a=B.get(e);a||(o.compReady.value=!1,o.compReadOnly.value=!1,o.compState.value="",o.compNote.value=`Opening <dm-${e}>\u2026`,a=await po(e)),!(!a||!c()||o.compTab.peek()!==e)&&(wa(),V(),z(),ce[o.compPart.peek()]?.focus())},Fs=(e,t)=>{const a=B.get(o.compTab.peek());if(!a||!o.compReady.peek()||a.readOnly)return;const s=it(a.form.source);s[e]=t,a.form.source=Ga(s),clearTimeout(Pt),Ot=a,Pt=setTimeout(()=>{Ot=null,ve(),Ze(a)},300)};l(M.effect(()=>{const e=o.compProject.value,t=o.compBundled.value,a=B.get(o.compTab.peek());!a||!o.compReady.peek()||a.readOnly||a.form.project===e&&a.form.bundled===t||(a.form.project=e,a.form.bundled=t,ve())}));const ye=N("#pe-comp-props");let uo=[];const mo=e=>{ye.replaceChildren();const t=document.createElement("p");t.className="pe-dock-none",t.textContent=e,ye.appendChild(t)};function fo(){const e=B.get(o.compTab.peek());if(!e){uo=[],mo("Open a component to try its props.");return}const{rows:t,error:a}=Xa(it(e.form.source).props,e.previewProps);if(uo=t,a){mo("The <props> block is not valid JSON - fix it to see the props here.");return}if(!t.length){e.previewProps={},mo("No props declared. Add them in the <props> block.");return}e.previewProps=Object.fromEntries(t.map(s=>[s.name,s.value])),ye.replaceChildren();for(const s of t){const r=document.createElement("div");r.className="pe-comp-prop",r.tabIndex=0,r.dataset.prop=s.name;const n=document.createElement("label");n.className="pe-comp-prop-label",n.textContent=s.label,n.title=`${s.name} (${s.type})`;const i=s.type==="array"||s.type==="object"?document.createElement("textarea"):document.createElement("input");s.type==="boolean"?(i.type="checkbox",i.className="form-check",i.checked=Qa(s.type,s.value)):(i.tagName==="INPUT"?i.type=s.type==="number"?"number":"text":i.rows=2,i.className="form-input form-input--sm pe-comp-prop-input",i.value=Qa(s.type,s.value)),i.setAttribute("aria-label",s.label);const g=()=>{e.previewProps[s.name]=tr(s.type,s.type==="boolean"?i.checked:i.value),xt===e.name&&Tt({type:"update",payload:{props:e.previewProps}})};i.addEventListener("input",g),i.addEventListener("change",g);const O=document.createElement("button");O.type="button",O.className="pe-kebab",O.setAttribute("aria-label",`Actions for ${s.label}`),O.title="Actions",O.textContent="\u22EE",r.append(n,i,O),ye.appendChild(r)}}const Le=async(e,t)=>{try{await navigator.clipboard.writeText(e),E.toast(`${t} copied.`,{type:"success"})}catch{E.toast(e,{type:"info",duration:8e3})}},qs=e=>{const t=B.get(o.compTab.peek()),a=uo.find(n=>n.name===e);if(!t||!a)return[];const s=Qn(a.name,a.type,t.previewProps[a.name]),r=`dm-${t.name}`;return[{header:a.label},{label:"Reset to its default",icon:"rotate-ccw",action:()=>{delete t.previewProps[a.name];const n=Xa(it(t.form.source).props,{}).rows.find(i=>i.name===a.name);t.previewProps[a.name]=n?n.value:null,fo(),xt===t.name&&Tt({type:"update",payload:{props:t.previewProps}})}},{label:"Copy as an attribute",icon:"copy",disabled:!s,action:()=>Le(s,"Attribute")},{label:"Copy the tag with this value",icon:"code",action:()=>Le(`<${r}${s?` ${s}`:""}></${r}>`,"Tag")},{label:"Copy the prop name",icon:"copy",action:()=>Le(a.name,"Name")},{type:"divider"},{label:"Edit in <props>",icon:"edit",action:()=>{o.compPart.value="props";const n=ce.props?.textarea;if(!n)return;const i=n.value.indexOf(`"${a.name}"`);n.focus(),i>=0&&n.setSelectionRange(i,i+a.name.length+2)}}]};{const e=E.contextMenu(ye,{cursor:"glyph",match:".pe-comp-prop",inherit:!1,density:"compact",items:t=>qs(t.dataset.prop)});l(()=>e?.destroy?.()),ye.addEventListener("click",t=>{const a=t.target.closest(".pe-kebab");a&&Se(a)}),ye.addEventListener("keydown",t=>{const a=t.target.closest(".pe-comp-prop");a&&t.key==="F10"&&t.shiftKey&&(t.preventDefault(),Se(a))})}let Be=!1;const Sa=()=>jt()||"",$t=async({project:e=Sa(),seed:t="",content:a=ir,css:s=""}={})=>{if(!Be){Be=!0;try{const n=await os({kind:"block",projects:J,taken:[...pe],project:e,name:t,check:Sn,register:l,create:async(i,g)=>{await k.blocks.put(i,_a({name:i,project:g,bundled:!1,content:a,css:s}))}});if(!c())return;if(!n){ee();return}if(await Fo(),!c())return;lt("#/blocks"),E.toast(`Created the block "${n}".`,{type:"success"}),Ne(n)}finally{Be=!1}}},tt=async({name:e="",source:t=Xn,project:a=Sa()}={})=>{if(!Be){Be=!0;try{const s=await os({kind:"component",projects:J,taken:[...se],project:a,name:e,register:l,check:(r,n)=>er(r,{isNew:!0,taken:n}),create:async(r,n)=>{await k.components.put(r,{source:t,bundled:!1,meta:n?{project:n}:{}})}});if(!c())return;if(!s){ee();return}if(await qo(),!c())return;lt("#/components"),E.toast(`Created the component "${s}".`,{type:"success"}),et(s)}finally{Be=!1}}},Ma=h.querySelector(".dm-plugin-body")||h,zs=M.computed(()=>{o.blockRev.value,o.compRev.value,o.docRev.value,Y(K()),o.base.value;const e=I(o.active.value),t=[...Pe().map(s=>Q({kind:"page",id:s.id})),...me().map(s=>Q({kind:"block",id:s})),...Ke().map(s=>Q({kind:"component",id:s}))];let a=[];return e?.kind==="block"||e?.kind==="component"?a=[o.active.value]:e?.kind==="page"&&u&&(a=[o.active.value,...me().filter(kt).map(s=>Q({kind:"block",id:s}))]),Yn(t,a)}),Vs={...p,...o,dirty:us,pageDirty:ds,activeDirty:ms,cssUnsaved:ps,hasPage:fs,activeKind:Uo,canView:_o,viewHref:bs,problem:vs,statusText:gs,saveAll:zs,dockKind:Wo,blockTagText:ks,compStateText:hs,compTagText:ys,anySelected:ws,allSelected:Es,someSelected:Ss},ot=(e,t)=>dt(e,t),bo={save:()=>to(!1),saveAllOpen:()=>to(!0),discard:async()=>{if(await ga(),!c())return;const e=I(o.active.peek());if(e?.kind==="block"){const t=$.get(e.id);if(!t||!re(t)||!await E.confirm(`Discard the changes to "${e.id}" you have not saved?`))return;t.form={...t.saved},W(Fe(e.id)),va(),be()}else if(e?.kind==="component"){const t=B.get(e.id);if(!t||!fe(t)||!await E.confirm(`Discard the changes to the component "${e.id}" you have not saved?`))return;t.form={...t.saved},t.state="",W(qe(e.id)),wa(),ve()}else{if(!u||e?.kind==="page"&&e.id!==u.id||!(Vt()||vt())||!await E.confirm("Discard the changes to this page you have not saved?"))return;We(JSON.parse(o.base.peek())),p.cssOpened.peek()&&(p.css.value=Z.peek()),W(je(u.id)),W(pt),_e()}V(),z(),U()},newPage:()=>ie(""),newBlock:()=>$t(),newComponent:()=>tt(),toggleSection:e=>{const t={here:"secHere",pages:"secPages",blocks:"secBlocks",components:"secComps"}[e];t&&(o[t].value=!o[t].peek(),ot(t,o[t].peek()))},toggleExplorer:()=>{o.explorerOn.value=!o.explorerOn.peek(),ot("explorer",o.explorerOn.peek())},toggleDock:()=>{o.dockOn.value=!o.dockOn.peek(),ot("dock",o.dockOn.peek()),o.dockOn.peek()&&o.dockTab.peek()==="history"&&!Ye&&le()},showDock:e=>{o.dockTab.value=e,e==="history"&&u?.isEdit&&!Ye&&le()},schemaChanged:()=>{o.seoSchemaNote.value=Eo(p.seoSchema.peek()).error},insertSchemaExample:async e=>{const t=e?.target,a=t?.value||"";if(t&&(t.value=""),!a||p.seoSchema.peek().trim()&&!await E.confirm("Replace the structured data with the example?"))return;const s=p.urlPath.peek().trim()||u?.id||"/";p.seoSchema.value=nn(a,{title:p.seoTitle.peek().trim()||p.title.peek().trim(),description:p.seoDesc.peek().trim()||p.description.peek().trim(),url:`${location.origin}${s}`,siteName:A?.title||A?.seo?.defaultTitle||""}),o.seoSchemaNote.value=""},setBlockPart:e=>{o.blockPart.value=e==="css"?"css":"html",(e==="css"?Ge:oe)?.focus()},formatBlock:()=>{const e=$.get(o.blockTab.peek());!e||!oe||(e.form.content=xn(e.form.content),oe.setValue(e.form.content),be(),U())},copyShortcode:async()=>{const e=o.blockTab.peek();e&&await Le(Pn(e),"Shortcode")},setCompPart:e=>{o.compPart.value=It.includes(e)?e:"template",ce[o.compPart.peek()]?.focus()},copyComponent:()=>{const e=B.get(o.compTab.peek());e&&tt({name:xo(e.name,[...se]),source:e.form.source})},setMode:e=>{o.cssMode.peek()&&(o.cssMode.value=!1),o.viewMode.value=Ro(e),ot("viewMode",o.viewMode.peek()),e!=="write"&&(U(!0),io())},toggleFullscreen:()=>{o.fullscreen.value=!o.fullscreen.peek(),ot("fullscreen",o.fullscreen.peek())},toggleCss:()=>{o.blockTab.peek()||o.compTab.peek()||(o.cssMode.peek()?(o.cssMode.value=!1,o.viewMode.value=Ro(Te().viewMode),G.refresh()):(ta(),o.cssMode.value=!0,o.viewMode.value="split"))},formatMarkdown:()=>{const e=F.selectionStart,t=pr(bt());G.unfoldAll(),xe=!0,F.value=t,xe=!1,F.selectionStart=F.selectionEnd=Math.min(e,t.length),F.dispatchEvent(new Event("input")),He.paint(),G.refresh(),E.toast("Markdown formatted.",{type:"success"})},toggleFold:()=>{o.folded.peek()?G.unfoldAll():G.foldAll(),o.folded.value=!o.folded.peek(),He.paint()},setWidth:e=>{o.liveWidth.value=e},refreshPreview:()=>{if(o.compTab.peek()){const e=B.get(o.compTab.peek());e&&Ze(e)}else U(!0)},share:async()=>{if(!u?.isEdit){E.toast("Save the page first - a share link needs a page to point at.",{type:"warning"});return}await rr(u.id)},toggleAll:e=>{const t=!!e?.target?.checked;o.versions.peek().forEach(a=>{a.checked.value=t})},deleteSelected:async()=>{const e=o.versions.peek().filter(a=>a.checked.peek()).map(a=>a.filename),t=e.length;if(!(!t||!await E.confirm(`Delete ${t} version${t>1?"s":""}? This cannot be undone.`)))try{await k.versions.bulkDelete(u.id,e),E.toast(`${t} version${t>1?"s":""} deleted.`,{type:"success"}),await le()}catch(a){E.toast("Delete failed: "+a.message,{type:"error"})}},saveNamed:async()=>{if(!u?.isEdit)return;const e=u;await lr(e.id)&&c()&&u===e&&await le()},closeCompare:()=>{o.compare.value=null,Et=null},restoreCompared:()=>la(Et)};Ue.querySelectorAll("[data-tip]").forEach(e=>E.tooltip(e,{content:e.getAttribute("data-tip"),position:"top"}));const Hs=M.applyBindings(Vs,Ma,{methods:bo});l(()=>Hs.dispose()),oe=No(N('.pe-block-edit[data-part="html"]'),{ariaLabel:"Block template HTML",language:"html",onChange:e=>ka("content",e)}),Ge=No(N('.pe-block-edit[data-part="css"]'),{ariaLabel:"Block CSS",language:"css",placeholder:`/* Scoped to this block - e.g.
121
121
  color: var(--dm-primary);
122
- h3 { font-size: 1.25rem; } */`,onChange:e=>va("css",e)}),l(()=>{oe.destroy(),Ge.destroy()});for(const e of It)ce[e]=Do(D(`.pe-comp-edit[data-part="${e}"]`),{ariaLabel:`Component <${e}> block`,language:{template:"html",props:"json",script:"js",style:"css"}[e],onChange:t=>js(e,t)});l(()=>{for(const e of It)ce[e].destroy()});const we=D(".pe-tabs-list"),$t=e=>e.target.closest(".pe-tab")?.dataset.id||"",Ma=e=>{const t=$t(e);t&&(e.target.closest(".pe-tab-close")?Je(t):Tt(t))},Ta=e=>{e.button===1&&$t(e)&&(e.preventDefault(),Je($t(e)))},Sa=e=>{const t=$t(e);!t||e.target.closest(".pe-tab-close")||(e.key==="Enter"||e.key===" "?(e.preventDefault(),Tt(t)):e.key==="Delete"?(e.preventDefault(),Je(t)):e.key==="F10"&&e.shiftKey&&(e.preventDefault(),Me(e.target.closest(".pe-tab"))))};we.addEventListener("click",Ma),we.addEventListener("auxclick",Ta),we.addEventListener("keydown",Sa),l(()=>{we.removeEventListener("click",Ma),we.removeEventListener("auxclick",Ta),we.removeEventListener("keydown",Sa)});const vo=e=>{for(const t of e)Je(t)},Vs=async e=>{const t=I(e);if(t?.id)if(t.kind==="page")a.active.peek()!==e&&await ie(t.id),c()&&f?.id===t.id&&Oa();else if(t.kind==="block"){const s=(A.get(t.id)||await oo(t.id,{quiet:!0}))?.form||Ha(await k.blocks.get(t.id).catch(()=>null));c()&&At({seed:Co(t.id,[...pe]),content:s.content,css:s.css})}else{const s=(B.get(t.id)||await po(t.id,{quiet:!0}))?.form.source??(await k.components.get(t.id).catch(()=>null))?.source??"";c()&&tt({name:Co(t.id,[...se]),source:s})}},Hs=e=>{const t=I(e);if(!t||!x.includes(e))return[];const o=x.indexOf(e),s=Ae.get(e)?.label.peek()||t.id,n=r=>{x=In(x,e,r),V()};return[{header:s},{label:"Close",icon:"x",action:()=>Je(e)},{label:"Close others",icon:"x-circle",disabled:x.length<2,action:()=>vo(Ao(x,e,"others"))},{label:"Close to the right",icon:"chevrons-right",disabled:o>=x.length-1,action:()=>vo(Ao(x,e,"right"))},{label:"Close all",icon:"layers",action:()=>vo(Ao(x,e,"all"))},{type:"divider"},{label:"Move left",icon:"arrow-left",disabled:o===0,action:()=>n(-1)},{label:"Move right",icon:"arrow-right",disabled:o>=x.length-1,action:()=>n(1)},{type:"divider"},{label:t.kind==="page"?"Clone page\u2026":`Duplicate ${t.kind}\u2026`,icon:"copy",disabled:!t.id,action:()=>Vs(e)},{label:"Copy link",icon:"link",disabled:!t.id,action:()=>Ne(`${location.origin}${location.pathname}#${_a(t)}`,"Link")}]};{const e=E.contextMenu(we,{cursor:"glyph",match:".pe-tab",inherit:!1,density:"compact",items:t=>Hs(t.dataset.id)});l(()=>e?.destroy?.())}const Ca=D(".pe-ex-scroll"),xa=e=>{const t=e.target.closest(".pe-ex-item"),o=t?.dataset.id||"";if(!o)return;const s=o.indexOf(":"),n=o.slice(0,s),r=o.slice(s+1),i=()=>{ge.has(r)?ge.delete(r):ge.add(r);try{S.set(ss,[...ge])}catch{}z()};if(n==="folder"){i();return}if(n==="page"&&e.target.closest(".pe-ex-chev")&&t.classList.contains("is-folder")){i();return}n==="page"?ie(r):n==="block"?Le(r):n==="component"&&et(r)};Ca.addEventListener("click",xa),l(()=>Ca.removeEventListener("click",xa));let go=null;l(()=>clearTimeout(go)),F.addEventListener("input",()=>{G.refresh(),ne.scrollTop=F.scrollTop,!xe&&(clearTimeout(go),go=setTimeout(Qo,120),U())}),F.addEventListener("scroll",()=>{ne.scrollTop=F.scrollTop}),ue.addEventListener("scroll",()=>{ne.scrollTop=ue.scrollTop}),l(M.effect(()=>{for(const e of Object.keys(p))e!=="css"&&e!=="cssOpened"&&p[e].value;a.bodyRev.value,U()})),l(M.effect(()=>{a.bodyRev.value,a.docRev.value;const e=f?bt():"";gt=f?wn(e,ft.map(t=>t.name),ft.filter(t=>t.shortTag).map(t=>t.name)):[],Wo=f?jn(e,se):[],yt(),z()})),l(M.effect(()=>{a.blockRev.value,a.compRev.value,a.docRev.value,a.active.value,a.filter.value,p.title.value,V(),z()})),l(M.effect(()=>{ne.hidden=!!(a.blockTab.value||a.compTab.value)}));let ko=null;l(()=>clearTimeout(ko)),l(M.effect(()=>{const e=p.css.value;a.cssMode.value&&(zt.paint(),mr(ne,e,ue),clearTimeout(ko),ko=setTimeout(Yo,400))}));{const e=D(".pe-version-list"),t=E.contextMenu(e,{cursor:"glyph",match:"tr[data-file]",inherit:!1,density:"compact",items:o=>{const s=Gt(o.dataset.file);return s?[{header:Qa(s)},{label:"Show beside the editor",icon:"columns",action:()=>Xt(s)},{label:"Restore this version\u2026",icon:"rotate-ccw",action:()=>na(s.filename)},{type:"divider"},{label:"Delete\u2026",icon:"trash",danger:!0,action:()=>xs(s)}]:[]}});l(()=>t?.destroy?.()),e.addEventListener("click",o=>{const s=o.target.closest("tr[data-file]");if(!s||o.target.matches("input"))return;const n=o.target.closest(".pe-kebab");if(n){Me(n);return}const r=Gt(s.dataset.file);r&&Xt(r)}),e.addEventListener("keydown",o=>{const s=o.target.closest("tr[data-file]");if(s){if(o.key==="F10"&&o.shiftKey)o.preventDefault(),Me(s);else if(o.key==="Enter"&&o.target===s){const n=Gt(s.dataset.file);n&&Xt(n)}}})}br(D("#editor-body"),D("#editor-divider"),l),Qs({image:e=>(a.cssMode.value=!1,vr(e)),scribe:e=>tn(e),card:e=>Us.open(e,{onMediaBrowse:()=>k.media.list().catch(()=>[])}),timeline:e=>_s.open(e,{onPreview:async t=>{try{return await k.pages.preview(t)}catch{return null}},onListCollections:()=>k.collections.list().catch(()=>[]),onGetCollection:t=>k.collections.get(t).catch(()=>null)})}),Jt.onEffects(e=>Gs.open(e)),Jt.onHelp(()=>La()),Jt.onTutorial(e=>on(e));const Pa=e=>{if(!c())return;const t=_n(e);t&&(e.preventDefault(),t==="save"?to(!1):t==="layout"?bo.setMode(Fn(a.viewMode.peek())):bo.toggleExplorer())};document.addEventListener("keydown",Pa),l(()=>document.removeEventListener("keydown",Pa));const at=y.querySelector(".dm-plugin-banner-name");l(M.effect(()=>{const e=a.curId.value,t=Ko.value,o=a.blockTab.value||a.compTab.value,s=t==="block"?"Blocks":t==="component"?"Components":"Pages",n=t==="block"||t==="component"?o:e===null?"Editor":fn(!!e,p.title.value,e);if(!c()||(document.title=`${n} - Domma CMS`,!at))return;const r=at.querySelector(".dm-plugin-banner-parent"),i=at.lastChild;r&&i&&i.nodeType===3&&at.querySelector(".dm-plugin-banner-sep")?(r.textContent=s,i.textContent=n):at.textContent=`${s} \u203A ${n}`})),rt(d,{id:"page-editor-back",icon:"arrow-left",title:"Back to Pages",onClick:()=>R.navigate("/pages")}),rt(d,{id:"page-editor-help",icon:"help-circle",title:"Editor reference",onClick:()=>La()});const Ks=rt(d,{id:"page-editor-view",icon:"external-link",title:"View on the site",onClick:()=>{f?.isEdit&&window.open(p.urlPath.peek().trim()||f.id,"_blank","noopener")}}),Oa=()=>{if(!f?.isEdit||a.saving.peek()||!c())return;const e=f,t=K(),o=Mo({...t,urlPath:e.id});if(o.length){E.toast(o[0].text,{type:"warning"});return}const s=sn({page:e.page,frontmatter:{...Kt(t,e,Ut(t)),adminTags:t.adminTags},body:t.body,source:e.id,pages:$,onCloned:async n=>{c()&&(await da(),c()&&ie(n))}});l(()=>s.close())},yo=rt(d,{id:"page-editor-clone",icon:"copy",title:"Clone page",onClick:Oa});l(M.effect(()=>{const e=Uo.value;for(const t of[Ks,yo])t&&(t.disabled=!e);yo&&(yo.title=e?"Clone page":"Open a saved page to clone it")})),rt(d,{id:"page-editor-settings",icon:"settings",title:"Editor settings - font, size, spacing",onClick:()=>{Vo=tr(zo)}}),l(Da(Ea,{position:"bottom"})),Domma.icons.scan();let Ra={ids:[],active:""};try{Ra=Vn(S.get(as))}catch{}const st=Ia(u),nt=Un(st,Ra,history.state?.pe===1);x=nt.ids.filter(e=>{const t=I(e);return t.kind==="page"||(t.kind==="component"?se:pe).has(t.id)}),la=!1;for(const e of x){const t=I(e);t.kind==="page"&&pa(t.id)}V(),z(),yt();for(const e of Oe)oo(e,{quiet:!0}).then(()=>{c()&&(V(),z())});for(const e of x){const t=I(e);t.kind==="component"&&e!==nt.active&&Te(t)&&po(t.id,{quiet:!0}).then(()=>{c()&&(V(),z())})}const Aa=st&&!st.id&&st.kind!=="page";nt.active&&x.includes(nt.active)?await Tt(nt.active):x.length?await Tt(x[0]):Aa||ee(),Aa&&c()&&(await(st.kind==="block"?At:tt)({project:jt(u)||""}),c()&&ee())}};function Lo(d,u){!d||typeof u!="string"||!u||[...d.options].some(v=>v.value===u)||(d.append(Object.assign(document.createElement("option"),{value:u,textContent:`${u} (custom)`})),d.value=u)}function mr(d,u,v){d.replaceChildren(...String(u).split(`
123
- `).map((l,b)=>{const h=document.createElement("span");h.className="editor-line-number-row";const w=document.createElement("span");w.className="fold-toggle fold-toggle--empty";const y=document.createElement("span");return y.className="editor-line-num",y.textContent=String(b+1),h.append(w,y),h})),d.scrollTop=v.scrollTop}function br(d,u,v){if(!d||!u)return;const l=15,b=85,h=c=>Math.min(b,Math.max(l,c));let w=h(parseFloat(Se().splitRatio)||50);const y=c=>{d.style.setProperty("--dm-editor-split",w+"%"),u.setAttribute("aria-valuenow",String(Math.round(w))),c&&dt("splitRatio",w)};y(!1);const C=c=>{const P=d.getBoundingClientRect();P.width&&(w=h((c.clientX-P.left)/P.width*100)),y(!1)},N=()=>{document.removeEventListener("pointermove",C),document.removeEventListener("pointerup",N),d.classList.remove("is-resizing"),u.classList.remove("is-dragging"),y(!0)};v(N),u.addEventListener("pointerdown",c=>{window.matchMedia("(max-width: 768px)").matches||(c.preventDefault(),d.classList.add("is-resizing"),u.classList.add("is-dragging"),document.addEventListener("pointermove",C),document.addEventListener("pointerup",N))}),u.addEventListener("dblclick",()=>{w=50,y(!0)}),u.addEventListener("keydown",c=>{const P=c.shiftKey?10:2;let H=w;if(c.key==="ArrowLeft")H=w-P;else if(c.key==="ArrowRight")H=w+P;else if(c.key==="Home")H=l;else if(c.key==="End")H=b;else if(c.key==="Enter"||c.key===" ")H=50;else return;c.preventDefault(),w=h(H),y(!0)})}async function vr(d){const u=d.selectionStart,v=d.selectionEnd,l=await an({title:"Upload or insert an image"});if(!l||!d.isConnected)return;let b=l.split("/").pop().split("?")[0];try{b=decodeURIComponent(b)}catch{}const h=b.replace(/[\\[\]]/g,"\\$&"),w=l.replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/ /g,"%20");d.setSelectionRange(u,v),Js(d,`![${h}](${w})`),d.dispatchEvent(new Event("input",{bubbles:!0}))}function gr(d,u,v){const l=Object.entries(u||{});return d?(d.replaceChildren(...l.map(([b,h])=>new Option(h?.label||b,b))),l.some(([b])=>b===v)?v:l[0]?.[0]||v):v}async function kr(d,u){if(!d)return;let v=[];try{const C=await k.get("/collections/roles/entries?limit=100");v=Array.isArray(C)?C:C?.entries||[]}catch{}const l=v.map(C=>({name:C.data?.name,label:C.data?.label||C.data?.name,level:C.data?.level??99})).filter(C=>C.name&&C.level!==0).sort((C,N)=>N.level-C.level),b=(C,N)=>Object.assign(document.createElement("option"),{value:C,textContent:N}),h=()=>{typeof u=="string"&&u&&![...d.options].some(C=>C.value===u)&&d.append(b(u,`${u} (custom)`))};if(!l.length)return h();const w=document.createElement("optgroup");w.label="Role and above",l.forEach(C=>w.append(b(C.name,`${C.label} and above`)));const y=document.createElement("optgroup");y.label="Role only",l.forEach(C=>y.append(b(`=${C.name}`,`${C.label} only`))),d.replaceChildren(b("public","Public - everyone"),w,y,b("private","Private - super-admin only")),h()}
122
+ h3 { font-size: 1.25rem; } */`,onChange:e=>ka("css",e)}),l(()=>{oe.destroy(),Ge.destroy()});for(const e of It)ce[e]=No(N(`.pe-comp-edit[data-part="${e}"]`),{ariaLabel:`Component <${e}> block`,language:{template:"html",props:"json",script:"js",style:"css"}[e],onChange:t=>Fs(e,t)});l(()=>{for(const e of It)ce[e].destroy()});const we=N(".pe-tabs-list"),At=e=>e.target.closest(".pe-tab")?.dataset.id||"",Ta=e=>{const t=At(e);t&&(e.target.closest(".pe-tab-close")?Je(t):Mt(t))},Ca=e=>{e.button===1&&At(e)&&(e.preventDefault(),Je(At(e)))},xa=e=>{const t=At(e);!t||e.target.closest(".pe-tab-close")||(e.key==="Enter"||e.key===" "?(e.preventDefault(),Mt(t)):e.key==="Delete"?(e.preventDefault(),Je(t)):e.key==="F10"&&e.shiftKey&&(e.preventDefault(),Se(e.target.closest(".pe-tab"))))};we.addEventListener("click",Ta),we.addEventListener("auxclick",Ca),we.addEventListener("keydown",xa),l(()=>{we.removeEventListener("click",Ta),we.removeEventListener("auxclick",Ca),we.removeEventListener("keydown",xa)});const vo=e=>{for(const t of e)Je(t)},Ks=async e=>{const t=I(e);if(t?.id)if(t.kind==="page")o.active.peek()!==e&&await ie(t.id),c()&&u?.id===t.id&&$a();else if(t.kind==="block"){const s=($.get(t.id)||await oo(t.id,{quiet:!0}))?.form||Ua(await k.blocks.get(t.id).catch(()=>null));c()&&$t({seed:xo(t.id,[...pe]),content:s.content,css:s.css})}else{const s=(B.get(t.id)||await po(t.id,{quiet:!0}))?.form.source??(await k.components.get(t.id).catch(()=>null))?.source??"";c()&&tt({name:xo(t.id,[...se]),source:s})}},Us=e=>{const t=I(e);if(!t||!x.includes(e))return[];const a=x.indexOf(e),s=$e.get(e)?.label.peek()||t.id,r=n=>{x=Vn(x,e,n),V()};return[{header:s},{label:"Close",icon:"x",action:()=>Je(e)},{label:"Close others",icon:"x-circle",disabled:x.length<2,action:()=>vo(Ao(x,e,"others"))},{label:"Close to the right",icon:"chevrons-right",disabled:a>=x.length-1,action:()=>vo(Ao(x,e,"right"))},{label:"Close all",icon:"layers",action:()=>vo(Ao(x,e,"all"))},{type:"divider"},{label:"Move left",icon:"arrow-left",disabled:a===0,action:()=>r(-1)},{label:"Move right",icon:"arrow-right",disabled:a>=x.length-1,action:()=>r(1)},{type:"divider"},{label:t.kind==="page"?"Clone page\u2026":`Duplicate ${t.kind}\u2026`,icon:"copy",disabled:!t.id,action:()=>Ks(e)},{label:"Copy link",icon:"link",disabled:!t.id,action:()=>Le(`${location.origin}${location.pathname}#${Ya(t)}`,"Link")}]};{const e=E.contextMenu(we,{cursor:"glyph",match:".pe-tab",inherit:!1,density:"compact",items:t=>Us(t.dataset.id)});l(()=>e?.destroy?.())}const Pa=N(".pe-ex-scroll"),Oa=e=>{const t=e.target.closest(".pe-ex-item"),a=t?.dataset.id||"";if(!a)return;const s=a.indexOf(":"),r=a.slice(0,s),n=a.slice(s+1),i=()=>{ge.has(n)?ge.delete(n):ge.add(n);try{S.set(rs,[...ge])}catch{}z()};if(r==="folder"){i();return}if(r==="page"&&e.target.closest(".pe-ex-chev")&&t.classList.contains("is-folder")){i();return}r==="page"?ie(n):r==="block"?Ne(n):r==="component"&&et(n)};Pa.addEventListener("click",Oa),l(()=>Pa.removeEventListener("click",Oa));let go=null;l(()=>clearTimeout(go)),F.addEventListener("input",()=>{G.refresh(),ne.scrollTop=F.scrollTop,!xe&&(clearTimeout(go),go=setTimeout(ea,120),U())}),F.addEventListener("scroll",()=>{ne.scrollTop=F.scrollTop}),ue.addEventListener("scroll",()=>{ne.scrollTop=ue.scrollTop}),l(M.effect(()=>{for(const e of Object.keys(p))e!=="css"&&e!=="cssOpened"&&p[e].value;o.bodyRev.value,U()})),l(M.effect(()=>{o.bodyRev.value,o.docRev.value;const e=u?bt():"";gt=u?Tn(e,mt.map(t=>t.name),mt.filter(t=>t.shortTag).map(t=>t.name)):[],Jo=u?zn(e,se):[],ht(),z()})),l(M.effect(()=>{o.blockRev.value,o.compRev.value,o.docRev.value,o.active.value,o.filter.value,p.title.value,V(),z()})),l(M.effect(()=>{ne.hidden=!!(o.blockTab.value||o.compTab.value)}));let ko=null;l(()=>clearTimeout(ko)),l(M.effect(()=>{const e=p.css.value;o.cssMode.value&&(zt.paint(),kr(ne,e,ue),clearTimeout(ko),ko=setTimeout(Go,400))}));{const e=N(".pe-version-list"),t=E.contextMenu(e,{cursor:"glyph",match:"tr[data-file]",inherit:!1,density:"compact",items:a=>{const s=Gt(a.dataset.file);return s?[{header:es(s)},{label:"Show beside the editor",icon:"columns",action:()=>Xt(s)},{label:"Restore this version\u2026",icon:"rotate-ccw",action:()=>la(s.filename)},{type:"divider"},{label:"Delete\u2026",icon:"trash",danger:!0,action:()=>Os(s)}]:[]}});l(()=>t?.destroy?.()),e.addEventListener("click",a=>{const s=a.target.closest("tr[data-file]");if(!s||a.target.matches("input"))return;const r=a.target.closest(".pe-kebab");if(r){Se(r);return}const n=Gt(s.dataset.file);n&&Xt(n)}),e.addEventListener("keydown",a=>{const s=a.target.closest("tr[data-file]");if(s){if(a.key==="F10"&&a.shiftKey)a.preventDefault(),Se(s);else if(a.key==="Enter"&&a.target===s){const r=Gt(s.dataset.file);r&&Xt(r)}}})}hr(N("#editor-body"),N("#editor-divider"),l),en({image:e=>(o.cssMode.value=!1,yr(e)),scribe:e=>an(e),card:e=>Ws.open(e,{onMediaBrowse:()=>k.media.list().catch(()=>[])}),timeline:e=>Ys.open(e,{onPreview:async t=>{try{return await k.pages.preview(t)}catch{return null}},onListCollections:()=>k.collections.list().catch(()=>[]),onGetCollection:t=>k.collections.get(t).catch(()=>null)})}),Jt.onEffects(e=>Qs.open(e)),Jt.onHelp(()=>Ba()),Jt.onTutorial(e=>sn(e));const Ra=e=>{if(!c())return;const t=Gn(e);t&&(e.preventDefault(),t==="save"?to(!1):t==="layout"?bo.setMode(Hn(o.viewMode.peek())):bo.toggleExplorer())};document.addEventListener("keydown",Ra),l(()=>document.removeEventListener("keydown",Ra));const at=h.querySelector(".dm-plugin-banner-name");l(M.effect(()=>{const e=o.curId.value,t=Uo.value,a=o.blockTab.value||o.compTab.value,s=t==="block"?"Blocks":t==="component"?"Components":"Pages",r=t==="block"||t==="component"?a:e===null?"Editor":gn(!!e,p.title.value,e);if(!c()||(document.title=`${r} - Domma CMS`,!at))return;const n=at.querySelector(".dm-plugin-banner-parent"),i=at.lastChild;n&&i&&i.nodeType===3&&at.querySelector(".dm-plugin-banner-sep")?(n.textContent=s,i.textContent=r):at.textContent=`${s} \u203A ${r}`})),rt(d,{id:"page-editor-back",icon:"arrow-left",title:"Back to Pages",onClick:()=>R.navigate("/pages")}),rt(d,{id:"page-editor-help",icon:"help-circle",title:"Editor reference",onClick:()=>Ba()});const _s=rt(d,{id:"page-editor-view",icon:"external-link",title:"View on the site",onClick:()=>{u?.isEdit&&window.open(p.urlPath.peek().trim()||u.id,"_blank","noopener")}}),$a=()=>{if(!u?.isEdit||o.saving.peek()||!c())return;const e=u,t=K(),a=Mo({...t,urlPath:e.id});if(a.length){E.toast(a[0].text,{type:"warning"});return}const s=cn({page:e.page,frontmatter:{...Kt(t,e,Ut(t)),adminTags:t.adminTags},body:t.body,source:e.id,pages:D,onCloned:async r=>{c()&&(await ua(),c()&&ie(r))}});l(()=>s.close())},ho=rt(d,{id:"page-editor-clone",icon:"copy",title:"Clone page",onClick:$a});l(M.effect(()=>{const e=_o.value;for(const t of[_s,ho])t&&(t.disabled=!e);ho&&(ho.title=e?"Clone page":"Open a saved page to clone it")})),rt(d,{id:"page-editor-settings",icon:"settings",title:"Editor settings - font, size, spacing",onClick:()=>{Ho=nr(Vo)}}),l(La(Ma,{position:"bottom"})),Domma.icons.scan();let Aa={ids:[],active:""};try{Aa=_n(S.get(ns))}catch{}const st=qa(m),nt=Jn(st,Aa,history.state?.pe===1);x=nt.ids.filter(e=>{const t=I(e);return t.kind==="page"||(t.kind==="component"?se:pe).has(t.id)}),ca=!1;for(const e of x){const t=I(e);t.kind==="page"&&ma(t.id)}V(),z(),ht();for(const e of Oe)oo(e,{quiet:!0}).then(()=>{c()&&(V(),z())});for(const e of x){const t=I(e);t.kind==="component"&&e!==nt.active&&Me(t)&&po(t.id,{quiet:!0}).then(()=>{c()&&(V(),z())})}const Da=st&&!st.id&&st.kind!=="page";nt.active&&x.includes(nt.active)?await Mt(nt.active):x.length?await Mt(x[0]):Da||ee(),Da&&c()&&(await(st.kind==="block"?$t:tt)({project:jt(m)||""}),c()&&ee())}};function Lo(d,m){!d||typeof m!="string"||!m||[...d.options].some(v=>v.value===m)||(d.append(Object.assign(document.createElement("option"),{value:m,textContent:`${m} (custom)`})),d.value=m)}function kr(d,m,v){d.replaceChildren(...String(m).split(`
123
+ `).map((l,b)=>{const y=document.createElement("span");y.className="editor-line-number-row";const w=document.createElement("span");w.className="fold-toggle fold-toggle--empty";const h=document.createElement("span");return h.className="editor-line-num",h.textContent=String(b+1),y.append(w,h),y})),d.scrollTop=v.scrollTop}function hr(d,m,v){if(!d||!m)return;const l=15,b=85,y=c=>Math.min(b,Math.max(l,c));let w=y(parseFloat(Te().splitRatio)||50);const h=c=>{d.style.setProperty("--dm-editor-split",w+"%"),m.setAttribute("aria-valuenow",String(Math.round(w))),c&&dt("splitRatio",w)};h(!1);const C=c=>{const P=d.getBoundingClientRect();P.width&&(w=y((c.clientX-P.left)/P.width*100)),h(!1)},L=()=>{document.removeEventListener("pointermove",C),document.removeEventListener("pointerup",L),d.classList.remove("is-resizing"),m.classList.remove("is-dragging"),h(!0)};v(L),m.addEventListener("pointerdown",c=>{window.matchMedia("(max-width: 768px)").matches||(c.preventDefault(),d.classList.add("is-resizing"),m.classList.add("is-dragging"),document.addEventListener("pointermove",C),document.addEventListener("pointerup",L))}),m.addEventListener("dblclick",()=>{w=50,h(!0)}),m.addEventListener("keydown",c=>{const P=c.shiftKey?10:2;let H=w;if(c.key==="ArrowLeft")H=w-P;else if(c.key==="ArrowRight")H=w+P;else if(c.key==="Home")H=l;else if(c.key==="End")H=b;else if(c.key==="Enter"||c.key===" ")H=50;else return;c.preventDefault(),w=y(H),h(!0)})}async function yr(d){const m=d.selectionStart,v=d.selectionEnd,l=await ln({title:"Upload or insert an image"});if(!l||!d.isConnected)return;let b=l.split("/").pop().split("?")[0];try{b=decodeURIComponent(b)}catch{}const y=b.replace(/[\\[\]]/g,"\\$&"),w=l.replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/ /g,"%20");d.setSelectionRange(m,v),Xs(d,`![${y}](${w})`),d.dispatchEvent(new Event("input",{bubbles:!0}))}function wr(d,m,v){const l=Object.entries(m||{});return d?(d.replaceChildren(...l.map(([b,y])=>new Option(y?.label||b,b))),l.some(([b])=>b===v)?v:l[0]?.[0]||v):v}async function Er(d,m){if(!d)return;let v=[];try{const C=await k.get("/collections/roles/entries?limit=100");v=Array.isArray(C)?C:C?.entries||[]}catch{}const l=v.map(C=>({name:C.data?.name,label:C.data?.label||C.data?.name,level:C.data?.level??99})).filter(C=>C.name&&C.level!==0).sort((C,L)=>L.level-C.level),b=(C,L)=>Object.assign(document.createElement("option"),{value:C,textContent:L}),y=()=>{typeof m=="string"&&m&&![...d.options].some(C=>C.value===m)&&d.append(b(m,`${m} (custom)`))};if(!l.length)return y();const w=document.createElement("optgroup");w.label="Role and above",l.forEach(C=>w.append(b(C.name,`${C.label} and above`)));const h=document.createElement("optgroup");h.label="Role only",l.forEach(C=>h.append(b(`=${C.name}`,`${C.label} only`))),d.replaceChildren(b("public","Public - everyone"),w,h,b("private","Private - super-admin only")),y()}
package/docs/cache.md CHANGED
@@ -94,6 +94,10 @@ Before the response cache is asked, the route decides whether the viewer may see
94
94
 
95
95
  `services/htmlResponse.js` `sendHtml()`: a page that came from the response cache (`{cached: true}` - the page route and the docs route) is also kept compressed, one entry per encoding under `enc:<br|gzip>:<sha1 of the HTML>`. Content-addressed, so never stale and never invalidated; bounded by the cache's LRU and TTL. The negotiation and parameters match @fastify/compress (brotli quality 4), which steps aside when `content-encoding` is already set. A 404, a draft or a per-viewer page is not kept and goes out through @fastify/compress as before.
96
96
 
97
+ ## Static assets
98
+
99
+ `services/assetCache.js`: an `onSend` hook added before @fastify/compress's. When @fastify/static has answered a GET with a 200 for a compressible type (its stream carries the file's path), the hook swaps the stream for a buffer kept in memory - the bytes as sent, per encoding (`br`, `gzip`, `identity`), keyed on the path and checked against the file's mtime and size on every request. @fastify/static still resolves the path, sets the type, cache-control, ETag and Last-Modified and answers 304s, HEADs and 404s. Files under 1 KB or over 2 MB, Range requests and non-text types are left to stream. Bounded at 64 MB (LRU by bytes); on while the response cache is on; emptied on a clear or re-init. `GET /api/cache/status` reports it as `assets`.
100
+
97
101
  ## Thundering-herd safety
98
102
 
99
103
  If three concurrent requests hit a cold key, the producer runs exactly once - the other two await the first's result via an in-flight promise map. This matters for popular pages on cache cold-start: without coalescing, N parallel requests trigger N parallel renders, each writing the same value back to the cache.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "1.8.27",
3
+ "version": "1.8.28",
4
4
  "description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
5
5
  "type": "module",
6
6
  "main": "server/server.js",
@@ -12,6 +12,7 @@ import path from 'path';
12
12
  import {fileURLToPath} from 'url';
13
13
  import * as cache from '../../services/cache/index.js';
14
14
  import {pageMemoStats} from '../../services/content.js';
15
+ import {assetCacheStats} from '../../services/assetCache.js';
15
16
  import {authenticate, requireAdmin} from '../../middleware/auth.js';
16
17
 
17
18
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -38,7 +39,7 @@ async function writeCacheConfig(cfg) {
38
39
  export async function cacheRoutes(fastify) {
39
40
  const adminOnly = {preHandler: [authenticate, requireAdmin]};
40
41
 
41
- fastify.get('/cache/status', adminOnly, async () => ({...cache.getStats(), pageMemo: pageMemoStats()}));
42
+ fastify.get('/cache/status', adminOnly, async () => ({...cache.getStats(), pageMemo: pageMemoStats(), assets: assetCacheStats()}));
42
43
 
43
44
  fastify.get('/cache/keys', adminOnly, async () => ({entries: cache.listEntries()}));
44
45
 
package/server/server.js CHANGED
@@ -14,6 +14,7 @@ import multipart from '@fastify/multipart';
14
14
  import rateLimit from '@fastify/rate-limit';
15
15
  import helmet from '@fastify/helmet';
16
16
  import compress from '@fastify/compress';
17
+ import {registerAssetCache} from './services/assetCache.js';
17
18
  import path from 'path';
18
19
  import fs from 'fs/promises';
19
20
  import {fileURLToPath} from 'url';
@@ -159,6 +160,12 @@ if (publicCorsPrefixes.length) {
159
160
  await app.register(multipart, { limits: { fileSize: serverConfig.uploads.maxFileSize } });
160
161
  await app.register(rateLimit, rateLimitOptions());
161
162
 
163
+ // Text assets (scripts, stylesheets, SVG, JSON) from memory, compressed once
164
+ // per encoding - services/assetCache.js. Its onSend hook must be added BEFORE
165
+ // compress registers its own, so compress finds content-encoding set and
166
+ // steps aside.
167
+ registerAssetCache(app);
168
+
162
169
  // Negotiates brotli/gzip/deflate per Accept-Encoding. Skips already-compressed
163
170
  // content types (images, fonts, video) and responses under threshold.
164
171
  await app.register(compress, {
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Static assets from memory, compressed once.
3
+ *
4
+ * @fastify/static streams every stylesheet and script from disk on every
5
+ * request - stat, open, read, close - and @fastify/compress then brotli's the
6
+ * stream for every visitor. On a busy site that was a whole core for about a
7
+ * thousand assets a second, all of it repeated work: the files do not change
8
+ * between requests. This hook, placed BEFORE @fastify/compress's, keeps the
9
+ * served form of a text asset in memory - the bytes as sent, per encoding
10
+ * (br, gzip, or identity for a client that accepts none) - and answers the
11
+ * next request with the buffer. The file is still stat'ed each time, so an
12
+ * edit (a new mtime or size) is seen at once, and @fastify/static still does
13
+ * everything else: it resolved the path, set the type, the cache-control the
14
+ * mount asked for, the ETag and Last-Modified, and answered the 304s, the
15
+ * HEADs and the 404s before this runs. The hook only swaps the stream for a
16
+ * buffer, so a file @fastify/static would not have served is never served
17
+ * here either.
18
+ *
19
+ * Only what is worth it: GET, a 200, a compressible type (the same rule
20
+ * @fastify/compress uses), no Range, a file between 1 KB and `maxFileBytes`.
21
+ * Images, fonts and media stream as before. On while the response cache is
22
+ * on, like the page memo - the admin's cache switch means all of it - and
23
+ * emptied when the cache is cleared or re-initialised. Bounded by bytes, not
24
+ * entries, with the least recently served evicted first.
25
+ */
26
+ import fs from 'node:fs/promises';
27
+ import * as cache from './cache/index.js';
28
+ import {encode, pickEncoding} from './htmlResponse.js';
29
+
30
+ /** @fastify/compress's own rule for what is text enough to compress. */
31
+ const COMPRESSIBLE = /^text\/(?!event-stream)|(?:\+|\/)json(?:;|$)|(?:\+|\/)text(?:;|$)|(?:\+|\/)xml(?:;|$)|javascript(?:;|$)/u;
32
+
33
+ const DEFAULTS = {maxBytes: 64 * 1024 * 1024, maxFileBytes: 2 * 1024 * 1024, minBytes: 1024};
34
+
35
+ /** @type {Map<string, {mtimeMs: number, size: number, body: Buffer}>} key = encoding + NUL + path */
36
+ const _entries = new Map();
37
+ /** @type {Map<string, Promise<Buffer>>} */
38
+ const _inflight = new Map();
39
+ let _bytes = 0;
40
+ let _limits = {...DEFAULTS};
41
+ const _stats = {hits: 0, misses: 0, evictions: 0};
42
+
43
+ cache.onInvalidate((reason) => {
44
+ // Tags name pages, collections and blocks, never files; a file's own
45
+ // change is seen through its mtime. A clear, a re-init or the cache going
46
+ // off empties this too.
47
+ if (reason !== 'tags') clearAssetCache();
48
+ });
49
+
50
+ export function clearAssetCache() {
51
+ _entries.clear();
52
+ _inflight.clear();
53
+ _bytes = 0;
54
+ }
55
+
56
+ export function assetCacheStats() {
57
+ return {entries: _entries.size, bytes: _bytes, maxBytes: _limits.maxBytes, hits: _stats.hits, misses: _stats.misses, evictions: _stats.evictions};
58
+ }
59
+
60
+ export function _resetAssetCacheForTests() {
61
+ clearAssetCache();
62
+ _stats.hits = 0;
63
+ _stats.misses = 0;
64
+ _stats.evictions = 0;
65
+ }
66
+
67
+ /**
68
+ * Is this a stream @fastify/static made for a file? @fastify/send opens the
69
+ * file with fs.createReadStream, which carries the path it opened.
70
+ */
71
+ function fileOf(payload) {
72
+ return payload && typeof payload.pipe === 'function' && typeof payload.path === 'string' ? payload.path : null;
73
+ }
74
+
75
+ async function bodyFor(file, st, encoding) {
76
+ const key = `${encoding}\0${file}`;
77
+ const kept = _entries.get(key);
78
+ if (kept && kept.mtimeMs === st.mtimeMs && kept.size === st.size) {
79
+ _entries.delete(key);
80
+ _entries.set(key, kept); // most recently served last
81
+ _stats.hits++;
82
+ return kept.body;
83
+ }
84
+ if (_inflight.has(key)) return _inflight.get(key);
85
+ _stats.misses++;
86
+ const making = (async () => {
87
+ try {
88
+ const raw = await fs.readFile(file);
89
+ const body = encoding === 'identity' ? raw : await encode(raw, encoding);
90
+ if (kept) { _bytes -= kept.body.length; _entries.delete(key); }
91
+ _entries.set(key, {mtimeMs: st.mtimeMs, size: st.size, body});
92
+ _bytes += body.length;
93
+ while (_bytes > _limits.maxBytes && _entries.size > 1) {
94
+ const [oldestKey, oldest] = _entries.entries().next().value;
95
+ _entries.delete(oldestKey);
96
+ _bytes -= oldest.body.length;
97
+ _stats.evictions++;
98
+ }
99
+ return body;
100
+ } finally {
101
+ _inflight.delete(key);
102
+ }
103
+ })();
104
+ _inflight.set(key, making);
105
+ return making;
106
+ }
107
+
108
+ /**
109
+ * Add the onSend hook. Call BEFORE registering @fastify/compress, so this
110
+ * runs first and compress sees `content-encoding` already set and steps aside.
111
+ *
112
+ * @param {import('fastify').FastifyInstance} app
113
+ * @param {{maxBytes?: number, maxFileBytes?: number, minBytes?: number}} [limits]
114
+ */
115
+ export function registerAssetCache(app, limits = {}) {
116
+ _limits = {...DEFAULTS, ...limits};
117
+ app.addHook('onSend', async (request, reply, payload) => {
118
+ if (request.method !== 'GET' || reply.statusCode !== 200 || !cache.isEnabled()) return payload;
119
+ const file = fileOf(payload);
120
+ if (!file || request.headers.range) return payload;
121
+ if (!COMPRESSIBLE.test(String(reply.getHeader('content-type') || ''))) return payload;
122
+ if (reply.getHeader('content-encoding')) return payload; // a precompressed file @fastify/static served itself
123
+
124
+ let st;
125
+ try { st = await fs.stat(file); } catch { return payload; }
126
+ if (!st.isFile() || st.size < _limits.minBytes || st.size > _limits.maxFileBytes) return payload;
127
+
128
+ const encoding = pickEncoding(request.headers['accept-encoding']) || 'identity';
129
+ let body;
130
+ try { body = await bodyFor(file, st, encoding); } catch { return payload; }
131
+
132
+ payload.destroy(); // the stream @fastify/static opened: release its file descriptor
133
+ reply.removeHeader('accept-ranges');
134
+ reply.header('content-length', body.length);
135
+ if (encoding !== 'identity') {
136
+ reply.header('content-encoding', encoding);
137
+ reply.header('vary', 'accept-encoding');
138
+ }
139
+ return body;
140
+ });
141
+ }
@@ -72,12 +72,12 @@ export function pickEncoding(header, offered = ENCODINGS) {
72
72
  /**
73
73
  * The compressed body, the way @fastify/compress would have made it.
74
74
  *
75
- * @param {string} html
75
+ * @param {string|Buffer} input
76
76
  * @param {string} encoding - 'br' | 'gzip'
77
77
  * @returns {Promise<Buffer>}
78
78
  */
79
- export function encodeHtml(html, encoding) {
80
- const input = Buffer.from(html, 'utf8');
79
+ export function encode(input, encoding) {
80
+ if (!Buffer.isBuffer(input)) input = Buffer.from(String(input), 'utf8');
81
81
  if (encoding === 'br') {
82
82
  return brotli(input, {params: {
83
83
  [zlib.constants.BROTLI_PARAM_QUALITY]: 4,
@@ -88,6 +88,9 @@ export function encodeHtml(html, encoding) {
88
88
  throw new Error(`Unknown encoding: ${encoding}`);
89
89
  }
90
90
 
91
+ /** encode() for a page's HTML. */
92
+ export const encodeHtml = encode;
93
+
91
94
  /** The validator's content part: the same hash whatever the encoding. */
92
95
  export function htmlHash(html) {
93
96
  return createHash('sha1').update(html).digest('base64');
@@ -687,6 +687,10 @@ function buildSeoMeta({page, site, baseUrl, title, description, image}) {
687
687
  });
688
688
  }
689
689
 
690
+ // The page's own structured data (frontmatter `seo.schema` - the editor's
691
+ // SEO tab): one object or a list, each emitted as its own block.
692
+ jsonLd.push(...schemaBlocks(page.seo?.schema));
693
+
690
694
  return {
691
695
  title,
692
696
  description,
@@ -703,6 +707,32 @@ function buildSeoMeta({page, site, baseUrl, title, description, image}) {
703
707
  };
704
708
  }
705
709
 
710
+ /**
711
+ * The JSON-LD blocks a page declares in `seo.schema`: an object, a list of
712
+ * them, or a JSON string of either (a hand-written frontmatter may hold it as
713
+ * text). Anything that is not a plain object with a string `@type` is dropped
714
+ * rather than emitted broken, and `@context` defaults to schema.org, which is
715
+ * what every example and validator expects. Pure.
716
+ *
717
+ * @param {unknown} value
718
+ * @returns {object[]}
719
+ */
720
+ export function schemaBlocks(value) {
721
+ let v = value;
722
+ if (typeof v === 'string') {
723
+ if (!v.trim()) return [];
724
+ try { v = JSON.parse(v); } catch { return []; }
725
+ }
726
+ const list = Array.isArray(v) ? v : [v];
727
+ const out = [];
728
+ for (const item of list) {
729
+ if (!item || typeof item !== 'object' || Array.isArray(item)) continue;
730
+ if (typeof item['@type'] !== 'string' || !item['@type'].trim()) continue;
731
+ out.push(item['@context'] ? item : {'@context': 'https://schema.org', ...item});
732
+ }
733
+ return out;
734
+ }
735
+
706
736
  /** An absolute URL for og:image and JSON-LD: crawlers ignore relative ones. */
707
737
  function absoluteUrl(url, origin) {
708
738
  const u = String(url || '').trim();