@stacklist-app/brandkit 1.1.1 → 1.1.2

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.
package/README.md CHANGED
@@ -9,7 +9,7 @@ Swap the config and assets to generate a brand guide for any project. An optiona
9
9
  ```bash
10
10
  npm install github:The-Stack-Lab/brandkit
11
11
  # or pin to a tag
12
- npm install github:The-Stack-Lab/brandkit#v1.1.1
12
+ npm install github:The-Stack-Lab/brandkit#v1.1.2
13
13
  ```
14
14
 
15
15
  Requires Node.js 18+.
@@ -23,7 +23,7 @@ npx brandkit dev brand # preview at http://localhost:4800 (live reload)
23
23
  npx brandkit build brand # bake into static files for production
24
24
  ```
25
25
 
26
- The `brand` directory name is your choice — pass any path.
26
+ `init` scaffolds a complete, ready-to-run demo brand (the self-titled "Brandkit" guide), so `dev` shows a polished guide immediately — then you swap in your own colors, fonts, copy, and logos. The `brand` directory name is your choice — pass any path.
27
27
 
28
28
  ## CLI
29
29
 
@@ -64,7 +64,7 @@ export default {
64
64
 
65
65
  `config.json` is the single source of truth. Top-level keys:
66
66
 
67
- - `brand` — name, tagline, description, version
67
+ - `brand` — name, tagline, description, version, date; optional `guideLabel` (renames the "Web Style Guide" header/footer label), `headerLogo` and `sidebarLogo` (logo image paths that replace the text wordmark in the header and left menu)
68
68
  - `fonts` — display + body with Google Fonts import
69
69
  - `theme` — CSS variable map (colors, gradients, font vars)
70
70
  - `nav` — sidebar structure
@@ -79,14 +79,22 @@ export default {
79
79
  - `accessibility` — contrast ratio grid
80
80
  - `cssVariables` — variable reference
81
81
 
82
- See `example/config.json` for a complete reference implementation (Freeway PHX).
82
+ See `example/config.json` for the complete default demo brand — the same config `init` scaffolds.
83
83
 
84
- ## How theming works
84
+ ## Theming
85
85
 
86
- `dist/styles.css` uses CSS custom properties (`var(--purple)`, `var(--font-display)`, etc.) with no `:root` block.
86
+ `dist/styles.css` ships a baseline `:root` block with sensible defaults for **every** token it consumes, so a minimal or partial config never renders broken. Your config only needs to *override* what it wants to change.
87
87
 
88
- - **Dev**: the engine reads `config.theme` and injects a `<style data-brandkit-theme>` tag at runtime.
89
- - **Build**: `brandkit build` prepends the generated `:root` block to `styles.css` and injects the Google Fonts `<link>` and page title into `index.html`.
88
+ The accent uses a fill/text split (the same convention as Material and shadcn/ui):
89
+
90
+ - `--accent` — the fill color (buttons, active states)
91
+ - `--accent-foreground` — text/icons **on** the fill (must meet contrast against `--accent`)
92
+ - `--accent-text` — the accent used **as** text or links on a light surface
93
+
94
+ This lets a low-contrast fill (say a bright orange) stay accessible: set `--accent-foreground: #000000` and `--accent-text` to a darker shade. Pre-1.1.2 configs that set `--purple` / `--purple-rgb` still work — those feed `--accent`.
95
+
96
+ - **Dev**: the engine reads `config.theme` and injects a `<style data-brandkit-theme>` block at runtime, which cascades over the defaults.
97
+ - **Build**: `brandkit build` appends the generated `:root` after the stylesheet's defaults and injects the Google Fonts `<link>` and page title into `index.html`.
90
98
 
91
99
  Swap the config, and every color, gradient, and font token updates everywhere.
92
100
 
package/cli/generate.js CHANGED
@@ -72,6 +72,7 @@ module.exports = function generate(args) {
72
72
  // Theme from CSS variables
73
73
  if (extracted.cssVars) {
74
74
  newFields.theme = extractCSS.mapToTheme(extracted.cssVars);
75
+ ensureThemeDefaults(newFields.theme);
75
76
  }
76
77
 
77
78
  // Fonts from Tailwind
@@ -202,6 +203,39 @@ module.exports = function generate(args) {
202
203
  console.log('');
203
204
  };
204
205
 
206
+ // Fill in the companion tokens styles.css consumes but a host stylesheet
207
+ // rarely defines: the "r, g, b" variants used by rgba() tints, and the
208
+ // accent on-color pairing. A light fill (e.g. orange) gets a black
209
+ // foreground so buttons stay legible; --accent-text defaults to the fill.
210
+ function ensureThemeDefaults(theme) {
211
+ if (!theme) return;
212
+ var rgbPairs = {
213
+ '--accent': '--accent-rgb',
214
+ '--ink': '--ink-rgb',
215
+ '--error': '--error-rgb',
216
+ '--success': '--success-rgb'
217
+ };
218
+ Object.keys(rgbPairs).forEach(function (hexKey) {
219
+ var rgbKey = rgbPairs[hexKey];
220
+ var val = theme[hexKey];
221
+ if (val && !theme[rgbKey] && /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(val)) {
222
+ theme[rgbKey] = helpers.hexToRgbString(val);
223
+ }
224
+ });
225
+ if (theme['--accent'] && !theme['--accent-foreground'] && /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(theme['--accent'])) {
226
+ // Pick the on-fill color with the higher WCAG contrast (not a luminance
227
+ // threshold — a mid-tone fill like orange reads better with black text
228
+ // even though it isn't "light").
229
+ var lum = helpers.relativeLuminance(theme['--accent']);
230
+ var contrastWhite = 1.05 / (lum + 0.05);
231
+ var contrastBlack = (lum + 0.05) / 0.05;
232
+ theme['--accent-foreground'] = contrastBlack >= contrastWhite ? '#000000' : '#FFFFFF';
233
+ }
234
+ if (theme['--accent'] && !theme['--accent-text']) {
235
+ theme['--accent-text'] = theme['--accent'];
236
+ }
237
+ }
238
+
205
239
  function buildColors(colorList) {
206
240
  var brand = [];
207
241
  var neutrals = [];
@@ -213,7 +247,7 @@ function buildColors(colorList) {
213
247
  for (var i = 0; i < colorList.length; i++) {
214
248
  var c = colorList[i];
215
249
  // Skip colors without valid hex (Tailwind function-based colors, etc.)
216
- if (!c.hex || typeof c.hex !== 'string' || !/^#[0-9a-fA-F]{3,8}$/.test(c.hex)) continue;
250
+ if (!c.hex || typeof c.hex !== 'string' || !/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(c.hex)) continue;
217
251
  if (!c.name) continue;
218
252
  var lowerName = c.name.toLowerCase();
219
253
 
package/cli/init.js CHANGED
@@ -37,6 +37,22 @@ module.exports = function init(args) {
37
37
  }
38
38
  }
39
39
 
40
+ // Copy the default showcase logos so the scaffold renders complete out of
41
+ // the box. Skipped on --update (preserve the user's assets) and skipped per
42
+ // file if the user already has one with that name.
43
+ var distLogosDir = path.join(distDir, 'logos');
44
+ if (!isUpdate && fs.existsSync(distLogosDir)) {
45
+ var logoFiles = fs.readdirSync(distLogosDir);
46
+ for (var l = 0; l < logoFiles.length; l++) {
47
+ var logoSrc = path.join(distLogosDir, logoFiles[l]);
48
+ var logoDest = path.join(logosDir, logoFiles[l]);
49
+ if (!fs.existsSync(logoDest)) {
50
+ fs.copyFileSync(logoSrc, logoDest);
51
+ console.log(' copied logos/' + logoFiles[l]);
52
+ }
53
+ }
54
+ }
55
+
40
56
  // Create starter config.json (skip if exists and not forcing)
41
57
  var configPath = path.join(targetDir, 'config.json');
42
58
  if (!fs.existsSync(configPath) && !isUpdate) {
package/dist/engine.js CHANGED
@@ -163,8 +163,18 @@
163
163
  4. Render gradients
164
164
  ============================================================== */
165
165
  function renderGradients() {
166
+ var gradientsSection = document.getElementById('gradients');
167
+ // No gradient configured → hide the whole section. The swatches are
168
+ // CSS-backed (var(--gradient-brand)), so leaving the scaffolding would
169
+ // render the default gradient under an empty "Gradient System" header.
170
+ if (!cfg.gradients || !cfg.gradients.length) {
171
+ if (gradientsSection) gradientsSection.style.display = 'none';
172
+ return;
173
+ }
174
+ if (gradientsSection) gradientsSection.style.display = '';
175
+
166
176
  var stopsContainer = document.getElementById('gradient-stops');
167
- if (!stopsContainer || !cfg.gradients || !cfg.gradients.length) return;
177
+ if (!stopsContainer) return;
168
178
  var brand = cfg.gradients[0];
169
179
  if (brand && brand.stops && brand.stops.length) {
170
180
  stopsContainer.innerHTML = brand.stops.map(function (s) {
@@ -732,11 +742,20 @@
732
742
  18. Render shell (header, intro, footer, misc)
733
743
  ============================================================== */
734
744
  function renderShell() {
735
- // Header
745
+ // Header — show brand.headerLogo image if set, else the brand name as text.
746
+ // brand.guideLabel renames the "Web Style Guide" label (header + footer).
747
+ var guideLabel = cfg.brand.guideLabel || 'Web Style Guide';
736
748
  var wordmark = document.getElementById('header-wordmark');
737
- if (wordmark) wordmark.textContent = cfg.brand.name;
749
+ if (wordmark) {
750
+ if (cfg.brand.headerLogo) {
751
+ wordmark.innerHTML = '<img class="header-logo" src="' + esc(cfg.brand.headerLogo) +
752
+ '" alt="' + esc(cfg.brand.name || 'Logo') + '">';
753
+ } else {
754
+ wordmark.textContent = cfg.brand.name;
755
+ }
756
+ }
738
757
  var meta = document.getElementById('header-meta');
739
- if (meta) meta.innerHTML = 'Web Style Guide v' + cfg.brand.version + '<br>' + cfg.brand.date;
758
+ if (meta) meta.innerHTML = esc(guideLabel) + ' v' + esc(cfg.brand.version) + '<br>' + esc(cfg.brand.date);
740
759
 
741
760
  // Intro
742
761
  var intro = document.getElementById('intro');
@@ -749,8 +768,8 @@
749
768
  // Footer
750
769
  var footer = document.getElementById('footer');
751
770
  if (footer) footer.innerHTML =
752
- '<span>' + cfg.brand.url + ' \u00B7 ' + cfg.brand.byline + '</span>' +
753
- '<span>Web Style Guide v' + cfg.brand.version + ' \u00B7 ' + cfg.brand.date + '</span>';
771
+ '<span>' + esc(cfg.brand.url) + ' \u00B7 ' + esc(cfg.brand.byline) + '</span>' +
772
+ '<span>' + esc(guideLabel) + ' v' + esc(cfg.brand.version) + ' \u00B7 ' + esc(cfg.brand.date) + '</span>';
754
773
 
755
774
  // Typography specimens — read descriptions from config
756
775
  var typeDisplayName = document.getElementById('type-display-name');
@@ -771,11 +790,21 @@
771
790
  var testerFont = document.getElementById('type-tester-font');
772
791
  var testerInput = document.getElementById('type-tester-input');
773
792
  if (testerFont && cfg.fonts) {
774
- testerFont.innerHTML =
775
- '<option value="' + cfg.fonts.display.family + '">' + cfg.fonts.display.family + '</option>' +
776
- '<option value="' + cfg.fonts.body.family + '">' + cfg.fonts.body.family + '</option>';
777
- if (testerInput) {
778
- testerInput.style.fontFamily = "'" + cfg.fonts.display.family + "', sans-serif";
793
+ // De-duplicate families so a brand using one typeface for both
794
+ // display and body shows a single option, not the same one twice.
795
+ var famList = [];
796
+ if (cfg.fonts.display && cfg.fonts.display.family) famList.push(cfg.fonts.display.family);
797
+ if (cfg.fonts.body && cfg.fonts.body.family) famList.push(cfg.fonts.body.family);
798
+ var seenFam = {};
799
+ var uniqueFams = [];
800
+ famList.forEach(function (f) {
801
+ if (!seenFam[f]) { seenFam[f] = true; uniqueFams.push(f); }
802
+ });
803
+ testerFont.innerHTML = uniqueFams.map(function (f) {
804
+ return '<option value="' + esc(f) + '">' + esc(f) + '</option>';
805
+ }).join('');
806
+ if (testerInput && uniqueFams.length) {
807
+ testerInput.style.fontFamily = "'" + uniqueFams[0] + "', sans-serif";
779
808
  }
780
809
  }
781
810
 
@@ -816,9 +845,16 @@
816
845
  '</div>';
817
846
  }
818
847
 
819
- // Sidebar brand name
848
+ // Sidebar brand — logo image if brand.sidebarLogo is set, else brand name
820
849
  var sidebarBrand = document.querySelector('.sidebar-brand');
821
- if (sidebarBrand) sidebarBrand.textContent = 'brandkit';
850
+ if (sidebarBrand) {
851
+ if (cfg.brand.sidebarLogo) {
852
+ sidebarBrand.innerHTML = '<img class="sidebar-logo" src="' + esc(cfg.brand.sidebarLogo) +
853
+ '" alt="' + esc(cfg.brand.name || 'Logo') + '">';
854
+ } else {
855
+ sidebarBrand.textContent = cfg.brand.name || 'Brand';
856
+ }
857
+ }
822
858
  }
823
859
 
824
860
  /* ==============================================================
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96" role="img" aria-label="Brandkit mark, reversed">
2
+ <rect x="1.5" y="1.5" width="93" height="93" rx="20.5" fill="none" stroke="#FFFFFF" stroke-width="3"/>
3
+ <g fill="#FFFFFF">
4
+ <rect x="26" y="28" width="44" height="11" rx="5.5"/>
5
+ <rect x="26" y="43" width="44" height="11" rx="5.5" opacity="0.82"/>
6
+ <rect x="26" y="58" width="28" height="11" rx="5.5" opacity="0.64"/>
7
+ </g>
8
+ </svg>
@@ -0,0 +1,14 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96" role="img" aria-label="Brandkit mark">
2
+ <defs>
3
+ <linearGradient id="bk-grad" x1="0" y1="0" x2="1" y2="1">
4
+ <stop offset="0" stop-color="#4F46E5"/>
5
+ <stop offset="1" stop-color="#7C3AED"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <rect width="96" height="96" rx="22" fill="url(#bk-grad)"/>
9
+ <g fill="#FFFFFF">
10
+ <rect x="26" y="28" width="44" height="11" rx="5.5"/>
11
+ <rect x="26" y="43" width="44" height="11" rx="5.5" opacity="0.82"/>
12
+ <rect x="26" y="58" width="28" height="11" rx="5.5" opacity="0.64"/>
13
+ </g>
14
+ </svg>
@@ -0,0 +1,15 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="324" height="96" viewBox="0 0 324 96" role="img" aria-label="brandkit">
2
+ <defs>
3
+ <linearGradient id="bk-wm-grad" x1="0" y1="0" x2="1" y2="1">
4
+ <stop offset="0" stop-color="#4F46E5"/>
5
+ <stop offset="1" stop-color="#7C3AED"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <rect x="0" y="8" width="80" height="80" rx="19" fill="url(#bk-wm-grad)"/>
9
+ <g fill="#FFFFFF">
10
+ <rect x="22" y="31" width="36" height="9" rx="4.5"/>
11
+ <rect x="22" y="44" width="36" height="9" rx="4.5" opacity="0.82"/>
12
+ <rect x="22" y="57" width="23" height="9" rx="4.5" opacity="0.64"/>
13
+ </g>
14
+ <text x="100" y="61" font-family="'Space Grotesk', 'Helvetica Neue', Arial, sans-serif" font-size="46" font-weight="600" letter-spacing="-1.5" fill="#111827">brandkit</text>
15
+ </svg>
@@ -0,0 +1,9 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="324" height="96" viewBox="0 0 324 96" role="img" aria-label="brandkit, reversed">
2
+ <rect x="1" y="9" width="78" height="78" rx="18" fill="none" stroke="#FFFFFF" stroke-width="2.5"/>
3
+ <g fill="#FFFFFF">
4
+ <rect x="22" y="31" width="36" height="9" rx="4.5"/>
5
+ <rect x="22" y="44" width="36" height="9" rx="4.5" opacity="0.82"/>
6
+ <rect x="22" y="57" width="23" height="9" rx="4.5" opacity="0.64"/>
7
+ </g>
8
+ <text x="100" y="61" font-family="'Space Grotesk', 'Helvetica Neue', Arial, sans-serif" font-size="46" font-weight="600" letter-spacing="-1.5" fill="#FFFFFF">brandkit</text>
9
+ </svg>
package/dist/styles.css CHANGED
@@ -4,6 +4,61 @@
4
4
  CSS custom properties injected by engine.js from config.json.
5
5
  ═══════════════════════════════════════════════════════════════════ */
6
6
 
7
+ /* ───────────────────────────────────────────────────────────────────
8
+ Default tokens — a config only needs to OVERRIDE these.
9
+ This stylesheet consumes every variable below; shipping defaults here
10
+ means a minimal or ported config never renders broken (invisible
11
+ buttons, colorless code, system fonts, transparent tints).
12
+
13
+ The injected config.theme (dev) and the generated :root (build) both
14
+ cascade AFTER this block, so any token a config sets wins.
15
+
16
+ Backward compatibility: pre-1.1.2 configs set --purple / --purple-rgb.
17
+ Those feed --accent / --accent-rgb below, so old configs keep working
18
+ without setting the new accent tokens.
19
+ ─────────────────────────────────────────────────────────────────── */
20
+ :root {
21
+ /* Surfaces & ink */
22
+ --white: #FFFFFF;
23
+ --cloud: #F9FAFB;
24
+ --mist: #F3F4F6;
25
+ --haze: #D1D5DB;
26
+ --slate: #6B7280;
27
+ --graphite: #374151;
28
+ --ink: #111827;
29
+ --ink-rgb: 17, 24, 39;
30
+
31
+ /* Fonts (overridden per-brand from config.fonts) */
32
+ --font-display: 'Inter', sans-serif;
33
+ --font-body: 'Inter', sans-serif;
34
+
35
+ /* Accent — fill vs. text split (Material/shadcn "on-color" convention)
36
+ --accent the fill color (buttons, active chips)
37
+ --accent-foreground text/icon color ON the fill (must pass on --accent)
38
+ --accent-text the accent used AS text/links on a light surface */
39
+ --accent: var(--purple, #6366F1);
40
+ --accent-rgb: var(--purple-rgb, 99, 102, 241);
41
+ --accent-foreground: #FFFFFF;
42
+ --accent-text: var(--accent);
43
+
44
+ /* Gradients */
45
+ --gradient-brand: linear-gradient(135deg, #4338CA 0%, #6366F1 50%, #EC4899 100%);
46
+ --gradient-brand-subtle: linear-gradient(135deg, rgba(99, 102, 241, 0.06) 0%, rgba(236, 72, 153, 0.06) 100%);
47
+
48
+ /* Semantic */
49
+ --success: #10B981;
50
+ --success-rgb: 16, 185, 129;
51
+ --warning: #F59E0B;
52
+ --error: #EF4444;
53
+ --error-rgb: 239, 68, 68;
54
+
55
+ /* Extended palette (coral accents, code-syntax highlight) */
56
+ --coral: #F87171;
57
+ --coral-dark: #DC2626;
58
+ --coral-light: #FCA5A5;
59
+ --lavender: #A5B4FC;
60
+ }
61
+
7
62
  * { margin: 0; padding: 0; box-sizing: border-box; }
8
63
 
9
64
  body {
@@ -33,12 +88,20 @@ body {
33
88
  font-family: var(--font-display), sans-serif;
34
89
  font-size: 18px;
35
90
  font-weight: 700;
36
- color: var(--purple);
91
+ color: var(--accent-text);
37
92
  padding: 0 24px;
38
93
  margin-bottom: 32px;
39
94
  letter-spacing: -0.02em;
40
95
  }
41
96
 
97
+ /* Optional logo image in the sidebar top (brand.sidebarLogo) */
98
+ .sidebar-logo {
99
+ display: block;
100
+ max-height: 28px;
101
+ max-width: 100%;
102
+ width: auto;
103
+ }
104
+
42
105
  .sidebar-nav { list-style: none; }
43
106
 
44
107
  .sidebar-nav a {
@@ -52,7 +115,7 @@ body {
52
115
  }
53
116
 
54
117
  .sidebar-nav a:hover { color: var(--ink); background: var(--cloud); }
55
- .sidebar-nav a.active { color: var(--purple); border-left-color: var(--purple); background: rgba(var(--purple-rgb), 0.04); font-weight: 500; }
118
+ .sidebar-nav a.active { color: var(--accent-text); border-left-color: var(--accent-text); background: rgba(var(--accent-rgb), 0.04); font-weight: 500; }
56
119
 
57
120
  /* ── Sidebar Grouped Navigation ── */
58
121
  .nav-group {
@@ -86,7 +149,7 @@ body {
86
149
  }
87
150
 
88
151
  .nav-group-items a:hover { color: var(--ink); background: var(--cloud); }
89
- .nav-group-items a.active { color: var(--purple); border-left-color: var(--purple); background: rgba(var(--purple-rgb), 0.04); font-weight: 500; }
152
+ .nav-group-items a.active { color: var(--accent-text); border-left-color: var(--accent-text); background: rgba(var(--accent-rgb), 0.04); font-weight: 500; }
90
153
 
91
154
  .main { margin-left: 200px; flex: 1; }
92
155
 
@@ -126,8 +189,8 @@ body {
126
189
  }
127
190
 
128
191
  .copy-format-bar button.active {
129
- background: var(--purple);
130
- color: var(--white);
192
+ background: var(--accent);
193
+ color: var(--accent-foreground);
131
194
  }
132
195
 
133
196
  .copy-format-bar button:hover:not(.active) { background: var(--cloud); }
@@ -209,6 +272,14 @@ body {
209
272
  z-index: 1;
210
273
  }
211
274
 
275
+ /* Optional logo image in the header (brand.headerLogo) */
276
+ .header-logo {
277
+ display: block;
278
+ max-height: 56px;
279
+ max-width: 100%;
280
+ width: auto;
281
+ }
282
+
212
283
  /* ── Section ── */
213
284
  .section { margin-bottom: 72px; scroll-margin-top: 24px; }
214
285
 
@@ -293,7 +364,7 @@ body {
293
364
  padding: 2px 0;
294
365
  }
295
366
 
296
- .color-value:hover { color: var(--purple); }
367
+ .color-value:hover { color: var(--accent-text); }
297
368
 
298
369
  .color-role { font-size: 11px; color: var(--haze); margin-top: 2px; }
299
370
 
@@ -428,9 +499,9 @@ body {
428
499
  font-family: var(--font-body), sans-serif;
429
500
  font-size: 12px;
430
501
  font-weight: 500;
431
- color: var(--purple);
432
- background: rgba(var(--purple-rgb), 0.06);
433
- border: 1px solid rgba(var(--purple-rgb), 0.15);
502
+ color: var(--accent-text);
503
+ background: rgba(var(--accent-rgb), 0.06);
504
+ border: 1px solid rgba(var(--accent-rgb), 0.15);
434
505
  border-radius: 6px;
435
506
  padding: 4px 8px;
436
507
  cursor: pointer;
@@ -488,7 +559,7 @@ body {
488
559
  .hierarchy-demo .h-primary { font-size: 15px; color: var(--ink); line-height: 1.7; margin-bottom: 14px; }
489
560
  .hierarchy-demo .h-secondary { font-size: 15px; color: var(--graphite); line-height: 1.7; margin-bottom: 14px; }
490
561
  .hierarchy-demo .h-tertiary { font-size: 13px; color: var(--slate); line-height: 1.6; margin-bottom: 14px; }
491
- .hierarchy-demo .h-accent { font-size: 15px; font-weight: 500; color: var(--purple); }
562
+ .hierarchy-demo .h-accent { font-size: 15px; font-weight: 500; color: var(--accent-text); }
492
563
 
493
564
  .hierarchy-labels { display: flex; gap: 28px; flex-wrap: wrap; }
494
565
 
@@ -512,8 +583,8 @@ body {
512
583
 
513
584
  .wordmark-box.on-gradient { background: var(--gradient-brand); color: var(--white); }
514
585
  .wordmark-box.on-dark { background: var(--ink); color: var(--white); }
515
- .wordmark-box.on-light { background: var(--mist); color: var(--purple); }
516
- .wordmark-box.on-white { background: var(--white); color: var(--purple); box-shadow: inset 0 0 0 1px rgba(var(--ink-rgb), 0.08); }
586
+ .wordmark-box.on-light { background: var(--mist); color: var(--accent-text); }
587
+ .wordmark-box.on-white { background: var(--white); color: var(--accent-text); box-shadow: inset 0 0 0 1px rgba(var(--ink-rgb), 0.08); }
517
588
 
518
589
  .wordmark-note { font-size: 12px; color: var(--slate); text-align: center; margin-top: 6px; }
519
590
 
@@ -607,8 +678,8 @@ body {
607
678
  }
608
679
 
609
680
  .logo-format-toggle button.active {
610
- background: var(--purple);
611
- color: var(--white);
681
+ background: var(--accent);
682
+ color: var(--accent-foreground);
612
683
  }
613
684
 
614
685
  .logo-format-toggle button:hover:not(.active) {
@@ -627,8 +698,8 @@ body {
627
698
  }
628
699
 
629
700
  .logo-card.bg-light .logo-format-toggle button.active {
630
- background: var(--purple);
631
- color: var(--white);
701
+ background: var(--accent);
702
+ color: var(--accent-foreground);
632
703
  }
633
704
 
634
705
  .logo-card.bg-light .logo-format-toggle button:hover:not(.active) {
@@ -682,8 +753,8 @@ body {
682
753
  color: var(--slate);
683
754
  }
684
755
  .logo-card.on-light .logo-format-toggle button.active {
685
- background: var(--purple);
686
- color: var(--white);
756
+ background: var(--accent);
757
+ color: var(--accent-foreground);
687
758
  }
688
759
  .logo-card.on-light .logo-format-toggle button:hover:not(.active) {
689
760
  background: var(--cloud);
@@ -696,7 +767,7 @@ body {
696
767
  .logo-card.on-light .logo-download-btn {
697
768
  border-color: var(--haze);
698
769
  background: transparent;
699
- color: var(--purple);
770
+ color: var(--accent-text);
700
771
  }
701
772
  .logo-card.on-light .logo-download-btn:hover {
702
773
  background: var(--cloud);
@@ -727,11 +798,11 @@ body {
727
798
  .logo-card.bg-light .logo-download-btn {
728
799
  border-color: var(--haze);
729
800
  background: transparent;
730
- color: var(--purple);
801
+ color: var(--accent-text);
731
802
  }
732
803
 
733
804
  .logo-card.bg-light .logo-download-btn:hover {
734
- background: rgba(var(--purple-rgb), 0.06);
805
+ background: rgba(var(--accent-rgb), 0.06);
735
806
  }
736
807
 
737
808
  /* ── Voice ── */
@@ -759,7 +830,7 @@ body {
759
830
  color: var(--ink);
760
831
  margin-bottom: 10px;
761
832
  padding-left: 16px;
762
- border-left: 2px solid rgba(var(--purple-rgb), 0.2);
833
+ border-left: 2px solid rgba(var(--accent-rgb), 0.2);
763
834
  }
764
835
 
765
836
  .voice-example:last-child { margin-bottom: 0; }
@@ -792,11 +863,11 @@ body {
792
863
  .btn-sm { height: 32px; padding: 0 16px; font-size: 13px; border-radius: 9999px; }
793
864
  .btn-md { height: 40px; padding: 0 24px; font-size: 14px; border-radius: 9999px; }
794
865
  .btn-lg { height: 48px; padding: 0 28px; font-size: 16px; border-radius: 9999px; }
795
- .btn-primary { background: var(--purple); color: var(--white); }
796
- .btn-primary:hover { background: #5A2688; }
866
+ .btn-primary { background: var(--accent); color: var(--accent-foreground); }
867
+ .btn-primary:hover { filter: brightness(0.9); }
797
868
  .btn-secondary { background: var(--mist); color: var(--ink); }
798
- .btn-secondary:hover { background: #DDD8E8; }
799
- .btn-ghost { background: transparent; color: var(--purple); border: 1px solid var(--haze); }
869
+ .btn-secondary:hover { filter: brightness(0.96); }
870
+ .btn-ghost { background: transparent; color: var(--accent-text); border: 1px solid var(--haze); }
800
871
  .btn-ghost:hover { background: var(--cloud); }
801
872
  .btn-gradient { background: var(--gradient-brand); color: var(--white); }
802
873
  .btn-gradient:hover { opacity: 0.9; }
@@ -827,8 +898,8 @@ body {
827
898
  font-weight: 500;
828
899
  padding: 3px 10px;
829
900
  border-radius: 9999px;
830
- background: rgba(var(--purple-rgb), 0.08);
831
- color: var(--purple);
901
+ background: rgba(var(--accent-rgb), 0.08);
902
+ color: var(--accent-text);
832
903
  }
833
904
 
834
905
  .dark-card-demo { background: var(--ink); border-radius: 20px; padding: 36px; margin-top: 20px; }
@@ -915,7 +986,7 @@ body {
915
986
 
916
987
  .a11y-badge.pass { background: rgba(var(--success-rgb), 0.15); color: var(--success); }
917
988
  .a11y-badge.fail { background: rgba(var(--error-rgb), 0.12); color: var(--error); }
918
- .a11y-badge.large { background: rgba(var(--purple-rgb), 0.12); color: var(--purple); }
989
+ .a11y-badge.large { background: rgba(var(--accent-rgb), 0.12); color: var(--accent-text); }
919
990
 
920
991
  /* ── Code Block ── */
921
992
  .code-block {
@@ -1,41 +1,62 @@
1
1
  /**
2
- * Starter config template with __TODO markers for AI agents.
3
- * Every section includes example entries with the correct field names
4
- * so AI agents can see the exact schema contract.
5
- * Extractable fields (colors, fonts, spacing, logos) are populated by generate.
2
+ * Default showcase config a complete, company-agnostic "Brandkit" brand guide.
3
+ *
4
+ * This is what `brandkit init` scaffolds and what the example/ demo renders, so
5
+ * a fresh standup looks polished immediately. It doubles as living documentation:
6
+ * every section is populated with real, neutral sample content (no __TODO), using
7
+ * the v1.1.2 accent convention (--accent / --accent-foreground / --accent-text).
8
+ *
9
+ * Swap the values for your own brand. `brandkit generate` overwrites the
10
+ * extractable fields (colors, fonts, spacing, logos) from your codebase and
11
+ * preserves the curated ones (voice, components, accessibility copy).
6
12
  */
7
13
  function starterConfig() {
8
14
  return {
9
15
  brand: {
10
- name: '__TODO',
11
- displayName: '__TODO',
12
- tagline: '__TODO',
13
- description: '__TODO: A brief description of the brand and its visual language.',
14
- url: '__TODO',
15
- byline: '__TODO',
16
+ name: 'brandkit',
17
+ displayName: 'Brandkit',
18
+ tagline: 'The config-driven brand guide',
19
+ description: 'Brandkit turns a single config.json into a complete, living brand guide — colors, typography, logos, voice, and components, all driven by design tokens. This is the default guide; swap the config and assets to make it your own.',
20
+ url: 'github.com/The-Stack-Lab/brandkit',
21
+ byline: 'Open source · MIT',
16
22
  version: '1.0',
17
- date: new Date().toLocaleDateString('en-US', { month: 'long', year: 'numeric' })
23
+ date: new Date().toLocaleDateString('en-US', { month: 'long', year: 'numeric' }),
24
+ // Renames the "Web Style Guide" label in the header + footer.
25
+ guideLabel: 'Web Style Guide',
26
+ // Optional logo images. headerLogo replaces the text wordmark on the
27
+ // gradient header (use a light/reversed logo); sidebarLogo replaces the
28
+ // brand name at the top of the left menu (use a dark logo). Leave empty
29
+ // to fall back to the brand name as text.
30
+ headerLogo: '',
31
+ sidebarLogo: 'logos/brandkit-wordmark-dark.svg'
18
32
  },
19
33
  fonts: {
20
34
  display: {
21
- family: 'Inter',
22
- googleImport: 'Inter:wght@300;400;500;600;700',
23
- description: '__TODO: Describe the display font character and usage.'
35
+ family: 'Space Grotesk',
36
+ googleImport: 'Space+Grotesk:wght@400;500;600;700',
37
+ description: 'Space Grotesk a geometric sans with a little character. Used for headlines, the wordmark, and large display moments.'
24
38
  },
25
39
  body: {
26
40
  family: 'Inter',
27
41
  googleImport: 'Inter:wght@300;400;500;600;700',
28
- description: '__TODO: Describe the body font character and usage.'
42
+ description: 'Inter a highly legible workhorse for body copy, UI labels, and long-form reading at every size.'
29
43
  }
30
44
  },
31
45
  theme: {
32
- '--purple': '#6366F1',
33
- '--violet': '#4338CA',
46
+ // Accent fill vs. text split. --accent is the fill; --accent-foreground
47
+ // is text/icons ON the fill; --accent-text is the accent used AS text on
48
+ // a light surface. For a low-contrast fill (e.g. a bright orange), set
49
+ // --accent-foreground to a dark value and --accent-text to a darker shade.
50
+ '--accent': '#4F46E5',
51
+ '--accent-rgb': '79, 70, 229',
52
+ '--accent-foreground': '#FFFFFF',
53
+ '--accent-text': '#4338CA',
54
+ '--accent-2': '#7C3AED',
34
55
  '--lavender': '#A5B4FC',
35
- '--coral': '#F87171',
36
- '--coral-dark': '#DC2626',
37
- '--coral-light': '#FCA5A5',
38
- '--magenta': '#EC4899',
56
+ '--coral': '#FB7185',
57
+ '--coral-dark': '#E11D48',
58
+ '--coral-light': '#FDA4AF',
59
+ '--magenta': '#DB2777',
39
60
  '--ink': '#111827',
40
61
  '--graphite': '#374151',
41
62
  '--slate': '#6B7280',
@@ -43,16 +64,15 @@ function starterConfig() {
43
64
  '--mist': '#F3F4F6',
44
65
  '--cloud': '#F9FAFB',
45
66
  '--white': '#FFFFFF',
46
- '--success': '#10B981',
47
- '--warning': '#F59E0B',
48
- '--error': '#EF4444',
49
- '--info': '#3B82F6',
50
- '--gradient-brand': 'linear-gradient(135deg, #4338CA 0%, #6366F1 50%, #EC4899 100%)',
51
- '--gradient-brand-subtle': 'linear-gradient(135deg, rgba(99, 102, 241, 0.06) 0%, rgba(236, 72, 153, 0.06) 100%)',
52
- '--purple-rgb': '99, 102, 241',
67
+ '--success': '#16A34A',
68
+ '--warning': '#D97706',
69
+ '--error': '#DC2626',
70
+ '--info': '#2563EB',
71
+ '--gradient-brand': 'linear-gradient(135deg, #4F46E5 0%, #7C3AED 100%)',
72
+ '--gradient-brand-subtle': 'linear-gradient(135deg, rgba(79, 70, 229, 0.06) 0%, rgba(124, 58, 237, 0.06) 100%)',
53
73
  '--ink-rgb': '17, 24, 39',
54
- '--error-rgb': '239, 68, 68',
55
- '--success-rgb': '16, 185, 129'
74
+ '--error-rgb': '220, 38, 38',
75
+ '--success-rgb': '22, 163, 74'
56
76
  },
57
77
  nav: [
58
78
  { group: 'Colors', items: [
@@ -76,57 +96,62 @@ function starterConfig() {
76
96
  ],
77
97
  colors: {
78
98
  brand: { label: 'Brand', items: [
79
- { name: '__TODO: Primary', hex: '#6366F1', oklch: 'oklch(0.55 0.22 264)', cssVar: '--color-primary', role: '__TODO: Primary accent', light: false },
80
- { name: '__TODO: Secondary', hex: '#EC4899', oklch: 'oklch(0.59 0.22 346)', cssVar: '--color-secondary', role: '__TODO: Secondary accent', light: false }
99
+ { name: 'Indigo', hex: '#4F46E5', oklch: 'oklch(0.51 0.23 277)', cssVar: '--accent', role: 'Primary accent — buttons, links, focus', light: false },
100
+ { name: 'Violet', hex: '#7C3AED', oklch: 'oklch(0.54 0.25 293)', cssVar: '--accent-2', role: 'Secondary accent, gradient end', light: false }
81
101
  ]},
82
102
  neutrals: { label: 'Neutrals', items: [
83
- { name: 'Ink', hex: '#111827', oklch: 'oklch(0.17 0.02 265)', cssVar: '--foreground', role: 'Primary text, headings', light: false },
84
- { name: 'Slate', hex: '#6B7280', oklch: 'oklch(0.55 0.02 265)', cssVar: '--muted-foreground', role: 'Tertiary text, captions', light: false },
85
- { name: 'Mist', hex: '#F3F4F6', oklch: 'oklch(0.96 0.01 265)', cssVar: '--secondary', role: 'Section backgrounds', light: true },
86
- { name: 'Cloud', hex: '#F9FAFB', oklch: 'oklch(0.98 0.00 265)', cssVar: '--background', role: 'Page background', light: true }
103
+ { name: 'Ink', hex: '#111827', oklch: 'oklch(0.21 0.03 265)', cssVar: '--ink', role: 'Primary text, headings', light: false },
104
+ { name: 'Graphite', hex: '#374151', oklch: 'oklch(0.37 0.03 260)', cssVar: '--graphite', role: 'Secondary text, descriptions', light: false },
105
+ { name: 'Slate', hex: '#6B7280', oklch: 'oklch(0.55 0.02 264)', cssVar: '--slate', role: 'Tertiary text, captions', light: false },
106
+ { name: 'Mist', hex: '#F3F4F6', oklch: 'oklch(0.97 0.00 265)', cssVar: '--mist', role: 'Borders, section backgrounds', light: true },
107
+ { name: 'Cloud', hex: '#F9FAFB', oklch: 'oklch(0.98 0.00 248)', cssVar: '--cloud', role: 'Page background', light: true }
87
108
  ]},
88
109
  semantic: { label: 'Semantic', items: [
89
- { name: 'Success', hex: '#10B981', oklch: 'oklch(0.70 0.17 163)', cssVar: '--success', role: 'Confirmations, positive states', light: false },
90
- { name: 'Warning', hex: '#F59E0B', oklch: 'oklch(0.78 0.16 80)', cssVar: '--warning', role: 'Attention, caution states', light: false },
91
- { name: 'Error', hex: '#EF4444', oklch: 'oklch(0.59 0.22 27)', cssVar: '--destructive', role: 'Errors, destructive actions', light: false }
110
+ { name: 'Success', hex: '#16A34A', oklch: 'oklch(0.63 0.17 149)', cssVar: '--success', role: 'Confirmations, positive states', light: false },
111
+ { name: 'Warning', hex: '#D97706', oklch: 'oklch(0.67 0.16 58)', cssVar: '--warning', role: 'Attention, caution states', light: false },
112
+ { name: 'Error', hex: '#DC2626', oklch: 'oklch(0.58 0.22 27)', cssVar: '--error', role: 'Errors, destructive actions', light: false }
92
113
  ]}
93
114
  },
94
115
  gradients: [
95
116
  {
96
117
  name: 'Brand',
97
- css: 'linear-gradient(135deg, #4338CA 0%, #6366F1 50%, #EC4899 100%)',
98
- description: '__TODO: Primary gradient usage',
118
+ css: 'linear-gradient(135deg, #4F46E5 0%, #7C3AED 100%)',
119
+ description: 'Hero backgrounds, feature headers, the wordmark lockup',
99
120
  stops: [
100
- { color: '#4338CA', position: '0%', name: 'Violet' },
101
- { color: '#6366F1', position: '50%', name: 'Indigo' },
102
- { color: '#EC4899', position: '100%', name: 'Pink' }
121
+ { color: '#4F46E5', position: '0%', name: 'Indigo' },
122
+ { color: '#7C3AED', position: '100%', name: 'Violet' }
103
123
  ]
104
124
  },
105
125
  {
106
126
  name: 'Subtle',
107
- css: 'linear-gradient(135deg, rgba(99, 102, 241, 0.06) 0%, rgba(236, 72, 153, 0.06) 100%)',
127
+ css: 'linear-gradient(135deg, rgba(79, 70, 229, 0.06) 0%, rgba(124, 58, 237, 0.06) 100%)',
108
128
  description: 'Card tints, section backgrounds, hover states'
109
129
  }
110
130
  ],
111
131
  gradientUsage: {
112
- do: ['__TODO: When to use the gradient'],
113
- dont: ['__TODO: When not to use the gradient']
132
+ do: ['Hero and feature section backgrounds', 'Large display headlines (as gradient text)', 'Key stat numbers and accent moments'],
133
+ dont: ['Body copy or small text', 'Behind dense UI or data tables', 'More than one focal area per screen']
114
134
  },
115
135
  sections: {
116
- gradients: '__TODO: Describe the gradient system.',
117
- gradientTextDemo: '__TODO: A headline to demo gradient text.',
118
- logos: 'Download logo files for use across web, social, and print materials. Select format and size, then download.',
119
- components: '__TODO: Describe the component patterns.',
120
- spacing: '__TODO: Describe the spacing scale.',
121
- variables: '__TODO: Describe the CSS variable system.'
136
+ gradients: 'One brand gradient, used sparingly for hero moments and large headlines. The subtle variant tints cards and section backgrounds without competing with content.',
137
+ gradientTextDemo: 'One config, every token.',
138
+ logos: 'Download the logo in the format and size you need. Select a variant, then download — raster formats can be resized on the fly.',
139
+ components: 'A small set of building blocks — buttons, cards, and stats — that inherit every brand token automatically. Change the config and they all update.',
140
+ spacing: 'A consistent spacing scale keeps layouts in rhythm. Use these tokens for padding, gaps, and margins instead of arbitrary values.',
141
+ variables: 'Every token below is a CSS custom property. Copy any line and drop it straight into your stylesheet.'
122
142
  },
123
143
  hierarchy: [
124
144
  { class: 'h-primary', label: 'Primary', colorVar: '--ink', colorName: 'Ink', hex: '#111827', description: 'Primary text — body copy, headings, and content where readability is critical.' },
125
145
  { class: 'h-secondary', label: 'Secondary', colorVar: '--graphite', colorName: 'Graphite', hex: '#374151', description: 'Secondary text — descriptions, supporting information, and labels.' },
126
146
  { class: 'h-tertiary', label: 'Tertiary', colorVar: '--slate', colorName: 'Slate', hex: '#6B7280', description: 'Tertiary text — captions, timestamps, footnotes, and metadata.' },
127
- { class: 'h-accent', label: 'Accent', colorVar: '--purple', colorName: 'Primary', hex: '#6366F1', description: 'Accent text — links, interactive labels, and calls to action.' }
147
+ { class: 'h-accent', label: 'Accent', colorVar: '--accent-text', colorName: 'Indigo', hex: '#4338CA', description: 'Accent text — links, interactive labels, and calls to action.' }
148
+ ],
149
+ logos: [
150
+ { name: 'Wordmark', description: 'Primary lockup — use on light backgrounds.', variants: { svg: 'logos/brandkit-wordmark-dark.svg' }, background: 'light' },
151
+ { name: 'Wordmark, reversed', description: 'For dark backgrounds and photography.', variants: { svg: 'logos/brandkit-wordmark-light.svg' }, background: 'dark' },
152
+ { name: 'Monogram', description: 'Compact mark for avatars and favicons.', variants: { svg: 'logos/brandkit-mark.svg' }, background: 'light' },
153
+ { name: 'Monogram on gradient', description: 'Reversed mark for the brand gradient.', variants: { svg: 'logos/brandkit-mark-white.svg' }, background: 'gradient' }
128
154
  ],
129
- logos: [],
130
155
  logoSizes: [
131
156
  { label: 'Original', width: null },
132
157
  { label: '800px', width: 800 },
@@ -134,34 +159,44 @@ function starterConfig() {
134
159
  { label: '200px', width: 200 }
135
160
  ],
136
161
  typography: [
137
- { name: 'Display XL', font: 'display', size: '72px', weight: 700, tracking: '-0.03em', leading: '1.05', sample: '__TODO: Hero headline' },
138
- { name: 'Display', font: 'display', size: '56px', weight: 700, tracking: '-0.025em', leading: '1.1', sample: '__TODO: Section headline' },
139
- { name: 'H1', font: 'display', size: '44px', weight: 700, tracking: '-0.02em', leading: '1.15', sample: '__TODO: Page heading' },
140
- { name: 'H2', font: 'display', size: '36px', weight: 700, tracking: '-0.015em', leading: '1.2', sample: '__TODO: Section heading' },
141
- { name: 'H3', font: 'display', size: '28px', weight: 600, tracking: '-0.01em', leading: '1.3', sample: '__TODO: Subsection heading' },
142
- { name: 'H4', font: 'display', size: '22px', weight: 600, tracking: '-0.005em', leading: '1.35', sample: '__TODO: Card heading' },
143
- { name: 'Body LG', font: 'body', size: '18px', weight: 400, tracking: '0', leading: '1.7', sample: '__TODO: Intro paragraph' },
144
- { name: 'Body', font: 'body', size: '16px', weight: 400, tracking: '0', leading: '1.7', sample: '__TODO: Body copy' },
145
- { name: 'Body SM', font: 'body', size: '14px', weight: 400, tracking: '0', leading: '1.6', sample: '__TODO: Small text' },
146
- { name: 'Caption', font: 'body', size: '12px', weight: 500, tracking: '0.02em', leading: '1.5', sample: '__TODO: Caption text' },
147
- { name: 'Overline', font: 'body', size: '11px', weight: 700, tracking: '0.1em', leading: '1.4', sample: '__TODO: Overline text', uppercase: true }
162
+ { name: 'Display XL', font: 'display', size: '72px', weight: 700, tracking: '-0.03em', leading: '1.05', sample: 'One config, every token' },
163
+ { name: 'Display', font: 'display', size: '56px', weight: 700, tracking: '-0.025em', leading: '1.1', sample: 'A brand guide that builds itself' },
164
+ { name: 'H1', font: 'display', size: '44px', weight: 700, tracking: '-0.02em', leading: '1.15', sample: 'Design tokens, end to end' },
165
+ { name: 'H2', font: 'display', size: '36px', weight: 700, tracking: '-0.015em', leading: '1.2', sample: 'Colors, type, and components' },
166
+ { name: 'H3', font: 'display', size: '28px', weight: 600, tracking: '-0.01em', leading: '1.3', sample: 'Consistent by construction' },
167
+ { name: 'H4', font: 'display', size: '22px', weight: 600, tracking: '-0.005em', leading: '1.35', sample: 'A card heading, set in display' },
168
+ { name: 'Body LG', font: 'body', size: '18px', weight: 400, tracking: '0', leading: '1.7', sample: 'Lead paragraphs and intros sit comfortably at this size with generous line height.' },
169
+ { name: 'Body', font: 'body', size: '16px', weight: 400, tracking: '0', leading: '1.7', sample: 'The default for body copy — legible, neutral, and easy on the eyes at any length.' },
170
+ { name: 'Body SM', font: 'body', size: '14px', weight: 400, tracking: '0', leading: '1.6', sample: 'Smaller supporting text for secondary details and helper copy.' },
171
+ { name: 'Caption', font: 'body', size: '12px', weight: 500, tracking: '0.02em', leading: '1.5', sample: 'Captions, timestamps, and metadata' },
172
+ { name: 'Overline', font: 'body', size: '11px', weight: 700, tracking: '0.1em', leading: '1.4', sample: 'Section label', uppercase: true }
148
173
  ],
149
174
  voice: {
150
- description: '__TODO: Describe the brand voice.',
151
- do: ['__TODO: Example of good copy'],
152
- dont: ['__TODO: Example of bad copy']
175
+ description: 'Clear, confident, and friendly. Brandkit speaks like a thoughtful teammate — it explains the what and the why without jargon, and never talks down to the reader.',
176
+ do: ['Swap the config and your whole guide updates.', 'Every token is one source of truth.', 'Ship a polished brand guide in minutes.'],
177
+ dont: ['Leverage synergistic design paradigms.', 'It just works (somehow).', 'Refer to the documentation for further details.']
153
178
  },
154
179
  accessibility: [
155
- { fg: '#6366F1', bg: '#FFFFFF', fgName: 'Primary', bgName: 'White', ratio: '__TODO', rating: '__TODO', border: true, largeText: false },
156
- { fg: '#FFFFFF', bg: '#111827', fgName: 'White', bgName: 'Ink', ratio: '__TODO', rating: '__TODO', border: false, largeText: false }
180
+ { fg: '#4338CA', bg: '#FFFFFF', fgName: 'Indigo Text', bgName: 'White', ratio: '7.9:1', rating: 'AAA', border: true, largeText: false },
181
+ { fg: '#FFFFFF', bg: '#4F46E5', fgName: 'White', bgName: 'Indigo', ratio: '6.3:1', rating: 'AA', border: false, largeText: false },
182
+ { fg: '#FFFFFF', bg: '#111827', fgName: 'White', bgName: 'Ink', ratio: '17.7:1', rating: 'AAA', border: false, largeText: false },
183
+ { fg: '#374151', bg: '#F9FAFB', fgName: 'Graphite', bgName: 'Cloud', ratio: '9.9:1', rating: 'AAA', border: true, largeText: false },
184
+ { fg: '#6B7280', bg: '#FFFFFF', fgName: 'Slate', bgName: 'White', ratio: '4.8:1', rating: 'AA', border: true, largeText: false },
185
+ { fg: '#FFFFFF', bg: '#16A34A', fgName: 'White', bgName: 'Success', ratio: '3.3:1', rating: 'AA Large', border: false, largeText: true }
157
186
  ],
158
187
  cssVariables: [
159
- { section: 'Colors', vars: [
160
- { prop: '--color-primary', value: '#6366F1', comment: 'Primary brand color' },
161
- { prop: '--foreground', value: '#111827', comment: 'Primary text' }
188
+ { section: 'Accent', vars: [
189
+ { prop: '--accent', value: '#4F46E5', comment: 'Fill buttons, active states' },
190
+ { prop: '--accent-foreground', value: '#FFFFFF', comment: 'Text/icons on the fill' },
191
+ { prop: '--accent-text', value: '#4338CA', comment: 'Accent as text on light' }
192
+ ]},
193
+ { section: 'Neutrals', vars: [
194
+ { prop: '--ink', value: '#111827', comment: 'Primary text' },
195
+ { prop: '--slate', value: '#6B7280', comment: 'Tertiary text' },
196
+ { prop: '--cloud', value: '#F9FAFB', comment: 'Page background' }
162
197
  ]},
163
198
  { section: 'Typography', vars: [
164
- { prop: '--font-display', value: "'Inter', sans-serif", comment: 'Display font' },
199
+ { prop: '--font-display', value: "'Space Grotesk', sans-serif", comment: 'Display font' },
165
200
  { prop: '--font-body', value: "'Inter', sans-serif", comment: 'Body font' }
166
201
  ]}
167
202
  ],
@@ -175,14 +210,25 @@ function starterConfig() {
175
210
  components: {
176
211
  buttons: [
177
212
  { variant: 'Primary', items: [
178
- { class: 'btn-primary', sizes: ['sm', 'md', 'lg'], labels: ['Small', '__TODO: Medium CTA', '__TODO: Large CTA'] }
213
+ { class: 'btn-primary', sizes: ['sm', 'md', 'lg'], labels: ['Get started', 'Get started', 'Get started'] }
214
+ ]},
215
+ { variant: 'Secondary & ghost', items: [
216
+ { class: 'btn-secondary', sizes: ['md'], labels: ['Documentation'] },
217
+ { class: 'btn-ghost', sizes: ['md'], labels: ['Learn more'] }
218
+ ]},
219
+ { variant: 'Gradient', items: [
220
+ { class: 'btn-gradient', sizes: ['md'], labels: ['Try the demo'] }
179
221
  ]}
180
222
  ],
181
223
  cards: [
182
- { title: '__TODO: Card Title', description: '__TODO: Card description text', tag: '__TODO' }
224
+ { title: 'One config', description: 'A single config.json drives every color, font, and component in the guide.', tag: 'Tokens' },
225
+ { title: 'Zero dependencies', description: 'Vanilla JS and CSS variables — nothing to install, nothing to maintain.', tag: 'Lightweight' },
226
+ { title: 'Bolts onto anything', description: 'Drop it into React, Vite, Astro, Next.js, or plain HTML in minutes.', tag: 'Portable' }
183
227
  ],
184
228
  stats: [
185
- { value: '__TODO', label: '__TODO: Stat description' }
229
+ { value: '1', label: 'Config file to rule them all' },
230
+ { value: '0', label: 'Runtime dependencies' },
231
+ { value: '12', label: 'Sections, rendered from tokens' }
186
232
  ]
187
233
  }
188
234
  };
@@ -94,7 +94,7 @@ function mapToTheme(cssVars) {
94
94
  var mapping = {
95
95
  '--background': '--cloud',
96
96
  '--foreground': '--ink',
97
- '--primary': '--purple',
97
+ '--primary': '--accent',
98
98
  '--secondary': '--mist',
99
99
  '--muted': '--mist',
100
100
  '--muted-foreground': '--slate',
@@ -226,7 +226,7 @@ function buildHierarchyFromTheme(theme) {
226
226
  var fg = resolve('--ink', ['--foreground'], '#111827');
227
227
  var secondary = resolve('--graphite', ['--slate', '--muted-foreground'], '#374151');
228
228
  var tertiary = resolve('--slate', ['--haze', '--muted-foreground'], '#6B7280');
229
- var accent = resolve('--purple', ['--coral', '--primary'], '#6366F1');
229
+ var accent = resolve('--accent-text', ['--accent', '--purple', '--coral', '--primary'], '#6366F1');
230
230
 
231
231
  function keyToName(key) {
232
232
  return key.replace(/^--/, '').replace(/-/g, ' ').replace(/\b\w/g, function (c) { return c.toUpperCase(); });
package/lib/template.js CHANGED
@@ -46,10 +46,12 @@ function generateFontsLink(config) {
46
46
  * Writes index.html and styles.css into the target directory.
47
47
  */
48
48
  function build(distDir, targetDir, config) {
49
- // Build styles.css: prepend :root block
49
+ // Build styles.css: append the config :root AFTER the stylesheet's default
50
+ // tokens so brand overrides win the cascade (styles.css ships a baseline
51
+ // :root; an earlier block would be overridden by it).
50
52
  var cssTemplate = fs.readFileSync(path.join(distDir, 'styles.css'), 'utf8');
51
53
  var rootCSS = generateRootCSS(config);
52
- var builtCSS = '/* Generated by brandkit build */\n' + rootCSS + '\n\n' + cssTemplate;
54
+ var builtCSS = cssTemplate + '\n\n/* Generated by brandkit build — brand overrides */\n' + rootCSS + '\n';
53
55
  fs.writeFileSync(path.join(targetDir, 'styles.css'), builtCSS);
54
56
 
55
57
  // Build index.html: inject fonts link + title
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stacklist-app/brandkit",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Config-driven brand guide that bolts onto any website. Zero dependencies.",
5
5
  "main": "lib/resolve.js",
6
6
  "bin": {