Files
LynkedUpPro_CRM/docs/superpowers/specs/2026-05-30-live-product-film-design.md

15 KiB
Raw Permalink Blame History

LynkedUp Pro — Live Product Film (design)

Date: 2026-05-30 Status: Approved (pending user review of this spec) Supersedes: the still-image marketing film in marketing/product-film/ (kept only as history)


Goal

Build a new flagship product film (~1516 min, hard cap 20 min) for a B2B audience of roofing-company owners and operators, in which real, live product interactions are the backbone: cursor moves, clicks a control, the system loads, and the result is revealed on camera. It replaces the previous Ken-Burns-over-stills film as the primary marketing/sales asset.

Audience & constraints

  • Dual B2B audience: owners (oversight, control, ROI) + operators (daily workflow).
  • Format: 1080p (1920×1080), 16:9, 30fps, dark mode.
  • Narration: on-screen captions + music bed + a written VO script delivered in the repo for voicing later. No spoken VO generated now.
  • Bookends: open on the real landing-page hero; close on the swinging-crane landing CTA (signature closer).
  • Zero CRM source changes from the film pipeline. The capture stage only drives the running app via Playwright; the synthetic cursor is injected from the Playwright side, never added to app source.
    • One deliberate exception (separate workstream): the AI-chat model + data-context update in src/components/Chatbot.jsx (see §9). This is an intentional product change the AI Assistant segment depends on, not a film artifact.

Architecture — two-stage pipeline

We reuse the existing marketing/product-film/ Remotion project (theme, captions, audio, build scripts) and add a video capture stage beside the current still capture.

CAPTURE  →  Playwright drives the running CRM (local dev server, real APIs), records each
            scripted interaction to video (one .webm per beat) → ffmpeg → constant-30fps
            public/clips/<id>.mp4 + clips.manifest.json (per-clip metadata + caption cues).
COMPOSE  →  Remotion <OffthreadVideo> stitches clips + caption/pillar overlays + music +
            crane bookends → out/lynkedup-pro-live-film.mp4.

We drive the local dev server with the real APIs (the existing vite dev runner that loads .env and invokes real /api handlers), so async loads (e.g. /api/storm-history) genuinely populate on camera — the "system is thinking" reveal is real.

Why this shape

  • True video frames preserve smooth load/transition animations (modal fades, map tiles streaming, panels populating) that a screenshot burst would stutter through.
  • One clip per beat keeps each segment independently re-filmable and the build incremental.

Synthetic cursor (the key technique)

Playwright's recorded video has no OS cursor (headless Chromium has no visible pointer). We inject one from the Playwright side:

  • scripts/lib/cursor.js exports a page-side init script (injected via page.addInitScript) that appends a branded pointer element (pointer-events:none, top z-index) to <body> and exposes window.__cursor.moveTo(x, y, ms) (eased move) and window.__cursor.clickPulse() (ripple).
  • Each scene step: ease the visual cursor to a target → fire the real page.mouse.click at the same coordinates (so the app actually responds) → trigger the click pulse.
  • The visual cursor and the real pointer stay in lockstep, baked into the footage. No post-hoc cursor compositing, no drift.

This injection happens entirely from Playwright; no app source is modified.


Scene scripting model

Each beat is an isolated scene file: scripts/scenes/<id>.mjs exporting async (page, cursor, ctx) => {...} describing the choreography. Scenes are grouped by login role; the orchestrator logs in once per role and films that role's scenes in one SPA session (auth is in-memory, so we navigate via history.pushState + popstate, never page.goto, exactly as the existing still capture does).

Async reveals are driven by the real event, not a fixed sleep — e.g. Storm Intel uses page.waitForResponse(/\/api\/storm-history/) so the clip captures the actual panel fill.

Form typing uses page.type() so text appears field-by-field on camera (lead creation).

Multi-role: owner (justin), field-agent (LUP-1040), sub-con (carlos), password password. The orchestrator already groups scenes by role.

Beats vs. scene files

The film has 21 beats (0a, 0b, and 119 in the structure table below). Two of them — 0b Promise and 18 Breadth flash — are caption-over-still b-roll that reuses the existing capture.mjs stills, so they need no video scene file.

That leaves 19 live video scene files in scripts/scenes/:

landing-hero, storm-intel, territory-map, lead-create, lead-verify, dispatch, kanban, sub-tasks, pro-canvas, estimates, owner-snapshot, operator-dashboard, people-skills, project-detail, documents, org-access, role-perspective, ai-assistant, crane-close.


Film structure (~1516 min, 8 acts, 19 segments)

# Act / Segment Live interaction ~sec
0a Landing hero Real landing, ambient glow, headline 12
0b Promise Pain→promise captions over subtle b-roll 18
ACT 1 — Win the work
1 Storm Intelligence Pin-drop → NOAA/IEM storm history fetches & fills (hail/wind/flood) 55
2 Territory Command Pin + draw a zone → assign canvasser (ZoneAssignmentModal) 50
3 Lead Creation Storm zone → CreateLeadPage, Quick/Full toggle, type fields, save → toast 60
4 Lead Verification Verify/enrich an inbound lead 30
ACT 2 — Run the operation
5 LynkDispatch Click lead card → AI recommendation drawer → accept → log + storm-mode toggle 55
6 Pipeline (Kanban) Drag a lead card across stages → campaign toast countdown → edit message 50
7 Subcontractor Tasks Owner assigns a sub task → status flow 35
ACT 3 — Estimate & sell
8 Pro-Canvas / Estimate Builder Measurements, template pick, XP/level-up gamification 60
9 Estimates library Open estimate → material/cost breakdown 35
ACT 4 — See the business
10 Owner Snapshot Click KPI card → FinancialDetailsModal; budget/pie/funnel charts; commission; storm attribution 55
11 Operator Dashboard Animated revenue counter, live weather widget, leaderboard 30
ACT 5 — Manage the team
12 People & Skill Scores Search person → detail → edit a skill score → save; masked PII 50
13 Project Details (tabbed) Open a project → cycle tabs (exact tab set confirmed from OwnerProjectDetail.jsx) 55
14 Documents Document library browse/preview 25
ACT 6 — Control & trust
15 Org Settings / Access Control Toggle a permission in the matrix → change-log records it; commission rules edit 55
16 Role Perspective Same workspace logged in as owner → employee → sub (access-control proof) 35
ACT 7 — Intelligence
17 AI Assistant Ask the assistant a scripted question → streamed end-to-end answer (see §9) 35
ACT 8 — Close
18 Breadth flash Fast cuts: vendors, documents, remaining screens (still b-roll) 18
19 Crane close Swinging-crane landing CTA + end card / logo 22

≈ 14 min of segments + titles/transitions → ~1516 min final. Within the 20-min cap.


Remotion composition

New components

  • src/LiveFilm.tsx — new <Series> root sequencing all clips + bookends + act titles.
  • src/components/ClipStage.tsx — wraps an <OffthreadVideo> clip with optional browser chrome (ScreenFrame), a subtle zoom-punch on key-moment timestamps, and caption overlays read from the manifest.
  • src/clips.manifest.json — per-clip metadata:
    {
      "fps": 30,
      "clips": [
        {
          "id": "storm-intel",
          "file": "clips/storm-intel.mp4",
          "role": "owner",
          "durationFrames": 1650,           // from ffprobe
          "act": "Win the work",
          "pillarTitle": "Storm Intelligence",
          "keyMoments": [ { "frame": 420, "zoom": 1.08 } ],
          "captions": [ { "fromFrame": 60, "toFrame": 240, "text": "Drop a pin anywhere." } ]
        }
      ]
    }
    
  • src/Root.tsx — register LiveFilm as the default composition; keep the old Film registered as a secondary composition.

Reused components: theme.ts, Caption, PillarTitle, AudioTrack, BlueprintBg, Crane / CraneCard / craneSwing (close), TypeReveal.

VO script: marketing/product-film/VO-SCRIPT.md — per-segment narration lines aligned to caption timing, for voicing later.


Capture scripts (Node, Playwright)

New

  • scripts/capture-video.mjs — orchestrator: parse --scene <id> (single) or all; group scenes by role; login per role; for each scene set up a recordVideo context (1920×1080), run the scene script, close context to flush the .webm; convert each .webm → constant 30fps .mp4 via ffmpeg-static; probe duration; write/refresh clips.manifest.json.
  • scripts/lib/cursor.js — injected synthetic-cursor page script + a Node-side cursor helper that drives moves/clicks.
  • scripts/lib/record.jsrecordVideo context helpers; webm→mp4 + ffprobe-duration via ffmpeg-static.
  • scripts/scenes/*.mjs — one file per beat (see scene list).

Reused: scripts/capture.mjs for breadth-flash stills.

New dependency: ffmpeg-static (avoids a system ffmpeg install on Windows).


§9 — AI Assistant (separate CRM workstream)

Status (2026-05-30): model swap + data budgeting DONE. Implemented and verified in src/components/Chatbot.jsx (helper unit tests 8/8; esbuild parse clean). Remaining AI work before filming segment 17: lock the deterministic demo question(s) and verify a complete end-to-end answer live against the running app.

The AI Assistant segment depends on deliberate changes to src/components/Chatbot.jsx:

  1. Model swap [DONE]: model: 'qwen/qwen3-32b''openai/gpt-oss-120b' (now via an AI_MODEL const). gpt-oss-120b is a non-reasoning instruct model, so the existing <think>-stripping regex becomes a no-op — cleaner streaming for on-camera capture.

1a. Context budgeting [DONE]: added capList() caps on every previously-unbounded role list (OWNER/CONTRACTOR projects 10, CONTRACTOR milestones 10, ADMIN non-compliant vendors 8, SUBCONTRACTOR tasks 10, FIELD_AGENT pending-sigs 8), a hard clampToTokenBudget() on the assembled system prompt (4,000 tok ≈ 16K chars), and a 12-message history bound. Makes a context-window overflow structurally impossible regardless of mock-data volume.

  1. "Update the data properly": ensure generateRoleContext(user, storeData) provides an accurate and sufficient (right-sized, not dumped) data context so a scripted demo question gets a complete, end-to-end answer. Concretely:

    • Audit the role used for the demo (default: OWNER justin) and confirm the summarized slices (KPIs, projects, milestones, vendor compliance, documents, personnel) are internally consistent with the mock store the rest of the film shows.
    • Trim the heaviest lists to the highest-signal entries so the answer is focused, while keeping enough breadth that the demo "feels end to end."
    • Define one deterministic demo question per filmed role that the trimmed context fully and correctly answers.
  2. Capture reliability: the scene films a real streamed answer; if the Groq backend is slow/flaky during capture, the scene falls back to a scripted prompt + pre-seeded reply so the segment is reliable. (The demo question is deterministic regardless.)

This workstream is sequenced before the ai-assistant capture scene and is the only part of the project that edits CRM source.


Risks & mitigations

  • Leaflet tiles load async → wait for tile-settle before interacting (else blank-map clips). Pre-warm + settle delay.
  • Nondeterministic API timingwaitForResponse on the real endpoint drives the reveal.
  • recordVideo records CSS pixels (no deviceScaleFactor) → record at 1920×1080 = exactly 1080p; convert to constant 30fps with ffmpeg.
  • Pro-Canvas / Estimate Builder is the highest-scripting-risk scene (drawing canvas, measurements, templates, XP) → script carefully; the level-up modal, if triggerable, is a delight moment.
  • AI Assistant backend → deterministic prompt + waitForResponse, scripted fallback reply.
  • Render scale → ~1516 min 1080p30 is a large render (likely 3060+ min, multi-hundred-MB mp4). Output out/lynkedup-pro-live-film.mp4 is gitignored (as before).

Testing

  • --scene <id> films one beat at a time for fast iteration.
  • After capture, ffprobe asserts each clip's duration; a pre-render smoke check fails fast if any manifest-referenced clip file is missing.
  • npx remotion preview to scrub the assembled film; npx remotion render for final.
  • AI workstream: verify the demo question returns a complete answer from the trimmed context before filming ai-assistant.

File structure summary

Create

  • marketing/product-film/src/LiveFilm.tsx
  • marketing/product-film/src/components/ClipStage.tsx
  • marketing/product-film/src/clips.manifest.json
  • marketing/product-film/scripts/capture-video.mjs
  • marketing/product-film/scripts/lib/cursor.js
  • marketing/product-film/scripts/lib/record.js
  • marketing/product-film/scripts/scenes/*.mjs (19 live video scene files; see "Beats vs. scene files")
  • marketing/product-film/VO-SCRIPT.md

Modify

  • marketing/product-film/src/Root.tsx (register LiveFilm as default)
  • marketing/product-film/package.json (add ffmpeg-static; capture-video script entry)
  • src/components/Chatbot.jsx (model swap + data-context update — §9, separate workstream)

Reuse unchanged

  • marketing/product-film/scripts/capture.mjs, theme/caption/audio/crane components.

Out of scope

  • Spoken voiceover (script only, voiced later).
  • The old still-based Film.tsx (retained as secondary composition, not deleted).
  • Any CRM source change other than the AI-chat model + data context in §9.