hyperframes 0.8.37 → 0.8.39

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 (30) hide show
  1. package/dist/cli.js +136 -148
  2. package/dist/docs/examples.md +1 -1
  3. package/dist/fontLocalizeCli.js +18 -95
  4. package/dist/hyperframes-player.global.js +1 -1
  5. package/dist/skills/hyperframes/SKILL.md +1 -1
  6. package/dist/skills/hyperframes/references/brief-contract.md +108 -0
  7. package/dist/skills/hyperframes/references/brief-format.md +72 -0
  8. package/dist/skills/hyperframes/references/capability-menu.md +1 -1
  9. package/dist/skills/hyperframes/references/frame-worker-core.md +80 -0
  10. package/dist/skills/hyperframes/references/intent-interview.md +8 -8
  11. package/dist/skills/hyperframes/references/production-loop.md +26 -0
  12. package/dist/skills/hyperframes/references/review-loop.md +41 -0
  13. package/dist/skills/hyperframes/references/route-briefs.md +1 -1
  14. package/dist/skills/hyperframes/references/script-format.md +52 -0
  15. package/dist/skills/hyperframes/references/storyboard-format.md +130 -0
  16. package/dist/skills/hyperframes/references/subagent-dispatch.md +35 -0
  17. package/dist/skills/hyperframes/scripts/lib/frame-packets-core.mjs +212 -0
  18. package/dist/skills/hyperframes-cli/SKILL.md +9 -8
  19. package/dist/skills/hyperframes-cli/references/init-and-scaffold.md +6 -6
  20. package/dist/skills/hyperframes-cli/references/preview-render.md +6 -3
  21. package/dist/studio/assets/{hyperframes-player-DrGk-lf5.js → hyperframes-player-BBFfad14.js} +1 -1
  22. package/dist/studio/assets/{index-DMsdtB-j.js → index-3XC5KvGR.js} +4 -4
  23. package/dist/studio/assets/{index-xK1YGiPD.js → index-CtYBUtk8.js} +1 -1
  24. package/dist/studio/assets/{index-BtGF7Kl-.js → index-De_8OqJf.js} +1 -1
  25. package/dist/studio/index.html +1 -1
  26. package/dist/studio/index.js +1 -1
  27. package/dist/studio/index.js.map +1 -1
  28. package/dist/templates/blank/index.html +17 -37
  29. package/dist/templates/from-file/index.html +74 -0
  30. package/package.json +1 -1
@@ -68687,12 +68687,24 @@ function parseHtmlStructure(source) {
68687
68687
  const blocks = { script: [], style: [] };
68688
68688
  const openTagsByName = /* @__PURE__ */ new Map();
68689
68689
  const openBlocks = [];
68690
+ let explicitOpenTag = null;
68690
68691
  const parser = new Parser(
68691
68692
  {
68692
- onopentag(name) {
68693
- const index = parser.startIndex;
68693
+ onopentagname(name) {
68694
+ let tokenStart = parser.endIndex - 1;
68695
+ while (tokenStart >= parser.startIndex && !/[\t\n\f\r />]/.test(source.charAt(tokenStart))) {
68696
+ tokenStart -= 1;
68697
+ }
68698
+ const index = source.indexOf("<", tokenStart + 1);
68699
+ explicitOpenTag = index >= 0 && index < parser.endIndex && source.slice(index + 1, parser.endIndex).toLowerCase() === name ? { index, nameEnd: parser.endIndex } : null;
68700
+ },
68701
+ onopentag(name, _attrs, isImplied) {
68702
+ const origin = !isImplied ? explicitOpenTag : null;
68703
+ const index = origin?.index ?? parser.startIndex;
68704
+ explicitOpenTag = null;
68694
68705
  const raw = source.slice(index, parser.endIndex + 1);
68695
- const attrs = raw.slice(name.length + 1, -1).replace(/\s*\/$/, "");
68706
+ const rawAttrs = origin ? source.slice(origin.nameEnd, parser.endIndex) : raw.slice(name.length + 1, -1);
68707
+ const attrs = rawAttrs.replace(/\s*\/$/, "");
68696
68708
  const tag = { raw, name, attrs, index };
68697
68709
  tags.push(tag);
68698
68710
  const sameNameStack = openTagsByName.get(name) ?? [];
@@ -69963,10 +69975,6 @@ function targetHasNoStableIdentity(selector, identity) {
69963
69975
  if (identity) return false;
69964
69976
  return selector === UNRESOLVED_TARGET || selector === "dwell/hold" || selector.startsWith("proxy \u2192 ");
69965
69977
  }
69966
- function readRegisteredTimelineCompositionId(script) {
69967
- const match = script.match(WINDOW_TIMELINE_ASSIGN_PATTERN);
69968
- return match?.[1] || match?.[2] || null;
69969
- }
69970
69978
  function unwrapRaw(value) {
69971
69979
  if (typeof value === "number") return value;
69972
69980
  if (typeof value !== "string") return void 0;
@@ -70133,21 +70141,6 @@ function makesOverlayVisible(win) {
70133
70141
  if (win.method === "from" && isHiddenGsapState(win.propertyValues)) return true;
70134
70142
  return isVisibleGsapState(win.propertyValues);
70135
70143
  }
70136
- function isSceneBoundaryExit(win) {
70137
- if (win.end <= win.position) return false;
70138
- if (win.method !== "to" && win.method !== "fromTo") return false;
70139
- return isHiddenGsapState(win.propertyValues);
70140
- }
70141
- function isHardKillSet(win, selector, boundary) {
70142
- return win.method === "set" && win.targetSelector === selector && Math.abs(win.position - boundary) <= SCENE_BOUNDARY_EPSILON_SECONDS && isHiddenGsapState(win.propertyValues);
70143
- }
70144
- function hiddenStateLiteral(values) {
70145
- if (zeroValue(values.autoAlpha)) return "{ autoAlpha: 0 }";
70146
- if (zeroValue(values.opacity)) return "{ opacity: 0 }";
70147
- if (stringValue(values.visibility)?.toLowerCase() === "hidden") return '{ visibility: "hidden" }';
70148
- if (stringValue(values.display)?.toLowerCase() === "none") return '{ display: "none" }';
70149
- return "{ opacity: 0 }";
70150
- }
70151
70144
  function findTagEnd2(source, tag) {
70152
70145
  const escapedTagName = tag.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
70153
70146
  const pattern = new RegExp(`<\\/?${escapedTagName}\\b[^>]*>`, "gi");
@@ -70164,56 +70157,11 @@ function findTagEnd2(source, tag) {
70164
70157
  }
70165
70158
  return source.length;
70166
70159
  }
70167
- function collectCompositionRanges(source, tags) {
70168
- return tags.map((tag) => {
70169
- const id = readDecodedAttr(tag.raw, "data-composition-id");
70170
- if (!id) return null;
70171
- return {
70172
- id,
70173
- start: tag.index,
70174
- end: findTagEnd2(source, tag)
70175
- };
70176
- }).filter((range) => range !== null);
70177
- }
70178
- function findContainingCompositionId(tag, ranges) {
70179
- let match = null;
70180
- for (const range of ranges) {
70181
- if (tag.index < range.start || tag.index >= range.end) continue;
70182
- if (!match || range.start >= match.start) match = range;
70183
- }
70184
- return match?.id || null;
70185
- }
70186
70160
  function getClipTagClasses(tag) {
70187
70161
  const classAttr = readAttr(tag.raw, "class") || "";
70188
70162
  const classes = classAttr.split(/\s+/).filter(Boolean);
70189
70163
  return classes.includes("clip") ? { classAttr, classes } : null;
70190
70164
  }
70191
- function collectClipStartBoundariesByComposition(source, tags) {
70192
- const ranges = collectCompositionRanges(source, tags);
70193
- const boundaries = /* @__PURE__ */ new Map();
70194
- for (const tag of tags) {
70195
- if (!getClipTagClasses(tag)) continue;
70196
- const compositionId = findContainingCompositionId(tag, ranges);
70197
- if (!compositionId) continue;
70198
- const start = numberValue(readAttr(tag.raw, "data-start") ?? void 0);
70199
- if (start == null || start <= 0) continue;
70200
- const compositionBoundaries = boundaries.get(compositionId) ?? /* @__PURE__ */ new Set();
70201
- compositionBoundaries.add(start);
70202
- boundaries.set(compositionId, compositionBoundaries);
70203
- }
70204
- return new Map(
70205
- [...boundaries.entries()].map(([compositionId, values]) => [
70206
- compositionId,
70207
- [...values].sort((a, b) => a - b)
70208
- ])
70209
- );
70210
- }
70211
- function findMatchingSceneBoundary(time, boundaries) {
70212
- for (const boundary of boundaries) {
70213
- if (Math.abs(time - boundary) <= SCENE_BOUNDARY_EPSILON_SECONDS) return boundary;
70214
- }
70215
- return null;
70216
- }
70217
70165
  function readStyleProperty(style, property) {
70218
70166
  const escapedProperty = property.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
70219
70167
  const match = style.match(new RegExp(`(?:^|;)\\s*${escapedProperty}\\s*:\\s*([^;]+)`, "i"));
@@ -72548,7 +72496,7 @@ ${allInlineStyles}`.replace(/\/\*[\s\S]*?\*\//g, "");
72548
72496
  },
72549
72497
  // overlapping_gsap_tweens + gsap_animates_clip_element
72550
72498
  // fallow-ignore-next-line complexity
72551
- async ({ source, tags, scripts, styles, rootCompositionId }) => {
72499
+ async ({ tags, scripts, styles }) => {
72552
72500
  const findings = [];
72553
72501
  const authoredHiddenSelectors = new Set(
72554
72502
  scripts.flatMap((script) => [...extractStandaloneHiddenSelectors(script.content)])
@@ -72569,13 +72517,10 @@ ${allInlineStyles}`.replace(/\/\*[\s\S]*?\*\//g, "");
72569
72517
  if (cls !== "clip") clipClasses.set(`.${cls}`, info);
72570
72518
  }
72571
72519
  }
72572
- const clipStartBoundariesByComposition = collectClipStartBoundariesByComposition(source, tags);
72573
72520
  const styleRules = collectSimpleStyleRules(styles);
72574
72521
  const reportedVisibleOverlayKeys = /* @__PURE__ */ new Set();
72575
72522
  for (const script of scripts) {
72576
- const localTimelineCompId = readRegisteredTimelineCompositionId(script.content);
72577
72523
  const gsapWindows = await cachedExtractGsapWindows(script.content);
72578
- const clipStartBoundaries = clipStartBoundariesByComposition.get(localTimelineCompId || rootCompositionId || "") ?? [];
72579
72524
  for (let i = 0; i < gsapWindows.length; i++) {
72580
72525
  const left = gsapWindows[i];
72581
72526
  if (!left) continue;
@@ -72637,28 +72582,6 @@ ${right.raw}`)
72637
72582
  snippet: truncateSnippet(fromToWindows.map((win) => win.raw).join("\n"))
72638
72583
  });
72639
72584
  }
72640
- if (clipStartBoundaries.length > 0) {
72641
- for (const win of gsapWindows) {
72642
- if (win.targetSelector === UNRESOLVED_TARGET) continue;
72643
- if (!isSceneBoundaryExit(win)) continue;
72644
- const boundary = findMatchingSceneBoundary(win.end, clipStartBoundaries);
72645
- if (boundary == null) continue;
72646
- const hasHardKill = gsapWindows.some(
72647
- (candidate) => isHardKillSet(candidate, win.targetSelector, boundary)
72648
- );
72649
- if (hasHardKill) continue;
72650
- const exitClipInfo = clipIds.get(win.targetSelector) || clipClasses.get(win.targetSelector);
72651
- const fixHint = exitClipInfo ? `"${win.targetSelector}" is a clip element \u2014 the framework already manages its visibility. Wrap the scene's content in an inner non-clip <div>, move the exit tween and the hard kill (\`tl.set("<inner-selector>", ${hiddenStateLiteral(win.propertyValues)}, ${boundary.toFixed(2)})\`) onto that wrapper instead.` : `Add \`tl.set("${win.targetSelector}", ${hiddenStateLiteral(win.propertyValues)}, ${boundary.toFixed(2)})\` after the exit tween.`;
72652
- findings.push({
72653
- code: "gsap_exit_missing_hard_kill",
72654
- severity: "error",
72655
- message: `GSAP exit on "${win.targetSelector}" ends at the ${boundary.toFixed(2)}s clip start boundary without a matching tl.set hard kill. Non-linear seeking can land after the fade and leave stale visibility state.`,
72656
- selector: win.targetSelector,
72657
- fixHint,
72658
- snippet: truncateSnippet(win.raw)
72659
- });
72660
- }
72661
- }
72662
72585
  for (const tag of tags) {
72663
72586
  const selectors = tagSimpleSelectors(tag);
72664
72587
  if (selectors.length === 0) continue;
@@ -99656,8 +99579,8 @@ async function runFontLocalize(io, localize) {
99656
99579
  }
99657
99580
 
99658
99581
  // src/version.ts
99659
- var VERSION = true ? "0.8.37" : "0.0.0-dev";
99660
- var PRODUCER_VERSION = true ? "0.8.37" : "0.0.0-dev";
99582
+ var VERSION = true ? "0.8.39" : "0.0.0-dev";
99583
+ var PRODUCER_VERSION = true ? "0.8.39" : "0.0.0-dev";
99661
99584
 
99662
99585
  // src/fontLocalizeCli.ts
99663
99586
  async function readStdin() {
@@ -1,4 +1,4 @@
1
- "use strict";var HyperframesPlayer=(()=>{var ee=Object.defineProperty;var et=Object.getOwnPropertyDescriptor;var tt=Object.getOwnPropertyNames;var it=Object.prototype.hasOwnProperty;var rt=(r,e)=>{for(var t in e)ee(r,t,{get:e[t],enumerable:!0})},nt=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of tt(e))!it.call(r,n)&&n!==t&&ee(r,n,{get:()=>e[n],enumerable:!(i=et(e,n))||i.enumerable});return r};var ot=r=>nt(ee({},"__esModule",{value:!0}),r);var Ht={};rt(Ht,{HyperframesPlayer:()=>K,SPEED_PRESETS:()=>re,formatSpeed:()=>O,formatTime:()=>q});function we(r){return r.hasRuntime||r.runtimeInjected?!1:!!(r.hasNestedCompositions||r.hasTimelines&&r.attempts>=5)}function P(r){return typeof r=="object"&&r!==null}function Ae(r){return P(r)&&typeof r.getDuration=="function"}function Ce(r){return P(r)&&typeof r.duration=="function"&&typeof r.time=="function"&&typeof r.seek=="function"&&typeof r.play=="function"&&typeof r.pause=="function"}var k="https://cdn.jsdelivr.net/npm/@hyperframes/core@0.8.37/dist/hyperframe.runtime.iife.js";function N(r){if(r===null)return null;let e=Number.parseInt(r,10);return Number.isFinite(e)&&e>0?e:null}function at(r){let e=r?.querySelector("[data-composition-id][data-width][data-height]")??r?.querySelector("[data-width][data-height]");if(!e)return null;let t=N(e.getAttribute("data-width")),i=N(e.getAttribute("data-height"));return t!==null&&i!==null?{width:t,height:i}:null}var W=class{constructor(e,t){this._iframe=e;this._callbacks=t}_iframe;_callbacks;_interval=null;_runtimeInjected=!1;get runtimeInjected(){return this._runtimeInjected}start(){this.stop(),this._runtimeInjected=!1;let e=0;this._interval=setInterval(()=>{e++;try{let t=this._iframe.contentWindow;if(!t)return;let i=!!(t.__hf||t.__player),n=!!(t.__timelines&&Object.keys(t.__timelines).length>0),o=!!this._iframe.contentDocument?.querySelector("[data-composition-src]");if(we({hasRuntime:i,hasTimelines:n,hasNestedCompositions:o,runtimeInjected:this._runtimeInjected,attempts:e})){this._injectRuntime();return}if(this._runtimeInjected&&!i)return;let d=this._resolvePlaybackDurationAdapter(t);if(d&&d.getDuration()>0){this.stop();let s=at(this._iframe.contentDocument);this._callbacks.onReady({duration:d.getDuration(),adapter:d,compositionSize:s});return}}catch{}e>=40&&(this.stop(),this._callbacks.onError("Composition timeline not found after 8s"))},200)}stop(){this._interval!==null&&(clearInterval(this._interval),this._interval=null)}resolveDirectTimelineAdapter(){try{let e=this._iframe.contentWindow;return e?this._resolveDirectTimelineAdapterFromWindow(e):null}catch{return null}}resolveDirectTimelineAdapterFromWindow(e){return this._resolveDirectTimelineAdapterFromWindow(e)}hasRuntimeBridge(e){return Reflect.get(e,"__hf")!==void 0||P(Reflect.get(e,"__player"))}_injectRuntime(){this._runtimeInjected=!0;try{let e=this._iframe.contentDocument;if(!e)return;let t=e.createElement("script");t.src=k,(e.head||e.documentElement).appendChild(t),this._callbacks.onRuntimeInjected?.()}catch{}}_resolveDirectTimelineAdapterFromWindow(e){if(this.hasRuntimeBridge(e))return null;let t=Reflect.get(e,"__timelines");if(!P(t))return null;let i=Object.keys(t);if(i.length===0)return null;let n=this._iframe.contentDocument?.querySelector("[data-composition-id]")?.getAttribute("data-composition-id"),o=n&&n in t?n:i[i.length-1],d=t[o];return Ce(d)?d:null}_resolvePlaybackDurationAdapter(e){let t=Reflect.get(e,"__player");if(Ae(t))return{kind:"runtime",getDuration:()=>t.getDuration()};let i=this._resolveDirectTimelineAdapterFromWindow(e);return i?{kind:"direct-timeline",timeline:i,getDuration:()=>i.duration()}:null}};var Re=`
1
+ "use strict";var HyperframesPlayer=(()=>{var ee=Object.defineProperty;var et=Object.getOwnPropertyDescriptor;var tt=Object.getOwnPropertyNames;var it=Object.prototype.hasOwnProperty;var rt=(r,e)=>{for(var t in e)ee(r,t,{get:e[t],enumerable:!0})},nt=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of tt(e))!it.call(r,n)&&n!==t&&ee(r,n,{get:()=>e[n],enumerable:!(i=et(e,n))||i.enumerable});return r};var ot=r=>nt(ee({},"__esModule",{value:!0}),r);var Ht={};rt(Ht,{HyperframesPlayer:()=>K,SPEED_PRESETS:()=>re,formatSpeed:()=>O,formatTime:()=>q});function we(r){return r.hasRuntime||r.runtimeInjected?!1:!!(r.hasNestedCompositions||r.hasTimelines&&r.attempts>=5)}function P(r){return typeof r=="object"&&r!==null}function Ae(r){return P(r)&&typeof r.getDuration=="function"}function Ce(r){return P(r)&&typeof r.duration=="function"&&typeof r.time=="function"&&typeof r.seek=="function"&&typeof r.play=="function"&&typeof r.pause=="function"}var k="https://cdn.jsdelivr.net/npm/@hyperframes/core@0.8.39/dist/hyperframe.runtime.iife.js";function N(r){if(r===null)return null;let e=Number.parseInt(r,10);return Number.isFinite(e)&&e>0?e:null}function at(r){let e=r?.querySelector("[data-composition-id][data-width][data-height]")??r?.querySelector("[data-width][data-height]");if(!e)return null;let t=N(e.getAttribute("data-width")),i=N(e.getAttribute("data-height"));return t!==null&&i!==null?{width:t,height:i}:null}var W=class{constructor(e,t){this._iframe=e;this._callbacks=t}_iframe;_callbacks;_interval=null;_runtimeInjected=!1;get runtimeInjected(){return this._runtimeInjected}start(){this.stop(),this._runtimeInjected=!1;let e=0;this._interval=setInterval(()=>{e++;try{let t=this._iframe.contentWindow;if(!t)return;let i=!!(t.__hf||t.__player),n=!!(t.__timelines&&Object.keys(t.__timelines).length>0),o=!!this._iframe.contentDocument?.querySelector("[data-composition-src]");if(we({hasRuntime:i,hasTimelines:n,hasNestedCompositions:o,runtimeInjected:this._runtimeInjected,attempts:e})){this._injectRuntime();return}if(this._runtimeInjected&&!i)return;let d=this._resolvePlaybackDurationAdapter(t);if(d&&d.getDuration()>0){this.stop();let s=at(this._iframe.contentDocument);this._callbacks.onReady({duration:d.getDuration(),adapter:d,compositionSize:s});return}}catch{}e>=40&&(this.stop(),this._callbacks.onError("Composition timeline not found after 8s"))},200)}stop(){this._interval!==null&&(clearInterval(this._interval),this._interval=null)}resolveDirectTimelineAdapter(){try{let e=this._iframe.contentWindow;return e?this._resolveDirectTimelineAdapterFromWindow(e):null}catch{return null}}resolveDirectTimelineAdapterFromWindow(e){return this._resolveDirectTimelineAdapterFromWindow(e)}hasRuntimeBridge(e){return Reflect.get(e,"__hf")!==void 0||P(Reflect.get(e,"__player"))}_injectRuntime(){this._runtimeInjected=!0;try{let e=this._iframe.contentDocument;if(!e)return;let t=e.createElement("script");t.src=k,(e.head||e.documentElement).appendChild(t),this._callbacks.onRuntimeInjected?.()}catch{}}_resolveDirectTimelineAdapterFromWindow(e){if(this.hasRuntimeBridge(e))return null;let t=Reflect.get(e,"__timelines");if(!P(t))return null;let i=Object.keys(t);if(i.length===0)return null;let n=this._iframe.contentDocument?.querySelector("[data-composition-id]")?.getAttribute("data-composition-id"),o=n&&n in t?n:i[i.length-1],d=t[o];return Ce(d)?d:null}_resolvePlaybackDurationAdapter(e){let t=Reflect.get(e,"__player");if(Ae(t))return{kind:"runtime",getDuration:()=>t.getDuration()};let i=this._resolveDirectTimelineAdapterFromWindow(e);return i?{kind:"direct-timeline",timeline:i,getDuration:()=>i.duration()}:null}};var Re=`
2
2
  :host {
3
3
  display: block;
4
4
  position: relative;
@@ -14,7 +14,7 @@ description: >
14
14
 
15
15
  # HyperFrames entry point
16
16
 
17
- HyperFrames **renders video from HTML** — a composition is an HTML file whose DOM declares timing with `data-*` attributes, whose animation runtime is seekable, and whose media playback is owned by the framework. The full authoring contract lives in `/hyperframes-core`; read it before writing composition HTML.
17
+ HyperFrames **renders video from HTML** — a composition is an HTML file whose DOM declares timing with `data-*` attributes, whose animation runtime is seekable, and whose media playback is owned by the framework. The full authoring contract lives in `/hyperframes-core`; read it before writing composition HTML. Brief, storyboard, review, production, dispatch, and frame-worker contracts live in this skill's `references/`.
18
18
 
19
19
  ## 1. Start from project state
20
20
 
@@ -0,0 +1,108 @@
1
+ # Brief contract
2
+
3
+ The intent layer (`/hyperframes` → `references/intent-interview.md`) asks creation questions once. The executing workflow writes the confirmed result to `BRIEF.md` and does not ask those questions again. This contract defines the canonical run-shape fields, shared brief fields, and question rules. Route-specific options live in `/hyperframes` → `references/routes/<workflow>.md`.
4
+
5
+ ## Contents
6
+
7
+ - [Run shape](#1-run-shape)
8
+ - [Shared fields](#2-shared-fields)
9
+ - [Question protocol](#3-question-protocol)
10
+
11
+ ## 1. Run shape
12
+
13
+ Three terms describe different concerns. Do not substitute one for another.
14
+
15
+ | Term | Values | Owns |
16
+ | ------------ | ------------------------------- | --------------------------------------------------------------------------------------------- |
17
+ | `flow` | `automation` or `companion` | Who drives execution. `companion` always executes in `/general-video`. |
18
+ | `storyboard` | `yes` or `no` | Whether the live board is used for plan and layout review. |
19
+ | `mode` | `collaborative` or `autonomous` | How later preference and checkpoint gates behave. The user never chooses this label directly. |
20
+
21
+ Derive `mode` once from the confirmed run shape:
22
+
23
+ | `flow` | `storyboard` | Derived `mode` |
24
+ | ------------ | ------------ | --------------- |
25
+ | `companion` | either value | `collaborative` |
26
+ | `automation` | `yes` | `collaborative` |
27
+ | `automation` | `no` | `autonomous` |
28
+
29
+ Default to `collaborative` only when a legacy project lacks enough state to derive a mode. `/motion-graphics` is autonomous by design and does not need the two run-shape questions.
30
+
31
+ ### Signals and persistence
32
+
33
+ - An ongoing signal such as “surprise me”, “decide for me”, “just build it”, or “stop asking” sets `flow: automation`, `storyboard: no`, and therefore `mode: autonomous` when it appears during intent capture.
34
+ - A bare “go” or “looks good” at a checkpoint accepts that checkpoint only. It does not change mode.
35
+ - After `STORYBOARD.md` exists, persist the derived mode in its frontmatter. On resume, an explicit `mode` in `STORYBOARD.md` overrides the derivation because it may represent a later user change.
36
+ - Mid-run “stop asking; finish it” changes only checkpoint behavior. Set `STORYBOARD.md` `mode` to `autonomous` when the file exists. Do not rewrite the already-confirmed `flow` or `storyboard` fields.
37
+ - Resume collaborative checkpoints only after an explicit signal such as “let's review together”; ordinary feedback does not change mode.
38
+
39
+ ### Gate behavior
40
+
41
+ | Gate | Collaborative | Autonomous |
42
+ | ------------------------------------------------------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------- |
43
+ | Preference: preset, voice, caption identity | Ask when the workflow marks it as required. | Decide and state the choice with a one-line reason. |
44
+ | Checkpoint: plan, sketches, pre-render review | Ask and wait. | Post the same summary, then continue. |
45
+ | Quality: fetch completeness, `lint`, `hyperframes check`, workflow verification | Run and stop on errors. | Run and stop on errors. |
46
+ | Routing ambiguity | Resolve explicitly; a wrong route changes the deliverable. | Same requirement. |
47
+ | Sign-in or credential unavailable | Show status and wait for sign-in or explicit offline selection. | Show status and continue through an available offline provider. |
48
+
49
+ Autonomous mode never silently drops a required capability. If the selected workflow has no local, cached, or offline provider for it, surface the blocker instead of omitting the capability. A credential problem does not relax the quality gates.
50
+
51
+ Rendering remains user-gated in both modes. After checks pass, collaborative runs ask “render now, or what changes?” Autonomous runs ask “preview first, or render?” Render only after the answer.
52
+
53
+ ### Studio comments
54
+
55
+ Checkpoint feedback may arrive in chat or in `.hyperframes/frame-comments.json` (format: `storyboard-format.md`). When the user replies to a checkpoint, read that file before interpreting the chat reply. Apply only the named frame changes, delete the comments file after handling it, and re-present the affected frames. A board submission does not notify the agent, so tell the user to reply in chat after submitting comments.
56
+
57
+ Autonomous is not silent: replace absorbed questions with visible decisions and short reasons. Every autonomous visual or video delivery names the final preview or rendered artifact as applicable, reports the actual duration for a time-based deliverable, and includes a contact sheet or snapshot sheet plus relevant frame identifiers when available. For multi-scene work, use scene midpoints; for a single-scene piece, use one or more proof times. This gives the user a review surface even though intermediate checkpoints did not pause.
58
+
59
+ ## 2. Shared fields
60
+
61
+ Ask only fields used by the selected route. Route entries identify their must-have questions and deferred questions. Values inferred or derived by policy are stated in the brief, not asked.
62
+
63
+ | Field | Meaning | Policy |
64
+ | ------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
65
+ | `flow` | Who drives execution | Ask at the end of intent capture when the route supports both flows. An autonomous signal answers it. |
66
+ | `storyboard` | Whether to review on the live board | Ask before `flow` when the route supports a board. A storyboard request answers it. |
67
+ | `destination` | Where the video will play | Infer from the request. Ask only when unknown and the answer changes aspect, type scale, or composition. |
68
+ | `aspect` | Canvas size | Derive from destination: social feed → `1080x1080`; TikTok/Reels/Shorts → `1080x1920`; YouTube/website/desktop → `1920x1080`. State the derivation. |
69
+ | `length` | Target duration | Let the workflow recommend a range supported by the material; include the reason. |
70
+ | `language` | Narration and caption language | Use the user's language and state it. |
71
+ | `audience` | Who will watch | Infer when clear. Ask only when a different answer changes the story or terminology. |
72
+ | `message` | The one thing the video must communicate | Derive and echo one sentence. Do not storyboard until this is clear. |
73
+ | `angle` | Route-specific story shape | Recommend one route-defined option with a reason. |
74
+ | `narration` | `yes`, `minimal`, or `no`, plus route-specific modes | Follow the selected route. |
75
+
76
+ ### Remembered defaults
77
+
78
+ Let `<MEDIA_DIR>` be the installed `/media-use` skill directory. Let `<MEMORY_ROOT>` be the existing project root. Before scaffolding, use a deliberately nonexistent probe path with no `.media` ancestor, such as `/tmp/hyperframes-intent-memory-<run-id>`; never use the current workspace as the probe. Read merged preferences with:
79
+
80
+ ```bash
81
+ node <MEDIA_DIR>/scripts/prefs.mjs get --hyperframes <MEMORY_ROOT> --json
82
+ ```
83
+
84
+ For the pre-project probe, `<MEMORY_ROOT>` is the nonexistent probe path, so only the personal tier can contribute. If that path already exists or contains `.media`, choose another. Do not claim project provenance before the real project exists.
85
+
86
+ A remembered value becomes the recommended answer and names its source. It never overrides the current request and never skips a required question. A confirmed recipe is different: adopting the bundle may fill the fields it contains because adoption itself is the confirmation.
87
+
88
+ Record only values the user confirmed, never values merely inferred or defaulted. Recording happens after the workflow writes `BRIEF.md`; supported keys are listed in `brief-format.md`. A user who sees the recommendation and accepts it has confirmed it. Personal defaults promote only according to `/media-use` memory rules.
89
+
90
+ The first time a project records a preference, say one short line that it will be remembered for future runs. Do not re-record a remembered value merely because an autonomous build reused it; only a confirmation in the current run creates a new memory event.
91
+
92
+ ## 3. Question protocol
93
+
94
+ Follow these invariants:
95
+
96
+ 1. Ask only unanswered fields that materially affect the output.
97
+ 2. Ask one field per message and wait for its answer before asking the next field.
98
+ 3. Put the recommended option first and attach a short reason. A numbered choice list is allowed, but every option in that list must answer the same field. Option lists fit factual fields (destination, length, language), where they scaffold recall; a creative field (message, angle, tone) the request has not already shaped takes an anchored open question — a list there steers the answer instead of collecting it.
99
+ 4. Skip a question when the current request already answers it. Inference alone is not an answer.
100
+ 5. Ask `storyboard` and then `flow` last, only for routes that support them.
101
+ 6. Announce deferred questions before hand-off; do not surprise the user later.
102
+ 7. When an autonomous signal appears, ask no remaining preference or checkpoint questions. State the completed brief and the reasons for decisions, then build.
103
+ 8. Use native question UI when available. Otherwise send one plain-text question with one numbered option list; never place several fields in the same list.
104
+ 9. Before the hand-off summary, run one integration check: look for a consequence the combined answers create that no single answer showed, and surface it with a proposed adjustment.
105
+ 10. The hand-off summary separates fields the user stated from fields that were inferred or defaulted, with receipts on both.
106
+ 11. Revision is not confirmation: after any correction to the summary, present the updated summary and confirm before executing.
107
+
108
+ At a checkpoint, “go” accepts that checkpoint's displayed recommendation. If a message explicitly presents a complete brief and says that “go” will accept every displayed default, then “go” may confirm that whole displayed brief; do not assume broader acceptance without that sentence.
@@ -0,0 +1,72 @@
1
+ # Brief format — `BRIEF.md`
2
+
3
+ Defines the **intent document** — the file a confirmed brief becomes. The questions that fill it live in the intent layer (`/hyperframes` → `references/intent-interview.md` + its `references/routes/<workflow>.md`); the field semantics live in `brief-contract.md` § 2. This file defines only the artifact: its shape, its home, and its lifecycle.
4
+
5
+ `BRIEF.md` sits at the project root, and the project's files read as four layers: **`BRIEF.md`** (why, for whom, and everything the user asked for) → **`STORYBOARD.md`** (what, frame by frame) → **`frame.md`** (how it looks) → **`compositions/`** (the thing itself).
6
+
7
+ ## Frontmatter — the confirmed fields
8
+
9
+ YAML block at the top: one key per deterministic field — the run's shape first, then the registry fields (`brief-contract.md` § 2) used by the route. Store canonical normalized values. Some values come directly from the user; others, such as `workflow`, `aspect`, and `language`, are routed, derived, or normalized and must use the vocabulary defined by the contract. Preserve the user's own wording in the body when it matters.
10
+
11
+ | Key | Meaning | Example |
12
+ | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
13
+ | `workflow` | the executing workflow (companion runs record `general-video`) | `faceless-explainer` |
14
+ | `flow` | `automation` — the matched workflow's pipeline · `companion` — co-creation in `/general-video` | `automation` |
15
+ | `storyboard` | `yes` — plan, sketches, and build reviewed on the live board (`review-loop.md`) · `no` — one shot from the confirmed brief | `yes` |
16
+ | `message` | the ONE thing the video must communicate | `"Ship it in an afternoon"` |
17
+ | `destination` / `aspect` / `language` / `audience` / `length` / `angle` … | the registry fields this route confirmed | — |
18
+
19
+ **Which keys are memory.** Only the preference-backed subset — `destination`, `aspect`, `language`, `flow`, `storyboard`, `voice`, `style_preset` — is recorded with `media-use` → `scripts/prefs.mjs record` (the store rejects any other key). `style_preset` is stored per workflow: record it with `--workflow <w>` (the store refuses it bare — a look confirmed for one genre is not a default for the others). `message`, `audience`, `length`, `angle` live in the frontmatter only: they describe this video, not the user.
20
+
21
+ ## Body — the intent in prose
22
+
23
+ Four sections, each optional — write what the intent layer actually learned, omit what it didn't:
24
+
25
+ - `## Intent` — a short paragraph: what the video is, for whom, why now; tone and feel in the user's own words.
26
+ - `## Assets` — the user's own material, one line each: `path — what it is, where it belongs`. Files named here are staged by the workflow, never re-discovered.
27
+ - `## Customizations` — capabilities the user opted into from the menu (`/hyperframes` → `references/capability-menu.md`) and any bespoke asks ("count-up on the revenue stat", "capture the pricing page too"), each with enough detail to act on.
28
+ - `## Notes` — everything true that fits no field: constraints, references, things to avoid.
29
+
30
+ Body prose is **project-local** — nothing in it enters cross-project memory. (Frozen recipes carry a blanked skeleton of it; a future prose-memory layer would extract from here, under its own approval rules.)
31
+
32
+ ## Lifecycle
33
+
34
+ - **Created once, by the workflow's Setup, as its first action after `hyperframes init`** — never before (`init` refuses a non-empty directory). The intent layer confirms the answers pre-project; Setup makes them durable, then records the preference-backed fields. Later confirmed changes update this same file.
35
+ - **It is the no-repeat token.** A workflow that finds `BRIEF.md` reads it and asks no brief question. Its `workflow:` names the executor — a workflow that finds another's name there is in the wrong room: load that skill and hand over, don't re-route through the intent layer. No `BRIEF.md` but the project exists (`hyperframes.json` / `STORYBOARD.md` on disk) → a pre-BRIEF project: resume from the storyboard's frontmatter and the recorded preferences, optionally backfilling `BRIEF.md` from what they already say — never re-interrogate a half-built project.
36
+ - **It stays the run's truth.** A mid-run decision updates it as it happens: an explicit change to a frontmatter field ("make it 9:16 after all") rewrites the field and re-records the preference — a changed mind is a confirmed answer; an accepted capability, adopted material, or bespoke ask lands as one line in the matching body section. Resume reads this file, so write-back is what makes a dead session resumable — a decision that lives only in chat is a decision resume never sees.
37
+ - **Execution mode derives; the storyboard's copy wins on resume.** `flow` × `storyboard` derive collaborative/autonomous checkpoint behavior (`brief-contract.md` § 1). Persist the derived mode in `STORYBOARD.md` frontmatter when that file exists. A mid-run mode-only switch updates `STORYBOARD.md`, not the already-confirmed `flow` or `storyboard`; an explicit change to either run-shape field still updates `BRIEF.md`.
38
+ - **`message` / `audience` live here first.** `STORYBOARD.md` frontmatter keeps its copies — the board and the parser read them — but when the two disagree, `BRIEF.md` holds what the user confirmed.
39
+ - **Recipes carry its skeleton.** Freezing a recipe (`review-loop.md` § 4) captures `brief-skeleton.md` — frontmatter structure kept, run-shape and content values blanked — so the next run starts pre-filled yet still confirms its own two run-shape answers.
40
+
41
+ ## Example
42
+
43
+ ```markdown
44
+ ---
45
+ workflow: faceless-explainer
46
+ flow: automation
47
+ storyboard: yes
48
+ message: "Compound interest is a snowball, not a ladder"
49
+ destination: x-feed
50
+ aspect: 1080x1080
51
+ language: en
52
+ length: 60s
53
+ angle: concept
54
+ ---
55
+
56
+ ## Intent
57
+
58
+ Teach retail investors why starting early beats contributing more. Confident,
59
+ a little playful — closer to a bar-napkin sketch than a lecture.
60
+
61
+ ## Assets
62
+
63
+ - public/growth-curve.png — the real 30-year S&P chart; the proof beat builds on it.
64
+
65
+ ## Customizations
66
+
67
+ - Count-up on the final dollar figure.
68
+
69
+ ## Notes
70
+
71
+ - No stock-photo aesthetics; keep it typographic.
72
+ ```
@@ -44,4 +44,4 @@ The narrative workflows carry genre-tuned design references — `story-design.md
44
44
  | a topic / mechanism / concept explainer | `/faceless-explainer` → `references/` |
45
45
  | a code-change walkthrough | `/pr-to-video` → `references/` (+ `code-vocabulary.md` for code frames) |
46
46
 
47
- `motion-language.md` and `cut-catalog.md` are near-identical across the three — take them from the genre you already resembled, or `/faceless-explainer`'s as the neutral default. **Borrow the shape and the taste, never the machinery**: their scripts and directory rules belong to their pipelines; the generic back half of any build lives in `hyperframes-core/references/production-loop.md`.
47
+ `motion-language.md` and `cut-catalog.md` are near-identical across the three — take them from the genre you already resembled, or `/faceless-explainer`'s as the neutral default. **Borrow the shape and the taste, never the machinery**: their scripts and directory rules belong to their pipelines; the generic back half of any build lives in `hyperframes/references/production-loop.md`.
@@ -0,0 +1,80 @@
1
+ # Frame worker — core contract (shared by the narrative video workflows)
2
+
3
+ The workflow-agnostic half of every frame worker's role. Each workflow's packet builder (`scripts/frame-packets.mjs`) prepends this file to that workflow's `sub-agents/frame-worker.md` (the **delta**) to form `.hyperframes/frame-packets/_role.md` — a worker reads the two as one role. Editing guidance: a rule that applies to any frame worker belongs here, once; a workflow-specific rule belongs in that workflow's delta. (`music-to-video` has its own composition model and does not use this contract.)
4
+
5
+ You build the frame composition file(s) assigned in your dispatch and nothing else — sibling workers build the other frames. The structural law behind the constraints and self-check below (sub-composition shape, timeline registration, clip attrs, transform-only motion, determinism, root sizing) lives in `hyperframes-core` (`references/sub-compositions.md`, `references/determinism-rules.md`, `references/data-attributes.md`); everything you must enforce is restated below — open one of those only when a rule here is unclear. This role + your packet also **supersede the skill catalog's own imperatives**: do not open `hyperframes/SKILL.md` or `hyperframes-core/SKILL.md` ("read this first" is for fresh requests — that routing already happened upstream, and its output is this dispatch).
6
+
7
+ **INPUT** — your dispatch provides this role, your frame packet(s), and:
8
+
9
+ - `PROJECT_DIR` — the project root; all paths are relative to it.
10
+ - `frame_id` — e.g. `03-feature`. Use it **verbatim** as the composition id, the `window.__timelines` key, and the file name (`compositions/frames/03-feature.html`) — that path **is** the frame's `src` in `STORYBOARD.md` (the orchestrator derived `frame_id` from it), so writing there is how the assembler finds your frame.
11
+ - Your **packet** (`.hyperframes/frame-packets/<frame_id>.md`) — everything selected upstream for this frame. You never open the shared `STORYBOARD.md` (see below); the packet carries your exact **`## Frame N` block**:
12
+ - `scene` — a one-line contact-sheet caption. **Design intent, never visible DOM text.**
13
+ - `voiceover` — the narration line. **Timing reference only** (sync entrances to the voice); **never** rendered as text — captions are a separate root track (see constraints).
14
+ - `duration` — your render length in seconds. **Fixed upstream; never change it or tween to fill a different length.**
15
+ - `transition_in` — informational. The injector stamps it at the root; **you do not author transitions.**
16
+ - the **time-coded shot sequence** — your build spec. A sequence of Scene lines (`Scene 1 (0.0–Xs): … → Scene 2: … → Scene N`), each stating what's on screen, what enters / moves / reveals, and the layout inline. Build it faithfully, beat for beat — every Scene window is a phase you must realize, and each reveal lands on its `voiceover` cue (this is what keeps the shot from freezing).
17
+ - `blueprint:` — an id (or the literal `compose`): the shot template this frame instantiates — the overall shape + its signature move. Its body is inlined in your packet (`## Selected blueprint`); `compose` means there's no template — sequence the shot from the Scene lines directly.
18
+ - `focal:` / `roles:` — which element is the hero and what each element is. **Semantics are workflow-specific — see the delta.**
19
+ - `sfx:` — the orchestrator's; you mount no audio.
20
+ - The packet also inlines the **rule recipe** (`## Selected motion rule: <id>`) for each named motion the Scene lines cite — the mechanics for that motion, which you reproduce, **never name-guess** (a guess loses the signature move). If a cited motion's recipe is missing from your packet, read `RULES_DIR/<id>.md` (`RULES_DIR` is in the packet header); a few recipes link an optional runnable demo in the shared `../hyperframes-animation/examples/<id>.html` — open it only when a recipe is unclear.
21
+ - `frame.md` (project root) — the **design-truth**: palette, type ramp, components, composition rules. The LOOK. Pull every visual token from here. This is the one file you read outside your packet.
22
+ - `../references/cut-catalog.md` (the workflow's own copy) — the **cut catalog** (zoom-through / inverse / cut-the-curve / waterfall). When a Scene seam is a within-scene swap, a scene-to-scene cut, or a text-to-text line change, build it INSIDE your composition per this catalog (Z-scale + blur + opacity, or per-word x-staggers). You never author the between-frame transition — story's `transition_in` + the injector own that.
23
+ - Canvas `<width>×<height>` and `Captions: <enabled | disabled>` (+ the keep-out cutoff when enabled).
24
+
25
+ **Retry** — if your context carries `lint` / `check` feedback from a prior pass, read it first and re-author so none of those findings recur; treat each as a hard constraint.
26
+
27
+ **OUTPUT** — `compositions/frames/<frame_id>.html` for each assigned packet: exactly one bare `<template>…</template>` fragment. The first non-whitespace bytes are `<template`; the last are `</template>`. Never emit `<!doctype>`, `<html>`, `<head>`, `<body>`, or any markup outside that single template. Writing your assigned file(s) (past the self-check below) is your **terminal action** — you do not edit `STORYBOARD.md`, mint audio, assemble the index, run the CLI, or report back. The orchestrator picks up the file and marks the frame's `status`.
28
+
29
+ ## When a confirmed sketch exists
30
+
31
+ In collaborative runs the orchestrator wireframes the board first, so your target file may already exist as the frame's **user-confirmed wireframe** — your dispatch says whether it does (a file found on a retry is your own prior output, not a sketch). Read it first and **keep its composition**: the placement, hierarchy, and copy were approved — don't move or drop them. Everything else is yours to finish: the full `frame.md` treatment (the sketch is deliberately unstyled), the finished content where the sketch used stand-in blocks (what that content is — real assets, invented visuals, a code block — is the delta's call), and the motion — map each Scene onto a timeline phase, reveal each piece on its `voiceover` cue with `fromTo` entrances, adding DOM only where a phase needs it. The frame's landed state must still read as the approved wireframe, fully dressed.
32
+
33
+ ## You do NOT decide
34
+
35
+ These belong to other steps — touching them collides with a sibling or breaks an upstream contract:
36
+
37
+ - **What is SAID** — narration is locked in `SCRIPT.md` / the `voiceover` line. You only show; you never write or restate narration text.
38
+ - **Duration** — fixed from real voice timing. Build your shot to land within it; don't stretch or trim it.
39
+ - **Transitions between frames** — the injector stamps them onto the root timeline. You author the shot itself (the VO-paced reveal sequence) but **never an exit** — the root transition IS the exit; a settle / fade-out only if you are the final frame.
40
+ - **Audio** (narration / BGM / SFX) — assembled at the root by the orchestrator. **No `<audio>` element in your composition.**
41
+ - **Design tokens** — palette / fonts / components come from `frame.md`. Don't invent them, and **never lift a word, label, or wordmark out of `frame.md` as your copy** — it is a style spec, not content. Visible text comes from your frame's `scene` / narrative.
42
+ - **Which motions / assets exist** — named upstream in your block (the shot sequence's motion verbs + `blueprint:` + the delta's own vocabularies). Implement them; don't fetch or invent new ones (you have no asset-fetch tool — never fabricate an asset URL or reference a file the dispatch didn't name).
43
+ - **The shared `STORYBOARD.md`** — your packet carries your block; never open or write the file itself. N siblings edit nothing there concurrently; the orchestrator owns its state.
44
+
45
+ ## Frame constraints
46
+
47
+ Shared law for every narrative frame, each load-bearing; your workflow's delta adds its own on top:
48
+
49
+ - **Caption keep-out — all content in the top ~83%.** A karaoke caption pill owns the bottom ~17% of the canvas. Keep every element (headline, cards, code panel, diagram, stats, brand mark) above `y ≈ 0.83 × height` — compute the pixel cutoff from your canvas (e.g. `≤ 900` on a 1080-tall frame, `≤ 1600` on a 1920-tall portrait). Holds **even when `Captions: disabled`** (bottom-edge consistency across frames).
50
+ - **Fill the content area — especially portrait.** Compose the whole top-83% region; don't float one small cluster mid-frame. Anchor the hero high (~0.2–0.35 × height), flow supporting elements down with rhythm, scale the hero toward full-bleed. (Landscape's region is short, so vertical centering near 0.42 × height is fine.)
51
+ - **Visible text is short motion-graphics copy** — a hero word / stat / one-word emphasis (`"$83K"`, `"2× faster"`, `"INSTANT"`), never a sentence from the narration. The root caption track already shows the spoken words synced to voice; repeating them double-prints on screen. (The delta may name exceptions — e.g. real code inside a code block is content, not narration.)
52
+ - **Build the whole shot — reveal across the full `duration`, never front-load.** Dumping the whole canvas in the first ~25% then holding it is exactly what reads as a PowerPoint slide. Instead reveal each piece — a line, a card, a node, a stat — **as the `voiceover` reaches it** (on a silent frame, on the beat), sequencing reveals across the shot and especially the back ~50%, with the macro camera move running underneath. **Only EXITS are banned** — a non-final frame unmounts mid-frame, so an exit tween truncates and reads as a glitch (the root transition IS the exit); mid-shot reveals are free and seek-safe. The lone exception is a note marked as a deliberate hold / stillness frame: there, an entrance + a quiet settle is right (a held read beats bad motion).
53
+ - **Implement the shot sequence faithfully — every Scene is a timeline phase.** The Scene lines ARE the build: map each Scene onto a phase of the one timeline, each piece revealing as the `voiceover` reaches it. For each **named motion** in a Scene, reproduce the mechanics of its inlined rule recipe — **never name-guess**. The inlined **`blueprint:` template** gives the overall shape; keep its **signature move** recognizable, then instantiate it with this frame's content / assets / timing. `compose` → no template; sequence the shot straight from the Scene lines. Whichever, never front-load the whole sequence at `t=0` — pace the reveals to the voiceover.
54
+
55
+ ## Workflow
56
+
57
+ 1. **Read** — your packet top to bottom (your frame block, the inlined blueprint, the inlined rule recipes), then `frame.md` (the look). Internalize the self-check codes below before you write — most lethal is **template transport**: every `<style>` + `<script>` (including the gsap load) must live INSIDE `<template>`, because the runtime only clones template contents and the assembled-project `lint` / `check` gate can miss an unwired blank sub-composition.
58
+ 2. **Design** — turn the time-coded shot sequence into a timeline using `frame.md`'s components and type ramp: each Scene window becomes a phase revealed on its `voiceover` cue, each named motion built from the recipe in your packet, the blueprint's signature move kept recognizable. Find a visual idea that reinforces the beat, not a literal restyle of the words.
59
+ 3. **Author** — write the full sub-composition to `compositions/frames/<frame_id>.html` (rewrite to iterate; last write wins). `<template>`-wrapped root carrying `data-composition-id="<frame_id>"` and styled via `#root` (not a class on that element — see the self-check below), exactly one `gsap.timeline({ paused: true })` registered at `window.__timelines["<frame_id>"]`, built synchronously.
60
+ Prefix authored ids and globally reusable class names with `<frame_id>-` so sibling frames assembled from parallel workers cannot collide. Contract selectors such as `#root` and `.clip` are the only exceptions.
61
+ 4. **Self-check, then finish** — re-read your file against the checklist below and fix in place; then continue to your next assigned packet, if any. You do **not** run the CLI.
62
+
63
+ ## Self-check before finishing (you do NOT run the CLI)
64
+
65
+ You **can't** meaningfully run `hyperframes lint` / `check` here: they operate on the **assembled project** (the `index.html` graph / bundle), and your frame isn't wired in yet — so they report on _other_ files, not yours (a false green). The **orchestrator** runs them after assembly (the correct unit), and **re-dispatches you with the finding** if your frame fails (see **Retry** above). So get it right on write: re-read your file against this checklist before finishing — the codes in parens are `hyperframes lint`'s and what the orchestrator may cite back:
66
+
67
+ - `missing_template_wrapper` / `missing_composition_id` — the entire file is exactly one bare `<template>…</template>` fragment (no DOCTYPE / full document); root carries `data-composition-id="<frame_id>"`.
68
+ - **Template transport** — every `<style>` and `<script>` block, including the GSAP load, lives inside `<template>`.
69
+ - `subcomposition_root_styled_by_class` — **style the frame root via `#root`, never a class on the `data-composition-id` element**: at render a class on the root gets scoped to a descendant selector that can't match it, so the **whole scene renders unstyled** (Studio preview still looks right — trust this rule, not the preview). Descendants use plain selectors.
70
+ - **Full-bleed background on a `class="clip"` layer, never `#root`** — author a frame's full-bleed ground (color field / gradient / grid) as a dedicated full-duration `class="clip"` background element on the lowest content track, **not** as a `background` on the `#root` / `data-composition-id` element. At assembly the frame root is clip-gated to its scene window, so a background painted on the root is not a dependable full-frame ground — dark content can end up over the host `body` (black) and render invisible. The video's base ground is painted separately by the assembler from `frame.md`'s `canvas` color onto the index `#root`; your full-bleed clip rides on top of it.
71
+ - `clip_missing_data_attrs` — every `class="clip"` element has `data-start` / `data-duration` / `data-track-index`.
72
+ - `timeline_not_paused` / `timeline_not_registered` — one paused timeline, registered at `window.__timelines["<frame_id>"]`.
73
+ - `css_transition_used` + repeat / yoyo / non-deterministic logic — none present (the renderer seeks frame-by-frame).
74
+ - `gsap_css_transform_conflict` — never put a CSS `transform` (e.g. `translateY(-50%)` centering) on an element you then GSAP-animate a transform prop on (`x` / `y` / `scale` / `rotation`): GSAP overwrites the whole `transform` and silently drops the CSS centering (the element jumps). Center with `margin` / `inset` (or `top`/`left` + offset), fold the offset into the tween via `xPercent` / `yPercent`, or use `fromTo` (the rule exempts it).
75
+ - **Hero visibility** — the main subject is visible by `t <= 0.5s`; entrance tweens use `fromTo` instead of CSS-hidden starting states.
76
+ - `exit_animation_on_non_final_scene` — no exit tween unless you are the final frame.
77
+ - **No front-loading (not a slide)** — the shot's pieces reveal on their `voiceover` cues across the duration, not all fired at `t=0`; a non-still frame keeps content arriving rather than holding a full canvas from ~25%.
78
+ - **Shot-sequence fidelity** — every Scene in the time-coded sequence is realized as a phase, the blueprint's signature move (unless `compose`) is present and recognizable, and the shot reveals to the voiceover (never front-loaded at `t=0`).
79
+ - `font_family_without_font_face` — every font you name has a matching `@font-face` **inside this file**. **Only use fonts that ship as files** with the project: the families declared in `frame.md` (their `.woff2` live in `assets/fonts/` or `capture/assets/fonts/` — point the `@font-face` `src` at the real file you find there). **Never name a font that has no file**, including system CJK / Japanese / Devanagari families (`Hiragino Sans`, `Yu Gothic`, `Noto Sans CJK`, `Noto Sans Devanagari`, …): the render machine is a clean headless Chrome with none of them installed, so the text silently falls back to a generic font and the typography is wrong in the MP4. For non-Latin or multilingual visible text, either use a shipped font that covers the script, or romanize / transliterate it (e.g. `日本語` → `Japanese`); if neither is possible it is out of scope for this frame — do not invent a font name.
80
+ - **Keep-out + no-narration-text** (eyeball, no code) — nothing sits below the 83% cutoff; no narration sentence is rendered as visible text.
@@ -9,7 +9,7 @@ These reads are mandatory when their condition matches; do not replace them with
9
9
  | The route is picked, before confirming or interviewing | `routes/<workflow>.md` — the whole file (contract + interview, ~1KB) |
10
10
  | Triage judged the request unformed, before any concept work | `pitch-round.md` |
11
11
  | Offering optional capabilities or collecting supplied media | The route-filtered rows in `capability-menu.md` |
12
- | Question rules or field semantics beyond the schema below | `../../hyperframes-core/references/brief-contract.md` |
12
+ | Question rules or field semantics beyond the schema below | `brief-contract.md` |
13
13
 
14
14
  ## Adapt orthogonal inputs first
15
15
 
@@ -24,22 +24,22 @@ A GitHub PR URL is not a website source. A named or adopted recipe already carri
24
24
  - **Remembered defaults.** Let `<MEDIA_DIR>` be the installed `/media-use` skill directory. For an existing project, `<MEMORY_ROOT>` is its root. Before scaffolding, use a deliberately nonexistent probe path with no `.media`, such as `/tmp/hyperframes-intent-memory-<run-id>`; never use the current workspace. Run `node <MEDIA_DIR>/scripts/prefs.mjs get --hyperframes <MEMORY_ROOT> --json`. Make each remembered value the recommended option and name its source. The pre-project probe sees only the personal tier; do not claim project provenance.
25
25
  - **Recipes.** Run `node <MEDIA_DIR>/scripts/recipe.mjs list --hyperframes <MEMORY_ROOT> --json`. If the user names a recipe, says "like last time," or a recipe matches the probable route, ask whether to adopt it before other brief questions — and make the offer earn the yes: say why it matches and what adopting saves ("this matches your launch-promo recipe — adopting fills destination, aspect, language, and the design spec; you'd confirm the message and the two run-shape questions"). When several match, list them and include "none." An adopted recipe locks the fields it contains; ask only its missing fields and the run-shape questions. It does not remove review or render approval gates.
26
26
 
27
- **2 — Triage the input.** What is the video about — a website (sold or shown), a PR, a topic, a music track, existing footage? And is the request **formed** — the message, the material, and the occasion readable from what the user gave — or **unformed**, a subject with no take on it? Source material doesn't settle this by itself: a site, document, or PR carries its own thesis, but five tellings of that thesis are five different videos — a request whose only shape comes from its source ("make a video about this URL") is formed about the facts and unformed about the telling, and enters the round to pitch the telling. A formed request runs the layer exactly as it always has; nothing below is added for it. An unformed one goes through the pitch round after routing (step 4) and earns one question here, before anything is generated: what is the user already picturing? Their answer seeds the round (`pitch-round.md`). A user who says they don't know video at all gets that reference's decision map instead of a question sequence. For a genuinely exploratory request ("we need a video but I'm not sure what kind"), don't interrogate — establish the subject and what exists to show, one question at a time, then close by **recommending** a route plus how the run will review: a text storyboard first, on a live board, with optional wireframe sketches before the full build (`../../hyperframes-core/references/review-loop.md`). The user hears the process before any workflow starts.
27
+ **2 — Triage the input.** What is the video about — a website (sold or shown), a PR, a topic, a music track, existing footage? And is the request **formed** — the message, the material, and the occasion readable from what the user gave — or **unformed**, a subject with no take on it? Source material doesn't settle this by itself: a site, document, or PR carries its own thesis, but five tellings of that thesis are five different videos — a request whose only shape comes from its source ("make a video about this URL") is formed about the facts and unformed about the telling, and enters the round to pitch the telling. A formed request runs the layer exactly as it always has; nothing below is added for it. An unformed one goes through the pitch round after routing (step 4) and earns one question here, before anything is generated: what is the user already picturing? Their answer seeds the round (`pitch-round.md`). A user who says they don't know video at all gets that reference's decision map instead of a question sequence. For a genuinely exploratory request ("we need a video but I'm not sure what kind"), don't interrogate — establish the subject and what exists to show, one question at a time, then close by **recommending** a route plus how the run will review: a text storyboard first, on a live board, with optional wireframe sketches before the full build (`review-loop.md`). The user hears the process before any workflow starts.
28
28
 
29
29
  **3 — Pick the route** (the route table and ambiguity rules in the SKILL.md), then read `routes/<workflow>.md`. Its Interview section lists the must-have questions to ask now, the **deferred asks** to announce, whether the two run-shape questions apply, and which fields the pitch round may answer.
30
30
 
31
31
  **4 — The pitch round** — unformed requests only; formed requests and recipe adoptions go straight to the must-haves. Sample five concepts along five genuinely different paths, at least two from the distribution's tail, and present them all before recommending one — pick, mix, and redirect are all answers. Each pitch names the capability or two it rides, in the plain language of `capability-menu.md` — the toolbox experienced as concepts, not listed as a menu. On an autonomous run the same gate runs internally, and the heads-up names the direction chosen and the typical one left behind. The procedure — the sampling gate, the presentation discipline, and the decision map for users new to video — is `pitch-round.md`. The chosen concept answers the route's pitch-eligible fields and lands in `BRIEF.md` under `## Intent`; the capabilities it named are confirmed with it, under `## Customizations`.
32
32
 
33
- **5 — The route's must-haves.** One question per field, recommended option first with its receipt (rules: `../../hyperframes-core/references/brief-contract.md` § 3). Skip a question only when the request already answered it — inference is not an answer, but a chosen pitch is: fields the pitch round settled are locked with the pitch as their receipt. Then announce the route's deferred asks in one line ("after I probe the clip, I'll offer 2–3 caption identities") so the user hears the run's full shape before it starts.
33
+ **5 — The route's must-haves.** One question per field, recommended option first with its receipt (rules: `brief-contract.md` § 3). Skip a question only when the request already answered it — inference is not an answer, but a chosen pitch is: fields the pitch round settled are locked with the pitch as their receipt. Then announce the route's deferred asks in one line ("after I probe the clip, I'll offer 2–3 caption identities") so the user hears the run's full shape before it starts.
34
34
 
35
35
  **6 — The two run-shape questions** — where the route's entry applies them, asked after the must-haves, each on its own:
36
36
 
37
- - **(a) Storyboard?** Review the plan, wireframe sketches, and the finished piece pass by pass on a live board (`../../hyperframes-core/references/review-loop.md`) — recommended for anything beyond a couple of scenes — or skip the board and get one finished video from the confirmed brief.
37
+ - **(a) Storyboard?** Review the plan, wireframe sketches, and the finished piece pass by pass on a live board (`review-loop.md`) — recommended for anything beyond a couple of scenes — or skip the board and get one finished video from the confirmed brief.
38
38
  - **(b) Automation or companion?** **Automation** — the matched workflow's pipeline executes the brief end to end. **Companion** — build it together in `/general-video` with every HyperFrames capability on the table; the route's answers still describe the video, general-video executes them.
39
39
 
40
40
  These two are **orthogonal — never merge them into one menu.** All four `flow` × `storyboard` combinations are valid user choices (a companion run reviews on the live board too when `storyboard: yes`); a flattened three-option list ("storyboard review / one shot / companion") silently makes companion-with-storyboard unselectable. When a diagram or source material summarizes the outcomes as three branches, that is the derived behavior (`brief-contract.md` § 1), not the question shape. In a form-style question UI, keep (a) and (b) as two separate selects.
41
41
 
42
- Signals replace questions, never add them: an ongoing "just build it" / "surprise me" / "don't ask" locks `flow: automation, storyboard: no`, and every unanswered field becomes a decision with a receipt in the heads-up. A storyboard request, however phrased, locks `storyboard: yes`. Remembered `flow` / `storyboard` values reorder the recommendations — they never make either question disappear. The run's collaborative/autonomous execution mode derives from these two answers — the old first question is never asked; the canonical mapping is `../../hyperframes-core/references/brief-contract.md` § 1.
42
+ Signals replace questions, never add them: an ongoing "just build it" / "surprise me" / "don't ask" locks `flow: automation, storyboard: no`, and every unanswered field becomes a decision with a receipt in the heads-up. A storyboard request, however phrased, locks `storyboard: yes`. Remembered `flow` / `storyboard` values reorder the recommendations — they never make either question disappear. The run's collaborative/autonomous execution mode derives from these two answers — the old first question is never asked; the canonical mapping is `brief-contract.md` § 1.
43
43
 
44
44
  **7 — Nice-to-have: recommend, then show.** Skip this step when the selected route file says to skip the front-door capability offer. Otherwise, once the must-haves are locked, send one offer, not an interrogation — recommendations first, catalog on request. Capabilities the chosen pitch already named are settled with the concept — this step recommends from what the pitch didn't cover, and after a pitch round it is often just the two open asks and the design ask:
45
45
 
@@ -50,17 +50,17 @@ Signals replace questions, never add them: an ongoing "just build it" / "surpris
50
50
 
51
51
  The full route-filtered slice appears only when the user asks what else is possible. An accepted recommendation is a confirmed answer: when it lands on a preference-backed field (a preset, a voice, a caption identity), it records like any other confirmation, and `/media-use`'s promotion rules make it the next run's recommended default. Capture answers verbatim in `BRIEF.md` under `## Assets`, `## Customizations`, or `## Notes`. One round; silence or "no" moves on.
52
52
 
53
- **8 — Hand off.** Three disciplines close the conversation (invariants: `../../hyperframes-core/references/brief-contract.md` § 3):
53
+ **8 — Hand off.** Three disciplines close the conversation (invariants: `brief-contract.md` § 3):
54
54
 
55
55
  - **One integration check.** Read the combined answers for a consequence no single answer showed — vertical at 90 seconds with a chart-dense concept means charts a phone can't read — and surface it with a proposed adjustment now, not at the sketch pass.
56
56
  - **Stated and inferred, apart.** Present the locked brief as one summary — deferred asks and the run's shape included — with what the user answered and what was inferred or defaulted as two visibly separate groups, receipts on both. The inferred group is where corrections live; an autonomous heads-up is mostly that group.
57
57
  - **Revision is not confirmation.** When the user corrects the summary, fold the change in and present it again; never execute an edited-but-unconfirmed brief.
58
58
 
59
- Then enter the workflow (`flow: companion` → `/general-video`; otherwise the matched route), installing it first per the SKILL.md's install step. The workflow's Setup writes `BRIEF.md` from this summary as its **first action after `hyperframes init`** (never before — `init` refuses a non-empty directory), using the canonical frontmatter below and preserving the user's important wording in the body — the chosen pitch, when there is one, under `## Intent`. It then records the preference-backed fields (`../../hyperframes-core/references/brief-format.md` names the subset), and asks no brief question again.
59
+ Then enter the workflow (`flow: companion` → `/general-video`; otherwise the matched route), installing it first per the SKILL.md's install step. The workflow's Setup writes `BRIEF.md` from this summary as its **first action after `hyperframes init`** (never before — `init` refuses a non-empty directory), using the canonical frontmatter below and preserving the user's important wording in the body — the chosen pitch, when there is one, under `## Intent`. It then records the preference-backed fields (`brief-format.md` names the subset), and asks no brief question again.
60
60
 
61
61
  ## BRIEF.md frontmatter — the carry-away artifact
62
62
 
63
- The interview's deliverable. Every later "what did the route require?" re-reads this ~1KB file, never this document. One key per confirmed field, canonical normalized values (full shape and body sections: `../../hyperframes-core/references/brief-format.md`):
63
+ The interview's deliverable. Every later "what did the route require?" re-reads this ~1KB file, never this document. One key per confirmed field, canonical normalized values (full shape and body sections: `brief-format.md`):
64
64
 
65
65
  | Key | Meaning | Example |
66
66
  | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | --------------------------- |