@devinilabs/reelstack 1.2.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.
Files changed (145) hide show
  1. package/LICENSE +128 -0
  2. package/README.md +125 -0
  3. package/cli/beats.js +124 -0
  4. package/cli/bootstrap.js +124 -0
  5. package/cli/capture.js +34 -0
  6. package/cli/direction.js +114 -0
  7. package/cli/icons.js +49 -0
  8. package/cli/index.js +101 -0
  9. package/cli/init.js +253 -0
  10. package/cli/license.js +168 -0
  11. package/cli/lint.js +865 -0
  12. package/cli/preview.js +59 -0
  13. package/cli/render.js +404 -0
  14. package/cli/scaffold.js +239 -0
  15. package/cli/smoke.js +76 -0
  16. package/cli/update.js +26 -0
  17. package/cli/utils.js +184 -0
  18. package/docs/buyers-guide.md +220 -0
  19. package/docs/design-discipline.md +130 -0
  20. package/docs/family-galleries/dark.md +95 -0
  21. package/docs/family-galleries/forbidden.md +78 -0
  22. package/docs/family-galleries/glass.md +98 -0
  23. package/docs/family-galleries/paper.md +82 -0
  24. package/docs/family-galleries/warm.md +86 -0
  25. package/docs/superpowers/plans/2026-05-09-reelstack-init-readiness-gate.md +1166 -0
  26. package/docs/superpowers/specs/2026-05-09-reelstack-init-readiness-gate-design.md +233 -0
  27. package/families/dark/components/DriftingSpotlights.tsx +59 -0
  28. package/families/dark/components/FilmGrain.tsx +44 -0
  29. package/families/dark/components/ForestCard.tsx +43 -0
  30. package/families/dark/components/GridBackground.tsx +29 -0
  31. package/families/dark/components/RadialVignette.tsx +21 -0
  32. package/families/dark/components/Scanlines.tsx +35 -0
  33. package/families/dark/components/SegmentOpacity.ts +37 -0
  34. package/families/dark/components/index.ts +13 -0
  35. package/families/dark/index.ts +31 -0
  36. package/families/dark/palette.ts +98 -0
  37. package/families/dark/presets/claudedispatch.ts +46 -0
  38. package/families/dark/presets/codedrop.ts +37 -0
  39. package/families/dark/presets/gpt55.ts +54 -0
  40. package/families/dark/presets/notebooklm.ts +50 -0
  41. package/families/dark/presets/resourcescta.ts +35 -0
  42. package/families/dark/presets/skills.ts +40 -0
  43. package/families/dark/presets/stitch.ts +46 -0
  44. package/families/dark/presets/stitch2.ts +43 -0
  45. package/families/dark/typography.ts +16 -0
  46. package/families/forbidden/components/ForbiddenCausticBlobs.tsx +52 -0
  47. package/families/forbidden/components/NewsprintTexture.tsx +28 -0
  48. package/families/forbidden/components/TintedShadow.tsx +36 -0
  49. package/families/forbidden/components/index.ts +38 -0
  50. package/families/forbidden/index.ts +17 -0
  51. package/families/forbidden/palette.ts +88 -0
  52. package/families/forbidden/presets/heretic.ts +44 -0
  53. package/families/forbidden/typography.ts +18 -0
  54. package/families/glass/components/BreakdownCard.tsx +158 -0
  55. package/families/glass/components/CausticBlobs.tsx +49 -0
  56. package/families/glass/components/Counter.tsx +72 -0
  57. package/families/glass/components/EyebrowPill.tsx +59 -0
  58. package/families/glass/components/FilmStrip.tsx +202 -0
  59. package/families/glass/components/FloatingGlyphs.tsx +78 -0
  60. package/families/glass/components/GlassCard.tsx +58 -0
  61. package/families/glass/components/GlassCardBezel.tsx +45 -0
  62. package/families/glass/components/HairlineGrid.tsx +30 -0
  63. package/families/glass/components/IridescentRing.tsx +114 -0
  64. package/families/glass/components/IridescentText.tsx +98 -0
  65. package/families/glass/components/LightBeam.tsx +46 -0
  66. package/families/glass/components/ParticleBurst.tsx +62 -0
  67. package/families/glass/components/SonarRings.tsx +81 -0
  68. package/families/glass/components/StaggeredWords.tsx +74 -0
  69. package/families/glass/components/index.ts +20 -0
  70. package/families/glass/index.ts +31 -0
  71. package/families/glass/palette.ts +93 -0
  72. package/families/glass/presets/claudewatch.ts +64 -0
  73. package/families/glass/presets/claudewatchcta.ts +43 -0
  74. package/families/glass/presets/graphify.ts +45 -0
  75. package/families/glass/presets/gstack.ts +48 -0
  76. package/families/glass/presets/jcode.ts +50 -0
  77. package/families/glass/presets/lilagents.ts +52 -0
  78. package/families/glass/presets/paperclip.ts +43 -0
  79. package/families/glass/typography.ts +15 -0
  80. package/families/index.ts +49 -0
  81. package/families/paper/components/CardSpring.tsx +42 -0
  82. package/families/paper/components/CreamGrid.tsx +26 -0
  83. package/families/paper/components/EditorialSerifText.tsx +51 -0
  84. package/families/paper/components/GreenAccentCard.tsx +10 -0
  85. package/families/paper/components/PaperShadow.tsx +30 -0
  86. package/families/paper/components/ScaleBlurText.tsx +40 -0
  87. package/families/paper/components/index.ts +11 -0
  88. package/families/paper/index.ts +23 -0
  89. package/families/paper/palette.ts +102 -0
  90. package/families/paper/presets/designreel.ts +32 -0
  91. package/families/paper/presets/devini3d.ts +45 -0
  92. package/families/paper/presets/justdrop.ts +39 -0
  93. package/families/paper/presets/opus.ts +48 -0
  94. package/families/paper/typography.ts +17 -0
  95. package/families/warm/components/AccentGlow.tsx +60 -0
  96. package/families/warm/components/BentoCell.tsx +56 -0
  97. package/families/warm/components/BentoGrid.tsx +30 -0
  98. package/families/warm/components/FilmGrain.tsx +36 -0
  99. package/families/warm/components/ScaleBlurCounter.tsx +71 -0
  100. package/families/warm/components/WarmSurface.tsx +35 -0
  101. package/families/warm/components/index.ts +11 -0
  102. package/families/warm/index.ts +19 -0
  103. package/families/warm/palette.ts +81 -0
  104. package/families/warm/presets/huashu.ts +49 -0
  105. package/families/warm/presets/mempalace.ts +51 -0
  106. package/families/warm/typography.ts +17 -0
  107. package/package.json +85 -0
  108. package/reference/dark/claudedispatch.tsx +2441 -0
  109. package/reference/dark/notebooklm.tsx +2316 -0
  110. package/reference/dark/stitch.tsx +3040 -0
  111. package/reference/forbidden/heretic.tsx +2636 -0
  112. package/reference/glass/claudewatch.tsx +3827 -0
  113. package/reference/glass/graphify.tsx +2418 -0
  114. package/reference/glass/paperclip.tsx +2218 -0
  115. package/reference/paper/designreel.tsx +883 -0
  116. package/reference/paper/justdrop.tsx +1898 -0
  117. package/reference/paper/opus.tsx +1770 -0
  118. package/reference/warm/huashu.tsx +3413 -0
  119. package/reference/warm/mempalace.tsx +2909 -0
  120. package/skill/SKILL.md +229 -0
  121. package/skill/commands/reelstack-beats.md +20 -0
  122. package/skill/commands/reelstack-capture.md +24 -0
  123. package/skill/commands/reelstack-critique.md +15 -0
  124. package/skill/commands/reelstack-dark.md +40 -0
  125. package/skill/commands/reelstack-direction.md +17 -0
  126. package/skill/commands/reelstack-forbidden.md +25 -0
  127. package/skill/commands/reelstack-glass.md +39 -0
  128. package/skill/commands/reelstack-icons.md +22 -0
  129. package/skill/commands/reelstack-init.md +17 -0
  130. package/skill/commands/reelstack-lint.md +22 -0
  131. package/skill/commands/reelstack-paper.md +36 -0
  132. package/skill/commands/reelstack-render.md +20 -0
  133. package/skill/commands/reelstack-warm.md +36 -0
  134. package/templates/dark/template.tsx +115 -0
  135. package/templates/forbidden/template.tsx +111 -0
  136. package/templates/glass/template.tsx +201 -0
  137. package/templates/paper/template.tsx +133 -0
  138. package/templates/warm/template.tsx +210 -0
  139. package/utils/ai-purple-blocklist.ts +13 -0
  140. package/utils/banned-fonts.ts +11 -0
  141. package/utils/cubic-bezier.ts +36 -0
  142. package/utils/easing.ts +84 -0
  143. package/utils/grid.ts +13 -0
  144. package/utils/render-presets.json +56 -0
  145. package/utils/safe-zones.tsx +57 -0
@@ -0,0 +1,233 @@
1
+ # ReelStack `init` — readiness gate design
2
+
3
+ **Date:** 2026-05-09
4
+ **Author:** Abhishek Raj (devinilabs)
5
+ **Status:** Draft, ready for implementation planning
6
+
7
+ ## Problem
8
+
9
+ The current `reelstack init` ([cli/init.js](../../../cli/init.js)) checks system dependencies (Node 20+, ffmpeg, whisper-cpp) but only `warn()`s on missing ones. It then prints "ReelStack is ready" regardless of whether those deps exist or whether the buyer is in a Remotion project.
10
+
11
+ The result is a paid-product trap: a buyer can complete `init`, see the success banner, run `/reelstack-render` minutes later, and watch it fail because ffmpeg isn't installed or there's no Remotion project to render against. The first impression of a $X9 product becomes "broken".
12
+
13
+ There are **four** distinct gaps that cause `/reelstack-*` commands to fail at runtime — not just "Remotion is missing" as initially framed:
14
+
15
+ | # | Gap | Breaks |
16
+ |---|---|---|
17
+ | 1 | No Remotion project (Root.tsx, configs, deps) in cwd | `npm run dev`, every scaffolded reel |
18
+ | 2 | `@devinilabs/reelstack` not in project deps | Scaffolded reels' `import` lines |
19
+ | 3 | System `ffmpeg` not in PATH | `/reelstack-beats`, `/reelstack-render` (BGM mix + GIF), `/reelstack-capture` |
20
+ | 4 | `whisper-cli` / `whisper-cpp` not in PATH | `/reelstack-beats` (audio-locked BEAT extraction) |
21
+
22
+ Note on #3: even though Remotion v4 ships its own ffmpeg internally for *its* render pipeline, ReelStack's CLI ([cli/render.js:188-225](../../../cli/render.js), [cli/beats.js:72](../../../cli/beats.js)) shells out to **system** ffmpeg via `spawnSync("ffmpeg", ...)`. Remotion's bundled binary doesn't help us. Note also that `npx remotion install` (which historically pre-downloaded ffmpeg) **was removed in v4.0.0** and must not be called.
23
+
24
+ ## Goal
25
+
26
+ Make `reelstack init`'s success banner provable: after `init` exits 0, every `/reelstack-*` command works.
27
+
28
+ ## Non-goals
29
+
30
+ - Auto-installing Node. Version managers vary too widely (nvm / fnm / Volta / asdf / system). Hard-fail with a guide link.
31
+ - Auto-installing system binaries on Linux or Windows. Too many package managers, sudo prompts, and edge cases. Print + exit.
32
+ - Calling `npx remotion install`. Removed in Remotion v4.0.0.
33
+ - Doing anything silently. Every install gets a `[Y/n]` prompt.
34
+
35
+ ## Design
36
+
37
+ A 4-tier readiness gate replaces the current warn-and-continue flow. Each tier is a hard precondition for the next; the success banner only prints if all four pass.
38
+
39
+ ### Top-level flow
40
+
41
+ ```
42
+ 1. license.verifyOrPrompt() (unchanged)
43
+ 2. Tier 1 — Node version hard gate
44
+ 3. Tier 2 — System binaries (ffmpeg, whisper-cli)
45
+ 4. Install ~/.reelstack runtime (unchanged: families, templates, utils, docs, cli, reference, package.json)
46
+ 5. Install ~/.claude/skills/reelstack (unchanged)
47
+ 6. Install ~/.claude/commands/* (unchanged)
48
+ 7. Tier 3 — Remotion project bootstrap (or detect existing)
49
+ 8. Tier 4 — Smoke test
50
+ 9. Success banner
51
+ ```
52
+
53
+ Steps 4-6 (the runtime + skill + commands installs from current [cli/init.js:88-117](../../../cli/init.js)) are **preserved unchanged**. The new tiers wrap around them, not replace them.
54
+
55
+ ### Tier 1 — Hard system gate
56
+
57
+ | Check | On fail |
58
+ |---|---|
59
+ | Node ≥ 20 | Print install URL (https://nodejs.org/, link to `nvm` / `fnm` install). Exit 1. |
60
+
61
+ Already detected today; just elevate `warn()` → `fail()` + exit.
62
+
63
+ ### Tier 2 — System binaries (ffmpeg required, whisper-cli soft)
64
+
65
+ Detection uses the existing `which()` helper.
66
+
67
+ ```
68
+ ffmpeg missing?
69
+ ├── platform === "darwin" && which("brew")
70
+ │ → prompt: "Run `brew install ffmpeg`? [Y/n]"
71
+ │ ├── yes → spawnSync("brew", ["install", "ffmpeg"], { stdio: "inherit" })
72
+ │ │ re-check via which("ffmpeg"); exit 1 if still missing
73
+ │ └── no → exit 1 (REQUIRED — /reelstack-render won't work)
74
+ ├── platform === "darwin" && no brew
75
+ │ → print: "Install Homebrew (https://brew.sh), then `brew install ffmpeg`"
76
+ │ exit 1
77
+ ├── platform === "linux"
78
+ │ → print: "Install via your package manager:
79
+ │ apt-get install ffmpeg # Debian/Ubuntu
80
+ │ dnf install ffmpeg # Fedora
81
+ │ pacman -S ffmpeg # Arch"
82
+ │ exit 1
83
+ └── platform === "win32"
84
+ → print: "Install via:
85
+ choco install ffmpeg # Chocolatey
86
+ scoop install ffmpeg # Scoop"
87
+ exit 1
88
+ ```
89
+
90
+ `whisper-cli` follows the **same flow** but is **soft**: declining doesn't exit, it just disables `/reelstack-beats` (a flag is set in `~/.reelstack/state.json` so other commands can detect it).
91
+
92
+ ### Tier 3 — Remotion project bootstrap
93
+
94
+ ```
95
+ detect cwd state:
96
+ • cwd has package.json with `remotion` dep
97
+ → already-Remotion path (current behavior at init.js:122-157)
98
+ • cwd has package.json without `remotion`
99
+ → warn: "This folder has a non-Remotion package.json. Bootstrap into a subdirectory instead."
100
+ offer: "Scaffold ./reelstack-project/? [Y/n]"
101
+ • cwd has no package.json
102
+ → offer: "Scaffold a Remotion project at ./reelstack-project/? [Y/n]"
103
+
104
+ if user accepts:
105
+ spawnSync("npx", ["create-video@latest", "--yes", "--blank", projectName],
106
+ { stdio: "inherit", cwd: process.cwd() })
107
+ process.chdir(path.join(process.cwd(), projectName))
108
+ spawnSync("npm", ["install", "@devinilabs/reelstack"],
109
+ { stdio: "inherit", cwd: process.cwd() })
110
+
111
+ if user declines:
112
+ print: "Re-run `reelstack init` from inside a Remotion project to wire up imports."
113
+ exit 0 (not a failure — buyer chose manual path)
114
+ ```
115
+
116
+ `projectName` defaults to `reelstack-project`; `init --name=<dir>` overrides.
117
+
118
+ The `--yes --blank` flags make `create-video` non-interactive so we control the flow. We use `--blank` (not a template) to keep the bootstrap minimal — the buyer's reels come from `/reelstack-*` scaffolders, not Remotion's defaults.
119
+
120
+ ### Tier 4 — Smoke test
121
+
122
+ Runs only after Tiers 1-3 pass. Three fast, non-destructive checks (~5 s total) inside the working Remotion project directory:
123
+
124
+ 1. `reelstack scaffold --family=glass --preset=graphify --name=Demo` — proves the scaffolder produces valid TS that registers a composition.
125
+ 2. `npx remotion compositions` — proves Remotion's CLI loads the project and finds `Demo`. Catches "configs are wrong" and "imports don't resolve".
126
+ 3. `ffmpeg -version` — final sanity check that the binary is executable, not just on disk.
127
+
128
+ If any step fails, the banner is `ReelStack is NOT ready — smoke test failed at step N` with the failing command's output preserved. Exit 1.
129
+
130
+ `init --skip-smoke` bypasses Tier 4 for advanced buyers (e.g. CI, re-runs).
131
+
132
+ ## CLI surface
133
+
134
+ ```
135
+ reelstack init # full flow
136
+ reelstack init --name=my-reels # custom subdirectory name for Tier 3
137
+ reelstack init --skip-smoke # skip Tier 4 (still prints "ready")
138
+ reelstack init --no-bootstrap # skip Tier 3 entirely
139
+ # if no Remotion project: print manual-path
140
+ # guidance and exit 0
141
+ # if Remotion project exists: behave normally
142
+ ```
143
+
144
+ ### Flag semantics
145
+
146
+ - **`--skip-smoke`**: success banner still prints. Buyer is opting out of the integration check (e.g. CI re-runs, advanced users). State.json records `smokeTest: "skipped"`.
147
+ - **`--no-bootstrap`**: when no Remotion project is detected, init does NOT run `create-video`. Prints "Re-run from inside a Remotion project" and exits 0 (success — buyer chose manual path). Tier 4 also skipped because there's nothing to smoke-test.
148
+
149
+ ## State persistence
150
+
151
+ A new file `~/.reelstack/state.json` records:
152
+
153
+ ```json
154
+ {
155
+ "lastInitAt": "2026-05-09T14:23:00.000Z",
156
+ "ffmpeg": "ok",
157
+ "whisperCli": "missing-declined",
158
+ "smokeTest": "passed"
159
+ }
160
+ ```
161
+
162
+ Used by:
163
+ - `/reelstack-beats` to fail fast with "whisper-cli was declined during init; run `brew install whisper-cpp` and re-run `reelstack init`".
164
+ - `reelstack init` re-runs to skip already-passed checks if `--fast` is passed.
165
+
166
+ ## Buyer experience (target output)
167
+
168
+ ```
169
+ $ npx @devinilabs/reelstack init
170
+
171
+ ReelStack v1.2
172
+ Welcome. Let's get ReelStack set up.
173
+
174
+ ✓ License verified (jane@devini.io)
175
+
176
+ Checking dependencies…
177
+ ✓ Node v22.5.0
178
+ ✗ ffmpeg not found
179
+ Run `brew install ffmpeg`? [Y/n] y
180
+ [brew output…]
181
+ ✓ ffmpeg installed
182
+ ✓ whisper-cli found
183
+
184
+ No Remotion project in this directory.
185
+ Scaffold one at ./reelstack-project/? [Y/n] y
186
+ [create-video output…]
187
+ ✓ Remotion project ready
188
+ ✓ @devinilabs/reelstack installed
189
+
190
+ Smoke test…
191
+ ✓ Demo.tsx scaffolded
192
+ ✓ Remotion CLI loads the Demo composition
193
+ ✓ ffmpeg executes
194
+
195
+ ReelStack is ready.
196
+
197
+ Next:
198
+ cd reelstack-project
199
+ npm run dev # open Remotion Studio
200
+ /reelstack-glass # scaffold a Glass Iridescent reel
201
+ ```
202
+
203
+ Every line is provable, not warned-and-prayed.
204
+
205
+ ## File-level changes
206
+
207
+ | File | Change |
208
+ |---|---|
209
+ | [cli/init.js](../../../cli/init.js) | Restructure into `runTier1()` … `runTier4()`. Replace warn-only checks with hard fails. Add bootstrap and smoke logic. |
210
+ | [cli/utils.js](../../../cli/utils.js) | Add `platformPackageInstaller()` (returns `{ name, command }` per OS), `loadState()` / `saveState()` for `~/.reelstack/state.json`. |
211
+ | `cli/bootstrap.js` (new) | Encapsulates Tier 3: detect cwd state, prompt, run `create-video`, install `@devinilabs/reelstack`. |
212
+ | `cli/smoke.js` (new) | Encapsulates Tier 4: scaffold Demo, run `remotion compositions`, run `ffmpeg -version`. |
213
+ | [cli/beats.js](../../../cli/beats.js) | Read state.json; fail fast with the "declined during init" message if `whisperCli === "missing-declined"`. |
214
+
215
+ No changes to scaffolders, families, or templates. This is an init-layer change.
216
+
217
+ ## Testing
218
+
219
+ - **Unit:** mock `which()`, `spawnSync()`, and `process.platform`; assert each tier's branches.
220
+ - **Integration:** docker images per platform target — `darwin-with-brew`, `darwin-without-brew`, `linux-no-deps`, `windows-no-deps` — run `init` end-to-end and assert exit codes + state.json contents.
221
+ - **Manual:** the dev's own machine, in three states: (a) clean cwd, (b) existing non-Remotion project, (c) existing Remotion project. Smoke test must produce a previewable Demo in case (a).
222
+
223
+ ## Open questions
224
+
225
+ None. Buyer experience and Tier-2 strategy were settled in the brainstorming session on 2026-05-09.
226
+
227
+ ## Related
228
+
229
+ - [cli/init.js](../../../cli/init.js) — current init flow
230
+ - [cli/render.js](../../../cli/render.js) — direct ffmpeg consumer
231
+ - [cli/beats.js](../../../cli/beats.js) — direct ffmpeg + whisper-cli consumer
232
+ - Remotion getting-started: `npx create-video@latest --yes --blank <name>`
233
+ - Removed: `npx remotion install` (Remotion v4.0.0)
@@ -0,0 +1,59 @@
1
+ import React from "react";
2
+ import { AbsoluteFill, useCurrentFrame } from "remotion";
3
+ import { palette, spotlightDrift } from "../palette";
4
+
5
+ /**
6
+ * DriftingSpotlights — two overlapping radial-gradient blobs drifting on
7
+ * sine/cosine paths.
8
+ *
9
+ * The Dark Cinematic family's signature background motion. Mood-lights the
10
+ * void without ever resolving — the eye chases them and the scene reads as
11
+ * "premium ad-film, late-night, cinematic."
12
+ *
13
+ * Drift rates come from `spotlightDrift` in the palette (xRate = 0.004,
14
+ * yRate = 0.003). Mix-blend-mode "screen" + 60px blur means the blobs add
15
+ * light without darkening the underlying scene.
16
+ *
17
+ * Frame-budget: cheap. Two absolutely-positioned divs with CSS gradients.
18
+ */
19
+ export const DriftingSpotlights: React.FC<{
20
+ reduceMotion?: boolean;
21
+ }> = ({ reduceMotion = false }) => {
22
+ const frame = useCurrentFrame();
23
+ const cx1 = reduceMotion ? 50 : 50 + 35 * Math.sin(frame * spotlightDrift.xRate);
24
+ const cy1 = reduceMotion ? 30 : 30 + 20 * Math.cos(frame * spotlightDrift.yRate);
25
+ const cx2 = reduceMotion ? 50 : 50 + 30 * Math.cos(frame * spotlightDrift.xRate * 0.7);
26
+ const cy2 = reduceMotion ? 60 : 60 + 25 * Math.sin(frame * spotlightDrift.yRate * 0.6);
27
+ return (
28
+ <AbsoluteFill style={{ pointerEvents: "none" }}>
29
+ <div
30
+ style={{
31
+ position: "absolute",
32
+ left: `${cx1}%`,
33
+ top: `${cy1}%`,
34
+ width: 1200,
35
+ height: 1200,
36
+ marginLeft: -600,
37
+ marginTop: -600,
38
+ background: `radial-gradient(circle, ${palette.claude}33 0%, transparent 60%)`,
39
+ filter: "blur(60px)",
40
+ mixBlendMode: "screen",
41
+ }}
42
+ />
43
+ <div
44
+ style={{
45
+ position: "absolute",
46
+ left: `${cx2}%`,
47
+ top: `${cy2}%`,
48
+ width: 1000,
49
+ height: 1000,
50
+ marginLeft: -500,
51
+ marginTop: -500,
52
+ background: `radial-gradient(circle, ${palette.violet}28 0%, transparent 60%)`,
53
+ filter: "blur(60px)",
54
+ mixBlendMode: "screen",
55
+ }}
56
+ />
57
+ </AbsoluteFill>
58
+ );
59
+ };
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+ import { AbsoluteFill } from "remotion";
3
+
4
+ /**
5
+ * FilmGrain — SVG turbulence noise overlay at 6% opacity, multiply blend.
6
+ *
7
+ * Adds analog texture to the otherwise-perfect zinc void. Used by the
8
+ * notebooklm preset and any "textured" Dark Cinematic scene where the bg
9
+ * feels too clean. SVG `feTurbulence` is GPU-rasterized once and stamped
10
+ * across the frame, so cost is effectively flat regardless of duration.
11
+ *
12
+ * Mix-blend "multiply" so the grain only darkens — it never blows out
13
+ * highlights on the spotlights.
14
+ */
15
+ export const FilmGrain: React.FC<{
16
+ opacity?: number;
17
+ baseFrequency?: number;
18
+ reduceMotion?: boolean;
19
+ }> = ({ opacity = 0.06, baseFrequency = 0.9, reduceMotion = false }) => {
20
+ // The SVG itself is static (no frame-based animation), but reduceMotion
21
+ // dampens grain visibility for low-motion contexts.
22
+ const finalOpacity = reduceMotion ? opacity * 0.5 : opacity;
23
+ const svg = `
24
+ <svg xmlns="http://www.w3.org/2000/svg" width="300" height="300">
25
+ <filter id="n">
26
+ <feTurbulence type="fractalNoise" baseFrequency="${baseFrequency}" numOctaves="2" stitchTiles="stitch"/>
27
+ <feColorMatrix type="saturate" values="0"/>
28
+ </filter>
29
+ <rect width="100%" height="100%" filter="url(#n)"/>
30
+ </svg>
31
+ `;
32
+ const dataUri = `url("data:image/svg+xml;utf8,${encodeURIComponent(svg)}")`;
33
+ return (
34
+ <AbsoluteFill
35
+ style={{
36
+ backgroundImage: dataUri,
37
+ backgroundRepeat: "repeat",
38
+ opacity: finalOpacity,
39
+ mixBlendMode: "multiply",
40
+ pointerEvents: "none",
41
+ }}
42
+ />
43
+ );
44
+ };
@@ -0,0 +1,43 @@
1
+ import React, { CSSProperties, ReactNode } from "react";
2
+ import { palette } from "../palette";
3
+
4
+ /**
5
+ * ForestCard — dark forest-green card surface (#1a2e1f).
6
+ *
7
+ * The Dark Cinematic family's signature embedded UI frame. Used by
8
+ * claudedispatch and gpt55 presets to wrap UI screenshots, terminal blocks,
9
+ * and benchmark cards so they sit in the scene as physical surfaces rather
10
+ * than floating overlays.
11
+ *
12
+ * Variants:
13
+ * - "default" — palette.cardForest (#1a2e1f)
14
+ * - "lifted" — palette.cardForestLift (#243a28)
15
+ *
16
+ * Multi-layer shadow stack reads as a real card on a dark stage.
17
+ */
18
+ export const ForestCard: React.FC<{
19
+ variant?: "default" | "lifted";
20
+ style?: CSSProperties;
21
+ children?: ReactNode;
22
+ }> = ({ variant = "default", style, children }) => {
23
+ const bg = variant === "lifted" ? palette.cardForestLift : palette.cardForest;
24
+ return (
25
+ <div
26
+ style={{
27
+ background: bg,
28
+ color: palette.fg,
29
+ borderRadius: 24,
30
+ padding: 36,
31
+ boxShadow: [
32
+ "0 1px 0 rgba(255,255,255,0.04) inset",
33
+ "0 -1px 0 rgba(0,0,0,0.4) inset",
34
+ "0 12px 28px rgba(0,0,0,0.35)",
35
+ "0 32px 64px rgba(0,0,0,0.55)",
36
+ ].join(", "),
37
+ ...style,
38
+ }}
39
+ >
40
+ {children}
41
+ </div>
42
+ );
43
+ };
@@ -0,0 +1,29 @@
1
+ import React from "react";
2
+ import { AbsoluteFill } from "remotion";
3
+
4
+ /**
5
+ * GridBackground — 60×60 px grid lines at 4% white opacity, masked to fade
6
+ * at the edges.
7
+ *
8
+ * Provides "environment" — a faint architectural plane that anchors floating
9
+ * cards and text. Always sits between DriftingSpotlights and the foreground.
10
+ *
11
+ * Pure CSS (background-image with two linear gradients + radial mask). Zero
12
+ * per-frame cost.
13
+ */
14
+ export const GridBackground: React.FC = () => (
15
+ <AbsoluteFill
16
+ style={{
17
+ backgroundImage: `
18
+ linear-gradient(to right, rgba(255,255,255,0.04) 1px, transparent 1px),
19
+ linear-gradient(to bottom, rgba(255,255,255,0.04) 1px, transparent 1px)
20
+ `,
21
+ backgroundSize: "60px 60px",
22
+ WebkitMaskImage:
23
+ "radial-gradient(ellipse at center, rgba(0,0,0,1) 30%, rgba(0,0,0,0) 75%)",
24
+ maskImage:
25
+ "radial-gradient(ellipse at center, rgba(0,0,0,1) 30%, rgba(0,0,0,0) 75%)",
26
+ pointerEvents: "none",
27
+ }}
28
+ />
29
+ );
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import { AbsoluteFill } from "remotion";
3
+
4
+ /**
5
+ * RadialVignette — edge-darkening radial gradient.
6
+ *
7
+ * Pure CSS, zero per-frame work. Sits on top of the spotlights and grid to
8
+ * pull focus toward the center of the 1080×1920 frame. Reads as cinema-grade
9
+ * lighting falloff; without it the corners feel "open" and amateur.
10
+ *
11
+ * `pointer-events: none` so it never intercepts overlay interactions.
12
+ */
13
+ export const RadialVignette: React.FC = () => (
14
+ <AbsoluteFill
15
+ style={{
16
+ background:
17
+ "radial-gradient(ellipse at center, rgba(0,0,0,0) 50%, rgba(0,0,0,0.55) 100%)",
18
+ pointerEvents: "none",
19
+ }}
20
+ />
21
+ );
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+ import { AbsoluteFill, useCurrentFrame } from "remotion";
3
+
4
+ /**
5
+ * Brutalist Tactical CRT scanline overlay — opt-in for Dark Cinematic reels
6
+ * that want a "declassified telemetry" feel. Uses repeating-linear-gradient
7
+ * to render scanlines + a slow vertical scroll for tube-warmup motion.
8
+ *
9
+ * Inspired by leonxlnx/taste-skill-brutalist's Tactical Telemetry mode.
10
+ */
11
+ export const Scanlines: React.FC<{
12
+ spacing?: number; // px between scanlines
13
+ opacity?: number; // overall opacity 0..1
14
+ scrollSpeed?: number; // px/frame
15
+ reduceMotion?: boolean;
16
+ }> = ({ spacing = 4, opacity = 0.06, scrollSpeed = 0.5, reduceMotion = false }) => {
17
+ const frame = useCurrentFrame();
18
+ const offset = reduceMotion ? 0 : (frame * scrollSpeed) % spacing;
19
+ return (
20
+ <AbsoluteFill
21
+ style={{
22
+ backgroundImage: `repeating-linear-gradient(
23
+ 0deg,
24
+ rgba(255,255,255,${opacity}),
25
+ rgba(255,255,255,${opacity}) 1px,
26
+ transparent 1px,
27
+ transparent ${spacing}px
28
+ )`,
29
+ backgroundPosition: `0 ${offset}px`,
30
+ mixBlendMode: "screen",
31
+ pointerEvents: "none",
32
+ }}
33
+ />
34
+ );
35
+ };
@@ -0,0 +1,37 @@
1
+ import { interpolate } from "remotion";
2
+ import { crossfadeTimings } from "../palette";
3
+
4
+ /**
5
+ * SegmentOpacity (`so`) — fades a scene in/out within a [startSec, endSec]
6
+ * range.
7
+ *
8
+ * The Dark Cinematic family's signature scene-transition mechanic. Every
9
+ * preset wraps its scenes in
10
+ *
11
+ * <div style={{ opacity: so(frame, fps, ...SEGS.body) }}>…</div>
12
+ *
13
+ * to crossfade between hook / body / anchor / cta beats without scene cuts.
14
+ *
15
+ * Defaults (`fadeInSec` = 0.45, `fadeOutSec` = 0.55) come from
16
+ * `palette.crossfadeTimings` so the whole family stays in sync. Override per
17
+ * call when a scene needs a faster snap or a longer dissolve.
18
+ *
19
+ * NOT a component — a pure helper. Lives in /components/ alongside the
20
+ * primitives because every template imports it from the same barrel.
21
+ */
22
+ export const so = (
23
+ frame: number,
24
+ fps: number,
25
+ startSec: number,
26
+ endSec: number,
27
+ fadeInSec: number = crossfadeTimings.fadeInSec,
28
+ fadeOutSec: number = crossfadeTimings.fadeOutSec,
29
+ ): number => {
30
+ const sec = frame / fps;
31
+ return interpolate(
32
+ sec,
33
+ [startSec, startSec + fadeInSec, endSec - fadeOutSec, endSec],
34
+ [0, 1, 1, 0],
35
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" },
36
+ );
37
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Dark Cinematic — primitive components index.
3
+ * Scaffold templates import from here:
4
+ * import { DriftingSpotlights, RadialVignette, GridBackground, ForestCard, FilmGrain, so }
5
+ * from "@devinilabs/reelstack/families/dark/components";
6
+ */
7
+ export { DriftingSpotlights } from "./DriftingSpotlights";
8
+ export { RadialVignette } from "./RadialVignette";
9
+ export { GridBackground } from "./GridBackground";
10
+ export { ForestCard } from "./ForestCard";
11
+ export { FilmGrain } from "./FilmGrain";
12
+ export { so } from "./SegmentOpacity";
13
+ export { Scanlines } from "./Scanlines";
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Dark Cinematic — family index.
3
+ */
4
+ export { palette, safeZones, crossfadeTimings, spotlightDrift } from "./palette";
5
+ export type { DarkPalette } from "./palette";
6
+
7
+ import { preset as stitch } from "./presets/stitch";
8
+ import { preset as stitch2 } from "./presets/stitch2";
9
+ import { preset as codedrop } from "./presets/codedrop";
10
+ import { preset as claudedispatch } from "./presets/claudedispatch";
11
+ import { preset as notebooklm } from "./presets/notebooklm";
12
+ import { preset as gpt55 } from "./presets/gpt55";
13
+ import { preset as resourcescta } from "./presets/resourcescta";
14
+ import { preset as skills } from "./presets/skills";
15
+
16
+ export const presets = {
17
+ stitch,
18
+ stitch2,
19
+ codedrop,
20
+ claudedispatch,
21
+ notebooklm,
22
+ gpt55,
23
+ resourcescta,
24
+ skills,
25
+ } as const;
26
+
27
+ export const family = {
28
+ name: "dark",
29
+ label: "Dark Cinematic",
30
+ presets: Object.keys(presets) as Array<keyof typeof presets>,
31
+ } as const;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * ReelStack — Dark Cinematic palette
3
+ *
4
+ * Verified against /Users/abhishekraj/my-video/src/StitchReel.tsx,
5
+ * ClaudeDispatchReel.tsx, CodeDropReel.tsx, NotebookLMReel.tsx.
6
+ *
7
+ * Mood: premium ad-film, late-night, mood-lit cards.
8
+ *
9
+ * Multi-brand accents are ALLOWED in this family — Stitch rose, Gemini blue,
10
+ * NotebookLM blue can all appear in the same reel as Claude terracotta.
11
+ * The single-accent rule does NOT apply here.
12
+ */
13
+ export const palette = {
14
+ // Backgrounds — zinc void
15
+ bg: "#0a0a0b",
16
+ bgLift: "#141416",
17
+ surface: "#1a1a1d",
18
+ surfaceLift: "#222226",
19
+
20
+ // Dark forest green card surface (for agent / benchmark / terminal cards)
21
+ cardForest: "#1a2e1f",
22
+ cardForestLift: "#243a28",
23
+
24
+ // Foreground
25
+ fg: "#f5f5f7", // contrast 18.7:1 on bg #0a0a0b — WCAG AAA
26
+ fgSoft: "#d1d1d6", // contrast 13.4:1 on bg #0a0a0b — WCAG AAA
27
+ fgMuted: "#8e8e93", // contrast 6.4:1 on bg #0a0a0b — WCAG AA
28
+ fgDim: "#5a5a60", // contrast 3.1:1 on bg #0a0a0b — WCAG AA-Large
29
+
30
+ // Brand accents
31
+ claude: "#D4663A",
32
+ claudeSoft: "#e07a54",
33
+ stitch: "#ff4d9b",
34
+ stitchSoft: "#ff7fb8",
35
+ gemini: "#4285f4",
36
+ notebookBlue: "#4F7DF3",
37
+
38
+ // Semantic
39
+ safe: "#4fc46a",
40
+ danger: "#e25822",
41
+ amber: "#fcbb00",
42
+ violet: "#8d54ff",
43
+
44
+ // Terminal text
45
+ terminalGreen: "#5be8a0",
46
+
47
+ // Macro-window dots
48
+ macClose: "#ff5f57",
49
+ macMin: "#ffbd2e",
50
+ macMax: "#28ca42",
51
+ } as const;
52
+
53
+ export const safeZones = {
54
+ top: 290,
55
+ bottom: 1500,
56
+ canvas: { width: 1080, height: 1920 },
57
+ } as const;
58
+
59
+ /**
60
+ * Crossfade timings used by every Dark Cinematic preset. SegmentOpacity (`so()`
61
+ * helper) reads these as defaults.
62
+ */
63
+ export const crossfadeTimings = {
64
+ fadeInSec: 0.45,
65
+ fadeOutSec: 0.55,
66
+ } as const;
67
+
68
+ /**
69
+ * Drift-velocity defaults for DriftingSpotlights — the family's signature
70
+ * background motion. Two or three spotlights drift at these rates on
71
+ * sine/cosine paths.
72
+ */
73
+ export const spotlightDrift = {
74
+ xRate: 0.004,
75
+ yRate: 0.003,
76
+ } as const;
77
+
78
+ /**
79
+ * NOTE: Dark Cinematic intentionally does NOT export an ALLOWED_ACCENTS list.
80
+ * Multi-brand accents are part of this family's identity — Stitch rose, Gemini
81
+ * blue, NotebookLM blue, and Claude terracotta can all appear in the same reel
82
+ * without lint flagging them as "off-palette". The lint command treats Dark as
83
+ * the multi-brand exception; every other family gets a strict accent allowlist.
84
+ */
85
+
86
+ export type DarkPalette = typeof palette;
87
+
88
+ /** Re-export grid units so consumers can pull them from the family entrypoint. */
89
+ export {
90
+ GRID,
91
+ GRID_2,
92
+ GRID_4,
93
+ GRID_6,
94
+ GRID_8,
95
+ GRID_12,
96
+ GRID_16,
97
+ GRID_24,
98
+ } from "../../utils/grid";