slide-cli 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.
Files changed (45) hide show
  1. package/README.md +184 -0
  2. package/TEMPLATE_GUIDE.md +661 -0
  3. package/dist/TEMPLATE_GUIDE.md +661 -0
  4. package/dist/index.js +277058 -0
  5. package/dist/scripts/build.js +41 -0
  6. package/dist/scripts/download-fonts.js +123 -0
  7. package/dist/templates/bold-title/sample.json +57 -0
  8. package/dist/templates/bold-title/template.html +212 -0
  9. package/dist/templates/bold-title/template.json +76 -0
  10. package/dist/templates/bold-title-wide/sample.json +58 -0
  11. package/dist/templates/bold-title-wide/template.html +224 -0
  12. package/dist/templates/bold-title-wide/template.json +76 -0
  13. package/dist/templates/minimal/sample.json +53 -0
  14. package/dist/templates/minimal/template.html +183 -0
  15. package/dist/templates/minimal/template.json +76 -0
  16. package/dist/templates/minimal-wide/sample.json +53 -0
  17. package/dist/templates/minimal-wide/template.html +208 -0
  18. package/dist/templates/minimal-wide/template.json +76 -0
  19. package/dist/templates/quote-card/sample.json +57 -0
  20. package/dist/templates/quote-card/template.html +203 -0
  21. package/dist/templates/quote-card/template.json +76 -0
  22. package/dist/templates/quote-card-wide/sample.json +58 -0
  23. package/dist/templates/quote-card-wide/template.html +215 -0
  24. package/dist/templates/quote-card-wide/template.json +76 -0
  25. package/package.json +66 -0
  26. package/scripts/build.js +41 -0
  27. package/scripts/download-fonts.js +123 -0
  28. package/templates/bold-title/sample.json +57 -0
  29. package/templates/bold-title/template.html +212 -0
  30. package/templates/bold-title/template.json +76 -0
  31. package/templates/bold-title-wide/sample.json +58 -0
  32. package/templates/bold-title-wide/template.html +224 -0
  33. package/templates/bold-title-wide/template.json +76 -0
  34. package/templates/minimal/sample.json +53 -0
  35. package/templates/minimal/template.html +183 -0
  36. package/templates/minimal/template.json +76 -0
  37. package/templates/minimal-wide/sample.json +53 -0
  38. package/templates/minimal-wide/template.html +208 -0
  39. package/templates/minimal-wide/template.json +76 -0
  40. package/templates/quote-card/sample.json +57 -0
  41. package/templates/quote-card/template.html +203 -0
  42. package/templates/quote-card/template.json +76 -0
  43. package/templates/quote-card-wide/sample.json +58 -0
  44. package/templates/quote-card-wide/template.html +215 -0
  45. package/templates/quote-card-wide/template.json +76 -0
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+ // Cross-platform build script (works on Windows, macOS, Linux)
3
+ // Run with: bun scripts/build.js OR node scripts/build.js
4
+
5
+ import { cpSync, mkdirSync, copyFileSync, rmSync, existsSync } from "fs";
6
+ import { join, dirname } from "path";
7
+ import { fileURLToPath } from "url";
8
+ import { execSync } from "child_process";
9
+
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ const root = join(__dirname, "..");
12
+ const dist = join(root, "dist");
13
+
14
+ // Clean dist
15
+ if (existsSync(dist)) {
16
+ rmSync(dist, { recursive: true, force: true });
17
+ console.log("šŸ—‘ Cleaned dist/");
18
+ }
19
+ mkdirSync(dist, { recursive: true });
20
+
21
+ // Bundle TypeScript entry point
22
+ console.log("āš™ļø Bundling src/index.ts …");
23
+ execSync(
24
+ "bun build ./src/index.ts --outdir ./dist --target node --format esm",
25
+ { cwd: root, stdio: "inherit" }
26
+ );
27
+
28
+ // Copy static assets
29
+ const copies = [
30
+ ["templates", "dist/templates"],
31
+ ["scripts", "dist/scripts"],
32
+ ];
33
+ for (const [src, dest] of copies) {
34
+ cpSync(join(root, src), join(root, dest), { recursive: true });
35
+ console.log(`šŸ“ Copied ${src}/ → ${dest}/`);
36
+ }
37
+
38
+ copyFileSync(join(root, "TEMPLATE_GUIDE.md"), join(root, "dist", "TEMPLATE_GUIDE.md"));
39
+ console.log("šŸ“„ Copied TEMPLATE_GUIDE.md → dist/");
40
+
41
+ console.log("\nāœ… Build complete → dist/");
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * scripts/download-fonts.js
4
+ *
5
+ * Downloads Noto CJK fonts for unicode support (CJK, Korean, Japanese, French, etc.)
6
+ * These fonts are too large for npm packages (~20MB each) so we fetch them directly
7
+ * from the Noto project's GitHub releases.
8
+ *
9
+ * Fonts are stored in ./fonts/ and are gitignored.
10
+ * Re-run manually with: node scripts/download-fonts.js
11
+ */
12
+
13
+ import { createWriteStream, mkdirSync, existsSync, statSync } from "fs";
14
+ import { get as httpsGet } from "https";
15
+ import { get as httpGet } from "http";
16
+ import { join, dirname } from "path";
17
+ import { fileURLToPath } from "url";
18
+
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ const FONTS_DIR = join(__dirname, "..", "fonts");
21
+
22
+ const FONTS = [
23
+ {
24
+ name: "NotoSansCJK-Bold",
25
+ filename: "NotoSansCJK-Bold.ttc",
26
+ // Google Fonts CDN via GitHub Noto CJK releases
27
+ url: "https://github.com/notofonts/noto-cjk/raw/main/Sans/OTC/NotoSansCJK-Bold.ttc",
28
+ minSize: 15_000_000, // ~20MB — if smaller, download failed
29
+ description: "Noto Sans CJK Bold (Latin, French, CJK, Korean, Japanese)",
30
+ },
31
+ {
32
+ name: "NotoSansCJK-Black",
33
+ filename: "NotoSansCJK-Black.ttc",
34
+ url: "https://github.com/notofonts/noto-cjk/raw/main/Sans/OTC/NotoSansCJK-Black.ttc",
35
+ minSize: 15_000_000,
36
+ description: "Noto Sans CJK Black (display titles, weight 900)",
37
+ },
38
+ {
39
+ name: "NotoSerifCJK-Bold",
40
+ filename: "NotoSerifCJK-Bold.ttc",
41
+ url: "https://github.com/notofonts/noto-cjk/raw/main/Serif/OTC/NotoSerifCJK-Bold.ttc",
42
+ minSize: 15_000_000,
43
+ description: "Noto Serif CJK Bold (serif quotes, headings)",
44
+ },
45
+ ];
46
+
47
+ mkdirSync(FONTS_DIR, { recursive: true });
48
+
49
+ function download(url, destPath, description) {
50
+ return new Promise((resolve, reject) => {
51
+ // Skip if already present and large enough
52
+ if (existsSync(destPath)) {
53
+ const size = statSync(destPath).size;
54
+ const font = FONTS.find((f) => destPath.endsWith(f.filename));
55
+ if (font && size >= font.minSize) {
56
+ console.log(` āœ“ ${description} (already downloaded)`);
57
+ resolve();
58
+ return;
59
+ }
60
+ }
61
+
62
+ console.log(` ↓ Downloading ${description}...`);
63
+ const file = createWriteStream(destPath);
64
+ const getter = url.startsWith("https") ? httpsGet : httpGet;
65
+
66
+ const request = getter(url, { headers: { "User-Agent": "slide-cli/1.0" } }, (res) => {
67
+ // Follow redirects (GitHub uses 302)
68
+ if (res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 307) {
69
+ file.close();
70
+ download(res.headers.location, destPath, description).then(resolve).catch(reject);
71
+ return;
72
+ }
73
+ if (res.statusCode !== 200) {
74
+ file.close();
75
+ reject(new Error(`HTTP ${res.statusCode} for ${url}`));
76
+ return;
77
+ }
78
+ res.pipe(file);
79
+ file.on("finish", () => {
80
+ file.close();
81
+ const size = statSync(destPath).size;
82
+ console.log(` āœ“ ${description} (${(size / 1_000_000).toFixed(1)}MB)`);
83
+ resolve();
84
+ });
85
+ });
86
+
87
+ request.on("error", (err) => {
88
+ file.close();
89
+ reject(err);
90
+ });
91
+ });
92
+ }
93
+
94
+ async function main() {
95
+ console.log("\nslide-cli: Setting up unicode fonts (Noto CJK)...");
96
+ console.log(` Destination: ${FONTS_DIR}\n`);
97
+
98
+ const failures = [];
99
+
100
+ for (const font of FONTS) {
101
+ const destPath = join(FONTS_DIR, font.filename);
102
+ try {
103
+ await download(font.url, destPath, font.description);
104
+ } catch (err) {
105
+ failures.push({ font, err });
106
+ console.warn(` ⚠ Could not download ${font.name}: ${err.message}`);
107
+ console.warn(` Slides with CJK/accented text will fall back to system fonts.`);
108
+ }
109
+ }
110
+
111
+ if (failures.length === 0) {
112
+ console.log("\n āœ“ All unicode fonts ready.\n");
113
+ } else {
114
+ console.log(`\n ⚠ ${failures.length} font(s) could not be downloaded.`);
115
+ console.log(` Re-run: node scripts/download-fonts.js\n`);
116
+ }
117
+ }
118
+
119
+ main().catch((err) => {
120
+ console.error("Font setup failed:", err.message);
121
+ // Non-zero exit would break bun install, so we warn and continue
122
+ process.exit(0);
123
+ });
@@ -0,0 +1,57 @@
1
+ {
2
+ "_template": "bold-title",
3
+ "_description": "High-impact editorial card with a gradient background, subtle grid overlay, and a giant Bebas Neue display title. Best for short punchy titles (1–3 words). Each slide can have its own gradient colors and highlight accent.",
4
+ "_slots": {
5
+ "title": "REQUIRED — the bold display title. Shown at 192px. Use text naturally — text-wrap:balance distributes lines evenly so any length looks intentional. Put context or explanation in subtitle, not in the title itself.",
6
+ "subtitle": "optional — one sentence of supporting text below the title",
7
+ "eyebrow": "optional — small uppercase label with a decorative line shown above the title",
8
+ "colorA": "optional — hex color for top of the gradient background (default: #1a0533)",
9
+ "colorB": "optional — hex color for bottom of the gradient background (default: #0d1f3c)",
10
+ "highlight": "optional — hex accent color used for the eyebrow text, dot, and rule (default: #ff6b35)",
11
+ "image": "optional — path or https:// URL. Used as a full-bleed background image behind the gradient. Dark, atmospheric photos work best (landscapes, textures, cityscapes)."
12
+ },
13
+ "title": "The Future Deck",
14
+ "slides": [
15
+ {
16
+ "layout": "bold-title",
17
+ "eyebrow": "The future is",
18
+ "title": "NOW",
19
+ "subtitle": "Everything you thought would take a decade is happening this year.",
20
+ "colorA": "#0d0221",
21
+ "colorB": "#1a0f2e",
22
+ "highlight": "#ff6b35",
23
+ "cta": "Stay curious →"
24
+ },
25
+ {
26
+ "layout": "bold-title",
27
+ "eyebrow": "Rule No. 2",
28
+ "title": "SHIP FAST",
29
+ "subtitle": "A good product shipped today beats a perfect product shipped never.",
30
+ "colorA": "#0a1628",
31
+ "colorB": "#051020",
32
+ "highlight": "#00d4ff",
33
+ "cta": "Move fast, learn faster"
34
+ },
35
+ {
36
+ "layout": "bold-title",
37
+ "eyebrow": "The bottom line",
38
+ "title": "BUILD",
39
+ "subtitle": "Ideas are worthless without execution. Start before you're ready.",
40
+ "colorA": "#1a0a00",
41
+ "colorB": "#0d1a00",
42
+ "highlight": "#f5c400",
43
+ "cta": "Start today"
44
+ },
45
+ {
46
+ "layout": "bold-title",
47
+ "eyebrow": "With image",
48
+ "title": "RISE",
49
+ "subtitle": "Great things are built one day at a time.",
50
+ "colorA": "#0a0a14",
51
+ "colorB": "#141428",
52
+ "highlight": "#a78bfa",
53
+ "image": "https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?w=1080",
54
+ "cta": "Keep climbing"
55
+ }
56
+ ]
57
+ }
@@ -0,0 +1,212 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <style>
6
+
7
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
8
+
9
+ html, body {
10
+ width: 1080px;
11
+ height: 1920px;
12
+ overflow: hidden;
13
+ }
14
+
15
+ body {
16
+ display: flex;
17
+ flex-direction: column;
18
+ position: relative;
19
+ font-family: 'Outfit', 'Helvetica Neue', Arial, sans-serif;
20
+ }
21
+
22
+ /* ── Full-bleed background image (optional, behind everything) ── */
23
+ .bg-image {
24
+ position: absolute;
25
+ inset: 0;
26
+ z-index: 0;
27
+ }
28
+ .bg-image img {
29
+ width: 100%;
30
+ height: 100%;
31
+ object-fit: cover;
32
+ display: block;
33
+ }
34
+ /* Darken image so text always stays readable */
35
+ .bg-image::after {
36
+ content: '';
37
+ position: absolute;
38
+ inset: 0;
39
+ background: rgba(0, 0, 0, 0.52);
40
+ }
41
+
42
+ /* ── Gradient layer (on top of image if present) ── */
43
+ .gradient-layer {
44
+ position: absolute;
45
+ inset: 0;
46
+ background: linear-gradient(160deg, {{colorA}} 0%, {{colorB}} 100%);
47
+ z-index: 1;
48
+ /* When image is present, blend the gradient on top */
49
+ mix-blend-mode: multiply;
50
+ }
51
+ body.has-image .gradient-layer {
52
+ opacity: 0.82;
53
+ }
54
+
55
+ /* Grid overlay */
56
+ .grid-layer {
57
+ position: absolute;
58
+ inset: 0;
59
+ background-image:
60
+ linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px),
61
+ linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px);
62
+ background-size: 80px 80px;
63
+ z-index: 2;
64
+ pointer-events: none;
65
+ }
66
+
67
+ /* Radial glow */
68
+ .glow-layer {
69
+ position: absolute;
70
+ top: -200px; left: -200px;
71
+ width: 900px; height: 900px;
72
+ background: radial-gradient(circle, {{highlight}}22 0%, transparent 70%);
73
+ z-index: 2;
74
+ pointer-events: none;
75
+ }
76
+
77
+ /* All content sits above the background layers */
78
+ .top-bar, .main, .bottom { position: relative; z-index: 3; }
79
+
80
+ .top-bar {
81
+ display: flex;
82
+ align-items: center;
83
+ justify-content: space-between;
84
+ padding: 72px 88px 0;
85
+ }
86
+
87
+ .brand-dot {
88
+ width: 24px; height: 24px;
89
+ border-radius: 50%;
90
+ background: {{highlight}};
91
+ }
92
+
93
+ /* counter: bumped opacity for legibility */
94
+ .slide-counter {
95
+ font-size: 30px; font-weight: 300;
96
+ color: rgba(255,255,255,0.45);
97
+ letter-spacing: 0.12em;
98
+ }
99
+
100
+ .main {
101
+ flex: 1;
102
+ display: flex;
103
+ flex-direction: column;
104
+ justify-content: center;
105
+ padding: 0 88px;
106
+ }
107
+
108
+ .eyebrow {
109
+ display: inline-flex;
110
+ align-items: center;
111
+ gap: 20px;
112
+ margin-bottom: 52px;
113
+ }
114
+ .eyebrow-line { width: 60px; height: 3px; background: {{highlight}}; }
115
+ /* eyebrow: bigger and heavier for mobile legibility */
116
+ .eyebrow-text {
117
+ font-family: 'Outfit', 'Noto Sans CJK', 'Helvetica Neue', Arial, sans-serif;
118
+ font-size: 36px; font-weight: 700;
119
+ letter-spacing: 0.14em; text-transform: uppercase;
120
+ color: {{highlight}};
121
+ }
122
+
123
+ .title {
124
+ font-family: 'Bebas Neue', 'Noto Sans CJK', Impact, 'Arial Narrow', sans-serif;
125
+ font-size: 176px;
126
+ font-weight: 900;
127
+ line-height: 1.0;
128
+ color: #ffffff;
129
+ margin-bottom: 60px;
130
+ text-wrap: pretty;
131
+ word-break: keep-all;
132
+ overflow-wrap: break-word;
133
+ }
134
+
135
+ /* subtitle: higher opacity, slightly larger */
136
+ .subtitle {
137
+ font-family: 'Outfit', 'Noto Sans CJK', 'Helvetica Neue', Arial, sans-serif;
138
+ font-size: 50px;
139
+ font-weight: 700;
140
+ line-height: 1.5;
141
+ color: rgba(255,255,255,0.72);
142
+ max-width: 840px;
143
+ text-wrap: balance;
144
+ word-break: keep-all;
145
+ overflow-wrap: break-word;
146
+ }
147
+
148
+ .bottom { padding: 0 88px 80px; }
149
+
150
+ .bottom-rule {
151
+ width: 100%; height: 1px;
152
+ background: linear-gradient(90deg, {{highlight}}88, transparent);
153
+ margin-bottom: 48px;
154
+ }
155
+
156
+ /* cta: larger and full opacity */
157
+ .cta-text {
158
+ font-size: 40px; font-weight: 700;
159
+ letter-spacing: 0.12em; text-transform: uppercase;
160
+ color: rgba(255,255,255,0.92);
161
+ }
162
+
163
+ .bg-num {
164
+ position: absolute;
165
+ right: -20px; bottom: 100px;
166
+ font-family: 'Bebas Neue', Impact, 'Arial Narrow', sans-serif;
167
+ font-size: 600px; line-height: 1;
168
+ color: rgba(255,255,255,0.03);
169
+ pointer-events: none;
170
+ z-index: 3;
171
+ user-select: none;
172
+ }
173
+ </style>
174
+ </head>
175
+ <body class="{{#if image}}has-image{{/if}}">
176
+
177
+ {{#if image}}
178
+ <div class="bg-image"><img src="{{image}}" alt=""></div>
179
+ {{/if}}
180
+
181
+ <div class="gradient-layer"></div>
182
+ <div class="grid-layer"></div>
183
+ <div class="glow-layer"></div>
184
+
185
+ <div class="top-bar">
186
+ <div class="brand-dot"></div>
187
+ <div class="slide-counter">{{slideIndex}} / {{totalSlides}}</div>
188
+ </div>
189
+
190
+ <div class="main">
191
+ {{#if eyebrow}}
192
+ <div class="eyebrow">
193
+ <div class="eyebrow-line"></div>
194
+ <span class="eyebrow-text">{{eyebrow}}</span>
195
+ </div>
196
+ {{/if}}
197
+
198
+ <h1 class="title">{{title}}</h1>
199
+
200
+ {{#if subtitle}}
201
+ <p class="subtitle">{{subtitle}}</p>
202
+ {{/if}}
203
+ </div>
204
+
205
+ <div class="bottom">
206
+ <div class="bottom-rule"></div>
207
+ {{#if cta}}<div class="cta-text">{{cta}}</div>{{/if}}
208
+ </div>
209
+
210
+ <div class="bg-num">{{slideIndex}}</div>
211
+ </body>
212
+ </html>
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "Bold Title",
3
+ "id": "bold-title",
4
+ "version": "1.1.0",
5
+ "description": "High-contrast editorial card with large bold title and gradient background",
6
+ "author": "slide-cli",
7
+ "aspectRatio": "9:16",
8
+ "width": 1080,
9
+ "height": 1920,
10
+ "tags": ["bold", "editorial", "gradient", "impact"],
11
+ "slots": [
12
+ {
13
+ "id": "title",
14
+ "type": "text",
15
+ "label": "Title",
16
+ "required": true,
17
+ "description": "Main bold display title at 192px. text-wrap:balance keeps multi-line titles visually even. Context and detail belong in subtitle, not here."
18
+ },
19
+ {
20
+ "id": "subtitle",
21
+ "type": "text",
22
+ "label": "Subtitle",
23
+ "required": false,
24
+ "default": "",
25
+ "description": "Supporting line below the title"
26
+ },
27
+ {
28
+ "id": "eyebrow",
29
+ "type": "text",
30
+ "label": "Eyebrow text",
31
+ "required": false,
32
+ "default": "",
33
+ "description": "Small text above the title"
34
+ },
35
+ {
36
+ "id": "colorA",
37
+ "type": "color",
38
+ "label": "Gradient start",
39
+ "required": false,
40
+ "default": "#1a0533",
41
+ "description": "Top gradient color"
42
+ },
43
+ {
44
+ "id": "colorB",
45
+ "type": "color",
46
+ "label": "Gradient end",
47
+ "required": false,
48
+ "default": "#0d1f3c",
49
+ "description": "Bottom gradient color"
50
+ },
51
+ {
52
+ "id": "highlight",
53
+ "type": "color",
54
+ "label": "Highlight color",
55
+ "required": false,
56
+ "default": "#ff6b35",
57
+ "description": "Accent color for eyebrow and decorations"
58
+ },
59
+ {
60
+ "id": "image",
61
+ "type": "image",
62
+ "label": "Background image",
63
+ "required": false,
64
+ "default": "",
65
+ "description": "Optional full-bleed background image layered behind the gradient. Dark atmospheric photos work best. File path or https:// URL."
66
+ },
67
+ {
68
+ "id": "cta",
69
+ "type": "text",
70
+ "label": "CTA / bottom text",
71
+ "required": false,
72
+ "default": "",
73
+ "description": "Call-to-action or bottom tagline"
74
+ }
75
+ ]
76
+ }
@@ -0,0 +1,58 @@
1
+ {
2
+ "_template": "bold-title-wide",
3
+ "_description": "High-impact editorial card with a gradient background — 16:9 widescreen. Title sits on the left, subtitle on the right in a two-column layout. Best for short punchy titles (1–4 words).",
4
+ "_slots": {
5
+ "title": "REQUIRED — the bold display title. Shown at 148px. Keep it punchy. Put context in subtitle.",
6
+ "subtitle": "optional — one sentence of supporting text shown to the right of the title",
7
+ "eyebrow": "optional — small uppercase label with a decorative line shown above the title",
8
+ "colorA": "optional — hex color for left of the gradient background (default: #1a0533)",
9
+ "colorB": "optional — hex color for right of the gradient background (default: #0d1f3c)",
10
+ "highlight": "optional — hex accent color used for the eyebrow text, dot, and rule (default: #ff6b35)",
11
+ "image": "optional — path or https:// URL. Used as a full-bleed background image behind the gradient.",
12
+ "cta": "optional — call-to-action text shown at the bottom"
13
+ },
14
+ "title": "The Future Deck",
15
+ "slides": [
16
+ {
17
+ "layout": "bold-title-wide",
18
+ "eyebrow": "The future is",
19
+ "title": "NOW",
20
+ "subtitle": "Everything you thought would take a decade is happening this year.",
21
+ "colorA": "#0d0221",
22
+ "colorB": "#1a0f2e",
23
+ "highlight": "#ff6b35",
24
+ "cta": "Stay curious →"
25
+ },
26
+ {
27
+ "layout": "bold-title-wide",
28
+ "eyebrow": "Rule No. 2",
29
+ "title": "SHIP FAST",
30
+ "subtitle": "A good product shipped today beats a perfect product shipped never.",
31
+ "colorA": "#0a1628",
32
+ "colorB": "#051020",
33
+ "highlight": "#00d4ff",
34
+ "cta": "Move fast, learn faster"
35
+ },
36
+ {
37
+ "layout": "bold-title-wide",
38
+ "eyebrow": "The bottom line",
39
+ "title": "BUILD",
40
+ "subtitle": "Ideas are worthless without execution. Start before you're ready.",
41
+ "colorA": "#1a0a00",
42
+ "colorB": "#0d1a00",
43
+ "highlight": "#f5c400",
44
+ "cta": "Start today"
45
+ },
46
+ {
47
+ "layout": "bold-title-wide",
48
+ "eyebrow": "With image",
49
+ "title": "RISE",
50
+ "subtitle": "Great things are built one day at a time.",
51
+ "colorA": "#0a0a14",
52
+ "colorB": "#141428",
53
+ "highlight": "#a78bfa",
54
+ "image": "https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?w=1920",
55
+ "cta": "Keep climbing"
56
+ }
57
+ ]
58
+ }