@waits/cadence 0.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 (82) hide show
  1. package/LICENSE +21 -0
  2. package/MOTION.md +70 -0
  3. package/README.md +109 -0
  4. package/bin/cli.mjs +21 -0
  5. package/package.json +59 -0
  6. package/prompts/art/compose.ts +18 -0
  7. package/prompts/art/fantasy.ts +15 -0
  8. package/prompts/art/landmarks.ts +49 -0
  9. package/prompts/art/style.ts +20 -0
  10. package/public/backgrounds/barton-springs.png +0 -0
  11. package/public/backgrounds/capitol.png +0 -0
  12. package/public/backgrounds/congress.png +0 -0
  13. package/public/backgrounds/enchanted-rock.png +0 -0
  14. package/public/backgrounds/hamilton-pool.png +0 -0
  15. package/public/backgrounds/mount-bonnell.png +0 -0
  16. package/public/backgrounds/pennybacker.png +0 -0
  17. package/public/backgrounds/ut-tower.png +0 -0
  18. package/scripts/_pkg.ts +28 -0
  19. package/scripts/audit.ts +69 -0
  20. package/scripts/changes.ts +70 -0
  21. package/scripts/check-motion.ts +37 -0
  22. package/scripts/cli.ts +79 -0
  23. package/scripts/generate-art.ts +72 -0
  24. package/scripts/guide.ts +114 -0
  25. package/scripts/make.ts +76 -0
  26. package/scripts/redesign.ts +83 -0
  27. package/scripts/render.ts +62 -0
  28. package/scripts/smoke.ts +43 -0
  29. package/scripts/sync-skill.ts +43 -0
  30. package/scripts/theme.ts +97 -0
  31. package/src/Root.tsx +51 -0
  32. package/src/adapters/index.ts +72 -0
  33. package/src/adapters/parse.ts +65 -0
  34. package/src/adapters/types.ts +24 -0
  35. package/src/brand/fonts.ts +74 -0
  36. package/src/brand/tokens.ts +31 -0
  37. package/src/brand/tone.ts +25 -0
  38. package/src/code/highlight.ts +90 -0
  39. package/src/components/Background.tsx +75 -0
  40. package/src/components/Changelog.tsx +17 -0
  41. package/src/components/ChangelogScene.tsx +117 -0
  42. package/src/components/CodeWindow.tsx +106 -0
  43. package/src/components/ContactSheet.tsx +44 -0
  44. package/src/components/Headline.tsx +73 -0
  45. package/src/components/MotionReel.tsx +108 -0
  46. package/src/components/panels/DataTable.tsx +34 -0
  47. package/src/components/panels/Diagram.tsx +67 -0
  48. package/src/components/panels/Feed.tsx +46 -0
  49. package/src/components/panels/Fork.tsx +86 -0
  50. package/src/components/panels/PanelCard.tsx +44 -0
  51. package/src/components/panels/Proof.tsx +64 -0
  52. package/src/components/panels/Stat.tsx +28 -0
  53. package/src/components/panels/Status.tsx +41 -0
  54. package/src/components/panels/StreamResume.tsx +48 -0
  55. package/src/components/panels/UploadProgress.tsx +42 -0
  56. package/src/components/panels/index.tsx +34 -0
  57. package/src/content/clarinet-3.18.beats.ts +58 -0
  58. package/src/content/gradient-demo.beats.ts +29 -0
  59. package/src/content/index-decoded.beats.ts +93 -0
  60. package/src/content/mainnet-launch.beats.ts +70 -0
  61. package/src/content/panels.beats.ts +40 -0
  62. package/src/content/sdk-6.5-mempool.beats.ts +67 -0
  63. package/src/content/streams-launch.beats.ts +143 -0
  64. package/src/content/streams.beats.ts +46 -0
  65. package/src/index.ts +4 -0
  66. package/src/motion/names.ts +29 -0
  67. package/src/motion/presets.ts +67 -0
  68. package/src/motion/useMotion.ts +63 -0
  69. package/src/prepare.ts +33 -0
  70. package/src/schema/beats.ts +161 -0
  71. package/src/templates/changelog-reel.ts +28 -0
  72. package/src/templates/feature-launch.ts +26 -0
  73. package/src/templates/index.ts +13 -0
  74. package/src/templates/milestone.ts +25 -0
  75. package/src/templates/parts.ts +32 -0
  76. package/src/templates/types.ts +31 -0
  77. package/src/theme/default.ts +39 -0
  78. package/src/theme/derive.ts +119 -0
  79. package/src/theme/index.ts +33 -0
  80. package/src/theme/library.ts +60 -0
  81. package/src/theme/slate.ts +39 -0
  82. package/src/theme/types.ts +67 -0
@@ -0,0 +1,86 @@
1
+ import { interpolate, useCurrentFrame } from "remotion";
2
+ import { COLORS, EASE, RADIUS } from "../../brand/tokens";
3
+ import { FONTS } from "../../brand/fonts";
4
+ import { enterStyle, staggerDelay } from "../../motion/presets";
5
+ import type { PanelSpec } from "../../schema/beats";
6
+ import { PanelCard, PanelHeader } from "./PanelCard";
7
+
8
+ type Spec = Extract<PanelSpec, { kind: "fork" }>;
9
+ type Block = Spec["blocks"][number];
10
+
11
+ const Chip: React.FC<{ block: Block; style?: React.CSSProperties }> = ({ block, style }) => {
12
+ const orphaned = block.state === "orphaned";
13
+ const isNew = block.state === "new";
14
+ return (
15
+ <span
16
+ style={{
17
+ fontFamily: FONTS.mono,
18
+ fontSize: 16,
19
+ fontVariantNumeric: "tabular-nums",
20
+ padding: "7px 12px",
21
+ borderRadius: RADIUS.md,
22
+ whiteSpace: "nowrap",
23
+ background: isNew ? COLORS.signalBlueSoft : COLORS.chrome,
24
+ border: `1px solid ${isNew ? COLORS.signalBlueBorder : COLORS.hairline}`,
25
+ color: isNew ? COLORS.signalBlue : orphaned ? COLORS.textMuted : COLORS.ink,
26
+ textDecoration: orphaned ? "line-through" : "none",
27
+ opacity: orphaned ? 0.6 : 1,
28
+ ...style,
29
+ }}
30
+ >
31
+ {block.height} · {block.hash}
32
+ </span>
33
+ );
34
+ };
35
+
36
+ /** An honest fork: the canonical chain forks, the orphan is archived, the new tip lights up. */
37
+ export const ForkPanel: React.FC<{ spec: Spec }> = ({ spec }) => {
38
+ const frame = useCurrentFrame();
39
+ const canonical = spec.blocks.filter((b) => b.state === "canonical");
40
+ const orphan = spec.blocks.find((b) => b.state === "orphaned");
41
+ const newTip = spec.blocks.find((b) => b.state === "new");
42
+
43
+ const forkIn = interpolate(frame, [54, 70], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: EASE.smooth });
44
+ const noteIn = interpolate(frame, [76, 92], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: EASE.smooth });
45
+
46
+ return (
47
+ <PanelCard motion={spec.motion}>
48
+ <PanelHeader>
49
+ <span style={{ color: COLORS.ink, fontSize: 18, fontWeight: 600 }}>onReorg</span>
50
+ <span style={{ fontSize: 14, color: COLORS.textMuted }}>archive-on-reorg</span>
51
+ </PanelHeader>
52
+
53
+ <div style={{ padding: "24px 26px" }}>
54
+ {/* canonical chain */}
55
+ <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
56
+ {canonical.map((b, i) => {
57
+ const start = staggerDelay(i, 9);
58
+ const p = interpolate(frame, [start, start + 14], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: EASE.smooth });
59
+ return (
60
+ <span key={b.height} style={{ display: "inline-flex", alignItems: "center", gap: 10, ...enterStyle("stagger", p) }}>
61
+ {i > 0 && <span style={{ color: COLORS.textMuted }}>→</span>}
62
+ <Chip block={b} />
63
+ </span>
64
+ );
65
+ })}
66
+ </div>
67
+
68
+ {/* the fork: orphan archived, new canonical lit */}
69
+ <div style={{ display: "flex", alignItems: "center", gap: 12, marginTop: 16, opacity: forkIn, transform: `translateY(${(1 - forkIn) * 8}px)` }}>
70
+ {orphan && (
71
+ <span style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>
72
+ <Chip block={orphan} />
73
+ <span style={{ fontFamily: FONTS.mono, fontSize: 11, fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.06em", color: COLORS.gold, background: COLORS.goldSoft, padding: "3px 8px", borderRadius: RADIUS.sm }}>archived</span>
74
+ </span>
75
+ )}
76
+ {orphan && newTip && <span style={{ color: COLORS.textMuted }}>↳</span>}
77
+ {newTip && <Chip block={newTip} />}
78
+ </div>
79
+
80
+ <div style={{ marginTop: 18, fontFamily: FONTS.note, fontSize: 22, color: COLORS.signalBlue, opacity: noteIn }}>
81
+ rewound to {spec.rewindTo} — archived, never lost
82
+ </div>
83
+ </div>
84
+ </PanelCard>
85
+ );
86
+ };
@@ -0,0 +1,44 @@
1
+ import type { CSSProperties, ReactNode } from "react";
2
+ import { COLORS, FLOAT_SHADOW, RADIUS } from "../../brand/tokens";
3
+ import { FONTS } from "../../brand/fonts";
4
+ import { useMotion, type MotionSpec } from "../../motion/useMotion";
5
+
6
+ /** Floating Field Notebook panel shell (translucent paper, hairline, float shadow). */
7
+ export const PanelCard: React.FC<{ motion?: MotionSpec; style?: CSSProperties; children: ReactNode }> = ({
8
+ motion = { enter: "settle", delay: 30 },
9
+ style,
10
+ children,
11
+ }) => {
12
+ const m = useMotion(motion);
13
+ return (
14
+ <div
15
+ style={{
16
+ width: "100%",
17
+ borderRadius: RADIUS.xl + 8,
18
+ background: "rgba(252,251,247,0.95)",
19
+ boxShadow: FLOAT_SHADOW,
20
+ border: "1px solid rgba(255,255,255,0.6)",
21
+ overflow: "hidden",
22
+ fontFamily: FONTS.mono,
23
+ ...m,
24
+ ...style,
25
+ }}
26
+ >
27
+ {children}
28
+ </div>
29
+ );
30
+ };
31
+
32
+ export const PanelHeader: React.FC<{ children: ReactNode }> = ({ children }) => (
33
+ <div
34
+ style={{
35
+ display: "flex",
36
+ alignItems: "center",
37
+ justifyContent: "space-between",
38
+ padding: "20px 26px",
39
+ borderBottom: `1px solid ${COLORS.hairline}`,
40
+ }}
41
+ >
42
+ {children}
43
+ </div>
44
+ );
@@ -0,0 +1,64 @@
1
+ import { interpolate, useCurrentFrame } from "remotion";
2
+ import { COLORS, EASE, RADIUS } from "../../brand/tokens";
3
+ import { FONTS } from "../../brand/fonts";
4
+ import { drawDashoffset, typewriterChars } from "../../motion/presets";
5
+ import type { PanelSpec } from "../../schema/beats";
6
+ import { PanelCard, PanelHeader } from "./PanelCard";
7
+
8
+ type Spec = Extract<PanelSpec, { kind: "proof" }>;
9
+
10
+ /** Group a hex signature into 4-char blocks; truncate the middle to a receipt line. */
11
+ const formatSig = (hex: string) => {
12
+ const clean = hex.replace(/[^0-9a-f]/gi, "");
13
+ const head = clean.slice(0, 24).match(/.{1,4}/g)?.join(" ") ?? clean;
14
+ const tail = clean.slice(-8).match(/.{1,4}/g)?.join(" ") ?? "";
15
+ return `${head} … ${tail}`;
16
+ };
17
+
18
+ /** ed25519 signature as the hero — an honest cryptographic receipt, not a sticker. */
19
+ export const ProofPanel: React.FC<{ spec: Spec }> = ({ spec }) => {
20
+ const frame = useCurrentFrame();
21
+ const sig = formatSig(spec.signature);
22
+
23
+ const eventIn = interpolate(frame, [22, 38], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: EASE.smooth });
24
+ const sigReveal = interpolate(frame, [40, 78], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: EASE.smooth });
25
+ const checkP = interpolate(frame, [82, 98], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: EASE.smooth });
26
+ const verifiedIn = interpolate(frame, [92, 104], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: EASE.smooth });
27
+
28
+ const CHECK_LEN = 26;
29
+
30
+ return (
31
+ <PanelCard motion={spec.motion}>
32
+ <PanelHeader>
33
+ <span style={{ color: COLORS.ink, fontSize: 18, fontWeight: 600 }}>streams.consume</span>
34
+ <span style={{ fontSize: 13, fontWeight: 600, color: COLORS.signalBlue, background: COLORS.signalBlueSoft, padding: "4px 10px", borderRadius: RADIUS.full, letterSpacing: "0.02em" }}>verify: true</span>
35
+ </PanelHeader>
36
+
37
+ <div style={{ padding: "22px 26px" }}>
38
+ {/* event line + its cursor */}
39
+ <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", opacity: eventIn, transform: `translateY(${(1 - eventIn) * 8}px)` }}>
40
+ <span style={{ fontSize: 19, color: COLORS.ink }}>{spec.eventLine}</span>
41
+ <span style={{ fontSize: 16, color: COLORS.textMuted, fontVariantNumeric: "tabular-nums" }}>{spec.cursor}</span>
42
+ </div>
43
+
44
+ <div style={{ height: 1, background: COLORS.hairline, margin: "18px 0" }} />
45
+
46
+ {/* the signature is the hero */}
47
+ <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase", color: COLORS.textMuted }}>signature · ed25519</div>
48
+ <div style={{ minHeight: 56, marginTop: 8, fontSize: 18, lineHeight: "28px", color: COLORS.ink, fontVariantNumeric: "tabular-nums", letterSpacing: "0.04em", whiteSpace: "pre-wrap" }}>
49
+ {sig.slice(0, typewriterChars(sigReveal, sig.length))}
50
+ </div>
51
+ <div style={{ marginTop: 6, fontSize: 15, color: COLORS.textMuted }}>key {spec.keyId}</div>
52
+
53
+ {/* verification resolves with a drawn check (reserved height, no shift) */}
54
+ <div style={{ display: "flex", alignItems: "center", gap: 10, marginTop: 18, height: 26 }}>
55
+ <svg width={26} height={26} viewBox="0 0 26 26">
56
+ <circle cx={13} cy={13} r={11} fill="none" stroke={COLORS.signalBlue} strokeWidth={1.5} opacity={0.35} />
57
+ <path d="M8 13.5 L11.5 17 L18 9.5" fill="none" stroke={COLORS.signalBlue} strokeWidth={2.2} strokeLinecap="round" strokeLinejoin="round" strokeDasharray={CHECK_LEN} strokeDashoffset={drawDashoffset(checkP, CHECK_LEN)} />
58
+ </svg>
59
+ <span style={{ fontSize: 17, fontWeight: 600, color: COLORS.signalBlue, opacity: verifiedIn }}>verified</span>
60
+ </div>
61
+ </div>
62
+ </PanelCard>
63
+ );
64
+ };
@@ -0,0 +1,28 @@
1
+ import { interpolate, useCurrentFrame } from "remotion";
2
+ import { COLORS, EASE } from "../../brand/tokens";
3
+ import { FONTS } from "../../brand/fonts";
4
+ import { countValue } from "../../motion/presets";
5
+ import type { PanelSpec } from "../../schema/beats";
6
+ import { PanelCard } from "./PanelCard";
7
+
8
+ type Spec = Extract<PanelSpec, { kind: "stat" }>;
9
+
10
+ /** A big headline number — the core announcement primitive. Counts up when the
11
+ * value is purely numeric ("10,000,000"); otherwise reveals as-is ("live"). */
12
+ export const StatPanel: React.FC<{ spec: Spec }> = ({ spec }) => {
13
+ const frame = useCurrentFrame();
14
+ const clean = spec.value.replace(/[, ]/g, "");
15
+ const isNumeric = /^\d+(\.\d+)?$/.test(clean);
16
+ const p = interpolate(frame, [18, 52], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: EASE.smooth });
17
+ const display = isNumeric ? Math.round(countValue(p, Number(clean))).toLocaleString() : spec.value;
18
+
19
+ return (
20
+ <PanelCard motion={spec.motion}>
21
+ <div style={{ padding: "52px 56px", textAlign: "center" }}>
22
+ <div style={{ fontFamily: FONTS.display, fontSize: 100, fontWeight: 600, letterSpacing: "-0.03em", lineHeight: 1, color: COLORS.ink, fontVariantNumeric: "tabular-nums" }}>{display}</div>
23
+ <div style={{ fontFamily: FONTS.body, fontSize: 24, color: COLORS.textMuted, marginTop: 14 }}>{spec.label}</div>
24
+ {spec.sub && <div style={{ fontFamily: FONTS.note, fontSize: 28, color: COLORS.signalBlue, marginTop: 10 }}>{spec.sub}</div>}
25
+ </div>
26
+ </PanelCard>
27
+ );
28
+ };
@@ -0,0 +1,41 @@
1
+ import { interpolate, useCurrentFrame } from "remotion";
2
+ import { COLORS, EASE, RADIUS } from "../../brand/tokens";
3
+ import { FONTS } from "../../brand/fonts";
4
+ import { enterStyle, staggerDelay } from "../../motion/presets";
5
+ import type { PanelSpec } from "../../schema/beats";
6
+ import { PanelCard, PanelHeader } from "./PanelCard";
7
+
8
+ type Spec = Extract<PanelSpec, { kind: "status" }>;
9
+
10
+ const STATE_COLOR: Record<Spec["services"][number]["state"], string> = {
11
+ ok: COLORS.successGreen,
12
+ syncing: COLORS.infoBlue,
13
+ error: COLORS.dangerRed,
14
+ idle: COLORS.textMuted as string,
15
+ };
16
+
17
+ export const StatusPanel: React.FC<{ spec: Spec }> = ({ spec }) => {
18
+ const frame = useCurrentFrame();
19
+ return (
20
+ <PanelCard motion={spec.motion}>
21
+ <PanelHeader>
22
+ <span style={{ color: COLORS.ink, fontSize: 19, fontWeight: 600 }}>{spec.title}</span>
23
+ </PanelHeader>
24
+ <div style={{ padding: "12px 26px 22px" }}>
25
+ {spec.services.map((s, i) => {
26
+ const start = staggerDelay(i, 9);
27
+ const p = interpolate(frame, [start, start + 14], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: EASE.smooth });
28
+ return (
29
+ <div key={s.name} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "14px 0", borderBottom: i < spec.services.length - 1 ? `1px solid ${COLORS.hairline}` : "none", ...enterStyle("stagger", p) }}>
30
+ <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
31
+ <span style={{ width: 9, height: 9, borderRadius: "50%", background: STATE_COLOR[s.state] }} />
32
+ <span style={{ fontSize: 18, color: COLORS.ink }}>{s.name}</span>
33
+ </div>
34
+ <span style={{ fontFamily: FONTS.mono, fontSize: 13, textTransform: "uppercase", letterSpacing: "0.05em", color: STATE_COLOR[s.state], background: `${STATE_COLOR[s.state]}1f`, padding: "3px 8px", borderRadius: RADIUS.sm }}>{s.detail ?? s.state}</span>
35
+ </div>
36
+ );
37
+ })}
38
+ </div>
39
+ </PanelCard>
40
+ );
41
+ };
@@ -0,0 +1,48 @@
1
+ import { interpolate, useCurrentFrame } from "remotion";
2
+ import { COLORS, EASE, RADIUS } from "../../brand/tokens";
3
+ import { FONTS } from "../../brand/fonts";
4
+ import { enterStyle, staggerDelay } from "../../motion/presets";
5
+ import type { PanelSpec } from "../../schema/beats";
6
+ import { PanelCard, PanelHeader } from "./PanelCard";
7
+
8
+ type Spec = Extract<PanelSpec, { kind: "stream-resume" }>;
9
+
10
+ const ROW_START = 40;
11
+ const ROW_STAGGER = 12;
12
+
13
+ /** The cursor is the hero: a precise resume point, items flowing past it. */
14
+ export const StreamResumePanel: React.FC<{ spec: Spec }> = ({ spec }) => {
15
+ const frame = useCurrentFrame();
16
+ const chipIn = interpolate(frame, [18, 34], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: EASE.smooth });
17
+
18
+ return (
19
+ <PanelCard motion={spec.motion}>
20
+ <PanelHeader>
21
+ <span style={{ color: COLORS.ink, fontSize: 18, fontWeight: 600 }}>streams.consume</span>
22
+ <span style={{ fontSize: 14, color: COLORS.textMuted }}>exactly-once</span>
23
+ </PanelHeader>
24
+
25
+ <div style={{ padding: "20px 26px 22px" }}>
26
+ {/* resume marker */}
27
+ <div style={{ display: "flex", alignItems: "center", gap: 12, opacity: chipIn, transform: `translateY(${(1 - chipIn) * 8}px)` }}>
28
+ <span style={{ fontSize: 14, color: COLORS.textMuted }}>▸ resuming from</span>
29
+ <span style={{ fontSize: 19, fontWeight: 700, color: COLORS.signalBlue, background: COLORS.signalBlueSoft, padding: "5px 12px", borderRadius: RADIUS.md, fontVariantNumeric: "tabular-nums", letterSpacing: "0.02em" }}>{spec.fromCursor}</span>
30
+ </div>
31
+
32
+ <div style={{ height: 1, borderTop: `1px dashed ${COLORS.hairline}`, margin: "16px 0 6px" }} />
33
+
34
+ {/* events flow in past the cursor, each tagged with its own cursor */}
35
+ {spec.rows.map((row, i) => {
36
+ const start = ROW_START + staggerDelay(i, ROW_STAGGER);
37
+ const p = interpolate(frame, [start, start + 16], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: EASE.smooth });
38
+ return (
39
+ <div key={i} style={{ display: "flex", alignItems: "center", gap: 16, padding: "12px 4px", borderBottom: i < spec.rows.length - 1 ? `1px solid ${COLORS.hairline}` : "none", ...enterStyle("stagger", p) }}>
40
+ <span style={{ fontSize: 15, color: COLORS.textMuted, fontVariantNumeric: "tabular-nums", minWidth: 96 }}>{row.cursor}</span>
41
+ <span style={{ fontSize: 18, color: COLORS.ink }}>{row.label}</span>
42
+ </div>
43
+ );
44
+ })}
45
+ </div>
46
+ </PanelCard>
47
+ );
48
+ };
@@ -0,0 +1,42 @@
1
+ import { interpolate, useCurrentFrame, useVideoConfig } from "remotion";
2
+ import { COLORS, EASE, RADIUS } from "../../brand/tokens";
3
+ import { FONTS } from "../../brand/fonts";
4
+ import type { PanelSpec } from "../../schema/beats";
5
+ import { PanelCard, PanelHeader } from "./PanelCard";
6
+
7
+ type Spec = Extract<PanelSpec, { kind: "upload-progress" }>;
8
+
9
+ export const UploadProgressPanel: React.FC<{ spec: Spec }> = ({ spec }) => {
10
+ const frame = useCurrentFrame();
11
+ const { fps } = useVideoConfig();
12
+ // upload climbs from ~frame 40 to ~frame 130
13
+ const pct = interpolate(frame, [40, 40 + fps * 3], [0, 100], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: EASE.smooth });
14
+ const part = Math.round((pct / 100) * spec.parts);
15
+ const sent = ((pct / 100) * spec.sizeMB).toFixed(0);
16
+
17
+ return (
18
+ <PanelCard motion={spec.motion}>
19
+ <PanelHeader>
20
+ <span style={{ color: COLORS.ink, fontSize: 19, fontWeight: 600 }}>↑ {spec.file}</span>
21
+ <span style={{ fontSize: 14, fontWeight: 600, color: "#c2410c", background: "rgba(194,65,12,0.10)", padding: "5px 11px", borderRadius: RADIUS.full }}>● Uploading…</span>
22
+ </PanelHeader>
23
+ <div style={{ padding: "26px" }}>
24
+ <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 16 }}>
25
+ <span style={{ fontSize: 40, fontWeight: 700, color: COLORS.ink, fontVariantNumeric: "tabular-nums" }}>{Math.round(pct)}%</span>
26
+ <span style={{ fontSize: 17, color: COLORS.textMuted }}>{sent} / {spec.sizeMB} MB · part {part}/{spec.parts}</span>
27
+ </div>
28
+ <div style={{ height: 10, borderRadius: RADIUS.full, background: "rgba(0,0,0,0.08)", overflow: "hidden" }}>
29
+ <div style={{ height: "100%", width: `${pct}%`, background: COLORS.signalBlue, borderRadius: RADIUS.full }} />
30
+ </div>
31
+ <div style={{ display: "flex", gap: 14, marginTop: 22 }}>
32
+ <Btn label="control.pause()" />
33
+ <Btn label="control.resume()" ghost />
34
+ </div>
35
+ </div>
36
+ </PanelCard>
37
+ );
38
+ };
39
+
40
+ const Btn: React.FC<{ label: string; ghost?: boolean }> = ({ label, ghost }) => (
41
+ <span style={{ flex: 1, textAlign: "center", padding: "12px", borderRadius: RADIUS.md, fontSize: 17, fontWeight: 600, color: ghost ? COLORS.textMuted : "#c2410c", background: ghost ? "transparent" : "rgba(194,65,12,0.10)", border: `1px solid ${ghost ? COLORS.hairline : "rgba(194,65,12,0.25)"}` }}>{label}</span>
42
+ );
@@ -0,0 +1,34 @@
1
+ import type { PanelSpec } from "../../schema/beats";
2
+ import { FeedPanel } from "./Feed";
3
+ import { UploadProgressPanel } from "./UploadProgress";
4
+ import { DataTablePanel } from "./DataTable";
5
+ import { StatusPanel } from "./Status";
6
+ import { StatPanel } from "./Stat";
7
+ import { ProofPanel } from "./Proof";
8
+ import { StreamResumePanel } from "./StreamResume";
9
+ import { ForkPanel } from "./Fork";
10
+ import { DiagramPanel } from "./Diagram";
11
+
12
+ /** Resolve a PanelSpec (by its `kind`) to the right panel component. */
13
+ export const Panel: React.FC<{ spec: PanelSpec }> = ({ spec }) => {
14
+ switch (spec.kind) {
15
+ case "feed":
16
+ return <FeedPanel spec={spec} />;
17
+ case "upload-progress":
18
+ return <UploadProgressPanel spec={spec} />;
19
+ case "data-table":
20
+ return <DataTablePanel spec={spec} />;
21
+ case "status":
22
+ return <StatusPanel spec={spec} />;
23
+ case "stat":
24
+ return <StatPanel spec={spec} />;
25
+ case "proof":
26
+ return <ProofPanel spec={spec} />;
27
+ case "stream-resume":
28
+ return <StreamResumePanel spec={spec} />;
29
+ case "fork":
30
+ return <ForkPanel spec={spec} />;
31
+ case "diagram":
32
+ return <DiagramPanel spec={spec} />;
33
+ }
34
+ };
@@ -0,0 +1,58 @@
1
+ import type { ChangelogInput } from "../schema/beats";
2
+
3
+ /**
4
+ * T0.1b — generalization proof on a TRUE external repo (stx-labs/clarinet, a Rust
5
+ * CLI, not a TS SDK). Sourced generically:
6
+ * - latest substantive release: GitHub release v3.18.0 → "Clarity 6 preview" + linter
7
+ * - honest commands: README (clarinet check --show-lints, brew install clarinet)
8
+ * No Secondlayer anything — different project, different shape (CLI/bash + checks).
9
+ */
10
+ const BG = "backgrounds/pennybacker.png";
11
+
12
+ const video: ChangelogInput = {
13
+ format: "16x9",
14
+ beats: [
15
+ {
16
+ id: "opener",
17
+ durationInFrames: 150,
18
+ background: { src: BG },
19
+ eyebrow: "new in clarinet 3.18",
20
+ headline: "Clarity 6, in preview.",
21
+ },
22
+ {
23
+ id: "lint",
24
+ durationInFrames: 220,
25
+ background: { src: BG },
26
+ eyebrow: "clarinet check",
27
+ headline: "Lint as you check.",
28
+ code: {
29
+ filename: "terminal",
30
+ lang: "bash",
31
+ source: `clarinet contract new counter
32
+ clarinet check --show-lints`,
33
+ },
34
+ panel: {
35
+ kind: "status",
36
+ title: "clarinet check",
37
+ services: [
38
+ { name: "counter.clar", state: "ok", detail: "checked" },
39
+ { name: "type analysis", state: "ok" },
40
+ { name: "unnecessary_tuple", state: "idle", detail: "style lint" },
41
+ { name: "clarity 6", state: "ok", detail: "preview" },
42
+ ],
43
+ },
44
+ },
45
+ {
46
+ id: "cta",
47
+ durationInFrames: 160,
48
+ background: { src: BG },
49
+ headline: "Start building.",
50
+ layout: "center",
51
+ code: { filename: "terminal", lang: "bash", source: `brew install clarinet` },
52
+ badge: "v3.18",
53
+ caption: "write · test · deploy on Stacks",
54
+ },
55
+ ],
56
+ };
57
+
58
+ export default video;
@@ -0,0 +1,29 @@
1
+ import type { ChangelogInput } from "../schema/beats";
2
+
3
+ /**
4
+ * Proves the AI-free style packs: a gradient background — no painting, no OpenAI,
5
+ * no committed art. A brand-new user can render a video out of the box.
6
+ */
7
+ const GRADIENT: [string, string] = ["#312e81", "#0b1120"]; // indigo → slate
8
+
9
+ const video: ChangelogInput = {
10
+ format: "16x9",
11
+ beats: [
12
+ {
13
+ id: "opener",
14
+ durationInFrames: 150,
15
+ background: { gradient: GRADIENT, angle: 155 },
16
+ eyebrow: "milestone",
17
+ headline: "A million installs.",
18
+ },
19
+ {
20
+ id: "stat",
21
+ durationInFrames: 170,
22
+ background: { gradient: GRADIENT, angle: 155 },
23
+ headline: "Thank you.",
24
+ panel: { kind: "stat", value: "1,000,000", label: "downloads", sub: "and counting" },
25
+ },
26
+ ],
27
+ };
28
+
29
+ export default video;
@@ -0,0 +1,93 @@
1
+ import type { ChangelogInput } from "../schema/beats";
2
+
3
+ /**
4
+ * Real changelog: the Index full decoded layer (sdk@5.5). Three beats over one
5
+ * "Hill Country Sublime" painting (continuity, like the reference video) — art +
6
+ * motion vocabulary + engine, end to end.
7
+ */
8
+ const BG = "backgrounds/mount-bonnell.png";
9
+
10
+ const video: ChangelogInput = {
11
+ format: "16x9",
12
+ beats: [
13
+ {
14
+ id: "events",
15
+ durationInFrames: 235,
16
+ background: { src: BG },
17
+ eyebrow: "new in sdk 5.5",
18
+ headline: "Every event, decoded.",
19
+ code: {
20
+ filename: "events.ts",
21
+ lang: "ts",
22
+ source: `import { Secondlayer } from "@secondlayer/sdk";
23
+
24
+ const sl = new Secondlayer();
25
+
26
+ const { events } = await sl.index.events({
27
+ eventType: "ft_transfer",
28
+ limit: 50,
29
+ });`,
30
+ },
31
+ panel: {
32
+ kind: "feed",
33
+ title: "index.events",
34
+ subtitle: "ft_transfer",
35
+ rows: [
36
+ { badge: "sBTC", label: "SP2J6…WVEF", value: "1,200.00" },
37
+ { badge: "USDA", label: "SP3K9…X1A0", value: "48.50" },
38
+ { badge: "ALEX", label: "SPF8M…7QQC", value: "9,000.00" },
39
+ { badge: "sBTC", label: "SP1Y4…NZ2D", value: "150.00" },
40
+ { badge: "WELSH", label: "SPGR2…0KME", value: "73.25" },
41
+ ],
42
+ },
43
+ },
44
+ {
45
+ id: "contract-calls",
46
+ durationInFrames: 235,
47
+ background: { src: BG },
48
+ eyebrow: "new in sdk 5.5",
49
+ headline: "Contract calls, typed.",
50
+ code: {
51
+ filename: "calls.ts",
52
+ lang: "ts",
53
+ source: `const { calls } = await sl.index.contractCalls({
54
+ contract: "SP….amm-pool-v2",
55
+ function: "swap-x-for-y",
56
+ limit: 4,
57
+ });`,
58
+ },
59
+ panel: {
60
+ kind: "data-table",
61
+ title: "index.contractCalls",
62
+ columns: ["block", "function", "result"],
63
+ rows: [
64
+ ["951475", "swap-x-for-y", "(ok u1200)"],
65
+ ["951474", "swap-x-for-y", "(ok u48)"],
66
+ ["951472", "add-liquidity", "(ok u9000)"],
67
+ ["951470", "swap-x-for-y", "(ok u150)"],
68
+ ],
69
+ },
70
+ },
71
+ {
72
+ id: "pipeline",
73
+ durationInFrames: 180,
74
+ background: { src: BG },
75
+ headline: "Decoded once. Query forever.",
76
+ panel: {
77
+ kind: "diagram",
78
+ nodes: [
79
+ { id: "node", label: "Stacks node", type: "default" },
80
+ { id: "idx", label: "Indexer", type: "data" },
81
+ { id: "api", label: "Index API", type: "api" },
82
+ ],
83
+ edges: [
84
+ { from: "node", to: "idx", label: "raw events" },
85
+ { from: "idx", to: "api", label: "decoded" },
86
+ ],
87
+ note: "decoded once — query forever",
88
+ },
89
+ },
90
+ ],
91
+ };
92
+
93
+ export default video;
@@ -0,0 +1,70 @@
1
+ import type { ChangelogInput } from "../schema/beats";
2
+
3
+ /**
4
+ * A product *announcement* (not a changelog) — same engine, different shape:
5
+ * opener title card → big stat → feature → install closer. Mirrors the arc of a
6
+ * classic launch video.
7
+ */
8
+ const PENNYBACKER = "backgrounds/pennybacker.png";
9
+ const BONNELL = "backgrounds/mount-bonnell.png";
10
+
11
+ const video: ChangelogInput = {
12
+ format: "16x9",
13
+ beats: [
14
+ // 1 · Opener — headline only over the painting
15
+ {
16
+ id: "opener",
17
+ durationInFrames: 150,
18
+ background: { src: PENNYBACKER },
19
+ eyebrow: "announcing",
20
+ headline: "Secondlayer is live on mainnet.",
21
+ },
22
+ // 2 · Big stat — the announcement primitive (counts up)
23
+ {
24
+ id: "scale",
25
+ durationInFrames: 170,
26
+ background: { src: BONNELL },
27
+ headline: "Indexed from genesis.",
28
+ panel: { kind: "stat", value: "10,000,000", label: "events decoded", sub: "block 0 → chain tip" },
29
+ },
30
+ // 3 · Feature — code + live panel
31
+ {
32
+ id: "query",
33
+ durationInFrames: 235,
34
+ background: { src: PENNYBACKER },
35
+ headline: "Query it in three lines.",
36
+ code: {
37
+ filename: "events.ts",
38
+ lang: "ts",
39
+ source: `const { events } = await sl.index.events({
40
+ eventType: "ft_transfer",
41
+ limit: 50,
42
+ });`,
43
+ },
44
+ panel: {
45
+ kind: "feed",
46
+ title: "index.events",
47
+ subtitle: "ft_transfer",
48
+ rows: [
49
+ { badge: "sBTC", label: "SP2J6…WVEF", value: "1,200.00" },
50
+ { badge: "USDA", label: "SP3K9…X1A0", value: "48.50" },
51
+ { badge: "ALEX", label: "SPF8M…7QQC", value: "9,000.00" },
52
+ { badge: "sBTC", label: "SP1Y4…NZ2D", value: "150.00" },
53
+ ],
54
+ },
55
+ },
56
+ // 4 · Install closer — centered terminal + tagline caption
57
+ {
58
+ id: "cta",
59
+ durationInFrames: 160,
60
+ background: { src: BONNELL },
61
+ headline: "Start building.",
62
+ layout: "center",
63
+ code: { filename: "terminal", lang: "bash", source: `bun add @secondlayer/sdk` },
64
+ badge: "v6.3",
65
+ caption: "index · streams · datasets · subgraphs · subscriptions",
66
+ },
67
+ ],
68
+ };
69
+
70
+ export default video;