From 25678b6c7a9688781effc250acb51f92e3edec49 Mon Sep 17 00:00:00 2001 From: Holger Drewes <931137+holgerd77@users.noreply.github.com> Date: Fri, 3 Jul 2026 16:08:46 +0200 Subject: [PATCH 1/9] Short video --- .cursor/rules/video-playwright.mdc | 55 ++ .gitignore | 8 +- docs/.vitepress/config.ts | 2 + docs/contributing/video-authoring.md | 350 +++++++++ docs/contributing/video-pipeline.md | 227 ++++++ docs/guide/architecture.md | 12 + docs/public/llms.txt | 2 + package.json | 5 + src/App.vue | 26 +- src/components/CompanionSheet.vue | 21 +- .../bytecodeStepperEC/BytecodeStepperEC.vue | 65 +- .../__tests__/useBytecodeStepper.spec.ts | 25 +- .../bytecodeStepperEC/useBytecodeStepper.ts | 5 +- .../PrecompileInterfaceEC.vue | 8 +- .../__tests__/usePrecompileState.spec.ts | 44 +- .../usePrecompileState.ts | 30 +- src/eComponents/ui/ActionButtonUIC.vue | 9 +- src/eComponents/ui/ExamplesUIC.vue | 3 +- src/explorations/ExplorationC.vue | 2 +- src/explorations/eip-7594/MyC.vue | 7 +- src/explorations/eip-7594/examples.ts | 2 + src/explorations/eip-7928/MyC.vue | 6 +- src/explorations/eip-8024/MyC.vue | 10 +- .../exampleFromQuery.explorations.spec.ts | 47 ++ src/libs/__tests__/exampleFromQuery.spec.ts | 54 ++ src/libs/exampleFromQuery.ts | 30 + src/libs/useExplorationExampleQuery.ts | 9 + src/main.css | 141 ++++ src/video/VideoOverlay.vue | 45 ++ src/video/VideoShell.vue | 123 ++++ src/video/__tests__/splitText.spec.ts | 19 + src/video/__tests__/useVideoMode.spec.ts | 24 + src/video/__tests__/videoBridge.spec.ts | 110 +++ src/video/annotationTarget.ts | 42 ++ src/video/companionSheetEvents.ts | 7 + src/video/overlays/VideoAnnotation.vue | 134 ++++ src/video/overlays/VideoBandShell.vue | 20 + src/video/overlays/VideoHighlightMark.vue | 37 + src/video/overlays/VideoHighlights.vue | 23 + src/video/overlays/VideoListFlashOverlay.vue | 31 + src/video/overlays/VideoOutroCard.vue | 49 ++ src/video/overlays/VideoPunchOverlay.vue | 29 + src/video/overlays/VideoSplitOverlay.vue | 83 +++ src/video/overlays/VideoTitleCard.vue | 37 + src/video/splitText.ts | 15 + src/video/types.ts | 142 ++++ src/video/useTargetRect.ts | 52 ++ src/video/useVideoMode.ts | 15 + src/video/video.css | 686 ++++++++++++++++++ src/video/videoBridge.ts | 133 ++++ src/views/ExplorationView.vue | 9 +- src/views/__tests__/AppLayout.spec.ts | 19 + video/.gitignore | 2 + video/README.md | 102 +++ video/package-lock.json | 62 ++ video/package.json | 20 + video/projects/eip-8024/README.md | 13 + video/projects/eip-8024/content.json | 123 ++++ video/projects/eip-8024/playbook.json | 85 +++ video/projects/eip-8024/zones.json | 39 + video/src/__tests__/annotationTarget.spec.ts | 31 + video/src/__tests__/formats.spec.ts | 22 + video/src/__tests__/loadProject.spec.ts | 54 ++ video/src/__tests__/storyboard.spec.ts | 33 + video/src/__tests__/trimLeadIn.spec.ts | 80 ++ video/src/annotationTarget.ts | 16 + video/src/bootstrap-playwright-env.ts | 2 + video/src/check-cli.ts | 13 + video/src/chromium.ts | 8 + video/src/explorationRegistry.ts | 53 ++ video/src/formats.ts | 43 ++ video/src/loadProject.ts | 122 ++++ video/src/parseRecordArgs.ts | 19 + video/src/playbookRunner.ts | 426 +++++++++++ video/src/preflight-cli.ts | 36 + video/src/record-cli.ts | 68 ++ video/src/recordVideo.ts | 176 +++++ video/src/storyboard-cli.ts | 43 ++ video/src/storyboard.ts | 326 +++++++++ video/src/trimLeadIn.ts | 287 ++++++++ video/src/types.ts | 184 +++++ vitest.config.ts | 1 + 82 files changed, 5541 insertions(+), 37 deletions(-) create mode 100644 .cursor/rules/video-playwright.mdc create mode 100644 docs/contributing/video-authoring.md create mode 100644 docs/contributing/video-pipeline.md create mode 100644 src/libs/__tests__/exampleFromQuery.explorations.spec.ts create mode 100644 src/libs/__tests__/exampleFromQuery.spec.ts create mode 100644 src/libs/exampleFromQuery.ts create mode 100644 src/libs/useExplorationExampleQuery.ts create mode 100644 src/video/VideoOverlay.vue create mode 100644 src/video/VideoShell.vue create mode 100644 src/video/__tests__/splitText.spec.ts create mode 100644 src/video/__tests__/useVideoMode.spec.ts create mode 100644 src/video/__tests__/videoBridge.spec.ts create mode 100644 src/video/annotationTarget.ts create mode 100644 src/video/companionSheetEvents.ts create mode 100644 src/video/overlays/VideoAnnotation.vue create mode 100644 src/video/overlays/VideoBandShell.vue create mode 100644 src/video/overlays/VideoHighlightMark.vue create mode 100644 src/video/overlays/VideoHighlights.vue create mode 100644 src/video/overlays/VideoListFlashOverlay.vue create mode 100644 src/video/overlays/VideoOutroCard.vue create mode 100644 src/video/overlays/VideoPunchOverlay.vue create mode 100644 src/video/overlays/VideoSplitOverlay.vue create mode 100644 src/video/overlays/VideoTitleCard.vue create mode 100644 src/video/splitText.ts create mode 100644 src/video/types.ts create mode 100644 src/video/useTargetRect.ts create mode 100644 src/video/useVideoMode.ts create mode 100644 src/video/video.css create mode 100644 src/video/videoBridge.ts create mode 100644 video/.gitignore create mode 100644 video/README.md create mode 100644 video/package-lock.json create mode 100644 video/package.json create mode 100644 video/projects/eip-8024/README.md create mode 100644 video/projects/eip-8024/content.json create mode 100644 video/projects/eip-8024/playbook.json create mode 100644 video/projects/eip-8024/zones.json create mode 100644 video/src/__tests__/annotationTarget.spec.ts create mode 100644 video/src/__tests__/formats.spec.ts create mode 100644 video/src/__tests__/loadProject.spec.ts create mode 100644 video/src/__tests__/storyboard.spec.ts create mode 100644 video/src/__tests__/trimLeadIn.spec.ts create mode 100644 video/src/annotationTarget.ts create mode 100644 video/src/bootstrap-playwright-env.ts create mode 100644 video/src/check-cli.ts create mode 100644 video/src/chromium.ts create mode 100644 video/src/explorationRegistry.ts create mode 100644 video/src/formats.ts create mode 100644 video/src/loadProject.ts create mode 100644 video/src/parseRecordArgs.ts create mode 100644 video/src/playbookRunner.ts create mode 100644 video/src/preflight-cli.ts create mode 100644 video/src/record-cli.ts create mode 100644 video/src/recordVideo.ts create mode 100644 video/src/storyboard-cli.ts create mode 100644 video/src/storyboard.ts create mode 100644 video/src/trimLeadIn.ts create mode 100644 video/src/types.ts diff --git a/.cursor/rules/video-playwright.mdc b/.cursor/rules/video-playwright.mdc new file mode 100644 index 0000000..5f8eadc --- /dev/null +++ b/.cursor/rules/video-playwright.mdc @@ -0,0 +1,55 @@ +--- +description: Video recording uses the isolated video/ Playwright package — never ad-hoc installs +alwaysApply: true +--- + +# Short-form videos & Playwright + +Exploration videos (9:16 vertical) are recorded by the **isolated** `website/video/` package. +Playwright is intentionally **not** in the root `package.json` (CI stays lean). + +Chromium is **shared with the OG pipeline** — if `npm run og:check` passes, video recording +can use the same browser cache. + +## One-time human setup (Holger's machine only) + +From `feelyourprotocol/website/`: + +```bash +npm run og:setup # if not done yet — downloads Chromium (~once per machine) +npm run video:setup # npm install in video/ only +npm run video:check # should print: OK: Playwright Chromium is installed... +``` + +**Holger has already run og:setup.** Do not run setup again unless he explicitly asks. + +`video/` CLIs reuse `og/` bootstrap — redirect Cursor's sandbox `PLAYWRIGHT_BROWSERS_PATH` +to the developer's real browser cache. Use `required_permissions: ["all"]` and **never** +run `video:setup` / `playwright install` as the first fix. + +## Agent rules (mandatory) + +### NEVER do these + +- `npm run video:setup`, `npm run og:setup`, or any `playwright install` +- Ad-hoc `npx playwright install` or sandbox browser downloads +- Retry setup when record fails — that is almost never the fix + +### ALWAYS do these + +1. Run video commands with **`required_permissions: ["all"]`** +2. Use npm scripts from `website/`: + - `npm run video:preflight` + - `npm run video:record -- ` +3. Optional fast check: `npm run video:preflight` with `required_permissions: ["all"]` + - `status: ready` → proceed + - `status: needs_agent_permissions` → re-run with `required_permissions: ["all"]` + - `status: needs_human_setup` → ask user to run `npm run og:check` locally + +### When record fails + +1. Permission errors → re-run with `required_permissions: ["all"]` +2. Still fails → ask user for `npm run og:check` output; do **not** run setup +3. Selector timeout / 404 → fix app or playbook, not Playwright setup + +See `video/README.md` and `docs/contributing/video-pipeline.md`. diff --git a/.gitignore b/.gitignore index 813a59b..2749606 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,10 @@ roadmap/.vitepress/cache SERVER_NOTES.md # Dist -dist/ \ No newline at end of file +dist/ + +# Isolated Playwright packages (local node_modules + artifacts) +og/node_modules +og/.chromium-ready.json +video/node_modules +video/projects/*/output/ \ No newline at end of file diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index ca484fd..99abc55 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -101,6 +101,8 @@ export default defineConfig({ { text: 'Styling & Design', link: '/contributing/styling' }, { text: 'Code Conventions', link: '/contributing/code-conventions' }, { text: 'Third-Party Libraries', link: '/contributing/third-party-libraries' }, + { text: 'Video Pipeline', link: '/contributing/video-pipeline' }, + { text: 'Authoring a Video Short', link: '/contributing/video-authoring' }, ], }, { diff --git a/docs/contributing/video-authoring.md b/docs/contributing/video-authoring.md new file mode 100644 index 0000000..1e14391 --- /dev/null +++ b/docs/contributing/video-authoring.md @@ -0,0 +1,350 @@ +# Authoring a video short + +How to plan, write, and record a Feel Your Protocol vertical short (~45–60 s) from a +live exploration. + +Operational commands and file layout: [Video pipeline](/contributing/video-pipeline). +Reference project: `video/projects/eip-8024/`. + +--- + +## Goals + +Each short should: + +1. Show **real** exploration UI (not mockups). +2. Use the **video overlay language** (black bars, Bebas Neue / Barlow Condensed) — not site chrome. +3. Guide the viewer's eye with layered overlays without overcrowding the frame. +4. Start with a **title-card thumbnail** (frame 0 after trim). + +Rendering lives in `src/video/`; copy and timing live in `video/projects//`. +Run `npm run website:build` before recording whenever overlay components change. + +--- + +## Workflow overview + +``` +Plan story arc → content.json (overlays, annotations, highlights) + → zones.json (focus areas) + → playbook.json (beats + timing) + → video:storyboard (validate timeline) + → website:build → video:record + → verify frame 0 thumbnail +``` + +--- + +## 1. Plan the story arc + +Break the video into **beats** — one playbook step each. Each beat can include: + +| Phase | Playbook | Purpose | +|-------|----------|---------| +| Guide | `annotate` | Pointer on live UI (parallel through the beat) | +| Cue | `overlay` + `cue` | Story text the viewer reads before action | +| Reveal | `selectExample`, `step`, `scroll`, `expandCompanion` | Exploration action | +| Hold | `wait` | Pause after action so the result sinks in | + +Typical arc for an opcode demo: + +1. **Title** — hook and topic +2. **Context** — hardfork / problem statement +3. **Setup** — select example, grow state (steps) +4. **Climax** — the one opcode that matters (slow step + highlights) +5. **Extra** — companion panel, calculator, etc. (optional) +6. **Recap** — tie back to the takeaway (text-only on current state) +7. **Outro** — closing line + CTAs + +Use `npm run video:storyboard -- ` to preview timing before recording. + +--- + +## 2. Project files + +Create `video/projects//`: + +| File | Contents | +|------|----------| +| `content.json` | `overlays`, `annotations`, `highlightSets` | +| `playbook.json` | Ordered beats with timing and actions | +| `zones.json` | Focus-area selectors for `focus` and annotation targets | +| `output/` | Recorded `.webm` (gitignored) | + +Add the exploration to `video/src/explorationRegistry.ts` if not already listed. + +--- + +## 3. Story overlays (`content.json` → `overlays`) + +Story overlays carry the narrator text. + +| Type | Use | +|------|-----| +| `title-card` | Opening — full band layout (see [Title and outro cards](#title-and-outro-cards)) | +| `outro-card` | Closing + CTAs | +| `punch` | Single headline + optional subline | +| `split` | Two staggered lines | +| `list-flash` | Headline + bullet list | + +In the playbook, pair overlays with **`placement: "top-banner"`** and a **`focus`** key +so the exploration panel stays visible below the banner. + +**Pattern:** + +```json +{ + "beat": "stack-grow", + "overlay": "stack-cue", + "placement": "top-banner", + "focus": "stack", + "cue": 3000, + "step": { "count": 9, "interval": 360 }, + "wait": 1600 +} +``` + +Show overlay → `cue` ms → hide → run actions → `wait` ms. + +--- + +## 4. UI annotations (`content.json` → `annotations`) + +Annotations are black callouts with a ring on a UI target. They answer: *where should I look?* + +```json +"annotations": { + "guide-stack": { + "target": "stack", + "label": "Live stack", + "hint": "Watch depth grow", + "side": "top" + } +} +``` + +In the playbook: `"annotate": "guide-stack"`. The annotation **stays visible** while the +story overlay hides and steps run — use it whenever the banner alone does not point at +the action area. + +Targets are keys from `zones.json` `focusAreas`, or built-in aliases: + +| Target key | Resolves to | +|------------|-------------| +| `disassembly-active` | `[data-disassembly-active="true"]` | +| `stack-top` | `[data-stack-depth="1"]` | +| `stack-depth-{n}` | `[data-stack-depth="{n}"]` | + +Explorations used in videos need stable `data-testid` / `data-stack-depth` attributes on +the elements you annotate (see `BytecodeStepperEC` for the EIP-8024 pattern). + +--- + +## 5. Climax highlights (`content.json` → `highlightSets`) + +Use a third layer for the **peak moment** — neon-yellow marker strokes over exact UI +elements. No labels, no borders, no rounded corners (visually separate from site UI and +from annotation callouts). + +Define sets in `content.json`: + +```json +"highlightSets": { + "dupn-windup": { + "marks": [ + { "target": "disassembly-active", "pad": 3 }, + { "target": "stack-depth-17", "pad": 2, "tilt": 0.35 } + ] + }, + "dupn-payoff": { + "marks": [ + { "target": "disassembly-active", "pad": 3 }, + { "target": "stack-top", "pad": 2, "tilt": -0.4 } + ] + } +} +``` + +Wire timing in the playbook `step` action: + +```json +"step": { + "count": 9, + "interval": 380, + "climaxFrom": 9, + "climaxInterval": 1050, + "climaxPauseMs": 550, + "highlightSet": "dupn-windup", + "highlightAfterStep": 9, + "highlightSetAfter": "dupn-payoff", + "holdAfterClimaxMs": 1500 +} +``` + +| Field | Purpose | +|-------|---------| +| `climaxFrom` | 1-based step index to enter slow motion | +| `climaxInterval` | Step delay during climax (ms) | +| `climaxPauseMs` | Pause before the first climax step | +| `highlightSet` | Highlights shown when climax starts | +| `highlightAfterStep` | Step number after which to switch sets | +| `highlightSetAfter` | Payoff highlight set | +| `holdAfterClimaxMs` | Extra hold after payoff highlights | + +--- + +## 6. Pacing and cue times + +Allocate enough read time for overlay density: + +| Overlay density | Recommended `cue` | +|-----------------|-------------------| +| Headline + subline (`punch`, `split`) | 3000–3400 ms | +| Headline + list (`list-flash`, 3+ items) | 4000–4200 ms | +| Title / outro band cards | 4500–5000 ms | +| Text-only recap (no actions) | 4000+ ms (`wait` only) | + +Step intervals: ~360–400 ms for routine steps; ~1000 ms+ for the climax step. + +Example timings from `eip-8024`: + +| Beat | Overlay | Timing | +|------|---------|--------| +| hook | title-card | `wait` 4500 ms | +| context | glamsterdam | `cue` 3400 ms | +| opcodes | three-opcodes | `cue` 4200 ms, `wait` 1200 ms | +| stack-grow | stack-cue | `cue` 3000 ms, `wait` 1600 ms | +| dupn-hit | dupn-cue | `cue` 3000 ms + climax step | +| calculator | calculator-tease | `cue` 2800 ms, `wait` 2400 ms | +| recap | old-limit-cue | `wait` 4200 ms (no actions) | +| outro | outro-card | `wait` 5000 ms | + +Storyboard validation warns when `cue` is missing on overlay+action beats or when ids +do not resolve in `content.json` / `zones.json`. + +--- + +## 7. Title and outro cards + +Full-screen cards use **`VideoBandShell`**: + +- Middle **~3/5** — black band with hero type (title, subtitle, hook / closing, CTAs). +- Top and bottom **~1/5** — live exploration peeking through. + +| Card | Peek role | +|------|-----------| +| Title | Show that a live demo runs underneath | +| Outro | Keep exploration visible behind CTAs | + +Outro CTAs support `variant: "secondary"` (smaller, e.g. Forkcast) and `"primary"` +(larger, e.g. FYP). Use `"READ ABOUT THE EIP"` for Forkcast, not generic "READ MORE". + +CSS: `html.fyp-video-capture` locks hero font sizes; `html.fyp-video-band-active` during band cards. + +--- + +## 8. Recap and example switches + +**Recap beats** should stay on the current exploration state — overlay text only, no +`selectExample` or `step`. Point `focus` at the result the viewer just watched (e.g. stack). + +```json +{ + "beat": "recap", + "overlay": "old-limit-cue", + "annotate": "guide-stack", + "focus": "stack", + "wait": 4200 +} +``` + +Write recap copy that references what already happened ("You just copied depth 17"), not +a new demo. + +Switch examples mid-video only when the overlay **introduces** a new scenario and the +reset is intentional. Pick an example that matches the overlay message (e.g. do not use +`invalid-dupn` to explain the historical DUP depth-16 limit — that example shows a +shallow-stack revert, a different concept). + +--- + +## 9. Record and verify + +```bash +npm run website:build +npm run video:record -- --preview # 540×960, faster iteration +npm run video:record -- # 1080×1920 final +``` + +After recording: + +1. Check the trim log — frame 0 should be the title band (valid stats: dark pixel ratio + ~0.8, mean luminance ~40 in the center crop). +2. Scrub the `.webm` at **0:00** (thumbnail), the climax timestamp, and the outro. +3. Confirm overlays, annotations, and highlights appear as storyboarded. + +The recorder hides the exploration until the title card is ready, preloads display fonts, +and post-trims lead-in with ffmpeg. Requires `ffmpeg` on PATH. + +--- + +## Troubleshooting + +| If you see… | Check… | +|-------------|--------| +| Missing overlays / annotations in output | Run `npm run website:build` before record | +| Gray letterboxing | Playwright `recordVideo.size` matches viewport dimensions | +| Frame 0 shows exploration UI | Trim log; title-band detection needs black center band | +| Small fallback font on thumbnail | Font preload (`display=block`) and capture CSS | +| Top banner covers example selector | Banner max-height CSS; `focus` uses `block: 'start'` scroll | +| Climax highlights / slow motion absent | All climax fields present on `step` in playbook JSON | +| UI resets without explanation | Remove mid-video `selectExample`; use text-only recap | +| Banner overlaps during focus | `zones.json` scroll margins; `--video-top-banner-max-h` | + +--- + +## Guidelines + +- Keep **three layers separate**: story overlays, annotations, climax highlights. +- Use **marker strokes** for highlights — not labeled boxes or site purple chrome. +- Give **dense overlays ≥ 3 s** cue time; run storyboard before recording. +- Make **frame 0** the title card; verify after every record. +- Put **`data-testid`** on controls the playbook clicks. + +--- + +## Reference example: EIP-8024 + +| ~Time | Beat | Content | +|-------|------|---------| +| 0:00 | title-card | EIP-8024, Glamsterdam, opcodes, stack-too-deep hook | +| 0:08 | glamsterdam → opcodes | Hardfork; three opcodes; select DUPN | +| 0:14 | stack-grow | 17 pushes — stack grows | +| 0:22 | dupn-hit | DUPN copies depth 17 — climax with markers + slow step | +| 0:34 | calculator | Companion / magic-byte calculator | +| 0:39 | recap | Classic DUP depth 16 vs depth 17 just demonstrated | +| 0:44 | outro | Closing + Forkcast + FYP CTAs | + +Files: `video/projects/eip-8024/{content,playbook,zones}.json`. + +--- + +## Checklist + +- [ ] Story arc defined in beats +- [ ] `zones.json` covers every `focus` and annotation target +- [ ] Dense overlays have adequate `cue` (3000–4200 ms); storyboard reviewed +- [ ] Annotations on beats with UI action after overlay hides +- [ ] Climax beat has `highlightSets` + climax timing (if applicable) +- [ ] Recap beats are text-only on current state; no unexplained example switches +- [ ] First step is `title-card` +- [ ] `npm run website:build` before record +- [ ] Frame 0 thumbnail verified after record + +--- + +## Related + +- [Video pipeline](/contributing/video-pipeline) — architecture, overlay types, selectors +- [Architecture — Video pipeline](/guide/architecture#video-pipeline-short-form) +- `video/README.md` — CLI and Playwright selectors diff --git a/docs/contributing/video-pipeline.md b/docs/contributing/video-pipeline.md new file mode 100644 index 0000000..7d450d5 --- /dev/null +++ b/docs/contributing/video-pipeline.md @@ -0,0 +1,227 @@ +# Video pipeline + +Short-form vertical videos (YouTube Shorts, Instagram Reels, X) that educate about +EIPs using live Feel Your Protocol explorations. + +## Architecture + +Two layers stay separate: + +1. **Website (`src/video/`)** — overlay rendering and video-mode chrome stripping. + Activated with `?fyp-video=1`. Does not contain video copy or playbooks. +2. **Video package (`video/`)** — `content.json`, `playbook.json`, Playwright recording CLI. + Mirrors the isolated `og/` package pattern. + +``` +website:build → static server → Playwright recordVideo + ↑ + ?fyp-video=1 + injected __FYP_VIDEO_CONFIG__ +``` + +## URL parameters + +| Param | Scope | Description | +|-------|-------|-------------| +| `fyp-video=1` | Any route | Video capture mode — hides header, footer, roadmap banner | +| `example=` | Explorations | Pre-selects an example preset on load | + +Example deep link: + +``` +/eip-8024-stack-opcodes-dupn-swapn-exchange?fyp-video=1&example=swapn +``` + +The `example` param works for **all** explorations: + +- E-Component explorations (`BytecodeStepperEC`, `PrecompileInterfaceEC`) via shared init logic +- Custom explorations (`eip-7594`, `eip-7928`) via `resolveInitialExample()` and `useExplorationExampleQuery()` + +Invalid keys fall back to each exploration's default example. + +For precompile explorations that also support field-level share URLs (`?b=`, `?hash=`, …), +`?example=` takes precedence when present. + +## Overlay system + +Overlays use a **separate visual language** from the main site: black bars, Bebas Neue / +Barlow Condensed, no rounded corners, topic color as accent only. + +Components in `src/video/`: + +| File | Role | +|------|------| +| `VideoShell.vue` | Overlay layer + `window.__FYP_VIDEO__` bridge | +| `VideoOverlay.vue` | Routes to type-specific overlay components | +| `overlays/VideoTitleCard.vue` | Full-screen movie title | +| `overlays/VideoSplitOverlay.vue` | Staggered split read flow | +| `video.css` | Capture-only typography and animations | + +Overlay types in `content.json`: + +| Type | Use | +|------|-----| +| `title-card` | Full-screen opening (black, movie poster) | +| `outro-card` | Full-screen closing | +| `punch` | Massive single-line highlight bar | +| `split` | Staggered black bars (upper-left → lower-right) | +| `list-flash` | Bold bullet list | + +Copy is injected via `window.__FYP_VIDEO_CONFIG__` before navigation. + +Full-screen **title** and **outro** cards use `VideoBandShell`: middle ~3/5 black hero band, +top/bottom ~1/5 peek zones show live exploration. See +[Authoring a video short](/contributing/video-authoring#title-and-outro-cards). + +### UI annotations (`content.json` → `annotations`) + +Parallel **guide callouts** point at exploration regions while story text may hide: + +```json +"annotations": { + "guide-stack": { + "target": "stack", + "label": "Live stack", + "hint": "Watch depth grow", + "side": "top" + } +} +``` + +Reference playbook step: `"annotate": "guide-stack"`. Targets use `zones.json` focus area keys +or built-in aliases (`disassembly-active`, `stack-top`, `stack-depth-{n}`). + +### Climax highlights (`content.json` → `highlightSets`) + +Third overlay layer — **neon-yellow marker strokes** over exact UI elements during peak +moments (no labels, no site-style borders). Playbook climax fields on `step`: + +| Field | Purpose | +|-------|---------| +| `climaxFrom` | 1-based step index to enter slow motion | +| `climaxInterval` | Step delay during climax (ms) | +| `climaxPauseMs` | Pause before first climax step | +| `highlightSet` | Highlight set id before climax step | +| `highlightAfterStep` | Switch highlights after this step completes | +| `highlightSetAfter` | Payoff highlight set id | +| `holdAfterClimaxMs` | Extra hold after payoff | + +See [Authoring a video short](/contributing/video-authoring#climax-highlights-content-json-highlightsets). + +## Story beats (cue → reveal) + +Each playbook step can tell a **story beat** synchronized with the exploration: + +| Field | Purpose | +|-------|---------| +| `beat` | Label for storyboard / validation | +| `overlay` | Text the viewer reads **before** the action | +| `annotate` | UI guide callout id — **stays visible** while story overlay hides and actions run | +| `placement` | `top-banner` or `bottom-banner` — keeps the focus area clear | +| `focus` | UI region to keep visible (see `zones.json`) | +| `cue` | Read time (ms) — overlay stays up, then hides before actions | +| `wait` | Hold time (ms) after actions — let the viewer absorb | + +**Pattern:** show overlay → `cue` → hide → step / select example → `wait` + +Preview the narrative timeline: + +```bash +npm run video:storyboard -- eip-8024 +npm run video:record -- eip-8024 --dry-run # storyboard + validation +``` + +### Safe zones (`zones.json`) + +Per-project map of exploration focus areas and overlay placements (540×960 reference). +Author overlays in `top-banner` so stack, disassembly, or companion stay visible below. + +## Playbook format + +Each project lives in `video/projects//`: + +| File | Purpose | +|------|---------| +| `content.json` | Overlay copy, annotations, highlight sets | +| `playbook.json` | Story beats: `overlay`, `cue`, `focus`, `selectExample`, `step`, … | +| `zones.json` | Focus-area selectors + banner placement metadata | +| `output/` | Recorded `.webm` (gitignored) | + +Example beat — read, then step with stack visible: + +```json +{ + "beat": "stack-grow", + "overlay": "stack-cue", + "placement": "top-banner", + "focus": "stack", + "cue": 2400, + "step": { "count": 9, "interval": 340 }, + "wait": 1400 +} +``` + +## Automation hooks + +Stable selectors for Playwright (see `video/README.md` for the full list). + +When adding new interactive controls to explorations, add a `data-testid` if the +video playbook needs to click them. + +`BytecodeStepperEC` auto-scrolls the disassembly and stack panels during stepping +(active PC row + deep stack entries stay in view). Playwright also syncs panel +scroll after each `step` action in the playbook. + +## Local development + +Preview video mode in the dev server (overlays only appear when config is injected — use record dry-run to validate copy): + +```bash +npm run dev +# /eip-8024-stack-opcodes-dupn-swapn-exchange?fyp-video=1&example=dupn +``` + +## Recording + +From `website/`: + +```bash +npm run website:build +npm run video:record -- eip-8024 --dry-run # print playbook steps +npm run video:record -- eip-8024 --preview # 540×960 .webm (faster) +npm run video:record -- eip-8024 # 1080×1920 .webm +``` + +Output: `video/projects/eip-8024/output/eip-8024-.webm` + +**Always run `npm run website:build` before recording** if `src/video/` changed. + +Recordings auto-trim lead-in (Playwright captures black/hidden frames before the title +card renders) and verify frame 0 shows the title band for preview thumbnails. Detection +uses center-crop dark-pixel ratio — not brightness alone. Requires `ffmpeg` on PATH. + +Details: [Record and verify](/contributing/video-authoring#record-and-verify). + +## Tests + +Unit tests cover: + +- `src/libs/__tests__/exampleFromQuery.spec.ts` — query parsing and resolution +- `src/libs/__tests__/exampleFromQuery.explorations.spec.ts` — all live explorations +- `src/video/__tests__/` — video mode detection and bridge logic +- `video/src/__tests__/` — project loading, playbook duration, lead-in trim + +Playwright recording is **not** run in CI (same policy as OG images). + +## Authoring guide + +[Authoring a video short](/contributing/video-authoring) — how to plan beats, write +overlays/annotations/highlights, set pacing, record, and verify thumbnails. Reference: +`video/projects/eip-8024/`. + +## Adding a new video + +1. Create `video/projects//content.json` and `playbook.json` +2. Add exploration to `video/src/explorationRegistry.ts` if not already listed +3. Run `npm run video:record -- ` + +No website changes required if the exploration type is already supported. diff --git a/docs/guide/architecture.md b/docs/guide/architecture.md index 1071bc9..59111df 100644 --- a/docs/guide/architecture.md +++ b/docs/guide/architecture.md @@ -145,3 +145,15 @@ The project uses a hybrid testing approach: - **E2E tests** (Cypress) as lean smoke tests for critical navigation flows and page-level integration Unit tests live alongside their components in `__tests__/` folders. E2E tests are consolidated in `cypress/e2e/`. + +## Video pipeline (short-form) + +Vertical exploration videos for YouTube Shorts, Reels, and X are recorded via an +isolated Playwright package in `video/` (same pattern as `og/` for social preview images). + +- **Website overlay shell** — `src/video/`, activated with `?fyp-video=1` +- **Content & playbooks** — `video/projects//` (JSON, separate from the site) +- **Deep-linking** — `?example=` pre-selects example presets on all explorations + +See [Video pipeline](/contributing/video-pipeline) for the workflow and +[Authoring a video short](/contributing/video-authoring) for how to write a project. diff --git a/docs/public/llms.txt b/docs/public/llms.txt index 1dc1b67..6624c37 100644 --- a/docs/public/llms.txt +++ b/docs/public/llms.txt @@ -28,6 +28,8 @@ at contributors and developers who want to understand or extend the project. - [Styling & Design](https://docs.feelyourprotocol.org/contributing/styling.html): The exploration design system and class conventions - [Code Conventions](https://docs.feelyourprotocol.org/contributing/code-conventions.html): Coding standards and test commands - [Third-Party Libraries](https://docs.feelyourprotocol.org/contributing/third-party-libraries.html): How new library dependencies are evaluated and added +- [Video Pipeline](https://docs.feelyourprotocol.org/contributing/video-pipeline.html): Short-form exploration videos — architecture, overlays, recording +- [Authoring a Video Short](https://docs.feelyourprotocol.org/contributing/video-authoring.html): How to plan, write, and record exploration shorts ## Related diff --git a/package.json b/package.json index a72cfec..d47066c 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,11 @@ "social:preview": "vite preview --config vite.social.config.ts", "capture:social": "run-s social:build capture:social:run", "capture:social:run": "npm run capture:social:run --prefix og --", + "video:setup": "npm run setup --prefix video", + "video:check": "npm run check --prefix video", + "video:preflight": "npm run preflight --prefix video", + "video:record": "npm run record --prefix video --", + "video:storyboard": "npm run storyboard --prefix video --", "website:preview": "vite preview --outDir dist/website", "docs:dev": "vitepress dev docs", "docs:build": "vitepress build docs", diff --git a/src/App.vue b/src/App.vue index d858151..f7c120a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,5 +1,5 @@ diff --git a/src/components/CompanionSheet.vue b/src/components/CompanionSheet.vue index 0510547..3d1e1e0 100644 --- a/src/components/CompanionSheet.vue +++ b/src/components/CompanionSheet.vue @@ -1,6 +1,8 @@