stillmade-sdk 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.stillmade/sdk-baseline.json +1 -1
  2. package/AGENTS.md +8 -2
  3. package/DESKTOP_CAPABILITIES.md +1 -1
  4. package/INTERFACE_DESIGN.md +167 -83
  5. package/MODULES.md +1 -1
  6. package/NATIVE_MCP.md +1 -1
  7. package/PASTE_TO_LLM.md +151 -12
  8. package/README.md +5 -2
  9. package/SDK.md +510 -115
  10. package/UI_AESTHETIC.md +64 -0
  11. package/blocks/native/sm.export-video/versions/2.1.0/package.js +580 -0
  12. package/blocks/native/sm.panel-strip/versions/2.4.0/package.js +1176 -0
  13. package/blocks/native/sm.panel-strip/versions/2.4.1/package.js +1316 -0
  14. package/blocks/native/sm.shot-plan/versions/2.2.0/package.js +800 -0
  15. package/blocks/native/sm.shot-plan/versions/2.2.1/package.js +920 -0
  16. package/examples/README.md +10 -0
  17. package/examples/custom-interface.stillmade.json +3 -3
  18. package/package.json +7 -1
  19. package/packages/block-cli/dev-host/assets/host.js +214 -28
  20. package/packages/block-cli/dev-host/assets/index.css +1 -1
  21. package/packages/block-cli/format.js +1 -1
  22. package/packages/block-cli/templates.js +53 -22
  23. package/packages/block-platform/block-readiness.js +5 -0
  24. package/packages/block-platform/brief-ports.js +29 -0
  25. package/packages/block-platform/catalog.js +32 -9
  26. package/packages/block-platform/current-semantic-source.js +7 -3
  27. package/packages/block-platform/flow-inspection.js +14 -4
  28. package/packages/block-platform/plain-descriptions.js +1 -1
  29. package/packages/block-platform/project-context.js +7 -0
  30. package/packages/block-platform/project-input-samples.js +3 -1
  31. package/packages/block-platform/project-inputs.js +11 -2
  32. package/packages/block-platform/project-start-input.js +46 -0
  33. package/packages/block-platform/step-groups.js +2 -0
  34. package/packages/block-platform/steps.js +55 -4
  35. package/packages/block-platform/type-preview.js +19 -3
  36. package/packages/block-platform/workspace-input-readset.js +6 -1
  37. package/packages/block-sdk/builder-contract.js +11 -5
  38. package/packages/block-sdk/collaboration-admission.js +4 -2
  39. package/packages/block-sdk/connection-adapters.js +35 -2
  40. package/packages/block-sdk/contracts.js +5 -2
  41. package/packages/block-sdk/examples.js +3 -3
  42. package/packages/block-sdk/first-party-identities.js +190 -0
  43. package/packages/block-sdk/first-party-packages.js +6 -1
  44. package/packages/block-sdk/index.d.ts +2 -0
  45. package/packages/block-sdk/index.js +1 -1
  46. package/packages/block-sdk/javascript.js +5 -1
  47. package/packages/block-sdk/platform-checks.js +13 -2
  48. package/packages/block-sdk/shared-agent-controls.js +5 -4
  49. package/packages/block-sdk/shared-controls.js +13 -9
  50. package/packages/block-sdk/view-design.js +104 -27
  51. package/packages/block-sdk/view.js +4 -2
  52. package/packages/design-system/block-kit.css +183 -0
  53. package/packages/design-system/block-kit.html +187 -0
@@ -1 +1 @@
1
- @font-face{font-family:Hanken Grotesk;font-style:normal;font-weight:100 900;font-display:swap;src:url(/fonts/HankenGrotesk-Variable.ttf)format("truetype")}@font-face{font-family:Hanken Grotesk;font-style:italic;font-weight:100 900;font-display:swap;src:url(/fonts/HankenGrotesk-Italic-Variable.ttf)format("truetype")}@font-face{font-family:JetBrains Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(/fonts/JetBrainsMono-Variable.ttf)format("truetype")}@font-face{font-family:JetBrains Mono;font-style:italic;font-weight:100 900;font-display:swap;src:url(/fonts/JetBrainsMono-Italic-Variable.ttf)format("truetype")}:root{--cream-50:#fdfaf1;--cream-100:#f7f0db;--cream-200:#ece3c9;--cream-300:#dcd2b5;--cream-600:#4e483a;--ink-50:#f2f0ec;--ink-100:#c9c6be;--ink-300:#4a4843;--ink-400:#26241f;--ink-500:#16140f;--ink-600:#0a0a0a;--signal:#e8b23a;--signal-dim:#e8b23a26;--signal-glow:#e8b23a4d;--font-display:"Balgin", "Hanken Grotesk", ui-sans-serif, sans-serif;--font-body:"Hanken Grotesk", ui-sans-serif, sans-serif;--font-mono:"JetBrains Mono", ui-monospace, monospace;--font:var(--font-body);--fs-display-xl:clamp(72px, 14vw, 220px);--fs-display-l:clamp(56px, 9vw, 144px);--fs-display-m:clamp(40px, 6vw, 96px);--fs-h4:1.25rem;--fs-body:1rem;--fs-body-s:.875rem;--fs-caption:.8125rem;--fs-micro:.75rem;--tr-display:-.02em;--tr-body:-.005em;--tr-micro:.08em;--lh-display:.92;--lh-tight:1.05;--s-1:4px;--s-2:8px;--s-3:12px;--s-4:16px;--s-5:24px;--s-6:32px;--s-7:48px;--s-8:64px;--s-9:96px;--s-10:128px;--s-11:192px;--page-gutter:clamp(20px, 3vw, 48px);--page-gutter-compact:clamp(16px, 5vw, 24px);--page-section-gap:clamp(32px, 5vw, 64px);--content-readable:720px;--content-wide:1440px;--control-height:40px;--touch-target:44px;--mobile-dock-height:66px;--z-nav:30;--hover:var(--hover-wash);--r-0:0;--r-1:2px;--r-2:4px;--r-3:8px;--r-pill:999px;--radius-sm:var(--r-2);--radius:var(--r-3);--radius-lg:var(--r-3);--bw-hair:.5px;--bw-1:1px;--bw-2:2px;--ease-out:cubic-bezier(.22, 1, .36, 1);--ease-in-out:cubic-bezier(.65, 0, .35, 1);--ease-spring:cubic-bezier(.34, 1.56, .64, 1);--dur-instant:80ms;--dur-fast:.16s;--dur-base:.24s;--dur-slow:.48s;--dur-reveal:.72s}:root,[data-mode=dark]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--bg:#101010;--bg-elev-1:#232221;--bg-elev-2:#33312f;--bg-inset:#171716;--fg:#f3eee5;--fg-muted:#bbb2a7;--fg-subtle:#f3eee594;--fg-on-accent:#191716;--border-tok:#58534d;--border-strong:#8b7e6f;--divider:#393735;--signal:#d0ae77;--signal-dim:#d0ae7726;--signal-glow:#d0ae774d;--hover-wash:#f7f0db0f;--press-wash:#f7f0db1f;--selection-bg:var(--cream-100);--selection-fg:var(--ink-600);--shadow-1:0 1px 0 #f7f0db0a inset, 0 4px 12px #00000080;--shadow-2:0 1px 0 #f7f0db0f inset, 0 16px 48px #0009;--scrim-top:linear-gradient(to bottom, #0a0a0ad9, #0a0a0a00);--scrim-bottom:linear-gradient(to top, #0a0a0ad9, #0a0a0a00);--bg-primary:var(--bg);--bg-secondary:var(--bg-elev-1);--bg-tertiary:var(--bg-elev-2);--bg-elevated:var(--bg-elev-1);--bg-hover:#403d3a;--border:var(--border-tok);--border-light:var(--border-strong);--accent:var(--fg);--accent-hover:var(--cream-50);--accent-dim:var(--hover-wash);--accent-glow:#f7f0db38;--accent-contrast:var(--bg);--editor-standout-bg:var(--fg);--editor-standout-fg:var(--bg);--text-primary:var(--fg);--text-secondary:var(--fg-muted);--text-muted:var(--fg-subtle);--success:var(--fg);--warning:var(--signal);--danger:var(--signal);--shadow:var(--shadow-1);--shadow-lg:var(--shadow-2)}[data-mode=light]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--signal:#e8b23a;--signal-dim:#e8b23a26;--signal-glow:#e8b23a4d;--bg:var(--cream-100);--bg-elev-1:var(--cream-50);--bg-elev-2:var(--cream-200);--bg-inset:var(--cream-200);--fg:var(--ink-600);--fg-muted:var(--cream-600);--fg-subtle:#0a0a0a61;--fg-on-accent:var(--cream-100);--border-tok:#0a0a0a1a;--border-strong:#0a0a0a38;--divider:#0a0a0a0f;--hover-wash:#0a0a0a0d;--press-wash:#0a0a0a1a;--selection-bg:var(--ink-600);--selection-fg:var(--cream-100);--shadow-1:0 1px 0 #ffffff80 inset, 0 4px 12px #0a0a0a0f;--shadow-2:0 1px 0 #fff9 inset, 0 16px 48px #0a0a0a1a;--scrim-top:linear-gradient(to bottom, #f7f0dbe6, #f7f0db00);--scrim-bottom:linear-gradient(to top, #f7f0dbe6, #f7f0db00);--bg-primary:var(--bg);--bg-secondary:var(--bg-elev-1);--bg-tertiary:var(--bg-elev-2);--bg-elevated:var(--bg-elev-2);--bg-hover:var(--cream-300);--border:var(--border-tok);--border-light:var(--border-strong);--accent:var(--fg);--accent-hover:var(--ink-500);--accent-dim:var(--hover-wash);--accent-glow:#0a0a0a38;--accent-contrast:var(--bg);--editor-standout-bg:var(--ink-300);--editor-standout-fg:var(--cream-100);--text-primary:var(--fg);--text-secondary:var(--fg-muted);--text-muted:var(--fg-subtle);--success:var(--fg);--warning:var(--signal);--danger:var(--signal);--shadow:var(--shadow-1);--shadow-lg:var(--shadow-2)}[data-mode=white]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--bg:#e9e8e6;--bg-elev-1:#fff;--bg-elev-2:#ebe9e6;--bg-inset:#f7f6f4;--fg:#1a1917;--fg-muted:#615c56;--fg-subtle:#77716b;--fg-on-accent:#fff;--border-tok:#bcb8b2;--border-strong:#978b7d;--divider:#cdc8c2;--hover-wash:#00000014;--press-wash:#00000021;--selection-bg:#1a1917;--selection-fg:#fff;--signal:#8e6b46;--signal-dim:#8e6b461a;--signal-glow:#8e6b4638;--shadow-1:0 1px 3px #0000000a;--shadow-2:0 12px 40px #0000001a;--scrim-top:linear-gradient(to bottom,#e9e8e6e6,#e9e8e600);--scrim-bottom:linear-gradient(to top,#e9e8e6e6,#e9e8e600);--bg-primary:var(--bg);--bg-secondary:var(--bg-elev-1);--bg-tertiary:var(--bg-elev-2);--bg-elevated:var(--bg-elev-1);--bg-hover:#e0ddd8;--border:var(--border-tok);--border-light:var(--border-strong);--accent:var(--fg);--accent-hover:#33302c;--accent-dim:var(--hover-wash);--accent-glow:#0000001f;--accent-contrast:var(--bg);--editor-standout-bg:#24211d;--editor-standout-fg:#fff;--text-primary:var(--fg);--text-secondary:var(--fg-muted);--text-muted:var(--fg-subtle);--success:var(--fg);--warning:var(--signal);--danger:var(--signal);--shadow:var(--shadow-1);--shadow-lg:var(--shadow-2)}button[data-icon-action=true]{cursor:pointer;flex-shrink:0;line-height:1;min-width:36px!important;min-height:36px!important;box-shadow:none!important;color:var(--text-secondary,var(--fg))!important;background:0 0!important;border:0!important;border-radius:0!important;justify-content:center!important;align-items:center!important;padding:8px!important;display:inline-flex!important}button[data-icon-action=true]:hover:not(:disabled){color:var(--text-primary,var(--fg))!important}button[data-icon-action=true]:focus-visible{outline-offset:2px;outline:2px solid var(--signal)!important}button[data-icon-action=true]:disabled{cursor:not-allowed;opacity:.4!important}button[data-icon-action=true][aria-pressed=true]{text-underline-offset:5px;text-decoration:underline;color:var(--text-primary,var(--fg))!important}@media (pointer:coarse){button[data-icon-action=true]{min-width:44px!important;min-height:44px!important}}*,:before,:after{box-sizing:border-box;margin:0;padding:0}:root[data-anim-theme="1"]:not([data-mode=white]){--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--bg:#0a0d13;--bg-elev-1:#0f141c;--bg-elev-2:#1a2230;--bg-inset:#0f141c;--fg:#d8dee8;--fg-muted:#8b98ac;--fg-subtle:#64748b;--fg-on-accent:#0a0d13;--border-tok:#232d3d;--border-strong:#303d50;--divider:#1a2230;--hover-wash:#8b98ac14;--press-wash:#8b98ac29;--selection-bg:#303d50;--selection-fg:#d8dee8;--bg-primary:#0a0d13;--bg-secondary:#0f141c;--bg-tertiary:#1a2230;--bg-elevated:#1a2230;--bg-hover:#232d3d;--border:#232d3d;--border-light:#303d50;--accent:#8b98ac;--accent-hover:#b6c0ce;--accent-dim:#8b98ac1f;--accent-glow:#8b98ac40;--accent-contrast:#0a0d13;--editor-standout-bg:#d8dee8;--editor-standout-fg:#0a0d13;--text-primary:#d8dee8;--text-secondary:#8b98ac;--text-muted:#64748b;--success:#8b98ac;--warning:#b6c0ce;--danger:#b6c0ce}html,body{background:var(--bg);width:100%;height:100%;color:var(--fg);font-family:var(--font-body);letter-spacing:var(--tr-body);-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility;font-size:14px;line-height:1.6;overflow:hidden}#root{width:100%;height:100%;overflow:hidden}.sm-app-shell{height:100dvh}@keyframes sm-navmenu-in{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}.mobile-nav-menu{animation:.2s cubic-bezier(.22,1,.36,1) sm-navmenu-in}::selection{background:var(--selection-bg);color:var(--selection-fg)}.display,h1,h2{font-family:var(--font-display);font-style:italic;font-weight:200;line-height:var(--lh-tight);letter-spacing:var(--tr-display);margin:0}.display{line-height:var(--lh-display)}h3,h4{font-family:var(--font-body);font-weight:500;line-height:var(--lh-tight);letter-spacing:-.01em;margin:0}.eyebrow{text-transform:uppercase;letter-spacing:var(--tr-micro);color:var(--fg-muted);font-size:11px;font-weight:500}code,pre,.mono{font-family:var(--font-mono);font-size:.92em}button{font-family:var(--font-body);cursor:pointer;letter-spacing:var(--tr-body);border:none;outline:none}textarea,input,select{font-family:var(--font-body);outline:none}select{background-color:var(--bg-elev-1);color:var(--text-primary);border:1px solid var(--border);border-radius:var(--r-2)}select option,select optgroup{background-color:var(--bg-elevated);color:var(--text-primary)}select option:checked{font-weight:600}.tsel-trigger{background-color:var(--bg-elev-1);color:var(--text-primary);border:1px solid var(--border);border-radius:var(--r-2);font-family:var(--font-body);font-size:inherit;cursor:pointer;text-align:left;justify-content:space-between;align-items:center;gap:8px;padding:6px 10px;display:inline-flex}.tsel-trigger:hover{border-color:var(--border-strong)}.tsel-trigger:focus-visible{outline:2px solid var(--accent);outline-offset:1px}.tsel-trigger:disabled{opacity:.5;cursor:default}.tsel-label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.tsel-caret{opacity:.7;flex:none;font-size:10px}.tsel-pop{z-index:100100;background:var(--bg-elevated);border:1px solid var(--border);border-radius:var(--r-2);max-height:260px;padding:4px;overflow-y:auto;box-shadow:0 8px 28px #00000047}.tsel-opt{color:var(--text-primary);cursor:pointer;white-space:nowrap;border-radius:6px;align-items:center;gap:6px;padding:6px 8px;font-size:13px;display:flex}.tsel-opt[data-active=true]{background:var(--bg-elev-1)}.tsel-opt[aria-selected=true]{font-weight:600}.tsel-opt[aria-disabled=true]{color:var(--text-muted);cursor:default}.tsel-opt[aria-disabled=true][data-active=true]{background:0 0}.tsel-check{opacity:.8;flex:none;width:12px;font-size:11px}.tsel-opt-label{text-overflow:ellipsis;flex:auto;overflow:hidden}.tsel-note{cursor:help;width:16px;height:16px;color:var(--text-muted);border:1px solid var(--border);border-radius:999px;flex:none;justify-content:center;align-items:center;font-size:11px;display:inline-flex;position:relative}.tsel-note:hover{color:var(--text-primary);border-color:var(--border-strong)}.tsel-tip{z-index:100110;background:var(--text-primary);color:var(--bg-elevated);box-sizing:border-box;white-space:normal;pointer-events:none;border-radius:5px;max-width:min(260px,100vw - 16px);padding:4px 8px;font-size:11px;font-weight:600;box-shadow:0 2px 10px #0000004d}@media (width<=767.98px),(height<=480px) and (pointer:coarse){.tsel-opt{box-sizing:border-box;min-height:44px;font-size:14px}.tsel-note{flex:0 0 44px;width:44px;height:44px;font-size:14px}}.video-editor input,.video-editor select,.video-editor textarea{background:var(--bg-inset);width:100%;min-width:0;color:var(--fg);border:var(--bw-1) solid var(--border-strong);border-radius:var(--r-2);font-family:var(--font-body);accent-color:var(--signal);padding:7px 8px;font-size:12px}.video-editor,.mobile-editor-root{-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}.video-editor input,.video-editor select,.video-editor textarea,.video-editor [contenteditable=true],.mobile-editor-root input,.mobile-editor-root select,.mobile-editor-root textarea,.mobile-editor-root [contenteditable=true]{-webkit-user-select:text;user-select:text;-webkit-touch-callout:default}.video-editor input[type=range]{background:0 0;border:0;padding:0}.video-editor input[type=checkbox]{width:auto;min-width:auto}.video-editor select:focus,.video-editor input:focus,.video-editor textarea:focus{border-color:var(--signal);box-shadow:0 0 0 1px var(--signal-dim)}:focus-visible{outline:2px solid var(--signal);outline-offset:2px}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:var(--border-strong);border-radius:var(--r-pill)}::-webkit-scrollbar-thumb:hover{background:var(--fg-subtle)}.sm-hide-scrollbar{scrollbar-width:none;-ms-overflow-style:none}.sm-hide-scrollbar::-webkit-scrollbar{width:0;height:0;display:none}.btn{align-items:center;gap:var(--s-2);padding:var(--s-2) var(--s-4);border-radius:var(--r-pill);letter-spacing:var(--tr-body);transition:transform var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), opacity var(--dur-fast) var(--ease-out);font-size:13px;font-weight:500;display:inline-flex}.btn:active{transform:scale(.98)}.btn-primary{background:var(--fg);color:var(--bg)}.btn-primary:hover{background:var(--fg);opacity:.86}.btn-primary:disabled{opacity:.4;cursor:not-allowed}.btn-secondary{color:var(--fg);border:var(--bw-1) solid var(--border-strong);background:0 0}.btn-secondary:hover{background:var(--hover-wash);border-color:var(--fg)}.btn-ghost{color:var(--fg-muted);background:0 0;padding:6px 10px}.btn-ghost:hover{background:var(--hover-wash);color:var(--fg)}.btn-icon{color:var(--fg-muted);border-radius:var(--r-2);transition:background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);background:0 0;justify-content:center;align-items:center;padding:5px;display:inline-flex}.btn-icon:hover{background:var(--hover-wash);color:var(--fg)}.btn-icon:active{transform:scale(.98)}.btn-icon:disabled{opacity:.4;cursor:not-allowed}.typing-indicator{align-items:center;gap:4px;padding:4px 0;display:flex}.typing-indicator span{border-radius:var(--r-pill);background:var(--fg-subtle);width:6px;height:6px;animation:1.2s infinite bounce}.typing-indicator span:nth-child(2){animation-delay:.2s}.typing-indicator span:nth-child(3){animation-delay:.4s}@keyframes bounce{0%,60%,to{opacity:.4;transform:translateY(0)}30%{opacity:1;transform:translateY(-5px)}}@keyframes fadeIn{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{0%,to{opacity:.4;transform:scale(.9)}50%{opacity:1;transform:scale(1.1)}}@keyframes toastIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}button:focus-visible,[role=button]:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:4px}@keyframes skeletonShimmer{0%{background-position:-200% 0}to{background-position:200% 0}}.skeleton{background:linear-gradient(90deg,#ffffff08 0%,#ffffff1a 50%,#ffffff08 100%) 0 0/200% 100%;border-radius:6px;animation:1.4s ease-in-out infinite skeletonShimmer}.node-state-empty{border-style:dashed!important}.node-state-generating{box-shadow:0 0 0 2px var(--accent);position:relative}.node-state-generating:after{content:"";border-radius:inherit;pointer-events:none;will-change:opacity;animation:1.6s ease-in-out infinite nodePulse;position:absolute;inset:-2px;box-shadow:0 0 18px #f7c8328c}@keyframes nodePulse{0%,to{opacity:.36}50%{opacity:1}}.node-state-failed{border-top:4px solid #d04848!important}.node-state-failed:before{content:"⚠ ";color:#d04848;z-index:2;pointer-events:none;font-size:12px;font-weight:800;position:absolute;top:6px;left:8px}.react-flow__node{cursor:default}.react-flow__node.dragging,.react-flow__pane.dragging .react-flow__viewport{will-change:transform}.react-flow__node:not(.react-flow__node-sceneGroupNode):not(.react-flow__node-shotLabelNode):not(.react-flow__node-stickyNoteNode):not(.react-flow__node-transitionNode){box-shadow:1px 1px 0 var(--node-side,#d8cfb3), 2px 2px 0 var(--node-side,#d8cfb3), 3px 3px 0 var(--node-side,#d8cfb3), 4px 4px 0 var(--node-side,#d8cfb3), 5px 5px 0 var(--node-side,#d8cfb3), 6px 6px 0 var(--node-side,#d8cfb3), 7px 7px 0 var(--node-edge,#0a0a0a2e), 14px 16px 18px -4px #0a0a0a2e;border-radius:10px}[data-mode=dark] .react-flow__node:not(.react-flow__node-sceneGroupNode):not(.react-flow__node-shotLabelNode):not(.react-flow__node-stickyNoteNode):not(.react-flow__node-transitionNode){--node-side:#1f1d18;--node-edge:#f7f0db2e}.react-flow__controls{grid-template-columns:repeat(2,1fr)!important;display:grid!important}.react-flow__controls button{background:var(--bg-elev-2)!important;border-color:var(--border-tok)!important;color:var(--fg-muted)!important;fill:var(--fg-muted)!important}.react-flow__controls button:hover{background:var(--ink-300)!important}.react-flow__edge-path{stroke:var(--border-strong)}.react-flow.rf-edges-panning .react-flow__edges{display:none}.react-flow__node.node-controls-locked input,.react-flow__node.node-controls-locked textarea,.react-flow__node.node-controls-locked select,.react-flow__node.node-controls-locked button,.react-flow__node.node-controls-locked [role=button]{pointer-events:none!important}.react-flow__node.node-controls-locked{cursor:grab}.react-flow__node.node-controls-locked:active{cursor:grabbing}.react-flow__node.scene-add-available-node{filter:drop-shadow(0 0 10px #f7f0db38)}.react-flow__node.scene-add-available-node>div{outline:1px solid var(--border-strong);outline-offset:4px;border-radius:var(--r-3)}.react-flow__node.scene-add-available-node:after{content:"";border:1px dashed var(--border-strong);border-radius:var(--r-3);pointer-events:none;position:absolute;inset:-8px}.react-flow__node.node-replace-available{filter:drop-shadow(0 0 10px #f7f0db38)}.react-flow__node.node-replace-available>div{outline:1px solid var(--border-strong);outline-offset:4px;border-radius:var(--r-3)}.react-flow__node.node-replace-available:after{content:"";border:1px dashed var(--border-strong);border-radius:var(--r-3);pointer-events:none;position:absolute;inset:-8px}select option{background:var(--bg-elev-2);color:var(--fg)}.spinner{border:2px solid var(--border-tok);border-top-color:var(--fg);border-radius:var(--r-pill);width:16px;height:16px;animation:.7s linear infinite spin}.media-upload-preview:hover .media-upload-overlay{opacity:1!important}@media (prefers-reduced-motion:reduce){*,:before,:after{transition-duration:1ms!important;animation-duration:1ms!important}}.assets-card .assets-card-overlay{opacity:0;transition:opacity .15s}.assets-card:hover .assets-card-overlay{opacity:1}.yt-video-row{transition:background .12s,box-shadow .12s,color .12s}.yt-video-row:hover{box-shadow:inset 2px 0 0 var(--accent), inset 0 0 14px var(--accent-glow);color:var(--text-primary);background:var(--hover-wash)!important}.chat-bubble ::selection{background:var(--signal);color:#0a0a0a}.chat-bubble ::selection{background:var(--signal);color:#0a0a0a}.chat-bubble:hover .chat-bubble-copy,.chat-bubble:focus-within .chat-bubble-copy,.chat-bubble-copy:hover,.chat-bubble-copy:focus{opacity:1!important}.chat-bubble-copy:hover{background:var(--bg-elevated-2,var(--bg-elevated));color:var(--text-primary)!important}.chat-bubble-copy:active{transform:scale(.94)}.sm-brand .sm-brandmark{transform-origin:50%;transition:transform .76s cubic-bezier(.2,.8,.2,1),filter .28s}.sm-brand:hover .sm-brandmark{filter:drop-shadow(0 0 7px color-mix(in srgb, currentColor 45%, transparent));will-change:transform, filter;transform:perspective(340px)rotateY(360deg)scale(1.06)}.sm-brand .sm-word{transition:transform .3s cubic-bezier(.2,.8,.2,1);transform:translate(-4px)}.sm-brand:hover .sm-word{will-change:transform;transform:translate(-4px,-1px)}@media (prefers-reduced-motion:reduce){.sm-brand .sm-brandmark{transition:transform .24s,filter .24s}.sm-brand:hover .sm-brandmark{transform:scale(1.06)}.sm-brand:hover .sm-word{transform:translate(-4px)}}body.sm-mobile-ui input,body.sm-mobile-ui textarea,body.sm-mobile-ui select{font-size:16px!important}body.sm-mobile-ui button,body.sm-mobile-ui select,body.sm-mobile-ui [role=button]{min-height:44px}body.sm-mobile-ui select:not([multiple]):not([size]){height:max(44px,2.75em)}body.sm-mobile-ui button.btn-icon,body.sm-mobile-ui button[data-compact],body.sm-mobile-ui .no-min-tap{min-height:0}body.sm-mobile-ui .yt-video-row{min-width:560px}html,body{background:var(--bg);color:var(--fg);margin:0}.dh-page{min-height:100dvh;font-family:var(--font-body);flex-direction:column;display:flex}.dh-header{justify-content:space-between;align-items:flex-end;gap:var(--s-4,16px);padding:var(--s-5,20px) var(--s-6,24px) var(--s-4,16px);border-bottom:var(--bw-1,1px) solid var(--border-tok);flex-wrap:wrap;display:flex}.dh-title{gap:var(--s-1,4px);min-width:0;display:grid}.dh-wordmark{letter-spacing:.08em;text-transform:uppercase;color:var(--fg-muted);font-size:12px}.dh-title h1{font-family:var(--font-display);letter-spacing:var(--tr-display);overflow-wrap:anywhere;margin:0;font-size:30px;font-style:italic;font-weight:200;line-height:1.1}.dh-meta{font-family:var(--font-mono);color:var(--fg-muted);overflow-wrap:anywhere;font-size:12px}.dh-controls{align-items:flex-end;gap:var(--s-3,12px);flex-wrap:wrap;display:flex}.dh-controls label{gap:var(--s-1,4px);color:var(--fg-muted);font-size:12px;display:grid}.dh-controls select{min-height:36px;padding:0 var(--s-3,12px);border:var(--bw-1,1px) solid var(--border-tok);border-radius:var(--r-2);background:var(--bg-elev-1);color:var(--fg);font:inherit;font-size:13px}.dh-controls select:focus-visible,.dh-input textarea:focus-visible,.dh-page .btn:focus-visible{outline:2px solid var(--signal);outline-offset:2px}.dh-problem{margin:var(--s-4,16px) var(--s-6,24px) 0;padding:var(--s-3,12px) var(--s-4,16px);border:var(--bw-1,1px) solid var(--border-strong);border-radius:var(--r-2);background:var(--bg-elev-1);white-space:pre-wrap;overflow-wrap:anywhere}.dh-main{gap:var(--s-6,24px);padding:var(--s-6,24px);flex:1;grid-template-columns:minmax(0,1fr) minmax(280px,380px);display:grid}.dh-stage{min-width:0}.dh-frame{min-width:0;margin:0 auto}.dh-noview{gap:var(--s-4,16px);padding:var(--s-6,24px);border:var(--bw-1,1px) solid var(--border-tok);border-radius:var(--r-3);justify-items:start;display:grid}.dh-noview p{color:var(--fg-muted);margin:0}.dh-panel{gap:var(--s-5,20px);flex-direction:column;min-width:0;display:flex}.dh-section-head{justify-content:space-between;align-items:center;gap:var(--s-2,8px);margin-bottom:var(--s-2,8px);display:flex}.dh-section-head h2{letter-spacing:var(--tr-body);margin:0;font-size:13px;font-weight:600}.dh-input textarea{box-sizing:border-box;width:100%;padding:var(--s-3,12px);border:var(--bw-1,1px) solid var(--border-tok);border-radius:var(--r-2);background:var(--bg-elev-1);color:var(--fg);font-family:var(--font-mono);resize:vertical;font-size:12px;line-height:1.5}.dh-input p,.dh-muted{margin:var(--s-2,8px) 0 0;color:var(--fg-muted);font-size:13px}.dh-json{max-height:240px;padding:var(--s-3,12px);border:var(--bw-1,1px) solid var(--border-tok);border-radius:var(--r-2);background:var(--bg-elev-1);font-family:var(--font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:12px;line-height:1.5;overflow:auto}.dh-console{flex-direction:column;min-height:0;display:flex}.dh-console ol{border-top:var(--bw-1,1px) solid var(--divider);max-height:360px;margin:0;padding:0;list-style:none;overflow:auto}.dh-entry{gap:var(--s-2,8px);padding:var(--s-2,8px) 0;border-bottom:var(--bw-1,1px) solid var(--divider);grid-template-columns:auto auto minmax(0,1fr);align-items:baseline;font-size:12px;display:grid}.dh-entry time{color:var(--fg-muted);font-variant-numeric:tabular-nums}.dh-kind{color:var(--fg-muted);text-transform:uppercase;letter-spacing:.06em;font-size:10px}.dh-entry pre{font-family:var(--font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:0}.dh-error .dh-kind,.dh-error pre{color:var(--fg);font-weight:600}.dh-stand-in .dh-kind{color:var(--signal)}@media (width<=900px){.dh-main{padding:var(--s-4,16px);grid-template-columns:minmax(0,1fr)}.dh-header{padding:var(--s-4,16px)}}
1
+ @font-face{font-family:Hanken Grotesk;font-style:normal;font-weight:100 900;font-display:swap;src:url(/fonts/HankenGrotesk-Variable.ttf)format("truetype")}@font-face{font-family:Hanken Grotesk;font-style:italic;font-weight:100 900;font-display:swap;src:url(/fonts/HankenGrotesk-Italic-Variable.ttf)format("truetype")}:root{--cream-50:#fdfaf1;--cream-100:#f7f0db;--cream-200:#ece3c9;--cream-300:#dcd2b5;--cream-600:#4e483a;--ink-50:#f2f0ec;--ink-100:#c9c6be;--ink-300:#4a4843;--ink-400:#26241f;--ink-500:#16140f;--ink-600:#0a0a0a;--signal:#e8b23a;--signal-dim:#e8b23a26;--signal-glow:#e8b23a4d;--font-display:"Balgin", "Hanken Grotesk", ui-sans-serif, sans-serif;--font-body:"Hanken Grotesk", ui-sans-serif, sans-serif;--font-mono:ui-monospace, "SF Mono", Menlo, Consolas, monospace;--font:var(--font-body);--fs-display-xl:clamp(72px, 14vw, 220px);--fs-display-l:clamp(56px, 9vw, 144px);--fs-display-m:clamp(40px, 6vw, 96px);--fs-h4:1.25rem;--fs-body:1rem;--fs-body-s:.875rem;--fs-caption:.8125rem;--fs-micro:.75rem;--tr-display:-.02em;--tr-body:-.005em;--tr-micro:.08em;--lh-display:.92;--lh-tight:1.05;--s-1:4px;--s-2:8px;--s-3:12px;--s-4:16px;--s-5:24px;--s-6:32px;--s-7:48px;--s-8:64px;--s-9:96px;--s-10:128px;--s-11:192px;--page-gutter:clamp(20px, 3vw, 48px);--page-gutter-compact:clamp(16px, 5vw, 24px);--page-section-gap:clamp(32px, 5vw, 64px);--content-readable:720px;--content-wide:1440px;--control-height:40px;--touch-target:44px;--mobile-dock-height:66px;--z-nav:30;--hover:var(--hover-wash);--r-0:0;--r-1:2px;--r-2:4px;--r-3:8px;--r-pill:999px;--radius-sm:var(--r-2);--radius:var(--r-3);--radius-lg:var(--r-3);--bw-hair:.5px;--bw-1:1px;--bw-2:2px;--ease-out:cubic-bezier(.22, 1, .36, 1);--ease-in-out:cubic-bezier(.65, 0, .35, 1);--ease-spring:cubic-bezier(.34, 1.56, .64, 1);--dur-instant:80ms;--dur-fast:.16s;--dur-base:.24s;--dur-slow:.48s;--dur-reveal:.72s}:root,[data-mode=dark]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--bg:#101010;--bg-elev-1:#232221;--bg-elev-2:#33312f;--bg-inset:#171716;--fg:#f3eee5;--fg-muted:#bbb2a7;--fg-subtle:#f3eee594;--fg-on-accent:#191716;--border-tok:#58534d;--border-strong:#8b7e6f;--divider:#393735;--signal:#d0ae77;--signal-dim:#d0ae7726;--signal-glow:#d0ae774d;--hover-wash:#f7f0db0f;--press-wash:#f7f0db1f;--selection-bg:var(--cream-100);--selection-fg:var(--ink-600);--shadow-1:0 1px 0 #f7f0db0a inset, 0 4px 12px #00000080;--shadow-2:0 1px 0 #f7f0db0f inset, 0 16px 48px #0009;--scrim-top:linear-gradient(to bottom, #0a0a0ad9, #0a0a0a00);--scrim-bottom:linear-gradient(to top, #0a0a0ad9, #0a0a0a00);--bg-primary:var(--bg);--bg-secondary:var(--bg-elev-1);--bg-tertiary:var(--bg-elev-2);--bg-elevated:var(--bg-elev-1);--bg-hover:#403d3a;--border:var(--border-tok);--border-light:var(--border-strong);--accent:var(--fg);--accent-hover:var(--cream-50);--accent-dim:var(--hover-wash);--accent-glow:#f7f0db38;--accent-contrast:var(--bg);--editor-standout-bg:var(--fg);--editor-standout-fg:var(--bg);--text-primary:var(--fg);--text-secondary:var(--fg-muted);--text-muted:var(--fg-subtle);--success:var(--fg);--warning:var(--signal);--danger:var(--signal);--attention-warning:#e0b24f;--attention-error:#e26a5f;--attention-ok:#6bc38e;--attention-info:#7da7e8;--shadow:var(--shadow-1);--shadow-lg:var(--shadow-2)}[data-mode=light]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--signal:#e8b23a;--signal-dim:#e8b23a26;--signal-glow:#e8b23a4d;--bg:var(--cream-100);--bg-elev-1:var(--cream-50);--bg-elev-2:var(--cream-200);--bg-inset:var(--cream-200);--fg:var(--ink-600);--fg-muted:var(--cream-600);--fg-subtle:#0a0a0a61;--fg-on-accent:var(--cream-100);--border-tok:#0a0a0a1a;--border-strong:#0a0a0a38;--divider:#0a0a0a0f;--hover-wash:#0a0a0a0d;--press-wash:#0a0a0a1a;--selection-bg:var(--ink-600);--selection-fg:var(--cream-100);--shadow-1:0 1px 0 #ffffff80 inset, 0 4px 12px #0a0a0a0f;--shadow-2:0 1px 0 #fff9 inset, 0 16px 48px #0a0a0a1a;--scrim-top:linear-gradient(to bottom, #f7f0dbe6, #f7f0db00);--scrim-bottom:linear-gradient(to top, #f7f0dbe6, #f7f0db00);--bg-primary:var(--bg);--bg-secondary:var(--bg-elev-1);--bg-tertiary:var(--bg-elev-2);--bg-elevated:var(--bg-elev-2);--bg-hover:var(--cream-300);--border:var(--border-tok);--border-light:var(--border-strong);--accent:var(--fg);--accent-hover:var(--ink-500);--accent-dim:var(--hover-wash);--accent-glow:#0a0a0a38;--accent-contrast:var(--bg);--editor-standout-bg:var(--ink-300);--editor-standout-fg:var(--cream-100);--text-primary:var(--fg);--text-secondary:var(--fg-muted);--text-muted:var(--fg-subtle);--success:var(--fg);--warning:var(--signal);--danger:var(--signal);--attention-warning:#b98200;--attention-error:#c2453d;--attention-ok:#2e7d4f;--attention-info:#2f67b8;--shadow:var(--shadow-1);--shadow-lg:var(--shadow-2)}[data-mode=white]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--bg:#e9e8e6;--bg-elev-1:#fff;--bg-elev-2:#ebe9e6;--bg-inset:#f7f6f4;--fg:#1a1917;--fg-muted:#615c56;--fg-subtle:#77716b;--fg-on-accent:#fff;--border-tok:#bcb8b2;--border-strong:#978b7d;--divider:#cdc8c2;--hover-wash:#00000014;--press-wash:#00000021;--selection-bg:#1a1917;--selection-fg:#fff;--signal:#8e6b46;--signal-dim:#8e6b461a;--signal-glow:#8e6b4638;--shadow-1:0 1px 3px #0000000a;--shadow-2:0 12px 40px #0000001a;--scrim-top:linear-gradient(to bottom,#e9e8e6e6,#e9e8e600);--scrim-bottom:linear-gradient(to top,#e9e8e6e6,#e9e8e600);--bg-primary:var(--bg);--bg-secondary:var(--bg-elev-1);--bg-tertiary:var(--bg-elev-2);--bg-elevated:var(--bg-elev-1);--bg-hover:#e0ddd8;--border:var(--border-tok);--border-light:var(--border-strong);--accent:var(--fg);--accent-hover:#33302c;--accent-dim:var(--hover-wash);--accent-glow:#0000001f;--accent-contrast:var(--bg);--editor-standout-bg:#24211d;--editor-standout-fg:#fff;--text-primary:var(--fg);--text-secondary:var(--fg-muted);--text-muted:var(--fg-subtle);--success:var(--fg);--warning:var(--signal);--danger:var(--signal);--attention-warning:#b98200;--attention-error:#c2453d;--attention-ok:#2e7d4f;--attention-info:#2f67b8;--shadow:var(--shadow-1);--shadow-lg:var(--shadow-2)}button[data-icon-action=true]{cursor:pointer;flex-shrink:0;line-height:1;min-width:36px!important;min-height:36px!important;box-shadow:none!important;color:var(--text-secondary,var(--fg))!important;background:0 0!important;border:0!important;border-radius:0!important;justify-content:center!important;align-items:center!important;padding:8px!important;display:inline-flex!important}button[data-icon-action=true]:hover:not(:disabled){color:var(--text-primary,var(--fg))!important}button[data-icon-action=true]:focus-visible{outline-offset:2px;outline:2px solid var(--signal)!important}button[data-icon-action=true]:disabled{cursor:not-allowed;opacity:.4!important}button[data-icon-action=true][aria-pressed=true]{text-underline-offset:5px;text-decoration:underline;color:var(--text-primary,var(--fg))!important}@media (pointer:coarse){button[data-icon-action=true]{min-width:44px!important;min-height:44px!important}}*,:before,:after{box-sizing:border-box;margin:0;padding:0}:root[data-anim-theme="1"]:not([data-mode=white]){--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--bg:#0a0d13;--bg-elev-1:#0f141c;--bg-elev-2:#1a2230;--bg-inset:#0f141c;--fg:#d8dee8;--fg-muted:#8b98ac;--fg-subtle:#64748b;--fg-on-accent:#0a0d13;--border-tok:#232d3d;--border-strong:#303d50;--divider:#1a2230;--hover-wash:#8b98ac14;--press-wash:#8b98ac29;--selection-bg:#303d50;--selection-fg:#d8dee8;--bg-primary:#0a0d13;--bg-secondary:#0f141c;--bg-tertiary:#1a2230;--bg-elevated:#1a2230;--bg-hover:#232d3d;--border:#232d3d;--border-light:#303d50;--accent:#8b98ac;--accent-hover:#b6c0ce;--accent-dim:#8b98ac1f;--accent-glow:#8b98ac40;--accent-contrast:#0a0d13;--editor-standout-bg:#d8dee8;--editor-standout-fg:#0a0d13;--text-primary:#d8dee8;--text-secondary:#8b98ac;--text-muted:#64748b;--success:#8b98ac;--warning:#b6c0ce;--danger:#b6c0ce}html,body{background:var(--bg);width:100%;height:100%;color:var(--fg);font-family:var(--font-body);letter-spacing:var(--tr-body);-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility;font-size:14px;line-height:1.6;overflow:hidden}#root{width:100%;height:100%;overflow:hidden}.sm-app-shell{height:100dvh}@keyframes sm-navmenu-in{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}.mobile-nav-menu{animation:.2s cubic-bezier(.22,1,.36,1) sm-navmenu-in}::selection{background:var(--selection-bg);color:var(--selection-fg)}.display,h1,h2{font-family:var(--font-display);font-style:italic;font-weight:200;line-height:var(--lh-tight);letter-spacing:var(--tr-display);margin:0}.display{line-height:var(--lh-display)}h3,h4{font-family:var(--font-body);font-weight:500;line-height:var(--lh-tight);letter-spacing:-.01em;margin:0}.eyebrow{color:var(--fg-muted);font-size:11px;font-weight:500}code,pre,.mono{font-family:var(--font-mono);font-size:.92em}.listing-identity code{font-family:inherit;font-size:inherit}button{font-family:var(--font-body);cursor:pointer;letter-spacing:var(--tr-body);border:none;outline:none}textarea,input,select{font-family:var(--font-body);outline:none}select{background-color:var(--bg-elev-1);color:var(--text-primary);border:1px solid var(--border);border-radius:var(--r-2)}select option,select optgroup{background-color:var(--bg-elevated);color:var(--text-primary)}select option:checked{font-weight:600}.tsel-trigger{background-color:var(--bg-elev-1);color:var(--text-primary);border:1px solid var(--border);border-radius:var(--r-2);font-family:var(--font-body);font-size:inherit;cursor:pointer;text-align:left;justify-content:space-between;align-items:center;gap:8px;padding:6px 10px;display:inline-flex}.tsel-trigger:hover{border-color:var(--border-strong)}.tsel-trigger:focus-visible{outline:2px solid var(--accent);outline-offset:1px}.tsel-trigger:disabled{opacity:.5;cursor:default}.tsel-label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.tsel-caret{opacity:.7;flex:none;font-size:10px}.tsel-pop{z-index:100100;background:var(--bg-elevated);border:1px solid var(--border);border-radius:var(--r-2);max-height:260px;padding:4px;overflow-y:auto;box-shadow:0 8px 28px #00000047}.tsel-opt{color:var(--text-primary);cursor:pointer;white-space:nowrap;border-radius:6px;align-items:center;gap:6px;padding:6px 8px;font-size:13px;display:flex}.tsel-opt[data-active=true]{background:var(--bg-elev-1)}.tsel-opt[aria-selected=true]{font-weight:600}.tsel-opt[aria-disabled=true]{color:var(--text-muted);cursor:default}.tsel-opt[aria-disabled=true][data-active=true]{background:0 0}.tsel-check{opacity:.8;flex:none;width:12px;font-size:11px}.tsel-opt-label{text-overflow:ellipsis;flex:auto;overflow:hidden}.tsel-note{cursor:help;width:16px;height:16px;color:var(--text-muted);border:1px solid var(--border);border-radius:999px;flex:none;justify-content:center;align-items:center;font-size:11px;display:inline-flex;position:relative}.tsel-note:hover{color:var(--text-primary);border-color:var(--border-strong)}.tsel-tip{z-index:100110;background:var(--text-primary);color:var(--bg-elevated);box-sizing:border-box;white-space:normal;pointer-events:none;border-radius:5px;max-width:min(260px,100vw - 16px);padding:4px 8px;font-size:11px;font-weight:600;box-shadow:0 2px 10px #0000004d}@media (width<=767.98px),(height<=480px) and (pointer:coarse){.tsel-opt{box-sizing:border-box;min-height:44px;font-size:14px}.tsel-note{flex:0 0 44px;width:44px;height:44px;font-size:14px}}.video-editor input,.video-editor select,.video-editor textarea{background:var(--bg-inset);width:100%;min-width:0;color:var(--fg);border:var(--bw-1) solid var(--border-strong);border-radius:var(--r-2);font-family:var(--font-body);accent-color:var(--signal);padding:7px 8px;font-size:12px}.video-editor,.mobile-editor-root{-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}.video-editor input,.video-editor select,.video-editor textarea,.video-editor [contenteditable=true],.mobile-editor-root input,.mobile-editor-root select,.mobile-editor-root textarea,.mobile-editor-root [contenteditable=true]{-webkit-user-select:text;user-select:text;-webkit-touch-callout:default}.video-editor input[type=range]{background:0 0;border:0;padding:0}.video-editor input[type=checkbox]{width:auto;min-width:auto}.video-editor select:focus,.video-editor input:focus,.video-editor textarea:focus{border-color:var(--signal);box-shadow:0 0 0 1px var(--signal-dim)}:focus-visible{outline:2px solid var(--signal);outline-offset:2px}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:var(--border-strong);border-radius:var(--r-pill)}::-webkit-scrollbar-thumb:hover{background:var(--fg-subtle)}.sm-hide-scrollbar{scrollbar-width:none;-ms-overflow-style:none}.sm-hide-scrollbar::-webkit-scrollbar{width:0;height:0;display:none}.btn{align-items:center;gap:var(--s-2);padding:var(--s-2) var(--s-4);border-radius:var(--r-pill);letter-spacing:var(--tr-body);transition:transform var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), opacity var(--dur-fast) var(--ease-out);font-size:13px;font-weight:500;display:inline-flex}.btn:active{transform:scale(.98)}.btn-primary{background:var(--fg);color:var(--bg)}.btn-primary:hover{background:var(--fg);opacity:.86}.btn-primary:disabled{opacity:.4;cursor:not-allowed}.btn-secondary{color:var(--fg);border:var(--bw-1) solid var(--border-strong);background:0 0}.btn-secondary:hover{background:var(--hover-wash);border-color:var(--fg)}.btn-ghost{color:var(--fg-muted);background:0 0;padding:6px 10px}.btn-ghost:hover{background:var(--hover-wash);color:var(--fg)}.btn-icon{color:var(--fg-muted);border-radius:var(--r-2);transition:background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);background:0 0;justify-content:center;align-items:center;padding:5px;display:inline-flex}.btn-icon:hover{background:var(--hover-wash);color:var(--fg)}.btn-icon:active{transform:scale(.98)}.btn-icon:disabled{opacity:.4;cursor:not-allowed}.typing-indicator{align-items:center;gap:4px;padding:4px 0;display:flex}.typing-indicator span{border-radius:var(--r-pill);background:var(--fg-subtle);width:6px;height:6px;animation:1.2s infinite bounce}.typing-indicator span:nth-child(2){animation-delay:.2s}.typing-indicator span:nth-child(3){animation-delay:.4s}@keyframes bounce{0%,60%,to{opacity:.4;transform:translateY(0)}30%{opacity:1;transform:translateY(-5px)}}@keyframes fadeIn{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{0%,to{opacity:.4;transform:scale(.9)}50%{opacity:1;transform:scale(1.1)}}@keyframes toastIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}button:focus-visible,[role=button]:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:4px}input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=color]):not([type=file]):not([type=button]):not([type=submit]):not([type=reset]):is(:focus,:focus-visible),textarea:is(:focus,:focus-visible),select:is(:focus,:focus-visible),[contenteditable]:not([contenteditable=false]):is(:focus,:focus-visible){outline:none!important}@keyframes skeletonShimmer{0%{background-position:-200% 0}to{background-position:200% 0}}.skeleton{background:linear-gradient(90deg,#ffffff08 0%,#ffffff1a 50%,#ffffff08 100%) 0 0/200% 100%;border-radius:6px;animation:1.4s ease-in-out infinite skeletonShimmer}.node-state-empty{border-style:dashed!important}.node-state-generating{box-shadow:0 0 0 2px var(--accent);position:relative}.node-state-generating:after{content:"";border-radius:inherit;pointer-events:none;will-change:opacity;animation:1.6s ease-in-out infinite nodePulse;position:absolute;inset:-2px;box-shadow:0 0 18px #f7c8328c}@keyframes nodePulse{0%,to{opacity:.36}50%{opacity:1}}.node-state-warning{position:relative;border-color:var(--attention-warning)!important}.node-state-warning:after{content:"";border-radius:inherit;pointer-events:none;box-shadow:0 0 0 1px var(--attention-warning), 0 0 14px color-mix(in srgb, var(--attention-warning) 40%, transparent);position:absolute;inset:-2px}.node-state-failed{border-top:4px solid #d04848!important}.node-state-failed:before{content:"⚠ ";color:#d04848;z-index:2;pointer-events:none;font-size:12px;font-weight:800;position:absolute;top:6px;left:8px}.react-flow__node{cursor:default}.react-flow__node.dragging,.react-flow__pane.dragging .react-flow__viewport{will-change:transform}.react-flow__node:not(.react-flow__node-sceneGroupNode):not(.react-flow__node-shotLabelNode):not(.react-flow__node-stickyNoteNode):not(.react-flow__node-transitionNode){box-shadow:1px 1px 0 var(--node-side,#d8cfb3), 2px 2px 0 var(--node-side,#d8cfb3), 3px 3px 0 var(--node-side,#d8cfb3), 4px 4px 0 var(--node-side,#d8cfb3), 5px 5px 0 var(--node-side,#d8cfb3), 6px 6px 0 var(--node-side,#d8cfb3), 7px 7px 0 var(--node-edge,#0a0a0a2e), 14px 16px 18px -4px #0a0a0a2e;border-radius:10px}[data-mode=dark] .react-flow__node:not(.react-flow__node-sceneGroupNode):not(.react-flow__node-shotLabelNode):not(.react-flow__node-stickyNoteNode):not(.react-flow__node-transitionNode){--node-side:#1f1d18;--node-edge:#f7f0db2e}.react-flow__controls{grid-template-columns:repeat(2,1fr)!important;display:grid!important}.react-flow__controls button{background:var(--bg-elev-2)!important;border-color:var(--border-tok)!important;color:var(--fg-muted)!important;fill:var(--fg-muted)!important}.react-flow__controls button:hover{background:var(--ink-300)!important}.react-flow__edge-path{stroke:var(--border-strong)}.react-flow.rf-edges-panning .react-flow__edges{display:none}.react-flow__node.node-controls-locked input,.react-flow__node.node-controls-locked textarea,.react-flow__node.node-controls-locked select,.react-flow__node.node-controls-locked button,.react-flow__node.node-controls-locked [role=button]{pointer-events:none!important}.react-flow__node.node-controls-locked{cursor:grab}.react-flow__node.node-controls-locked:active{cursor:grabbing}.react-flow__node.scene-add-available-node{filter:drop-shadow(0 0 10px #f7f0db38)}.react-flow__node.scene-add-available-node>div{outline:1px solid var(--border-strong);outline-offset:4px;border-radius:var(--r-3)}.react-flow__node.scene-add-available-node:after{content:"";border:1px dashed var(--border-strong);border-radius:var(--r-3);pointer-events:none;position:absolute;inset:-8px}.react-flow__node.node-replace-available{filter:drop-shadow(0 0 10px #f7f0db38)}.react-flow__node.node-replace-available>div{outline:1px solid var(--border-strong);outline-offset:4px;border-radius:var(--r-3)}.react-flow__node.node-replace-available:after{content:"";border:1px dashed var(--border-strong);border-radius:var(--r-3);pointer-events:none;position:absolute;inset:-8px}select option{background:var(--bg-elev-2);color:var(--fg)}.spinner{border:2px solid var(--border-tok);border-top-color:var(--fg);border-radius:var(--r-pill);width:16px;height:16px;animation:.7s linear infinite spin}.media-upload-preview:hover .media-upload-overlay{opacity:1!important}@media (prefers-reduced-motion:reduce){*,:before,:after{transition-duration:1ms!important;animation-duration:1ms!important}}.assets-card .assets-card-overlay{opacity:0;transition:opacity .15s}.assets-card:hover .assets-card-overlay{opacity:1}.yt-video-row{transition:background .12s,box-shadow .12s,color .12s}.yt-video-row:hover{box-shadow:inset 2px 0 0 var(--accent), inset 0 0 14px var(--accent-glow);color:var(--text-primary);background:var(--hover-wash)!important}.chat-bubble ::selection{background:var(--signal);color:#0a0a0a}.chat-bubble ::selection{background:var(--signal);color:#0a0a0a}.chat-bubble:hover .chat-bubble-copy,.chat-bubble:focus-within .chat-bubble-copy,.chat-bubble-copy:hover,.chat-bubble-copy:focus{opacity:1!important}.chat-bubble-copy:hover{background:var(--bg-elevated-2,var(--bg-elevated));color:var(--text-primary)!important}.chat-bubble-copy:active{transform:scale(.94)}.sm-brand .sm-brandmark{transform-origin:50%;transition:transform .76s cubic-bezier(.2,.8,.2,1),filter .28s}.sm-brand:hover .sm-brandmark{filter:drop-shadow(0 0 7px color-mix(in srgb, currentColor 45%, transparent));will-change:transform, filter;transform:perspective(340px)rotateY(360deg)scale(1.06)}.sm-brand .sm-word{transition:transform .3s cubic-bezier(.2,.8,.2,1);transform:translate(-4px)}.sm-brand:hover .sm-word{will-change:transform;transform:translate(-4px,-1px)}@media (prefers-reduced-motion:reduce){.sm-brand .sm-brandmark{transition:transform .24s,filter .24s}.sm-brand:hover .sm-brandmark{transform:scale(1.06)}.sm-brand:hover .sm-word{transform:translate(-4px)}}body.sm-mobile-ui input,body.sm-mobile-ui textarea,body.sm-mobile-ui select{font-size:16px!important}body.sm-mobile-ui button,body.sm-mobile-ui select,body.sm-mobile-ui [role=button]{min-height:44px}body.sm-mobile-ui select:not([multiple]):not([size]){height:max(44px,2.75em)}body.sm-mobile-ui button.btn-icon,body.sm-mobile-ui button[data-compact],body.sm-mobile-ui .no-min-tap{min-height:0}body.sm-mobile-ui .yt-video-row{min-width:560px}:where(html:has(.sm-block)){container-type:inline-size}:where(.sm-block){gap:var(--s-5,24px);min-width:0;display:grid}:where(.sm-block,.sm-block *){box-sizing:border-box}:where(.sm-block)>:where(*){min-width:0}@container (width>=720px){:where(.sm-block[data-layout=split]){grid-template-columns:minmax(0,1.6fr) minmax(260px,1fr);align-items:start}:where(.sm-block[data-layout=split])>:where(.sm-stage){grid-row:span 6}}:where(.sm-row){align-items:center;gap:var(--s-2,8px);flex-wrap:wrap;display:flex}:where(.sm-stage){min-height:220px;padding:var(--s-3,12px);border-radius:var(--r-3,8px);background:var(--bg-inset);place-items:center;display:grid;position:relative;overflow:hidden}:where(.sm-stage[data-ratio="16:9"]){aspect-ratio:16/9;min-height:0}:where(.sm-stage[data-ratio="9:16"]){aspect-ratio:9/16;justify-self:center;width:auto;height:min(70vh,640px);min-height:0;max-height:70vh}:where(.sm-stage[data-ratio="1:1"]){aspect-ratio:1;min-height:0}:where(.sm-stage)>:where(img,video,canvas){object-fit:contain;border-radius:var(--r-2,4px);max-width:100%;max-height:100%;display:block}:where(.sm-stage[data-fill])>:where(img,video,canvas){object-fit:cover;width:100%;height:100%}:where(.sm-empty){justify-items:center;gap:var(--s-3,12px);padding:var(--s-5,24px) var(--s-4,16px);text-align:center;color:var(--fg-muted);display:grid}:where(.sm-empty)>:where(.sm-glyph){--sm-glyph-size:40px;color:var(--fg-subtle)}:where(.sm-empty)>:where(p){max-width:24ch;font-size:var(--fs-body,1rem);margin:0}:where(.sm-drop){outline:1px dashed var(--border-tok);outline-offset:-8px}:where(.sm-drop[data-over]){outline-color:var(--fg);background:var(--hover-wash)}:where(.sm-actions){align-items:center;gap:var(--s-2,8px);flex-wrap:wrap;display:flex}:where(.sm-actions)>:where(.sm-status){margin-inline-start:var(--s-2,8px)}:where(.sm-primary,.sm-quiet){justify-content:center;align-items:center;gap:var(--s-2,8px);min-height:var(--touch-target,44px);border-radius:var(--r-pill,999px);font:inherit;cursor:pointer;text-align:center;overflow-wrap:anywhere;max-width:100%;padding:10px 20px;font-weight:500;display:inline-flex}:where(.sm-primary){border:1px solid var(--fg);background:var(--fg);color:var(--bg)}:where(.sm-quiet){border:1px solid var(--border-tok);color:var(--fg);background:0 0}:where(.sm-quiet):hover{background:var(--hover-wash)}:where(.sm-icon){min-width:var(--touch-target,44px);min-height:var(--touch-target,44px);border-radius:var(--r-2,4px);color:var(--fg-muted);cursor:pointer;background:0 0;border:0;place-items:center;padding:0;display:inline-grid}:where(.sm-icon):hover{color:var(--fg);background:var(--hover-wash)}:where(.sm-icon[aria-pressed=true]){color:var(--fg);background:var(--press-wash)}:where(.sm-primary,.sm-quiet,.sm-icon):disabled{opacity:.45;cursor:default}:where(.sm-primary,.sm-quiet,.sm-icon):focus-visible{outline:2px solid var(--signal);outline-offset:3px}@container (width<=420px){:where(.sm-actions)>:where(.sm-primary){flex:100%}}:where(.sm-field){gap:6px;min-width:0;display:grid}:where(.sm-field)>:where(label,.sm-label){align-items:center;gap:var(--s-1,4px);font-size:var(--fs-body-s,.875rem);color:var(--fg-muted);min-height:24px;display:flex}:where(.sm-field) :where(input:not([type=checkbox]):not([type=radio]):not([type=range]),select,textarea){width:100%;min-height:var(--touch-target,44px);border:1px solid var(--border-tok);border-radius:var(--r-3,8px);background:var(--bg-elev-1);color:var(--fg);font:inherit;padding:10px 12px}:where(.sm-field) :where(textarea){resize:vertical;min-height:96px}:where(.sm-fields){gap:var(--s-4,16px);grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));display:grid}:where(.sm-choices){gap:var(--s-2,8px);grid-template-columns:repeat(auto-fill,minmax(var(--sm-tile,96px),1fr));border:0;min-width:0;margin:0;padding:0;display:grid}:where(.sm-choices[data-size=wide]){--sm-tile:160px}:where(.sm-choices)>:where(legend){font-size:var(--fs-body-s,.875rem);color:var(--fg-muted);margin-bottom:6px;padding:0}:where(.sm-choices)>:where(label){padding:var(--s-2,8px);border:1px solid var(--border-tok);border-radius:var(--r-3,8px);background:var(--bg-elev-1);color:var(--fg-muted);font-size:var(--fs-caption,.8125rem);text-align:center;cursor:pointer;align-content:start;justify-items:center;gap:6px;display:grid;position:relative}:where(.sm-choices)>:where(label):hover{border-color:var(--border-strong);color:var(--fg)}:where(.sm-choices) :where(input[type=radio],input[type=checkbox]){opacity:0;cursor:pointer;width:100%;height:100%;margin:0;position:absolute;inset:0}:where(.sm-choices)>:where(label:has(input:checked)){border-color:var(--fg);color:var(--fg);box-shadow:inset 0 0 0 1px var(--fg)}:where(.sm-choices)>:where(label:has(input:focus-visible)){outline:2px solid var(--signal);outline-offset:2px}:where(.sm-choices) :where(img,video,canvas,.sm-swatch){width:100%;aspect-ratio:var(--sm-tile-ratio,1);object-fit:cover;border-radius:var(--r-2,4px);background:var(--bg-inset);display:block}:where(.sm-choices) :where(.sm-glyph){--sm-glyph-size:28px;margin:10px 0 4px}:where(.sm-choices[data-ratio="16:9"]){--sm-tile-ratio:16/9}:where(.sm-choices[data-ratio="9:16"]){--sm-tile-ratio:9/16}:where(.sm-choices[data-compact])>:where(label){min-height:var(--touch-target,44px);grid-auto-flow:column;place-content:center;align-items:center;padding:6px 12px}:where(.sm-choices[data-compact]) :where(.sm-glyph){--sm-glyph-size:18px;margin:0}:where(.sm-choices) :where(audio){z-index:1;width:100%;height:32px;position:relative}:where(.sm-shape){box-sizing:border-box;width:calc(var(--sm-shape-size,32px) * min(1,var(--r,1)));aspect-ratio:var(--r,1);border:2px solid;border-radius:3px;max-width:100%;margin:4px auto;display:block}:where(.sm-choices)>:where(label:has(.sm-shape)){grid-template-rows:1fr auto;align-content:stretch}:where(.sm-choices) :where(.sm-shape){--sm-shape-size:36px;align-self:center;margin:6px auto 2px}:where(.sm-choices[data-compact]) :where(.sm-shape){--sm-shape-size:20px;margin:0}:where(.sm-gallery){gap:var(--s-2,8px);grid-template-columns:repeat(auto-fill,minmax(min(100%,var(--sm-tile,140px)),1fr));margin:0;padding:0;list-style:none;display:grid}:where(.sm-gallery)>:where(*){border-radius:var(--r-3,8px);background:var(--bg-inset);margin:0;position:relative;overflow:hidden}:where(.sm-gallery) :where(img,video,canvas){width:100%;aspect-ratio:var(--sm-tile-ratio,1);object-fit:cover;display:block}:where(.sm-gallery) :where(figcaption){font-size:var(--fs-caption,.8125rem);color:var(--fg-muted);padding:6px 8px}:where(.sm-gallery) :where([aria-selected=true],.is-selected){box-shadow:inset 0 0 0 2px var(--fg)}:where(.sm-status){align-items:center;gap:var(--s-2,8px);min-height:24px;font-size:var(--fs-body-s,.875rem);color:var(--fg-muted);display:inline-flex}:where(.sm-status):empty{display:none}:where(.sm-status[data-state]):before{content:"";background:currentColor;border-radius:50%;flex:none;width:14px;height:14px}:where(.sm-status[data-state=loading]):before{background:0 0;border:2px solid;border-right-color:#0000;animation:.8s linear infinite sm-spin}:where(.sm-status[data-state=success]){color:var(--attention-ok,var(--fg))}:where(.sm-status[data-state=success]):before{background:0 0;border:2px solid;border-width:0 2px 2px 0;border-radius:0;width:6px;height:11px;margin:0 4px 3px;transform:rotate(45deg)}:where(.sm-status[data-state=waiting]){color:var(--attention-warning,var(--signal))}:where(.sm-status[data-state=waiting]):before{width:8px;height:8px;margin:0 3px}:where(.sm-status[data-state=error]){color:var(--attention-error,var(--signal))}:where(.sm-status[data-state=error]):before{clip-path:polygon(50% 0,100% 100%,0 100%);border-radius:0}:where(.sm-progress){border-radius:var(--r-pill,999px);background:var(--bg-elev-2);width:100%;height:4px;accent-color:var(--fg);appearance:none;border:0;overflow:hidden}:where(.sm-progress)::-webkit-progress-bar{background:var(--bg-elev-2)}:where(.sm-progress)::-webkit-progress-value{background:var(--fg)}:where(.sm-progress)::-moz-progress-bar{background:var(--fg)}:where(.sm-more){border-top:1px solid var(--divider)}:where(.sm-more)>:where(summary){align-items:center;gap:var(--s-2,8px);min-height:var(--touch-target,44px);color:var(--fg-muted);font-size:var(--fs-body-s,.875rem);cursor:pointer;padding:0;list-style:none;display:flex}:where(.sm-more)>:where(summary)::-webkit-details-marker{display:none}:where(.sm-more)>:where(summary):after{content:"";width:7px;height:7px;transition:transform var(--dur-fast,.16s) var(--ease-out,ease);border:1.5px solid;border-width:0 1.5px 1.5px 0;margin-inline-start:2px;transform:translateY(-2px)rotate(45deg)}:where(.sm-more[open])>:where(summary):after{transform:translateY(2px)rotate(-135deg)}:where(.sm-more)>:where(summary):hover{color:var(--fg)}:where(.sm-more)>:where(:not(summary)){margin-top:var(--s-2,8px)}:where(.sm-more[open]){padding-bottom:var(--s-3,12px)}:where(.sm-label){position:relative}:where(.sm-hint){vertical-align:middle;display:inline-block}:where(:not(.sm-label))>:where(.sm-hint){position:relative}:where(.sm-hint)>:where(summary){width:var(--touch-target,44px);height:var(--touch-target,44px);min-height:0;color:var(--fg-subtle);cursor:pointer;border:0;place-items:center;margin:-8px;padding:0;list-style:none;display:inline-grid}:where(.sm-hint)>:where(summary)::-webkit-details-marker{display:none}:where(.sm-hint)>:where(summary):before{content:"i";width:15px;height:15px;font:600 10px/1 var(--font-body);border:1.5px solid;border-radius:50%;place-items:center;font-style:normal;display:grid}:where(.sm-hint)>:where(summary):hover,:where(.sm-hint[open])>:where(summary){color:var(--fg)}:where(.sm-hint)>:where(:not(summary)){z-index:5;inset-inline-start:0;border:1px solid var(--border-tok);border-radius:var(--r-3,8px);background:var(--bg-elev-2);width:max-content;max-width:min(280px,100%);color:var(--fg);font-size:var(--fs-body-s,.875rem);box-shadow:var(--shadow-1);margin:4px 0 0;padding:10px 12px;line-height:1.45;position:absolute;top:100%}:where(:not(.sm-label))>:where(.sm-hint[open]){display:block}:where(:not(.sm-label))>:where(.sm-hint)>:where(:not(summary)){width:auto;max-width:60ch;position:static}:where(.sm-glyph){--g:var(--sm-glyph-size,20px);width:var(--g);height:var(--g);color:inherit;vertical-align:middle;flex:none;display:inline-block;position:relative}:where(.sm-glyph):before,:where(.sm-glyph):after{content:"";box-sizing:border-box;position:absolute}:where(.sm-glyph[data-glyph=play]):before{clip-path:polygon(0 0,100% 50%,0 100%);background:currentColor;inset:15% 15% 15% 25%}:where(.sm-glyph[data-glyph=pause]):before{border:solid;border-width:0 calc(var(--g)*.14);inset:18% 25%}:where(.sm-glyph[data-glyph=plus]):before,:where(.sm-glyph[data-glyph=close]):before{background:linear-gradient(currentColor,currentColor) 50%/100% 2px no-repeat,linear-gradient(currentColor,currentColor) 50%/2px 100% no-repeat;inset:15%}:where(.sm-glyph[data-glyph=close]):before{transform:rotate(45deg)}:where(.sm-glyph[data-glyph=check]):before{border:2px solid;border-width:0 2px 2px 0;width:30%;height:58%;top:12%;left:36%;transform:rotate(45deg)}:where(.sm-glyph[data-glyph=chevron]):before{border:2px solid;border-width:0 2px 2px 0;width:40%;height:40%;top:22%;left:30%;transform:rotate(45deg)}:where(.sm-glyph[data-glyph=upload]):before{border:2px solid;border-top:0;border-radius:0 0 3px 3px;height:35%;bottom:10%;left:10%;right:10%}:where(.sm-glyph[data-glyph=upload]):after{clip-path:polygon(50% 0,100% 45%,60% 45%,60% 100%,40% 100%,40% 45%,0 45%);background:currentColor;height:62%;top:4%;left:25%;right:25%}:where(.sm-glyph[data-glyph=image]):before{border:2px solid;border-radius:3px;inset:12%}:where(.sm-glyph[data-glyph=image]):after{clip-path:polygon(0 100%,35% 20%,60% 65%,75% 45%,100% 100%);background:currentColor;height:34%;bottom:24%;left:22%;right:22%}:where(.sm-glyph[data-glyph=video]):before{border:2px solid;border-radius:3px;inset:22% 28% 22% 8%}:where(.sm-glyph[data-glyph=video]):after{clip-path:polygon(0 35%,100% 0,100% 100%,0 65%);background:currentColor;width:20%;top:30%;bottom:30%;right:8%}:where(.sm-glyph[data-glyph=audio]):before{background:linear-gradient(currentColor,currentColor) 0 60%/2px 40% no-repeat,linear-gradient(currentColor,currentColor) 33%/2px 80% no-repeat,linear-gradient(currentColor,currentColor) 66%/2px 100% no-repeat,linear-gradient(currentColor,currentColor) 100% 60%/2px 50% no-repeat;inset:15% 12%}:where(.sm-glyph[data-glyph=text]):before{background:linear-gradient(currentColor,currentColor) 0 0/100% 2px no-repeat,linear-gradient(currentColor,currentColor) 0/100% 2px no-repeat,linear-gradient(currentColor,currentColor) 0 100%/60% 2px no-repeat;inset:20% 12%}:where(.sm-glyph[data-glyph=spark]):before{clip-path:polygon(50% 0,60% 40%,100% 50%,60% 60%,50% 100%,40% 60%,0 50%,40% 40%);background:currentColor;inset:8%}:where(.sm-glyph[data-glyph=info]):before{border:2px solid;border-radius:50%;inset:10%}:where(.sm-glyph[data-glyph=info]):after{background:linear-gradient(currentColor,currentColor) 0 0/2px 2px no-repeat,linear-gradient(currentColor,currentColor) 0 100%/2px 60% no-repeat;width:2px;height:46%;margin-left:-1px;top:28%;left:50%}:where(.sm-glyph[data-glyph=warning]):before{clip-path:polygon(50% 0,100% 100%,0 100%);background:currentColor;inset:8% 4% 10%}:where(.sm-glyph[data-glyph=warning]):after{background:linear-gradient(var(--bg),var(--bg)) 0 0/2px 60% no-repeat,linear-gradient(var(--bg),var(--bg)) 0 100%/2px 2px no-repeat;width:2px;height:38%;margin-left:-1px;top:38%;left:50%}:where(.sm-glyph[data-glyph=spinner]):before{border:2px solid;border-right-color:#0000;border-radius:50%;animation:.8s linear infinite sm-spin;inset:12%}:where([class*=sm-][hidden]){display:none!important}:where(.sm-sr){clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}@keyframes sm-spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){:where(.sm-status[data-state=loading]):before,:where(.sm-glyph[data-glyph=spinner]):before{opacity:.6;border-right-color:currentColor;animation:none}:where(.sm-more)>:where(summary):after{transition:none}}.sm-quiet{border:1px solid var(--border-tok)}html,body{background:var(--bg);color:var(--fg);margin:0}.dh-page{min-height:100dvh;font-family:var(--font-body);flex-direction:column;display:flex}.dh-header{justify-content:space-between;align-items:flex-end;gap:var(--s-4,16px);padding:var(--s-5,20px) var(--s-6,24px) var(--s-4,16px);border-bottom:var(--bw-1,1px) solid var(--border-tok);flex-wrap:wrap;display:flex}.dh-title{gap:var(--s-1,4px);min-width:0;display:grid}.dh-wordmark{color:var(--fg-muted);font-size:13px}.dh-title h1{font-family:var(--font-display);letter-spacing:var(--tr-display);overflow-wrap:anywhere;margin:0;font-size:30px;font-style:italic;font-weight:200;line-height:1.1}.dh-meta{color:var(--fg-muted);overflow-wrap:anywhere;font-size:12px}.dh-controls{align-items:flex-end;gap:var(--s-3,12px);flex-wrap:wrap;display:flex}.dh-controls label{gap:var(--s-1,4px);color:var(--fg-muted);font-size:12px;display:grid}.dh-controls select{min-height:36px;padding:0 var(--s-3,12px);border:var(--bw-1,1px) solid var(--border-tok);border-radius:var(--r-2);background:var(--bg-elev-1);color:var(--fg);font:inherit;font-size:13px}.dh-controls select:focus-visible,.dh-input textarea:focus-visible,.dh-page .btn:focus-visible{outline:2px solid var(--signal);outline-offset:2px}.dh-problem{margin:var(--s-4,16px) var(--s-6,24px) 0;padding:var(--s-3,12px) var(--s-4,16px);border:var(--bw-1,1px) solid var(--border-strong);border-radius:var(--r-2);background:var(--bg-elev-1);white-space:pre-wrap;overflow-wrap:anywhere}.dh-main{gap:var(--s-6,24px);padding:var(--s-6,24px);flex:1;grid-template-columns:minmax(0,1fr) minmax(280px,380px);display:grid}.dh-stage{min-width:0}.dh-frame{min-width:0;margin:0 auto}.dh-noview{gap:var(--s-4,16px);padding:var(--s-6,24px);border:var(--bw-1,1px) solid var(--border-tok);border-radius:var(--r-3);justify-items:start;display:grid}.dh-noview p{color:var(--fg-muted);margin:0}.dh-panel{gap:var(--s-5,20px);flex-direction:column;min-width:0;display:flex}.dh-section-head{justify-content:space-between;align-items:center;gap:var(--s-2,8px);margin-bottom:var(--s-2,8px);display:flex}.dh-section-head h2{letter-spacing:var(--tr-body);margin:0;font-size:13px;font-weight:600}.dh-input textarea{box-sizing:border-box;width:100%;padding:var(--s-3,12px);border:var(--bw-1,1px) solid var(--border-tok);border-radius:var(--r-2);background:var(--bg-elev-1);color:var(--fg);font-family:var(--font-mono);resize:vertical;font-size:12px;line-height:1.5}.dh-input p,.dh-muted{margin:var(--s-2,8px) 0 0;color:var(--fg-muted);font-size:13px}.dh-json{max-height:240px;padding:var(--s-3,12px);border:var(--bw-1,1px) solid var(--border-tok);border-radius:var(--r-2);background:var(--bg-elev-1);font-family:var(--font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:12px;line-height:1.5;overflow:auto}.dh-console{flex-direction:column;min-height:0;display:flex}.dh-console ol{border-top:var(--bw-1,1px) solid var(--divider);max-height:360px;margin:0;padding:0;list-style:none;overflow:auto}.dh-entry{gap:var(--s-2,8px);padding:var(--s-2,8px) 0;border-bottom:var(--bw-1,1px) solid var(--divider);grid-template-columns:auto auto minmax(0,1fr);align-items:baseline;font-size:12px;display:grid}.dh-entry time{color:var(--fg-muted);font-variant-numeric:tabular-nums}.dh-kind{color:var(--fg-muted);font-size:11px}.dh-entry pre{font-family:var(--font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:0}.dh-error .dh-kind,.dh-error pre{color:var(--fg);font-weight:600}.dh-stand-in .dh-kind{color:var(--signal)}@media (width<=900px){.dh-main{padding:var(--s-4,16px);grid-template-columns:minmax(0,1fr)}.dh-header{padding:var(--s-4,16px)}}
@@ -13,7 +13,7 @@ Start
13
13
  Build and check
14
14
  validate [folder] Check the manifest, source and interface (--mobile adds the phone profile)
15
15
  test [folder] Run tests/fixtures.json
16
- run [folder] [input.json] Run once and print the outputs (--state file, --save-state file)
16
+ run [folder] [input.json] Run once and print the outputs (--state file, --save-state file); preview is the same command
17
17
  dev [folder] Preview the interface with hot reload (--port 4800, --build "npm run build";
18
18
  --live: review each saved version in your StillMade account)
19
19
  types [folder] Write stillmade-env.d.ts: TypeScript types for the ports and globals
@@ -103,40 +103,71 @@ export default async function run(input, stillmade) {
103
103
  `;
104
104
  const cardsManifest=(id,name)=>manifest({id,version:'1.0.0',name,description:'Turn lines of text into numbered title cards, with a pinned card shared by everyone in the project.',runtime:'javascript',
105
105
  inputs:{lines:{type:'text',primary:true,required:true,semantic:'plain_text',description:'One title per line.'}},
106
- outputs:{cards:{type:'json',primary:true,semantic:'title_cards',description:'The numbered title cards.',schema:{cards:'object[]'}}},ui:[]});
106
+ outputs:{cards:{type:'json',primary:true,semantic:'title_cards',description:'The numbered title cards.',schema:{cards:'object[]'}}},
107
+ // The required input also gets a generated control: pack --original and import refuse a required input nobody can set.
108
+ ui:[{control:'text',port:'lines'}]});
107
109
  const CARDS_TESTS=[{name:'Two titles',input:{lines:'Opening\nThe long road'},expected:{cards:{cards:[{number:1,title:'Opening'},{number:2,title:'The long road'}]}}}];
108
110
 
109
- const HTML_VIEW_HTML=`<main class="cards">
110
- <label for="lines">One title per line</label>
111
- <textarea id="lines" rows="4" data-stillmade-input="lines"></textarea>
112
- <button type="button" id="make" data-stillmade-action="run">Make cards</button>
113
- <section class="result" id="result">
114
- <ol id="list"></ol>
115
- <p role="status" id="status"></p>
111
+ const HTML_VIEW_HTML=`<main class="sm-block">
112
+ <section class="sm-stage" aria-live="polite">
113
+ <div class="sm-empty" id="empty">
114
+ <span class="sm-glyph" data-glyph="text" aria-hidden="true"></span>
115
+ <p>Cards show here</p>
116
+ </div>
117
+ <ol class="sm-gallery cards" id="list" hidden></ol>
116
118
  </section>
119
+ <div class="sm-field">
120
+ <div class="sm-label">
121
+ <label for="lines">Titles</label>
122
+ <details class="sm-hint"><summary aria-label="About titles" title="About titles"></summary><p>One per line, up to 50.</p></details>
123
+ </div>
124
+ <textarea id="lines" rows="4" data-stillmade-input="lines" data-stillmade-share="lines"></textarea>
125
+ </div>
126
+ <div class="sm-actions">
127
+ <button type="button" class="sm-primary" id="make" data-stillmade-action="run">Make cards</button>
128
+ <p class="sm-status" id="status" role="status"></p>
129
+ </div>
117
130
  </main>
118
131
  `;
119
- const HTML_VIEW_JS=`// A frame view in plain JavaScript: no build step.
132
+ // The Block kit (injected by the host) styles everything above; this is only
133
+ // what is specific to title cards.
134
+ const HTML_VIEW_CSS=`.cards{--sm-tile:180px;align-self:start;width:100%}
135
+ .cards li{display:grid;gap:4px;padding:12px;background:var(--bg-elev-1)}
136
+ .cards li span{color:var(--fg-muted);font-size:.8125rem}
137
+ `;
138
+ const HTML_VIEW_JS=`// A frame view in plain JavaScript: no build step. Layout and controls come
139
+ // from the Block kit classes (sm-*) that StillMade adds to every view.
120
140
  const lines = document.getElementById('lines'), make = document.getElementById('make');
121
- const result = document.getElementById('result'), list = document.getElementById('list'), status = document.getElementById('status');
122
- // loading, error or success on the result area, for people and StillMade's device checks.
123
- const show = (phase, text) => { result.dataset.stillmadeState = phase; result.setAttribute('aria-busy', String(phase === 'loading')); status.setAttribute('role', phase === 'error' ? 'alert' : 'status'); status.textContent = text; };
141
+ const empty = document.getElementById('empty'), list = document.getElementById('list'), status = document.getElementById('status');
142
+ // loading, error or success on the status, for people and StillMade's device checks.
143
+ const show = (phase, text) => { status.dataset.state = phase; status.dataset.stillmadeState = phase; status.setAttribute('role', phase === 'error' ? 'alert' : 'status'); status.textContent = text; };
144
+ // Everyone in the project sees the same titles and cards; read-only teammates cannot run.
145
+ let busy = false, canEdit = true;
146
+ const render = (cards) => {
147
+ list.replaceChildren(...cards.map((card) => {
148
+ const item = document.createElement('li');
149
+ const number = document.createElement('span'); number.textContent = String(card.number);
150
+ const title = document.createElement('strong'); title.textContent = card.title;
151
+ item.append(number, title);
152
+ return item;
153
+ }));
154
+ empty.hidden = cards.length > 0; list.hidden = cards.length === 0;
155
+ };
124
156
  StillMade.onInput((input) => { lines.value = input.lines || ''; });
157
+ StillMade.onShared((shared) => {
158
+ canEdit = shared.canEdit; make.disabled = busy || !canEdit;
159
+ if (shared.outputs && shared.outputs.cards) render(shared.outputs.cards.cards);
160
+ });
125
161
  make.addEventListener('click', async () => {
126
- make.disabled = true; show('loading', 'Making cards');
162
+ if (busy || !canEdit) return;
163
+ busy = true; make.disabled = true; show('loading', 'Making');
127
164
  try {
128
165
  const output = await StillMade.run({ lines: lines.value });
129
- list.replaceChildren(...output.cards.cards.map((card) => {
130
- const item = document.createElement('li');
131
- const number = document.createElement('span'); number.textContent = String(card.number);
132
- const title = document.createElement('strong'); title.textContent = card.title;
133
- item.append(number, title);
134
- return item;
135
- }));
166
+ render(output.cards.cards);
136
167
  const count = output.cards.cards.length;
137
168
  show('success', count + (count === 1 ? ' card' : ' cards'));
138
169
  } catch (error) { show('error', error.message); }
139
- finally { make.disabled = false; }
170
+ finally { busy = false; make.disabled = !canEdit; }
140
171
  });
141
172
  `;
142
173
 
@@ -213,7 +244,7 @@ export const TEMPLATES=Object.freeze({
213
244
  }},
214
245
  'view-html':{description:'An interface in plain HTML, CSS and JavaScript; no build step.',runtime:'javascript',files:async()=>{
215
246
  const pkg=cardsManifest('example.title-cards-html','Title cards');
216
- return {'stillmade.block.json':json(pkg),'src/run.js':CARDS_RUN,'src/view.html':HTML_VIEW_HTML,'src/view.css':CARDS_CSS,'src/view.js':HTML_VIEW_JS,'src/view.config.json':json({runtime:'frame'}),'tests/fixtures.json':json(CARDS_TESTS),'README.md':readme(pkg.name,'A Block with a frame view in plain web code. Edit src/view.html, src/view.css and src/view.js; `stillmade-block dev .` reloads on save.',OFFLINE)};
247
+ return {'stillmade.block.json':json(pkg),'src/run.js':CARDS_RUN,'src/view.html':HTML_VIEW_HTML,'src/view.css':HTML_VIEW_CSS,'src/view.js':HTML_VIEW_JS,'src/view.config.json':json({runtime:'frame'}),'tests/fixtures.json':json(CARDS_TESTS),'README.md':readme(pkg.name,'A Block with a frame view in plain web code. Edit src/view.html, src/view.css and src/view.js; `stillmade-block dev .` reloads on save.',OFFLINE)};
217
248
  }},
218
249
  text:{description:'Hosted text generation (text.generate) on the person\'s credits.',runtime:'capability',files:fromExample(async()=>(await import('../block-sdk/capability-example.js')).textGenerate,'A capability Block: src/capability.json asks StillMade for text.generate. People choose a model and approve the price.',HOSTED)},
219
250
  'image-generate':{description:'Hosted image generation with any catalog model.',runtime:'capability',files:fromExample(async()=>(await import('../block-sdk/image-generation-example.js')).imageGenerate,'Hosted image generation.',HOSTED)},
@@ -15,6 +15,7 @@ import {initialBlockState,validateBlockState} from '../block-sdk/state.js';
15
15
  import {createUniversalResolver} from '../block-sdk/universal-resolver.js';
16
16
  import {requiresDesktopExecution} from '../block-sdk/desktop-execution.js';
17
17
  import {mobileActivationStatus} from '../block-sdk/platform-checks.js';
18
+ import {blockKitProblems,BLOCK_KIT_READINESS_RULES} from '../block-sdk/view-design.js';
18
19
  import {FIRST_PARTY_SDK_PACKAGES} from '../block-sdk/first-party-packages.js';
19
20
  import {previewProjectType} from './type-preview.js';
20
21
  import {semanticRegistryForManifests} from '../block-sdk/package-semantics.js';
@@ -140,6 +141,10 @@ export async function assessBlockReadiness(pkg,{catalog=FIRST_PARTY_SDK_PACKAGES
140
141
  else if(sharedEditing?.passed===true)verified('DOD-13',sharedEditing.reason);
141
142
  else if(sharedEditing?.passed===false)failed('DOD-13',sharedEditing.reason);
142
143
  else notVerified('DOD-13',sharedEditing?.reason||'The shared-interface source contract passed; two-participant runtime behavior has not been checked.');
144
+ // A new interface names its action, shows no price and no page heading before device checks
145
+ // count. StillMade's own built-in Blocks (sm.*) are redesigned separately.
146
+ const kit=String(manifest.id).startsWith('sm.')?[]:blockKitProblems(pkg.view).filter(problem=>BLOCK_KIT_READINESS_RULES.includes(problem.rule));
147
+ if(kit.length)failed('DOD-15',kit.map(problem=>problem.message).join(' '));
143
148
  const ui=platformChecks?mobileActivationStatus(platformChecks,sourceDigest,manifest):null;
144
149
  if(ui?.ready)verified('DOD-15',ui.reason);else notVerified('DOD-15',ui?.reason||'Device UI checks have not run for this exact package.');
145
150
 
@@ -0,0 +1,29 @@
1
+ // One rule for which of a Block's inputs is its brief (October 16, 2026). A brief is
2
+ // the facts about the thing a project makes (the car, the shop, the offer), so it comes
3
+ // from that project's own Chat step, never from the conversation that built the Project
4
+ // Type or from the Type's saved defaults. Built-in Blocks declare it as a brief input;
5
+ // a custom Block often uses a plain text input named or labelled "brief". Every writer
6
+ // that must leave briefs alone (the planner, Chat's build actions, a new project's start)
7
+ // and the brief UI use this rule, so they agree.
8
+ // A context input reads a project value and is never a brief the person writes.
9
+
10
+ const spaced=value=>String(value??'').replace(/([a-z0-9])([A-Z])/g,'$1 $2').replace(/[_\-.]+/g,' ');
11
+ const BRIEF=/\bbrief\b/i;
12
+
13
+ /** True when an input is a Step's brief: a brief input, or a text input named or labelled "brief". */
14
+ export function isBriefPort(name,port,{label}={}){
15
+ if(!port||typeof port!=='object'||Array.isArray(port)||port.context)return false;
16
+ if(port.type==='brief')return true;
17
+ return port.type==='text'&&[name,label,port.label].some(value=>BRIEF.test(spaced(value)));
18
+ }
19
+
20
+ /** The label a Block's own control gives an input, if any (manifest.ui). */
21
+ export function inputLabel(manifest,name){
22
+ const control=Array.isArray(manifest?.ui)?manifest.ui.find(item=>item?.port===name):null;
23
+ return typeof control?.label==='string'?control.label:undefined;
24
+ }
25
+
26
+ /** The names of a Block's brief inputs. */
27
+ export function briefInputNames(manifest){
28
+ return Object.entries(manifest?.inputs||{}).filter(([name,port])=>isBriefPort(name,port,{label:inputLabel(manifest,name)})).map(([name])=>name);
29
+ }
@@ -79,17 +79,25 @@ const screens = [
79
79
  ];
80
80
  const HOST_WORKSPACE_BLOCKS = [SCRIPT_WRITER_BLOCK,...screens.map(([key,name,phase,source,description,features])=>{
81
81
  const legacy=LEGACY_BLOCKS.find(block=>block.manifest.id===`sm.${key}`);
82
- const version=key==='editor'?'1.5.0':key==='pipeline'?'1.2.0':'1.1.0';
82
+ // The Editor's main release is the original one (1.0): the full video editor.
83
+ // Its portable 2.x releases are a separate, simpler view kept for their exact pins.
84
+ // Its released manifest keeps its name (Video editor); it is listed as the Editor, at the same address.
85
+ if(key==='editor')return {...legacy,source,workspace:{phase,features},summary:{...legacy.summary,text:description},adapterStatus:'workspace',listingName:name};
86
+ const version=key==='pipeline'?'1.2.0':'1.1.0';
83
87
  return {...legacy,manifest:hostReleaseManifest(legacy.manifest.id,version),source,workspace:{phase,features},summary:{...legacy.summary,text:description},adapterStatus:'workspace'};
84
88
  })];
85
89
  HOST_WORKSPACE_BLOCKS.splice(HOST_WORKSPACE_BLOCKS.findIndex(block=>block.manifest.id==='sm.pipeline')+1,0,VIDEO_ASSETS_BLOCK);
86
90
  // Promote a portable workspace after its accepted production MVP and rendered
87
91
  // acceptance are complete; advanced host extensions can remain on old exact pins. Exact portable releases remain usable as normal
88
92
  // Steps before promotion; this map only controls the default production route.
89
- export const CURRENT_PORTABLE_WORKSPACE_VERSIONS=Object.freeze({'sm.chat':'2.0.3','sm.script':'2.1.5','sm.voiceover':'2.1.6','sm.shot-plan':'2.1.3','sm.blueprint':'2.1.9','sm.pipeline':'2.2.5','sm.video-assets':'2.1.4','sm.panel-strip':'2.3.7','sm.board-editor':'2.2.10','sm.animated-scene':'2.3.8','sm.editor':'2.2.11'});
93
+ export const CURRENT_PORTABLE_WORKSPACE_VERSIONS=Object.freeze({'sm.chat':'2.0.3','sm.script':'2.1.5','sm.voiceover':'2.1.6','sm.shot-plan':'2.2.1','sm.blueprint':'2.1.9','sm.pipeline':'2.2.5','sm.video-assets':'2.1.4','sm.panel-strip':'2.4.1','sm.board-editor':'2.2.10','sm.animated-scene':'2.3.8','sm.editor':'2.2.11'});
94
+ // Workspaces whose main release stays the host one: the original Editor (1.0) is
95
+ // the full video editor. Its accepted portable release stays discoverable for
96
+ // Steps that hand the Editor a prepared timeline, which only it accepts.
97
+ export const MAIN_HOST_WORKSPACES=new Set(['sm.editor']);
90
98
  export const WORKSPACE_BLOCKS=HOST_WORKSPACE_BLOCKS.map(block=>{
91
99
  const version=CURRENT_PORTABLE_WORKSPACE_VERSIONS[block.manifest.id];
92
- if(!version)return block;
100
+ if(!version||MAIN_HOST_WORKSPACES.has(block.manifest.id))return block;
93
101
  const portable=FIRST_PARTY_SDK_PACKAGES.find(pkg=>pkg.manifest.id===block.manifest.id&&pkg.manifest.version===version);
94
102
  if(!portable)throw Error(`Missing accepted portable workspace ${block.manifest.id}@${version}`);
95
103
  return portable;
@@ -124,10 +132,23 @@ const newer=(left,right)=>{const a=semverParts(left),b=semverParts(right);for(le
124
132
  const currentPortableById=new Map();
125
133
  // An unset app connector is a project placeholder, not something to find or install.
126
134
  export const UNDISCOVERABLE_SDK_BLOCKS=new Set(['sm.connect-app']);
127
- for(const block of SDK_BUILTIN_BLOCKS){if(UNDISCOVERABLE_SDK_BLOCKS.has(block.manifest.id))continue;const prior=currentPortableById.get(block.manifest.id);if(!prior||newer(block.manifest.version,prior.manifest.version))currentPortableById.set(block.manifest.id,block);}
135
+ // Export video is offered as the original Editor opened at its Export (1.0.0), so
136
+ // people can still change the cut before rendering (the owner, Oct 15). The
137
+ // render-only 2.0.0 stays in BUILTIN_BLOCKS for exact project pins and for
138
+ // planning with explicit candidates; a prepared timeline goes to the portable Editor.
139
+ const EXPORT_VIDEO_EDITOR=LEGACY_BLOCKS.find(block=>block.manifest.id==='sm.export-video'&&block.manifest.version==='1.0.0');
140
+ export const DISCOVERED_AS_HOST=new Map([['sm.export-video',EXPORT_VIDEO_EDITOR]]);
141
+ for(const block of SDK_BUILTIN_BLOCKS){if(UNDISCOVERABLE_SDK_BLOCKS.has(block.manifest.id)||DISCOVERED_AS_HOST.has(block.manifest.id))continue;const prior=currentPortableById.get(block.manifest.id);if(!prior||newer(block.manifest.version,prior.manifest.version))currentPortableById.set(block.manifest.id,block);}
128
142
  export const DISCOVERABLE_SDK_BUILTIN_BLOCKS = Object.freeze([...currentPortableById.values()]);
129
- export const DISCOVERABLE_BLOCKS = [STARTING_CHAT_BLOCK,...WORKSPACE_BLOCKS,...DISCOVERABLE_SDK_BUILTIN_BLOCKS.filter(block=>!WORKSPACE_BLOCKS.some(current=>current.manifest.id===block.manifest.id&&current.manifest.version===block.manifest.version))];
130
- export const BUILTIN_BLOCKS = [STARTING_CHAT_BLOCK,...WORKSPACE_BLOCKS,...HISTORICAL_WORKSPACE_BLOCKS,DESCRIBE_MEDIA_HOST_1_1,YOUTUBE_HOST_1_1,...GENERATION_HOST_RELEASES,...SDK_BUILTIN_BLOCKS.filter(block=>!WORKSPACE_BLOCKS.some(current=>current.manifest.id===block.manifest.id&&current.manifest.version===block.manifest.version)),...LEGACY_BLOCKS];
143
+ const ACCEPTED_PORTABLE_RELEASES=[...MAIN_HOST_WORKSPACES].map(id=>FIRST_PARTY_SDK_PACKAGES.find(pkg=>pkg.manifest.id===id&&pkg.manifest.version===CURRENT_PORTABLE_WORKSPACE_VERSIONS[id])).filter(Boolean);
144
+ export const DISCOVERABLE_BLOCKS = [STARTING_CHAT_BLOCK,...WORKSPACE_BLOCKS,...ACCEPTED_PORTABLE_RELEASES,...DISCOVERED_AS_HOST.values(),...DISCOVERABLE_SDK_BUILTIN_BLOCKS.filter(block=>!ACCEPTED_PORTABLE_RELEASES.includes(block)).filter(block=>!WORKSPACE_BLOCKS.some(current=>current.manifest.id===block.manifest.id&&current.manifest.version===block.manifest.version))];
145
+ // Planners pin a finished file to a Block's output port, which the Editor alias has
146
+ // none of, so planning keeps the newest render-only release in its place. Chat
147
+ // builds then fold it into the Editor (compact-project.js), so people still get
148
+ // the Editor at its Export.
149
+ const newestRelease=id=>SDK_BUILTIN_BLOCKS.filter(block=>block.manifest.id===id).reduce((best,block)=>!best||newer(block.manifest.version,best.manifest.version)?block:best,null);
150
+ export const PLANNING_BLOCKS = DISCOVERABLE_BLOCKS.map(block=>DISCOVERED_AS_HOST.get(block.manifest.id)===block?newestRelease(block.manifest.id)||block:block);
151
+ export const BUILTIN_BLOCKS = [STARTING_CHAT_BLOCK,...WORKSPACE_BLOCKS,...HISTORICAL_WORKSPACE_BLOCKS,DESCRIBE_MEDIA_HOST_1_1,YOUTUBE_HOST_1_1,...GENERATION_HOST_RELEASES,...SDK_BUILTIN_BLOCKS.filter(block=>!WORKSPACE_BLOCKS.some(current=>current.manifest.id===block.manifest.id&&current.manifest.version===block.manifest.version)),...LEGACY_BLOCKS.filter(block=>!WORKSPACE_BLOCKS.some(current=>current.manifest.id===block.manifest.id&&current.manifest.version===block.manifest.version))];
131
152
  const flows={guided:['voiceover','shot-plan','blueprint','pipeline','editor'],blank:['pipeline','editor'],imagePanel:['voiceover','panel-strip','editor'],whiteboard:['voiceover','board-editor','editor'],animated:['voiceover','shot-plan','animated-scene','editor']};
132
153
  const typeDescriptions={
133
154
  guided:'Turn an idea into a video, from the script to the final cut.',
@@ -139,7 +160,7 @@ const typeDescriptions={
139
160
  // Freeze the actual pre-collaboration-release catalog fallback. Historical type
140
161
  // definitions must not follow future changes to the discoverable Block map.
141
162
  const PINNED_TYPE_WORKSPACE_VERSIONS=Object.freeze({'voiceover':'2.1.3','shot-plan':'2.1.2','blueprint':'2.1.7','pipeline':'2.2.2','editor':'2.2.7','panel-strip':'2.3.5','board-editor':'2.2.8','animated-scene':'2.3.6'});
142
- const currentBuiltinTypes=(version,pipelineVersion,scriptVersion=SCRIPT_WRITER_BLOCK.manifest.version,versions={})=>LEGACY_TYPES.map(type=>({...type,description:typeDescriptions[type.legacyMode],summary:{...type.summary,text:typeDescriptions[type.legacyMode]},version,stages:flows[type.legacyMode].map((key,i)=>{const block=WORKSPACE_BLOCKS.find(block=>block.manifest.id===`sm.${key}`);return {id:`stage-${i+1}`,blockId:block.manifest.id,version:versions[key]||(key==='pipeline'?pipelineVersion:key==='script'?scriptVersion:PINNED_TYPE_WORKSPACE_VERSIONS[key]),label:block.manifest.name};})}));
163
+ const currentBuiltinTypes=(version,pipelineVersion,scriptVersion=SCRIPT_WRITER_BLOCK.manifest.version,versions={})=>LEGACY_TYPES.map(type=>({...type,description:typeDescriptions[type.legacyMode],summary:{...type.summary,text:typeDescriptions[type.legacyMode]},version,stages:flows[type.legacyMode].map((key,i)=>{const block=WORKSPACE_BLOCKS.find(block=>block.manifest.id===`sm.${key}`);return {id:`stage-${i+1}`,blockId:block.manifest.id,version:versions[key]||(key==='pipeline'?pipelineVersion:key==='script'?scriptVersion:PINNED_TYPE_WORKSPACE_VERSIONS[key]),label:key==='editor'?'Editor':block.manifest.name};})}));
143
164
  const PRIOR_HOST_WORKSPACE_VERSIONS={'shot-plan':'1.1.0','blueprint':'1.1.0','panel-strip':'2.3.4','board-editor':'1.1.0','animated-scene':'1.1.0'};
144
165
  const PREVIOUS_BUILTIN_TYPES=currentBuiltinTypes('1.2.0','1.2.0',SCRIPT_WRITER_BLOCK.manifest.version,PRIOR_HOST_WORKSPACE_VERSIONS);
145
166
  const SHOTS_MIGRATED_TYPES=currentBuiltinTypes('1.3.0','1.2.0',SCRIPT_WRITER_BLOCK.manifest.version,{'blueprint':'1.1.0','board-editor':'1.1.0','animated-scene':'1.1.0'});
@@ -155,8 +176,10 @@ const SHARED_CANVAS_TYPES = currentBuiltinTypes('1.11.0','2.2.3',SCRIPT_WRITER_B
155
176
  const SHARED_WORKSPACE_TYPES = currentBuiltinTypes('1.12.0','2.2.3',SCRIPT_WRITER_BLOCK.manifest.version,{'blueprint':'2.1.8','panel-strip':'2.3.6','board-editor':'2.2.9','animated-scene':'2.3.7'});
156
177
  const PRIOR_CURRENT_TYPES = currentBuiltinTypes('1.13.0','2.2.3',SCRIPT_WRITER_BLOCK.manifest.version,{'blueprint':'2.1.8','panel-strip':'2.3.6','board-editor':'2.2.9','animated-scene':'2.3.7','editor':'2.2.8'});
157
178
  const PREVIOUS_PORTABLE_TYPES=currentBuiltinTypes('1.14.0','2.2.4',SCRIPT_WRITER_BLOCK.manifest.version,{'blueprint':'2.1.8','panel-strip':'2.3.6','board-editor':'2.2.9','animated-scene':'2.3.7','editor':'2.2.8'});
158
- export const CURRENT_BUILTIN_TYPES = [...currentBuiltinTypes('1.15.0','2.2.4','2.1.4',{'voiceover':'2.1.4','shot-plan':'2.1.3','blueprint':'2.1.9','panel-strip':'2.3.7','board-editor':'2.2.10','animated-scene':'2.3.8','editor':'2.2.9'}),...REVENUE_REVIEW_PROJECT_TYPES,PRODUCT_AD_VARIANTS_PROJECT_TYPE,...FIRST_REVENUE_PROJECT_TYPES];
159
- const HISTORICAL_CURRENT_TYPES=[PRODUCT_AD_VARIANTS_PROJECT_TYPE_1_0_3,PRIOR_PRODUCT_AD_VARIANTS_PROJECT_TYPE,PREVIOUS_PRODUCT_AD_VARIANTS_PROJECT_TYPE,...PREVIOUS_PORTABLE_TYPES,...PRIOR_CURRENT_TYPES,...SHARED_WORKSPACE_TYPES,...SHARED_CANVAS_TYPES,...SHARED_BLUEPRINT_TYPES,...PRIOR_SHARED_TYPES,...MEDIA_LIBRARY_TYPES,...EDITOR_AUDIO_TYPES,...PLAYABLE_EDITOR_TYPES,...ADVANCED_PORTABLE_TYPES,...PORTABLE_MVP_TYPES,...PRE_PROMOTION_CURRENT_TYPES,...PREVIOUS_BUILTIN_TYPES.filter(type=>flows[type.legacyMode].includes('shot-plan')),...currentBuiltinTypes('1.1.0','1.1.0','1.1.0',PRIOR_HOST_WORKSPACE_VERSIONS)];
179
+ // 1.16.0: every built-in type ends in the original Editor (1.0), the full video editor.
180
+ const PORTABLE_EDITOR_TYPES=currentBuiltinTypes('1.15.0','2.2.4','2.1.4',{'voiceover':'2.1.4','shot-plan':'2.1.3','blueprint':'2.1.9','panel-strip':'2.3.7','board-editor':'2.2.10','animated-scene':'2.3.8','editor':'2.2.9'});
181
+ export const CURRENT_BUILTIN_TYPES = [...currentBuiltinTypes('1.16.0','2.2.4','2.1.4',{'voiceover':'2.1.4','shot-plan':'2.1.3','blueprint':'2.1.9','panel-strip':'2.3.7','board-editor':'2.2.10','animated-scene':'2.3.8','editor':'1.0.0'}),...REVENUE_REVIEW_PROJECT_TYPES,PRODUCT_AD_VARIANTS_PROJECT_TYPE,...FIRST_REVENUE_PROJECT_TYPES];
182
+ const HISTORICAL_CURRENT_TYPES=[...PORTABLE_EDITOR_TYPES,PRODUCT_AD_VARIANTS_PROJECT_TYPE_1_0_3,PRIOR_PRODUCT_AD_VARIANTS_PROJECT_TYPE,PREVIOUS_PRODUCT_AD_VARIANTS_PROJECT_TYPE,...PREVIOUS_PORTABLE_TYPES,...PRIOR_CURRENT_TYPES,...SHARED_WORKSPACE_TYPES,...SHARED_CANVAS_TYPES,...SHARED_BLUEPRINT_TYPES,...PRIOR_SHARED_TYPES,...MEDIA_LIBRARY_TYPES,...EDITOR_AUDIO_TYPES,...PLAYABLE_EDITOR_TYPES,...ADVANCED_PORTABLE_TYPES,...PORTABLE_MVP_TYPES,...PRE_PROMOTION_CURRENT_TYPES,...PREVIOUS_BUILTIN_TYPES.filter(type=>flows[type.legacyMode].includes('shot-plan')),...currentBuiltinTypes('1.1.0','1.1.0','1.1.0',PRIOR_HOST_WORKSPACE_VERSIONS)];
160
183
  export const BUILTIN_TYPES = [...CURRENT_BUILTIN_TYPES,...HISTORICAL_CURRENT_TYPES,...LEGACY_TYPES];
161
184
  export const workspaceBlock = (id,version)=>DISCOVERABLE_BLOCKS.find(block=>block.manifest.id===id && (!version || block.manifest.version===version));
162
185
  export const discoverableBuiltin = item=>!!workspaceBlock((item.manifest || item).id,(item.manifest || item).version);
@@ -1,4 +1,4 @@
1
- import {assert,digest} from '../block-sdk/contracts.js';
1
+ import {assert,digest,HOSTED_CAPABILITY_OPERATIONS,PORTABLE_ACTIONS} from '../block-sdk/contracts.js';
2
2
  import {semanticValueIsFresh} from '../block-sdk/semantic-freshness.js';
3
3
  import {readCanvasSemanticContext} from './semantic-results.js';
4
4
 
@@ -24,8 +24,12 @@ export async function pinExplicitFlowSource(pin,edge,sourceNode,canvasEdge,requi
24
24
  export async function pinCurrentSemanticSource(canvas,stage,node,port,requirement,permissions=[]) {
25
25
  const record=readCanvasSemanticContext(canvas).snapshot().values.find(value=>value.source.kind==='block' && value.source.stepId===stage.id && value.source.port===port);
26
26
  assert(record && !node.data.blockStale && semanticValueIsFresh(record,requirement),'SEGMENT_SOURCE','Update the source Block before reusing its result.');
27
- const missingPermissions=(record.permissions||[]).filter(permission=>!permissions.includes(permission));
28
- assert(!missingPermissions.length,'SEGMENT_SOURCE',`The receiving Block cannot reuse this saved result: its pinned permission contract lacks ${missingPermissions.slice(0,5).join(', ')}${missingPermissions.length>5?' and more':''}. Review a compatible Block release or route; the retained result was not applied.`);
27
+ // Hosted operations (text.generate, ...) and portable actions are things the
28
+ // producer did, not data its result carries: a Shots Block downstream of a
29
+ // Script Writer must not need text.generate to reuse the script. Data scopes
30
+ // (project context, network, filesystem, secrets) still have to be held.
31
+ const missingPermissions=(record.permissions||[]).filter(permission=>!permissions.includes(permission)&&!Object.hasOwn(HOSTED_CAPABILITY_OPERATIONS,permission)&&!PORTABLE_ACTIONS.includes(permission));
32
+ assert(!missingPermissions.length,'SEGMENT_SOURCE',`Not applied, for privacy: this saved result was made with project data this Step's Block release isn't allowed to read (${missingPermissions.slice(0,5).join(', ')}${missingPermissions.length>5?' and more':''}). Earlier updates are kept. Run this Step again from its page, or choose a Block release that can read that data.`);
29
33
  assert(Object.hasOwn(node.data.blockOutputs || {},port),'SEGMENT_SOURCE','The source Block has no current saved result.');
30
34
  const valueDigest=await digest(node.data.blockOutputs[port]);
31
35
  assert(valueDigest===await digest(record.value),'SEGMENT_SOURCE','The saved result changed. Update its source before continuing.');
@@ -11,7 +11,7 @@ import {readCanvasSemanticContext} from './semantic-results.js';
11
11
  import {stageNodeId} from './type-workspace.js';
12
12
  import {nativeHostFor} from './native-hosts.js';
13
13
  import {BUILTIN_BLOCKS} from './catalog.js';
14
- import {ADAPTERS} from '../block-sdk/connection-adapters.js';
14
+ import {ADAPTERS,NARRATION_ADAPTERS} from '../block-sdk/connection-adapters.js';
15
15
  import {projectFlowSnapshot,PROJECT_FLOW_CONTRACT_VERSION,PROJECT_FLOW_FAILURE_CODES} from '../block-sdk/project-flow-contract.js';
16
16
  import {projectFlowOverrideTarget} from './project-flow-input-override.js';
17
17
  import {portablePinnedValue,restorePinnedValue} from './pinned-media-value.js';
@@ -30,6 +30,9 @@ const mediaAvailability=value=>localMediaUrl(value)?'playable':/^(blob:|data:|fi
30
30
  const localMediaDetail=value=>{
31
31
  const url=value?.url||'',assetId=/^sm-media:\/\/asset\/([a-f0-9-]{36})$/.exec(url)?.[1];
32
32
  if(assetId&&value.assetId===assetId&&typeof value.versionId==='string'&&/^[a-f0-9]{64}$/.test(value.versionId||''))return {localSource:'desktop',localReference:{assetId,versionId:value.versionId,kind:value.kind}};
33
+ // Media kept in a browser's own storage (client deviceMedia.js): the address names the device record exactly.
34
+ const device=/^sm-media:\/\/device\/([a-f0-9-]{36})\/([a-f0-9]{64})$/.exec(url);
35
+ if(device)return {localSource:'browser-device',localReference:{assetId:device[1],versionId:device[2],kind:value.kind}};
33
36
  return /^(sm-media:|file:)/i.test(url)?{localSource:'device'}:/^blob:/i.test(url)?{localSource:'browser-session'}:/^data:/i.test(url)?{localSource:'inline'}:{};
34
37
  };
35
38
  const hasLocalMedia=value=>!!value&&(value.availability==='local-only'&&value.localSource!=='inline'||value.kind==='collection'&&(value.items||[]).some(item=>hasLocalMedia(item.preview))||value.kind==='structured'&&(value.fields||[]).some(field=>hasLocalMedia(field.preview)));
@@ -81,11 +84,18 @@ const pinOptionsFor=(canvas,producer,stage,portKey,inputRequirement,edge,labels,
81
84
  };
82
85
  const adapterDetail=(pin,materialized)=>{
83
86
  if(!pin?.id)return null;
84
- const definition=ADAPTERS.find(item=>item.id===pin.id&&Number(item.version)===Number(pin.version));
87
+ const definition=[...ADAPTERS,...NARRATION_ADAPTERS].find(item=>item.id===pin.id&&Number(item.version)===Number(pin.version));
85
88
  if(!definition)return {id:String(pin.id).slice(0,160),version:String(pin.version??'unknown').slice(0,40),available:false,status:'This saved conversion version is unavailable.'};
86
- const effect=pin.id==='scene-to-shots'?'Uses the selected scene’s ordered project shots.':pin.id==='shot-to-selected-image'?'Uses the project image selected for this shot.':pin.id==='csv-to-table'||pin.id==='csv-file-to-table'?'Reads CSV rows into a table.':pin.id==='text-to-script'?'Wraps text in the Block script shape.':pin.id==='script-to-text'?'Reads text from the Block script shape.':pin.id==='text-file-to-text'?'Reads the selected inline text file.':pin.id==='integer-to-number'?'Converts the integer value to a number.':pin.id.endsWith('-to-asset')?'Keeps the same media reference as a typed asset.':pin.id.startsWith('asset-to-')?'Passes the typed media reference to a matching media input.':'Applies the registered deterministic value conversion.';
89
+ const effect=pin.id==='scene-to-shots'?'Uses the selected scene’s ordered project shots.':pin.id==='shot-to-selected-image'?'Uses the project image selected for this shot.':pin.id==='csv-to-table'||pin.id==='csv-file-to-table'?'Reads CSV rows into a table.':pin.id==='text-to-script'?'Wraps text in the Block script shape.':pin.id==='script-as-narration'||pin.id==='text-as-narration'?'Uses this script as the narration.':pin.id==='transcript-as-narration-timing'?'Uses these word timings as the narration timing.':pin.id==='script-to-text'?'Reads text from the Block script shape.':pin.id==='text-file-to-text'?'Reads the selected inline text file.':pin.id==='integer-to-number'?'Converts the integer value to a number.':pin.id.endsWith('-to-asset')?'Keeps the same media reference as a typed asset.':pin.id.startsWith('asset-to-')?'Passes the typed media reference to a matching media input.':'Applies the registered deterministic value conversion.';
87
90
  return {id:definition.id,version:definition.version,available:true,inputType:definition.output,outputType:definition.input,effect,status:materialized?'Source value is available; conversion runs with this input.':'Waiting for the source value; conversion has not run.',steps:definition.steps||[{id:definition.id,version:definition.version}],contextFields:definition.contextFields||[]};
88
91
  };
92
+ // A project-context input (Shots' saved script, the voiceover timing) is filled
93
+ // from the project when the project has one; no connection can fill it.
94
+ const CONTEXT_NAMES={script:'saved script',transcript:'voiceover timing',shotPlan:'shot plan',assets:'media',versions:'media versions',editor:'edit',metadata:'details'};
95
+ export function contextInputNote(context){
96
+ const name=CONTEXT_NAMES[context]||String(context||'information').replace(/([a-z0-9])([A-Z])/g,'$1 $2').replace(/[_-]+/g,' ').toLowerCase();
97
+ return `Uses the project’s ${name} when there is one.`;
98
+ }
89
99
  const sourceName=(selected,labels)=>{
90
100
  if(['conversion-block','conversion-block-path'].includes(selected?.kind))return selected.sourceLabel || 'Add converter Blocks';
91
101
  const source=selected?.record?.source;
@@ -189,7 +199,7 @@ export function inspectProjectFlow(type,canvas={nodes:[],edges:[]},{projectId=nu
189
199
  const state=optionalUnused?'not-used':item.state==='broken'?'needs-choice':!item.requirement.required&&!item.selected?'not-used':sourceSkipped?'skipped':source?.kind==='block'&&!produced?'waiting':sourceStale?'needs-updating':materialized?'ready':item.state==='needs-user-input'?'needs-choice':'waiting';
190
200
  const selectedEdge=canvasEdge&&source?.kind==='block'&&canvasEdge.source===producer?.id&&canvasStage?.id===source.stageId&&canvasPort===source.port?canvasEdge:null;
191
201
  const pinOptions=selectedEdge&&!item.selected?.adapter?pinOptionsFor(canvas,producer,sourceStage,source.port,item.requirement,selectedEdge,labels,mediaVersions,restrictedResults):{canPinVersion:false,pinSource:null,pinChoices:[]};
192
- return {key:item.key,label:item.label,required:item.requirement.required!==false,state,reason:sourceSkipped?'The selected Block skipped this conditional path, so it produced no value.':native?.reason||item.reason,source:sourceName(item.selected,labels),sourceKind:source?.kind||null,sourcePlacementId:source?.stageId||null,sourceOutputKey:source?.port||null,selectedCandidateId:item.selected?.id||null,explicit:item.explicit===true,selectionReason:source?selectionReason(item,source):null,valuePolicy:source?.kind==='block'?(item.explicit?'follow-selected-output':'automatic-resolution'):source?.kind==='default'?'declared-default':source?'current-source-value':null,adapter:item.selected?.adapter?{id:item.selected.adapter.id,version:item.selected.adapter.version}:null,adapterDetail:adapterDetail(item.selected?.adapter,materialized),materialized,...(native?.observation?{sourceObservation:{...native.observation,revision:retainedRevision([native.observation,native.value])}}:{}),preview:materialized&&!sensitive(item.selected?.record)&&!(source?.kind==='block'&&(!(native?.port||producer?.data?.blockPackage?.manifest?.outputs?.[source.port])||sensitive(native?.port||producer.data.blockPackage.manifest.outputs[source.port])))&&!restrictedResults.has(`${source?.stageId}:${source?.port}`)?inputPreview(value,item.requirement):null,...pinOptions,...candidatePageSummary,...overrideMeta};
202
+ return {key:item.key,label:item.label,...(item.requirement.context?{context:item.requirement.context}:{}),required:item.requirement.required!==false,state,reason:sourceSkipped?'The selected Block skipped this conditional path, so it produced no value.':native?.reason||(item.requirement.context&&!materialized?contextInputNote(item.requirement.context):item.reason),source:sourceName(item.selected,labels),sourceKind:source?.kind||null,sourcePlacementId:source?.stageId||null,sourceOutputKey:source?.port||null,selectedCandidateId:item.selected?.id||null,explicit:item.explicit===true,selectionReason:source?selectionReason(item,source):null,valuePolicy:source?.kind==='block'?(item.explicit?'follow-selected-output':'automatic-resolution'):source?.kind==='default'?'declared-default':source?'current-source-value':null,adapter:item.selected?.adapter?{id:item.selected.adapter.id,version:item.selected.adapter.version}:null,adapterDetail:adapterDetail(item.selected?.adapter,materialized),materialized,...(native?.observation?{sourceObservation:{...native.observation,revision:retainedRevision([native.observation,native.value])}}:{}),preview:materialized&&!sensitive(item.selected?.record)&&!(source?.kind==='block'&&(!(native?.port||producer?.data?.blockPackage?.manifest?.outputs?.[source.port])||sensitive(native?.port||producer.data.blockPackage.manifest.outputs[source.port])))&&!restrictedResults.has(`${source?.stageId}:${source?.port}`)?inputPreview(value,item.requirement):null,...pinOptions,...candidatePageSummary,...overrideMeta};
193
203
  });
194
204
  const last=node?.data?.blockLastRun;
195
205
  const usedInputs=inputEvidence(last?.capturedInputs,inputs,[manifest?.inputs]);
@@ -15,7 +15,7 @@ export const PLAIN_BLOCK_DESCRIPTIONS=Object.freeze({
15
15
  'stillmade.product-ad-variants':'Make three short product ads with different opening hooks from your own footage. Nothing is invented.',
16
16
  'sm.youtube':'Upload a finished video to your connected YouTube channel.',
17
17
  'sm.stock-search':'Search free-to-use stock photos and videos and pick one.',
18
- 'sm.export-video':'Turn your timeline into a video file you can download and keep in the project.',
18
+ 'sm.export-video':'Opens the video editor at its Export: change anything on the timeline, then make the video file.',
19
19
  'sm.export-audio':'Turn your timeline into an audio file you can download and keep in the project.',
20
20
  'sm.motion-graphics':'Add animated titles, text, charts and transitions to your timeline. You see the change before it applies.',
21
21
  'sm.score':'Put music, sound effects and voiceover from your project on the timeline. You see the change before it applies.',
@@ -115,6 +115,9 @@ export function projectContextSnapshot(data={},options={}) {
115
115
  const ref={kind:'file',assetId:str(value.assetId),versionId:str(value.versionId),url:value.url,mimeType:value.mimeType,...pick(value,['name','bytes']),...ownership(owner),...(label?{label:str(label).slice(0,200)}:{}),source:'canvas'};
116
116
  files.set(JSON.stringify([ref.assetId,ref.versionId,ref.url]),ref);
117
117
  };
118
+ // Files a Step's "Render final file" stored (blockRenders): earlier versions only,
119
+ // never a current asset or an input of another Step.
120
+ const addFinishedRenders=(d,label)=>{for(const row of list(d.blockRenders))if(['video','audio'].includes(row?.kind)&&typeof row.url==='string'&&row.url.startsWith('/api/media/'))addMedia({...row,source:'block',owner:d,label,record:{createdAt:row.at},active:false});};
118
121
  const canvasNodes=new Map(list(canvas.nodes).map(node=>[node.id,node]));
119
122
  for(const node of canvasNodes.values()){
120
123
  // outputPort can expose a programmable output named `out` through the
@@ -153,6 +156,9 @@ export function projectContextSnapshot(data={},options={}) {
153
156
  for(const ref of current)if(!seen.has(ref.url)){const value=addMedia({...ref,source:'canvas',owner:d,role,label});if(value)selected.push(value);}
154
157
  references[referenceField].push(document({id:node.id,name:str(d.styleMeta?.name || d.name || d.label),...pick(d,['description','characterId','locationId']),...ownership(d),source:'canvas',...(referenceField==='styles' ? pick(d.styleMeta,['stylePrompt','description']) : {}),reference:selected[0] || null,references:selected}));
155
158
  }
159
+ // A finished render stays a version of the project whether or not its Step is
160
+ // current (October 17, 2026: a stale Export video Step hid its only render).
161
+ if(options.includeBlockOutputs!==false && node.type==='programmableBlockNode')addFinishedRenders(d,label);
156
162
  if(options.includeBlockOutputs!==false && node.type==='programmableBlockNode' && !d.blockStale && !d.blockLastRun?.segmentRunId){
157
163
  // Batch/single-run sources are captured by the host. Output refs are guest
158
164
  // values, so their scene/shot claims must never widen context access.
@@ -170,6 +176,7 @@ export function projectContextSnapshot(data={},options={}) {
170
176
  if(options.includeBlockOutputs!==false)for(const entry of list(canvas.workflowArchive)){
171
177
  const node=entry.node,d=object(node?.data);
172
178
  if(node?.type!=='programmableBlockNode')continue;
179
+ addFinishedRenders(d,entry.step?.label || d.blockPackage?.manifest?.name || node.id);
173
180
  const results=[{outputs:d.blockOutputs,source:d.blockLastRun?.batchSource || d.blockLastRun?.source,at:d.blockLastRun?.at},...list(d.blockBatchResults),...list(d.blockSegmentResults),...list(d.blockUnusedResults).map(result=>({...result,active:false}))];
174
181
  for(const result of results){
175
182
  const owner=result.source ? ownership(result.source) : d;
@@ -7,7 +7,9 @@ export function projectInputSampleValues(type,samples={}) {
7
7
  assert(plain(samples),'PROJECT_INPUT','Use a shared sample object keyed by input name.');
8
8
  const declarations=validateProjectInputs(type),byKey=new Map(declarations.map(contract=>[contract.key,contract]));
9
9
  assert(Object.keys(samples).every(key=>byKey.has(key)),'PROJECT_INPUT','A shared sample refers to an input that is no longer declared.');
10
- const initial=seedProjectInputDefaults({},type).semanticContext;
10
+ // The author's rehearsal of the Type uses every shared default, briefs included; a real
11
+ // project never starts with a brief default (project-inputs.js).
12
+ const initial=seedProjectInputDefaults({},type,{briefs:true}).semanticContext;
11
13
  const context=createSemanticContext(initial);
12
14
  for(const [key,value] of Object.entries(samples)) {
13
15
  const contract=byKey.get(key);
@@ -1,6 +1,7 @@
1
1
  import {assert,plain,safeDocument,stableStringify} from '../block-sdk/contracts.js';
2
2
  import {input,semanticCompatibility} from '../block-sdk/semantic-contracts.js';
3
3
  import {createSemanticContext} from './semantic-context.js';
4
+ import {isBriefPort} from './brief-ports.js';
4
5
 
5
6
  const fields=new Set(['key','type','semantic','required','description','accepts','schema','sources','default','sensitivity','freshnessPolicy','cardinality','batchable','min','max']);
6
7
 
@@ -45,8 +46,16 @@ export function validateProjectInputs(type) {
45
46
  });
46
47
  }
47
48
 
48
- export function seedProjectInputDefaults(canvas,type,{now=Date.now()}={}) {
49
- const declarations=validateProjectInputs(type).filter(contract=>Object.hasOwn(contract,'default'));
49
+ // A shared input that is a brief (brief-ports.js) never starts a project with the Type's
50
+ // default: a brief is the facts about what this project makes, which its own Chat step
51
+ // gathers, and a default was written while the Type was being built (October 16, 2026).
52
+ // briefs:true is only for the author's own rehearsal of the Type (type-preview.js samples).
53
+ // A shared input whose meaning is a brief (project_brief, production_brief, or a
54
+ // Block's own kind of brief such as "noman.vehicle_brief") is one too: its default
55
+ // resolves into every Step brief of that meaning (October 17, 2026).
56
+ export const briefLikeInput=contract=>isBriefPort(contract.key,{...contract,label:contract.description})||/\bbrief\b/i.test(String(contract.semantic||'').replace(/[_\-.]+/g,' '));
57
+ export function seedProjectInputDefaults(canvas,type,{now=Date.now(),briefs=false}={}) {
58
+ const declarations=validateProjectInputs(type).filter(contract=>Object.hasOwn(contract,'default')&&(briefs||!briefLikeInput(contract)));
50
59
  if(!declarations.length)return canvas;
51
60
  assert(Number.isFinite(now) && now>=0,'PROJECT_INPUT','Use a valid project start time.');
52
61
  assert(canvas.semanticContext===undefined || canvas.semanticContext?.schemaVersion===1 && Array.isArray(canvas.semanticContext.values),'PROJECT_CONTEXT','Use a supported project context.');