@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,210 @@
1
+ /**
2
+ * Auto-scaffolded by ReelStack.
3
+ *
4
+ * Family: Warm Signature
5
+ * Preset: __PRESET_NAME__ (source: __SOURCE_REEL__)
6
+ * Duration: __DURATION_FRAMES__ frames @ __FPS__ fps
7
+ *
8
+ * Generated rules baked in:
9
+ * - IG safe zones reserved (top 290px / bottom 422px)
10
+ * - Motion floor primer (FilmGrain + AccentGlow ambient on every scene)
11
+ * - GSAP-vocabulary easings via reelstack/utils/easing
12
+ * - Audio-lock skeleton (BEATS const ready for /reelstack-beats output)
13
+ *
14
+ * STRICT RULE: amber primary, emerald payoff, red wall. No other accents.
15
+ * The lint command flags any other color used in this reel.
16
+ */
17
+ import React from "react";
18
+ import { Sequence, getInputProps, useCurrentFrame } from "remotion";
19
+
20
+ import { palette } from "@devinilabs/reelstack/families/warm";
21
+ import {
22
+ AccentGlow,
23
+ FilmGrain,
24
+ BentoGrid,
25
+ BentoCell,
26
+ WarmSurface,
27
+ ScaleBlurCounter,
28
+ } from "@devinilabs/reelstack/families/warm/components";
29
+ import { ease } from "@devinilabs/reelstack/utils/easing";
30
+ import { SafeZones, TOP_SAFE, BOTTOM_SAFE } from "@devinilabs/reelstack/utils/safe-zones";
31
+
32
+ // ─── BEATS ────────────────────────────────────────────────────────────────
33
+ // Run /reelstack-beats <vo.wav> to populate this from whisper-cli SRT.
34
+ // Hand-eyeballing beats causes 6+ second drift on 90s reels — don't.
35
+ const BEATS = {
36
+ hook: 0,
37
+ name: 180,
38
+ body: 600,
39
+ anchor: 1200,
40
+ cta: 1800,
41
+ } as const;
42
+
43
+ /** Read inputProps for reduceMotion at module load.
44
+ * Buyers can render a low-motion variant via:
45
+ * npx remotion render <id> out/x.mp4 --props='{"reduceMotion":true}'
46
+ */
47
+ const _inputProps = (getInputProps() as { reduceMotion?: boolean }) || {};
48
+ const REDUCE_MOTION = _inputProps.reduceMotion === true;
49
+
50
+ export const __REEL_NAME__Reel: React.FC = () => {
51
+ return (
52
+ <WarmSurface>
53
+ {/* Background motion layers (the family's signature ambient) */}
54
+ <FilmGrain reduceMotion={REDUCE_MOTION} />
55
+ <AccentGlow reduceMotion={REDUCE_MOTION} />
56
+
57
+ {/* Hook — between safe zones */}
58
+ <Sequence from={BEATS.hook}>
59
+ <HookScene />
60
+ </Sequence>
61
+
62
+ {/* Name drop — preset-specific */}
63
+ <Sequence from={BEATS.name}>
64
+ <NameScene />
65
+ </Sequence>
66
+
67
+ {/* Body — bento matrix */}
68
+ <Sequence from={BEATS.body}>
69
+ <BodyScene />
70
+ </Sequence>
71
+
72
+ {/* Anchor — second motion floor anchor */}
73
+ <Sequence from={BEATS.anchor}>
74
+ <AnchorScene />
75
+ </Sequence>
76
+
77
+ {/* CTA */}
78
+ <Sequence from={BEATS.cta}>
79
+ <CtaScene />
80
+ </Sequence>
81
+
82
+ {/* Dev-only safe-zone overlay. Set visible={false} for production. */}
83
+ <SafeZones visible={false} />
84
+ </WarmSurface>
85
+ );
86
+ };
87
+
88
+ // ─── Scenes ──────────────────────────────────────────────────────────────
89
+
90
+ const HookScene: React.FC = () => {
91
+ const frame = useCurrentFrame();
92
+ const t = ease.power4Out(Math.min(1, frame / 30));
93
+ return (
94
+ <WarmSurface
95
+ style={{
96
+ background: "transparent",
97
+ paddingTop: TOP_SAFE + 100,
98
+ paddingInline: 64,
99
+ paddingBottom: BOTTOM_SAFE + 100,
100
+ display: "flex",
101
+ flexDirection: "column",
102
+ gap: 20,
103
+ justifyContent: "center",
104
+ }}
105
+ >
106
+ <span
107
+ style={{
108
+ fontSize: 90,
109
+ fontWeight: 700,
110
+ letterSpacing: -2,
111
+ lineHeight: 1.05,
112
+ opacity: t,
113
+ transform: `translateY(${(1 - t) * 32}px)`,
114
+ }}
115
+ >
116
+ __HOOK__
117
+ </span>
118
+ </WarmSurface>
119
+ );
120
+ };
121
+
122
+ const NameScene: React.FC = () => (
123
+ <WarmSurface
124
+ style={{
125
+ background: "transparent",
126
+ paddingInline: 64,
127
+ display: "flex",
128
+ justifyContent: "center",
129
+ alignItems: "center",
130
+ }}
131
+ >
132
+ <span
133
+ style={{
134
+ fontSize: 168,
135
+ fontWeight: 700,
136
+ color: palette.amber,
137
+ letterSpacing: -3,
138
+ textShadow: `0 0 60px ${palette.amberGlow}`,
139
+ }}
140
+ >
141
+ __SUB__
142
+ </span>
143
+ </WarmSurface>
144
+ );
145
+
146
+ const BodyScene: React.FC = () => (
147
+ <WarmSurface
148
+ style={{
149
+ background: "transparent",
150
+ paddingTop: TOP_SAFE + 80,
151
+ paddingInline: 64,
152
+ }}
153
+ >
154
+ <BentoGrid cols={2} gap={18}>
155
+ {[1, 2, 3, 4].map((i) => (
156
+ <BentoCell key={i} startFrame={i * 6} idx={i} reduceMotion={REDUCE_MOTION} />
157
+ ))}
158
+ </BentoGrid>
159
+ </WarmSurface>
160
+ );
161
+
162
+ const AnchorScene: React.FC = () => {
163
+ const frame = useCurrentFrame();
164
+ // Gentle camera pan via easing
165
+ const t = ease.power3Out(Math.min(1, frame / 60));
166
+ return (
167
+ <WarmSurface
168
+ style={{
169
+ background: "transparent",
170
+ paddingTop: TOP_SAFE + 100,
171
+ paddingInline: 64,
172
+ display: "flex",
173
+ flexDirection: "column",
174
+ gap: 24,
175
+ transform: `translateY(${(1 - t) * 24}px)`,
176
+ }}
177
+ >
178
+ <ScaleBlurCounter
179
+ startFrame={0}
180
+ durationFrames={48}
181
+ toValue={1000}
182
+ suffix="+"
183
+ fontSize={168}
184
+ reduceMotion={REDUCE_MOTION}
185
+ />
186
+ <span style={{ fontSize: 96, fontWeight: 700, color: palette.emerald, letterSpacing: -2 }}>
187
+ Payoff.
188
+ </span>
189
+ <span style={{ fontSize: 36, color: palette.fgSoft }}>
190
+ Use emerald only for "ship" / launch / success moments. Single-accent rule.
191
+ </span>
192
+ </WarmSurface>
193
+ );
194
+ };
195
+
196
+ const CtaScene: React.FC = () => (
197
+ <WarmSurface
198
+ style={{
199
+ background: "transparent",
200
+ paddingInline: 64,
201
+ paddingBottom: BOTTOM_SAFE + 80,
202
+ display: "flex",
203
+ flexDirection: "column",
204
+ gap: 20,
205
+ justifyContent: "flex-end",
206
+ }}
207
+ >
208
+ <span style={{ fontSize: 64, fontWeight: 700, color: palette.amber }}>__CTA__</span>
209
+ </WarmSurface>
210
+ );
@@ -0,0 +1,13 @@
1
+ /** "Generic AI purple/violet" hexes commonly emitted by AI design tools.
2
+ * ReelStack bans them in scaffolded code; lint flags any color matching this list.
3
+ * Forbidden's ultraviolet (#6B5BD9) and plasma (#A87FE8) are family-specific overrides
4
+ * and are NOT in the blocklist (they live in the Forbidden palette explicitly).
5
+ */
6
+ export const AI_PURPLE_HEXES = [
7
+ "#7c3aed", "#8b5cf6", "#6366f1", "#a78bfa", "#c084fc",
8
+ "#9333ea", "#7e22ce", "#a855f7",
9
+ ] as const;
10
+ export const isAiPurple = (hex: string): boolean => {
11
+ const lc = hex.toLowerCase();
12
+ return AI_PURPLE_HEXES.some((b) => b.toLowerCase() === lc);
13
+ };
@@ -0,0 +1,11 @@
1
+ /** Fonts banned by leonxlnx/taste-skill — generic AI defaults that signal lack of taste.
2
+ * Used by `reelstack lint` to flag any fontFamily containing one of these.
3
+ * Allowed in ReelStack: Geist, Geist Mono, system-ui (fallback). Editorial-serif options
4
+ * per Cream Paper / Warm Signature variant overlays: Lyon Text, Newsreader, Playfair Display.
5
+ */
6
+ export const BANNED_FONTS = [
7
+ "Inter", "Roboto", "Helvetica", "Arial", "Open Sans", "Lato", "Source Sans Pro",
8
+ "Nunito", "Poppins", "Montserrat", "Raleway", "PT Sans", "Ubuntu",
9
+ ] as const;
10
+ export const isBannedFont = (fontFamily: string): boolean =>
11
+ BANNED_FONTS.some((b) => fontFamily.toLowerCase().includes(b.toLowerCase()));
@@ -0,0 +1,36 @@
1
+ /** Soft-variant cubic-bezier easings (from leonxlnx/taste-skill-soft).
2
+ * Applied to Glass + Paper + Warm families' "premium polish" motion.
3
+ * Each is a CSS cubic-bezier string; for Remotion `interpolate({easing})`,
4
+ * use the matching JS-callback variant via `bezierEase(...)` helper.
5
+ */
6
+ export const SOFT_BEZIERS = {
7
+ softInOut: "cubic-bezier(0.32, 0.72, 0, 1)",
8
+ softElastic: "cubic-bezier(0.16, 1, 0.3, 1)",
9
+ softSnappy: "cubic-bezier(0.22, 1, 0.36, 1)",
10
+ } as const;
11
+ /** Bezier as a Remotion-friendly easing function. */
12
+ export const bezierEase = (mX1: number, mY1: number, mX2: number, mY2: number) => {
13
+ // Cubic bezier solver. Input t in [0,1], output curve value in [0,1].
14
+ // Standard implementation — handles the 4-control-point bezier curve.
15
+ const A = (a1: number, a2: number) => 1 - 3 * a2 + 3 * a1;
16
+ const B = (a1: number, a2: number) => 3 * a2 - 6 * a1;
17
+ const C = (a1: number) => 3 * a1;
18
+ const calcBezier = (t: number, a1: number, a2: number) =>
19
+ ((A(a1, a2) * t + B(a1, a2)) * t + C(a1)) * t;
20
+ const slope = (t: number, a1: number, a2: number) =>
21
+ 3 * A(a1, a2) * t * t + 2 * B(a1, a2) * t + C(a1);
22
+ const getTForX = (x: number): number => {
23
+ let t = x;
24
+ for (let i = 0; i < 8; i++) {
25
+ const cs = slope(t, mX1, mX2);
26
+ if (cs === 0) return t;
27
+ const cx = calcBezier(t, mX1, mX2) - x;
28
+ t -= cx / cs;
29
+ }
30
+ return t;
31
+ };
32
+ return (x: number) => calcBezier(getTForX(x), mY1, mY2);
33
+ };
34
+ export const softInOut = bezierEase(0.32, 0.72, 0, 1);
35
+ export const softElastic = bezierEase(0.16, 1, 0.3, 1);
36
+ export const softSnappy = bezierEase(0.22, 1, 0.36, 1);
@@ -0,0 +1,84 @@
1
+ /**
2
+ * ReelStack — GSAP→Remotion easing dictionary.
3
+ *
4
+ * Translates GSAP's easing vocabulary into Remotion-compatible callback
5
+ * functions. Use as the `easing` argument to `interpolate()`.
6
+ *
7
+ * Example:
8
+ * interpolate(frame, [0, 30], [0, 1], { easing: ease.power4Out })
9
+ *
10
+ * The matching spring configs match design-system.md:
11
+ * spring({ frame, fps, config: spring.snappy })
12
+ */
13
+
14
+ export type Easing = (t: number) => number;
15
+
16
+ const clamp = (n: number) => Math.max(0, Math.min(1, n));
17
+
18
+ // Power eases (1=linear, 2=quad, 3=cubic, 4=quart, 5=quint)
19
+ const power = (p: number) => ({
20
+ In: (t: number) => Math.pow(clamp(t), p),
21
+ Out: (t: number) => 1 - Math.pow(1 - clamp(t), p),
22
+ InOut: (t: number) =>
23
+ clamp(t) < 0.5
24
+ ? Math.pow(2 * clamp(t), p) / 2
25
+ : 1 - Math.pow(-2 * clamp(t) + 2, p) / 2,
26
+ });
27
+
28
+ const expo = {
29
+ In: (t: number) => (t === 0 ? 0 : Math.pow(2, 10 * clamp(t) - 10)),
30
+ Out: (t: number) => (t === 1 ? 1 : 1 - Math.pow(2, -10 * clamp(t))),
31
+ InOut: (t: number) => {
32
+ const x = clamp(t);
33
+ if (x === 0) return 0;
34
+ if (x === 1) return 1;
35
+ return x < 0.5
36
+ ? Math.pow(2, 20 * x - 10) / 2
37
+ : (2 - Math.pow(2, -20 * x + 10)) / 2;
38
+ },
39
+ };
40
+
41
+ const back = {
42
+ In: (t: number) => {
43
+ const x = clamp(t);
44
+ const c1 = 1.70158;
45
+ const c3 = c1 + 1;
46
+ return c3 * x * x * x - c1 * x * x;
47
+ },
48
+ Out: (t: number) => {
49
+ const x = clamp(t);
50
+ const c1 = 1.70158;
51
+ const c3 = c1 + 1;
52
+ return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2);
53
+ },
54
+ };
55
+
56
+ export const ease = {
57
+ linear: (t: number) => clamp(t),
58
+ power2In: power(2).In,
59
+ power2Out: power(2).Out,
60
+ power3In: power(3).In,
61
+ power3Out: power(3).Out,
62
+ power4In: power(4).In,
63
+ power4Out: power(4).Out,
64
+ power5In: power(5).In,
65
+ power5Out: power(5).Out,
66
+ expoIn: expo.In,
67
+ expoOut: expo.Out,
68
+ expoInOut: expo.InOut,
69
+ backIn: back.In,
70
+ backOut: back.Out,
71
+ } as const;
72
+
73
+ /**
74
+ * Spring configs — pass directly to Remotion's `spring({ config })`.
75
+ */
76
+ export const springs = {
77
+ smooth: { damping: 200, mass: 1, stiffness: 100 },
78
+ snappy: { damping: 20, mass: 1, stiffness: 200 },
79
+ gentle: { damping: 15, mass: 1, stiffness: 80 },
80
+ bouncy: { damping: 8, mass: 1, stiffness: 100 },
81
+ glass: { damping: 12, mass: 1, stiffness: 120 },
82
+ } as const;
83
+
84
+ export type SpringName = keyof typeof springs;
package/utils/grid.ts ADDED
@@ -0,0 +1,13 @@
1
+ /** ReelStack 4-px grid system. Every padding/margin/gap should be a multiple of GRID. */
2
+ export const GRID = 4;
3
+ export const GRID_2 = GRID * 2; // 8
4
+ export const GRID_3 = GRID * 3; // 12
5
+ export const GRID_4 = GRID * 4; // 16
6
+ export const GRID_5 = GRID * 5; // 20
7
+ export const GRID_6 = GRID * 6; // 24
8
+ export const GRID_8 = GRID * 8; // 32
9
+ export const GRID_12 = GRID * 12; // 48
10
+ export const GRID_16 = GRID * 16; // 64
11
+ export const GRID_20 = GRID * 20; // 80
12
+ export const GRID_24 = GRID * 24; // 96
13
+ export const isOnGrid = (n: number): boolean => n % GRID === 0;
@@ -0,0 +1,56 @@
1
+ {
2
+ "ig": {
3
+ "label": "Instagram Reels",
4
+ "width": 1080,
5
+ "height": 1920,
6
+ "fps": 30,
7
+ "codec": "h264",
8
+ "videoBitrate": "8M",
9
+ "audioCodec": "aac",
10
+ "audioBitrate": "192k",
11
+ "pixelFormat": "yuv420p",
12
+ "remotionFlags": [
13
+ "--codec=h264",
14
+ "--video-bitrate=8M",
15
+ "--audio-codec=aac",
16
+ "--audio-bitrate=192k",
17
+ "--pixel-format=yuv420p"
18
+ ]
19
+ },
20
+ "tiktok": {
21
+ "label": "TikTok",
22
+ "width": 1080,
23
+ "height": 1920,
24
+ "fps": 30,
25
+ "codec": "h264",
26
+ "videoBitrate": "8M",
27
+ "audioCodec": "aac",
28
+ "audioBitrate": "192k",
29
+ "pixelFormat": "yuv420p",
30
+ "remotionFlags": [
31
+ "--codec=h264",
32
+ "--video-bitrate=8M",
33
+ "--audio-codec=aac",
34
+ "--audio-bitrate=192k",
35
+ "--pixel-format=yuv420p"
36
+ ]
37
+ },
38
+ "shorts": {
39
+ "label": "YouTube Shorts",
40
+ "width": 1080,
41
+ "height": 1920,
42
+ "fps": 30,
43
+ "codec": "h264",
44
+ "videoBitrate": "9M",
45
+ "audioCodec": "aac",
46
+ "audioBitrate": "192k",
47
+ "pixelFormat": "yuv420p",
48
+ "remotionFlags": [
49
+ "--codec=h264",
50
+ "--video-bitrate=9M",
51
+ "--audio-codec=aac",
52
+ "--audio-bitrate=192k",
53
+ "--pixel-format=yuv420p"
54
+ ]
55
+ }
56
+ }
@@ -0,0 +1,57 @@
1
+ import React from "react";
2
+ import { AbsoluteFill } from "remotion";
3
+
4
+ /**
5
+ * SafeZones — IG/TikTok/Shorts overlay component.
6
+ *
7
+ * Visually marks the top 290 px and bottom 422 px of the 1920 canvas as
8
+ * reserved (Instagram chrome, TikTok username + caption, Shorts CTA bar).
9
+ *
10
+ * Pass `visible={false}` for production renders. For Remotion Studio
11
+ * iteration, pass `visible={process.env.NODE_ENV !== "production"}`.
12
+ */
13
+ export const TOP_SAFE = 290;
14
+ export const BOTTOM_SAFE = 422; // bottom 22% — y where reserved band starts is 1920 - 422 = 1498
15
+
16
+ export const SafeZones: React.FC<{
17
+ visible?: boolean;
18
+ tint?: string;
19
+ }> = ({ visible = true, tint = "rgba(255, 0, 100, 0.10)" }) => {
20
+ if (!visible) return null;
21
+ return (
22
+ <AbsoluteFill style={{ pointerEvents: "none" }}>
23
+ <div
24
+ style={{
25
+ position: "absolute",
26
+ top: 0,
27
+ left: 0,
28
+ right: 0,
29
+ height: TOP_SAFE,
30
+ background: tint,
31
+ borderBottom: "2px dashed rgba(255, 0, 100, 0.4)",
32
+ }}
33
+ />
34
+ <div
35
+ style={{
36
+ position: "absolute",
37
+ bottom: 0,
38
+ left: 0,
39
+ right: 0,
40
+ height: BOTTOM_SAFE,
41
+ background: tint,
42
+ borderTop: "2px dashed rgba(255, 0, 100, 0.4)",
43
+ }}
44
+ />
45
+ </AbsoluteFill>
46
+ );
47
+ };
48
+
49
+ /**
50
+ * Helper that returns true if a y-coordinate (and optional height) overlaps
51
+ * a reserved band. Used by `reelstack lint`.
52
+ */
53
+ export function overlapsSafeZone(y: number, height = 0): "top" | "bottom" | null {
54
+ if (y < TOP_SAFE) return "top";
55
+ if (y + height > 1920 - BOTTOM_SAFE) return "bottom";
56
+ return null;
57
+ }