diff --git a/DECISIONS.md b/DECISIONS.md index 0f003fc..360b62d 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -53,3 +53,7 @@ - PR video approach: use Remotion for frame-accurate React motion graphics and `@remotion/player` for in-app preview at `/pr-video` before rendering. - Remotion dependency policy: keep `remotion`, `@remotion/cli`, and `@remotion/player` on the same exact version to avoid render/runtime mismatches. - Remotion styling bridge: use `@remotion/tailwind` so Vite preview and Remotion CLI renders share the same Tailwind v3 visual system. + +## 2026-06-13 +- Remotion sound design: use `@remotion/sfx` at the same exact Remotion version for predefined motion-graphic SFX cues instead of hand-generating local audio assets. +- PR video BGM: use a locally generated light musical WAV under `public/audio/pr-video/` with Remotion fade controls, avoiding external music licensing and network-dependent playback. diff --git a/package.json b/package.json index 0a5cf7e..c8a15d2 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", "@remotion/player": "4.0.475", + "@remotion/sfx": "4.0.475", "@tanstack/react-query": "^5.90.21", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 782b84f..4594c9a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: '@remotion/player': specifier: 4.0.475 version: 4.0.475(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/sfx': + specifier: 4.0.475 + version: 4.0.475 '@tanstack/react-query': specifier: ^5.90.21 version: 5.90.21(react@19.2.4) @@ -1063,6 +1066,9 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' + '@remotion/sfx@4.0.475': + resolution: {integrity: sha512-cs3O5wwuY42hxZkI4xWxPD5WzO4zZeV6fPkDk45MudXqJiwZA3YDEuSWCOOaglAwFyBaJ5foMCZJ8UxpdrFzEA==} + '@remotion/streaming@4.0.475': resolution: {integrity: sha512-wBxed6DczdwybFSNsZqVnmdnkCSWjkg0/Q37eTCfq56bJ6lM5IJ2ZiyEfswE7TfI0OFpp1CWOFWCGf27O7WTDw==} @@ -3694,6 +3700,8 @@ snapshots: - bufferutil - utf-8-validate + '@remotion/sfx@4.0.475': {} + '@remotion/streaming@4.0.475': {} '@remotion/studio-server@4.0.475(lightningcss@1.31.1)(postcss@8.5.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': diff --git a/public/audio/pr-video/team-po-light-loop.wav b/public/audio/pr-video/team-po-light-loop.wav new file mode 100644 index 0000000..c3edd47 Binary files /dev/null and b/public/audio/pr-video/team-po-light-loop.wav differ diff --git a/src/features/pr-video/components/pr-video-player.tsx b/src/features/pr-video/components/pr-video-player.tsx index 251dbdc..c35db26 100644 --- a/src/features/pr-video/components/pr-video-player.tsx +++ b/src/features/pr-video/components/pr-video-player.tsx @@ -7,7 +7,6 @@ export function PrVideoPlayer() { return (
Math.round(seconds * TEAM_PO_PR_VIDEO.fps); + +const sceneStarts = { + opening: 0, + problem: 5.5, + matching: 11, + workspace: 20, + report: 30, + closing: 41, + finale: 50, +} as const; + +const atScene = (scene: keyof typeof sceneStarts, seconds: number) => + at(sceneStarts[scene] + seconds); + +const soundCues: readonly SoundCue[] = [ + { + id: "opening-prompt-stroke", + frame: atScene("opening", 0.12), + durationInFrames: 24, + src: whoosh, + volume: 0.045, + playbackRate: 1.08, + }, + { + id: "opening-logo-block-one", + frame: atScene("opening", 0.45), + durationInFrames: 11, + src: uiSwitch, + volume: 0.05, + playbackRate: 0.88, + }, + { + id: "opening-logo-block-two", + frame: atScene("opening", 0.58), + durationInFrames: 11, + src: uiSwitch, + volume: 0.048, + playbackRate: 0.96, + }, + { + id: "opening-logo-block-three", + frame: atScene("opening", 0.72), + durationInFrames: 11, + src: uiSwitch, + volume: 0.046, + playbackRate: 1.04, + }, + { + id: "opening-team-word-swipe", + frame: atScene("opening", 1.02), + durationInFrames: 20, + src: whip, + volume: 0.068, + playbackRate: 0.96, + }, + { + id: "opening-ui-cluster-arrives", + frame: atScene("opening", 1.65), + durationInFrames: 28, + src: whoosh, + volume: 0.058, + playbackRate: 0.98, + }, + { + id: "opening-po-resolve", + frame: atScene("opening", 1.78), + durationInFrames: 18, + src: shutterModern, + volume: 0.064, + playbackRate: 0.78, + }, + { + id: "opening-flow-connectors-draw", + frame: atScene("opening", 2.35), + durationInFrames: 40, + src: whoosh, + volume: 0.05, + playbackRate: 0.9, + }, + { + id: "opening-camera-drift", + frame: atScene("opening", 4.7), + durationInFrames: 34, + src: whoosh, + volume: 0.042, + playbackRate: 0.74, + }, + { + id: "problem-reveal-wipe", + frame: atScene("problem", 0), + durationInFrames: 30, + src: whoosh, + volume: 0.08, + playbackRate: 0.86, + }, + { + id: "problem-board-lands", + frame: atScene("problem", 0.62), + durationInFrames: 20, + src: shutterModern, + volume: 0.064, + playbackRate: 0.84, + }, + { + id: "problem-issue-node-cluster-one", + frame: atScene("problem", 1), + durationInFrames: 14, + src: uiSwitch, + volume: 0.045, + playbackRate: 0.82, + }, + { + id: "problem-warning-field", + frame: atScene("problem", 1.25), + durationInFrames: 22, + src: uiSwitch, + volume: 0.062, + playbackRate: 0.7, + }, + { + id: "problem-issue-node-cluster-two", + frame: atScene("problem", 1.38), + durationInFrames: 14, + src: uiSwitch, + volume: 0.041, + playbackRate: 0.92, + }, + { + id: "problem-pressure-build", + frame: atScene("problem", 1.82), + durationInFrames: 36, + src: whoosh, + volume: 0.045, + playbackRate: 0.78, + }, + { + id: "problem-signal-converges", + frame: atScene("problem", 2.18), + durationInFrames: 28, + src: whoosh, + volume: 0.066, + playbackRate: 0.92, + }, + { + id: "problem-signal-bars", + frame: atScene("problem", 2.32), + durationInFrames: 20, + src: mouseClick, + volume: 0.038, + playbackRate: 0.76, + }, + { + id: "problem-bottleneck-lock", + frame: atScene("problem", 2.62), + durationInFrames: 24, + src: shutterModern, + volume: 0.078, + playbackRate: 0.72, + }, + { + id: "problem-pain-point-rail", + frame: atScene("problem", 2.9), + durationInFrames: 22, + src: uiSwitch, + volume: 0.048, + playbackRate: 0.78, + }, + { + id: "problem-bridge-copy", + frame: atScene("problem", 4.45), + durationInFrames: 24, + src: whoosh, + volume: 0.044, + playbackRate: 1, + }, + { + id: "matching-reveal-wipe", + frame: atScene("matching", 0), + durationInFrames: 30, + src: whoosh, + volume: 0.082, + playbackRate: 0.94, + }, + { + id: "matching-window-lands", + frame: atScene("matching", 0.7), + durationInFrames: 20, + src: shutterModern, + volume: 0.06, + playbackRate: 0.8, + }, + { + id: "matching-request-field-role", + frame: atScene("matching", 1.05), + durationInFrames: 12, + src: uiSwitch, + volume: 0.042, + playbackRate: 0.9, + }, + { + id: "matching-request-field-scope", + frame: atScene("matching", 1.21), + durationInFrames: 12, + src: uiSwitch, + volume: 0.038, + playbackRate: 0.98, + }, + { + id: "matching-headline-underline", + frame: atScene("matching", 1.35), + durationInFrames: 24, + src: whoosh, + volume: 0.034, + playbackRate: 1.08, + }, + { + id: "matching-request-button", + frame: atScene("matching", 1.86), + durationInFrames: 14, + src: mouseClick, + volume: 0.076, + playbackRate: 0.72, + }, + { + id: "matching-session-scan", + frame: atScene("matching", 2.25), + durationInFrames: 30, + src: whoosh, + volume: 0.056, + playbackRate: 1.08, + }, + { + id: "matching-profile-selected-one", + frame: atScene("matching", 2.45), + durationInFrames: 14, + src: uiSwitch, + volume: 0.052, + playbackRate: 0.84, + }, + { + id: "matching-profile-selected-two", + frame: atScene("matching", 2.63), + durationInFrames: 14, + src: uiSwitch, + volume: 0.054, + playbackRate: 0.92, + }, + { + id: "matching-team-panel", + frame: atScene("matching", 3.2), + durationInFrames: 24, + src: shutterModern, + volume: 0.068, + playbackRate: 0.72, + }, + { + id: "matching-role-slots-settle", + frame: atScene("matching", 3.45), + durationInFrames: 22, + src: uiSwitch, + volume: 0.046, + playbackRate: 0.78, + }, + { + id: "matching-team-panel-spark", + frame: atScene("matching", 3.55), + durationInFrames: 18, + src: uiSwitch, + volume: 0.046, + playbackRate: 0.68, + }, + { + id: "workspace-reveal-wipe", + frame: atScene("workspace", 0), + durationInFrames: 30, + src: whoosh, + volume: 0.076, + playbackRate: 0.9, + }, + { + id: "workspace-window-lands", + frame: atScene("workspace", 0.62), + durationInFrames: 20, + src: shutterModern, + volume: 0.06, + playbackRate: 0.78, + }, + { + id: "workspace-headline-underline", + frame: atScene("workspace", 1.25), + durationInFrames: 24, + src: whoosh, + volume: 0.034, + playbackRate: 1.04, + }, + { + id: "workspace-stage-forming", + frame: atScene("workspace", 1.6), + durationInFrames: 12, + src: uiSwitch, + volume: 0.044, + playbackRate: 0.82, + }, + { + id: "workspace-stage-progress", + frame: atScene("workspace", 2), + durationInFrames: 56, + src: whoosh, + volume: 0.046, + playbackRate: 0.86, + }, + { + id: "workspace-checklist-first", + frame: atScene("workspace", 2.02), + durationInFrames: 12, + src: mouseClick, + volume: 0.046, + playbackRate: 0.7, + }, + { + id: "workspace-active-stage", + frame: atScene("workspace", 2.18), + durationInFrames: 14, + src: uiSwitch, + volume: 0.056, + playbackRate: 0.78, + }, + { + id: "workspace-contract-lift", + frame: atScene("workspace", 2.55), + durationInFrames: 26, + src: whoosh, + volume: 0.052, + playbackRate: 1.08, + }, + { + id: "workspace-drawer-appears", + frame: atScene("workspace", 3.35), + durationInFrames: 22, + src: shutterModern, + volume: 0.064, + playbackRate: 0.72, + }, + { + id: "workspace-active-confirmed", + frame: atScene("workspace", 3.62), + durationInFrames: 16, + src: uiSwitch, + volume: 0.062, + playbackRate: 0.68, + }, + { + id: "report-reveal-wipe", + frame: atScene("report", 0), + durationInFrames: 30, + src: whoosh, + volume: 0.076, + playbackRate: 0.9, + }, + { + id: "report-window-lands", + frame: atScene("report", 0.62), + durationInFrames: 20, + src: shutterModern, + volume: 0.058, + playbackRate: 0.78, + }, + { + id: "report-headline-underline", + frame: atScene("report", 1.22), + durationInFrames: 24, + src: whoosh, + volume: 0.034, + playbackRate: 1.04, + }, + { + id: "report-activity-rows", + frame: atScene("report", 1.55), + durationInFrames: 18, + src: uiSwitch, + volume: 0.044, + playbackRate: 0.78, + }, + { + id: "report-metrics-count-up", + frame: atScene("report", 1.7), + durationInFrames: 22, + src: uiSwitch, + volume: 0.052, + playbackRate: 0.72, + }, + { + id: "report-chart-line-draw", + frame: atScene("report", 1.8), + durationInFrames: 42, + src: whoosh, + volume: 0.056, + playbackRate: 0.88, + }, + { + id: "report-chart-dot-resolve", + frame: atScene("report", 2.72), + durationInFrames: 14, + src: uiSwitch, + volume: 0.058, + playbackRate: 0.8, + }, + { + id: "report-insight-panel", + frame: atScene("report", 3.15), + durationInFrames: 24, + src: shutterModern, + volume: 0.064, + playbackRate: 0.74, + }, + { + id: "report-next-action-confirmed", + frame: atScene("report", 3.62), + durationInFrames: 16, + src: uiSwitch, + volume: 0.06, + playbackRate: 0.7, + }, + { + id: "closing-reveal-wipe", + frame: atScene("closing", 0), + durationInFrames: 30, + src: whoosh, + volume: 0.072, + playbackRate: 0.84, + }, + { + id: "closing-brand-arrives", + frame: atScene("closing", 0.22), + durationInFrames: 16, + src: uiSwitch, + volume: 0.038, + playbackRate: 0.76, + }, + { + id: "closing-token-matching", + frame: atScene("closing", 0.52), + durationInFrames: 12, + src: uiSwitch, + volume: 0.042, + playbackRate: 0.68, + }, + { + id: "closing-token-workspace", + frame: atScene("closing", 0.74), + durationInFrames: 12, + src: uiSwitch, + volume: 0.04, + playbackRate: 0.76, + }, + { + id: "closing-ribbon-path-draw", + frame: atScene("closing", 0.9), + durationInFrames: 58, + src: whoosh, + volume: 0.054, + playbackRate: 0.66, + }, + { + id: "closing-token-report", + frame: atScene("closing", 0.96), + durationInFrames: 12, + src: uiSwitch, + volume: 0.04, + playbackRate: 0.82, + }, + { + id: "closing-token-shipping", + frame: atScene("closing", 1.18), + durationInFrames: 12, + src: uiSwitch, + volume: 0.042, + playbackRate: 0.88, + }, + { + id: "closing-headline-line-one", + frame: atScene("closing", 1.62), + durationInFrames: 16, + src: shutterModern, + volume: 0.046, + playbackRate: 0.66, + }, + { + id: "closing-headline-line-two", + frame: atScene("closing", 1.95), + durationInFrames: 16, + src: shutterModern, + volume: 0.05, + playbackRate: 0.7, + }, + { + id: "closing-headline-line-three", + frame: atScene("closing", 2.32), + durationInFrames: 18, + src: shutterModern, + volume: 0.054, + playbackRate: 0.64, + }, + { + id: "closing-token-locks", + frame: atScene("closing", 2.55), + durationInFrames: 22, + src: uiSwitch, + volume: 0.04, + playbackRate: 0.62, + }, + { + id: "closing-headline-shine", + frame: atScene("closing", 3.18), + durationInFrames: 28, + src: whip, + volume: 0.038, + playbackRate: 0.72, + }, + { + id: "closing-copy-lock", + frame: atScene("closing", 3.36), + durationInFrames: 20, + src: shutterModern, + volume: 0.042, + playbackRate: 0.62, + }, + { + id: "closing-underline-draw", + frame: atScene("closing", 3.58), + durationInFrames: 28, + src: whoosh, + volume: 0.034, + playbackRate: 0.9, + }, + { + id: "closing-comet-pass", + frame: atScene("closing", 4.72), + durationInFrames: 50, + src: whoosh, + volume: 0.058, + playbackRate: 0.82, + }, + { + id: "closing-feature-ignition", + frame: atScene("closing", 5.3), + durationInFrames: 20, + src: uiSwitch, + volume: 0.042, + playbackRate: 0.64, + }, + { + id: "finale-reveal", + frame: atScene("finale", 0), + durationInFrames: 30, + src: whoosh, + volume: 0.082, + playbackRate: 0.88, + }, + { + id: "finale-prompt-stroke", + frame: atScene("finale", 0.22), + durationInFrames: 22, + src: whoosh, + volume: 0.04, + playbackRate: 1.02, + }, + { + id: "finale-logo-block-one", + frame: atScene("finale", 0.46), + durationInFrames: 11, + src: uiSwitch, + volume: 0.048, + playbackRate: 0.76, + }, + { + id: "finale-logo-block-two", + frame: atScene("finale", 0.6), + durationInFrames: 11, + src: uiSwitch, + volume: 0.046, + playbackRate: 0.84, + }, + { + id: "finale-logo-block-three", + frame: atScene("finale", 0.74), + durationInFrames: 11, + src: uiSwitch, + volume: 0.044, + playbackRate: 0.92, + }, + { + id: "finale-team-word", + frame: atScene("finale", 0.9), + durationInFrames: 22, + src: whip, + volume: 0.06, + playbackRate: 0.86, + }, + { + id: "finale-po-resolve", + frame: atScene("finale", 1.36), + durationInFrames: 20, + src: shutterModern, + volume: 0.06, + playbackRate: 0.66, + }, + { + id: "finale-message-settle", + frame: atScene("finale", 1.86), + durationInFrames: 24, + src: shutterModern, + volume: 0.052, + playbackRate: 0.68, + }, + { + id: "finale-message-underline", + frame: atScene("finale", 2.2), + durationInFrames: 34, + src: whoosh, + volume: 0.046, + playbackRate: 0.88, + }, + { + id: "finale-bottom-pulse", + frame: atScene("finale", 4.2), + durationInFrames: 20, + src: uiSwitch, + volume: 0.058, + playbackRate: 0.62, + }, +]; + +export function PrVideoSoundDesign() { + return ( + <> + + {soundCues.map((cue) => ( + + + + ))} + + ); +} + +function PrVideoBackgroundMusic() { + return ( + + interpolate( + frame, + [ + 0, + at(2.4), + TEAM_PO_PR_VIDEO.durationInFrames - at(4.2), + TEAM_PO_PR_VIDEO.durationInFrames, + ], + [0, 0.1, 0.1, 0], + { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }, + ) + } + /> + ); +} diff --git a/src/features/pr-video/components/team-po-pr-video.tsx b/src/features/pr-video/components/team-po-pr-video.tsx index 3b2b109..f549ad2 100644 --- a/src/features/pr-video/components/team-po-pr-video.tsx +++ b/src/features/pr-video/components/team-po-pr-video.tsx @@ -28,6 +28,8 @@ import { useVideoConfig, } from "remotion"; +import { PrVideoSoundDesign } from "@/features/pr-video/components/pr-video-sound-design"; + const EASE_OUT = Easing.bezier(0.16, 1, 0.3, 1); const EASE_IN_OUT = Easing.bezier(0.45, 0, 0.55, 1); const OPENING_SCENE_FRAMES = 180; @@ -431,6 +433,7 @@ export function TeamPoPrVideo() { > + );