viteboard 0.1.9 → 0.2.0

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.
@@ -0,0 +1 @@
1
+ :root{--brand-100: #ffffff;--brand-200: #4dabf7;--brand-300: #0078d4;--brand-400: #495057;--brand-500: #0078d4;--brand-600: #005a9e;--neutral-100: #f3f2f1;--neutral-200: #eaeaea;--neutral-300: #d1d1d1;--neutral-400: #c8c8c8;--neutral-500: #6e6e6e;--neutral-600: #7a7a7a;--neutral-700: #495057;--neutral-800: #4a4a4a;--neutral-900: #0f0f0f;--neutral-000: #ffffff;--success-main: #28a745;--success-bg: #e6f4ea;--success-text: #1e7e34;--warning-main: #dc2626;--warning-bg: #fef2f2;--warning-text: #ffffff;--error-main: #cd0000;--error-bg: #ffe5e5;--error-text: #b91c1c;--info-main: #1080a6;--info-bg: #e0f7fa;--info-text: #0369a1;--bg-canvas: var(--neutral-100);--bg-toolbar: rgba(255, 255, 255, .92);--bg-panel: var(--brand-100);--bg-hover: rgba(0, 0, 0, .05);--bg-active: rgba(0, 120, 212, .12);--surface-raised: var(--brand-100);--accent: var(--neutral-500);--accent-quiet: rgba(110, 110, 110, .14);--accent-soft: rgba(110, 110, 110, .08);--accent-secondary: var(--brand-300);--accent-secondary-quiet: rgba(0, 120, 212, .13);--focus-glow: rgba(0, 120, 212, .26);--text-primary: var(--neutral-900);--text-secondary: #6e6e6e;--border-subtle: rgba(0, 0, 0, .08);--selection: var(--accent);--shadow-soft: 0 4px 24px rgba(0, 0, 0, .08), 0 1px 3px rgba(0, 0, 0, .06);--shadow-modal: 0 12px 48px rgba(0, 0, 0, .18);--grid-dot: rgba(110, 110, 110, .16);--danger: var(--error-main)}[data-theme=dark]{--bg-canvas: #0d0d0d;--bg-toolbar: rgba(20, 22, 23, .94);--bg-panel: #1a1a1a;--bg-hover: rgba(255, 255, 255, .055);--bg-active: rgba(97, 218, 251, .12);--surface-raised: #212121;--accent: #61dafb;--accent-quiet: rgba(97, 218, 251, .14);--accent-soft: rgba(97, 218, 251, .075);--accent-secondary: #73d0f4;--accent-secondary-quiet: rgba(97, 218, 251, .18);--focus-glow: rgba(97, 218, 251, .28);--text-primary: #f6f6f3;--text-secondary: #a7aaa8;--border-subtle: rgba(255, 255, 255, .09);--selection: var(--accent);--shadow-soft: 0 16px 46px rgba(0, 0, 0, .52), 0 1px 1px rgba(255, 255, 255, .03);--shadow-modal: 0 22px 70px rgba(0, 0, 0, .72);--grid-dot: rgba(97, 218, 251, .1)}*{margin:0;padding:0;box-sizing:border-box}html,body{width:100%;height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;background:var(--bg-canvas);color:var(--text-primary)}#app{position:fixed;top:0;right:0;bottom:0;left:0}.canvas-stack{position:absolute;top:0;right:0;bottom:0;left:0}.canvas-stack canvas{position:absolute;top:0;right:0;bottom:0;left:0;display:block}.bottom-tool-cluster{position:fixed;bottom:20px;left:50%;transform:translate(-50%);display:flex;align-items:center;justify-content:center;gap:10px;z-index:125}.toolbar{position:fixed;bottom:20px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:2px;padding:6px;background:var(--bg-toolbar);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border:1px solid var(--border-subtle);border-radius:14px;box-shadow:var(--shadow-soft);z-index:100}.bottom-tool-cluster .toolbar{position:relative;inset:auto;transform:none;z-index:1}.shape-tool-group{position:relative;display:flex}.shape-tool-popover{position:absolute;left:50%;bottom:calc(100% + 12px);transform:translate(-50%) translateY(8px) scale(.96);transform-origin:bottom center;display:grid;grid-template-columns:repeat(3,38px);gap:4px;padding:6px;background:var(--bg-toolbar);border:1px solid var(--border-subtle);border-radius:14px;box-shadow:var(--shadow-soft);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);opacity:0;pointer-events:none;transition:opacity .16s ease,transform .2s cubic-bezier(.2,.8,.2,1)}.shape-tool-group.open .shape-tool-popover{opacity:1;pointer-events:auto;transform:translate(-50%) translateY(0) scale(1)}.shape-option-btn{width:38px;height:38px}.shape-tool-popover:after{content:"";position:absolute;left:50%;bottom:-5px;width:10px;height:10px;transform:translate(-50%) rotate(45deg);background:var(--bg-toolbar);border-right:1px solid var(--border-subtle);border-bottom:1px solid var(--border-subtle)}.context-tool-popover{position:relative;width:0;height:52px;transform:translate(-14px) scale(.96);transform-origin:left center;display:flex;align-items:center;gap:8px;padding:6px 0;background:var(--bg-toolbar);border:1px solid transparent;border-radius:999px;box-shadow:var(--shadow-soft);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);opacity:0;overflow:hidden;pointer-events:none;z-index:0;transition:width .26s cubic-bezier(.2,.8,.2,1),padding .26s cubic-bezier(.2,.8,.2,1),opacity .18s ease,transform .24s cubic-bezier(.2,.8,.2,1),border-color .18s ease}.context-tool-popover.is-visible{padding:6px 10px;border-color:var(--border-subtle);opacity:1;pointer-events:auto;transform:translate(0) scale(1)}.sticky-color-popover.is-visible{width:286px}.text-size-popover.is-visible{width:300px}.shape-style-popover.is-visible{width:386px}.sticky-color-btn,.shape-fill-btn{width:24px;height:24px;border:1px solid rgba(0,0,0,.16);border-radius:999px;background:var(--swatch);box-shadow:inset 0 0 0 1px #ffffff6b;cursor:pointer;opacity:0;transform:translate(-6px) scale(.88);transition:opacity .16s ease,transform .2s cubic-bezier(.2,.8,.2,1),box-shadow .12s ease,border-color .12s ease}.shape-text-color-btn{flex:0 0 auto;width:32px;height:32px;border:1px solid var(--border-subtle);border-radius:999px;background:var(--surface-raised);color:var(--text-primary);font-size:12px;font-weight:800;cursor:pointer;opacity:0;transform:translate(-6px) scale(.88);transition:opacity .16s ease,transform .2s cubic-bezier(.2,.8,.2,1),box-shadow .12s ease,border-color .12s ease,background .12s ease}.shape-text-color-btn[aria-label="White text"]{background:#fff;color:#252829}.shape-text-color-btn[aria-label="Black text"]{background:#252829;color:#fff}.context-tool-divider{width:1px;height:28px;background:var(--border-subtle);opacity:0;transform:scaleY(.65);transition:opacity .16s ease,transform .2s cubic-bezier(.2,.8,.2,1)}.text-size-btn{flex:1 1 0;min-width:0;height:38px;padding:0 8px;border:1px solid transparent;border-radius:999px;background:transparent;color:var(--text-secondary);font-size:12px;font-weight:750;line-height:1;text-align:center;white-space:nowrap;cursor:pointer;opacity:0;transform:translate(-6px) scale(.88);transition:opacity .16s ease,transform .2s cubic-bezier(.2,.8,.2,1),background .12s ease,color .12s ease,border-color .12s ease}.context-tool-popover.is-visible .sticky-color-btn,.context-tool-popover.is-visible .shape-fill-btn,.context-tool-popover.is-visible .shape-text-color-btn,.context-tool-popover.is-visible .text-size-btn,.context-tool-popover.is-visible .context-tool-divider{opacity:1;transform:translate(0) scale(1)}.context-tool-popover.is-visible>:nth-child(2){transition-delay:15ms}.context-tool-popover.is-visible>:nth-child(3){transition-delay:.03s}.context-tool-popover.is-visible>:nth-child(4){transition-delay:45ms}.context-tool-popover.is-visible>:nth-child(5){transition-delay:.06s}.context-tool-popover.is-visible>:nth-child(6){transition-delay:75ms}.context-tool-popover.is-visible>:nth-child(7){transition-delay:.09s}.context-tool-popover.is-visible>:nth-child(8){transition-delay:.105s}.sticky-color-btn:hover,.shape-fill-btn:hover{transform:translateY(-1px) scale(1.04);box-shadow:inset 0 0 0 1px #ffffff85,0 4px 12px #00000029}.sticky-color-btn:focus-visible,.shape-fill-btn:focus-visible{outline:2px solid var(--selection);outline-offset:3px}.text-size-btn:hover,.text-size-btn:focus-visible{background:var(--bg-hover);color:var(--text-primary);outline:none}.shape-text-color-btn:hover,.shape-text-color-btn:focus-visible{border-color:color-mix(in srgb,var(--selection) 45%,var(--border-subtle));box-shadow:0 4px 12px #00000024;outline:none}.sticky-color-btn.active,.shape-fill-btn.active{border-color:var(--brand-500);box-shadow:inset 0 0 0 1px #ffffff8a,0 0 0 3px #428d3033}.shape-text-color-btn.active{border-color:var(--brand-500);box-shadow:0 0 0 3px #428d3033}.text-size-btn.active{border-color:color-mix(in srgb,var(--selection) 36%,var(--border-subtle));background:var(--bg-active);color:var(--text-primary)}[data-theme=dark] .sticky-color-btn,[data-theme=dark] .shape-fill-btn{border-color:#ffffff2e;box-shadow:inset 0 0 0 1px #00000029}.layer-menu{position:fixed;min-width:176px;padding:6px;background:var(--surface-raised);border:1px solid var(--border-subtle);border-radius:12px;box-shadow:var(--shadow-modal);z-index:150}.layer-menu-label{padding:8px 10px 5px;color:var(--text-secondary);font-size:11px;font-weight:750;letter-spacing:.08em;text-transform:uppercase}.layer-menu button{width:100%;height:34px;padding:0 10px;border:0;border-radius:8px;background:transparent;color:var(--text-primary);font:inherit;font-size:13px;font-weight:650;text-align:left;cursor:pointer}.layer-menu button:hover,.layer-menu button:focus-visible{background:var(--bg-hover);outline:none}.layer-menu-divider{height:1px;margin:5px 6px;background:var(--border-subtle)}.toolbar-divider{width:1px;height:24px;background:var(--border-subtle);margin:0 4px}.tool-btn{position:relative;display:flex;align-items:center;justify-content:center;width:38px;height:38px;border:none;border-radius:9px;background:transparent;color:var(--text-primary);cursor:pointer;transition:background .12s ease}.tool-btn:hover{background:var(--bg-hover)}.tool-btn:focus-visible{outline:2px solid var(--selection);outline-offset:1px}.tool-btn.active{background:var(--bg-active);color:var(--selection)}.tool-btn svg{width:19px;height:19px;stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}.tool-btn .tooltip{position:absolute;bottom:calc(100% + 10px);left:50%;transform:translate(-50%);white-space:nowrap;padding:5px 9px;font-size:11.5px;font-weight:500;border-radius:7px;background:var(--text-primary);color:var(--bg-panel);pointer-events:none;opacity:0;transition:opacity .12s ease}.tool-btn:hover .tooltip{opacity:1}.tooltip kbd{font-family:inherit;opacity:.65;margin-left:5px}.top-left{position:fixed;top:14px;left:14px;z-index:100;display:flex;align-items:center;gap:8px}.board-title{height:40px;padding:0 11px;background:var(--bg-toolbar);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border-radius:10px;box-shadow:var(--shadow-soft);font-size:13px;font-weight:600;color:var(--text-primary);border:1px solid var(--border-subtle);outline:none;min-width:60px;max-width:calc(100vw - 460px)}.board-title:focus{border-color:var(--selection)}.board-title-measure{position:absolute;visibility:hidden;pointer-events:none;white-space:pre;font-size:13px;font-weight:600;font-family:inherit}.top-right{position:fixed;top:14px;right:14px;z-index:100;display:flex;align-items:center;gap:8px}.control-group{display:flex;align-items:center;gap:2px;padding:4px;background:var(--bg-toolbar);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border:1px solid var(--border-subtle);border-radius:10px;box-shadow:var(--shadow-soft)}.control-btn{display:flex;align-items:center;justify-content:center;height:30px;min-width:30px;padding:0 8px;border:none;border-radius:7px;background:transparent;color:var(--text-primary);font-size:12px;font-weight:500;cursor:pointer;transition:background .12s ease;position:relative}.control-btn:hover{background:var(--bg-hover)}.control-btn.danger-control:hover{color:var(--danger)}.control-btn:focus-visible{outline:2px solid var(--selection)}.control-btn svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}.zoom-label{font-variant-numeric:tabular-nums;min-width:48px;text-align:center}.board-live-status{display:inline-flex;align-items:center;gap:8px;padding:8px 12px;border-radius:10px;background:var(--bg-toolbar);border:1px solid var(--border-subtle);box-shadow:var(--shadow-soft);font-size:12px;font-weight:600;color:var(--text-secondary)}.board-live-status[data-state=saved]{color:var(--success-text)}.board-live-status[data-state=stale]{color:var(--warning-main)}.board-live-status[data-state=error]{color:var(--danger)}.perf-overlay{position:fixed;top:60px;right:14px;z-index:99;padding:12px 14px;background:var(--bg-toolbar);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border:1px solid var(--border-subtle);border-radius:10px;box-shadow:var(--shadow-soft);font-family:SF Mono,ui-monospace,Menlo,monospace;font-size:11px;line-height:1.7;color:var(--text-primary);white-space:pre;pointer-events:none;display:none}.perf-overlay.visible{display:block}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;background:#0006;z-index:200;display:flex;align-items:center;justify-content:center}.modal{background:var(--surface-raised);border:1px solid var(--border-subtle);border-radius:16px;box-shadow:var(--shadow-modal);width:520px;max-width:calc(100vw - 40px);max-height:calc(100vh - 80px);overflow-y:auto;padding:24px}.modal h2{font-size:16px;font-weight:600;margin-bottom:16px}.modal-close{float:right;border:none;background:transparent;color:var(--text-secondary);font-size:18px;cursor:pointer;line-height:1;padding:2px 6px;border-radius:6px}.modal-close:hover{background:var(--bg-hover)}.modal .board-title{height:38px;max-width:none;padding:0 10px;background:var(--bg-panel);-webkit-backdrop-filter:none;backdrop-filter:none;border:1px solid var(--border-subtle);border-radius:8px;box-shadow:none;font-size:13px;font-weight:500}.modal .board-title:focus{border-color:color-mix(in srgb,var(--accent) 48%,var(--border-subtle));box-shadow:none}.command-palette{position:fixed;top:0;right:0;bottom:0;left:0;z-index:650;display:none;align-items:flex-start;justify-content:center;padding-top:min(120px,14vh);background:#0f172a47}.command-palette.visible{display:flex}.command-panel{width:min(640px,calc(100vw - 32px));max-height:min(620px,calc(100vh - 80px));overflow:hidden;border:1px solid var(--border-subtle);border-radius:12px;background:var(--surface-raised);box-shadow:var(--shadow-modal)}.command-search{display:flex;align-items:center;gap:10px;padding:13px 16px;border-bottom:1px solid var(--border-subtle);color:var(--text-secondary)}.command-search svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.command-search input{flex:1 1 auto;min-width:0;border:none;outline:none;background:transparent;color:var(--text-primary);font:inherit;font-size:15px}.command-results{max-height:min(520px,calc(100vh - 160px));overflow-y:auto;padding:6px}.command-result{display:block;padding:10px 12px;border-radius:8px;color:var(--text-primary);text-decoration:none}.command-result:hover,.command-result.active{background:var(--bg-active)}.command-result-title{font-size:14px;font-weight:600;line-height:1.35}.command-result-meta,.command-result-summary{margin-top:3px;font-size:12px;line-height:1.45;color:var(--text-secondary)}.command-result-summary{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.command-result mark{border-radius:4px;padding:0 2px;background:var(--accent-quiet);color:inherit}.command-empty{padding:28px 16px;text-align:center;color:var(--text-secondary);font-size:13px}.template-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}.template-card{display:flex;flex-direction:column;gap:4px;padding:14px;border:1px solid var(--border-subtle);border-radius:10px;background:transparent;color:var(--text-primary);cursor:pointer;text-align:left;transition:border-color .12s ease,background .12s ease}.template-card:hover{border-color:var(--selection);background:var(--bg-hover)}.template-card .t-name{font-size:13px;font-weight:600}.template-card .t-desc{font-size:11.5px;color:var(--text-secondary);line-height:1.4}.storage-row{display:flex;justify-content:space-between;padding:8px 0;font-size:13px;border-bottom:1px solid var(--border-subtle)}.storage-row span:last-child{color:var(--text-secondary);font-variant-numeric:tabular-nums}.modal-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:16px}.modal-copy{color:var(--text-secondary);font-size:13.5px;line-height:1.6}.btn{padding:8px 14px;border:1px solid var(--border-subtle);border-radius:8px;background:transparent;color:var(--text-primary);font-size:12.5px;font-weight:500;cursor:pointer;transition:background .12s ease}.btn:hover{background:var(--bg-hover)}.btn.danger{color:var(--danger);border-color:var(--danger)}.btn.primary{background:var(--accent-secondary);border-color:var(--selection);color:#fff}.btn.doc-export{display:inline-flex;align-items:center;gap:8px}.btn.doc-export svg,.docs-export-intro svg,.docs-export-actions .btn svg{width:15px;height:15px;stroke:currentColor;stroke-width:1.8;fill:none;stroke-linecap:round;stroke-linejoin:round}.stress-panel{position:fixed;bottom:84px;left:50%;transform:translate(-50%);z-index:99;display:none;flex-wrap:wrap;gap:6px;max-width:640px;justify-content:center;padding:10px;background:var(--bg-toolbar);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border:1px solid var(--border-subtle);border-radius:12px;box-shadow:var(--shadow-soft)}.stress-panel.visible{display:flex}.stress-panel .btn{font-size:11.5px;padding:6px 10px}.text-editor{position:absolute;z-index:50;border:1.5px solid var(--selection);border-radius:4px;background:transparent;color:inherit;resize:none;overflow:hidden;outline:none;padding:0;line-height:1.35}.toast{position:fixed;bottom:84px;left:50%;transform:translate(-50%) translateY(8px);z-index:300;padding:10px 18px;background:var(--text-primary);color:var(--bg-panel);border-radius:10px;font-size:13px;font-weight:500;box-shadow:var(--shadow-soft);opacity:0;transition:opacity .2s ease,transform .2s ease;pointer-events:none}.toast.visible{opacity:1;transform:translate(-50%) translateY(0)}@media (prefers-reduced-motion: reduce){*{transition:none!important}}.shell{--sidebar-width: 270px;display:flex;width:100%;height:100%;overflow:hidden}.shell-main{flex:1 1 auto;min-width:0;height:100%;display:flex;flex-direction:column}.shell.sidebar-collapsed .sidebar{display:none}.shell-topbar{position:relative;flex:0 0 auto;height:58px;display:flex;align-items:center;gap:10px;padding:12px 18px;border-bottom:1px solid var(--border-subtle);background:color-mix(in srgb,var(--bg-panel) 94%,transparent);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px)}.topbar-icon-btn,.topbar-search,.topbar-cart,.topbar-export,.topbar-edit,.topbar-machine,.topbar-theme{height:34px;border:1px solid var(--border-subtle);border-radius:8px;background:transparent;color:var(--text-secondary);cursor:pointer;transition:background .12s ease,color .12s ease,border-color .12s ease}.topbar-icon-btn{width:34px;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto}.topbar-icon-btn:hover,.topbar-search:hover,.topbar-cart:hover,.topbar-export:hover,.topbar-edit:hover,.topbar-machine:hover,.topbar-theme:hover{background:var(--bg-hover);color:var(--text-primary)}.topbar-icon-btn:focus-visible,.topbar-search:focus-visible,.topbar-cart:focus-visible,.topbar-export:focus-visible,.topbar-edit:focus-visible,.topbar-machine:focus-visible,.topbar-theme:focus-visible{outline:2px solid var(--selection);outline-offset:1px}.topbar-icon-btn svg,.topbar-search svg,.topbar-export svg,.topbar-edit svg,.topbar-theme svg{width:17px;height:17px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.topbar-search{width:min(360px,42vw);display:inline-flex;align-items:center;gap:9px;padding:0 8px 0 10px;text-align:left;font-size:13px}.topbar-search span{flex:1 1 auto;min-width:0}.topbar-search kbd{flex:0 0 auto;padding:2px 6px;border-radius:5px;background:var(--bg-hover);color:var(--text-secondary);font:11px/1.3 SF Mono,ui-monospace,Menlo,monospace}.topbar-cart{display:inline-flex;align-items:center;gap:8px;padding:0 10px 0 12px;font-size:13px;font-weight:500}.topbar-search{margin-right:auto}.topbar-cart[hidden]{display:none}.topbar-cart:disabled{opacity:.58;cursor:default}.topbar-cart strong{min-width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;border-radius:999px;background:var(--accent-quiet);color:var(--selection);font-size:12px}.topbar-export,.topbar-edit{display:inline-flex;align-items:center;gap:7px;padding:0 12px;font-size:13px;font-weight:500}.topbar-export{margin-left:auto;width:34px;padding:0;justify-content:center}.topbar-edit{margin-left:0}.topbar-machine{display:inline-flex;align-items:center;gap:7px;padding:0 12px;font-size:13px;font-weight:500;border-color:color-mix(in srgb,var(--selection) 28%,var(--border-subtle))}.topbar-machine[hidden]{display:none}.topbar-export[hidden],.topbar-export:disabled,.topbar-edit:disabled{opacity:.58}.topbar-export[hidden],.topbar-edit[hidden],.shell-topbar.editing-doc .topbar-cart{display:none}.topbar-export-menu{position:absolute;top:calc(100% + 8px);right:56px;z-index:850;min-width:188px;padding:5px;background:var(--surface-raised);border:1px solid var(--border-subtle);border-radius:10px;box-shadow:var(--shadow-modal);display:none}.topbar-export-menu.visible{display:block}.topbar-export-item{width:100%;display:flex;align-items:center;gap:8px;height:32px;padding:0 10px;border:0;border-radius:7px;background:transparent;color:var(--text-primary);font:inherit;font-size:13px;text-align:left;cursor:pointer;white-space:nowrap}.topbar-export-item:hover,.topbar-export-item:focus-visible{background:var(--bg-hover);outline:none}.topbar-export-item svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.topbar-cart-popover{position:absolute;top:calc(100% + 8px);right:58px;z-index:800;display:none;width:330px;filter:drop-shadow(0 16px 36px rgba(0,0,0,.16))}.topbar-cart-popover.visible{display:block}.topbar-cart-popover .capability-cart{position:static}.content.cart-popover-open .capability-layout>.capability-cart{visibility:hidden}.sidebar{flex:0 0 var(--sidebar-width);width:var(--sidebar-width);height:100%;min-height:0;display:flex;flex-direction:column;border-right:1px solid var(--border-subtle);background:var(--surface-raised);padding:0 12px}.sidebar-resize-handle{flex:0 0 6px;width:6px;border:none;background:transparent;color:transparent;cursor:ew-resize;padding:0;display:inline-flex;align-items:center;justify-content:center;position:relative}.sidebar-resize-handle:before{content:"";width:1px;height:100%;background:transparent;transition:background .12s ease}.sidebar-resize-handle:hover{background:transparent}.sidebar-resize-handle:hover:before,body.sidebar-resizing .sidebar-resize-handle:before{background:color-mix(in srgb,var(--selection) 55%,var(--border-subtle))}.sidebar-resize-handle svg{width:10px;height:10px;stroke:transparent;fill:none;stroke-width:1.8}.shell.sidebar-collapsed .sidebar-resize-handle{display:none}.sidebar-header{display:flex;align-items:center;height:58px;padding:0 12px;border-bottom:1px solid transparent}.sidebar-brand{font-size:17px;font-weight:700;letter-spacing:-.01em;color:var(--text-primary);text-decoration:none;border:none;background:none;cursor:pointer;padding:0;font-family:inherit}.sidebar-nav{flex:1 1 auto;min-height:0;overflow-y:auto;padding-bottom:18px;scrollbar-width:thin;scrollbar-color:transparent transparent}.sidebar:hover .sidebar-nav{scrollbar-color:color-mix(in srgb,var(--border-subtle) 88%,transparent) transparent}.sidebar-nav::-webkit-scrollbar{width:10px}.sidebar-nav::-webkit-scrollbar-thumb{background:transparent;border-radius:999px}.sidebar:hover .sidebar-nav::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--border-subtle) 88%,transparent)}body.sidebar-resizing{cursor:ew-resize;-webkit-user-select:none;user-select:none}.wiki-project-empty{display:grid;gap:12px}.wiki-project-empty p{margin:0}.sidebar-footer{flex:0 0 auto;padding:10px 0 14px;border-top:1px solid var(--border-subtle);background:var(--surface-raised)}.sidebar-add-repo{width:100%;height:36px;border:1px dashed color-mix(in srgb,var(--selection) 38%,var(--border-subtle));border-radius:8px;background:transparent;color:var(--text-secondary);font-size:13px;font-weight:600;cursor:pointer;transition:background .12s ease,color .12s ease,border-color .12s ease}.sidebar-add-repo:hover{background:var(--accent-soft);border-color:color-mix(in srgb,var(--selection) 58%,var(--border-subtle));color:var(--text-primary)}.nav-group{margin-bottom:18px}.nav-section-head,.nav-root-head{display:flex;align-items:center;justify-content:space-between;gap:10px}.nav-repo-group{margin-bottom:10px}.nav-repo-header,.nav-dir-toggle{width:100%;display:flex;align-items:center;gap:6px;padding:6px 8px;border:0;border-radius:7px;background:transparent;color:var(--text-secondary);font-size:11px;font-weight:700;letter-spacing:.06em;text-align:left;text-transform:uppercase;cursor:pointer}.nav-repo-header-row{display:flex;align-items:center;gap:4px;position:relative}.nav-repo-header-row .nav-repo-header{flex:1 1 auto;min-width:0}.nav-count-pill{flex:0 0 auto;padding:3px 8px;border:1px solid var(--border-subtle);border-radius:999px;background:color-mix(in srgb,var(--surface-raised) 82%,var(--accent-soft));color:var(--text-secondary);font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase}.nav-dir-toggle{padding:4px 6px;font-size:11px;text-transform:none;letter-spacing:0;font-weight:650}.nav-dir-project{opacity:.85;color:var(--text-secondary)}.nav-dir-project:hover{opacity:1}.nav-dir-docs{opacity:1;color:var(--text-secondary);font-weight:600}.nav-repo-header,.nav-dir-toggle{transition:background .12s ease,color .12s ease,opacity .12s ease}.nav-repo-header:hover,.nav-dir-toggle:hover{background:var(--bg-hover);color:var(--text-primary)}.nav-repo-header:focus-visible,.nav-dir-toggle:focus-visible{outline:2px solid var(--selection);outline-offset:1px}.nav-repo-header>span:not(.nav-repo-chevron),.nav-dir-toggle>span:not(.nav-repo-chevron){flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.nav-repo-chevron{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:12px;transform:rotate(90deg);transition:transform .12s ease}.nav-repo-group.collapsed .nav-repo-chevron{transform:rotate(0)}.nav-collapsible-dir.collapsed>.nav-dir-header-row .nav-dir-toggle .nav-repo-chevron{transform:rotate(0)}.nav-dir-header-row{display:flex;align-items:center;gap:4px;position:relative}.nav-dir-header-row .nav-dir-toggle{flex:1 1 auto;min-width:0}.nav-dir-header-row .nav-dir-wiki-link{flex:0 0 auto}.nav-repo-body[hidden],.nav-dir-body[hidden]{display:none}.nav-label{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-secondary);padding:6px 8px}.nav-empty-copy{font-size:12px;font-weight:500;text-transform:none;letter-spacing:.01em;line-height:1.45}.nav-subgroup{margin:2px 0 2px 8px}.nav-subgroup .nav-label{font-size:11px;text-transform:none;letter-spacing:0;font-weight:600;color:var(--text-secondary);opacity:.85}.nav-docs-root{margin:0 0 2px}.nav-root-path-label{padding:4px 8px 3px;font-size:10px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--text-secondary);opacity:.6;cursor:default;-webkit-user-select:none;user-select:none}.nav-root-path-label:hover{opacity:1;color:var(--text-secondary)}.nav-path-seg-toggle{font-size:12px;font-weight:600;text-transform:none;letter-spacing:0;color:var(--text-secondary);opacity:.9}.nav-link{display:flex;align-items:center;padding:6px 10px;margin:1px 0;border-radius:7px;font-size:13px;line-height:1.4;color:var(--text-secondary);text-decoration:none;border-left:2px solid transparent;transition:background .12s ease,color .12s ease}.nav-link:hover{background:var(--bg-hover);color:var(--text-primary)}.nav-link-muted{font-size:12px;opacity:.82}.nav-link.active{background:var(--bg-active);color:var(--selection);font-weight:600}.nav-tree-group,.nav-tree-children{margin:0;padding:0}.nav-tree-children{position:relative}.nav-tree-children:before{content:"";position:absolute;left:calc(-4px + var(--tree-depth, 1) * 16px);top:2px;bottom:2px;width:1px;pointer-events:none;background:color-mix(in srgb,var(--text-secondary) 22%,transparent);opacity:.62;border-radius:999px}.nav-tree-group.collapsed>.nav-tree-children:before,.nav-tree-children:empty:before{display:none}.nav-tree-row{--tree-depth: 0;width:100%;min-height:28px;display:flex;align-items:center;gap:6px;padding:4px 8px 4px calc(8px + var(--tree-depth) * 16px);border:0;border-radius:6px;background:transparent;color:var(--text-secondary);font:inherit;font-size:13px;line-height:1.35;text-align:left;text-decoration:none}button.nav-tree-row{cursor:pointer}.nav-tree-row:hover{background:var(--bg-hover);color:var(--text-primary)}.nav-tree-row:focus-visible{outline:2px solid var(--selection);outline-offset:-1px}.nav-tree-row.active{background:var(--bg-active);color:var(--selection);font-weight:600}.nav-tree-folder{font-weight:550}.nav-tree-docs-root{color:var(--text-primary);font-weight:650}.nav-tree-badge{margin-left:auto;color:var(--selection);font-size:9px;font-weight:700;letter-spacing:.06em}.nav-tree-doc{color:var(--text-secondary)}.nav-tree-board{color:var(--nav-board-color)}.nav-tree-capability,.nav-tree-capability-folder{color:var(--nav-cap-color)}.nav-tree-file,.nav-tree-scaffold{opacity:.52}.nav-tree-file{cursor:default;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.nav-tree-capability-file{cursor:pointer;opacity:.76}.nav-tree-capability-file:hover{opacity:1}.nav-tree-scaffold:hover{opacity:.8}.nav-tree-group.collapsed>.nav-tree-row .nav-repo-chevron{transform:rotate(0)}.nav-tree-group.nav-drop-target>.nav-tree-row{position:relative;background:color-mix(in srgb,var(--selection) 13%,var(--surface-raised));color:var(--text-primary);outline:2px solid var(--selection);outline-offset:0;box-shadow:0 0 0 3px color-mix(in srgb,var(--selection) 17%,transparent)}.nav-tree-group.nav-drop-target>.nav-tree-row:after{content:"Drop into";margin-left:auto;padding:2px 6px;border-radius:4px;background:var(--selection);color:var(--bg-canvas);font-size:10px;font-weight:700;letter-spacing:.03em;text-transform:uppercase}.nav-tree-group.nav-drop-target.nav-drop-auto-expanded>.nav-tree-children{position:relative;margin:3px 0;border:1px dashed color-mix(in srgb,var(--selection) 68%,transparent);border-radius:5px;background:color-mix(in srgb,var(--selection) 5%,transparent)}[data-theme=dark] .nav-link.active{color:var(--neutral-100)}.nav-node-icon{flex:0 0 auto;width:13px;height:13px;margin-right:5px;vertical-align:middle;fill:currentColor;stroke:none;opacity:.85;position:relative;top:-1px}.nav-node-icon-doc{fill:#4285f4;opacity:1}.nav-node-icon-board{fill:#8b5cf6;opacity:1}[data-theme=dark] .nav-node-icon-board{fill:#a78bfa}.nav-node-icon-capability{fill:#f59e0b;opacity:1}[data-theme=dark] .nav-node-icon-capability{fill:#fbbf24}:root{--nav-board-color: #8b5cf6;--nav-board-bg: rgba(139, 92, 246, .08);--nav-board-bg-active: rgba(139, 92, 246, .15);--nav-cap-color: #8a6800;--nav-cap-bg: rgba(138, 104, 0, .08);--nav-cap-bg-active: rgba(138, 104, 0, .15)}[data-theme=dark]{--nav-board-color: #a78bfa;--nav-board-bg: rgba(167, 139, 250, .08);--nav-board-bg-active: rgba(167, 139, 250, .14);--nav-cap-color: #d4a800;--nav-cap-bg: rgba(212, 168, 0, .08);--nav-cap-bg-active: rgba(212, 168, 0, .14)}.nav-link-board{display:flex;align-items:center;color:var(--nav-board-color)}.nav-link-board:hover{background:var(--nav-board-bg);color:var(--nav-board-color)}.nav-link-board.active{background:var(--nav-board-bg-active);color:var(--nav-board-color);font-weight:600}.nav-link-capability{display:flex;align-items:center;color:var(--nav-cap-color)}.nav-link-capability:hover{background:var(--nav-cap-bg);color:var(--nav-cap-color)}.nav-link-capability.active{background:var(--nav-cap-bg-active);color:var(--nav-cap-color);font-weight:600}.nav-project-boards-row{margin-top:4px;padding-top:4px;border-top:1px solid var(--border-subtle)}.nav-context-menu{z-index:900;min-width:168px;padding:5px;background:var(--surface-raised);border:1px solid var(--border-subtle);border-radius:10px;box-shadow:var(--shadow-modal);-webkit-user-select:none;user-select:none}.nav-context-label{padding:5px 10px 4px;font-size:10.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:180px}.nav-context-divider{height:1px;margin:4px 5px;background:var(--border-subtle)}.nav-context-item{width:100%;display:flex;align-items:center;gap:8px;height:32px;padding:0 10px;border:0;border-radius:7px;background:transparent;color:var(--text-primary);font:inherit;font-size:13px;text-align:left;cursor:pointer;white-space:nowrap}.nav-context-item-parent{position:relative;justify-content:flex-start}.nav-context-item-parent:after{content:"";position:absolute;top:0;left:100%;width:10px;height:100%}.nav-context-chevron{margin-left:auto;display:inline-flex;align-items:center;justify-content:center;color:var(--text-secondary)}.nav-ctx-chevron-svg{width:12px;height:12px}.nav-context-submenu{position:absolute;top:-5px;left:calc(100% - 2px);z-index:901;min-width:180px;padding:5px;background:var(--surface-raised);border:1px solid var(--border-subtle);border-radius:10px;box-shadow:var(--shadow-modal);display:none}.nav-context-item-parent:hover>.nav-context-submenu,.nav-context-item-parent:focus-within>.nav-context-submenu{display:block}.nav-context-item:hover,.nav-context-item:focus-visible{background:var(--bg-hover);outline:none}.nav-context-item-danger{color:var(--danger)}.nav-context-item-danger:hover,.nav-context-item-danger:focus-visible{background:color-mix(in srgb,var(--danger) 8%,transparent)}.nav-dragging{opacity:.48;cursor:grabbing!important}.nav-link[draggable=true],.nav-dir-toggle[draggable=true]{cursor:pointer}.nav-drop-target>.nav-dir-toggle,.nav-drop-target>.nav-dir-header-row,.nav-drop-target.nav-repo-header,.nav-drop-target.nav-docs-root{background:color-mix(in srgb,var(--accent) 20%,transparent)!important;outline:2px solid var(--accent)!important;outline-offset:-2px;box-shadow:inset 0 0 8px color-mix(in srgb,var(--accent) 30%,transparent);border-radius:6px;position:relative}@keyframes dropPulse{0%,to{outline-color:var(--accent)}50%{outline-color:color-mix(in srgb,var(--accent) 60%,transparent)}}.nav-drop-target>.nav-dir-toggle,.nav-drop-target>.nav-dir-header-row,.nav-drop-target.nav-repo-header,.nav-drop-target.nav-docs-root{animation:dropPulse 1s ease-in-out infinite}.nav-ctx-icon{flex:0 0 auto;width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center}.nav-ctx-icon svg.nav-node-icon{width:13px;height:13px;fill:currentColor;stroke:none}.nav-ctx-svg{width:14px;height:14px}.nav-ctx-doc,.nav-ctx-dir{color:var(--text-secondary)}.nav-ctx-board{color:var(--nav-board-color)}.nav-ctx-cap{color:var(--nav-cap-color)}.nav-ctx-edit,.nav-ctx-rename{color:var(--text-secondary)}.nav-ctx-delete{color:var(--danger)}.content-scroll{flex:1 1 auto;min-height:0;overflow-y:auto}.content{max-width:820px;margin:0 auto;padding:36px 40px 120px}.content.content-wide{max-width:1240px}.external-doc-eyebrow{margin:0 0 18px;color:var(--text-secondary);font-size:12px;font-weight:650;letter-spacing:.05em;text-transform:uppercase}.external-repo-overview .page-bar{align-items:flex-start}.repo-source-pill{flex:0 0 auto;padding:6px 10px;border:1px solid var(--border-subtle);border-radius:999px;color:var(--text-secondary);font-size:12px;font-weight:600}.external-doc-dir-list{display:grid;gap:22px}.external-doc-dir h2{margin:0 0 10px;color:var(--text-secondary);font-size:12px;font-weight:700;letter-spacing:.05em;text-transform:uppercase}.external-doc-file-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:10px}.external-doc-card{display:flex;flex-direction:column;gap:4px;min-height:82px;padding:14px;border:1px solid var(--border-subtle);border-radius:8px;background:var(--surface-raised);color:var(--text-primary);text-decoration:none;transition:border-color .12s ease,background .12s ease,transform .12s ease}.external-doc-card:hover{border-color:color-mix(in srgb,var(--selection) 42%,var(--border-subtle));background:color-mix(in srgb,var(--accent) 4%,var(--surface-raised));transform:translateY(-1px)}.external-doc-card strong{font-size:14px;line-height:1.3}.external-doc-card span{color:var(--text-secondary);font-size:12px;line-height:1.35;overflow-wrap:anywhere}.page-bar{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:8px}.page-bar h1{margin:0}.page-bar-label{font-size:12px;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.05em}.page-lead{color:var(--text-secondary);font-size:14px;line-height:1.6;margin:4px 0 24px}.card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:14px}.card{position:relative;display:flex;flex-direction:column;gap:10px;min-height:190px;padding:18px;border:1px solid var(--border-subtle);border-radius:8px;text-decoration:none;color:var(--text-primary);background:var(--surface-raised);transition:border-color .12s ease,background .12s ease,transform .12s ease}.card:is(button){-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;text-align:left;font:inherit;cursor:pointer}.card:hover{border-color:color-mix(in srgb,var(--selection) 42%,var(--border-subtle));transform:translateY(-1px)}.card.selected{border-color:color-mix(in srgb,var(--selection) 72%,var(--border-subtle));background:color-mix(in srgb,var(--accent) 5%,var(--surface-raised))}.card.selected:before{content:"";position:absolute;inset:0 auto 0 0;width:3px;border-radius:8px 0 0 8px;background:var(--selection)}.card-title{max-width:92%;font-size:16px;font-weight:650;line-height:1.25;letter-spacing:0}.card-title a{color:inherit;text-decoration:none}.card-title a:hover{color:var(--selection)}.card-desc{font-size:13px;color:var(--text-secondary);line-height:1.55}.card-tags{display:flex;flex-wrap:wrap;gap:6px;margin-top:auto}.card-actions{padding-top:4px}.card-actions .btn{height:30px;padding:0 11px;border-radius:999px;font-size:12px}.card-actions .capability-add-btn.primary{background:transparent;border-color:color-mix(in srgb,var(--selection) 42%,var(--border-subtle));color:var(--selection)}.card-actions .capability-add-btn.primary:hover{background:var(--accent-soft)}.card-actions .capability-add-btn:not(.primary){border-color:transparent;background:var(--bg-hover);color:var(--text-secondary)}.tag{font-size:11px;padding:3px 8px;border-radius:999px;background:var(--accent-secondary-quiet);color:color-mix(in srgb,var(--accent-secondary) 72%,var(--text-primary))}.empty-note{grid-column:1 / -1;padding:32px;text-align:center;color:var(--text-secondary);border:1px dashed var(--border-subtle);border-radius:12px}.capability-layout{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:28px;align-items:start}.capability-grid{grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}.capability-library h1{font-size:32px;letter-spacing:0;margin-bottom:6px}.capability-library .page-lead{max-width:680px;margin-bottom:28px}.capability-meta-tags{margin:0 0 18px}.capability-entry-section{margin-top:6px}.capability-entry-lead{max-width:760px;margin-top:0;margin-bottom:18px}.capability-files-grid{grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:12px}.board-library .page-lead{max-width:760px}.board-grid{grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:18px}.token-library .page-bar{margin-bottom:6px}.token-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}.token-section{margin-top:30px}.token-section h2{margin:0 0 14px;font-size:17px;font-weight:700}.token-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:12px}.token-card{position:relative;display:grid;grid-template-columns:minmax(0,1fr) 44px;align-items:stretch;min-height:72px;border:1px solid var(--border-subtle);border-radius:8px;background:var(--surface-raised);overflow:hidden;transition:border-color .12s ease,background .12s ease}.token-card:hover{border-color:color-mix(in srgb,var(--selection) 36%,var(--border-subtle))}.token-card.selected{border-color:var(--brand-500);background:color-mix(in srgb,var(--accent) 4%,var(--surface-raised))}.token-card input{position:absolute;opacity:0;pointer-events:none}.token-select-zone{display:grid;grid-template-columns:42px minmax(0,1fr);gap:12px;align-items:center;padding:10px;cursor:pointer}.token-copy-btn{display:flex;align-items:center;justify-content:center;width:44px;border:none;border-left:1px solid var(--border-subtle);background:transparent;color:var(--text-secondary);cursor:pointer;transition:background .12s ease,color .12s ease}.token-copy-btn:hover{background:var(--accent-soft);color:var(--selection)}.token-copy-btn:focus-visible{outline:2px solid var(--selection);outline-offset:-2px}.token-copy-btn svg{width:17px;height:17px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.token-swatch{width:42px;height:42px;border:1px solid var(--border-subtle);border-radius:7px;box-shadow:inset 0 0 0 1px #00000005}[data-theme=dark] .card,[data-theme=dark] .token-card,[data-theme=dark] .capability-cart,[data-theme=dark] .command-panel,[data-theme=dark] .modal{border-color:#ffffff14;box-shadow:0 1px #ffffff06,0 18px 48px #0000005c}[data-theme=dark] .card:hover,[data-theme=dark] .token-card:hover{border-color:color-mix(in srgb,var(--accent) 38%,rgba(255,255,255,.12))}[data-theme=dark] .shell-topbar,[data-theme=dark] .sidebar{border-color:#ffffff13}[data-theme=dark] .nav-link.active{background:#a3a27e29;color:#fff}[data-theme=dark] .btn.primary{color:#111312}.token-copy{min-width:0;display:grid;gap:2px}.token-copy strong{font-size:13px;line-height:1.25}.token-copy code{color:var(--text-secondary);font:12px/1.25 SF Mono,ui-monospace,Menlo,monospace;background:transparent}.token-copy small{color:var(--text-secondary);font-size:11.5px;line-height:1.3}.capability-cart{position:sticky;top:18px;border:1px solid var(--border-subtle);border-radius:8px;background:color-mix(in srgb,var(--bg-panel) 96%,var(--bg-hover));overflow:hidden;box-shadow:0 1px #00000005}.capability-cart-head,.capability-cart-actions{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:14px;border-bottom:1px solid var(--border-subtle)}.capability-cart-actions{border-top:1px solid var(--border-subtle);border-bottom:none}.capability-cart-head h2{margin:0;font-size:13px;font-weight:650;text-transform:uppercase;letter-spacing:.04em;color:var(--text-secondary)}.capability-cart-count{min-width:24px;height:24px;display:inline-flex;align-items:center;justify-content:center;border-radius:999px;background:var(--accent-quiet);color:var(--selection);font-size:12px;font-weight:700}.capability-cart-body{display:flex;flex-direction:column;gap:4px;max-height:420px;overflow-y:auto;padding:8px}.capability-cart-empty{padding:22px 12px;color:var(--text-secondary);font-size:12.5px;line-height:1.5}.capability-cart-item{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding:10px;border-radius:8px;background:var(--surface-raised);border:1px solid transparent}.capability-cart-item:hover{border-color:var(--border-subtle)}.capability-cart-item-title{font-size:13px;font-weight:600;line-height:1.35}.capability-cart-item-meta{margin-top:2px;font-size:11px;color:var(--text-secondary);line-height:1.35}.capability-cart-remove{border:none;background:transparent;color:var(--text-secondary);font-size:11.5px;cursor:pointer;padding:1px 0}.capability-cart-remove:hover{color:var(--danger)}.markdown-body{font-size:15px;line-height:1.7;color:var(--text-primary)}.markdown-body h1{font-size:30px;font-weight:700;margin:8px 0 20px;letter-spacing:-.015em}.markdown-body h2{font-size:22px;font-weight:650;margin:36px 0 14px;padding-bottom:8px;border-bottom:1px solid var(--border-subtle)}.markdown-body h3{font-size:17px;font-weight:650;margin:26px 0 10px}.markdown-body h4{font-size:15px;font-weight:650;margin:20px 0 8px}.markdown-body p{margin:14px 0}.markdown-body a{color:var(--selection);text-decoration:none}.markdown-body a:hover{text-decoration:underline}.markdown-body ul,.markdown-body ol{margin:14px 0;padding-left:26px}.markdown-body li{margin:5px 0}.markdown-body li.task{list-style:none;margin-left:-22px}.markdown-body li.task input{margin-right:8px}.markdown-body blockquote{margin:16px 0;padding:4px 16px;border-left:3px solid var(--selection);background:var(--bg-hover);border-radius:0 8px 8px 0;color:var(--text-secondary)}.markdown-body code{font-family:SF Mono,ui-monospace,Menlo,monospace;font-size:.86em;background:var(--bg-hover);padding:2px 6px;border-radius:5px}.markdown-body pre.code-block{position:relative;margin:16px 0;padding:16px;background:var(--bg-hover);border:1px solid var(--border-subtle);border-radius:10px;overflow-x:auto}.markdown-body pre.code-block code{background:none;padding:0;font-size:13px;line-height:1.6}.code-copy-btn{position:absolute;top:10px;right:10px;height:26px;padding:0 9px;border:1px solid var(--border-subtle);border-radius:7px;background:color-mix(in srgb,var(--bg-panel) 92%,var(--surface-raised));color:var(--text-secondary);font-size:11.5px;font-weight:600;cursor:pointer}.code-copy-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.markdown-body img{max-width:100%;border-radius:8px}.markdown-body hr{border:none;border-top:1px solid var(--border-subtle);margin:32px 0}.markdown-body .markdown-table-scroll{width:100%;max-width:100%;overflow-x:auto;overflow-y:hidden;margin:18px 0}.markdown-body table{width:max-content;min-width:100%;border-collapse:collapse;margin:0;font-size:13.5px}.markdown-body th,.markdown-body td{border:1px solid var(--border-subtle);padding:8px 12px;text-align:left;white-space:normal;word-break:break-word;overflow-wrap:anywhere;max-width:min(28rem,40vw)}.markdown-body th{background:var(--bg-hover);font-weight:600}.markdown-body .md-error{margin:16px 0;padding:14px 16px;border:1px solid #ef4444;border-radius:10px;background:#ef444414;color:var(--text-primary)}.markdown-body .md-error pre{margin:8px 0 0;font-size:12px;white-space:pre-wrap;color:var(--text-secondary)}.board-embed{margin:24px 0}.board-embed-frame{position:relative;width:100%;border:1px solid var(--border-subtle);border-radius:12px;overflow:hidden;background:var(--bg-canvas);box-shadow:var(--shadow-soft);display:flex;align-items:center;justify-content:center}.board-embed-frame canvas{display:block}.board-embed-frame.error{color:var(--text-secondary);font-size:13px}.board-embed-msg{padding:24px}.board-embed-open{position:absolute;bottom:10px;right:10px;padding:5px 10px;font-size:12px;font-weight:500;border-radius:8px;background:var(--bg-toolbar);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid var(--border-subtle);color:var(--text-primary);text-decoration:none;opacity:0;transition:opacity .15s ease}.board-embed-frame:hover .board-embed-open{opacity:1}.board-embed figcaption{margin-top:8px;font-size:12.5px;color:var(--text-secondary);text-align:center}.board-editor-host{position:fixed;top:0;right:0;bottom:0;left:0;z-index:500}.top-left .back-btn{height:40px;width:auto;gap:6px;padding:0 12px;background:var(--bg-toolbar);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border:1px solid var(--border-subtle);box-shadow:var(--shadow-soft);font-size:13px;font-weight:500}.top-left .back-btn span{line-height:1}.embed-select-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;cursor:crosshair;background:#0f172a2e}.embed-select-rect{position:absolute;border:1.5px solid var(--selection);background:color-mix(in srgb,var(--selection) 16%,transparent);box-shadow:0 0 0 9999px #0f172a1f;pointer-events:none}.embed-select-hint{position:fixed;top:16px;left:50%;transform:translate(-50%);padding:8px 14px;border-radius:10px;font-size:13px;font-weight:500;color:#fff;background:#0f172ad1;pointer-events:none}.doc-page{position:relative}.doc-actions{display:flex;justify-content:flex-end;gap:8px;margin-bottom:8px}.docs-export-modal{display:grid;gap:16px}.docs-export-intro{display:grid;grid-template-columns:16px minmax(0,1fr);gap:10px;padding:14px 15px;border:1px solid var(--border-subtle);border-radius:10px;background:color-mix(in srgb,var(--accent) 4%,var(--surface-raised))}.docs-export-intro strong{display:block;margin-bottom:4px;font-size:13px}.docs-export-intro p,.docs-export-notes p{margin:0;color:var(--text-secondary);font-size:13px;line-height:1.55}.docs-export-form{display:grid;gap:12px}.docs-export-field{display:grid;gap:6px}.docs-export-field>span{font-size:12px;font-weight:600;color:var(--text-secondary)}.docs-export-field>input,.docs-export-field>select{width:100%;padding:10px 11px;border:1px solid var(--border-subtle);border-radius:8px;background:var(--surface-raised);color:var(--text-primary);font:inherit}.docs-export-field>input:focus,.docs-export-field>select:focus{outline:2px solid color-mix(in srgb,var(--selection) 38%,transparent);outline-offset:1px;border-color:color-mix(in srgb,var(--selection) 58%,var(--border-subtle))}.docs-export-checkbox{display:grid;grid-template-columns:16px minmax(0,1fr);align-items:start;gap:10px;padding:12px 13px;border:1px solid var(--border-subtle);border-radius:10px;background:var(--surface-raised)}.docs-export-checkbox input{margin-top:2px}.docs-export-checkbox-copy{display:grid;gap:3px}.docs-export-checkbox-copy strong{font-size:12.5px}.docs-export-checkbox-copy small{color:var(--text-secondary);font-size:12px;line-height:1.45}.docs-export-actions{justify-content:flex-end}.docs-export-studio-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1600;display:flex;align-items:stretch;justify-content:center;padding:18px;background:#0f172a6b;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px)}.docs-export-studio{width:min(1640px,calc(100vw - 36px));height:calc(100vh - 36px);display:grid;grid-template-rows:auto minmax(0,1fr);border:1px solid var(--border-subtle);border-radius:16px;overflow:hidden;background:var(--surface-raised);box-shadow:var(--shadow-modal)}.docs-export-studio-header{display:flex;align-items:flex-start;justify-content:space-between;gap:20px;padding:18px 20px 16px;border-bottom:1px solid var(--border-subtle);background:color-mix(in srgb,var(--bg-panel) 95%,transparent)}.docs-export-studio-header-copy span{display:block;margin-bottom:6px;color:var(--text-secondary);font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}.docs-export-studio-header-copy h2{margin:0;font-size:24px;letter-spacing:-.03em}.docs-export-studio-header-copy p{max-width:760px;margin:6px 0 0;color:var(--text-secondary);font-size:13px;line-height:1.6}.docs-export-studio-header-actions{display:flex;align-items:center;gap:10px}.docs-export-primary-btn,.docs-export-secondary-btn{display:inline-flex;align-items:center;gap:8px}.docs-export-primary-btn{padding-inline:16px;border-color:color-mix(in srgb,var(--selection) 70%,var(--border-subtle));background:linear-gradient(180deg,color-mix(in srgb,var(--selection) 82%,#ffffff 18%),color-mix(in srgb,var(--selection) 94%,#000000 6%));box-shadow:0 10px 24px color-mix(in srgb,var(--selection) 22%,transparent)}.docs-export-primary-btn:hover{background:linear-gradient(180deg,color-mix(in srgb,var(--selection) 88%,#ffffff 12%),color-mix(in srgb,var(--selection) 96%,#000000 4%))}.docs-export-primary-btn svg,.docs-export-secondary-btn svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.docs-export-studio-close{font-size:20px;line-height:1;min-width:34px;justify-content:center}.docs-export-studio-layout{min-height:0;display:grid;grid-template-columns:420px minmax(0,1fr)}.docs-export-studio-controls{min-height:0;border-right:1px solid var(--border-subtle);background:var(--surface-raised)}.docs-export-studio-controls-body{height:100%;overflow:auto;padding:18px;display:grid;align-content:start;gap:16px}.docs-export-studio-section{display:grid;gap:10px;padding:14px;border:1px solid var(--border-subtle);border-radius:12px;background:color-mix(in srgb,var(--surface-raised) 84%,var(--bg-hover))}.docs-export-studio-section-heading{display:flex;align-items:center;gap:10px}.docs-export-studio-section-heading span{display:inline-grid;width:26px;height:26px;place-items:center;border-radius:999px;background:color-mix(in srgb,var(--selection) 10%,transparent);color:var(--selection);font-size:10px;font-weight:800;letter-spacing:.08em}.docs-export-studio-section-heading strong{color:var(--text-primary);font-size:13px;letter-spacing:-.01em}.docs-export-studio-note{padding:14px;border:1px solid var(--border-subtle);border-radius:12px;background:color-mix(in srgb,var(--accent) 4%,var(--surface-raised))}.docs-export-studio-note p{margin:0;color:var(--text-secondary);font-size:12.5px;line-height:1.6}.docs-export-studio-note p+p{margin-top:8px}.docs-export-included-summary{margin:0;color:var(--text-secondary);font-size:12.5px;line-height:1.55}.docs-export-included-list{margin:0;padding-left:18px;display:grid;gap:8px}.docs-export-included-list li{display:grid;gap:2px}.docs-export-included-list strong{font-size:12.5px}.docs-export-included-list small{color:var(--text-secondary);font-size:11px;word-break:break-word}.docs-export-preview-shell{min-width:0;min-height:0;display:grid;grid-template-rows:auto minmax(0,1fr);background:var(--surface-raised)}.docs-export-preview-header{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:14px 18px;border-bottom:1px solid var(--border-subtle);background:color-mix(in srgb,var(--bg-panel) 96%,transparent)}.docs-export-preview-header-copy{display:grid;gap:4px}.docs-export-preview-meta{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}.docs-export-preview-chip{padding:5px 9px;border:1px solid var(--border-subtle);border-radius:999px;background:color-mix(in srgb,var(--surface-raised) 80%,var(--bg-hover));color:var(--text-secondary);font-size:11px;font-weight:600}.docs-export-preview-header span{color:var(--text-secondary);font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}.docs-export-preview-header strong{color:var(--text-primary);font-size:12px;font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace}.docs-export-preview-scroll{min-width:0;min-height:0;overflow:auto;padding:20px}.docs-export-publication-shell{position:static;visibility:visible;pointer-events:auto}.docs-export-publication{width:210mm;padding:10mm 0;background:var(--neutral-100);color:var(--text-primary);font-family:Inter,ui-sans-serif,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;--docs-export-scale: 1;--docs-export-paper: var(--surface-raised);--docs-export-ink: var(--text-primary);--docs-export-ink-soft: var(--text-secondary);--docs-export-ink-faint: color-mix(in srgb, var(--text-primary) 46%, var(--surface-raised));--docs-export-line: color-mix(in srgb, var(--text-primary) 10%, transparent);--docs-export-line-strong: color-mix(in srgb, var(--selection) 18%, transparent);--docs-export-accent: var(--brand-300);--docs-export-accent-strong: var(--brand-600);--docs-export-accent-soft: color-mix(in srgb, var(--brand-300) 8%, var(--surface-raised));--docs-export-chip-bg: color-mix(in srgb, var(--brand-300) 6%, var(--surface-raised))}.docs-export-publication-shell[data-docs-export-scale="90"] .docs-export-publication{--docs-export-scale: .92}.docs-export-publication-shell[data-docs-export-scale="100"] .docs-export-publication{--docs-export-scale: 1}.docs-export-publication-shell[data-docs-export-scale="110"] .docs-export-publication{--docs-export-scale: 1.08}.docs-export-publication-shell[data-docs-export-paper=Letter] .docs-export-publication{width:215.9mm}.docs-export-publication>*{width:100%;box-sizing:border-box}.docs-export-cover,.docs-export-toc,.docs-export-doc{background:var(--docs-export-paper);border:1px solid var(--docs-export-line);box-shadow:0 18px 48px #11110e14}.docs-export-cover,.docs-export-toc{min-height:277mm;padding:22mm 19mm 18mm}.docs-export-publication-shell[data-docs-export-paper=Letter] .docs-export-cover,.docs-export-publication-shell[data-docs-export-paper=Letter] .docs-export-toc{min-height:259.4mm}.docs-export-cover{display:grid;align-content:center;gap:18px;padding-top:26mm}.docs-export-cover-logo{max-width:220px;max-height:74px;object-fit:contain}.docs-export-cover-eyebrow,.docs-export-doc-eyebrow{margin:0;color:var(--docs-export-accent);font-size:10px;font-weight:700;letter-spacing:.12em;text-transform:uppercase}.docs-export-cover h1{margin:0;font-size:calc(34px * var(--docs-export-scale));line-height:1.05;letter-spacing:-.04em}.docs-export-cover-subtitle{max-width:650px;margin:0;color:var(--docs-export-ink-soft);font-size:calc(14px * var(--docs-export-scale));line-height:1.7}.docs-export-cover-meta{display:grid;gap:10px;max-width:420px;margin-top:8px}.docs-export-cover-row{display:flex;align-items:baseline;justify-content:space-between;gap:18px;padding-top:10px;border-top:1px solid var(--docs-export-line)}.docs-export-cover-row span{color:var(--docs-export-ink-faint);font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em}.docs-export-cover-row strong{text-align:right;font-size:12px}.docs-export-cover-notice{max-width:680px;margin:6px 0 0;padding-top:14px;border-top:1px solid var(--docs-export-line);color:var(--docs-export-ink-soft);font-size:12.5px;line-height:1.7}.docs-export-toc{break-before:page;page-break-before:always;page-break-after:always}.docs-export-toc h2{margin:0 0 8px;font-size:28px;letter-spacing:-.03em}.docs-export-toc-note{margin:0 0 18px;color:var(--docs-export-ink-faint);font-size:13px;line-height:1.6}.docs-export-toc-list{margin:0;padding:0;list-style:none}.docs-export-toc-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:18px;align-items:start;padding:11px 0;border-top:1px solid var(--docs-export-line);color:inherit;text-decoration:none}.docs-export-toc-row:hover{color:var(--docs-export-accent-strong)}.docs-export-toc-label{display:grid;gap:4px}.docs-export-toc-label strong{font-size:13px}.docs-export-toc-label small{color:var(--docs-export-ink-faint);font-size:11px}.docs-export-toc-row>span{color:var(--docs-export-accent);font-size:11px;font-weight:700;letter-spacing:.08em}.docs-export-doc{padding:22mm 18mm 16mm}.docs-export-branch-break{padding:0 0 12mm}.docs-export-branch-breadcrumb{margin:0 0 6px;color:var(--docs-export-ink-faint);font-size:10px;font-style:italic;letter-spacing:.02em}.docs-export-branch-break h2{margin:0;font-size:calc(28px * var(--docs-export-scale));line-height:1.06;letter-spacing:-.04em}.docs-export-branch-rule{margin-top:12px;border-top:1px solid var(--docs-export-line-strong)}.docs-export-doc-break-after{break-after:page;page-break-after:always}.docs-export-doc-header{margin-bottom:18px;padding-bottom:12px;border-bottom:1px solid var(--docs-export-line);break-after:avoid-page;page-break-after:avoid}.docs-export-doc-context{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;margin-top:8px;color:var(--docs-export-ink-faint);font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase}.docs-export-doc-context span:last-child{text-align:right}.docs-export-doc-header h2{margin:5px 0 0;font-size:calc(24px * var(--docs-export-scale));line-height:1.12;letter-spacing:-.03em}.docs-export-doc-path{margin-top:7px;color:var(--docs-export-ink-faint);font-size:11px;font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace;word-break:break-word}.docs-export-doc-path-single{margin:0 0 14px}.docs-export-doc-body .markdown-body{font-size:calc(14px * var(--docs-export-scale));line-height:1.72;color:var(--docs-export-ink);orphans:3;widows:3}.docs-export-doc-body .markdown-body h1,.docs-export-doc-body .markdown-body h2,.docs-export-doc-body .markdown-body h3,.docs-export-doc-body .markdown-body h4{break-after:avoid}.docs-export-doc-body .markdown-body h1{font-size:calc(28px * var(--docs-export-scale))}.docs-export-doc-body .markdown-body h2{font-size:calc(20px * var(--docs-export-scale))}.docs-export-doc-body .markdown-body h3{font-size:calc(16px * var(--docs-export-scale))}.docs-export-doc-body .markdown-body p,.docs-export-doc-body .markdown-body li,.docs-export-doc-body .markdown-body blockquote,.docs-export-doc-body .markdown-body th,.docs-export-doc-body .markdown-body td{color:var(--docs-export-ink)}.docs-export-doc-body .markdown-body pre.code-block,.docs-export-doc-body .markdown-body blockquote,.docs-export-doc-body .markdown-body figure.board-embed,.docs-export-toc-row,.docs-export-cover-row{break-inside:avoid}.docs-export-doc-body .markdown-body pre.code-block,.docs-export-doc-body .markdown-body .markdown-table-scroll,.docs-export-doc-body .markdown-body table{break-inside:auto;page-break-inside:auto}.docs-export-doc-body .markdown-body pre.code-block{background:color-mix(in srgb,var(--neutral-100) 88%,var(--surface-raised));border-color:var(--docs-export-line)}.docs-export-doc-body .markdown-body th{background:color-mix(in srgb,var(--neutral-100) 88%,var(--surface-raised))}.docs-export-doc-body .markdown-body .code-copy-btn,.docs-export-doc-body .markdown-body .board-embed-open{display:none!important}.docs-export-doc-body .markdown-body figure.board-embed{margin:22px 0}.docs-export-doc-body .markdown-body .board-embed-frame{border-color:var(--docs-export-line);background:color-mix(in srgb,var(--neutral-100) 86%,var(--surface-raised))}.docs-export-empty-publication{display:grid;place-items:center;min-height:420px;padding:40px;text-align:center}.docs-export-empty-publication h2{margin:0;font-size:28px;letter-spacing:-.03em}.docs-export-empty-publication p{max-width:420px;margin:10px 0 0;color:#5a5a52;font-size:13px;line-height:1.7}.docs-export-disclosure{border:1px solid var(--border-subtle);border-radius:12px;background:var(--surface-raised)}.docs-export-disclosure-summary{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;cursor:pointer;list-style:none}.docs-export-disclosure-summary::-webkit-details-marker{display:none}.docs-export-disclosure-summary-text{display:grid;gap:3px}.docs-export-disclosure-summary-text strong{font-size:12.5px}.docs-export-disclosure-summary-text small{color:var(--text-secondary);font-size:11px}.docs-export-disclosure-badge{display:inline-grid;min-width:28px;height:28px;place-items:center;padding-inline:8px;border-radius:999px;background:color-mix(in srgb,var(--selection) 12%,transparent);color:var(--selection);font-size:12px;font-weight:800}.docs-export-disclosure[data-selection-state=empty] .docs-export-disclosure-badge{background:color-mix(in srgb,#b91c1c 10%,transparent);color:#b91c1c}.docs-export-disclosure-body{padding:0 14px 14px;border-top:1px solid var(--border-subtle)}.docs-export-doc-picker-actions{display:flex;gap:8px;padding-top:12px}.docs-export-doc-search{padding-top:12px}.docs-export-doc-picker-list{display:grid;gap:10px;max-height:320px;margin-top:12px;overflow:auto}.docs-export-doc-picker-item{display:grid;grid-template-columns:16px minmax(0,1fr);gap:10px;align-items:start;padding:10px 0;border-top:1px solid var(--border-subtle)}.docs-export-doc-picker-item:first-child{border-top:0;padding-top:0}.docs-export-doc-picker-copy{display:grid;gap:4px}.docs-export-doc-picker-copy strong{font-size:12.5px}.docs-export-doc-picker-copy small,.docs-export-doc-picker-copy p{margin:0;color:var(--text-secondary);font-size:11px;line-height:1.5}.docs-export-field-textarea textarea,.docs-export-field>textarea{width:100%;min-height:96px;padding:10px 11px;border:1px solid var(--border-subtle);border-radius:8px;background:var(--surface-raised);color:var(--text-primary);font:inherit;resize:vertical}.docs-export-logo-field{display:grid;gap:10px;padding:12px 13px;border:1px solid var(--border-subtle);border-radius:10px;background:var(--surface-raised)}.docs-export-logo-field-label{font-size:12px;font-weight:600;color:var(--text-secondary)}.docs-export-logo-actions{display:flex;gap:8px}.docs-export-logo-preview{min-height:64px;display:grid;place-items:center;padding:12px;border:1px dashed var(--border-subtle);border-radius:10px;background:color-mix(in srgb,var(--surface-raised) 72%,var(--bg-hover));color:var(--text-secondary);font-size:12px}.docs-export-logo-preview img{max-width:100%;max-height:56px;object-fit:contain}.docs-export-field-textarea textarea:focus,.docs-export-field>textarea:focus{outline:2px solid color-mix(in srgb,var(--selection) 38%,transparent);outline-offset:1px;border-color:color-mix(in srgb,var(--selection) 58%,var(--border-subtle))}.docs-export-publication-shell[data-docs-export-style=report] .docs-export-cover,.docs-export-publication-shell[data-docs-export-style=report] .docs-export-toc,.docs-export-publication-shell[data-docs-export-style=report] .docs-export-doc{border-top:5px solid var(--docs-export-accent-strong)}.docs-export-publication-shell[data-docs-export-style=report] .docs-export-cover-eyebrow,.docs-export-publication-shell[data-docs-export-style=report] .docs-export-doc-eyebrow,.docs-export-publication-shell[data-docs-export-style=report] .docs-export-doc-path,.docs-export-publication-shell[data-docs-export-style=report] .docs-export-toc-row>span{color:var(--docs-export-accent-strong)}.docs-export-publication-shell[data-docs-export-style=report] .docs-export-cover-row{border-top-color:var(--docs-export-line-strong)}@media print{html[data-docs-export-print=true],html[data-docs-export-print=true] body,html[data-docs-export-print=true] #app{width:auto!important;height:auto!important;overflow:visible!important;background:#fffdfa!important}html[data-docs-export-print=true] body{print-color-adjust:exact;-webkit-print-color-adjust:exact}html[data-docs-export-print=true] .shell,html[data-docs-export-print=true] .modal-backdrop,html[data-docs-export-print=true] .command-palette,html[data-docs-export-print=true] .toast,html[data-docs-export-print=true] .doc-editor,html[data-docs-export-print=true] .stress-panel{display:none!important}html[data-docs-export-print=true] .docs-export-studio-backdrop{position:static!important;inset:auto!important;padding:0!important;background:transparent!important;-webkit-backdrop-filter:none!important;backdrop-filter:none!important}html[data-docs-export-print=true] .docs-export-studio{width:auto!important;height:auto!important;display:block!important;border:0!important;border-radius:0!important;box-shadow:none!important;background:transparent!important}html[data-docs-export-print=true] .docs-export-studio-header,html[data-docs-export-print=true] .docs-export-studio-controls,html[data-docs-export-print=true] .docs-export-preview-header{display:none!important}html[data-docs-export-print=true] .docs-export-studio-layout,html[data-docs-export-print=true] .docs-export-preview-shell,html[data-docs-export-print=true] .docs-export-preview-scroll{display:block!important;min-height:0!important;overflow:visible!important;padding:0!important;background:transparent!important}html[data-docs-export-print=true] .docs-export-publication-shell{position:static!important;left:auto!important;top:auto!important;z-index:auto!important;visibility:visible!important;pointer-events:auto!important;width:auto!important}html[data-docs-export-print=true] .docs-export-publication{width:auto!important;padding:0!important;background:transparent!important}html[data-docs-export-print=true] .docs-export-cover,html[data-docs-export-print=true] .docs-export-toc,html[data-docs-export-print=true] .docs-export-doc{min-height:0!important;margin:0!important;border:0!important;box-shadow:none!important;background:transparent!important}html[data-docs-export-print=true] .docs-export-cover,html[data-docs-export-print=true] .docs-export-toc{padding:6mm 6mm 4mm!important}html[data-docs-export-print=true] .docs-export-doc{padding:4mm 6mm 3mm!important}html[data-docs-export-print=true] .docs-export-branch-break{padding:0 0 5mm!important;break-after:avoid-page;page-break-after:avoid}html[data-docs-export-print=true] .docs-export-cover{break-after:page;page-break-after:always}html[data-docs-export-print=true] .docs-export-toc{break-before:auto;break-after:page;page-break-after:always}html[data-docs-export-print=true] .docs-export-doc-break-after{break-after:page;page-break-after:always}}.btn.doc-edit{background:transparent;border-color:var(--border-subtle);color:var(--text-secondary);font-weight:500}.btn.doc-edit:hover{background:var(--bg-hover);color:var(--text-primary)}.capability-doc-actions{align-items:center}.capability-doc-actions .capability-detail-btn,.capability-doc-actions .capability-detail-btn.primary{height:34px;padding:0 13px;border-radius:999px;background:transparent;border-color:color-mix(in srgb,var(--selection) 34%,var(--border-subtle));color:var(--text-primary);font-size:12.5px;font-weight:500}.capability-doc-actions .capability-detail-btn:hover,.capability-doc-actions .capability-detail-btn.primary:hover{background:var(--accent-soft);color:var(--text-primary)}.capability-doc-actions .capability-detail-btn.is-selected{background:var(--accent-secondary);border-color:var(--accent-secondary);color:#fff}.capability-doc-actions .capability-detail-btn.is-selected:hover{background:color-mix(in srgb,var(--accent-secondary) 88%,#000);color:#fff}.markdown-body figure.board-embed.embed-chip{display:flex;align-items:center;gap:8px;padding:12px 16px;border:1px dashed var(--border-subtle);border-radius:10px;background:var(--bg-hover);color:var(--text-secondary);font-size:13px;-webkit-user-select:none;user-select:none;cursor:pointer;outline:none}.markdown-body figure.board-embed.embed-chip:hover,.markdown-body figure.board-embed.embed-chip.selected,.markdown-body figure.board-embed.embed-chip:focus{border-color:color-mix(in srgb,var(--selection) 54%,var(--border-subtle));background:var(--accent-soft);color:var(--text-primary)}.embed-chip-icon{font-size:15px}.embed-chip-remove{margin-left:auto;color:var(--text-secondary);font-size:12px;font-weight:600;opacity:0;transition:opacity .12s ease}.embed-chip:hover .embed-chip-remove,.embed-chip.selected .embed-chip-remove,.embed-chip:focus .embed-chip-remove{opacity:.85}.doc-editor-surface .embed-caret-spacer{min-height:1.35em}.doc-editor{position:fixed;top:0;right:0;bottom:0;left:0;z-index:700;display:flex;flex-direction:column;background:var(--bg-canvas)}.doc-editor-bar{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:10px 16px;border-bottom:1px solid var(--border-subtle);background:var(--bg-canvas)}.doc-editor-bar-left{display:flex;align-items:center;gap:12px;min-width:0}.doc-editor-done{padding:7px 16px;border:1px solid var(--border-subtle);background:transparent;color:var(--text-secondary);border-radius:8px;font-size:13px;font-weight:500;cursor:pointer;transition:background .12s ease,color .12s ease}.doc-editor-done:hover{background:var(--bg-hover);color:var(--text-primary)}.doc-editor-path{font-size:12.5px;color:var(--text-secondary);font-family:SF Mono,ui-monospace,Menlo,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.doc-editor-status{display:inline-flex;align-items:center;gap:8px;font-size:12px;color:var(--text-secondary);opacity:.6;white-space:nowrap}.doc-editor-status[data-state=stale]{color:var(--warning-main);opacity:1;font-weight:600}.doc-editor-status[data-state=error]{color:var(--danger);opacity:1;font-weight:600}.status-action-btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:1px solid currentColor;background:transparent;color:inherit;border-radius:999px;padding:2px 8px;font:inherit;font-weight:600;cursor:pointer}.doc-editor-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:6px;padding:7px 16px;border-bottom:1px solid var(--border-subtle);background:var(--bg-canvas);position:sticky;top:0;z-index:1}.doc-editor-tb-group{display:flex;align-items:center;gap:3px;padding:3px;border:1px solid var(--border-subtle);border-radius:10px;background:color-mix(in srgb,var(--surface-raised) 72%,transparent)}.doc-editor-tb-btn{display:inline-flex;align-items:center;justify-content:center;min-width:32px;height:32px;padding:0 8px;border:none;border-radius:8px;background:transparent;color:var(--text-secondary);font-size:13px;font-weight:600;cursor:pointer;transition:background .12s ease,color .12s ease}.doc-editor-tb-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.doc-editor-tb-btn.active{background:var(--accent-secondary-quiet);color:var(--text-primary);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--accent-secondary) 28%,transparent)}.doc-editor-tb-btn:focus-visible{outline:2px solid var(--selection);outline-offset:2px}.doc-editor-tb-btn.icon svg{width:17px;height:17px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.doc-editor-scroll{flex:1 1 auto;overflow-y:auto}.doc-editor-surface{max-width:820px;margin:0 auto;padding:36px 40px 160px;min-height:100%;outline:none;background:transparent}.doc-editor-surface:focus{outline:none}.doc-editor-surface img{cursor:default}.doc-editor-surface:empty:before{content:"Start writing...";color:var(--text-secondary);opacity:.55}.doc-editor-surface a{cursor:text}.doc-editor-surface pre.code-block{position:relative}.doc-editor-surface pre.code-block:focus-within{box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--selection) 34%,transparent)}.text-file-editor-scroll{padding:0}.text-file-editor-textarea{width:100%;min-height:100%;padding:24px 28px 160px;border:none;background:transparent;color:var(--text-primary);font:14px/1.6 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;resize:none;-moz-tab-size:2;tab-size:2}.text-file-editor-textarea:focus{outline:none}.breadcrumb{display:flex;align-items:center;flex-wrap:wrap;gap:4px;margin-bottom:20px;font-size:12px;font-weight:500;color:var(--text-secondary)}.breadcrumb-link{color:var(--text-secondary);text-decoration:none;padding:2px 4px;border-radius:4px;transition:color .1s ease,background .1s ease}.breadcrumb-link:hover{color:var(--text-primary);background:var(--bg-hover)}.breadcrumb-sep{color:var(--text-secondary);opacity:.4;-webkit-user-select:none;user-select:none;padding:0 2px}.breadcrumb-current{color:var(--text-primary);font-weight:600}.page-nav{display:flex;align-items:stretch;justify-content:space-between;gap:12px;margin-top:56px;padding-top:20px;border-top:1px solid var(--border-subtle)}.page-nav-prev,.page-nav-next{display:flex;align-items:center;gap:10px;padding:12px 16px;border:1px solid var(--border-subtle);border-radius:10px;text-decoration:none;color:var(--text-secondary);background:var(--surface-raised);font-size:13px;font-weight:500;min-width:0;max-width:48%;transition:border-color .12s ease,background .12s ease,color .12s ease}.page-nav-prev:hover,.page-nav-next:hover{border-color:color-mix(in srgb,var(--selection) 45%,var(--border-subtle));background:var(--bg-hover);color:var(--text-primary)}.page-nav-prev{margin-right:auto}.page-nav-next{margin-left:auto;flex-direction:row-reverse}.page-nav-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.page-nav-arrow{flex:0 0 auto;font-size:15px;line-height:1;color:var(--text-secondary);transition:color .12s ease}.page-nav-prev:hover .page-nav-arrow,.page-nav-next:hover .page-nav-arrow{color:var(--text-primary)}.page-nav-spacer{flex:1}.sidebar-brand{border:none;background:none;cursor:pointer;font-size:17px;font-weight:700;letter-spacing:-.01em;color:var(--text-primary);text-decoration:none;padding:0;transition:color .12s ease}.sidebar-brand:hover{color:var(--selection)}.sidebar-brand:focus-visible{outline:2px solid var(--selection);outline-offset:3px;border-radius:4px}.sidebar-docs-mode .sidebar-brand{color:var(--selection)}.sidebar-docs-mode .sidebar-brand:after{content:" ·";opacity:.4}.docs-mode-back-row{padding:8px 0 4px}.docs-mode-back{display:flex;align-items:center;gap:6px;border:none;background:none;cursor:pointer;font-size:12px;font-weight:600;color:var(--text-secondary);padding:5px 8px;border-radius:7px;width:100%;text-align:left;transition:background .1s ease,color .1s ease}.docs-mode-back:hover{background:var(--bg-hover);color:var(--text-primary)}.docs-mode-back svg{width:12px;height:12px;flex:0 0 auto}.docs-mode-tree{padding-left:0}.home-page{padding-top:8px}.home-layout{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:36px;align-items:start}.home-guide{min-width:0}.home-hero-title{font-size:30px;font-weight:750;letter-spacing:-.02em;line-height:1.1;margin:0 0 8px;color:var(--text-primary)}.home-hero-sub{font-size:14px;line-height:1.65;color:var(--text-secondary);margin:0 0 32px}.home-agent-callout{display:flex;align-items:center;justify-content:space-between;gap:14px;margin:0 0 28px;padding:12px 14px;border:1px solid var(--border-subtle);border-radius:10px;background:color-mix(in srgb,var(--surface-raised) 88%,var(--accent-soft))}.home-agent-copy{margin:0;font-size:13px;color:var(--text-secondary);line-height:1.5}.home-agent-btn{flex:0 0 auto}.home-feature{margin-bottom:28px;padding-bottom:28px;border-bottom:1px solid var(--border-subtle)}.home-feature:last-of-type{border-bottom:none}.home-feature-header{margin-bottom:8px}.home-feature-badge{display:inline-block;padding:2px 10px;border-radius:999px;font-size:11px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;border:1px solid var(--border-subtle);background:var(--accent-soft);color:var(--text-secondary)}.home-feature-badge-board{background:var(--nav-board-bg);color:var(--nav-board-color);border-color:color-mix(in srgb,var(--nav-board-color) 25%,var(--border-subtle))}.home-feature-badge-capability{background:var(--nav-cap-bg);color:var(--nav-cap-color);border-color:color-mix(in srgb,var(--nav-cap-color) 25%,var(--border-subtle))}.home-feature-badge-doc{background:var(--accent-soft);color:var(--text-secondary)}.home-feature-body{font-size:13.5px;line-height:1.65;color:var(--text-secondary);margin:0 0 10px}.home-feature-list{margin:0;padding-left:18px;display:flex;flex-direction:column;gap:4px}.home-feature-list li{font-size:13px;line-height:1.55;color:var(--text-secondary)}.home-shortcut-title{font-size:11px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--text-secondary);margin:32px 0 10px}.home-shortcut-table{display:grid;gap:1px;background:var(--border-subtle);border:1px solid var(--border-subtle);border-radius:10px;overflow:hidden}.home-shortcut-row{display:flex;align-items:center;gap:16px;padding:7px 12px;background:var(--surface-raised)}.home-shortcut-row:hover{background:var(--bg-hover)}.home-shortcut-key{flex:0 0 auto;min-width:200px;font:11.5px/1.4 SF Mono,ui-monospace,Menlo,monospace;color:var(--text-primary);background:transparent;border:none;padding:0}.home-shortcut-desc{font-size:12.5px;color:var(--text-secondary)}.home-panel{position:sticky;top:24px;border:1px solid var(--border-subtle);border-radius:12px;background:var(--surface-raised);overflow:hidden}.home-panel-head{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-bottom:1px solid var(--border-subtle)}.home-panel-title{font-size:11px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--text-secondary)}.home-panel-add{padding:4px 10px;font-size:12px;height:auto}.home-panel-empty{padding:20px 14px;font-size:13px;color:var(--text-secondary);line-height:1.5}.home-panel-project{border-bottom:1px solid var(--border-subtle)}.home-panel-project:last-child{border-bottom:none}.home-panel-project-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;padding:10px 14px 6px;text-decoration:none;border-bottom:1px solid var(--border-subtle);transition:background .1s ease}.home-panel-project-head:hover{background:var(--bg-hover)}.home-panel-project-name{font-size:13px;font-weight:650;color:var(--text-primary);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.home-panel-project-meta{flex:0 0 auto;font-size:11px;color:var(--text-secondary);white-space:nowrap}.home-panel-roots{display:flex;flex-direction:column}.home-panel-root{display:flex;align-items:baseline;justify-content:space-between;gap:8px;padding:5px 14px;text-decoration:none;transition:background .1s ease;border-bottom:1px solid color-mix(in srgb,var(--border-subtle) 50%,transparent)}.home-panel-root:last-of-type{border-bottom:none}.home-panel-root:hover{background:var(--bg-hover)}.home-panel-root-path{font-size:11.5px;font-family:SF Mono,ui-monospace,Menlo,monospace;color:var(--text-secondary);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.home-panel-root:hover .home-panel-root-path{color:var(--text-primary)}.home-panel-root-meta{flex:0 0 auto;font-size:10.5px;color:var(--text-secondary);opacity:.7;white-space:nowrap}.home-panel-more{display:block;padding:6px 14px 8px;font-size:11.5px;color:var(--text-secondary);text-decoration:none}.home-panel-more:hover{color:var(--text-primary)}.nav-repo-label-text{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.nav-repo-wiki-link{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:5px;color:var(--text-secondary);opacity:0;transition:opacity .12s ease,background .12s ease;text-decoration:none}.nav-repo-header:hover .nav-repo-wiki-link,.nav-dir-toggle:hover .nav-repo-wiki-link,.nav-dir-header-row:hover .nav-dir-wiki-link,.nav-repo-wiki-link:focus-visible{opacity:1}.nav-dir-project~.nav-dir-wiki-link{opacity:0}.nav-dir-header-row:hover .nav-dir-project~.nav-dir-wiki-link{opacity:.7}.nav-repo-wiki-link:hover{background:var(--bg-active);color:var(--text-primary)}.nav-repo-wiki-link svg{width:12px;height:12px}.wiki-page{padding-top:4px}.wiki-head{margin-bottom:28px;padding-bottom:20px;border-bottom:1px solid var(--border-subtle)}.wiki-back{display:inline-block;font-size:12px;color:var(--text-secondary);text-decoration:none;margin-bottom:10px}.wiki-back:hover{color:var(--text-primary)}.wiki-title{font-size:26px;font-weight:750;letter-spacing:-.01em;margin:0 0 16px}.wiki-stats{display:flex;gap:24px;flex-wrap:wrap}.wiki-stat{text-align:left}.wiki-stat-val{font-size:22px;font-weight:700;font-variant-numeric:tabular-nums;color:var(--text-primary);line-height:1}.wiki-stat-label{font-size:11px;color:var(--text-secondary);margin-top:2px;text-transform:uppercase;letter-spacing:.05em;font-weight:600}.wiki-root-section{margin-bottom:24px;padding-bottom:24px;border-bottom:1px solid var(--border-subtle)}.wiki-root-section:last-child{border-bottom:none}.wiki-root-head{margin-bottom:12px}.wiki-root-path{font-size:12px;font-weight:700;font-family:SF Mono,ui-monospace,Menlo,monospace;color:var(--text-secondary);letter-spacing:.01em}.wiki-root-empty{font-size:12px;color:var(--text-secondary);font-style:italic}.wiki-root-cols{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px}.wiki-col-head{font-size:10.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--text-secondary);margin-bottom:6px;padding-bottom:5px;border-bottom:1px solid var(--border-subtle)}.wiki-col-list{display:flex;flex-direction:column;gap:1px}.wiki-col-item{display:block;padding:4px 6px;border-radius:5px;font-size:12.5px;text-decoration:none;color:var(--text-secondary);transition:background .1s ease,color .1s ease;text-transform:capitalize}.wiki-col-item:hover{background:var(--bg-hover);color:var(--text-primary)}.wiki-col-item-board{color:var(--nav-board-color)}.wiki-col-item-board:hover{background:var(--nav-board-bg);color:var(--nav-board-color)}.wiki-col-item-cap{color:var(--nav-cap-color)}.wiki-col-item-cap:hover{background:var(--nav-cap-bg);color:var(--nav-cap-color)}.wiki-col-item-path{display:block;font-size:10.5px;font-family:SF Mono,ui-monospace,Menlo,monospace;color:var(--text-secondary);opacity:.6;margin-top:1px}@media (max-width: 720px){.sidebar{flex-basis:220px;width:220px}.shell-topbar{padding:10px 12px}.topbar-search{width:auto;flex:1 1 auto}.topbar-search kbd,.topbar-edit span{display:none}.topbar-export,.topbar-edit{width:34px;padding:0;justify-content:center}.capability-layout{grid-template-columns:1fr}.capability-cart{position:static}.content{padding:32px 20px 100px}.doc-editor-surface{padding:24px 18px 140px}}.nav-repo-more{display:flex;align-items:center;justify-content:center;flex:0 0 auto;width:22px;height:22px;padding:0;margin-left:auto;margin-right:2px;background:transparent;border:none;border-radius:4px;color:var(--text-secondary);cursor:pointer;opacity:0;transition:opacity .12s ease,background .12s ease}.nav-repo-header-row:hover .nav-repo-more{opacity:.5}.nav-repo-more:focus-visible{outline:2px solid var(--selection);outline-offset:1px;opacity:1}.nav-repo-more:hover{background:var(--bg-hover);opacity:1!important}.nav-browse-dir-toggle{display:flex;align-items:center;gap:4px;width:100%;padding:4px 8px;background:transparent;border:none;border-radius:6px;color:var(--text-secondary);font-size:13px;cursor:pointer;text-align:left;opacity:.45;transition:opacity .12s ease,background .12s ease}.nav-browse-dir-toggle:hover{background:var(--bg-hover);opacity:.75}.nav-inline-input{display:block;width:100%;padding:4px 8px;margin:1px 0;font:inherit;font-size:13px;color:var(--text-primary);background:var(--bg-canvas);border:1px solid var(--brand-300);border-radius:4px;outline:none;box-shadow:0 0 0 2px #0078d426}.nav-inline-input::placeholder{color:var(--text-secondary);opacity:.6}
package/dist/index.html CHANGED
@@ -6,8 +6,8 @@
6
6
  <link rel="describedby" href="/llms.txt" />
7
7
  <link rel="alternate" type="application/json" href="/.well-known/viteboard.json" />
8
8
  <title>ViteBoard</title>
9
- <script type="module" crossorigin src="/assets/index-CE8x3f28.js"></script>
10
- <link rel="stylesheet" crossorigin href="/assets/index-DvhOK1J_.css">
9
+ <script type="module" crossorigin src="/assets/index-CD-CmqNM.js"></script>
10
+ <link rel="stylesheet" crossorigin href="/assets/index-CD3PKrv7.css">
11
11
  </head>
12
12
  <body>
13
13
  <div id="app"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viteboard",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "description": "Local-first documentation workspace and infinite-canvas whiteboard. Markdown docs, boards, and capability specs as plain files on disk.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/vite.config.ts CHANGED
@@ -2,11 +2,13 @@
2
2
  import { defineConfig, type Plugin, type Connect } from "vite";
3
3
  import { promises as fs } from "node:fs";
4
4
  import * as path from "node:path";
5
+ import os from "node:os";
5
6
  import { execFile } from "node:child_process";
6
7
  import { createHash } from "node:crypto";
7
8
  import { promisify } from "node:util";
8
9
  import type { IncomingMessage, ServerResponse } from "node:http";
9
- import { fileURLToPath } from "node:url";
10
+ import { fileURLToPath, pathToFileURL } from "node:url";
11
+ import { renderPdfFromHtml as renderPdfFromHtmlPortable, sanitizePdfFileName as sanitizePdfFileNamePortable } from "./bin/pdf-export.mjs";
10
12
 
11
13
  const WORKSPACE_DIR = ".workspace";
12
14
  const WORKSPACE_FILE = "workspace.json";
@@ -105,10 +107,176 @@ function contentApiPlugin(): Plugin {
105
107
  res.end(body);
106
108
  }
107
109
 
110
+ function sendBinary(res: ServerResponse, status: number, body: Buffer, type: string, fileName?: string): void {
111
+ res.statusCode = status;
112
+ res.setHeader("Content-Type", type);
113
+ if (fileName) res.setHeader("Content-Disposition", `attachment; filename="${fileName.replace(/[\r\n\"]/g, "-")}"`);
114
+ res.end(body);
115
+ }
116
+
108
117
  async function readMachineAsset(fileName: string): Promise<string> {
109
118
  return fs.readFile(path.join(machineDir, fileName), "utf-8");
110
119
  }
111
120
 
121
+ let chromiumBinaryPromise: Promise<string> | null = null;
122
+
123
+ async function resolveChromiumBinary(): Promise<string> {
124
+ if (chromiumBinaryPromise) return chromiumBinaryPromise;
125
+ chromiumBinaryPromise = (async () => {
126
+ const candidates = ["chromium-browser", "chromium", "google-chrome", "google-chrome-stable"];
127
+ for (const candidate of candidates) {
128
+ try {
129
+ await execFileAsync("which", [candidate]);
130
+ return candidate;
131
+ } catch {
132
+ /* keep trying */
133
+ }
134
+ }
135
+ throw new Error("No Chromium-compatible browser is available for PDF export.");
136
+ })();
137
+ return chromiumBinaryPromise;
138
+ }
139
+
140
+ function sanitizePdfFileName(input: string): string {
141
+ const cleaned = String(input ?? "").trim().replace(/[\\/:*?"<>|]+/g, "-").replace(/\s+/g, " ").slice(0, 120) || "export";
142
+ return cleaned.toLowerCase().endsWith(".pdf") ? cleaned : `${cleaned}.pdf`;
143
+ }
144
+
145
+ function mmToInches(mm: number): number {
146
+ return mm / 25.4;
147
+ }
148
+
149
+ async function openCdpPage(port: string, url: string): Promise<string> {
150
+ const res = await fetch(`http://127.0.0.1:${port}/json/new?about:blank`, { method: "PUT" });
151
+ const target = await res.json() as { webSocketDebuggerUrl: string };
152
+ if (!target.webSocketDebuggerUrl) throw new Error("Could not open Chromium page target for PDF export.");
153
+ return target.webSocketDebuggerUrl;
154
+ }
155
+
156
+ async function printPdfViaCdp(wsUrl: string, url: string, pdf: {
157
+ displayHeaderFooter: boolean;
158
+ headerTemplate?: string;
159
+ footerTemplate?: string;
160
+ marginTopMm?: number;
161
+ marginBottomMm?: number;
162
+ marginLeftMm?: number;
163
+ marginRightMm?: number;
164
+ }): Promise<Buffer> {
165
+ const ws = new WebSocket(wsUrl);
166
+ let nextId = 0;
167
+ const pending = new Map<number, { resolve: (value: any) => void; reject: (error: Error) => void }>();
168
+ let loadResolve: (() => void) | null = null;
169
+ const loaded = new Promise<void>((resolve) => { loadResolve = resolve; });
170
+ ws.onmessage = (event) => {
171
+ const message = JSON.parse(String(event.data));
172
+ if (message.method === "Page.loadEventFired") {
173
+ loadResolve?.();
174
+ loadResolve = null;
175
+ return;
176
+ }
177
+ if (!message.id) return;
178
+ const entry = pending.get(message.id);
179
+ if (!entry) return;
180
+ pending.delete(message.id);
181
+ if (message.error) entry.reject(new Error(message.error.message || "CDP error"));
182
+ else entry.resolve(message.result);
183
+ };
184
+ const opened = new Promise<void>((resolve, reject) => {
185
+ ws.onopen = () => resolve();
186
+ ws.onerror = () => reject(new Error("Could not connect to Chromium PDF export session."));
187
+ });
188
+ const send = (method: string, params: Record<string, unknown> = {}) => new Promise<any>((resolve, reject) => {
189
+ const id = ++nextId;
190
+ pending.set(id, { resolve, reject });
191
+ ws.send(JSON.stringify({ id, method, params }));
192
+ });
193
+ await opened;
194
+ try {
195
+ await send("Page.enable");
196
+ await send("Runtime.enable");
197
+ await send("Page.navigate", { url });
198
+ await loaded;
199
+ await send("Runtime.evaluate", {
200
+ expression: "document.fonts ? document.fonts.ready.then(() => true) : true",
201
+ awaitPromise: true,
202
+ returnByValue: true,
203
+ });
204
+ const result = await send("Page.printToPDF", {
205
+ printBackground: true,
206
+ preferCSSPageSize: true,
207
+ displayHeaderFooter: pdf.displayHeaderFooter,
208
+ headerTemplate: pdf.headerTemplate ?? "<div></div>",
209
+ footerTemplate: pdf.footerTemplate ?? "<div></div>",
210
+ marginTop: mmToInches(pdf.marginTopMm ?? 12),
211
+ marginBottom: mmToInches(pdf.marginBottomMm ?? 12),
212
+ marginLeft: mmToInches(pdf.marginLeftMm ?? 12),
213
+ marginRight: mmToInches(pdf.marginRightMm ?? 12),
214
+ }) as { data: string };
215
+ return Buffer.from(result.data, "base64");
216
+ } finally {
217
+ try { ws.close(); } catch { /* ignore */ }
218
+ }
219
+ }
220
+
221
+ async function renderPdfFromHtml(html: string, fileName: string, pdf: {
222
+ displayHeaderFooter?: boolean;
223
+ headerTemplate?: string;
224
+ footerTemplate?: string;
225
+ marginTopMm?: number;
226
+ marginBottomMm?: number;
227
+ marginLeftMm?: number;
228
+ marginRightMm?: number;
229
+ } = {}): Promise<Buffer> {
230
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "viteboard-pdf-"));
231
+ const htmlPath = path.join(tmpDir, "publication.html");
232
+ const profileDir = path.join(tmpDir, "profile");
233
+ const browser = await resolveChromiumBinary();
234
+ let browserProcess: import("node:child_process").ChildProcess | null = null;
235
+ try {
236
+ await fs.writeFile(htmlPath, html, "utf-8");
237
+ await fs.mkdir(profileDir, { recursive: true });
238
+ browserProcess = execFile(browser, [
239
+ "--headless",
240
+ "--disable-gpu",
241
+ "--no-sandbox",
242
+ `--user-data-dir=${profileDir}`,
243
+ "--remote-debugging-port=0",
244
+ "about:blank",
245
+ ], { windowsHide: true });
246
+ const activePortPath = path.join(profileDir, "DevToolsActivePort");
247
+ for (let attempt = 0; attempt < 100; attempt += 1) {
248
+ try {
249
+ const raw = await fs.readFile(activePortPath, "utf-8");
250
+ const port = raw.split(/\r?\n/)[0]?.trim();
251
+ if (!port) throw new Error("Chromium debugging port missing.");
252
+ const wsUrl = await openCdpPage(port, pathToFileURL(htmlPath).href);
253
+ return await printPdfViaCdp(wsUrl, pathToFileURL(htmlPath).href, {
254
+ displayHeaderFooter: Boolean(pdf.displayHeaderFooter),
255
+ headerTemplate: pdf.headerTemplate,
256
+ footerTemplate: pdf.footerTemplate,
257
+ marginTopMm: pdf.marginTopMm,
258
+ marginBottomMm: pdf.marginBottomMm,
259
+ marginLeftMm: pdf.marginLeftMm,
260
+ marginRightMm: pdf.marginRightMm,
261
+ });
262
+ } catch {
263
+ await new Promise((resolve) => setTimeout(resolve, 100));
264
+ }
265
+ }
266
+ throw new Error("Timed out starting Chromium PDF export session.");
267
+ } finally {
268
+ if (browserProcess) {
269
+ const exited = new Promise<void>((resolve) => {
270
+ browserProcess?.once("exit", () => resolve());
271
+ browserProcess?.once("close", () => resolve());
272
+ });
273
+ try { browserProcess.kill(); } catch { /* ignore */ }
274
+ await Promise.race([exited, new Promise((resolve) => setTimeout(resolve, 500))]);
275
+ }
276
+ await fs.rm(tmpDir, { recursive: true, force: true });
277
+ }
278
+ }
279
+
112
280
  function revisionForText(content: string): string {
113
281
  return createHash("sha1").update(content).digest("hex");
114
282
  }
@@ -966,6 +1134,15 @@ function contentApiPlugin(): Plugin {
966
1134
  return send(res, 200, { ok: true, removed });
967
1135
  }
968
1136
 
1137
+ if (route === "/__api/export/pdf" && method === "POST") {
1138
+ const { html, fileName, pdf } = JSON.parse(await readBody(req));
1139
+ if (typeof html !== "string" || !html.trim()) {
1140
+ return send(res, 400, { error: "Missing HTML payload" });
1141
+ }
1142
+ const pdfBuffer = await renderPdfFromHtmlPortable(html, String(fileName ?? "export.pdf"), typeof pdf === "object" && pdf ? pdf : {});
1143
+ return sendBinary(res, 200, pdfBuffer, "application/pdf", sanitizePdfFileNamePortable(String(fileName ?? "export.pdf")));
1144
+ }
1145
+
969
1146
  if (route === "/__api/workspace-file" && method === "GET") {
970
1147
  await ensureWorkspaceDir();
971
1148
  const manifest = await loadManifest();