@openvidstudio/core 0.1.0

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.
package/src/index.ts ADDED
@@ -0,0 +1,60 @@
1
+ // @openvidstudio/core: public API. Everything a video project needs to build
2
+ // scenes in the openvidstudio house style: camera rig, motion helpers, SFX,
3
+ // look pass, design tokens, and the shared scene/UI components.
4
+
5
+ // Camera + layers
6
+ export { CameraRig, Layer, useCamera } from "./camera";
7
+ export type { CamKeyframe } from "./camera";
8
+
9
+ // Motion helpers
10
+ export { E, SPRING, tween, pop, jitter, staggerDelay } from "./motion";
11
+
12
+ // SFX
13
+ export {
14
+ KeySound,
15
+ TypingSfx,
16
+ Click,
17
+ Whoosh,
18
+ Blip,
19
+ SuccessChime,
20
+ Bell,
21
+ EnterKey,
22
+ MusicBed,
23
+ } from "./sfx";
24
+
25
+ // Look pass
26
+ export { FilmGrain, Vignette, ColorGrade, LookPass } from "./look";
27
+
28
+ // Design tokens
29
+ export { color, font, glow, panelShadow, radius } from "./tokens";
30
+
31
+ // Scene wrapper
32
+ export { CinematicScene } from "./CinematicScene";
33
+
34
+ // Device / UI frames
35
+ export { BrowserFrame } from "./DeviceFrame";
36
+
37
+ // Kinetic typography
38
+ export { TitleSlam, Caption } from "./KineticText";
39
+
40
+ // Terminal replay
41
+ export {
42
+ TerminalReplay,
43
+ computeTerminalTimeline,
44
+ } from "./TerminalReplay";
45
+ export type { TermLine, TermStep } from "./TerminalReplay";
46
+
47
+ // Code panel
48
+ export { CodePanel } from "./CodePanel";
49
+ export type { Token, CodeLine } from "./CodePanel";
50
+
51
+ // Checklist panel
52
+ export { ChecklistPanel } from "./ChecklistPanel";
53
+ export type { ChecklistItem } from "./ChecklistPanel";
54
+
55
+ // Cursor actor
56
+ export { CursorActor } from "./CursorActor";
57
+ export type { CursorKeyframe } from "./CursorActor";
58
+
59
+ // Repo CTA scene
60
+ export { RepoCta } from "./RepoCta";
package/src/look.tsx ADDED
@@ -0,0 +1,90 @@
1
+ // Global look pass: film grain (animated), vignette, subtle color grade.
2
+ // Always rendered by CinematicScene on top of scene content. Cheap on purpose:
3
+ // grain = static SVG-turbulence tile jittered per frame; vignette/grade = gradient overlays.
4
+
5
+ import React from "react";
6
+ import { useCurrentFrame } from "remotion";
7
+ import { jitter } from "./motion";
8
+
9
+ // 256px noise tile as inline SVG (no binary assets needed)
10
+ const NOISE_TILE =
11
+ "data:image/svg+xml;utf8," +
12
+ encodeURIComponent(
13
+ `<svg xmlns='http://www.w3.org/2000/svg' width='256' height='256'>` +
14
+ `<filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/>` +
15
+ `<feColorMatrix type='saturate' values='0'/></filter>` +
16
+ `<rect width='256' height='256' filter='url(#n)'/></svg>`,
17
+ );
18
+
19
+ export const FilmGrain: React.FC<{ opacity?: number }> = ({
20
+ opacity = 0.05,
21
+ }) => {
22
+ const frame = useCurrentFrame();
23
+ const ox = Math.floor(jitter(frame, 1) * 256);
24
+ const oy = Math.floor(jitter(frame, 2) * 256);
25
+ return (
26
+ <div
27
+ style={{
28
+ position: "absolute",
29
+ inset: 0,
30
+ pointerEvents: "none",
31
+ backgroundImage: `url("${NOISE_TILE}")`,
32
+ backgroundRepeat: "repeat",
33
+ backgroundPosition: `${ox}px ${oy}px`,
34
+ opacity,
35
+ mixBlendMode: "overlay",
36
+ }}
37
+ />
38
+ );
39
+ };
40
+
41
+ export const Vignette: React.FC<{ strength?: number }> = ({
42
+ strength = 0.32,
43
+ }) => (
44
+ <div
45
+ style={{
46
+ position: "absolute",
47
+ inset: 0,
48
+ pointerEvents: "none",
49
+ background: `radial-gradient(ellipse 75% 65% at 50% 48%, transparent 55%, rgba(0,0,0,${strength}) 100%)`,
50
+ }}
51
+ />
52
+ );
53
+
54
+ /** Subtle grade: cool shadows, slight warm lift top-left (warm/cool contrast per STYLE.md). */
55
+ export const ColorGrade: React.FC<{ warmth?: number }> = ({
56
+ warmth = 0.06,
57
+ }) => (
58
+ <>
59
+ <div
60
+ style={{
61
+ position: "absolute",
62
+ inset: 0,
63
+ pointerEvents: "none",
64
+ background: `linear-gradient(135deg, rgba(255,170,90,${warmth}) 0%, transparent 45%)`,
65
+ mixBlendMode: "soft-light",
66
+ }}
67
+ />
68
+ <div
69
+ style={{
70
+ position: "absolute",
71
+ inset: 0,
72
+ pointerEvents: "none",
73
+ background: `linear-gradient(315deg, rgba(60,110,255,${warmth * 1.2}) 0%, transparent 50%)`,
74
+ mixBlendMode: "soft-light",
75
+ }}
76
+ />
77
+ </>
78
+ );
79
+
80
+ export const LookPass: React.FC<{
81
+ grain?: number;
82
+ vignette?: number;
83
+ warmth?: number;
84
+ }> = ({ grain = 0.05, vignette = 0.32, warmth = 0.06 }) => (
85
+ <>
86
+ <ColorGrade warmth={warmth} />
87
+ <FilmGrain opacity={grain} />
88
+ <Vignette strength={vignette} />
89
+ </>
90
+ );
package/src/motion.ts ADDED
@@ -0,0 +1,50 @@
1
+ // Easing + spring presets. Linear easing is banned (STYLE.md). Always pick from here.
2
+
3
+ import { Easing, interpolate, spring } from "remotion";
4
+
5
+ export const E = {
6
+ // default cinematic move: slow-out, soft landing
7
+ cinematic: Easing.bezier(0.33, 0.0, 0.13, 1.0),
8
+ // aggressive whip (cuts, fast pans)
9
+ whip: Easing.bezier(0.8, 0.0, 0.1, 1.0),
10
+ // gentle drift (ambient motion inside a shot)
11
+ drift: Easing.bezier(0.4, 0.0, 0.6, 1.0),
12
+ // snappy UI reaction (button press, blip reveal)
13
+ snap: Easing.bezier(0.2, 0.9, 0.25, 1.0),
14
+ } as const;
15
+
16
+ export const SPRING = {
17
+ soft: { damping: 26, stiffness: 90, mass: 1 },
18
+ pop: { damping: 14, stiffness: 160, mass: 0.8 },
19
+ heavy: { damping: 30, stiffness: 60, mass: 1.4 },
20
+ } as const;
21
+
22
+ /** interpolate with a mandatory named easing and clamped edges. */
23
+ export const tween = (
24
+ frame: number,
25
+ range: [number, number],
26
+ out: [number, number],
27
+ easing: (t: number) => number = E.cinematic,
28
+ ) =>
29
+ interpolate(frame, range, out, {
30
+ easing,
31
+ extrapolateLeft: "clamp",
32
+ extrapolateRight: "clamp",
33
+ });
34
+
35
+ /** spring entrance helper */
36
+ export const pop = (
37
+ frame: number,
38
+ fps: number,
39
+ delay = 0,
40
+ config: { damping: number; stiffness: number; mass: number } = SPRING.pop,
41
+ ) => spring({ frame: frame - delay, fps, config });
42
+
43
+ /** deterministic per-frame pseudo-random (for grain jitter etc.) */
44
+ export const jitter = (frame: number, seed = 1) => {
45
+ const x = Math.sin(frame * 127.1 + seed * 311.7) * 43758.5453;
46
+ return x - Math.floor(x); // 0..1
47
+ };
48
+
49
+ /** stagger helper: frame offset for item i */
50
+ export const staggerDelay = (i: number, step = 3) => i * step;
package/src/sfx.tsx ADDED
@@ -0,0 +1,106 @@
1
+ // SFX sequencer. All sounds live in public/sfx/ (generated by scripts/gen-sfx.sh).
2
+ // Components emit sounds by rendering these helpers at computed frames, audio stays
3
+ // perfectly in sync with visuals because both derive from the same frame math.
4
+
5
+ import React from "react";
6
+ import { Audio, Sequence, staticFile } from "remotion";
7
+ import { jitter } from "./motion";
8
+
9
+ const KEYS = ["key_a.wav", "key_b.wav", "key_c.wav"];
10
+
11
+ export const KeySound: React.FC<{ at: number; seed?: number; volume?: number }> = ({
12
+ at,
13
+ seed = 0,
14
+ volume = 0.16,
15
+ }) => (
16
+ <Sequence from={at} durationInFrames={6}>
17
+ <Audio
18
+ src={staticFile(`sfx/${KEYS[Math.floor(jitter(at, seed) * KEYS.length)]}`)}
19
+ volume={volume}
20
+ />
21
+ </Sequence>
22
+ );
23
+
24
+ /** One KeySound per typed character. charFrames = absolute frame each char appears. */
25
+ export const TypingSfx: React.FC<{ charFrames: number[]; volume?: number }> = ({
26
+ charFrames,
27
+ volume = 0.16,
28
+ }) => (
29
+ <>
30
+ {charFrames.map((f, i) => (
31
+ <KeySound key={i} at={f} seed={i} volume={volume} />
32
+ ))}
33
+ </>
34
+ );
35
+
36
+ export const Click: React.FC<{ at: number; volume?: number }> = ({
37
+ at,
38
+ volume = 0.4,
39
+ }) => (
40
+ <Sequence from={at} durationInFrames={8}>
41
+ <Audio src={staticFile("sfx/click.wav")} volume={volume} />
42
+ </Sequence>
43
+ );
44
+
45
+ export const Whoosh: React.FC<{ at: number; volume?: number }> = ({
46
+ at,
47
+ volume = 0.35,
48
+ }) => (
49
+ <Sequence from={at} durationInFrames={20}>
50
+ <Audio src={staticFile("sfx/whoosh.wav")} volume={volume} />
51
+ </Sequence>
52
+ );
53
+
54
+ export const Blip: React.FC<{ at: number; volume?: number }> = ({
55
+ at,
56
+ volume = 0.3,
57
+ }) => (
58
+ <Sequence from={at} durationInFrames={8}>
59
+ <Audio src={staticFile("sfx/blip.wav")} volume={volume} />
60
+ </Sequence>
61
+ );
62
+
63
+ export const SuccessChime: React.FC<{ at: number; volume?: number }> = ({
64
+ at,
65
+ volume = 0.35,
66
+ }) => (
67
+ <Sequence from={at} durationInFrames={20}>
68
+ <Audio src={staticFile("sfx/success.wav")} volume={volume} />
69
+ </Sequence>
70
+ );
71
+
72
+ /** Struck bell, for a completion moment that wants more weight than a chime. */
73
+ export const Bell: React.FC<{ at: number; volume?: number }> = ({
74
+ at,
75
+ volume = 0.3,
76
+ }) => (
77
+ <Sequence from={at} durationInFrames={48}>
78
+ <Audio src={staticFile("sfx/bell.wav")} volume={volume} />
79
+ </Sequence>
80
+ );
81
+
82
+ /** Enter key. Deeper and heavier than a letter key, for the end of a typed command. */
83
+ export const EnterKey: React.FC<{ at: number; volume?: number }> = ({
84
+ at,
85
+ volume = 0.2,
86
+ }) => (
87
+ <Sequence from={at} durationInFrames={8}>
88
+ <Audio src={staticFile("sfx/key_enter.wav")} volume={volume} />
89
+ </Sequence>
90
+ );
91
+
92
+ /**
93
+ * Ambient bed under the whole composition.
94
+ *
95
+ * Synthesized by scripts/gen-sfx.sh rather than licensed, so a rendered video
96
+ * carries no third-party audio rights. The source is 60s; `durationInFrames` loops
97
+ * it to cover the composition.
98
+ */
99
+ export const MusicBed: React.FC<{ durationInFrames: number; volume?: number }> = ({
100
+ durationInFrames,
101
+ volume = 0.16,
102
+ }) => (
103
+ <Sequence from={0} durationInFrames={durationInFrames}>
104
+ <Audio src={staticFile("sfx/music-bed.mp3")} volume={volume} loop />
105
+ </Sequence>
106
+ );
package/src/tokens.ts ADDED
@@ -0,0 +1,41 @@
1
+ // Design tokens: single source for every scene. Per-video themes may override via themes/.
2
+
3
+ export const color = {
4
+ // dark stage
5
+ bg0: "#0B0E14",
6
+ bg1: "#11151F",
7
+ panel: "#151A24",
8
+ panelBorder: "#232B3A",
9
+ textPrimary: "#E6EAF2",
10
+ textSecondary: "#8A94A6",
11
+ // accents
12
+ accent: "#4E9EFF",
13
+ accentAlt: "#9D6BFF",
14
+ success: "#3ECF8E",
15
+ warn: "#F5A623",
16
+ danger: "#F26D6D",
17
+ // light scenes
18
+ lightBg: "#F4F6F9",
19
+ lightCard: "#FFFFFF",
20
+ lightShadow: "rgba(15,23,42,0.12)",
21
+ } as const;
22
+
23
+ export const font = {
24
+ ui: "Inter",
25
+ mono: "JetBrains Mono",
26
+ } as const;
27
+
28
+ export const glow = (c: string, strength = 1) => ({
29
+ textShadow: `0 0 ${8 * strength}px ${c}88, 0 0 ${24 * strength}px ${c}44`,
30
+ });
31
+
32
+ export const panelShadow = (dark = true) =>
33
+ dark
34
+ ? "0 24px 80px rgba(0,0,0,0.55), 0 4px 16px rgba(0,0,0,0.4)"
35
+ : `0 24px 80px ${color.lightShadow}, 0 4px 16px rgba(15,23,42,0.08)`;
36
+
37
+ export const radius = {
38
+ window: 12,
39
+ card: 10,
40
+ pill: 999,
41
+ } as const;