@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,28 @@
1
+ import type { ChangelogInput } from "../schema/beats";
2
+ import { clip, cta, DEFAULT_BG, opener, type TBeat } from "./parts";
3
+ import type { Template } from "./types";
4
+
5
+ /**
6
+ * "The classic" — opener → a numbered changelog (data-table of the real
7
+ * features) → install closer. Deterministic and honest: no invented code, just
8
+ * the feature titles the adapter parsed and the real install command.
9
+ */
10
+ export const changelogReel: Template = (m, opts = {}) => {
11
+ const bg = opts.background ?? DEFAULT_BG;
12
+ const beats: ChangelogInput["beats"] = [opener(m, bg, opts.headline)];
13
+
14
+ const rows = m.features.slice(0, 6).map((f, i) => [String(i + 1), clip(f.title)]);
15
+ if (rows.length) {
16
+ beats.push({
17
+ id: "changes",
18
+ durationInFrames: Math.min(320, 150 + rows.length * 24),
19
+ background: bg,
20
+ headline: "What's new.",
21
+ panel: { kind: "data-table", title: `${m.product} ${m.version}`, columns: ["#", "change"], rows },
22
+ });
23
+ }
24
+
25
+ const close = cta(m, bg);
26
+ if (close) beats.push(close);
27
+ return { format: opts.format ?? "16x9", beats };
28
+ };
@@ -0,0 +1,26 @@
1
+ import type { ChangelogInput } from "../schema/beats";
2
+ import { clip, cta, DEFAULT_BG, opener } from "./parts";
3
+ import type { Template } from "./types";
4
+
5
+ /**
6
+ * "Feature launch" — opener → one title card per top feature → install. Cinematic,
7
+ * one feature gets one beat. Honest: just the real feature titles (no fake code).
8
+ */
9
+ export const featureLaunch: Template = (m, opts = {}) => {
10
+ const bg = opts.background ?? DEFAULT_BG;
11
+ const beats: ChangelogInput["beats"] = [opener(m, bg, opts.headline)];
12
+
13
+ m.features.slice(0, 4).forEach((f, i) => {
14
+ beats.push({
15
+ id: `feature-${i}`,
16
+ durationInFrames: 140,
17
+ background: bg,
18
+ eyebrow: `feature ${i + 1}`,
19
+ headline: clip(f.title, 40),
20
+ });
21
+ });
22
+
23
+ const close = cta(m, bg);
24
+ if (close) beats.push(close);
25
+ return { format: opts.format ?? "16x9", beats };
26
+ };
@@ -0,0 +1,13 @@
1
+ import type { Template } from "./types";
2
+ import { changelogReel } from "./changelog-reel";
3
+ import { featureLaunch } from "./feature-launch";
4
+ import { milestone } from "./milestone";
5
+
6
+ /** Off-the-shelf templates. A manifest + a template name → a video. */
7
+ export const TEMPLATES: Record<string, Template> = {
8
+ "changelog-reel": changelogReel,
9
+ "feature-launch": featureLaunch,
10
+ milestone,
11
+ };
12
+
13
+ export type { Template, TemplateOpts, BackgroundSpec } from "./types";
@@ -0,0 +1,25 @@
1
+ import type { ChangelogInput } from "../schema/beats";
2
+ import { cta, DEFAULT_BG, opener } from "./parts";
3
+ import type { Template } from "./types";
4
+
5
+ /**
6
+ * "Milestone" — opener → one big number → install. Uses `opts.stat` when given;
7
+ * otherwise an honest default drawn from the manifest (feature count this release).
8
+ */
9
+ export const milestone: Template = (m, opts = {}) => {
10
+ const bg = opts.background ?? DEFAULT_BG;
11
+ const stat = opts.stat ?? { value: String(m.features.length), label: "new features", sub: `in ${m.version}` };
12
+ const beats: ChangelogInput["beats"] = [
13
+ opener(m, bg, opts.headline),
14
+ {
15
+ id: "stat",
16
+ durationInFrames: 170,
17
+ background: bg,
18
+ headline: "By the numbers.",
19
+ panel: { kind: "stat", value: stat.value, label: stat.label, sub: stat.sub },
20
+ },
21
+ ];
22
+ const close = cta(m, bg);
23
+ if (close) beats.push(close);
24
+ return { format: opts.format ?? "16x9", beats };
25
+ };
@@ -0,0 +1,32 @@
1
+ import type { UpdateManifest } from "../adapters/types";
2
+ import type { ChangelogInput } from "../schema/beats";
3
+ import type { BackgroundSpec } from "./types";
4
+
5
+ export type TBeat = ChangelogInput["beats"][number];
6
+
7
+ /** Shared building blocks so every template stays consistent + honest. */
8
+ /** The default backdrop: procedural, theme-colored, no asset / no API key. */
9
+ export const DEFAULT_BG: BackgroundSpec = { shapes: true };
10
+ export const clip = (s: string, n = 52) => (s.length > n ? s.slice(0, n - 1).trimEnd() + "…" : s);
11
+
12
+ export const opener = (m: UpdateManifest, bg: BackgroundSpec, headline?: string): TBeat => ({
13
+ id: "opener",
14
+ durationInFrames: 150,
15
+ background: bg,
16
+ eyebrow: `new in ${m.product}`,
17
+ headline: headline ?? `${m.version} is out.`,
18
+ });
19
+
20
+ /** Install closer — only when the manifest actually knows the install command. */
21
+ export const cta = (m: UpdateManifest, bg: BackgroundSpec): TBeat | null =>
22
+ m.install
23
+ ? {
24
+ id: "cta",
25
+ durationInFrames: 160,
26
+ background: bg,
27
+ headline: "Get it.",
28
+ layout: "center",
29
+ code: { filename: "terminal", lang: "bash", source: m.install },
30
+ badge: `v${m.version}`,
31
+ }
32
+ : null;
@@ -0,0 +1,31 @@
1
+ import type { UpdateManifest } from "../adapters/types";
2
+ import type { ChangelogInput, Format } from "../schema/beats";
3
+
4
+ /** Loose background (authoring shape; defaults filled at render-validate time). */
5
+ export type BackgroundSpec = {
6
+ src?: string;
7
+ treatment?: "kenburns" | "static";
8
+ gradient?: [string, string];
9
+ angle?: number;
10
+ solid?: string;
11
+ /** Procedural theme-colored backdrop — the default. */
12
+ shapes?: boolean;
13
+ };
14
+
15
+ export type TemplateOpts = {
16
+ format?: Format;
17
+ /** Resolved background (style pack): image / gradient / solid. */
18
+ background?: BackgroundSpec;
19
+ /** Optional headline override for the opener. */
20
+ headline?: string;
21
+ /** For the milestone template: a big number to feature. */
22
+ stat?: { value: string; label: string; sub?: string };
23
+ };
24
+
25
+ /**
26
+ * A Template turns a parsed UpdateManifest into beats — deterministically, with
27
+ * no LLM and no fabricated code (it only shows what the manifest actually knows:
28
+ * feature titles + the real install command). The brain/skill path adds honest
29
+ * code on top; templates are the no-LLM path the GitHub Action uses.
30
+ */
31
+ export type Template = (manifest: UpdateManifest, opts?: TemplateOpts) => ChangelogInput;
@@ -0,0 +1,39 @@
1
+ import type { ThemeConfig } from "./types";
2
+
3
+ /** The default theme — warm tinted-neutrals, blue accent, gold version marker. */
4
+ export const defaultTheme: ThemeConfig = {
5
+ name: "default",
6
+ fonts: {
7
+ display: "Sora, ui-sans-serif, system-ui, sans-serif",
8
+ body: "Public Sans, ui-sans-serif, system-ui, sans-serif",
9
+ mono: "Fira Code, SFMono-Regular, Menlo, monospace",
10
+ note: "Caveat, cursive",
11
+ },
12
+ colors: {
13
+ ink: "#111111",
14
+ paper: "#fafafa",
15
+ paperElevated: "#ffffff",
16
+ chrome: "#f0f0f0",
17
+ hairline: "#e5e5e5",
18
+ hairlineHover: "#dddddd",
19
+ textMuted: "rgba(0,0,0,0.65)",
20
+ textDim: "rgba(0,0,0,0.12)",
21
+ signalBlue: "#2563eb",
22
+ signalBlueSoft: "rgba(37,99,235,0.10)",
23
+ signalBlueBorder: "rgba(37,99,235,0.33)",
24
+ markerPink: "#ff00aa",
25
+ gold: "#c08a2e",
26
+ goldSoft: "rgba(192,138,46,0.16)",
27
+ titleWhite: "#f7f6f2",
28
+ successGreen: "#22c55e",
29
+ warningYellow: "#eab308",
30
+ dangerRed: "#ef4444",
31
+ infoBlue: "#3b82f6",
32
+ accentTeal: "#1588b2",
33
+ },
34
+ floatShadow: "0 30px 80px rgba(30,41,59,0.28), 0 2px 6px rgba(30,41,59,0.12)",
35
+ codeBg: "#f7f5ee",
36
+ caretBg: "rgba(252,249,242,0.95)",
37
+ codeTheme: { fg: "#1f2937", kw: "#0e9488", nw: "#8250df", str: "#c2410c", num: "#7c3aed", fn: "#2563eb", punct: "#5b6470", comment: "#9aa0a6" },
38
+ backdrop: ["#e6edff", "#f8faff"],
39
+ };
@@ -0,0 +1,119 @@
1
+ import { defaultTheme } from "./default";
2
+ import type { CodeTheme, ThemeConfig, ThemeFonts } from "./types";
3
+
4
+ /** "#abc" | "#aabbcc" → {r,g,b}. */
5
+ function parseHex(hex: string): { r: number; g: number; b: number } {
6
+ let h = hex.replace("#", "").trim();
7
+ if (h.length === 3) h = h.split("").map((c) => c + c).join("");
8
+ return { r: parseInt(h.slice(0, 2), 16), g: parseInt(h.slice(2, 4), 16), b: parseInt(h.slice(4, 6), 16) };
9
+ }
10
+ const rgba = (hex: string, a: number) => {
11
+ const { r, g, b } = parseHex(hex);
12
+ return `rgba(${r},${g},${b},${a})`;
13
+ };
14
+
15
+ /** hex → HSL (h 0-360, s/l 0-100). */
16
+ function hexToHsl(hex: string): [number, number, number] {
17
+ const { r, g, b } = parseHex(hex);
18
+ const rn = r / 255, gn = g / 255, bn = b / 255;
19
+ const mx = Math.max(rn, gn, bn), mn = Math.min(rn, gn, bn), d = mx - mn;
20
+ const l = (mx + mn) / 2;
21
+ let h = 0;
22
+ const s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
23
+ if (d !== 0) {
24
+ if (mx === rn) h = ((gn - bn) / d) % 6;
25
+ else if (mx === gn) h = (bn - rn) / d + 2;
26
+ else h = (rn - gn) / d + 4;
27
+ h *= 60;
28
+ if (h < 0) h += 360;
29
+ }
30
+ return [h, s * 100, l * 100];
31
+ }
32
+ /** HSL → hex. */
33
+ function hslToHex(h: number, s: number, l: number): string {
34
+ h = ((h % 360) + 360) % 360;
35
+ s /= 100;
36
+ l /= 100;
37
+ const c = (1 - Math.abs(2 * l - 1)) * s;
38
+ const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
39
+ const m = l - c / 2;
40
+ let [r, g, b] = [0, 0, 0];
41
+ if (h < 60) [r, g, b] = [c, x, 0];
42
+ else if (h < 120) [r, g, b] = [x, c, 0];
43
+ else if (h < 180) [r, g, b] = [0, c, x];
44
+ else if (h < 240) [r, g, b] = [0, x, c];
45
+ else if (h < 300) [r, g, b] = [x, 0, c];
46
+ else [r, g, b] = [c, 0, x];
47
+ const to = (v: number) => Math.round((v + m) * 255).toString(16).padStart(2, "0");
48
+ return `#${to(r)}${to(g)}${to(b)}`;
49
+ }
50
+
51
+ /**
52
+ * A readable syntax palette tied to the accent: keywords sit at the accent's
53
+ * complement, functions take the accent, numbers a violet rotation, strings a
54
+ * fixed warm tone (kept independent so they never clash). Punctuation/comments
55
+ * are low-saturation grays carrying a hint of the accent hue.
56
+ */
57
+ function deriveCodeTheme(accent: string): CodeTheme {
58
+ const [h] = hexToHsl(accent);
59
+ return {
60
+ fg: hslToHex(h, 16, 20),
61
+ kw: hslToHex(h + 165, 62, 40),
62
+ nw: hslToHex(h + 90, 55, 46),
63
+ str: hslToHex(28, 72, 42),
64
+ num: hslToHex(h + 270, 60, 50),
65
+ fn: hslToHex(h, 70, 46),
66
+ punct: hslToHex(h, 10, 42),
67
+ comment: hslToHex(h, 8, 62),
68
+ };
69
+ }
70
+
71
+ /** Light [from, to] gradient (pale tints of the accent) for the procedural backdrop. */
72
+ function deriveBackdrop(accent: string): [string, string] {
73
+ const [h] = hexToHsl(accent);
74
+ return [hslToHex(h, 58, 90), hslToHex(h + 14, 42, 97)];
75
+ }
76
+
77
+ /**
78
+ * Derive a full ThemeConfig from a brand accent (+ optional neutrals/fonts). The
79
+ * accent drives links/badges/the one pointing color, a matching code palette, and
80
+ * the default backdrop. This is brand extraction's output (and the base every
81
+ * named library theme is minted from) — give one color, get a complete theme.
82
+ */
83
+ export function deriveTheme(opts: {
84
+ name?: string;
85
+ accent: string;
86
+ ink?: string;
87
+ paper?: string;
88
+ gold?: string;
89
+ markerPink?: string;
90
+ fonts?: ThemeFonts;
91
+ }): ThemeConfig {
92
+ const base = defaultTheme;
93
+ const ink = opts.ink ?? base.colors.ink;
94
+ const paper = opts.paper ?? base.colors.paper;
95
+ return {
96
+ name: opts.name ?? "brand",
97
+ fonts: opts.fonts ?? base.fonts,
98
+ colors: {
99
+ ...base.colors,
100
+ ink,
101
+ paper,
102
+ paperElevated: "#ffffff",
103
+ textMuted: rgba(ink, 0.65),
104
+ textDim: rgba(ink, 0.12),
105
+ signalBlue: opts.accent,
106
+ signalBlueSoft: rgba(opts.accent, 0.1),
107
+ signalBlueBorder: rgba(opts.accent, 0.3),
108
+ infoBlue: opts.accent,
109
+ markerPink: opts.markerPink ?? base.colors.markerPink,
110
+ gold: opts.gold ?? base.colors.gold,
111
+ goldSoft: rgba(opts.gold ?? base.colors.gold, 0.16),
112
+ },
113
+ floatShadow: base.floatShadow,
114
+ codeBg: base.codeBg,
115
+ caretBg: base.caretBg,
116
+ codeTheme: deriveCodeTheme(opts.accent),
117
+ backdrop: deriveBackdrop(opts.accent),
118
+ };
119
+ }
@@ -0,0 +1,33 @@
1
+ import type { ThemeConfig } from "./types";
2
+ import { defaultTheme } from "./default";
3
+ import { slateTheme } from "./slate";
4
+ import { LIBRARY } from "./library";
5
+
6
+ export type { ThemeConfig, ThemeColors } from "./types";
7
+
8
+ /** Registry of built-in themes. Add a brand's extracted theme here (or load it). */
9
+ export const THEMES: Record<string, ThemeConfig> = {
10
+ default: defaultTheme,
11
+ slate: slateTheme,
12
+ ...LIBRARY,
13
+ };
14
+
15
+ /**
16
+ * The active theme, selected at bundle time (env vars Remotion inlines into the
17
+ * bundle). A full extracted theme can be injected as JSON via
18
+ * `REMOTION_VIDEO_THEME_JSON`; otherwise a named preset via `REMOTION_VIDEO_THEME`.
19
+ * `scripts/render.ts` sets these from `--theme-file` / `--theme`; Studio → `default`.
20
+ */
21
+ function resolveActiveTheme(): ThemeConfig {
22
+ const json = process.env.REMOTION_VIDEO_THEME_JSON;
23
+ if (json) {
24
+ try {
25
+ return JSON.parse(json) as ThemeConfig;
26
+ } catch {
27
+ /* fall through to a named preset */
28
+ }
29
+ }
30
+ return THEMES[process.env.REMOTION_VIDEO_THEME ?? "default"] ?? defaultTheme;
31
+ }
32
+
33
+ export const activeTheme: ThemeConfig = resolveActiveTheme();
@@ -0,0 +1,60 @@
1
+ import { deriveTheme } from "./derive";
2
+ import type { ThemeConfig } from "./types";
3
+
4
+ /**
5
+ * The named theme library. Each is minted from one accent via `deriveTheme`
6
+ * (which generates a matching code palette + backdrop), with per-theme fonts and
7
+ * optional neutral/marker overrides. `default` and `slate` stay hand-authored in
8
+ * their own files; everything here is registered alongside them in `index.ts`.
9
+ */
10
+ const S = (f: string) => `${f}, ui-sans-serif, system-ui, sans-serif`;
11
+ const M = (f: string) => `${f}, SFMono-Regular, Menlo, monospace`;
12
+ const R = (f: string) => `${f}, Georgia, "Times New Roman", serif`;
13
+ const NOTE = "Caveat, cursive";
14
+
15
+ type Spec = {
16
+ accent: string;
17
+ ink?: string;
18
+ paper?: string;
19
+ gold?: string;
20
+ marker?: string;
21
+ display: string;
22
+ body: string;
23
+ mono: string;
24
+ };
25
+
26
+ const COOL_INK = "#0f172a";
27
+
28
+ const SPECS: Record<string, Spec> = {
29
+ cobalt: { accent: "#2f5fff", ink: COOL_INK, paper: "#f5f7fb", display: S("Manrope"), body: S("Manrope"), mono: M("JetBrains Mono") },
30
+ emerald: { accent: "#10b981", display: S("Space Grotesk"), body: S("Inter"), mono: M("Fira Code") },
31
+ amber: { accent: "#d97706", paper: "#fbf8f3", gold: "#b45309", display: S("Sora"), body: S("Public Sans"), mono: M("IBM Plex Mono") },
32
+ crimson: { accent: "#e11d48", marker: "#0ea5e9", display: S("Archivo"), body: S("Inter"), mono: M("Space Mono") },
33
+ violet: { accent: "#7c3aed", display: S("Plus Jakarta Sans"), body: S("Inter"), mono: M("JetBrains Mono") },
34
+ teal: { accent: "#0d9488", paper: "#f3f8f7", display: S("Outfit"), body: S("Inter"), mono: M("Fira Code") },
35
+ indigo: { accent: "#4338ca", ink: COOL_INK, display: S("Inter"), body: S("Inter"), mono: M("IBM Plex Mono") },
36
+ sky: { accent: "#0ea5e9", paper: "#f4f9fc", display: S("Figtree"), body: S("Figtree"), mono: M("JetBrains Mono") },
37
+ sunset: { accent: "#f97316", marker: "#7c3aed", display: S("Epilogue"), body: S("Inter"), mono: M("Fira Code") },
38
+ rose: { accent: "#f43f5e", paper: "#fdf6f7", display: S("DM Sans"), body: S("DM Sans"), mono: M("Fira Code") },
39
+ lime: { accent: "#65a30d", display: S("Space Grotesk"), body: S("Work Sans"), mono: M("Space Mono") },
40
+ grape: { accent: "#9333ea", marker: "#22c55e", display: S("Manrope"), body: S("Inter"), mono: M("Space Mono") },
41
+ forest: { accent: "#15803d", ink: "#14241b", paper: "#f4f7f3", display: R("Fraunces"), body: R("Spectral"), mono: M("JetBrains Mono") },
42
+ editorial: { accent: "#9333ea", ink: "#1a1a1a", paper: "#fbfaf8", display: R("Playfair Display"), body: R("Spectral"), mono: M("IBM Plex Mono") },
43
+ graphite: { accent: "#475569", ink: COOL_INK, paper: "#f6f7f8", marker: "#ef4444", display: S("Geist"), body: S("Geist"), mono: M("Geist Mono") },
44
+ mono: { accent: "#111111", paper: "#f5f5f5", marker: "#2563eb", display: S("Geist"), body: S("Geist"), mono: M("Geist Mono") },
45
+ };
46
+
47
+ export const LIBRARY: Record<string, ThemeConfig> = Object.fromEntries(
48
+ Object.entries(SPECS).map(([name, s]) => [
49
+ name,
50
+ deriveTheme({
51
+ name,
52
+ accent: s.accent,
53
+ ink: s.ink,
54
+ paper: s.paper,
55
+ gold: s.gold,
56
+ markerPink: s.marker,
57
+ fonts: { display: s.display, body: s.body, mono: s.mono, note: NOTE },
58
+ }),
59
+ ]),
60
+ );
@@ -0,0 +1,39 @@
1
+ import type { ThemeConfig } from "./types";
2
+
3
+ /** A cooler contrasting theme — slate neutrals, indigo accent, copper marker. */
4
+ export const slateTheme: ThemeConfig = {
5
+ name: "slate",
6
+ fonts: {
7
+ display: "Inter, ui-sans-serif, system-ui, sans-serif",
8
+ body: "Inter, ui-sans-serif, system-ui, sans-serif",
9
+ mono: "JetBrains Mono, SFMono-Regular, Menlo, monospace",
10
+ note: "Caveat, cursive",
11
+ },
12
+ colors: {
13
+ ink: "#0f172a",
14
+ paper: "#f6f7f9",
15
+ paperElevated: "#ffffff",
16
+ chrome: "#eef1f4",
17
+ hairline: "#e2e8f0",
18
+ hairlineHover: "#cbd5e1",
19
+ textMuted: "rgba(15,23,42,0.62)",
20
+ textDim: "rgba(15,23,42,0.12)",
21
+ signalBlue: "#4f46e5",
22
+ signalBlueSoft: "rgba(79,70,229,0.10)",
23
+ signalBlueBorder: "rgba(79,70,229,0.30)",
24
+ markerPink: "#e11d8f",
25
+ gold: "#b5762a",
26
+ goldSoft: "rgba(181,118,42,0.16)",
27
+ titleWhite: "#f8fafc",
28
+ successGreen: "#16a34a",
29
+ warningYellow: "#d97706",
30
+ dangerRed: "#dc2626",
31
+ infoBlue: "#4f46e5",
32
+ accentTeal: "#0e7490",
33
+ },
34
+ floatShadow: "0 30px 80px rgba(15,23,42,0.30), 0 2px 6px rgba(15,23,42,0.14)",
35
+ codeBg: "#f4f6f8",
36
+ caretBg: "rgba(248,250,252,0.95)",
37
+ codeTheme: { fg: "#0f172a", kw: "#0891b2", nw: "#7c3aed", str: "#b45309", num: "#9333ea", fn: "#4f46e5", punct: "#64748b", comment: "#94a3b8" },
38
+ backdrop: ["#eceafd", "#f7f8fc"],
39
+ };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * A video theme. The engine reads colors/shadow/code surfaces from the active
3
+ * theme; nothing in the components hardcodes a brand. Fonts are loaded separately
4
+ * (see brand/fonts.ts) and become themeable with custom fonts later.
5
+ */
6
+ export type ThemeColors = {
7
+ ink: string;
8
+ paper: string;
9
+ paperElevated: string;
10
+ chrome: string;
11
+ hairline: string;
12
+ hairlineHover: string;
13
+ textMuted: string;
14
+ textDim: string;
15
+ /** The single pointing accent. */
16
+ signalBlue: string;
17
+ signalBlueSoft: string;
18
+ signalBlueBorder: string;
19
+ /** One human-flourish color. */
20
+ markerPink: string;
21
+ /** Version / NEW marker (eyebrow + pill). */
22
+ gold: string;
23
+ goldSoft: string;
24
+ /** Warm off-white for headlines over imagery. */
25
+ titleWhite: string;
26
+ successGreen: string;
27
+ warningYellow: string;
28
+ dangerRed: string;
29
+ infoBlue: string;
30
+ accentTeal: string;
31
+ };
32
+
33
+ /** Syntax-highlight palette for the code window. */
34
+ export type CodeTheme = {
35
+ fg: string;
36
+ kw: string; // keywords (import/const/await)
37
+ nw: string; // new / constructor
38
+ str: string; // strings
39
+ num: string; // numbers
40
+ fn: string; // functions / types
41
+ punct: string; // punctuation
42
+ comment: string;
43
+ };
44
+
45
+ /** Font family stacks (must reference families loaded in brand/fonts.ts). */
46
+ export type ThemeFonts = {
47
+ display: string;
48
+ body: string;
49
+ mono: string;
50
+ note: string;
51
+ };
52
+
53
+ export type ThemeConfig = {
54
+ name: string;
55
+ colors: ThemeColors;
56
+ fonts: ThemeFonts;
57
+ /** Floating window/panel shadow. */
58
+ floatShadow: string;
59
+ /** Code window background. */
60
+ codeBg: string;
61
+ /** Typewriter caret fill. */
62
+ caretBg: string;
63
+ /** Syntax colors. */
64
+ codeTheme: CodeTheme;
65
+ /** Light [from, to] gradient for the procedural default backdrop (Background "shapes"). */
66
+ backdrop?: [string, string];
67
+ };