@021.is/brand-studio 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1196 @@
1
+ import { BrandStudioSection, SpinnerLoadingMark } from './chunk-55AYEWNQ.js';
2
+ import { generateScheme, schemeToCssText, ROLE_GROUPS, contrastBadge, contrastRatio } from './chunk-JQV3ASME.js';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+ import { useRef, useState } from 'react';
5
+
6
+ var ITEMS = [
7
+ { section: BrandStudioSection.Overview, label: "Overview" },
8
+ { section: BrandStudioSection.Logo, label: "Logo" },
9
+ { section: BrandStudioSection.Colors, label: "Colors" },
10
+ { section: BrandStudioSection.Typography, label: "Type" },
11
+ { section: BrandStudioSection.Icons, label: "Icons" },
12
+ { section: BrandStudioSection.Motion, label: "Motion" },
13
+ { section: BrandStudioSection.Download, label: "Download" }
14
+ ];
15
+ function buildNavigation(basePath) {
16
+ return ITEMS.map((it) => ({
17
+ section: it.section,
18
+ label: it.label,
19
+ href: it.section === BrandStudioSection.Overview ? basePath : `${basePath}/${it.section}`
20
+ }));
21
+ }
22
+ function Navigation({ current, basePath, renderLink }) {
23
+ const items = buildNavigation(basePath);
24
+ return /* @__PURE__ */ jsx("nav", { style: { borderBottom: "1px solid var(--bs-border, #D8DAD0)" }, children: /* @__PURE__ */ jsx(
25
+ "ul",
26
+ {
27
+ style: {
28
+ display: "flex",
29
+ gap: "1.5rem",
30
+ listStyle: "none",
31
+ margin: 0,
32
+ padding: "0 0 0.75rem 0",
33
+ fontFamily: "var(--bs-mono, ui-monospace, monospace)",
34
+ fontSize: "0.78rem",
35
+ letterSpacing: "0.12em",
36
+ textTransform: "uppercase"
37
+ },
38
+ children: items.map((it) => /* @__PURE__ */ jsx("li", { children: renderLink({
39
+ href: it.href,
40
+ children: it.label,
41
+ isActive: it.section === current
42
+ }) }, it.section))
43
+ }
44
+ ) });
45
+ }
46
+ var eyebrow = {
47
+ fontFamily: "var(--bs-mono)",
48
+ fontSize: "0.72rem",
49
+ letterSpacing: "0.18em",
50
+ textTransform: "uppercase",
51
+ color: "var(--md-primary)",
52
+ margin: 0
53
+ };
54
+ function Swatch({
55
+ colors,
56
+ bg,
57
+ on
58
+ }) {
59
+ const bgHex = colors[bg];
60
+ const onHex = colors[on];
61
+ const badge = contrastBadge(contrastRatio(onHex, bgHex));
62
+ const failing = badge.tier === "fail";
63
+ return /* @__PURE__ */ jsxs(
64
+ "article",
65
+ {
66
+ style: {
67
+ borderRadius: "0.6rem",
68
+ border: "1px solid var(--md-outline-variant)",
69
+ overflow: "hidden"
70
+ },
71
+ children: [
72
+ /* @__PURE__ */ jsxs("div", { style: { background: bgHex, color: onHex, padding: "1rem 1.1rem", minHeight: "5.5rem" }, children: [
73
+ /* @__PURE__ */ jsx("p", { style: { margin: 0, fontWeight: 600, fontSize: "0.9rem" }, children: String(bg) }),
74
+ /* @__PURE__ */ jsxs(
75
+ "p",
76
+ {
77
+ style: {
78
+ margin: "0.15rem 0 0",
79
+ fontFamily: "var(--bs-mono)",
80
+ fontSize: "0.68rem",
81
+ letterSpacing: "0.04em",
82
+ opacity: 0.85
83
+ },
84
+ children: [
85
+ bgHex,
86
+ " \xB7 on ",
87
+ onHex
88
+ ]
89
+ }
90
+ )
91
+ ] }),
92
+ /* @__PURE__ */ jsxs(
93
+ "div",
94
+ {
95
+ style: {
96
+ padding: "0.5rem 1.1rem",
97
+ fontFamily: "var(--bs-mono)",
98
+ fontSize: "0.68rem",
99
+ color: failing ? "var(--md-error)" : "var(--md-on-surface-variant)",
100
+ background: "var(--md-surface-container-low)"
101
+ },
102
+ children: [
103
+ badge.label,
104
+ " \xB7 ",
105
+ badge.tier
106
+ ]
107
+ }
108
+ )
109
+ ]
110
+ }
111
+ );
112
+ }
113
+ function SchemeGrid({ colors }) {
114
+ return /* @__PURE__ */ jsx("div", { style: { display: "grid", gap: "1.5rem" }, children: ROLE_GROUPS.map((group) => /* @__PURE__ */ jsxs("div", { children: [
115
+ /* @__PURE__ */ jsx(
116
+ "p",
117
+ {
118
+ style: {
119
+ fontFamily: "var(--bs-mono)",
120
+ fontSize: "0.68rem",
121
+ letterSpacing: "0.12em",
122
+ textTransform: "uppercase",
123
+ color: "var(--md-on-surface-variant)",
124
+ margin: "0 0 0.6rem"
125
+ },
126
+ children: group.title
127
+ }
128
+ ),
129
+ /* @__PURE__ */ jsx(
130
+ "div",
131
+ {
132
+ style: {
133
+ display: "grid",
134
+ gap: "0.6rem",
135
+ gridTemplateColumns: "repeat(auto-fill, minmax(180px, 1fr))"
136
+ },
137
+ children: group.pairs.map(([bg, on]) => /* @__PURE__ */ jsx(Swatch, { colors, bg, on }, String(bg)))
138
+ }
139
+ )
140
+ ] }, group.title)) });
141
+ }
142
+ function ColorsSection({ scheme }) {
143
+ return /* @__PURE__ */ jsxs("section", { children: [
144
+ /* @__PURE__ */ jsxs("header", { children: [
145
+ /* @__PURE__ */ jsx("p", { style: eyebrow, children: "Colors" }),
146
+ /* @__PURE__ */ jsx(
147
+ "h2",
148
+ {
149
+ style: {
150
+ fontSize: "clamp(1.5rem, 3vw, 2.25rem)",
151
+ letterSpacing: "-0.02em",
152
+ margin: "0.4rem 0 0",
153
+ fontWeight: 600,
154
+ color: "var(--md-on-surface)"
155
+ },
156
+ children: "One seed. A complete system."
157
+ }
158
+ ),
159
+ /* @__PURE__ */ jsx(
160
+ "p",
161
+ {
162
+ style: { maxWidth: "52ch", color: "var(--md-on-surface-variant)", marginTop: "0.75rem" },
163
+ children: "A Material Design 3 tonal palette derived from the brand seed. Every on-color is contrast-checked against its surface per WCAG 2.1, in both light and dark."
164
+ }
165
+ )
166
+ ] }),
167
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "2.25rem", display: "grid", gap: "2.5rem" }, children: [
168
+ /* @__PURE__ */ jsxs("div", { children: [
169
+ /* @__PURE__ */ jsx("p", { style: { ...eyebrow, fontSize: "0.7rem", marginBottom: "1rem" }, children: "Light scheme" }),
170
+ /* @__PURE__ */ jsx(SchemeGrid, { colors: scheme.light })
171
+ ] }),
172
+ /* @__PURE__ */ jsxs("div", { children: [
173
+ /* @__PURE__ */ jsx("p", { style: { ...eyebrow, fontSize: "0.7rem", marginBottom: "1rem" }, children: "Dark scheme" }),
174
+ /* @__PURE__ */ jsx(SchemeGrid, { colors: scheme.dark })
175
+ ] })
176
+ ] })
177
+ ] });
178
+ }
179
+ var ICON_SIZES = [64, 128, 256, 512, 1024];
180
+ var WORDMARK_HEIGHTS = [32, 48, 64, 96, 128];
181
+ function triggerDownload(svgString, filename) {
182
+ const blob = new Blob([svgString], { type: "image/svg+xml;charset=utf-8" });
183
+ const url = URL.createObjectURL(blob);
184
+ const a = document.createElement("a");
185
+ a.href = url;
186
+ a.download = filename;
187
+ document.body.appendChild(a);
188
+ a.click();
189
+ a.remove();
190
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
191
+ }
192
+ function DownloadSection({
193
+ config,
194
+ staticLogo,
195
+ staticLogoDark
196
+ }) {
197
+ const iconLightRef = useRef(null);
198
+ const iconDarkRef = useRef(null);
199
+ const measureRef = useRef(null);
200
+ const [iconSize, setIconSize] = useState(256);
201
+ const [wmHeight, setWmHeight] = useState(64);
202
+ const scheme = config.scheme ?? generateScheme(config.color);
203
+ const sans = config.type.sans;
204
+ const lightBg = scheme.light.surfaceContainer;
205
+ const darkBg = scheme.dark.surfaceContainer;
206
+ const lightInk = scheme.light.onSurface;
207
+ const darkInk = scheme.dark.onSurface;
208
+ const lightLabel = scheme.light.onSurfaceVariant;
209
+ const darkLabel = scheme.dark.onSurfaceVariant;
210
+ const iconDarkNode = staticLogoDark ?? staticLogo;
211
+ const hasDistinctDark = staticLogoDark !== void 0 && staticLogoDark !== null;
212
+ function downloadIcon(mode) {
213
+ const ref = mode === "light" ? iconLightRef : iconDarkRef;
214
+ const svg = ref.current?.querySelector("svg");
215
+ if (!svg) return;
216
+ const clone = svg.cloneNode(true);
217
+ clone.setAttribute("xmlns", "http://www.w3.org/2000/svg");
218
+ clone.setAttribute("width", String(iconSize));
219
+ clone.setAttribute("height", String(iconSize));
220
+ triggerDownload(
221
+ new XMLSerializer().serializeToString(clone),
222
+ `${config.name}-icon-${iconSize}-${mode}.svg`
223
+ );
224
+ }
225
+ async function downloadWordmark(mode) {
226
+ await document.fonts?.ready;
227
+ const text = measureRef.current?.querySelector("text");
228
+ if (!text) return;
229
+ const bbox = text.getBBox();
230
+ const pad = bbox.height * 0.14;
231
+ const vbW = bbox.width + pad * 2;
232
+ const vbH = bbox.height + pad * 2;
233
+ const scale = wmHeight / vbH;
234
+ const fill = mode === "light" ? lightInk : darkInk;
235
+ const out = [
236
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${Math.round(vbW * scale)}" height="${wmHeight}" viewBox="0 0 ${vbW.toFixed(1)} ${vbH.toFixed(1)}" role="img" aria-label="${config.name}">`,
237
+ `<text x="${(pad - bbox.x).toFixed(1)}" y="${pad.toFixed(1)}" dominant-baseline="text-before-edge" font-family="${sans}, system-ui, sans-serif" font-weight="600" font-size="100" letter-spacing="-3" fill="${fill}">${config.name}</text>`,
238
+ "</svg>"
239
+ ].join("");
240
+ triggerDownload(out, `${config.name}-wordmark-${wmHeight}-${mode}.svg`);
241
+ }
242
+ return /* @__PURE__ */ jsxs("section", { children: [
243
+ /* @__PURE__ */ jsx(
244
+ "h2",
245
+ {
246
+ style: {
247
+ fontSize: "1.6rem",
248
+ fontWeight: 600,
249
+ color: "var(--md-on-surface)",
250
+ margin: "0 0 0.5rem"
251
+ },
252
+ children: "Download"
253
+ }
254
+ ),
255
+ /* @__PURE__ */ jsx(
256
+ "p",
257
+ {
258
+ style: {
259
+ color: "var(--md-on-surface-variant)",
260
+ maxWidth: "40rem",
261
+ margin: "0 0 2.5rem",
262
+ lineHeight: 1.6
263
+ },
264
+ children: "Export the app icon and wordmark as standalone SVGs in light and dark variants. Pick a size, download, drop into your console branding page."
265
+ }
266
+ ),
267
+ /* @__PURE__ */ jsxs(
268
+ "div",
269
+ {
270
+ style: {
271
+ display: "grid",
272
+ gap: "1.5rem",
273
+ gridTemplateColumns: "repeat(auto-fit, minmax(20rem, 1fr))"
274
+ },
275
+ children: [
276
+ /* @__PURE__ */ jsxs("div", { style: cardStyle, children: [
277
+ /* @__PURE__ */ jsx("p", { style: eyebrowStyle, children: "App icon \xB7 square" }),
278
+ /* @__PURE__ */ jsxs("div", { style: previewRow, children: [
279
+ /* @__PURE__ */ jsx(PreviewPane, { bg: lightBg, label: "Light", labelColor: lightLabel, children: /* @__PURE__ */ jsx("div", { ref: iconLightRef, style: { width: 80, height: 80 }, children: staticLogo ?? /* @__PURE__ */ jsx(NoAsset, {}) }) }),
280
+ /* @__PURE__ */ jsx(PreviewPane, { bg: darkBg, label: "Dark", labelColor: darkLabel, children: /* @__PURE__ */ jsx("div", { ref: iconDarkRef, style: { width: 80, height: 80 }, children: iconDarkNode ?? /* @__PURE__ */ jsx(NoAsset, {}) }) })
281
+ ] }),
282
+ /* @__PURE__ */ jsx(Dropdown, { value: iconSize, onChange: setIconSize, options: ICON_SIZES, unit: "px" }),
283
+ /* @__PURE__ */ jsxs(ButtonRow, { children: [
284
+ /* @__PURE__ */ jsx(
285
+ "button",
286
+ {
287
+ type: "button",
288
+ onClick: () => downloadIcon("light"),
289
+ disabled: !staticLogo,
290
+ style: buttonStyle(!staticLogo, "primary"),
291
+ children: "Light SVG"
292
+ }
293
+ ),
294
+ /* @__PURE__ */ jsx(
295
+ "button",
296
+ {
297
+ type: "button",
298
+ onClick: () => downloadIcon("dark"),
299
+ disabled: !iconDarkNode,
300
+ style: buttonStyle(!iconDarkNode, "secondary"),
301
+ children: "Dark SVG"
302
+ }
303
+ )
304
+ ] }),
305
+ staticLogo && !hasDistinctDark ? /* @__PURE__ */ jsxs(Note, { children: [
306
+ "No dark icon supplied \u2014 dark export reuses the light mark. Pass",
307
+ " ",
308
+ /* @__PURE__ */ jsx("code", { style: mono, children: "staticLogoDark" }),
309
+ " for a true dark variant."
310
+ ] }) : null
311
+ ] }),
312
+ /* @__PURE__ */ jsxs("div", { style: cardStyle, children: [
313
+ /* @__PURE__ */ jsx("p", { style: eyebrowStyle, children: "Wordmark \xB7 logo text" }),
314
+ /* @__PURE__ */ jsxs("div", { style: previewRow, children: [
315
+ /* @__PURE__ */ jsx(PreviewPane, { bg: lightBg, label: "Light", labelColor: lightLabel, children: /* @__PURE__ */ jsx(WordmarkPreview, { name: config.name, sans, color: lightInk }) }),
316
+ /* @__PURE__ */ jsx(PreviewPane, { bg: darkBg, label: "Dark", labelColor: darkLabel, children: /* @__PURE__ */ jsx(WordmarkPreview, { name: config.name, sans, color: darkInk }) })
317
+ ] }),
318
+ /* @__PURE__ */ jsx(
319
+ Dropdown,
320
+ {
321
+ value: wmHeight,
322
+ onChange: setWmHeight,
323
+ options: WORDMARK_HEIGHTS,
324
+ unit: "px tall"
325
+ }
326
+ ),
327
+ /* @__PURE__ */ jsxs(ButtonRow, { children: [
328
+ /* @__PURE__ */ jsx(
329
+ "button",
330
+ {
331
+ type: "button",
332
+ onClick: () => downloadWordmark("light"),
333
+ style: buttonStyle(false, "primary"),
334
+ children: "Light SVG"
335
+ }
336
+ ),
337
+ /* @__PURE__ */ jsx(
338
+ "button",
339
+ {
340
+ type: "button",
341
+ onClick: () => downloadWordmark("dark"),
342
+ style: buttonStyle(false, "secondary"),
343
+ children: "Dark SVG"
344
+ }
345
+ )
346
+ ] })
347
+ ] })
348
+ ]
349
+ }
350
+ ),
351
+ /* @__PURE__ */ jsxs(
352
+ "p",
353
+ {
354
+ style: {
355
+ color: "var(--md-on-surface-variant)",
356
+ fontSize: "0.78rem",
357
+ marginTop: "1.5rem",
358
+ lineHeight: 1.6
359
+ },
360
+ children: [
361
+ "SVG is vector \u2014 the size just sets the export width/height. The wordmark references the",
362
+ " ",
363
+ /* @__PURE__ */ jsx("code", { style: mono, children: sans }),
364
+ " font; targets without it fall back to the system sans."
365
+ ]
366
+ }
367
+ ),
368
+ /* @__PURE__ */ jsx(
369
+ "svg",
370
+ {
371
+ ref: measureRef,
372
+ "aria-hidden": "true",
373
+ width: "1000",
374
+ height: "300",
375
+ style: { position: "absolute", left: "-9999px", top: 0, opacity: 0, pointerEvents: "none" },
376
+ children: /* @__PURE__ */ jsx(
377
+ "text",
378
+ {
379
+ x: "0",
380
+ y: "0",
381
+ dominantBaseline: "text-before-edge",
382
+ fontFamily: `${sans}, system-ui, sans-serif`,
383
+ fontWeight: 600,
384
+ fontSize: 100,
385
+ letterSpacing: "-3",
386
+ children: config.name
387
+ }
388
+ )
389
+ }
390
+ )
391
+ ] });
392
+ }
393
+ var mono = { fontFamily: "var(--bs-mono)" };
394
+ function NoAsset() {
395
+ return /* @__PURE__ */ jsx("span", { style: { color: "var(--md-on-surface-variant)", fontSize: "0.8rem" }, children: "no icon" });
396
+ }
397
+ function WordmarkPreview({ name, sans, color }) {
398
+ return /* @__PURE__ */ jsx(
399
+ "span",
400
+ {
401
+ style: {
402
+ fontFamily: `${sans}, system-ui, sans-serif`,
403
+ fontWeight: 600,
404
+ fontSize: "1.9rem",
405
+ letterSpacing: "-0.03em",
406
+ color
407
+ },
408
+ children: name
409
+ }
410
+ );
411
+ }
412
+ function PreviewPane({
413
+ bg,
414
+ label,
415
+ labelColor,
416
+ children
417
+ }) {
418
+ return /* @__PURE__ */ jsxs(
419
+ "div",
420
+ {
421
+ style: {
422
+ position: "relative",
423
+ flex: 1,
424
+ display: "flex",
425
+ alignItems: "center",
426
+ justifyContent: "center",
427
+ height: "8rem",
428
+ borderRadius: "0.6rem",
429
+ background: bg,
430
+ overflow: "hidden"
431
+ },
432
+ children: [
433
+ /* @__PURE__ */ jsx(
434
+ "span",
435
+ {
436
+ style: {
437
+ position: "absolute",
438
+ top: "0.4rem",
439
+ left: "0.55rem",
440
+ fontFamily: "var(--bs-mono, ui-monospace, monospace)",
441
+ fontSize: "0.62rem",
442
+ letterSpacing: "0.12em",
443
+ textTransform: "uppercase",
444
+ color: labelColor,
445
+ opacity: 0.85
446
+ },
447
+ children: label
448
+ }
449
+ ),
450
+ children
451
+ ]
452
+ }
453
+ );
454
+ }
455
+ var previewRow = {
456
+ display: "flex",
457
+ gap: "0.6rem",
458
+ margin: "0.75rem 0 1rem"
459
+ };
460
+ function ButtonRow({ children }) {
461
+ return /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: "0.5rem", marginTop: "0.6rem" }, children });
462
+ }
463
+ function Note({ children }) {
464
+ return /* @__PURE__ */ jsx(
465
+ "p",
466
+ {
467
+ style: {
468
+ marginTop: "0.7rem",
469
+ fontSize: "0.72rem",
470
+ color: "var(--md-on-surface-variant)",
471
+ lineHeight: 1.5
472
+ },
473
+ children
474
+ }
475
+ );
476
+ }
477
+ function Dropdown({
478
+ value,
479
+ onChange,
480
+ options,
481
+ unit
482
+ }) {
483
+ return /* @__PURE__ */ jsx(
484
+ "select",
485
+ {
486
+ value,
487
+ onChange: (e) => onChange(Number(e.target.value)),
488
+ style: {
489
+ width: "100%",
490
+ padding: "0.55rem 0.7rem",
491
+ borderRadius: "0.5rem",
492
+ border: "1px solid var(--md-outline-variant)",
493
+ background: "var(--md-surface-container-low)",
494
+ color: "var(--md-on-surface)",
495
+ fontFamily: "var(--bs-mono, ui-monospace, monospace)",
496
+ fontSize: "0.8rem",
497
+ cursor: "pointer"
498
+ },
499
+ children: options.map((o) => /* @__PURE__ */ jsxs("option", { value: o, children: [
500
+ o,
501
+ " ",
502
+ unit
503
+ ] }, o))
504
+ }
505
+ );
506
+ }
507
+ var cardStyle = {
508
+ border: "1px solid var(--md-outline-variant)",
509
+ borderRadius: "0.85rem",
510
+ padding: "1.25rem",
511
+ background: "var(--md-surface-container-low)"
512
+ };
513
+ var eyebrowStyle = {
514
+ fontFamily: "var(--bs-mono, ui-monospace, monospace)",
515
+ fontSize: "0.7rem",
516
+ letterSpacing: "0.14em",
517
+ textTransform: "uppercase",
518
+ color: "var(--md-primary)",
519
+ margin: 0
520
+ };
521
+ function buttonStyle(disabled, kind) {
522
+ const isPrimary = kind === "primary";
523
+ return {
524
+ flex: 1,
525
+ padding: "0.6rem 0.9rem",
526
+ borderRadius: "0.5rem",
527
+ border: isPrimary ? "none" : "1px solid var(--md-primary)",
528
+ background: disabled ? "var(--md-surface-variant)" : isPrimary ? "var(--md-primary)" : "transparent",
529
+ color: disabled ? "var(--md-on-surface-variant)" : isPrimary ? "var(--md-on-primary)" : "var(--md-primary)",
530
+ fontSize: "0.82rem",
531
+ fontWeight: 600,
532
+ cursor: disabled ? "not-allowed" : "pointer",
533
+ whiteSpace: "nowrap"
534
+ };
535
+ }
536
+ var eyebrow2 = {
537
+ fontFamily: "var(--bs-mono)",
538
+ fontSize: "0.72rem",
539
+ letterSpacing: "0.18em",
540
+ textTransform: "uppercase",
541
+ color: "var(--md-primary)",
542
+ margin: 0
543
+ };
544
+ function Tile({ label, children }) {
545
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: "0.6rem" }, children: [
546
+ /* @__PURE__ */ jsx(
547
+ "div",
548
+ {
549
+ style: {
550
+ display: "flex",
551
+ alignItems: "center",
552
+ justifyContent: "center",
553
+ width: "6rem",
554
+ height: "6rem",
555
+ borderRadius: "0.75rem",
556
+ background: "var(--md-surface-container-low)",
557
+ border: "1px solid var(--md-outline-variant)"
558
+ },
559
+ children: /* @__PURE__ */ jsx("div", { style: { width: 56, height: 56 }, children })
560
+ }
561
+ ),
562
+ label ? /* @__PURE__ */ jsx(
563
+ "span",
564
+ {
565
+ style: {
566
+ fontFamily: "var(--bs-mono)",
567
+ fontSize: "0.7rem",
568
+ color: "var(--md-on-surface-variant)"
569
+ },
570
+ children: label
571
+ }
572
+ ) : null
573
+ ] });
574
+ }
575
+ function IconsSection({ staticLogo, icons }) {
576
+ const items = icons ?? [];
577
+ const hasAny = Boolean(staticLogo) || items.length > 0;
578
+ return /* @__PURE__ */ jsxs("section", { children: [
579
+ /* @__PURE__ */ jsxs("header", { children: [
580
+ /* @__PURE__ */ jsx("p", { style: eyebrow2, children: "Icons" }),
581
+ /* @__PURE__ */ jsx(
582
+ "h2",
583
+ {
584
+ style: {
585
+ fontSize: "clamp(1.5rem, 3vw, 2.25rem)",
586
+ letterSpacing: "-0.02em",
587
+ margin: "0.4rem 0 0",
588
+ fontWeight: 600,
589
+ color: "var(--md-on-surface)"
590
+ },
591
+ children: "Brand glyphs."
592
+ }
593
+ ),
594
+ /* @__PURE__ */ jsxs(
595
+ "p",
596
+ {
597
+ style: { maxWidth: "52ch", color: "var(--md-on-surface-variant)", marginTop: "0.75rem" },
598
+ children: [
599
+ "Your brand's own marks and glyphs. brand-studio ships no icon set \u2014 pass yours via the",
600
+ " ",
601
+ /* @__PURE__ */ jsx("code", { style: { fontFamily: "var(--bs-mono)" }, children: "icons" }),
602
+ " prop."
603
+ ]
604
+ }
605
+ )
606
+ ] }),
607
+ hasAny ? /* @__PURE__ */ jsxs(
608
+ "div",
609
+ {
610
+ style: {
611
+ marginTop: "2rem",
612
+ display: "flex",
613
+ flexWrap: "wrap",
614
+ gap: "1.5rem"
615
+ },
616
+ children: [
617
+ staticLogo ? /* @__PURE__ */ jsx(Tile, { label: "logo", children: staticLogo }) : null,
618
+ items.map((it, i) => /* @__PURE__ */ jsx(Tile, { label: it.label, children: it.node }, it.label ?? `icon-${i}`))
619
+ ]
620
+ }
621
+ ) : /* @__PURE__ */ jsxs(
622
+ "p",
623
+ {
624
+ style: {
625
+ marginTop: "2rem",
626
+ padding: "2.5rem",
627
+ borderRadius: "0.75rem",
628
+ border: "1px dashed var(--md-outline-variant)",
629
+ color: "var(--md-on-surface-variant)",
630
+ textAlign: "center",
631
+ fontSize: "0.9rem"
632
+ },
633
+ children: [
634
+ "No glyphs supplied. Pass ",
635
+ /* @__PURE__ */ jsx("code", { style: { fontFamily: "var(--bs-mono)" }, children: "staticLogo" }),
636
+ " ",
637
+ "or ",
638
+ /* @__PURE__ */ jsx("code", { style: { fontFamily: "var(--bs-mono)" }, children: "icons" }),
639
+ " to",
640
+ " ",
641
+ /* @__PURE__ */ jsx("code", { style: { fontFamily: "var(--bs-mono)" }, children: "<BrandStudio>" }),
642
+ "."
643
+ ]
644
+ }
645
+ )
646
+ ] });
647
+ }
648
+ var eyebrow3 = {
649
+ fontFamily: "var(--bs-mono)",
650
+ fontSize: "0.7rem",
651
+ letterSpacing: "0.16em",
652
+ textTransform: "uppercase",
653
+ color: "var(--md-primary)",
654
+ margin: 0
655
+ };
656
+ var caption = {
657
+ marginTop: "0.75rem",
658
+ fontSize: "0.9rem",
659
+ lineHeight: 1.55,
660
+ color: "var(--md-on-surface-variant)",
661
+ maxWidth: "60ch"
662
+ };
663
+ function LogoSection({ staticLogo, animatedLogo }) {
664
+ const sizes = [16, 24, 32, 48, 64, 128, 200];
665
+ return /* @__PURE__ */ jsxs("section", { style: { display: "grid", gap: "3rem" }, children: [
666
+ /* @__PURE__ */ jsxs("header", { children: [
667
+ /* @__PURE__ */ jsx("p", { style: eyebrow3, children: "Logo" }),
668
+ /* @__PURE__ */ jsx(
669
+ "h2",
670
+ {
671
+ style: {
672
+ fontSize: "clamp(1.5rem, 3vw, 2.25rem)",
673
+ letterSpacing: "-0.02em",
674
+ margin: "0.4rem 0 0",
675
+ fontWeight: 600,
676
+ color: "var(--md-on-surface)"
677
+ },
678
+ children: "Mark, scale, motion."
679
+ }
680
+ )
681
+ ] }),
682
+ /* @__PURE__ */ jsxs("div", { children: [
683
+ /* @__PURE__ */ jsx("p", { style: eyebrow3, children: "Animated \xB7 loading" }),
684
+ /* @__PURE__ */ jsx(
685
+ "div",
686
+ {
687
+ style: {
688
+ marginTop: "0.75rem",
689
+ display: "flex",
690
+ justifyContent: "center",
691
+ padding: "3rem",
692
+ borderRadius: "0.75rem",
693
+ background: "var(--md-surface-container)",
694
+ border: "1px solid var(--md-outline-variant)",
695
+ color: "var(--md-primary)"
696
+ },
697
+ children: animatedLogo ?? /* @__PURE__ */ jsx(SpinnerLoadingMark, { size: 220, foreground: "var(--md-primary)" })
698
+ }
699
+ ),
700
+ /* @__PURE__ */ jsx("p", { style: caption, children: "The loading-state mark. Loops indefinitely." })
701
+ ] }),
702
+ staticLogo ? /* @__PURE__ */ jsxs("div", { children: [
703
+ /* @__PURE__ */ jsx("p", { style: eyebrow3, children: "Static \xB7 size scale" }),
704
+ /* @__PURE__ */ jsx(
705
+ "div",
706
+ {
707
+ style: {
708
+ marginTop: "0.75rem",
709
+ display: "flex",
710
+ flexWrap: "wrap",
711
+ gap: "2rem",
712
+ alignItems: "flex-end",
713
+ padding: "2rem",
714
+ borderRadius: "0.75rem",
715
+ background: "var(--md-surface-container-low)",
716
+ border: "1px solid var(--md-outline-variant)"
717
+ },
718
+ children: sizes.map((s) => /* @__PURE__ */ jsxs(
719
+ "div",
720
+ {
721
+ style: {
722
+ display: "flex",
723
+ flexDirection: "column",
724
+ alignItems: "center",
725
+ gap: "0.5rem"
726
+ },
727
+ children: [
728
+ /* @__PURE__ */ jsx("div", { style: { width: s, height: s }, children: staticLogo }),
729
+ /* @__PURE__ */ jsxs(
730
+ "span",
731
+ {
732
+ style: {
733
+ fontFamily: "var(--bs-mono)",
734
+ fontSize: "0.7rem",
735
+ color: "var(--md-on-surface-variant)"
736
+ },
737
+ children: [
738
+ s,
739
+ "px"
740
+ ]
741
+ }
742
+ )
743
+ ]
744
+ },
745
+ s
746
+ ))
747
+ }
748
+ )
749
+ ] }) : null
750
+ ] });
751
+ }
752
+ var eyebrow4 = {
753
+ fontFamily: "var(--bs-mono)",
754
+ fontSize: "0.72rem",
755
+ letterSpacing: "0.18em",
756
+ textTransform: "uppercase",
757
+ color: "var(--md-primary)",
758
+ margin: 0
759
+ };
760
+ var PRIMITIVES = [
761
+ ["Sweep", "Wipe a highlight across a mark"],
762
+ ["Drift", "Gentle idle float"],
763
+ ["PulseLoop", "Scale + fade breathing"],
764
+ ["RingDraw", "Stroke a ring into place"],
765
+ ["Wobble", "Playful rotate-settle"],
766
+ ["AppearGrow", "Pop in from nothing"],
767
+ ["Sequence", "Compose steps, persist + loop"],
768
+ ["Strikethrough", "Draw a line through"]
769
+ ];
770
+ function MotionPlaygroundSection({
771
+ animatedLogo
772
+ }) {
773
+ return /* @__PURE__ */ jsxs("section", { children: [
774
+ /* @__PURE__ */ jsxs("header", { children: [
775
+ /* @__PURE__ */ jsx("p", { style: eyebrow4, children: "Motion" }),
776
+ /* @__PURE__ */ jsx(
777
+ "h2",
778
+ {
779
+ style: {
780
+ fontSize: "clamp(1.5rem, 3vw, 2.25rem)",
781
+ letterSpacing: "-0.02em",
782
+ margin: "0.4rem 0 0",
783
+ fontWeight: 600,
784
+ color: "var(--md-on-surface)"
785
+ },
786
+ children: "Loading."
787
+ }
788
+ ),
789
+ /* @__PURE__ */ jsxs(
790
+ "p",
791
+ {
792
+ style: { maxWidth: "56ch", color: "var(--md-on-surface-variant)", marginTop: "0.75rem" },
793
+ children: [
794
+ "The brand mark as a loading state. Pass your own animated mark via",
795
+ " ",
796
+ /* @__PURE__ */ jsx("code", { style: { fontFamily: "var(--bs-mono)" }, children: "animatedLogo" }),
797
+ "; otherwise a neutral spinner stands in."
798
+ ]
799
+ }
800
+ )
801
+ ] }),
802
+ /* @__PURE__ */ jsx(
803
+ "div",
804
+ {
805
+ style: {
806
+ marginTop: "2rem",
807
+ display: "flex",
808
+ justifyContent: "center",
809
+ padding: "3rem",
810
+ borderRadius: "0.75rem",
811
+ background: "var(--md-surface-container)",
812
+ border: "1px solid var(--md-outline-variant)",
813
+ color: "var(--md-primary)"
814
+ },
815
+ children: animatedLogo ?? /* @__PURE__ */ jsx(SpinnerLoadingMark, { size: 220, foreground: "var(--md-primary)" })
816
+ }
817
+ ),
818
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "2.5rem" }, children: [
819
+ /* @__PURE__ */ jsx("p", { style: { ...eyebrow4, fontSize: "0.7rem", marginBottom: "1rem" }, children: "Motion primitives" }),
820
+ /* @__PURE__ */ jsx(
821
+ "div",
822
+ {
823
+ style: {
824
+ display: "grid",
825
+ gap: "0.6rem",
826
+ gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))"
827
+ },
828
+ children: PRIMITIVES.map(([name, desc]) => /* @__PURE__ */ jsxs(
829
+ "div",
830
+ {
831
+ style: {
832
+ padding: "0.9rem 1.1rem",
833
+ borderRadius: "0.6rem",
834
+ border: "1px solid var(--md-outline-variant)",
835
+ background: "var(--md-surface-container-low)"
836
+ },
837
+ children: [
838
+ /* @__PURE__ */ jsx(
839
+ "p",
840
+ {
841
+ style: {
842
+ margin: 0,
843
+ fontFamily: "var(--bs-mono)",
844
+ fontSize: "0.82rem",
845
+ color: "var(--md-on-surface)"
846
+ },
847
+ children: name
848
+ }
849
+ ),
850
+ /* @__PURE__ */ jsx(
851
+ "p",
852
+ {
853
+ style: {
854
+ margin: "0.3rem 0 0",
855
+ fontSize: "0.8rem",
856
+ color: "var(--md-on-surface-variant)"
857
+ },
858
+ children: desc
859
+ }
860
+ )
861
+ ]
862
+ },
863
+ name
864
+ ))
865
+ }
866
+ )
867
+ ] })
868
+ ] });
869
+ }
870
+ var eyebrow5 = {
871
+ fontFamily: "var(--bs-mono)",
872
+ fontSize: "0.72rem",
873
+ letterSpacing: "0.18em",
874
+ textTransform: "uppercase",
875
+ color: "var(--md-primary)",
876
+ margin: 0
877
+ };
878
+ function OverviewSection({ config }) {
879
+ return /* @__PURE__ */ jsxs("section", { children: [
880
+ /* @__PURE__ */ jsx("p", { style: eyebrow5, children: "Brand kit" }),
881
+ /* @__PURE__ */ jsxs(
882
+ "h1",
883
+ {
884
+ style: {
885
+ fontSize: "clamp(2rem, 4vw, 3.25rem)",
886
+ letterSpacing: "-0.025em",
887
+ margin: "0.5rem 0 0",
888
+ fontWeight: 600,
889
+ color: "var(--md-on-surface)"
890
+ },
891
+ children: [
892
+ config.name,
893
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--md-primary)" }, children: "." })
894
+ ]
895
+ }
896
+ ),
897
+ config.tagline ? /* @__PURE__ */ jsx(
898
+ "p",
899
+ {
900
+ style: {
901
+ margin: "1rem 0 0",
902
+ fontSize: "1.05rem",
903
+ color: "var(--md-on-surface-variant)",
904
+ maxWidth: "44ch"
905
+ },
906
+ children: config.tagline
907
+ }
908
+ ) : null,
909
+ config.voice && config.voice.length > 0 ? /* @__PURE__ */ jsxs("div", { style: { marginTop: "2.5rem" }, children: [
910
+ /* @__PURE__ */ jsx("p", { style: { ...eyebrow5, fontSize: "0.7rem", letterSpacing: "0.16em" }, children: "Voice" }),
911
+ /* @__PURE__ */ jsx(
912
+ "ul",
913
+ {
914
+ style: {
915
+ margin: "0.75rem 0 0",
916
+ padding: 0,
917
+ listStyle: "none",
918
+ display: "grid",
919
+ gap: "0.5rem"
920
+ },
921
+ children: config.voice.map((v) => /* @__PURE__ */ jsx(
922
+ "li",
923
+ {
924
+ style: {
925
+ color: "var(--md-on-surface)",
926
+ fontFamily: "var(--bs-mono)",
927
+ fontSize: "0.92rem"
928
+ },
929
+ children: v
930
+ },
931
+ v
932
+ ))
933
+ }
934
+ )
935
+ ] }) : null
936
+ ] });
937
+ }
938
+ function TypographySection({ config }) {
939
+ const sans = config.type.sans;
940
+ const mono2 = config.type.mono;
941
+ return /* @__PURE__ */ jsxs("section", { children: [
942
+ /* @__PURE__ */ jsxs("header", { children: [
943
+ /* @__PURE__ */ jsx(
944
+ "p",
945
+ {
946
+ style: {
947
+ fontFamily: "var(--bs-mono)",
948
+ fontSize: "0.72rem",
949
+ letterSpacing: "0.18em",
950
+ textTransform: "uppercase",
951
+ color: "var(--md-primary)",
952
+ margin: 0
953
+ },
954
+ children: "Typography"
955
+ }
956
+ ),
957
+ /* @__PURE__ */ jsxs(
958
+ "h2",
959
+ {
960
+ style: {
961
+ fontSize: "clamp(1.5rem, 3vw, 2.25rem)",
962
+ letterSpacing: "-0.02em",
963
+ margin: "0.4rem 0 0",
964
+ fontWeight: 600,
965
+ color: "var(--md-on-surface)"
966
+ },
967
+ children: [
968
+ sans,
969
+ mono2 ? ` + ${mono2}` : null
970
+ ]
971
+ }
972
+ )
973
+ ] }),
974
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "2.5rem", display: "grid", gap: "2rem" }, children: [
975
+ /* @__PURE__ */ jsx(Card, { label: "Display \xB7 72/1.05 \xB7 -0.025em", children: /* @__PURE__ */ jsxs(
976
+ "p",
977
+ {
978
+ style: {
979
+ fontFamily: sans,
980
+ fontSize: "4.5rem",
981
+ fontWeight: 600,
982
+ letterSpacing: "-0.025em",
983
+ lineHeight: 1.05,
984
+ margin: 0,
985
+ color: "var(--md-on-surface)"
986
+ },
987
+ children: [
988
+ config.name,
989
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--md-primary)" }, children: "." })
990
+ ]
991
+ }
992
+ ) }),
993
+ /* @__PURE__ */ jsx(Card, { label: "Headings", children: /* @__PURE__ */ jsx("div", { style: { display: "grid", gap: "0.75rem" }, children: [
994
+ { size: "3rem", label: "h1 \xB7 48" },
995
+ { size: "2.25rem", label: "h2 \xB7 36" },
996
+ { size: "1.5rem", label: "h3 \xB7 24" },
997
+ { size: "1.125rem", label: "h4 \xB7 18" }
998
+ ].map((h) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "baseline", gap: "1.5rem" }, children: [
999
+ /* @__PURE__ */ jsx(
1000
+ "span",
1001
+ {
1002
+ style: {
1003
+ fontFamily: "var(--bs-mono)",
1004
+ fontSize: "0.7rem",
1005
+ color: "var(--md-on-surface-variant)",
1006
+ minWidth: "5rem"
1007
+ },
1008
+ children: h.label
1009
+ }
1010
+ ),
1011
+ /* @__PURE__ */ jsx(
1012
+ "span",
1013
+ {
1014
+ style: {
1015
+ fontFamily: sans,
1016
+ fontSize: h.size,
1017
+ fontWeight: 600,
1018
+ letterSpacing: "-0.015em",
1019
+ color: "var(--md-on-surface)"
1020
+ },
1021
+ children: "The quick brown fox jumps."
1022
+ }
1023
+ )
1024
+ ] }, h.label)) }) }),
1025
+ /* @__PURE__ */ jsx(Card, { label: "Body \xB7 16/1.55", children: /* @__PURE__ */ jsx(
1026
+ "p",
1027
+ {
1028
+ style: {
1029
+ fontFamily: sans,
1030
+ fontSize: "1rem",
1031
+ lineHeight: 1.55,
1032
+ color: "var(--md-on-surface)",
1033
+ maxWidth: "62ch",
1034
+ margin: 0
1035
+ },
1036
+ children: "Pack my box with five dozen liquor jugs. The quick brown fox jumps over the lazy dog, then waltzes back to set the body copy at a comfortable measure for sustained reading."
1037
+ }
1038
+ ) }),
1039
+ mono2 ? /* @__PURE__ */ jsx(Card, { label: `Mono \xB7 ${mono2} \xB7 captions, eyebrows`, children: /* @__PURE__ */ jsx(
1040
+ "p",
1041
+ {
1042
+ style: {
1043
+ fontFamily: mono2,
1044
+ fontSize: "0.9rem",
1045
+ letterSpacing: "0.04em",
1046
+ color: "var(--md-on-surface-variant)",
1047
+ margin: 0
1048
+ },
1049
+ children: "const scheme = generateScheme({ seed })"
1050
+ }
1051
+ ) }) : null
1052
+ ] })
1053
+ ] });
1054
+ }
1055
+ function Card({ label, children }) {
1056
+ return /* @__PURE__ */ jsxs(
1057
+ "article",
1058
+ {
1059
+ style: {
1060
+ padding: "2rem",
1061
+ borderRadius: "0.75rem",
1062
+ border: "1px solid var(--md-outline-variant)",
1063
+ background: "var(--md-surface-container-low)"
1064
+ },
1065
+ children: [
1066
+ /* @__PURE__ */ jsx(
1067
+ "p",
1068
+ {
1069
+ style: {
1070
+ margin: "0 0 1rem",
1071
+ fontFamily: "var(--bs-mono)",
1072
+ fontSize: "0.7rem",
1073
+ letterSpacing: "0.16em",
1074
+ textTransform: "uppercase",
1075
+ color: "var(--md-primary)"
1076
+ },
1077
+ children: label
1078
+ }
1079
+ ),
1080
+ children
1081
+ ]
1082
+ }
1083
+ );
1084
+ }
1085
+ function resolveSection(route) {
1086
+ const head = route?.[0];
1087
+ switch (head) {
1088
+ case BrandStudioSection.Logo:
1089
+ case BrandStudioSection.Colors:
1090
+ case BrandStudioSection.Typography:
1091
+ case BrandStudioSection.Icons:
1092
+ case BrandStudioSection.Motion:
1093
+ case BrandStudioSection.Download:
1094
+ return head;
1095
+ default:
1096
+ return BrandStudioSection.Overview;
1097
+ }
1098
+ }
1099
+ function BrandStudio({
1100
+ config,
1101
+ route,
1102
+ basePath = "/brand",
1103
+ staticLogo,
1104
+ staticLogoDark,
1105
+ animatedLogo,
1106
+ icons
1107
+ }) {
1108
+ const section = resolveSection(route);
1109
+ const scheme = config.scheme ?? generateScheme(config.color);
1110
+ const cssText = schemeToCssText(scheme, {
1111
+ lightSelector: "[data-brand-studio]",
1112
+ darkSelector: ".dark [data-brand-studio], [data-brand-studio].dark"
1113
+ });
1114
+ const link = ({
1115
+ href,
1116
+ children,
1117
+ isActive
1118
+ }) => /* @__PURE__ */ jsx(
1119
+ "a",
1120
+ {
1121
+ href,
1122
+ style: {
1123
+ color: isActive ? "var(--md-primary)" : "var(--md-on-surface-variant)",
1124
+ textDecoration: "none",
1125
+ fontWeight: isActive ? 600 : 400,
1126
+ borderBottom: isActive ? "2px solid var(--md-primary)" : "2px solid transparent",
1127
+ paddingBottom: "0.4rem"
1128
+ },
1129
+ children
1130
+ }
1131
+ );
1132
+ return /* @__PURE__ */ jsxs(
1133
+ "main",
1134
+ {
1135
+ "data-brand-studio": "",
1136
+ style: {
1137
+ maxWidth: "72rem",
1138
+ margin: "0 auto",
1139
+ padding: "4rem 1.5rem 6rem",
1140
+ fontFamily: `var(--bs-sans, ${config.type.sans})`,
1141
+ color: "var(--md-on-surface)",
1142
+ ["--bs-sans"]: config.type.sans,
1143
+ ["--bs-mono"]: config.type.mono ?? "ui-monospace, monospace",
1144
+ ["--bs-serif"]: config.type.serif ?? "Georgia, serif",
1145
+ ["--bs-border"]: "var(--md-outline-variant)"
1146
+ },
1147
+ children: [
1148
+ /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: cssText } }),
1149
+ /* @__PURE__ */ jsx(Navigation, { current: section, basePath, renderLink: link }),
1150
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "3rem" }, children: [
1151
+ section === BrandStudioSection.Overview && /* @__PURE__ */ jsx(OverviewSection, { config }),
1152
+ section === BrandStudioSection.Logo && /* @__PURE__ */ jsx(LogoSection, { config, staticLogo, animatedLogo }),
1153
+ section === BrandStudioSection.Colors && /* @__PURE__ */ jsx(ColorsSection, { scheme }),
1154
+ section === BrandStudioSection.Typography && /* @__PURE__ */ jsx(TypographySection, { config }),
1155
+ section === BrandStudioSection.Icons && /* @__PURE__ */ jsx(IconsSection, { staticLogo, icons }),
1156
+ section === BrandStudioSection.Motion && /* @__PURE__ */ jsx(MotionPlaygroundSection, { animatedLogo, staticLogo }),
1157
+ section === BrandStudioSection.Download && /* @__PURE__ */ jsx(
1158
+ DownloadSection,
1159
+ {
1160
+ config,
1161
+ staticLogo,
1162
+ staticLogoDark
1163
+ }
1164
+ )
1165
+ ] }),
1166
+ /* @__PURE__ */ jsxs(
1167
+ "footer",
1168
+ {
1169
+ style: {
1170
+ marginTop: "5rem",
1171
+ paddingTop: "1.5rem",
1172
+ borderTop: "1px solid var(--md-outline-variant)",
1173
+ fontFamily: "var(--bs-mono)",
1174
+ fontSize: "0.72rem",
1175
+ color: "var(--md-on-surface-variant)",
1176
+ display: "flex",
1177
+ flexWrap: "wrap",
1178
+ gap: "1.5rem",
1179
+ justifyContent: "space-between"
1180
+ },
1181
+ children: [
1182
+ /* @__PURE__ */ jsxs("span", { children: [
1183
+ "Powered by ",
1184
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--md-primary)" }, children: "@021.is/brand-studio" })
1185
+ ] }),
1186
+ config.operator ? /* @__PURE__ */ jsx("span", { children: config.operator }) : null,
1187
+ config.domain ? /* @__PURE__ */ jsx("span", { children: config.domain }) : null
1188
+ ]
1189
+ }
1190
+ )
1191
+ ]
1192
+ }
1193
+ );
1194
+ }
1195
+
1196
+ export { BrandStudio, Navigation, buildNavigation };