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,385 @@
1
+ // Landscape (1920x1080) web-surface scene architecture.
2
+ // Replace PRODUCT_DOMAIN, copy placeholders, and capture filenames.
3
+ // Register: PromoWeb = benefit-led marketing; PromoDashboard = calm, outcome-led.
4
+ import React from 'react';
5
+ import {
6
+ AbsoluteFill,
7
+ Img,
8
+ interpolate,
9
+ spring,
10
+ staticFile,
11
+ useCurrentFrame,
12
+ useVideoConfig,
13
+ } from 'remotion';
14
+ import {TransitionSeries, linearTiming} from '@remotion/transitions';
15
+ import {fade} from '@remotion/transitions/fade';
16
+ import {slide} from '@remotion/transitions/slide';
17
+ import {colors, fonts, radialBackground} from './theme';
18
+
19
+ const PRODUCT_DOMAIN = 'app.product.com';
20
+
21
+ export const DURATIONS = {
22
+ intro: 100,
23
+ feature1: 140,
24
+ feature2: 140,
25
+ feature3: 140,
26
+ outro: 120,
27
+ };
28
+ const TRANSITION_FRAMES = 12;
29
+ export const TOTAL_DURATION =
30
+ Object.values(DURATIONS).reduce((a, b) => a + b, 0) - 4 * TRANSITION_FRAMES;
31
+
32
+ const springIn = (frame: number, fps: number, delay = 0) =>
33
+ spring({frame: frame - delay, fps, config: {damping: 16}});
34
+
35
+ const Backdrop: React.FC<{children: React.ReactNode}> = ({children}) => (
36
+ <AbsoluteFill style={{background: radialBackground}}>{children}</AbsoluteFill>
37
+ );
38
+
39
+ const Watermark: React.FC<{word: string}> = ({word}) => {
40
+ const frame = useCurrentFrame();
41
+ const {durationInFrames} = useVideoConfig();
42
+ const drift = interpolate(frame, [0, durationInFrames], [0, -24]);
43
+ return (
44
+ <div
45
+ style={{
46
+ position: 'absolute',
47
+ bottom: 40,
48
+ left: -140,
49
+ fontFamily: fonts.display,
50
+ fontWeight: 700,
51
+ fontSize: 420,
52
+ textTransform: 'uppercase',
53
+ letterSpacing: 8,
54
+ whiteSpace: 'nowrap',
55
+ color: 'transparent',
56
+ WebkitTextStroke: '1px rgba(250,250,250,0.05)',
57
+ transform: `translateX(${drift}px)`,
58
+ }}
59
+ >
60
+ {word}
61
+ </div>
62
+ );
63
+ };
64
+
65
+ // Browser chrome window: traffic lights + URL pill + capture with Ken Burns.
66
+ const BrowserFrame: React.FC<{
67
+ src: string;
68
+ enterFrom: 'left' | 'right';
69
+ focusX?: string; // objectPosition x-target of the Ken Burns drift
70
+ }> = ({src, enterFrom, focusX = 'center'}) => {
71
+ const frame = useCurrentFrame();
72
+ const {fps, durationInFrames} = useVideoConfig();
73
+ const enter = springIn(frame, fps, 4);
74
+
75
+ const offset = interpolate(enter, [0, 1], [1, 0]);
76
+ const translateX = (enterFrom === 'left' ? -1200 : 1200) * offset;
77
+ const tilt = interpolate(enter, [0, 1], [enterFrom === 'left' ? 3 : -3, 0.8]);
78
+ const kenBurns = interpolate(frame, [0, durationInFrames], [1.0, 1.06]);
79
+
80
+ return (
81
+ <div
82
+ style={{
83
+ position: 'absolute',
84
+ top: '50%',
85
+ [enterFrom === 'left' ? 'left' : 'right']: 60,
86
+ width: '58%',
87
+ perspective: 1600,
88
+ transform: `translateY(-50%) translateX(${translateX}px)`,
89
+ }}
90
+ >
91
+ <div
92
+ style={{
93
+ borderRadius: 16,
94
+ border: `1px solid ${colors.borderStrong}`,
95
+ backgroundColor: colors.surface,
96
+ overflow: 'hidden',
97
+ boxShadow: '0 40px 90px rgba(0,0,0,0.65)',
98
+ transform: `rotateY(${tilt}deg)`,
99
+ }}
100
+ >
101
+ <div
102
+ style={{
103
+ display: 'flex',
104
+ alignItems: 'center',
105
+ gap: 16,
106
+ padding: '14px 20px',
107
+ backgroundColor: colors.panel,
108
+ borderBottom: `1px solid ${colors.border}`,
109
+ }}
110
+ >
111
+ <div style={{display: 'flex', gap: 8}}>
112
+ {['#FF5F57', '#FEBC2E', '#28C840'].map((c) => (
113
+ <div
114
+ key={c}
115
+ style={{width: 12, height: 12, borderRadius: 6, backgroundColor: c}}
116
+ />
117
+ ))}
118
+ </div>
119
+ <div
120
+ style={{
121
+ flex: 1,
122
+ maxWidth: 420,
123
+ margin: '0 auto',
124
+ padding: '6px 18px',
125
+ borderRadius: 8,
126
+ backgroundColor: colors.bg,
127
+ fontFamily: fonts.body,
128
+ fontSize: 16,
129
+ color: colors.textMuted,
130
+ textAlign: 'center',
131
+ }}
132
+ >
133
+ {PRODUCT_DOMAIN}
134
+ </div>
135
+ </div>
136
+ <div style={{aspectRatio: '16 / 9', overflow: 'hidden'}}>
137
+ <Img
138
+ src={staticFile(src)}
139
+ style={{
140
+ width: '100%',
141
+ height: '100%',
142
+ objectFit: 'cover',
143
+ objectPosition: `${focusX} top`,
144
+ transform: `scale(${kenBurns})`,
145
+ }}
146
+ />
147
+ </div>
148
+ </div>
149
+ </div>
150
+ );
151
+ };
152
+
153
+ // Copy block on the opposite side, vertically centered.
154
+ const CopyBlock: React.FC<{
155
+ eyebrow: string;
156
+ headline: string;
157
+ body: string;
158
+ side: 'left' | 'right';
159
+ }> = ({eyebrow, headline, body, side}) => {
160
+ const frame = useCurrentFrame();
161
+ const {fps} = useVideoConfig();
162
+ const rows = [
163
+ {
164
+ text: eyebrow,
165
+ style: {
166
+ fontFamily: fonts.display,
167
+ fontWeight: 600,
168
+ fontSize: 26,
169
+ letterSpacing: 5,
170
+ textTransform: 'uppercase' as const,
171
+ color: colors.accent,
172
+ },
173
+ },
174
+ {
175
+ text: headline,
176
+ style: {
177
+ fontFamily: fonts.display,
178
+ fontWeight: 700,
179
+ fontSize: 64,
180
+ letterSpacing: 2,
181
+ textTransform: 'uppercase' as const,
182
+ color: colors.textPrimary,
183
+ lineHeight: 1.08,
184
+ },
185
+ },
186
+ {
187
+ text: body,
188
+ style: {
189
+ fontFamily: fonts.body,
190
+ fontWeight: 500,
191
+ fontSize: 28,
192
+ color: colors.textSecondary,
193
+ lineHeight: 1.4,
194
+ },
195
+ },
196
+ ];
197
+ return (
198
+ <div
199
+ style={{
200
+ position: 'absolute',
201
+ top: '50%',
202
+ [side]: 90,
203
+ width: '32%',
204
+ transform: 'translateY(-50%)',
205
+ display: 'flex',
206
+ flexDirection: 'column',
207
+ gap: 22,
208
+ }}
209
+ >
210
+ {rows.map((row, i) => {
211
+ const s = springIn(frame, fps, 8 + i * 5);
212
+ return (
213
+ <div
214
+ key={row.text}
215
+ style={{
216
+ ...row.style,
217
+ opacity: s,
218
+ transform: `translateY(${interpolate(s, [0, 1], [36, 0])}px)`,
219
+ }}
220
+ >
221
+ {row.text}
222
+ </div>
223
+ );
224
+ })}
225
+ </div>
226
+ );
227
+ };
228
+
229
+ const FeatureScene: React.FC<{
230
+ watermark: string;
231
+ eyebrow: string;
232
+ headline: string;
233
+ body: string;
234
+ capture: string;
235
+ browserSide: 'left' | 'right';
236
+ }> = ({watermark, eyebrow, headline, body, capture, browserSide}) => (
237
+ <Backdrop>
238
+ <Watermark word={watermark} />
239
+ <BrowserFrame src={capture} enterFrom={browserSide} />
240
+ <CopyBlock
241
+ eyebrow={eyebrow}
242
+ headline={headline}
243
+ body={body}
244
+ side={browserSide === 'left' ? 'right' : 'left'}
245
+ />
246
+ </Backdrop>
247
+ );
248
+
249
+ const Lockup: React.FC<{speed?: number}> = ({speed = 1}) => {
250
+ const frame = useCurrentFrame();
251
+ const {fps} = useVideoConfig();
252
+ const logoS = springIn(frame, fps, 2 * speed);
253
+ const titleS = springIn(frame, fps, 14 * speed);
254
+ const barS = springIn(frame, fps, 24 * speed);
255
+ return (
256
+ <div style={{display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 24}}>
257
+ <Img
258
+ src={staticFile('logo.png')}
259
+ style={{
260
+ width: 240,
261
+ height: 240,
262
+ opacity: logoS,
263
+ transform: `scale(${interpolate(logoS, [0, 1], [0.7, 1])})`,
264
+ }}
265
+ />
266
+ <div
267
+ style={{
268
+ fontFamily: fonts.display,
269
+ fontWeight: 700,
270
+ fontSize: 96,
271
+ letterSpacing: 4,
272
+ textTransform: 'uppercase',
273
+ color: colors.textPrimary,
274
+ lineHeight: 1,
275
+ opacity: titleS,
276
+ transform: `translateY(${interpolate(titleS, [0, 1], [60, 0])}px)`,
277
+ }}
278
+ >
279
+ APP NAME
280
+ </div>
281
+ <div
282
+ style={{
283
+ width: 160,
284
+ height: 6,
285
+ backgroundColor: colors.accent,
286
+ transformOrigin: 'left',
287
+ transform: `scaleX(${barS})`,
288
+ }}
289
+ />
290
+ </div>
291
+ );
292
+ };
293
+
294
+ const IntroScene: React.FC = () => (
295
+ <AbsoluteFill
296
+ style={{backgroundColor: colors.bg, justifyContent: 'center', alignItems: 'center'}}
297
+ >
298
+ <Lockup />
299
+ </AbsoluteFill>
300
+ );
301
+
302
+ const OutroScene: React.FC = () => {
303
+ const frame = useCurrentFrame();
304
+ const {fps} = useVideoConfig();
305
+ const ctaS = springIn(frame, fps, 22);
306
+ return (
307
+ <AbsoluteFill
308
+ style={{backgroundColor: colors.bg, justifyContent: 'center', alignItems: 'center'}}
309
+ >
310
+ <div style={{display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 30}}>
311
+ <Lockup speed={0.6} />
312
+ <div
313
+ style={{
314
+ fontFamily: fonts.body,
315
+ fontWeight: 600,
316
+ fontSize: 34,
317
+ color: colors.textPrimary,
318
+ opacity: ctaS,
319
+ transform: `translateY(${interpolate(ctaS, [0, 1], [26, 0])}px)`,
320
+ }}
321
+ >
322
+ {PRODUCT_DOMAIN}
323
+ </div>
324
+ </div>
325
+ </AbsoluteFill>
326
+ );
327
+ };
328
+
329
+ export const PromoWeb: React.FC = () => {
330
+ const timing = linearTiming({durationInFrames: TRANSITION_FRAMES});
331
+ return (
332
+ <TransitionSeries>
333
+ <TransitionSeries.Sequence durationInFrames={DURATIONS.intro}>
334
+ <IntroScene />
335
+ </TransitionSeries.Sequence>
336
+ <TransitionSeries.Transition presentation={fade()} timing={timing} />
337
+
338
+ <TransitionSeries.Sequence durationInFrames={DURATIONS.feature1}>
339
+ <FeatureScene
340
+ watermark="FEATURE WORD"
341
+ eyebrow="EYEBROW"
342
+ headline="HEADLINE"
343
+ body="One-sentence body copy."
344
+ capture="web/01-home.png"
345
+ browserSide="right"
346
+ />
347
+ </TransitionSeries.Sequence>
348
+ <TransitionSeries.Transition
349
+ presentation={slide({direction: 'from-right'})}
350
+ timing={timing}
351
+ />
352
+
353
+ <TransitionSeries.Sequence durationInFrames={DURATIONS.feature2}>
354
+ <FeatureScene
355
+ watermark="FEATURE WORD"
356
+ eyebrow="EYEBROW"
357
+ headline="HEADLINE"
358
+ body="One-sentence body copy."
359
+ capture="web/02-feature.png"
360
+ browserSide="left"
361
+ />
362
+ </TransitionSeries.Sequence>
363
+ <TransitionSeries.Transition
364
+ presentation={slide({direction: 'from-left'})}
365
+ timing={timing}
366
+ />
367
+
368
+ <TransitionSeries.Sequence durationInFrames={DURATIONS.feature3}>
369
+ <FeatureScene
370
+ watermark="FEATURE WORD"
371
+ eyebrow="EYEBROW"
372
+ headline="HEADLINE"
373
+ body="One-sentence body copy."
374
+ capture="web/03-feature.png"
375
+ browserSide="right"
376
+ />
377
+ </TransitionSeries.Sequence>
378
+ <TransitionSeries.Transition presentation={fade()} timing={timing} />
379
+
380
+ <TransitionSeries.Sequence durationInFrames={DURATIONS.outro}>
381
+ <OutroScene />
382
+ </TransitionSeries.Sequence>
383
+ </TransitionSeries>
384
+ );
385
+ };
@@ -0,0 +1,70 @@
1
+ // Playwright capture script for web-surface promos.
2
+ // Run from tools/promo/: PROMO_EMAIL=... PROMO_PASSWORD=... npx tsx capture/capture.ts
3
+ // Requires: npx playwright install chromium (once).
4
+ import {chromium} from 'playwright';
5
+
6
+ const BASE_URL = process.env.PROMO_URL ?? 'http://localhost:5173';
7
+ const EMAIL = process.env.PROMO_EMAIL;
8
+ const PASSWORD = process.env.PROMO_PASSWORD;
9
+ // Multi-tenant products: seed an isolated demo tenant and scope every
10
+ // navigation to it — credentials alone do not make the state predictable.
11
+ const TENANT = process.env.PROMO_TENANT;
12
+
13
+ // Loading UI that must be GONE before any capture (adapt to the app).
14
+ const LOADING_SELECTORS = ['[data-loading]', '.skeleton', '[role="progressbar"]'];
15
+
16
+ // One entry per feature scene. `readySelector` must match REAL data,
17
+ // not a skeleton — captures with spinners are a quality failure.
18
+ const SHOTS: Array<{path: string; readySelector: string; out: string}> = [
19
+ {path: '/', readySelector: 'main', out: '01-home.png'},
20
+ // {path: '/students', readySelector: 'table tbody tr', out: '02-students.png'},
21
+ ];
22
+
23
+ async function main() {
24
+ // headless is mandatory: a headed browser hangs the run in terminal envs.
25
+ const browser = await chromium.launch({headless: true});
26
+ const context = await browser.newContext({
27
+ viewport: {width: 1920, height: 1080},
28
+ deviceScaleFactor: 2,
29
+ });
30
+ const page = await context.newPage();
31
+
32
+ // Stabilize captures: kill CSS animations and transitions.
33
+ await page.addInitScript(() => {
34
+ const style = document.createElement('style');
35
+ style.textContent = '*{animation:none!important;transition:none!important}';
36
+ document.addEventListener('DOMContentLoaded', () =>
37
+ document.head.appendChild(style)
38
+ );
39
+ });
40
+
41
+ // Login (dashboard surfaces). Wait for a post-login element, never the redirect.
42
+ if (EMAIL && PASSWORD) {
43
+ await page.goto(`${BASE_URL}/login`);
44
+ await page.fill('input[type="email"]', EMAIL);
45
+ await page.fill('input[type="password"]', PASSWORD);
46
+ await page.click('button[type="submit"]');
47
+ await page.waitForSelector('[data-authenticated], main', {timeout: 15000});
48
+ }
49
+
50
+ for (const shot of SHOTS) {
51
+ const prefix = TENANT ? `/t/${TENANT}` : ''; // adapt to the app's tenant routing
52
+ await page.goto(`${BASE_URL}${prefix}${shot.path}`);
53
+ await page.waitForLoadState('networkidle');
54
+ // networkidle is not enough on async dashboards: also require every
55
+ // loading indicator to unmount, then a real-data selector.
56
+ for (const sel of LOADING_SELECTORS) {
57
+ await page.waitForSelector(sel, {state: 'detached', timeout: 15000}).catch(() => {});
58
+ }
59
+ await page.waitForSelector(shot.readySelector, {timeout: 15000});
60
+ await page.screenshot({path: `public/web/${shot.out}`});
61
+ console.log(`captured ${shot.out}`);
62
+ }
63
+
64
+ await browser.close();
65
+ }
66
+
67
+ main().catch((err) => {
68
+ console.error(err);
69
+ process.exit(1);
70
+ });
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "APP_SLUG-promo",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "description": "Remotion promotional videos for APP_NAME",
6
+ "scripts": {
7
+ "studio": "remotion studio",
8
+ "capture": "tsx capture/capture.ts",
9
+ "render:web": "remotion render PromoWeb out/promo-web.mp4",
10
+ "render:dashboard": "remotion render PromoDashboard out/promo-dashboard.mp4"
11
+ },
12
+ "dependencies": {
13
+ "@remotion/cli": "4.0.496",
14
+ "@remotion/google-fonts": "4.0.496",
15
+ "@remotion/transitions": "4.0.496",
16
+ "react": "^19.0.0",
17
+ "react-dom": "^19.0.0",
18
+ "remotion": "4.0.496"
19
+ },
20
+ "devDependencies": {
21
+ "@types/react": "^19.0.0",
22
+ "playwright": "^1.50.0",
23
+ "tsx": "^4.19.0",
24
+ "typescript": "^5.6.0"
25
+ }
26
+ }
@@ -0,0 +1,84 @@
1
+ # Promo Video (Web Surfaces) — Runbook
2
+
3
+ Extends the `promo-video` pipeline for landscape web-surface promos. Shared fundamentals (workspace isolation, timeline math, foreground renders, verify protocol) are identical — this file covers only what differs.
4
+
5
+ ## §1 Capture protocol (Playwright)
6
+
7
+ - Adapt `assets/capture.template.ts`. Run with `npx playwright install chromium` done once, then `npx tsx capture.ts` (or `node` after tsc) from `tools/promo/` — FOREGROUND, and `headless: true` set explicitly on `chromium.launch()`: in a terminal environment a headed browser hangs the agent waiting for a GUI.
8
+ - Viewport fixed at 1920×1080, `deviceScaleFactor: 2` → 3840×2160 PNGs; the browser frame downscales them crisp.
9
+ - Target: local dev server with seeded demo data (preferred — deterministic) or the deployed URL. 4–6 captures per surface, each a distinct feature screen.
10
+ - Multi-tenant products: seeding credentials is not enough — the STATE must be predictable. Seed an isolated demo tenant and pass its ID via env (`PROMO_TENANT`); the script scopes every navigation to it. This guarantees no empty states and no leakage of another tenant's data into the capture.
11
+ - Dashboard auth: credentials from env (`PROMO_EMAIL` / `PROMO_PASSWORD`), never hardcoded. Sequence per capture: authenticate → wait for the state to hydrate (`networkidle` AND spinners/skeletons UNMOUNTED — wait `state: 'detached'` on known loading selectors — AND a real-data selector present) → snap. Async dashboards (tables, metrics, payments) render piecemeal; `networkidle` alone still shows skeletons.
12
+ - Hide flaky UI before capture: dismiss toasts/cookie banners, `page.addStyleTag` to disable CSS animations (`*{animation:none!important;transition:none!important}`) so captures are stable.
13
+ - Motion variant: `browser.newContext({recordVideo: {dir, size}})`, script the flow slowly (deliberate 800ms pauses between actions), close context to flush the webm, convert to mp4 with ffmpeg, embed via `<OffthreadVideo>`.
14
+
15
+ ## §2 Project layout (multi-composition)
16
+
17
+ If `tools/promo/` exists (mobile promo), extend it — one Remotion project, several compositions:
18
+
19
+ ```
20
+ tools/promo/
21
+ ├── capture/capture.ts # Playwright capture script
22
+ ├── public/ # mobile assets (existing)
23
+ ├── public/web/ # web captures
24
+ ├── src/Root.tsx # registers Promo + PromoWeb + PromoDashboard
25
+ ├── src/Promo.tsx # mobile (existing)
26
+ ├── src/web/theme.ts # web-surface token bridge
27
+ ├── src/web/PromoWeb.tsx
28
+ └── src/web/PromoDashboard.tsx
29
+ ```
30
+
31
+ Add `playwright` + `tsx` as devDependencies; add scripts `render:web` / `render:dashboard`; root scripts `promo:render:web` / `promo:render:dashboard` (cd form: `cd tools/promo && npm run render:web`). If scaffolding fresh, use `assets/package.template.json`.
32
+
33
+ ### Per-surface theming via input props
34
+
35
+ To avoid rewriting `src/web/theme.ts` per surface, drive COLOR tokens through Remotion input props — one scene codebase, N surface renders:
36
+
37
+ ```ts
38
+ // src/web/theme.ts
39
+ import {getInputProps} from 'remotion';
40
+ const base = {bg: '#0A0A0B', /* … */ accent: '#DC2626'};
41
+ const {colors: overrides} = getInputProps() as {colors?: Partial<typeof base>};
42
+ export const colors = {...base, ...overrides};
43
+ ```
44
+
45
+ Render with `npx remotion render PromoDashboard out/promo-dashboard.mp4 --props=./dashboard-tokens.json` where the JSON is `{"colors": {"accent": "#1D4ED8", …}}`. LIMIT: fonts cannot come from input props — `loadFont()` executes at module scope; font choices stay code-level per surface.
46
+
47
+ ## §3 Scene system (landscape 1920×1080)
48
+
49
+ Same animation grammar as `promo-video` (springs damping 14–18, 12-frame transitions, staggered copy, watermark words) with landscape-specific composition:
50
+
51
+ - **Browser frame** instead of phone bezel (see `assets/browser-promo.template.tsx`): rounded ~16px window, top chrome bar with three traffic-light dots and a URL pill showing the REAL product domain, capture below, big soft shadow. Slight rotateY tilt optional and subtler than mobile (±3°).
52
+ - **Split layout**: copy block vertically centered on one side (~38% width), browser frame filling the other (~58%), alternating sides per scene. Watermark word behind, larger canvas → ~420px.
53
+ - **Ken Burns on captures**: slow scale 1.0→1.06 across the scene (linear interpolate is fine here — it is a drift, not a gesture), `objectPosition` aimed at the screen's key region. Wide screenshots carry motion this way instead of tilt-float.
54
+ - **Intro/outro**: same lockup pattern; outro CTA is the surface URL (e.g. `app.product.com`) instead of a store line.
55
+ - Default length 20–25s, 5–6 scenes.
56
+
57
+ ## §4 Register per surface
58
+
59
+ | Surface | Audience | Copy register | Example headline shape |
60
+ |---|---|---|---|
61
+ | Web app (`PromoWeb`) | End users | Product marketing, benefit-led — same energy as the mobile promo | "YOUR PROGRESS, ANYWHERE" |
62
+ | Dashboard (`PromoDashboard`) | Operators/staff | Calm, outcome-led, zero hype; verbs about control and time saved | "EVERY STUDENT, ONE SCREEN" |
63
+
64
+ Dashboard scenes favor showing dense real data (tables, filters, detail panels) — density IS the selling point. Do not crop tables down to look "clean"; show the tool doing work.
65
+
66
+ ## §5 Render + verify
67
+
68
+ Identical to `promo-video` §6, once per composition:
69
+ `npx remotion render PromoWeb out/promo-web.mp4` and `npx remotion render PromoDashboard out/promo-dashboard.mp4`, then ffprobe check (1920×1080, 30fps, expected duration) and ≥2 `remotion still` spot-checks per video (one feature scene, the outro).
70
+
71
+ ## §6 Troubleshooting
72
+
73
+ | Symptom | Cause / Fix |
74
+ |---|---|
75
+ | Captures blurry in frame | Missing `deviceScaleFactor: 2`, or upscaling a 1x capture |
76
+ | Dashboard capture shows skeletons/spinners | Waited on `networkidle` only — add explicit selector waits for real data |
77
+ | Captures differ between runs | Animations not disabled, toasts present, or non-seeded data — §1 |
78
+ | Login flow flaky in capture script | Race on redirect: wait for a post-login selector, not the navigation event |
79
+ | Second Remotion project appeared | Wrong — consolidate into `tools/promo/` (one project, many compositions) |
80
+ | Playwright browser missing at capture | Run `npx playwright install chromium` once inside `tools/promo/` |
81
+ | Login returns `invalid_credentials` despite a correct password hash | Directly-seeded `auth.users` rows are often incomplete for GoTrue: NULL `instance_id`, no matching `auth.identities` row, and NULL string-token columns (GoTrue errors "converting NULL to string is unsupported"). Fix in the local DB before capture: set `instance_id` to the zero UUID, insert an email identity per user, and coalesce token columns to `''`. |
82
+ | Capture script hangs in terminal | Headed browser waiting for a GUI — enforce `headless: true` on launch |
83
+ | Dashboard capture shows empty states | Tenant not seeded/scoped — seed the demo tenant and pass `PROMO_TENANT` (§1) |
84
+ | Colors ignored at render | `--props` file path wrong or `getInputProps()` read after module init in the wrong file — override in `theme.ts` itself (§2) |