@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 +16 -8
- package/cli/generate.js +35 -1
- package/cli/init.js +16 -0
- package/dist/engine.js +49 -13
- package/dist/logos/brandkit-mark-white.svg +8 -0
- package/dist/logos/brandkit-mark.svg +14 -0
- package/dist/logos/brandkit-wordmark-dark.svg +15 -0
- package/dist/logos/brandkit-wordmark-light.svg +9 -0
- package/dist/styles.css +100 -29
- package/lib/config-schema.js +124 -78
- package/lib/extract-css.js +1 -1
- package/lib/generate-helpers.js +1 -1
- package/lib/template.js +4 -2
- package/package.json +1 -1
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.
|
|
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
|
|
82
|
+
See `example/config.json` for the complete default demo brand — the same config `init` scaffolds.
|
|
83
83
|
|
|
84
|
-
##
|
|
84
|
+
## Theming
|
|
85
85
|
|
|
86
|
-
`dist/styles.css`
|
|
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
|
-
|
|
89
|
-
|
|
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
|
|
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
|
|
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)
|
|
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 =
|
|
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>
|
|
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
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
if (
|
|
778
|
-
|
|
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)
|
|
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(--
|
|
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(--
|
|
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(--
|
|
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(--
|
|
130
|
-
color: var(--
|
|
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(--
|
|
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(--
|
|
432
|
-
background: rgba(var(--
|
|
433
|
-
border: 1px solid rgba(var(--
|
|
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(--
|
|
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(--
|
|
516
|
-
.wordmark-box.on-white { background: var(--white); color: var(--
|
|
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(--
|
|
611
|
-
color: var(--
|
|
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(--
|
|
631
|
-
color: var(--
|
|
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(--
|
|
686
|
-
color: var(--
|
|
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(--
|
|
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(--
|
|
801
|
+
color: var(--accent-text);
|
|
731
802
|
}
|
|
732
803
|
|
|
733
804
|
.logo-card.bg-light .logo-download-btn:hover {
|
|
734
|
-
background: rgba(var(--
|
|
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(--
|
|
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(--
|
|
796
|
-
.btn-primary:hover {
|
|
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 {
|
|
799
|
-
.btn-ghost { background: transparent; color: var(--
|
|
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(--
|
|
831
|
-
color: var(--
|
|
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(--
|
|
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 {
|
package/lib/config-schema.js
CHANGED
|
@@ -1,41 +1,62 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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: '
|
|
11
|
-
displayName: '
|
|
12
|
-
tagline: '
|
|
13
|
-
description: '
|
|
14
|
-
url: '
|
|
15
|
-
byline: '
|
|
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: '
|
|
22
|
-
googleImport: '
|
|
23
|
-
description: '
|
|
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: '
|
|
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
|
-
|
|
33
|
-
|
|
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': '#
|
|
36
|
-
'--coral-dark': '#
|
|
37
|
-
'--coral-light': '#
|
|
38
|
-
'--magenta': '#
|
|
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': '#
|
|
47
|
-
'--warning': '#
|
|
48
|
-
'--error': '#
|
|
49
|
-
'--info': '#
|
|
50
|
-
'--gradient-brand': 'linear-gradient(135deg, #
|
|
51
|
-
'--gradient-brand-subtle': 'linear-gradient(135deg, rgba(
|
|
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': '
|
|
55
|
-
'--success-rgb': '
|
|
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: '
|
|
80
|
-
{ name: '
|
|
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.
|
|
84
|
-
{ name: '
|
|
85
|
-
{ name: '
|
|
86
|
-
{ name: '
|
|
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: '#
|
|
90
|
-
{ name: 'Warning', hex: '#
|
|
91
|
-
{ name: 'Error', hex: '#
|
|
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, #
|
|
98
|
-
description: '
|
|
118
|
+
css: 'linear-gradient(135deg, #4F46E5 0%, #7C3AED 100%)',
|
|
119
|
+
description: 'Hero backgrounds, feature headers, the wordmark lockup',
|
|
99
120
|
stops: [
|
|
100
|
-
{ color: '#
|
|
101
|
-
{ color: '#
|
|
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(
|
|
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: ['
|
|
113
|
-
dont: ['
|
|
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: '
|
|
117
|
-
gradientTextDemo: '
|
|
118
|
-
logos: 'Download logo
|
|
119
|
-
components: '
|
|
120
|
-
spacing: '
|
|
121
|
-
variables: '
|
|
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: '--
|
|
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: '
|
|
138
|
-
{ name: 'Display', font: 'display', size: '56px', weight: 700, tracking: '-0.025em', leading: '1.1', sample: '
|
|
139
|
-
{ name: 'H1', font: 'display', size: '44px', weight: 700, tracking: '-0.02em', leading: '1.15', sample: '
|
|
140
|
-
{ name: 'H2', font: 'display', size: '36px', weight: 700, tracking: '-0.015em', leading: '1.2', sample: '
|
|
141
|
-
{ name: 'H3', font: 'display', size: '28px', weight: 600, tracking: '-0.01em', leading: '1.3', sample: '
|
|
142
|
-
{ name: 'H4', font: 'display', size: '22px', weight: 600, tracking: '-0.005em', leading: '1.35', sample: '
|
|
143
|
-
{ name: 'Body LG', font: 'body', size: '18px', weight: 400, tracking: '0', leading: '1.7', sample: '
|
|
144
|
-
{ name: 'Body', font: 'body', size: '16px', weight: 400, tracking: '0', leading: '1.7', sample: '
|
|
145
|
-
{ name: 'Body SM', font: 'body', size: '14px', weight: 400, tracking: '0', leading: '1.6', sample: '
|
|
146
|
-
{ name: 'Caption', font: 'body', size: '12px', weight: 500, tracking: '0.02em', leading: '1.5', sample: '
|
|
147
|
-
{ name: 'Overline', font: 'body', size: '11px', weight: 700, tracking: '0.1em', leading: '1.4', sample: '
|
|
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: '
|
|
151
|
-
do: ['
|
|
152
|
-
dont: ['
|
|
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: '#
|
|
156
|
-
{ fg: '#FFFFFF', bg: '#
|
|
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: '
|
|
160
|
-
{ prop: '--
|
|
161
|
-
{ prop: '--foreground', value: '#
|
|
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: "'
|
|
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: ['
|
|
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: '
|
|
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: '
|
|
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
|
};
|
package/lib/extract-css.js
CHANGED
package/lib/generate-helpers.js
CHANGED
|
@@ -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('--
|
|
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:
|
|
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
|
|
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
|