argos-harness 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.
Files changed (217) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -0
  3. package/assets/agents/auditor.md +140 -0
  4. package/assets/agents/commit-pr-pilot.md +154 -0
  5. package/assets/agents/explorer.md +93 -0
  6. package/assets/agents/implementer.md +117 -0
  7. package/assets/agents/leader.md +149 -0
  8. package/assets/agents/researcher.md +89 -0
  9. package/assets/agents/review-readability.md +87 -0
  10. package/assets/agents/review-reliability.md +100 -0
  11. package/assets/agents/review-resilience.md +87 -0
  12. package/assets/agents/review-risk.md +87 -0
  13. package/assets/agents/reviewer.md +167 -0
  14. package/assets/agents/ticket-audit.md +129 -0
  15. package/assets/hooks/argos-guard-destructive.sh +127 -0
  16. package/assets/hooks/argos-quality-gate.sh +129 -0
  17. package/assets/managed/aterrizaje.md +24 -0
  18. package/assets/managed/formato-respuesta.md +21 -0
  19. package/assets/managed/identidad.md +48 -0
  20. package/assets/managed/operaciones-seguras.md +14 -0
  21. package/assets/managed/orquestacion.md +169 -0
  22. package/assets/output-styles/argos.md +71 -0
  23. package/assets/skills/ai-sdk-5/SKILL.md +230 -0
  24. package/assets/skills/angular/SKILL.md +19 -0
  25. package/assets/skills/angular/references/architecture.md +137 -0
  26. package/assets/skills/angular/references/core.md +197 -0
  27. package/assets/skills/angular/references/forms.md +115 -0
  28. package/assets/skills/angular/references/performance.md +124 -0
  29. package/assets/skills/apollo-client/SKILL.md +61 -0
  30. package/assets/skills/app-blueprint/SKILL.md +45 -0
  31. package/assets/skills/app-blueprint/assets/module-template.md +48 -0
  32. package/assets/skills/app-blueprint/assets/system-template.md +38 -0
  33. package/assets/skills/app-blueprint/references/workflow.md +101 -0
  34. package/assets/skills/app-builder/SKILL.md +43 -0
  35. package/assets/skills/app-builder/phases/0-product.md +58 -0
  36. package/assets/skills/app-builder/phases/1-scaffold.md +33 -0
  37. package/assets/skills/app-builder/phases/10-store.md +40 -0
  38. package/assets/skills/app-builder/phases/2-data.md +34 -0
  39. package/assets/skills/app-builder/phases/3-domain.md +33 -0
  40. package/assets/skills/app-builder/phases/4-ui-nav.md +32 -0
  41. package/assets/skills/app-builder/phases/5-identity.md +30 -0
  42. package/assets/skills/app-builder/phases/6-polish.md +34 -0
  43. package/assets/skills/app-builder/phases/7-brand.md +31 -0
  44. package/assets/skills/app-builder/phases/8-web.md +30 -0
  45. package/assets/skills/app-builder/phases/9-docs.md +31 -0
  46. package/assets/skills/app-ia/SKILL.md +54 -0
  47. package/assets/skills/astro/SKILL.md +39 -0
  48. package/assets/skills/axios/SKILL.md +61 -0
  49. package/assets/skills/branch-pr/SKILL.md +200 -0
  50. package/assets/skills/bullmq/SKILL.md +55 -0
  51. package/assets/skills/chained-pr/SKILL.md +48 -0
  52. package/assets/skills/chained-pr/references/chaining-details.md +99 -0
  53. package/assets/skills/cognitive-doc-design/SKILL.md +81 -0
  54. package/assets/skills/comment-writer/SKILL.md +74 -0
  55. package/assets/skills/dashboard-ia/SKILL.md +54 -0
  56. package/assets/skills/django-drf/SKILL.md +180 -0
  57. package/assets/skills/go-testing/SKILL.md +47 -0
  58. package/assets/skills/go-testing/references/examples.md +89 -0
  59. package/assets/skills/issue-creation/SKILL.md +223 -0
  60. package/assets/skills/jira-epic/SKILL.md +306 -0
  61. package/assets/skills/jira-task/SKILL.md +382 -0
  62. package/assets/skills/judgment-day/SKILL.md +52 -0
  63. package/assets/skills/judgment-day/references/prompts-and-formats.md +98 -0
  64. package/assets/skills/lightsail-deploy/SKILL.md +44 -0
  65. package/assets/skills/lightsail-deploy/references/runbook.md +102 -0
  66. package/assets/skills/loop-back-debug/SKILL.md +102 -0
  67. package/assets/skills/mantine-form/SKILL.md +57 -0
  68. package/assets/skills/mongoose/SKILL.md +66 -0
  69. package/assets/skills/nextjs-15/SKILL.md +144 -0
  70. package/assets/skills/not-boring-mobile/SKILL.md +43 -0
  71. package/assets/skills/not-boring-mobile/references/not-boring-playbook.md +65 -0
  72. package/assets/skills/playwright/SKILL.md +315 -0
  73. package/assets/skills/pr-comments/SKILL.md +93 -0
  74. package/assets/skills/pr-create/SKILL.md +64 -0
  75. package/assets/skills/promo-video/SKILL.md +52 -0
  76. package/assets/skills/promo-video/assets/package.template.json +22 -0
  77. package/assets/skills/promo-video/assets/promo.template.tsx +469 -0
  78. package/assets/skills/promo-video/assets/theme.template.ts +27 -0
  79. package/assets/skills/promo-video/references/pipeline.md +119 -0
  80. package/assets/skills/promo-video-web/SKILL.md +51 -0
  81. package/assets/skills/promo-video-web/assets/browser-promo.template.tsx +385 -0
  82. package/assets/skills/promo-video-web/assets/capture.template.ts +70 -0
  83. package/assets/skills/promo-video-web/assets/package.template.json +26 -0
  84. package/assets/skills/promo-video-web/references/pipeline.md +84 -0
  85. package/assets/skills/pytest/SKILL.md +180 -0
  86. package/assets/skills/react-19/SKILL.md +118 -0
  87. package/assets/skills/react-hook-form/SKILL.md +59 -0
  88. package/assets/skills/react-router/SKILL.md +60 -0
  89. package/assets/skills/redux-toolkit/SKILL.md +60 -0
  90. package/assets/skills/review-diff/SKILL.md +101 -0
  91. package/assets/skills/ship-docs/SKILL.md +45 -0
  92. package/assets/skills/ship-docs/references/ship-docs-playbook.md +31 -0
  93. package/assets/skills/skill-creator/SKILL.md +97 -0
  94. package/assets/skills/skill-creator/assets/SKILL-TEMPLATE.md +68 -0
  95. package/assets/skills/skill-creator/references/skill-style-guide.md +79 -0
  96. package/assets/skills/skill-improver/SKILL.md +50 -0
  97. package/assets/skills/skill-improver/references/skill-style-guide.md +79 -0
  98. package/assets/skills/socketio/SKILL.md +58 -0
  99. package/assets/skills/spec-bootstrap/SKILL.md +62 -0
  100. package/assets/skills/store-ship/SKILL.md +52 -0
  101. package/assets/skills/store-ship/assets/android-supply.template.md +22 -0
  102. package/assets/skills/store-ship/assets/eas.template.json +32 -0
  103. package/assets/skills/store-ship/assets/maestro-flow.template.yaml +27 -0
  104. package/assets/skills/store-ship/assets/store.config.template.json +28 -0
  105. package/assets/skills/store-ship/references/pipeline.md +198 -0
  106. package/assets/skills/stripe/SKILL.md +83 -0
  107. package/assets/skills/tailwind-4/SKILL.md +193 -0
  108. package/assets/skills/tamagui/SKILL.md +60 -0
  109. package/assets/skills/tanstack-query/SKILL.md +58 -0
  110. package/assets/skills/ticket-intake/SKILL.md +55 -0
  111. package/assets/skills/typescript/SKILL.md +134 -0
  112. package/assets/skills/verify-before-done/SKILL.md +111 -0
  113. package/assets/skills/webapp-rebuilder/SKILL.md +48 -0
  114. package/assets/skills/webapp-rebuilder/assets/charter-template.md +46 -0
  115. package/assets/skills/webapp-rebuilder/references/workflow.md +47 -0
  116. package/assets/skills/winston-logging/SKILL.md +61 -0
  117. package/assets/skills/work-unit-commits/SKILL.md +84 -0
  118. package/assets/skills/zod-4/SKILL.md +210 -0
  119. package/assets/skills/zustand-5/SKILL.md +216 -0
  120. package/bin/argos.js +6 -0
  121. package/dist/commands/adopt.d.ts +35 -0
  122. package/dist/commands/adopt.d.ts.map +1 -0
  123. package/dist/commands/adopt.js +347 -0
  124. package/dist/commands/adopt.js.map +1 -0
  125. package/dist/commands/doctor.d.ts +20 -0
  126. package/dist/commands/doctor.d.ts.map +1 -0
  127. package/dist/commands/doctor.js +478 -0
  128. package/dist/commands/doctor.js.map +1 -0
  129. package/dist/commands/init.d.ts +32 -0
  130. package/dist/commands/init.d.ts.map +1 -0
  131. package/dist/commands/init.js +358 -0
  132. package/dist/commands/init.js.map +1 -0
  133. package/dist/commands/remove.d.ts +62 -0
  134. package/dist/commands/remove.d.ts.map +1 -0
  135. package/dist/commands/remove.js +487 -0
  136. package/dist/commands/remove.js.map +1 -0
  137. package/dist/commands/workspace.d.ts +73 -0
  138. package/dist/commands/workspace.d.ts.map +1 -0
  139. package/dist/commands/workspace.js +354 -0
  140. package/dist/commands/workspace.js.map +1 -0
  141. package/dist/index.d.ts +2 -0
  142. package/dist/index.d.ts.map +1 -0
  143. package/dist/index.js +24 -0
  144. package/dist/index.js.map +1 -0
  145. package/dist/lib/assets.d.ts +29 -0
  146. package/dist/lib/assets.d.ts.map +1 -0
  147. package/dist/lib/assets.js +57 -0
  148. package/dist/lib/assets.js.map +1 -0
  149. package/dist/lib/atomic-write.d.ts +17 -0
  150. package/dist/lib/atomic-write.d.ts.map +1 -0
  151. package/dist/lib/atomic-write.js +41 -0
  152. package/dist/lib/atomic-write.js.map +1 -0
  153. package/dist/lib/backup.d.ts +11 -0
  154. package/dist/lib/backup.d.ts.map +1 -0
  155. package/dist/lib/backup.js +42 -0
  156. package/dist/lib/backup.js.map +1 -0
  157. package/dist/lib/config.d.ts +36 -0
  158. package/dist/lib/config.d.ts.map +1 -0
  159. package/dist/lib/config.js +52 -0
  160. package/dist/lib/config.js.map +1 -0
  161. package/dist/lib/detect.d.ts +58 -0
  162. package/dist/lib/detect.d.ts.map +1 -0
  163. package/dist/lib/detect.js +330 -0
  164. package/dist/lib/detect.js.map +1 -0
  165. package/dist/lib/ficha.d.ts +10 -0
  166. package/dist/lib/ficha.d.ts.map +1 -0
  167. package/dist/lib/ficha.js +38 -0
  168. package/dist/lib/ficha.js.map +1 -0
  169. package/dist/lib/git.d.ts +27 -0
  170. package/dist/lib/git.d.ts.map +1 -0
  171. package/dist/lib/git.js +79 -0
  172. package/dist/lib/git.js.map +1 -0
  173. package/dist/lib/managed-files.d.ts +35 -0
  174. package/dist/lib/managed-files.d.ts.map +1 -0
  175. package/dist/lib/managed-files.js +97 -0
  176. package/dist/lib/managed-files.js.map +1 -0
  177. package/dist/lib/markers.d.ts +64 -0
  178. package/dist/lib/markers.d.ts.map +1 -0
  179. package/dist/lib/markers.js +157 -0
  180. package/dist/lib/markers.js.map +1 -0
  181. package/dist/lib/navori-import.d.ts +42 -0
  182. package/dist/lib/navori-import.d.ts.map +1 -0
  183. package/dist/lib/navori-import.js +65 -0
  184. package/dist/lib/navori-import.js.map +1 -0
  185. package/dist/lib/openclaw-agents.d.ts +53 -0
  186. package/dist/lib/openclaw-agents.d.ts.map +1 -0
  187. package/dist/lib/openclaw-agents.js +118 -0
  188. package/dist/lib/openclaw-agents.js.map +1 -0
  189. package/dist/lib/package-root.d.ts +11 -0
  190. package/dist/lib/package-root.d.ts.map +1 -0
  191. package/dist/lib/package-root.js +23 -0
  192. package/dist/lib/package-root.js.map +1 -0
  193. package/dist/lib/paths.d.ts +11 -0
  194. package/dist/lib/paths.d.ts.map +1 -0
  195. package/dist/lib/paths.js +16 -0
  196. package/dist/lib/paths.js.map +1 -0
  197. package/dist/lib/settings-merge.d.ts +125 -0
  198. package/dist/lib/settings-merge.d.ts.map +1 -0
  199. package/dist/lib/settings-merge.js +373 -0
  200. package/dist/lib/settings-merge.js.map +1 -0
  201. package/dist/lib/version.d.ts +3 -0
  202. package/dist/lib/version.d.ts.map +1 -0
  203. package/dist/lib/version.js +13 -0
  204. package/dist/lib/version.js.map +1 -0
  205. package/dist/lib/which.d.ts +8 -0
  206. package/dist/lib/which.d.ts.map +1 -0
  207. package/dist/lib/which.js +30 -0
  208. package/dist/lib/which.js.map +1 -0
  209. package/dist/lib/workspaces.d.ts +133 -0
  210. package/dist/lib/workspaces.d.ts.map +1 -0
  211. package/dist/lib/workspaces.js +241 -0
  212. package/dist/lib/workspaces.js.map +1 -0
  213. package/dist/lib/zod-messages.d.ts +4 -0
  214. package/dist/lib/zod-messages.d.ts.map +1 -0
  215. package/dist/lib/zod-messages.js +28 -0
  216. package/dist/lib/zod-messages.js.map +1 -0
  217. package/package.json +44 -0
@@ -0,0 +1,469 @@
1
+ // Scene architecture template — verified on a real render.
2
+ // Replace SCREENSHOT_ASPECT, scene copy, screenshot filenames, and lockup text
3
+ // with the target app's values. Copy language = the store listing's locale.
4
+ import React from 'react';
5
+ import {
6
+ AbsoluteFill,
7
+ Audio,
8
+ Img,
9
+ interpolate,
10
+ spring,
11
+ staticFile,
12
+ useCurrentFrame,
13
+ useVideoConfig,
14
+ } from 'remotion';
15
+ import {TransitionSeries, linearTiming} from '@remotion/transitions';
16
+ import {fade} from '@remotion/transitions/fade';
17
+ import {slide} from '@remotion/transitions/slide';
18
+ import {colors, fonts, radialBackground} from './theme';
19
+
20
+ // Real pixel ratio of the source screenshots (width / height).
21
+ const SCREENSHOT_ASPECT = 1320 / 2868;
22
+
23
+ // Scene durations (frames @ 30fps). Transitions overlap TRANSITION_FRAMES each:
24
+ // total = sum(durations) - (transitions * TRANSITION_FRAMES). Tune to hit target.
25
+ export const DURATIONS = {
26
+ intro: 110,
27
+ feature1: 145,
28
+ feature2: 145,
29
+ feature3: 145,
30
+ feature4: 130,
31
+ outro: 135,
32
+ };
33
+ const TRANSITION_FRAMES = 12;
34
+ export const TOTAL_DURATION =
35
+ Object.values(DURATIONS).reduce((a, b) => a + b, 0) - 5 * TRANSITION_FRAMES;
36
+
37
+ const springIn = (frame: number, fps: number, delay = 0) =>
38
+ spring({frame: frame - delay, fps, config: {damping: 16}});
39
+
40
+ const Backdrop: React.FC<{children: React.ReactNode}> = ({children}) => (
41
+ <AbsoluteFill style={{background: radialBackground}}>{children}</AbsoluteFill>
42
+ );
43
+
44
+ // Giant outlined word drifting behind feature scenes for depth.
45
+ const Watermark: React.FC<{word: string}> = ({word}) => {
46
+ const frame = useCurrentFrame();
47
+ const {durationInFrames} = useVideoConfig();
48
+ const drift = interpolate(frame, [0, durationInFrames], [0, -18]);
49
+ return (
50
+ <div
51
+ style={{
52
+ position: 'absolute',
53
+ top: 260,
54
+ left: -120,
55
+ fontFamily: fonts.display,
56
+ fontWeight: 700,
57
+ fontSize: 340,
58
+ textTransform: 'uppercase',
59
+ letterSpacing: 8,
60
+ whiteSpace: 'nowrap',
61
+ color: 'transparent',
62
+ WebkitTextStroke: '1px rgba(250,250,250,0.05)',
63
+ transform: `translateX(${drift}px)`,
64
+ }}
65
+ >
66
+ {word}
67
+ </div>
68
+ );
69
+ };
70
+
71
+ // Staggered copy block: eyebrow, headline, body — 5 frames apart.
72
+ const HeadlineBlock: React.FC<{
73
+ eyebrow: string;
74
+ headline: string;
75
+ body: string;
76
+ delay?: number;
77
+ }> = ({eyebrow, headline, body, delay = 8}) => {
78
+ const frame = useCurrentFrame();
79
+ const {fps} = useVideoConfig();
80
+ const rows = [
81
+ {
82
+ text: eyebrow,
83
+ style: {
84
+ fontFamily: fonts.display,
85
+ fontWeight: 600,
86
+ fontSize: 30,
87
+ letterSpacing: 5,
88
+ textTransform: 'uppercase' as const,
89
+ color: colors.accent,
90
+ },
91
+ },
92
+ {
93
+ text: headline,
94
+ style: {
95
+ fontFamily: fonts.display,
96
+ fontWeight: 700,
97
+ fontSize: 72,
98
+ letterSpacing: 2,
99
+ textTransform: 'uppercase' as const,
100
+ color: colors.textPrimary,
101
+ lineHeight: 1.05,
102
+ },
103
+ },
104
+ {
105
+ text: body,
106
+ style: {
107
+ fontFamily: fonts.body,
108
+ fontWeight: 500,
109
+ fontSize: 34,
110
+ color: colors.textSecondary,
111
+ },
112
+ },
113
+ ];
114
+ return (
115
+ <div
116
+ style={{
117
+ position: 'absolute',
118
+ top: 130,
119
+ left: 0,
120
+ right: 0,
121
+ display: 'flex',
122
+ flexDirection: 'column',
123
+ alignItems: 'center',
124
+ gap: 20,
125
+ textAlign: 'center',
126
+ padding: '0 80px',
127
+ }}
128
+ >
129
+ {rows.map((row, i) => {
130
+ const s = springIn(frame, fps, delay + i * 5);
131
+ return (
132
+ <div
133
+ key={row.text}
134
+ style={{
135
+ ...row.style,
136
+ opacity: s,
137
+ transform: `translateY(${interpolate(s, [0, 1], [40, 0])}px)`,
138
+ }}
139
+ >
140
+ {row.text}
141
+ </div>
142
+ );
143
+ })}
144
+ </div>
145
+ );
146
+ };
147
+
148
+ // CSS device frame with entrance spring, perspective tilt settling to a
149
+ // small residual (never fully flat — keeps depth), and a slow sine float.
150
+ const Phone: React.FC<{
151
+ src: string;
152
+ enterFrom: 'bottom' | 'left' | 'right';
153
+ tiltY: number;
154
+ }> = ({src, enterFrom, tiltY}) => {
155
+ const frame = useCurrentFrame();
156
+ const {fps, height} = useVideoConfig();
157
+ const enter = springIn(frame, fps, 4);
158
+
159
+ const phoneHeight = height * 0.62;
160
+ const phoneWidth = phoneHeight * SCREENSHOT_ASPECT;
161
+
162
+ const offset = interpolate(enter, [0, 1], [1, 0]);
163
+ const translateX =
164
+ enterFrom === 'left' ? -900 * offset : enterFrom === 'right' ? 900 * offset : 0;
165
+ const translateY = enterFrom === 'bottom' ? 1100 * offset : 0;
166
+
167
+ const rotateY = interpolate(enter, [0, 1], [tiltY, tiltY * 0.25]);
168
+ const rotateX = interpolate(enter, [0, 1], [2, 0.5]);
169
+
170
+ const float = Math.sin((frame / fps) * Math.PI) * 8;
171
+
172
+ return (
173
+ <div
174
+ style={{
175
+ position: 'absolute',
176
+ bottom: 110,
177
+ left: '50%',
178
+ perspective: 1200,
179
+ transform: `translateX(-50%) translate(${translateX}px, ${translateY + float}px)`,
180
+ }}
181
+ >
182
+ <div
183
+ style={{
184
+ width: phoneWidth,
185
+ height: phoneHeight,
186
+ borderRadius: 64,
187
+ border: `10px solid ${colors.border}`,
188
+ backgroundColor: colors.surface,
189
+ overflow: 'hidden',
190
+ boxShadow: '0 40px 80px rgba(0,0,0,0.6)',
191
+ transform: `rotateY(${rotateY}deg) rotateX(${rotateX}deg)`,
192
+ }}
193
+ >
194
+ <Img
195
+ src={staticFile(src)}
196
+ style={{width: '100%', height: '100%', objectFit: 'cover'}}
197
+ />
198
+ </div>
199
+ </div>
200
+ );
201
+ };
202
+
203
+ // One-shot accent glow pulse behind the device — use on ONE scene only.
204
+ const GlowPulse: React.FC = () => {
205
+ const frame = useCurrentFrame();
206
+ const opacity = interpolate(frame, [18, 40, 85], [0, 1, 0], {
207
+ extrapolateLeft: 'clamp',
208
+ extrapolateRight: 'clamp',
209
+ });
210
+ return (
211
+ <div
212
+ style={{
213
+ position: 'absolute',
214
+ bottom: 60,
215
+ left: '50%',
216
+ transform: 'translateX(-50%)',
217
+ width: 1000,
218
+ height: 1100,
219
+ borderRadius: '50%',
220
+ background:
221
+ 'radial-gradient(ellipse at center, rgba(220,38,38,0.25) 0%, rgba(220,38,38,0) 65%)',
222
+ opacity,
223
+ }}
224
+ />
225
+ );
226
+ };
227
+
228
+ const FeatureScene: React.FC<{
229
+ watermark: string;
230
+ eyebrow: string;
231
+ headline: string;
232
+ body: string;
233
+ screenshot: string;
234
+ enterFrom: 'bottom' | 'left' | 'right';
235
+ tiltY: number;
236
+ glow?: boolean;
237
+ }> = ({watermark, eyebrow, headline, body, screenshot, enterFrom, tiltY, glow}) => (
238
+ <Backdrop>
239
+ <Watermark word={watermark} />
240
+ {glow ? <GlowPulse /> : null}
241
+ <Phone src={screenshot} enterFrom={enterFrom} tiltY={tiltY} />
242
+ <HeadlineBlock eyebrow={eyebrow} headline={headline} body={body} />
243
+ </Backdrop>
244
+ );
245
+
246
+ // Logo + wordmark lockup shared by intro and outro. `speed` shortens delays.
247
+ const Lockup: React.FC<{speed?: number}> = ({speed = 1}) => {
248
+ const frame = useCurrentFrame();
249
+ const {fps} = useVideoConfig();
250
+
251
+ const logoS = springIn(frame, fps, 2 * speed);
252
+ const eyebrowS = springIn(frame, fps, 22 * speed);
253
+ const titleS = springIn(frame, fps, 16 * speed);
254
+ const barS = springIn(frame, fps, 28 * speed);
255
+
256
+ return (
257
+ <div
258
+ style={{
259
+ display: 'flex',
260
+ flexDirection: 'column',
261
+ alignItems: 'center',
262
+ gap: 28,
263
+ }}
264
+ >
265
+ <Img
266
+ src={staticFile('logo.png')}
267
+ style={{
268
+ width: 360,
269
+ height: 360,
270
+ opacity: logoS,
271
+ transform: `scale(${interpolate(logoS, [0, 1], [0.7, 1])})`,
272
+ }}
273
+ />
274
+ <div
275
+ style={{
276
+ fontFamily: fonts.display,
277
+ fontWeight: 600,
278
+ fontSize: 28,
279
+ letterSpacing: 6,
280
+ textTransform: 'uppercase',
281
+ color: colors.textSecondary,
282
+ opacity: eyebrowS,
283
+ transform: `translateY(${interpolate(eyebrowS, [0, 1], [24, 0])}px)`,
284
+ }}
285
+ >
286
+ APP TAGLINE
287
+ </div>
288
+ <div
289
+ style={{
290
+ fontFamily: fonts.display,
291
+ fontWeight: 700,
292
+ fontSize: 110,
293
+ letterSpacing: 4,
294
+ textTransform: 'uppercase',
295
+ color: colors.textPrimary,
296
+ lineHeight: 1,
297
+ opacity: titleS,
298
+ transform: `translateY(${interpolate(titleS, [0, 1], [70, 0])}px)`,
299
+ }}
300
+ >
301
+ APP NAME
302
+ </div>
303
+ <div
304
+ style={{
305
+ width: 180,
306
+ height: 6,
307
+ backgroundColor: colors.accent,
308
+ transformOrigin: 'left',
309
+ transform: `scaleX(${barS})`,
310
+ }}
311
+ />
312
+ </div>
313
+ );
314
+ };
315
+
316
+ const IntroScene: React.FC = () => (
317
+ <AbsoluteFill
318
+ style={{
319
+ backgroundColor: colors.bg,
320
+ justifyContent: 'center',
321
+ alignItems: 'center',
322
+ }}
323
+ >
324
+ <Lockup />
325
+ </AbsoluteFill>
326
+ );
327
+
328
+ const OutroScene: React.FC = () => {
329
+ const frame = useCurrentFrame();
330
+ const {fps} = useVideoConfig();
331
+ const ctaS = springIn(frame, fps, 26);
332
+ const subS = springIn(frame, fps, 34);
333
+
334
+ return (
335
+ <AbsoluteFill
336
+ style={{
337
+ backgroundColor: colors.bg,
338
+ justifyContent: 'center',
339
+ alignItems: 'center',
340
+ }}
341
+ >
342
+ <div
343
+ style={{
344
+ display: 'flex',
345
+ flexDirection: 'column',
346
+ alignItems: 'center',
347
+ gap: 36,
348
+ }}
349
+ >
350
+ <Lockup speed={0.6} />
351
+ <div
352
+ style={{
353
+ fontFamily: fonts.body,
354
+ fontWeight: 600,
355
+ fontSize: 36,
356
+ color: colors.textPrimary,
357
+ opacity: ctaS,
358
+ transform: `translateY(${interpolate(ctaS, [0, 1], [30, 0])}px)`,
359
+ }}
360
+ >
361
+ STORE CTA LINE
362
+ </div>
363
+ <div
364
+ style={{
365
+ fontFamily: fonts.body,
366
+ fontWeight: 400,
367
+ fontSize: 30,
368
+ color: colors.textSecondary,
369
+ opacity: subS,
370
+ transform: `translateY(${interpolate(subS, [0, 1], [24, 0])}px)`,
371
+ }}
372
+ >
373
+ OUTRO SUBTITLE
374
+ </div>
375
+ </div>
376
+ </AbsoluteFill>
377
+ );
378
+ };
379
+
380
+ // User-provided music bed (public/audio/bgm.mp3). Keep null when absent —
381
+ // never source or download music for the user (licensing is theirs).
382
+ const BGM: string | null = null;
383
+
384
+ export const Promo: React.FC = () => {
385
+ const timing = linearTiming({durationInFrames: TRANSITION_FRAMES});
386
+ return (
387
+ <>
388
+ {BGM ? (
389
+ <Audio
390
+ src={staticFile(BGM)}
391
+ volume={(f) =>
392
+ interpolate(f, [TOTAL_DURATION - 30, TOTAL_DURATION], [1, 0], {
393
+ extrapolateLeft: 'clamp',
394
+ })
395
+ }
396
+ />
397
+ ) : null}
398
+ <TransitionSeries>
399
+ <TransitionSeries.Sequence durationInFrames={DURATIONS.intro}>
400
+ <IntroScene />
401
+ </TransitionSeries.Sequence>
402
+ <TransitionSeries.Transition presentation={fade()} timing={timing} />
403
+
404
+ <TransitionSeries.Sequence durationInFrames={DURATIONS.feature1}>
405
+ <FeatureScene
406
+ watermark="FEATURE WORD"
407
+ eyebrow="EYEBROW"
408
+ headline="HEADLINE"
409
+ body="One-sentence body copy."
410
+ screenshot="01-feature.png"
411
+ enterFrom="bottom"
412
+ tiltY={-6}
413
+ />
414
+ </TransitionSeries.Sequence>
415
+ <TransitionSeries.Transition
416
+ presentation={slide({direction: 'from-right'})}
417
+ timing={timing}
418
+ />
419
+
420
+ <TransitionSeries.Sequence durationInFrames={DURATIONS.feature2}>
421
+ <FeatureScene
422
+ watermark="FEATURE WORD"
423
+ eyebrow="EYEBROW"
424
+ headline="HEADLINE"
425
+ body="One-sentence body copy."
426
+ screenshot="02-feature.png"
427
+ enterFrom="right"
428
+ tiltY={6}
429
+ />
430
+ </TransitionSeries.Sequence>
431
+ <TransitionSeries.Transition
432
+ presentation={slide({direction: 'from-left'})}
433
+ timing={timing}
434
+ />
435
+
436
+ <TransitionSeries.Sequence durationInFrames={DURATIONS.feature3}>
437
+ <FeatureScene
438
+ watermark="FEATURE WORD"
439
+ eyebrow="EYEBROW"
440
+ headline="HEADLINE"
441
+ body="One-sentence body copy."
442
+ screenshot="03-feature.png"
443
+ enterFrom="left"
444
+ tiltY={-6}
445
+ />
446
+ </TransitionSeries.Sequence>
447
+ <TransitionSeries.Transition presentation={fade()} timing={timing} />
448
+
449
+ <TransitionSeries.Sequence durationInFrames={DURATIONS.feature4}>
450
+ <FeatureScene
451
+ watermark="FEATURE WORD"
452
+ eyebrow="EYEBROW"
453
+ headline="HEADLINE"
454
+ body="One-sentence body copy."
455
+ screenshot="04-feature.png"
456
+ enterFrom="bottom"
457
+ tiltY={-6}
458
+ glow
459
+ />
460
+ </TransitionSeries.Sequence>
461
+ <TransitionSeries.Transition presentation={fade()} timing={timing} />
462
+
463
+ <TransitionSeries.Sequence durationInFrames={DURATIONS.outro}>
464
+ <OutroScene />
465
+ </TransitionSeries.Sequence>
466
+ </TransitionSeries>
467
+ </>
468
+ );
469
+ };
@@ -0,0 +1,27 @@
1
+ // Token bridge: map the app's design tokens 1:1 — never invent values.
2
+ // Replace font families and hex values with the app's own (e.g. lib/theme.ts).
3
+ import {loadFont as loadDisplay} from '@remotion/google-fonts/Oswald';
4
+ import {loadFont as loadBody} from '@remotion/google-fonts/Inter';
5
+
6
+ const display = loadDisplay('normal', {weights: ['600', '700']});
7
+ const body = loadBody('normal', {weights: ['400', '500', '600']});
8
+
9
+ export const fonts = {
10
+ display: display.fontFamily,
11
+ body: body.fontFamily,
12
+ };
13
+
14
+ export const colors = {
15
+ bg: '#0A0A0B',
16
+ surface: '#17171A',
17
+ panel: '#212126',
18
+ border: '#26262B',
19
+ borderStrong: '#3F3F46',
20
+ accent: '#DC2626',
21
+ textPrimary: '#FAFAFA',
22
+ textSecondary: '#A1A1AA',
23
+ textMuted: '#71717A',
24
+ };
25
+
26
+ // Shared radial backdrop: surface center fading to near-black edges.
27
+ export const radialBackground = `radial-gradient(ellipse at center, ${colors.surface} 0%, ${colors.bg} 70%)`;
@@ -0,0 +1,119 @@
1
+ # Promo Video Pipeline — Runbook
2
+
3
+ Verified end-to-end on a real run (Expo SDK 57 monorepo, Remotion 4.0.496, macOS).
4
+
5
+ ## §1 Prerequisites
6
+
7
+ - Node 18+; network access (google-fonts packages fetch font files at render time).
8
+ - Inputs inventory before scaffolding:
9
+ - App token file (colors, font roles) — e.g. `apps/mobile/lib/theme.ts`.
10
+ - Transparent logo PNG at high resolution (e.g. `docs/logo/mark-transparent-3072.png`).
11
+ - Store screenshots (e.g. `store/screenshots/ios/iphone-6.9/*.png`) — record their pixel size; the device-frame aspect ratio derives from it (e.g. 1320/2868).
12
+ - Store copy + locale (`store/store.config.json` title/subtitle/promoText) — on-screen copy must match the listing's language and claims.
13
+
14
+ ## §2 Project setup
15
+
16
+ - Scaffold `tools/promo/` from `assets/package.template.json`. CRITICAL: this path must be OUTSIDE the monorepo's npm `workspaces` globs (`apps/*`, `packages/*`) so it keeps its own `node_modules` — Remotion needs react-dom and a hoisted install will fight the app's React (Expo/RN).
17
+ - Pin all Remotion packages (`remotion`, `@remotion/cli`, `@remotion/google-fonts`, `@remotion/transitions`) to the SAME exact 4.x version; react/react-dom 19.
18
+ - `npm install` inside `tools/promo` (non-interactive).
19
+ - Minimal source layout: `src/index.ts` (registerRoot), `src/Root.tsx` (one `<Composition id="Promo">`, 1080×1920@30), `src/theme.ts`, `src/Promo.tsx`. Boilerplate for index/Root:
20
+
21
+ ```tsx
22
+ // src/index.ts
23
+ import {registerRoot} from 'remotion';
24
+ import {Root} from './Root';
25
+ registerRoot(Root);
26
+
27
+ // src/Root.tsx
28
+ import {Composition} from 'remotion';
29
+ import {Promo, TOTAL_DURATION} from './Promo';
30
+ export const Root = () => (
31
+ <Composition id="Promo" component={Promo} durationInFrames={TOTAL_DURATION}
32
+ fps={30} width={1080} height={1920} />
33
+ );
34
+ ```
35
+
36
+ - Copy logo + selected screenshots into `tools/promo/public/`; reference ONLY via `staticFile()`. Skip visually weak screenshots (login screens rarely earn a scene).
37
+ - Add root script `"promo:render": "cd tools/promo && npx remotion render Promo out/promo.mp4"` — the explicit `cd` forces the directory context so the render always resolves `tools/promo`'s own `node_modules` (`npm --prefix` also works, but the `cd` form is unambiguous); gitignore `tools/promo/out/`.
38
+
39
+ ## §3 Brand mapping
40
+
41
+ - Map the app tokens 1:1 into `src/theme.ts` (see `assets/theme.template.ts`): background, surface, border, accent, three text tiers. Never invent values.
42
+ - Fonts via `@remotion/google-fonts/<Family>` `loadFont()` — load only the weights used. Respect the app's typography roles (e.g. condensed display face always uppercase with letter-spacing; body face for sentences).
43
+ - Shared backdrop: radial gradient, surface color at center fading to background at ~70% — reads premium, avoids flat black.
44
+
45
+ ## §4 Scene direction system
46
+
47
+ The default 25s structure (six scenes): intro lockup → 3–4 feature scenes → outro. All animation via `spring()` (damping 14–18); nothing linear except slow drifts. Scene-to-scene via `@remotion/transitions` `TransitionSeries` (fade or slide, ~12 frames).
48
+
49
+ - **Intro**: logo springs in (scale 0.7→1 + opacity), wordmark rises from below, accent bar wipes (scaleX, origin left), eyebrow line above in secondary text.
50
+ - **Feature scene template** (see `assets/promo.template.tsx`):
51
+ - Giant watermark word behind everything: display font ~340px uppercase, `color: transparent` + `WebkitTextStroke 1px rgba(250,250,250,0.05)`, partially off-canvas, drifting a few px across the scene.
52
+ - Device frame built in CSS: rounded ~64px, ~10px bezel border, overflow hidden, `boxShadow 0 40px 80px rgba(0,0,0,0.6)`, perspective 1200px with rotateY tilt that springs from ±6° to ~25% residual (never fully flat — keeps depth), slow ±8px sine float.
53
+ - Phone enters springing from bottom/left/right — alternate direction and tilt sign per scene for variety.
54
+ - Copy block: eyebrow (accent color, letter-spaced uppercase) → headline (display 700 ~72px) → body (body font, secondary color), staggered ~5 frames apart.
55
+ - Accent moment: one scene gets a one-shot radial glow pulse behind the device (accent color at 0.25 alpha, opacity up-then-down).
56
+ - **Outro**: lockup again at higher speed, then CTA line ("Disponible en el App Store" / locale equivalent), then one subtitle. Hold ≥1.5s fully settled at the end.
57
+
58
+ ### Audio bed
59
+
60
+ Use a user-provided `public/audio/bgm.mp3`, or source one ONLY on explicit user request — never silently.
61
+
62
+ Sourcing protocol (verified):
63
+ - Use a library whose license allows commercial use without attribution: Mixkit Stock Music Free License (works well — tag pages at `mixkit.co/free-stock-music/tag/<tag>/` embed JSON-LD with name/genre/duration/direct mp3 URL, scrapeable with curl + a browser UA). Pixabay's license also qualifies but the site 403-blocks non-browser fetching — don't burn time there.
64
+ - Derive search tags from the brand vibe, not generic terms: athletic/martial dark brand → `trailer`, `sports`, `percussion`, `epic`; playful consumer app → `upbeat`, `funk`, `pop`. Prefer instrumental "Percussion Trailer" / "Trailer Music" genres for dark athletic brands — no melodic identity to clash with the copy.
65
+ - You cannot listen: say so, pick by genre/tags/duration, and note the user judges with one listen (swapping the file re-renders in minutes).
66
+ - Record source URL + license in a comment directly above the `<Audio>` element.
67
+ - Track longer than the video is fine — playback cuts at composition end; the fade-out handles the exit.
68
+
69
+ Render it as a SIBLING of the `TransitionSeries` (`<Audio>` is not a wrapper):
70
+
71
+ ```tsx
72
+ <>
73
+ <Audio
74
+ src={staticFile('audio/bgm.mp3')}
75
+ volume={(f) =>
76
+ interpolate(f, [TOTAL_DURATION - 30, TOTAL_DURATION], [1, 0], {
77
+ extrapolateLeft: 'clamp',
78
+ })
79
+ }
80
+ />
81
+ <TransitionSeries>…</TransitionSeries>
82
+ </>
83
+ ```
84
+
85
+ The 30-frame fade-out prevents a hard audio cut; the export is then social-ready with no post-production.
86
+
87
+ ### Non-vertical source screenshots
88
+
89
+ Tablet (4:3) or horizontal screenshots must NEVER be stretched into the 9:16 canvas. Two accepted routes: (a) constrain the capture inside a fixed mobile mockup frame at its true aspect; (b) blurred-background fill — the same image scaled to cover the canvas with `filter: blur(40px)` + a dark overlay behind the true-aspect foreground copy.
90
+
91
+ ## §5 Timeline math
92
+
93
+ `TransitionSeries` transitions OVERLAP scenes: total = Σ(scene durations) − Σ(transition frames). Example that lands exactly on 750 frames / 25s @30fps with five 12-frame transitions: 110 + 145 + 145 + 145 + 130 + 135 − 60 = 750. Export `TOTAL_DURATION` computed from the durations object — never hardcode the total.
94
+
95
+ ## §6 Render + verify
96
+
97
+ - Render from `tools/promo`: `npx remotion render Promo out/promo.mp4` — ALWAYS foreground. A render started with a backgrounded process inside a sub-agent dies when that agent's session ends; whoever owns the render waits for it.
98
+ - Verify: Remotion bundles ffprobe (`node_modules/.bin` or via `npx remotion versions`) — check h264, expected WxH, fps, duration. System ffprobe is often absent; don't assume it.
99
+ - Spot-check at least two frames visually: `npx remotion still Promo out/frame.png --frame=<n>` (pick a mid-feature frame and the outro) and view them.
100
+ - Deliver the MP4 with duration/size and the marketing-vs-App-Preview note.
101
+
102
+ ## §7 Video-clip variant (instead of stills)
103
+
104
+ - Record the simulator during a Maestro tour flow: `xcrun simctl io booted recordVideo clip.mov` (Ctrl-C to stop), one clip per feature.
105
+ - Trim/normalize with ffmpeg if needed; place clips in `public/`; swap `<Img>` for `<OffthreadVideo src={staticFile('clip.mp4')} muted>` inside the same device frame. `OffthreadVideo` is the render-safe choice (frame-accurate, no HTMLVideoElement flakiness).
106
+ - Keep clips short (3–4s per scene) and trim dead frames at the start.
107
+
108
+ ## §8 Troubleshooting
109
+
110
+ | Symptom | Cause / Fix |
111
+ |---|---|
112
+ | Peer/type conflicts on install | Project got hoisted into workspaces — move outside globs; own lockfile |
113
+ | Fonts render as system sans | `loadFont()` not called at module scope, or offline — google-fonts needs network at render |
114
+ | Render "completes" but no file | Render was backgrounded and its owner session ended — re-run foreground |
115
+ | Total duration off by N frames | Forgot transition overlap subtraction — §5 |
116
+ | Screenshot distorted in frame | Frame aspect must derive from the real screenshot pixel ratio, `objectFit: cover` |
117
+ | Tablet/horizontal screenshots in a 9:16 comp | Never stretch — mockup-frame constrain or blurred-background fill (§4) |
118
+ | No audio in exported MP4 | `<Audio>` missing or rendered inside a Sequence that ends early — mount it at composition top level (§4) |
119
+ | Studio port busy | `npx remotion studio --port <other>` |
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: promo-video-web
3
+ description: Trigger: webapp promo, dashboard promo, web promo video, promo del dashboard, promo de la web, landing video, product demo video. Generate branded promotional MP4s for web surfaces (web app, admin dashboard) with Remotion + Playwright captures.
4
+ ---
5
+
6
+ # Promo Video — Web Surfaces
7
+
8
+ ## Activation Contract
9
+
10
+ Activate when a web surface — the product web app (`apps/web`) or the admin dashboard (`apps/dashboard`) — needs a promotional/marketing video. Each surface gets its OWN composition and MP4 (different audience, different register). Mobile app promos belong to `promo-video`.
11
+
12
+ ## Hard Rules
13
+
14
+ - Same isolation rule as `promo-video`: the Remotion project lives at `tools/promo/` OUTSIDE npm workspaces. If `tools/promo/` already exists (mobile promo), ADD compositions to it — never create a second Remotion project.
15
+ - Captures come from the REAL running app via Playwright against seeded demo data — never mockups. Fixed viewport 1920×1080, `deviceScaleFactor: 2` (crisp 2x assets). Dashboard captures log in with demo credentials injected via env vars — never hardcoded in the capture script.
16
+ - Capture stability is a gate: `headless: true` strictly enforced (the agent must never hang waiting for a GUI in terminal environments), and every capture waits for `networkidle` PLUS explicit unmount of spinners/skeletons PLUS a real-data selector — a capture showing a loading state is a quality failure.
17
+ - Brand fidelity: colors/fonts from the surface's own token source (web CSS vars / tailwind theme); copy from the product definition — never invent claims.
18
+ - Register per surface: web app = product marketing (benefit-led copy); dashboard = operator tool (calm, outcome-led copy: "manage X in seconds", no hype). A dashboard promo that reads like a consumer ad is a quality failure.
19
+ - Landscape 1080p (1920×1080@30) default for web surfaces; vertical only on explicit request.
20
+ - On-screen copy in the product's locale; code and comments in English.
21
+ - Foreground renders only; TransitionSeries timeline math (total = Σ durations − Σ overlaps) — same as `promo-video`.
22
+
23
+ ## Decision Gates
24
+
25
+ | Situation | Route |
26
+ |---|---|
27
+ | Which surface | One composition per surface: `PromoWeb`, `PromoDashboard` — separate MP4s, separate copy registers |
28
+ | `tools/promo/` exists | Add `src/web/` sources + compositions + `render:web` / `render:dashboard` scripts; add `playwright` devDep |
29
+ | Motion instead of stills | Playwright `recordVideo` context while scripting the flow; embed via `<OffthreadVideo>` |
30
+ | App not deployed / no local stack | Capture against the local dev server with seeded data; block only if neither runs |
31
+ | Multiple surfaces share scene code | Drive COLOR tokens via `getInputProps()` + `--props=./<surface>-tokens.json` at render time instead of rewriting `theme.ts` per run; fonts stay code-level (`loadFont()` runs at module scope and cannot come from JSON) |
32
+
33
+ ## Execution Steps
34
+
35
+ 1. Read `references/pipeline.md`. Inventory: surface URL (local or deployed), demo credentials (env), token source, product copy, locale.
36
+ 2. Seed a PREDICTABLE, isolated demo state first — on multi-tenant products, pass an explicit test-tenant ID via env (`PROMO_TENANT`) so captures never show empty states or another tenant's data.
37
+ 3. Capture: adapt `assets/capture.template.ts`; run it (foreground, headless) to produce `public/web/*.png` (or clips) — authenticate, wait for the state to hydrate (spinners/skeletons unmounted, real-data selector present), then snap. Login flow for the dashboard, key screens per surface, 4–6 captures.
38
+ 4. Scaffold or extend `tools/promo/` per the Decision Gates; map web tokens into a surface theme file (or per-surface `--props` token JSON — Decision Gates).
39
+ 5. Build scenes on `assets/browser-promo.template.tsx`: intro lockup → 3–4 feature scenes (browser-chrome-framed capture with slow Ken Burns zoom, split copy layout) → CTA outro with the surface URL.
40
+ 6. Render each composition foreground (`npx remotion render PromoWeb out/promo-web.mp4`, same for dashboard); add root scripts (cd form, per `promo-video`); verify duration/fps + `remotion still` spot-checks; deliver MP4s.
41
+
42
+ ## Output Contract
43
+
44
+ Return: MP4 path(s) with duration/fps/size, capture inventory, files created, root scripts added, and which register was applied per surface.
45
+
46
+ ## References
47
+
48
+ - `references/pipeline.md` — capture protocol, browser-frame scene system, per-surface registers, multi-composition layout, troubleshooting.
49
+ - `assets/capture.template.ts` — Playwright capture script (viewport, auth via env, screenshot set).
50
+ - `assets/browser-promo.template.tsx` — landscape scene architecture: browser chrome frame, Ken Burns, split copy layout, CTA outro.
51
+ - `assets/package.template.json` — deps when scaffolding fresh (Remotion pinned + playwright).