create-analytics-demo 1.0.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.
@@ -0,0 +1,476 @@
1
+ import {
2
+ AbsoluteFill,
3
+ interpolate,
4
+ useCurrentFrame,
5
+ spring,
6
+ useVideoConfig,
7
+ Sequence,
8
+ } from "remotion";
9
+ import React from "react";
10
+ import { config } from "./demo.config";
11
+ import { IPhoneFrame } from "./components/IPhoneFrame";
12
+ import { EventParticle } from "./components/EventParticle";
13
+ import { TouchRipple } from "./components/TouchRipple";
14
+ import { CodePanel } from "./components/CodePanel";
15
+ import { ServiceLogoCard } from "./components/ServiceLogo";
16
+ import { SplashScreen } from "./screens/SplashScreen";
17
+ import { LoginScreen } from "./screens/LoginScreen";
18
+ import { HomeScreen } from "./screens/HomeScreen";
19
+
20
+ const { theme } = config;
21
+ const [primary, secondary] = config.services;
22
+
23
+ // ── Scene 1: App Launch — Anonymous Device ID ───────────────────────────────
24
+
25
+ const SceneAppLaunch: React.FC = () => {
26
+ const frame = useCurrentFrame();
27
+ return (
28
+ <AbsoluteFill
29
+ style={{
30
+ background:
31
+ "linear-gradient(135deg, #0a0a12 0%, #12121f 50%, #0a0a12 100%)",
32
+ }}
33
+ >
34
+ <div
35
+ style={{
36
+ position: "absolute",
37
+ top: 30,
38
+ left: 0,
39
+ right: 0,
40
+ textAlign: "center",
41
+ }}
42
+ >
43
+ <h1
44
+ style={{
45
+ color: "white",
46
+ fontSize: 32,
47
+ fontFamily: "system-ui",
48
+ margin: 0,
49
+ fontWeight: "700",
50
+ }}
51
+ >
52
+ App Launch — Anonymous User
53
+ </h1>
54
+ </div>
55
+
56
+ <div style={{ position: "absolute", left: 100, top: 100 }}>
57
+ <IPhoneFrame>
58
+ <SplashScreen />
59
+ </IPhoneFrame>
60
+ </div>
61
+
62
+ <ServiceLogoCard
63
+ service={primary}
64
+ x={900}
65
+ y={180}
66
+ delay={10}
67
+ isActive={frame > 40}
68
+ />
69
+ <ServiceLogoCard
70
+ service={secondary}
71
+ x={900}
72
+ y={380}
73
+ delay={20}
74
+ isActive={frame > 60}
75
+ />
76
+
77
+ <EventParticle
78
+ startFrame={40}
79
+ startX={380}
80
+ startY={350}
81
+ endX={920}
82
+ endY={220}
83
+ color={primary.color}
84
+ label="getDeviceId()"
85
+ />
86
+ <EventParticle
87
+ startFrame={60}
88
+ startX={380}
89
+ startY={350}
90
+ endX={920}
91
+ endY={420}
92
+ color={secondary.color}
93
+ label="changeUser(deviceId)"
94
+ />
95
+
96
+ <CodePanel
97
+ title="IdentityManager.swift"
98
+ lines={[
99
+ "// Anonymous user — use Device ID",
100
+ "let deviceId = analytics.getDeviceId()",
101
+ "",
102
+ "// Don't set userId for anonymous",
103
+ "analytics.setUserId(nil)",
104
+ "",
105
+ `// Use Device ID as ${secondary.name} external ID`,
106
+ "engagement.changeUser(",
107
+ " externalId: deviceId",
108
+ ")",
109
+ ]}
110
+ startFrame={35}
111
+ x={480}
112
+ y={280}
113
+ />
114
+ </AbsoluteFill>
115
+ );
116
+ };
117
+
118
+ // ── Scene 2: Login — Set User ID ────────────────────────────────────────────
119
+
120
+ const SceneLogin: React.FC = () => {
121
+ const frame = useCurrentFrame();
122
+ const showSuccess = frame > 50;
123
+ return (
124
+ <AbsoluteFill
125
+ style={{
126
+ background:
127
+ "linear-gradient(135deg, #0a0a12 0%, #12121f 50%, #0a0a12 100%)",
128
+ }}
129
+ >
130
+ <div
131
+ style={{
132
+ position: "absolute",
133
+ top: 30,
134
+ left: 0,
135
+ right: 0,
136
+ textAlign: "center",
137
+ }}
138
+ >
139
+ <h1
140
+ style={{
141
+ color: "white",
142
+ fontSize: 32,
143
+ fontFamily: "system-ui",
144
+ margin: 0,
145
+ fontWeight: "700",
146
+ }}
147
+ >
148
+ User Login — Set Identity
149
+ </h1>
150
+ </div>
151
+
152
+ <div style={{ position: "absolute", left: 100, top: 100 }}>
153
+ <IPhoneFrame>
154
+ <LoginScreen showSuccess={showSuccess} />
155
+ <TouchRipple x={140} y={280} frame={frame} startFrame={35} />
156
+ </IPhoneFrame>
157
+ </div>
158
+
159
+ <ServiceLogoCard
160
+ service={primary}
161
+ x={900}
162
+ y={180}
163
+ delay={0}
164
+ isActive={frame > 55}
165
+ />
166
+ <ServiceLogoCard
167
+ service={secondary}
168
+ x={900}
169
+ y={380}
170
+ delay={0}
171
+ isActive={frame > 85}
172
+ />
173
+
174
+ <EventParticle
175
+ startFrame={55}
176
+ startX={380}
177
+ startY={350}
178
+ endX={920}
179
+ endY={220}
180
+ color={primary.color}
181
+ label="setUserId(uid)"
182
+ />
183
+ <EventParticle
184
+ startFrame={70}
185
+ startX={380}
186
+ startY={320}
187
+ endX={920}
188
+ endY={220}
189
+ color={primary.color}
190
+ label="Login Completed"
191
+ />
192
+ <EventParticle
193
+ startFrame={85}
194
+ startX={380}
195
+ startY={380}
196
+ endX={920}
197
+ endY={420}
198
+ color={secondary.color}
199
+ label="changeUser(uid)"
200
+ />
201
+
202
+ <CodePanel
203
+ title="AuthManager.swift"
204
+ lines={[
205
+ "// Authenticated user — use Firebase UID",
206
+ "let userId = user.uid",
207
+ "",
208
+ `// Set ${primary.name} user ID`,
209
+ "analytics.setUserId(userId)",
210
+ "",
211
+ `// Set ${secondary.name} external ID`,
212
+ "engagement.changeUser(",
213
+ " externalId: userId",
214
+ ")",
215
+ "",
216
+ "// Track login event",
217
+ "analytics.track(",
218
+ ' event: "Login Completed",',
219
+ ' properties: ["method": "email"]',
220
+ ")",
221
+ ]}
222
+ startFrame={50}
223
+ x={480}
224
+ y={220}
225
+ />
226
+ </AbsoluteFill>
227
+ );
228
+ };
229
+
230
+ // ── Scene 3: Summary ────────────────────────────────────────────────────────
231
+
232
+ const SceneSummary: React.FC = () => {
233
+ const frame = useCurrentFrame();
234
+ const { fps } = useVideoConfig();
235
+
236
+ const items = [
237
+ {
238
+ icon: "\u{1F511}",
239
+ text: "Identity Management",
240
+ subtext: "Device ID \u2192 User ID on login",
241
+ delay: 15,
242
+ color: "#9C27B0",
243
+ },
244
+ {
245
+ icon: "\u{1F4CA}",
246
+ text: "Analytics Events",
247
+ subtext: "Track user actions across the app",
248
+ delay: 35,
249
+ color: primary.color,
250
+ },
251
+ {
252
+ icon: "\u{1F514}",
253
+ text: "Engagement",
254
+ subtext: "Push notifications & campaigns",
255
+ delay: 55,
256
+ color: secondary.color,
257
+ },
258
+ ];
259
+
260
+ const logoScale = spring({ frame, fps, config: { damping: 40 } });
261
+
262
+ return (
263
+ <AbsoluteFill
264
+ style={{
265
+ background:
266
+ "linear-gradient(135deg, #0a0a12 0%, #12121f 50%, #0a0a12 100%)",
267
+ alignItems: "center",
268
+ }}
269
+ >
270
+ {/* App icon */}
271
+ <div
272
+ style={{
273
+ position: "absolute",
274
+ top: 60,
275
+ width: 90,
276
+ height: 90,
277
+ borderRadius: 22,
278
+ background: `linear-gradient(135deg, ${theme.accent}, ${theme.accentSoft})`,
279
+ display: "flex",
280
+ alignItems: "center",
281
+ justifyContent: "center",
282
+ fontSize: 44,
283
+ color: "white",
284
+ fontWeight: "bold",
285
+ transform: `scale(${logoScale})`,
286
+ }}
287
+ >
288
+ {config.appName.charAt(0)}
289
+ </div>
290
+
291
+ <div style={{ position: "absolute", top: 165, textAlign: "center" }}>
292
+ <h1
293
+ style={{
294
+ color: "white",
295
+ fontSize: 38,
296
+ fontFamily: "system-ui",
297
+ margin: 0,
298
+ fontWeight: "700",
299
+ }}
300
+ >
301
+ {config.appName}
302
+ </h1>
303
+ <p
304
+ style={{
305
+ color: theme.accent,
306
+ fontSize: 18,
307
+ fontFamily: "system-ui",
308
+ marginTop: 6,
309
+ }}
310
+ >
311
+ Analytics & Engagement Stack
312
+ </p>
313
+ </div>
314
+
315
+ <div
316
+ style={{
317
+ position: "absolute",
318
+ top: 260,
319
+ display: "flex",
320
+ flexDirection: "column",
321
+ gap: 16,
322
+ width: 500,
323
+ }}
324
+ >
325
+ {items.map((item, i) => {
326
+ const itemScale = spring({
327
+ frame: frame - item.delay,
328
+ fps,
329
+ config: { damping: 40 },
330
+ });
331
+ const itemOpacity = interpolate(
332
+ frame - item.delay,
333
+ [0, 15],
334
+ [0, 1],
335
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" }
336
+ );
337
+ return (
338
+ <div
339
+ key={i}
340
+ style={{
341
+ display: "flex",
342
+ alignItems: "center",
343
+ gap: 16,
344
+ transform: `scale(${Math.max(0, itemScale)})`,
345
+ opacity: Math.max(0, itemOpacity),
346
+ background: theme.surface,
347
+ padding: "12px 18px",
348
+ borderRadius: 16,
349
+ border: `1px solid ${theme.stroke}`,
350
+ }}
351
+ >
352
+ <div
353
+ style={{
354
+ width: 46,
355
+ height: 46,
356
+ borderRadius: 12,
357
+ background: `${item.color}22`,
358
+ border: `2px solid ${item.color}`,
359
+ display: "flex",
360
+ alignItems: "center",
361
+ justifyContent: "center",
362
+ fontSize: 22,
363
+ }}
364
+ >
365
+ {item.icon}
366
+ </div>
367
+ <div>
368
+ <div
369
+ style={{
370
+ color: "white",
371
+ fontSize: 16,
372
+ fontWeight: "600",
373
+ fontFamily: "system-ui",
374
+ }}
375
+ >
376
+ {item.text}
377
+ </div>
378
+ <div
379
+ style={{
380
+ color: theme.textSecondary,
381
+ fontSize: 12,
382
+ fontFamily: "system-ui",
383
+ }}
384
+ >
385
+ {item.subtext}
386
+ </div>
387
+ </div>
388
+ </div>
389
+ );
390
+ })}
391
+ </div>
392
+
393
+ {/* Service logos at bottom */}
394
+ <div
395
+ style={{
396
+ position: "absolute",
397
+ bottom: 50,
398
+ display: "flex",
399
+ gap: 50,
400
+ alignItems: "center",
401
+ }}
402
+ >
403
+ <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
404
+ <svg width={45} height={45} viewBox="0 0 100 100" fill="none">
405
+ <rect width="100" height="100" rx="20" fill={primary.color} />
406
+ <path
407
+ d="M25 70 L35 45 L45 55 L55 30 L65 50 L75 25"
408
+ stroke="white"
409
+ strokeWidth="6"
410
+ strokeLinecap="round"
411
+ strokeLinejoin="round"
412
+ fill="none"
413
+ />
414
+ <circle cx="75" cy="25" r="6" fill="white" />
415
+ </svg>
416
+ <span
417
+ style={{
418
+ color: "white",
419
+ fontSize: 18,
420
+ fontFamily: "system-ui",
421
+ fontWeight: "600",
422
+ }}
423
+ >
424
+ {primary.name}
425
+ </span>
426
+ </div>
427
+ <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
428
+ <div
429
+ style={{
430
+ width: 45,
431
+ height: 45,
432
+ borderRadius: 12,
433
+ background: secondary.color,
434
+ display: "flex",
435
+ alignItems: "center",
436
+ justifyContent: "center",
437
+ color: "white",
438
+ fontWeight: "bold",
439
+ fontSize: 18,
440
+ }}
441
+ >
442
+ {secondary.name.charAt(0)}
443
+ </div>
444
+ <span
445
+ style={{
446
+ color: "white",
447
+ fontSize: 18,
448
+ fontFamily: "system-ui",
449
+ fontWeight: "600",
450
+ }}
451
+ >
452
+ {secondary.name}
453
+ </span>
454
+ </div>
455
+ </div>
456
+ </AbsoluteFill>
457
+ );
458
+ };
459
+
460
+ // ── Main Composition — 3 starter scenes ─────────────────────────────────────
461
+
462
+ export const MyComposition = () => {
463
+ return (
464
+ <AbsoluteFill>
465
+ <Sequence from={0} durationInFrames={200}>
466
+ <SceneAppLaunch />
467
+ </Sequence>
468
+ <Sequence from={200} durationInFrames={200}>
469
+ <SceneLogin />
470
+ </Sequence>
471
+ <Sequence from={400} durationInFrames={200}>
472
+ <SceneSummary />
473
+ </Sequence>
474
+ </AbsoluteFill>
475
+ );
476
+ };
@@ -0,0 +1,18 @@
1
+ import { Composition } from "remotion";
2
+ import { MyComposition } from "./Composition";
3
+ import { config } from "./demo.config";
4
+
5
+ export const RemotionRoot: React.FC = () => {
6
+ return (
7
+ <>
8
+ <Composition
9
+ id="MyComp"
10
+ component={MyComposition}
11
+ durationInFrames={config.video.durationInFrames}
12
+ fps={config.video.fps}
13
+ width={config.video.width}
14
+ height={config.video.height}
15
+ />
16
+ </>
17
+ );
18
+ };
@@ -0,0 +1,130 @@
1
+ import React from "react";
2
+ import { useCurrentFrame, useVideoConfig, spring, interpolate } from "remotion";
3
+
4
+ /** Code panel for Remotion scenes (animated slide-in with syntax coloring) */
5
+ export const CodePanel: React.FC<{
6
+ lines: string[];
7
+ startFrame: number;
8
+ x: number;
9
+ y: number;
10
+ title: string;
11
+ }> = ({ lines, startFrame, x, y, title }) => {
12
+ const frame = useCurrentFrame();
13
+ const { fps } = useVideoConfig();
14
+ const localFrame = frame - startFrame;
15
+ if (localFrame < 0) return null;
16
+
17
+ const slideIn = spring({
18
+ frame: localFrame,
19
+ fps,
20
+ config: { damping: 30, stiffness: 100 },
21
+ });
22
+ const panelOpacity = interpolate(localFrame, [0, 15], [0, 1], {
23
+ extrapolateRight: "clamp",
24
+ });
25
+
26
+ return (
27
+ <div
28
+ style={{
29
+ position: "absolute",
30
+ left: x,
31
+ top: y,
32
+ transform: `translateX(${interpolate(slideIn, [0, 1], [50, 0])}px)`,
33
+ opacity: panelOpacity,
34
+ background: "#1e1e2e",
35
+ borderRadius: 12,
36
+ padding: 14,
37
+ border: "1px solid #333",
38
+ boxShadow: "0 20px 60px rgba(0,0,0,0.5)",
39
+ minWidth: 360,
40
+ }}
41
+ >
42
+ {/* Traffic lights + filename */}
43
+ <div
44
+ style={{
45
+ display: "flex",
46
+ alignItems: "center",
47
+ gap: 8,
48
+ marginBottom: 10,
49
+ }}
50
+ >
51
+ <div style={{ display: "flex", gap: 6 }}>
52
+ <div
53
+ style={{
54
+ width: 10,
55
+ height: 10,
56
+ borderRadius: 5,
57
+ background: "#FF5F56",
58
+ }}
59
+ />
60
+ <div
61
+ style={{
62
+ width: 10,
63
+ height: 10,
64
+ borderRadius: 5,
65
+ background: "#FFBD2E",
66
+ }}
67
+ />
68
+ <div
69
+ style={{
70
+ width: 10,
71
+ height: 10,
72
+ borderRadius: 5,
73
+ background: "#27CA40",
74
+ }}
75
+ />
76
+ </div>
77
+ <span
78
+ style={{
79
+ color: "#666",
80
+ fontSize: 11,
81
+ fontFamily: "monospace",
82
+ marginLeft: 8,
83
+ }}
84
+ >
85
+ {title}
86
+ </span>
87
+ </div>
88
+
89
+ {/* Code lines */}
90
+ <div
91
+ style={{
92
+ fontFamily: "SF Mono, Menlo, monospace",
93
+ fontSize: 11,
94
+ lineHeight: 1.5,
95
+ }}
96
+ >
97
+ {lines.map((line, i) => {
98
+ const lineDelay = startFrame + 8 + i * 4;
99
+ const lineOpacity = interpolate(frame - lineDelay, [0, 5], [0, 1], {
100
+ extrapolateLeft: "clamp",
101
+ extrapolateRight: "clamp",
102
+ });
103
+ let color = "#ABB2BF";
104
+ if (
105
+ line.includes("analytics.") ||
106
+ line.includes("braze.") ||
107
+ line.includes("engagementClient") ||
108
+ line.includes("engagement.")
109
+ )
110
+ color = "#61AFEF";
111
+ else if (
112
+ line.includes("setUserId") ||
113
+ line.includes("changeUser") ||
114
+ line.includes("getDeviceId")
115
+ )
116
+ color = "#E5C07B";
117
+ else if (line.includes('"')) color = "#98C379";
118
+ else if (line.match(/\d+\.\d+/) || line.match(/: \d+/))
119
+ color = "#D19A66";
120
+ else if (line.includes("//")) color = "#5C6370";
121
+ return (
122
+ <div key={i} style={{ opacity: lineOpacity, color, whiteSpace: "pre" }}>
123
+ {line}
124
+ </div>
125
+ );
126
+ })}
127
+ </div>
128
+ </div>
129
+ );
130
+ };
@@ -0,0 +1,60 @@
1
+ import React from "react";
2
+ import { useCurrentFrame, useVideoConfig, spring, interpolate } from "remotion";
3
+
4
+ export const EventParticle: React.FC<{
5
+ startFrame: number;
6
+ startX: number;
7
+ startY: number;
8
+ endX: number;
9
+ endY: number;
10
+ color: string;
11
+ label: string;
12
+ }> = ({ startFrame, startX, startY, endX, endY, color, label }) => {
13
+ const frame = useCurrentFrame();
14
+ const { fps } = useVideoConfig();
15
+ const localFrame = frame - startFrame;
16
+ if (localFrame < 0 || localFrame > 50) return null;
17
+
18
+ const progress = spring({
19
+ frame: localFrame,
20
+ fps,
21
+ config: { damping: 25, stiffness: 80 },
22
+ });
23
+ const midX = (startX + endX) / 2;
24
+ const midY = Math.min(startY, endY) - 100;
25
+ const t = progress;
26
+ const x =
27
+ (1 - t) * (1 - t) * startX + 2 * (1 - t) * t * midX + t * t * endX;
28
+ const y =
29
+ (1 - t) * (1 - t) * startY + 2 * (1 - t) * t * midY + t * t * endY;
30
+ const scale = interpolate(localFrame, [0, 10, 40, 50], [0, 1.2, 1, 0], {
31
+ extrapolateRight: "clamp",
32
+ });
33
+ const opacity = interpolate(localFrame, [40, 50], [1, 0], {
34
+ extrapolateLeft: "clamp",
35
+ extrapolateRight: "clamp",
36
+ });
37
+
38
+ return (
39
+ <div
40
+ style={{
41
+ position: "absolute",
42
+ left: x - 60,
43
+ top: y - 12,
44
+ transform: `scale(${scale})`,
45
+ opacity,
46
+ background: color,
47
+ padding: "6px 14px",
48
+ borderRadius: 20,
49
+ color: "white",
50
+ fontSize: 11,
51
+ fontFamily: "SF Mono, monospace",
52
+ fontWeight: "600",
53
+ boxShadow: `0 4px 20px ${color}66`,
54
+ whiteSpace: "nowrap",
55
+ }}
56
+ >
57
+ {label}
58
+ </div>
59
+ );
60
+ };