sonance-brand-mcp 1.4.3 → 1.4.7

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.
@@ -71,8 +71,8 @@ font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-s
71
71
  | Black | `#000000` | `--sonance-black` | `bg-sonance-black` | Text on light backgrounds |
72
72
 
73
73
  **Print Specifications** (for reference):
74
- - Charcoal: Pantone 7545 C, CMYK 72/51/39/36, RAL 7024
75
- - The Beam: Pantone 3262 C, CMYK 79/0/32/0
74
+ - Charcoal: Pantone 7545 C, CMYK 65/43/26/78, RAL 7024
75
+ - The Beam: Pantone 3262 C, CMYK 82/11/0/0
76
76
 
77
77
  ### Gray Scale (derived from Charcoal)
78
78
  | Level | Hex | CSS Variable |
@@ -115,8 +115,8 @@ export const brandPresets: BrandPreset[] = [
115
115
 
116
116
  export const brandLogos: Record<BrandId, BrandLogos> = {
117
117
  sonance: {
118
- light: '/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png',
119
- dark: '/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png',
118
+ light: '/logos/sonance/Sonance_Logo_2C_Dark_RGB.png',
119
+ dark: '/logos/sonance/Sonance_Logo_2C_Light_RGB.png',
120
120
  alt: 'Sonance',
121
121
  },
122
122
  iport: {
@@ -16,36 +16,36 @@ export function ThemeToggle({ className }: { className?: string }) {
16
16
  }
17
17
 
18
18
  return (
19
- <div data-sonance-name="theme-toggle" className={cn("flex items-center gap-1 p-1 bg-background-secondary rounded-sm", className)}>
19
+ <div data-sonance-name="theme-toggle" className={cn("flex items-center gap-1 p-1 bg-background-secondary rounded-sm theme-toggle-container", className)}>
20
20
  <button
21
21
  onClick={() => setTheme("light")}
22
22
  className={cn(
23
- "p-2 rounded-sm transition-colors",
23
+ "theme-toggle-btn",
24
24
  theme === "light" ? "bg-background text-foreground" : "text-foreground-muted hover:text-foreground"
25
25
  )}
26
26
  aria-label="Light mode"
27
27
  >
28
- <Sun className="w-4 h-4" />
28
+ <Sun className="theme-toggle-icon" />
29
29
  </button>
30
30
  <button
31
31
  onClick={() => setTheme("dark")}
32
32
  className={cn(
33
- "p-2 rounded-sm transition-colors",
33
+ "theme-toggle-btn",
34
34
  theme === "dark" ? "bg-background text-foreground" : "text-foreground-muted hover:text-foreground"
35
35
  )}
36
36
  aria-label="Dark mode"
37
37
  >
38
- <Moon className="w-4 h-4" />
38
+ <Moon className="theme-toggle-icon" />
39
39
  </button>
40
40
  <button
41
41
  onClick={() => setTheme("system")}
42
42
  className={cn(
43
- "p-2 rounded-sm transition-colors",
43
+ "theme-toggle-btn",
44
44
  theme === "system" ? "bg-background text-foreground" : "text-foreground-muted hover:text-foreground"
45
45
  )}
46
46
  aria-label="System theme"
47
47
  >
48
- <Monitor className="w-4 h-4" />
48
+ <Monitor className="theme-toggle-icon" />
49
49
  </button>
50
50
  </div>
51
51
  );
@@ -0,0 +1,17 @@
1
+ {
2
+ "schema": "bloom-dataviz-registry/v1",
3
+ "updatedAt": "2026-07-13T00:00:00.000Z",
4
+ "themes": [
5
+ {
6
+ "id": "teal-ignition",
7
+ "label": "Teal Ignition",
8
+ "brand": "sonance",
9
+ "scope": {
10
+ "kind": "library"
11
+ },
12
+ "file": "teal-ignition.json",
13
+ "cover": null,
14
+ "publishedAt": "2026-07-13T00:00:00.000Z"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,196 @@
1
+ {
2
+ "schema": "bloom-viz-theme/v1",
3
+ "id": "teal-ignition",
4
+ "label": "Teal Ignition",
5
+ "brand": "sonance",
6
+ "scope": {
7
+ "kind": "library"
8
+ },
9
+ "story": {
10
+ "title": "Sonance Data-Viz · Teal Ignition",
11
+ "description": "Teal Pulse opens in the dark like a held breath. A bioluminescent form gathers itself from nothing, petal over translucent petal, teal deepening to near-black at the seams. Threads of amber and violet flicker beneath the surface — capillaries of stray color that never break the surface calm. The bloom widens, contracts, widens again, patient as tide. Toward the bottom third, the glow surrenders to charcoal void, unhurried, as if the mural remembers it is also night."
12
+ },
13
+ "publishedAt": "2026-07-14T16:23:57.822Z",
14
+ "source": {
15
+ "app": "bloom",
16
+ "assetId": "ast_viz5de80f12"
17
+ },
18
+ "cover": "/dataviz/teal-ignition-cover.png",
19
+ "mural": "/dataviz/teal-ignition-mural.mp4",
20
+ "theme": {
21
+ "label": "Teal Ignition",
22
+ "reasoning": "Leads with electric teal instead of brand blue to give the dashboard a livelier, more energetic pulse, while glass surfaces, whitespace, and calm draw-ins keep it wall-display quiet.",
23
+ "light": {
24
+ "surface": "#ffffff",
25
+ "plane": "#f8f9fa",
26
+ "inkPrimary": "#333f48",
27
+ "inkSecondary": "#515c64",
28
+ "inkMuted": "#8f999f",
29
+ "grid": "#eceeef",
30
+ "baseline": "#e2e5e6",
31
+ "categorical": [
32
+ "#009b93",
33
+ "#ed9e2c",
34
+ "#8b5cf6",
35
+ "#00a3e1",
36
+ "#ff5a5f",
37
+ "#8bc34a",
38
+ "#e0449c",
39
+ "#5468ff"
40
+ ],
41
+ "sequential": [
42
+ "#e4f4fc",
43
+ "#bee6f7",
44
+ "#8fd4f0",
45
+ "#57bee6",
46
+ "#1fa3d8",
47
+ "#0884b9",
48
+ "#056090"
49
+ ],
50
+ "diverging": {
51
+ "neg": [
52
+ "#0b72b9",
53
+ "#5fa9d9",
54
+ "#afd6ec"
55
+ ],
56
+ "mid": "#9aa3a8",
57
+ "pos": [
58
+ "#ffd59e",
59
+ "#ffa84d",
60
+ "#e8752a"
61
+ ]
62
+ },
63
+ "status": {
64
+ "good": "#2bb673",
65
+ "warning": "#e8a23c",
66
+ "serious": "#f2622e",
67
+ "critical": "#e1341e"
68
+ }
69
+ },
70
+ "dark": {
71
+ "surface": "#242a31",
72
+ "plane": "#1a1f24",
73
+ "inkPrimary": "#f5f5f5",
74
+ "inkSecondary": "#d9d9d6",
75
+ "inkMuted": "#8f999f",
76
+ "grid": "#2b323a",
77
+ "baseline": "#343c45",
78
+ "categorical": [
79
+ "#009b93",
80
+ "#c48300",
81
+ "#987ce9",
82
+ "#00a0d1",
83
+ "#e46268",
84
+ "#6ea532",
85
+ "#df5aa4",
86
+ "#6e82ff"
87
+ ],
88
+ "sequential": [
89
+ "#063a57",
90
+ "#0a5878",
91
+ "#0f7ca0",
92
+ "#1aa0c8",
93
+ "#3fbee0",
94
+ "#7dd6ee",
95
+ "#c9eef8"
96
+ ],
97
+ "diverging": {
98
+ "neg": [
99
+ "#4fa8d9",
100
+ "#7cc4e6",
101
+ "#b9e1f2"
102
+ ],
103
+ "mid": "#7b848a",
104
+ "pos": [
105
+ "#ffb877",
106
+ "#ff9645",
107
+ "#e86a25"
108
+ ]
109
+ },
110
+ "status": {
111
+ "good": "#35cc82",
112
+ "warning": "#f0b24e",
113
+ "serious": "#ff7a46",
114
+ "critical": "#ff4c38"
115
+ }
116
+ },
117
+ "marks": {
118
+ "barRadius": 5,
119
+ "barMax": 20,
120
+ "gap": 2,
121
+ "lineWidth": 2,
122
+ "markerSize": 9,
123
+ "areaAlpha": 0.13,
124
+ "glow": 0.45
125
+ },
126
+ "chrome": {
127
+ "style": "glass",
128
+ "tag": "#00b2a9"
129
+ },
130
+ "type": {
131
+ "family": "Montserrat",
132
+ "axisSize": 11
133
+ },
134
+ "motion": {
135
+ "style": "rise",
136
+ "duration": 650,
137
+ "easing": "cubic-bezier(0.16,1,0.3,1)",
138
+ "stagger": 70,
139
+ "breathe": true
140
+ }
141
+ },
142
+ "tokensCss": "/* ============================================================\n Sonance DATA-VIZ TOKENS — \"Teal Ignition\"\n Designed in Bloom Studio · 2026-07-14\n Drop next to globals.css; dark mode keys off [data-theme=\"dark\"]\n (same convention as the Sonance brand theme).\n ============================================================ */\n\n:root {\n --viz-surface: #ffffff;\n --viz-plane: #f8f9fa;\n --viz-ink: #333f48;\n --viz-ink-secondary: #515c64;\n --viz-ink-muted: #8f999f;\n --viz-grid: #eceeef;\n --viz-baseline: #e2e5e6;\n --viz-cat-1: #009b93;\n --viz-cat-2: #ed9e2c;\n --viz-cat-3: #8b5cf6;\n --viz-cat-4: #00a3e1;\n --viz-cat-5: #ff5a5f;\n --viz-cat-6: #8bc34a;\n --viz-cat-7: #e0449c;\n --viz-cat-8: #5468ff;\n --viz-seq-100: #e4f4fc;\n --viz-seq-200: #bee6f7;\n --viz-seq-300: #8fd4f0;\n --viz-seq-400: #57bee6;\n --viz-seq-500: #1fa3d8;\n --viz-seq-600: #0884b9;\n --viz-seq-700: #056090;\n --viz-div-neg-1: #0b72b9;\n --viz-div-neg-2: #5fa9d9;\n --viz-div-neg-3: #afd6ec;\n --viz-div-mid: #9aa3a8;\n --viz-div-pos-1: #ffd59e;\n --viz-div-pos-2: #ffa84d;\n --viz-div-pos-3: #e8752a;\n --viz-status-good: #2bb673;\n --viz-status-warning: #e8a23c;\n --viz-status-serious: #f2622e;\n --viz-status-critical: #e1341e;\n\n /* marks */\n --viz-bar-radius: 5px;\n --viz-bar-max: 20px;\n --viz-gap: 2px;\n --viz-line-width: 2px;\n --viz-marker-size: 9px;\n --viz-area-alpha: 0.13;\n\n /* chrome */\n --viz-chrome: glass;\n --viz-tag: #00b2a9;\n --viz-glow: 0.45;\n\n /* motion */\n --viz-duration: 650ms;\n --viz-easing: cubic-bezier(0.16,1,0.3,1);\n --viz-stagger: 70ms;\n}\n\n[data-theme=\"dark\"] {\n --viz-surface: #242a31;\n --viz-plane: #1a1f24;\n --viz-ink: #f5f5f5;\n --viz-ink-secondary: #d9d9d6;\n --viz-ink-muted: #8f999f;\n --viz-grid: #2b323a;\n --viz-baseline: #343c45;\n --viz-cat-1: #009b93;\n --viz-cat-2: #c48300;\n --viz-cat-3: #987ce9;\n --viz-cat-4: #00a0d1;\n --viz-cat-5: #e46268;\n --viz-cat-6: #6ea532;\n --viz-cat-7: #df5aa4;\n --viz-cat-8: #6e82ff;\n --viz-seq-100: #063a57;\n --viz-seq-200: #0a5878;\n --viz-seq-300: #0f7ca0;\n --viz-seq-400: #1aa0c8;\n --viz-seq-500: #3fbee0;\n --viz-seq-600: #7dd6ee;\n --viz-seq-700: #c9eef8;\n --viz-div-neg-1: #4fa8d9;\n --viz-div-neg-2: #7cc4e6;\n --viz-div-neg-3: #b9e1f2;\n --viz-div-mid: #7b848a;\n --viz-div-pos-1: #ffb877;\n --viz-div-pos-2: #ff9645;\n --viz-div-pos-3: #e86a25;\n --viz-status-good: #35cc82;\n --viz-status-warning: #f0b24e;\n --viz-status-serious: #ff7a46;\n --viz-status-critical: #ff4c38;\n}\n",
143
+ "recharts": "// Sonance data-viz theme — \"Teal Ignition\"\n// Designed in Bloom Studio. Pairs with components/ui/chart.tsx (ChartContainer).\n// Colors resolve through the --viz-* CSS variables (dataviz-tokens.css), so\n// light/dark follow the app's data-theme automatically.\nimport { type ChartConfig } from \"@/components/ui/chart\";\n\n/** Fixed categorical order — assign series to slots in order, never cycle. */\nexport const VIZ_SERIES = [\"var(--viz-cat-1)\", \"var(--viz-cat-2)\", \"var(--viz-cat-3)\", \"var(--viz-cat-4)\", \"var(--viz-cat-5)\", \"var(--viz-cat-6)\", \"var(--viz-cat-7)\", \"var(--viz-cat-8)\"] as const;\n\nexport const VIZ_SEQUENTIAL = [\"var(--viz-seq-100)\", \"var(--viz-seq-200)\", \"var(--viz-seq-300)\", \"var(--viz-seq-400)\", \"var(--viz-seq-500)\", \"var(--viz-seq-600)\", \"var(--viz-seq-700)\"] as const;\n\nexport const VIZ_STATUS = {\n good: \"var(--viz-status-good)\",\n warning: \"var(--viz-status-warning)\",\n serious: \"var(--viz-status-serious)\",\n critical: \"var(--viz-status-critical)\",\n} as const;\n\n/** Build a ChartConfig assigning each series key its fixed slot color. */\nexport function vizConfig(keys: string[], labels?: Record<string, string>): ChartConfig {\n const cfg: ChartConfig = {};\n keys.forEach((k, i) => {\n cfg[k] = { label: labels?.[k] ?? k, color: VIZ_SERIES[i % VIZ_SERIES.length] };\n });\n return cfg;\n}\n\n/** Shared cartesian defaults matching the specimen sheet. */\nexport const VIZ_CARTESIAN = {\n grid: { stroke: \"var(--viz-grid)\", strokeWidth: 1, vertical: false },\n axisTick: { fill: \"var(--viz-ink-muted)\", fontSize: 11 },\n axisLine: { stroke: \"var(--viz-baseline)\", strokeWidth: 1 },\n bar: { radius: [5, 5, 0, 0] as [number, number, number, number], maxBarSize: 20 },\n line: { strokeWidth: 2, dot: false },\n activeDot: { r: 5, stroke: \"var(--viz-surface)\", strokeWidth: 2 },\n} as const;\n",
144
+ "validation": {
145
+ "light": {
146
+ "ok": true,
147
+ "checks": [
148
+ {
149
+ "check": "Lightness band",
150
+ "state": "true",
151
+ "detail": "all 8 inside L 0.43–0.77"
152
+ },
153
+ {
154
+ "check": "Chroma floor",
155
+ "state": "true",
156
+ "detail": "all 8 >= 0.1"
157
+ },
158
+ {
159
+ "check": "CVD separation",
160
+ "state": "pass",
161
+ "detail": "worst adjacent #8bc34a↔#ff5a5f ΔE 13.2 (deutan) · tritan 39.2 · normal 69.3"
162
+ },
163
+ {
164
+ "check": "Contrast vs surface",
165
+ "state": "relief",
166
+ "detail": "below 3:1 — relief required (visible labels or table view): [[\"#ed9e2c\",2.2],[\"#00a3e1\",2.87],[\"#8bc34a\",2.1]]"
167
+ }
168
+ ]
169
+ },
170
+ "dark": {
171
+ "ok": true,
172
+ "checks": [
173
+ {
174
+ "check": "Lightness band",
175
+ "state": "true",
176
+ "detail": "all 8 inside L 0.48–0.67"
177
+ },
178
+ {
179
+ "check": "Chroma floor",
180
+ "state": "true",
181
+ "detail": "all 8 >= 0.1"
182
+ },
183
+ {
184
+ "check": "CVD separation",
185
+ "state": "pass",
186
+ "detail": "worst adjacent #00a0d1↔#987ce9 ΔE 13.3 (deutan) · tritan 36.0 · normal 55.0"
187
+ },
188
+ {
189
+ "check": "Contrast vs surface",
190
+ "state": "pass",
191
+ "detail": "all 8 >= 3:1"
192
+ }
193
+ ]
194
+ }
195
+ }
196
+ }
@@ -10,13 +10,17 @@
10
10
  ============================================ */
11
11
 
12
12
  :root {
13
+ /* Bloom Accent Colors */
14
+ --bloom-emerald: #3fe0a5;
15
+ --bloom-violet: #a78bfa;
16
+
13
17
  /* Core Brand Colors - Official Sonance Palette (from Brand Guidelines PDF) */
14
18
  --sonance-charcoal: #333F48;
15
19
  --sonance-blue: #00A3E1; /* "The Beam" - cyan accent on logo | Pantone 2191C/2191U */
16
20
  --sonance-light-gray: #D9D9D6;
17
21
  --sonance-white: #FFFFFF;
18
22
  --sonance-black: #000000;
19
-
23
+
20
24
  /* Sonance Foundation Colors */
21
25
  --foundation-green: #00B2A9;
22
26
 
@@ -78,6 +82,7 @@
78
82
  --background-secondary: #f8f9fa;
79
83
  --background-tertiary: #f0f2f3;
80
84
  --background-elevated: #FFFFFF;
85
+ --bg-2: #f0f2f3;
81
86
 
82
87
  /* Light Mode - Foreground / Text */
83
88
  --foreground: #333F48;
@@ -90,6 +95,7 @@
90
95
  --border-hover: #b8bfc4;
91
96
  --border-focus: #333F48;
92
97
  --divider: #f0f2f3;
98
+ --line-2: #e2e2e2;
93
99
 
94
100
  /* Light Mode - Interactive Elements */
95
101
  --primary: #333F48;
@@ -233,11 +239,12 @@
233
239
 
234
240
  /* Dark Mode */
235
241
  [data-theme="dark"] {
236
- /* Backgrounds */
237
- --background: #1a1f24;
238
- --background-secondary: #242a31;
239
- --background-tertiary: #2d343c;
240
- --background-elevated: #333F48;
242
+ /* Backgrounds - Bloom Aesthetic */
243
+ --background: #07080c;
244
+ --background-secondary: #0f121b;
245
+ --background-tertiary: #0f121b;
246
+ --background-elevated: #0f121b;
247
+ --bg-2: #0b0d14;
241
248
 
242
249
  /* Foreground / Text */
243
250
  --foreground: #FFFFFF;
@@ -246,10 +253,11 @@
246
253
  --foreground-subtle: #6b7780;
247
254
 
248
255
  /* Borders & Dividers */
249
- --border: #3a444c;
250
- --border-hover: #424c54;
256
+ --border: #1f2937;
257
+ --border-hover: #374151;
251
258
  --border-focus: #D9D9D6;
252
- --divider: #2d343c;
259
+ --divider: #1f2937;
260
+ --line-2: rgba(255, 255, 255, 0.08);
253
261
 
254
262
  /* Interactive Elements */
255
263
  --primary: #FFFFFF;
@@ -262,13 +270,13 @@
262
270
  --secondary-foreground: #FFFFFF;
263
271
 
264
272
  /* Cards & Surfaces */
265
- --card: #242a31;
266
- --card-hover: #2d343c;
267
- --card-border: #3d474f;
273
+ --card: #0f121b;
274
+ --card-hover: #1a1d2a;
275
+ --card-border: #1f2937;
268
276
 
269
277
  /* Input Fields */
270
- --input: #2d343c;
271
- --input-border: #3d474f;
278
+ --input: #0f121b;
279
+ --input-border: #1f2937;
272
280
  --input-focus: #FFFFFF;
273
281
  --input-placeholder: #7a8389;
274
282
 
@@ -294,13 +302,13 @@
294
302
  --info-light: rgba(66, 165, 245, 0.15);
295
303
 
296
304
  /* Sidebar Colors - Dark Mode */
297
- --sidebar-background: #1a1f24;
305
+ --sidebar-background: #07080c;
298
306
  --sidebar-foreground: #FFFFFF;
299
307
  --sidebar-primary: #FFFFFF;
300
308
  --sidebar-primary-foreground: #333F48;
301
- --sidebar-accent: #242a31;
309
+ --sidebar-accent: #0f121b;
302
310
  --sidebar-accent-foreground: #FFFFFF;
303
- --sidebar-border: #3a444c;
311
+ --sidebar-border: #1f2937;
304
312
  --sidebar-ring: #00A3E1;
305
313
 
306
314
  /* ============================================
@@ -338,18 +346,18 @@
338
346
  /* System preference detection fallback */
339
347
  @media (prefers-color-scheme: dark) {
340
348
  :root:not([data-theme="light"]) {
341
- --background: #1a1f24;
342
- --background-secondary: #242a31;
343
- --background-tertiary: #2d343c;
344
- --background-elevated: #333F48;
349
+ --background: #07080c;
350
+ --background-secondary: #0f121b;
351
+ --background-tertiary: #0f121b;
352
+ --background-elevated: #0f121b;
345
353
  --foreground: #FFFFFF;
346
354
  --foreground-secondary: #D9D9D6;
347
355
  --foreground-muted: #8f999f;
348
356
  --foreground-subtle: #6b7780;
349
- --border: #3a444c;
350
- --border-hover: #424c54;
357
+ --border: #1f2937;
358
+ --border-hover: #374151;
351
359
  --border-focus: #D9D9D6;
352
- --divider: #2d343c;
360
+ --divider: #1f2937;
353
361
  --primary: #FFFFFF;
354
362
  --primary-hover: #D9D9D6;
355
363
  --primary-active: #b8bfc4;
@@ -357,11 +365,11 @@
357
365
  --secondary: transparent;
358
366
  --secondary-hover: #2d343c;
359
367
  --secondary-foreground: #FFFFFF;
360
- --card: #242a31;
361
- --card-hover: #2d343c;
362
- --card-border: #3a444c;
363
- --input: #2d343c;
364
- --input-border: #3a444c;
368
+ --card: #0f121b;
369
+ --card-hover: #1a1d2a;
370
+ --card-border: #1f2937;
371
+ --input: #0f121b;
372
+ --input-border: #1f2937;
365
373
  --input-focus: #FFFFFF;
366
374
  --input-placeholder: #6b7780;
367
375
  --overlay: rgba(0, 0, 0, 0.7);
@@ -432,6 +440,10 @@
432
440
  --color-sidebar-border: var(--sidebar-border);
433
441
  --color-sidebar-ring: var(--sidebar-ring);
434
442
 
443
+ /* Bloom Accent Colors */
444
+ --color-bloom-emerald: var(--bloom-emerald);
445
+ --color-bloom-violet: var(--bloom-violet);
446
+
435
447
  /* Brand Colors */
436
448
  --color-sonance-charcoal: var(--sonance-charcoal);
437
449
  --color-sonance-blue: var(--sonance-blue);
@@ -516,6 +528,391 @@ body {
516
528
  -moz-osx-font-smoothing: grayscale;
517
529
  }
518
530
 
531
+ /* Bloom Dark Mode Ambient Gradients */
532
+ [data-theme="dark"] body {
533
+ background:
534
+ radial-gradient(1200px 700px at 82% -8%, rgba(167, 139, 250, 0.10), transparent 60%),
535
+ radial-gradient(1000px 650px at -6% 108%, rgba(63, 224, 165, 0.09), transparent 55%),
536
+ #07080c;
537
+ }
538
+
539
+ /* ============================================
540
+ BLOOM-STYLE APP SHELL & SIDEBAR
541
+ ============================================ */
542
+
543
+ /* App Shell Layout */
544
+ .app-shell {
545
+ display: grid;
546
+ grid-template-columns: 252px minmax(0, 1fr);
547
+ min-height: 100vh;
548
+ }
549
+
550
+ /* Main Content Area with Bloom's 90% zoom */
551
+ .app-main {
552
+ zoom: 0.9;
553
+ padding-top: 0;
554
+ }
555
+
556
+ /* Remove zoom on smaller screens where it would make text too small */
557
+ @media (max-width: 1000px) {
558
+ .app-main {
559
+ zoom: 1;
560
+ }
561
+ }
562
+
563
+ /* Floating Sidebar Card - Desktop */
564
+ .app-side {
565
+ position: sticky;
566
+ left: 14px;
567
+ width: 252px;
568
+ background: #FFFFFF;
569
+ border: 1px solid var(--border);
570
+ border-radius: 22px;
571
+ overflow: hidden;
572
+ display: flex;
573
+ flex-direction: column;
574
+ box-shadow:
575
+ 0 4px 6px -1px rgba(0, 0, 0, 0.05),
576
+ 0 10px 15px -3px rgba(0, 0, 0, 0.05),
577
+ 0 20px 25px -5px rgba(0, 0, 0, 0.04);
578
+ z-index: 40;
579
+ }
580
+
581
+ [data-theme="dark"] .app-side {
582
+ background: #0f121b;
583
+ border-color: rgba(255, 255, 255, 0.08);
584
+ box-shadow:
585
+ 0 4px 6px -1px rgba(0, 0, 0, 0.3),
586
+ 0 10px 15px -3px rgba(0, 0, 0, 0.2);
587
+ }
588
+
589
+ /* Sidebar Navigation Scroll Container */
590
+ .app-side nav {
591
+ overflow-y: auto;
592
+ overflow-x: hidden;
593
+ max-height: calc(100% - 160px); /* Account for header and footer */
594
+ }
595
+
596
+ /* Smooth scrolling for sidebar */
597
+ .app-side nav {
598
+ scroll-behavior: smooth;
599
+ }
600
+
601
+ /* Custom scrollbar for sidebar */
602
+ .app-side nav::-webkit-scrollbar {
603
+ width: 6px;
604
+ }
605
+
606
+ .app-side nav::-webkit-scrollbar-track {
607
+ background: transparent;
608
+ }
609
+
610
+ .app-side nav::-webkit-scrollbar-thumb {
611
+ background: var(--border);
612
+ border-radius: 3px;
613
+ }
614
+
615
+ .app-side nav::-webkit-scrollbar-thumb:hover {
616
+ background: var(--border-hover);
617
+ }
618
+
619
+ /* Navigation Links - Bloom Style */
620
+ .side-link {
621
+ display: flex;
622
+ align-items: center;
623
+ gap: 12px;
624
+ padding: 10px 12px;
625
+ border-radius: 10px;
626
+ font-size: 14px;
627
+ font-weight: 500;
628
+ color: var(--foreground-secondary);
629
+ transition: all 0.15s ease-out;
630
+ text-decoration: none;
631
+ position: relative;
632
+ }
633
+
634
+ .side-link:hover {
635
+ background: #f0f2f3;
636
+ color: var(--foreground);
637
+ }
638
+
639
+ [data-theme="dark"] .side-link:hover {
640
+ background: #0b0d14;
641
+ color: #FFFFFF;
642
+ }
643
+
644
+ /* Active State - Bloom Gradient */
645
+ .side-link.on {
646
+ background: linear-gradient(90deg, rgba(63, 224, 165, 0.16), rgba(167, 139, 250, 0.10));
647
+ color: var(--foreground);
648
+ font-weight: 600;
649
+ }
650
+
651
+ [data-theme="dark"] .side-link.on {
652
+ background: linear-gradient(90deg, rgba(63, 224, 165, 0.16), rgba(167, 139, 250, 0.10));
653
+ color: #FFFFFF;
654
+ }
655
+
656
+ /* Sidebar Footer */
657
+ .side-footer {
658
+ border-top: 1px solid var(--border);
659
+ padding: 16px;
660
+ flex-shrink: 0;
661
+ }
662
+
663
+ [data-theme="dark"] .side-footer {
664
+ border-color: rgba(255, 255, 255, 0.08);
665
+ }
666
+
667
+ /* ============================================
668
+ RESPONSIVE SIDEBAR ENHANCEMENTS
669
+ ============================================ */
670
+
671
+ /* Fluid Logo Scaling */
672
+ .sidebar-logo-container {
673
+ min-height: 80px;
674
+ }
675
+
676
+ .sidebar-logo {
677
+ max-width: clamp(140px, 85%, 200px);
678
+ height: auto !important;
679
+ width: auto !important;
680
+ }
681
+
682
+ /* Nested Navigation Layout */
683
+ .nested-nav-container {
684
+ margin-left: 12px;
685
+ margin-top: 6px;
686
+ padding-left: clamp(20px, 1.25rem, 28px);
687
+ border-left: 1px solid var(--border);
688
+ display: flex;
689
+ flex-direction: column;
690
+ gap: 4px;
691
+ }
692
+
693
+ .nested-nav-item {
694
+ display: flex;
695
+ align-items: center;
696
+ gap: 8px;
697
+ padding: 6px 12px;
698
+ text-sm: 14px;
699
+ transition: color 0.15s ease-out;
700
+ border-radius: 6px;
701
+ min-width: 0; /* Enable text truncation */
702
+ }
703
+
704
+ .nested-nav-item:hover {
705
+ background: rgba(0, 0, 0, 0.03);
706
+ }
707
+
708
+ [data-theme="dark"] .nested-nav-item:hover {
709
+ background: rgba(255, 255, 255, 0.03);
710
+ }
711
+
712
+ /* Theme Toggle Responsive Sizing */
713
+ .theme-toggle-container {
714
+ display: flex;
715
+ align-items: center;
716
+ justify-content: center;
717
+ }
718
+
719
+ .theme-toggle-btn {
720
+ padding: clamp(6px, 0.5rem, 10px);
721
+ border-radius: 8px;
722
+ transition: all 0.15s ease-out;
723
+ display: flex;
724
+ align-items: center;
725
+ justify-content: center;
726
+ min-width: 32px;
727
+ min-height: 32px;
728
+ }
729
+
730
+ .theme-toggle-icon {
731
+ width: clamp(14px, 1rem, 18px);
732
+ height: clamp(14px, 1rem, 18px);
733
+ }
734
+
735
+
736
+ /* Mobile Drawer Backdrop - Hidden on desktop */
737
+ .mobile-drawer-backdrop {
738
+ display: none;
739
+ }
740
+
741
+ @media (max-width: 600px) {
742
+ .mobile-drawer-backdrop {
743
+ display: block;
744
+ position: fixed;
745
+ inset: 0;
746
+ background: rgba(0, 0, 0, 0.5);
747
+ z-index: 49;
748
+ opacity: 0;
749
+ pointer-events: none;
750
+ transition: opacity 0.2s ease-out;
751
+ }
752
+
753
+ .mobile-drawer-backdrop.open {
754
+ opacity: 1;
755
+ pointer-events: auto;
756
+ }
757
+
758
+ [data-theme="dark"] .mobile-drawer-backdrop {
759
+ background: rgba(0, 0, 0, 0.7);
760
+ }
761
+ }
762
+
763
+ /* Hide mobile header and bottom tab bar on desktop */
764
+ .mobile-header,
765
+ .bottom-tab-bar {
766
+ display: none;
767
+ }
768
+
769
+ /* Mobile Drawer - Full sidebar access */
770
+ @media (max-width: 600px) {
771
+ /* Mobile Header */
772
+ .mobile-header {
773
+ display: flex;
774
+ position: fixed;
775
+ top: 0;
776
+ left: 0;
777
+ right: 0;
778
+ height: 64px;
779
+ background: var(--background);
780
+ border-bottom: 1px solid var(--border);
781
+ align-items: center;
782
+ justify-content: center;
783
+ padding: 0 16px;
784
+ z-index: 50;
785
+ }
786
+
787
+ [data-theme="dark"] .mobile-header {
788
+ background: #0f121b;
789
+ border-bottom-color: rgba(255, 255, 255, 0.08);
790
+ }
791
+
792
+ /* Drawer Sidebar - Override desktop floating styles */
793
+ .app-side {
794
+ position: fixed !important;
795
+ top: 0 !important;
796
+ left: 0;
797
+ bottom: 0;
798
+ width: 280px;
799
+ height: 100vh !important;
800
+ border-radius: 0 !important;
801
+ border: none;
802
+ border-right: 1px solid var(--border);
803
+ transform: translateX(-100%);
804
+ transition: transform 0.25s ease-out;
805
+ z-index: 50;
806
+ overflow-y: auto;
807
+ box-shadow: none !important;
808
+ }
809
+
810
+ .app-side.open {
811
+ transform: translateX(0);
812
+ }
813
+
814
+ [data-theme="dark"] .app-side {
815
+ border-right-color: rgba(255, 255, 255, 0.08);
816
+ }
817
+
818
+ /* Drawer navigation scrollable */
819
+ .app-side nav {
820
+ max-height: none !important;
821
+ overflow-y: auto;
822
+ padding-bottom: 24px;
823
+ }
824
+
825
+ /* Logo section visible in drawer */
826
+ .app-side > div:first-child {
827
+ display: flex !important;
828
+ }
829
+
830
+ /* Mobile logo sizing */
831
+ .sidebar-logo {
832
+ max-width: clamp(160px, 90%, 220px);
833
+ }
834
+
835
+ /* Mobile theme toggle - horizontal layout */
836
+ .theme-toggle-container {
837
+ flex-direction: row;
838
+ gap: 2px;
839
+ padding: 2px;
840
+ }
841
+
842
+ .theme-toggle-btn {
843
+ min-width: 40px;
844
+ min-height: 40px;
845
+ padding: 8px;
846
+ }
847
+
848
+ .theme-toggle-icon {
849
+ width: 18px;
850
+ height: 18px;
851
+ }
852
+
853
+ /* Bottom Tab Bar - Essential items only */
854
+ .app-shell {
855
+ grid-template-columns: 1fr;
856
+ padding-top: 64px; /* Account for mobile header */
857
+ }
858
+
859
+ .app-main {
860
+ padding-bottom: 64px; /* Account for tab bar */
861
+ }
862
+
863
+ .bottom-tab-bar {
864
+ display: flex;
865
+ position: fixed;
866
+ bottom: 0;
867
+ left: 0;
868
+ right: 0;
869
+ height: 64px;
870
+ background: var(--background);
871
+ border-top: 1px solid var(--border);
872
+ align-items: center;
873
+ justify-content: space-around;
874
+ padding: 8px;
875
+ z-index: 40;
876
+ box-shadow: 0 -4px 6px -1px rgba(0, 0, 0, 0.05);
877
+ }
878
+
879
+ [data-theme="dark"] .bottom-tab-bar {
880
+ background: #0f121b;
881
+ border-top-color: rgba(255, 255, 255, 0.08);
882
+ box-shadow: 0 -4px 6px -1px rgba(0, 0, 0, 0.3);
883
+ }
884
+
885
+ .tab-bar-item {
886
+ display: flex;
887
+ flex-direction: column;
888
+ align-items: center;
889
+ gap: 4px;
890
+ padding: 8px 12px;
891
+ min-width: 64px;
892
+ color: var(--foreground-muted);
893
+ text-decoration: none;
894
+ transition: color 0.15s ease-out;
895
+ border-radius: 8px;
896
+ }
897
+
898
+ .tab-bar-item:hover,
899
+ .tab-bar-item.active {
900
+ color: var(--foreground);
901
+ background: var(--secondary-hover);
902
+ }
903
+
904
+ .tab-bar-item svg {
905
+ width: 20px;
906
+ height: 20px;
907
+ }
908
+
909
+ .tab-bar-item span {
910
+ font-size: 11px;
911
+ font-weight: 500;
912
+ line-height: 1;
913
+ }
914
+ }
915
+
519
916
  /* Prevent flash of unstyled content */
520
917
  html {
521
918
  color-scheme: light dark;
@@ -525,6 +922,26 @@ html {
525
922
  UTILITY CLASSES
526
923
  ============================================ */
527
924
 
925
+ /* Responsive Content Padding - Adapts to sidebar width */
926
+ .content-responsive {
927
+ padding-left: 2rem;
928
+ padding-right: 2rem;
929
+ }
930
+
931
+ @media (max-width: 860px) {
932
+ .content-responsive {
933
+ padding-left: 1.5rem;
934
+ padding-right: 1.5rem;
935
+ }
936
+ }
937
+
938
+ @media (max-width: 600px) {
939
+ .content-responsive {
940
+ padding-left: 1rem;
941
+ padding-right: 1rem;
942
+ }
943
+ }
944
+
528
945
  /* Color swatch display */
529
946
  .color-swatch {
530
947
  width: 100%;
@@ -596,8 +1013,8 @@ html {
596
1013
  }
597
1014
 
598
1015
  [data-theme="dark"] ::selection {
599
- background: var(--sonance-white);
600
- color: var(--sonance-charcoal);
1016
+ background: rgba(167, 139, 250, 0.35);
1017
+ color: var(--sonance-white);
601
1018
  }
602
1019
 
603
1020
  /* Code block selection - ensure visibility on dark backgrounds */
@@ -49,6 +49,9 @@
49
49
  "/logos/my-sonance/My.Sonance_Logo_2C_Reverse_RGB.png",
50
50
  "/logos/my-sonance/My.Sonance_Logo_Black_RGB.png",
51
51
  "/logos/my-sonance/My.Sonance_Logo_Reverse_RGB.png",
52
+ "/logos/sonance/Sonance_Beam_Fade_RGB.png",
53
+ "/logos/sonance/Sonance_Beam_RGB.png",
54
+ "/logos/sonance/Sonance_Beam_RGB.svg",
52
55
  "/logos/sonance/Sonance_Logo_2C_Dark_RGB.png",
53
56
  "/logos/sonance/Sonance_Logo_2C_Light_RGB.png",
54
57
  "/logos/sonance/Sonance_Logo_2C_Reverse_RGB.png",
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg id="LockUp_2C_Dark" data-name="LockUp 2C Dark" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 792 612">
3
+ <defs>
4
+ <style>
5
+ .cls-1 {
6
+ fill: #00a8e4;
7
+ stroke-width: 0px;
8
+ }
9
+ </style>
10
+ </defs>
11
+ <path class="cls-1" d="M583.26,450.92h-68.73l-116.01-252.86-116.65,252.86h-66.2L371.09,118.1h56.74l155.43,332.81Z"/>
12
+ <path class="cls-1" d="M583.26,450.92h-68.73l-116.01-252.86-116.65,252.86h-66.2L371.09,118.1h56.74l155.43,332.81Z"/>
13
+ </svg>
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg id="LockUp_2C_Dark" data-name="LockUp 2C Dark" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 792 612">
3
+ <defs>
4
+ <style>
5
+ .cls-1 {
6
+ fill: #00a8e4;
7
+ stroke-width: 0px;
8
+ }
9
+ </style>
10
+ </defs>
11
+ <path class="cls-1" d="M583.26,450.92h-68.73l-116.01-252.86-116.65,252.86h-66.2L371.09,118.1h56.74l155.43,332.81Z"/>
12
+ <path class="cls-1" d="M583.26,450.92h-68.73l-116.01-252.86-116.65,252.86h-66.2L371.09,118.1h56.74l155.43,332.81Z"/>
13
+ </svg>
@@ -0,0 +1,5 @@
1
+ {
2
+ "schema": "bloom-motion-registry/v1",
3
+ "updatedAt": "2026-07-13T00:00:00.000Z",
4
+ "assets": []
5
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "schema": "bloom-motion/v1",
3
+ "id": "sonance-logo-amber",
4
+ "name": "Sonance logo Amber",
5
+ "kind": "logo",
6
+ "brand": "sonance",
7
+ "file": "/motion/sonance-logo-amber.mp4",
8
+ "poster": "/motion/sonance-logo-amber-poster.png",
9
+ "story": {
10
+ "title": "Sonance logo Amber",
11
+ "description": "The mark hovers unlit at first, a dark umber field with no edges to hold onto. Then embers rise—slow, warm, unhurried—drifting upward like sparks remembering fire. Bokeh depth folds the background into soft distance, while heat shimmer trembles along the borders, alive but never intruding on the calm dark center. No letters form, no symbol resolves. Sonance appears instead as atmosphere: a mark you feel arriving before you see it, composed entirely of warmth, patience, and rising light."
12
+ },
13
+ "duration": null,
14
+ "publishedAt": "2026-07-14T01:30:30.104Z",
15
+ "source": {
16
+ "app": "bloom",
17
+ "assetId": "ast_logo1de502f0"
18
+ }
19
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "schema": "bloom-sections/v1",
3
+ "updatedAt": "2026-07-14T00:00:00.000Z",
4
+ "sections": []
5
+ }
@@ -4,7 +4,7 @@
4
4
  ========================================================================== */
5
5
 
6
6
  :root {
7
- --zoom--scale: 0.93;
7
+ --zoom--scale: 0.9;
8
8
  /* Sonance Logo Sizing */
9
9
  --sonance-logo-scale: 0.94;
10
10
  --sonance-logo-width: auto;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Bloom → Sonance Brand studio assets
3
+ *
4
+ * Serves design languages and motion assets published from the Bloom studio.
5
+ * Directory-derived: each published item is its own JSON file, so there is no
6
+ * shared index to overwrite. These tools read every item file across the
7
+ * built-in Data Viz / Motion sections AND any Bloom-created custom section,
8
+ * so a design language is reachable by id no matter where it's filed.
9
+ *
10
+ * Wired into index.ts with three lines:
11
+ * import { BLOOM_TOOLS, handleBloomTool } from "./bloom-assets.js";
12
+ * ...BLOOM_TOOLS, (inside the ListTools tools array)
13
+ * const bloom = await handleBloomTool(name, args); if (bloom) return bloom;
14
+ */
15
+ export declare const BLOOM_TOOLS: ({
16
+ name: string;
17
+ description: string;
18
+ inputSchema: {
19
+ type: string;
20
+ properties: {
21
+ theme_id?: undefined;
22
+ include?: undefined;
23
+ asset_id?: undefined;
24
+ };
25
+ required: never[];
26
+ };
27
+ } | {
28
+ name: string;
29
+ description: string;
30
+ inputSchema: {
31
+ type: string;
32
+ properties: {
33
+ theme_id: {
34
+ type: string;
35
+ description: string;
36
+ };
37
+ include: {
38
+ type: string;
39
+ enum: string[];
40
+ description: string;
41
+ };
42
+ asset_id?: undefined;
43
+ };
44
+ required: string[];
45
+ };
46
+ } | {
47
+ name: string;
48
+ description: string;
49
+ inputSchema: {
50
+ type: string;
51
+ properties: {
52
+ asset_id: {
53
+ type: string;
54
+ description: string;
55
+ };
56
+ theme_id?: undefined;
57
+ include?: undefined;
58
+ };
59
+ required: string[];
60
+ };
61
+ })[];
62
+ export declare function handleBloomTool(name: string, args: unknown): Promise<{
63
+ content: {
64
+ type: "text";
65
+ text: string;
66
+ }[];
67
+ isError?: boolean;
68
+ } | null>;
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Bloom → Sonance Brand studio assets
3
+ *
4
+ * Serves design languages and motion assets published from the Bloom studio.
5
+ * Directory-derived: each published item is its own JSON file, so there is no
6
+ * shared index to overwrite. These tools read every item file across the
7
+ * built-in Data Viz / Motion sections AND any Bloom-created custom section,
8
+ * so a design language is reachable by id no matter where it's filed.
9
+ *
10
+ * Wired into index.ts with three lines:
11
+ * import { BLOOM_TOOLS, handleBloomTool } from "./bloom-assets.js";
12
+ * ...BLOOM_TOOLS, (inside the ListTools tools array)
13
+ * const bloom = await handleBloomTool(name, args); if (bloom) return bloom;
14
+ */
15
+ import * as fs from "fs";
16
+ import * as path from "path";
17
+ import { fileURLToPath } from "url";
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ const __dirname = path.dirname(__filename);
20
+ const BUNDLED_ASSETS = path.join(__dirname, "assets");
21
+ const IS_BUNDLED = fs.existsSync(BUNDLED_ASSETS);
22
+ const DEV_PROJECT_ROOT = path.resolve(__dirname, "../..");
23
+ const SITE_BASE_URL = "https://brand.sonance.com";
24
+ const dataRoot = () => (IS_BUNDLED ? BUNDLED_ASSETS : path.join(DEV_PROJECT_ROOT, "src/data"));
25
+ function readJson(file) {
26
+ try {
27
+ return JSON.parse(fs.readFileSync(file, "utf-8"));
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ }
33
+ // Every item file in a directory (skip the legacy index + section-meta files).
34
+ function itemsInDir(dir) {
35
+ try {
36
+ return fs
37
+ .readdirSync(dir)
38
+ .filter((f) => f.endsWith(".json") && f !== "index.json" && !f.startsWith("_"))
39
+ .map((f) => readJson(path.join(dir, f)))
40
+ .filter(Boolean);
41
+ }
42
+ catch {
43
+ return [];
44
+ }
45
+ }
46
+ function sections() {
47
+ return readJson(path.join(dataRoot(), "sections.json"))?.sections || [];
48
+ }
49
+ // Built-in dir for a kind + every custom section of that kind.
50
+ function sourceDirs(kind) {
51
+ const out = [{ dir: path.join(dataRoot(), kind), section: kind === "dataviz" ? "Data Viz" : "Motion" }];
52
+ for (const s of sections())
53
+ if (s.kind === kind)
54
+ out.push({ dir: path.join(dataRoot(), "sections", s.slug), section: s.name });
55
+ return out;
56
+ }
57
+ function text(t) {
58
+ return { content: [{ type: "text", text: t }] };
59
+ }
60
+ function errorText(t) {
61
+ return { content: [{ type: "text", text: t }], isError: true };
62
+ }
63
+ const DATAVIZ_USAGE = `## Using a Bloom data-viz language
64
+
65
+ 1. **Tokens first.** Import the theme's tokens CSS (all variables are prefixed \`--viz-*\`,
66
+ with a \`[data-theme="dark"]\` block). Never hardcode the hex values in app code.
67
+ 2. **Categorical order is fixed.** Series take colors in the exact order of
68
+ \`categorical[0..7]\` — slot 0 is the accent/primary series. Do not re-sort by preference;
69
+ the ordering is CVD-verified (adjacent-color deltas are checked for deuteranopia/protanopia).
70
+ 3. **Recharts.** The \`recharts\` export is a ready ChartConfig-style module for the Sonance
71
+ \`@/components/ui/chart\` wrapper (ChartContainer). Use \`vizConfig\`/\`VIZ_CARTESIAN\` from it.
72
+ 4. **Sequential / diverging / status** ramps are included for heatmaps, deltas, and states.
73
+ 5. **Marks** (bar radius, line width, area alpha, glow) and **motion** (duration, easing,
74
+ stagger) are part of the language — apply them, don't restyle per chart.`;
75
+ export const BLOOM_TOOLS = [
76
+ {
77
+ name: "list_dataviz_themes",
78
+ description: "Lists the data-viz design languages published from the Bloom studio (bar/line/area/donut/scatter/heatmap etc. styling: categorical palettes, sequential/diverging ramps, marks, motion) across all brand sections. Use get_dataviz_theme for the full payload.",
79
+ inputSchema: { type: "object", properties: {}, required: [] },
80
+ },
81
+ {
82
+ name: "get_dataviz_theme",
83
+ description: "Returns a published Bloom data-viz language: the full bloom-viz-theme/v1 JSON (light+dark palettes, marks, motion, chrome), its CSS custom-property tokens, and a Recharts config for the Sonance chart component. Use list_dataviz_themes to see available ids.",
84
+ inputSchema: {
85
+ type: "object",
86
+ properties: {
87
+ theme_id: { type: "string", description: "Registry id of the theme (e.g. 'teal-ignition')." },
88
+ include: {
89
+ type: "string",
90
+ enum: ["all", "theme", "tokens", "recharts"],
91
+ description: "Which payload to return. Defaults to 'all'.",
92
+ },
93
+ },
94
+ required: ["theme_id"],
95
+ },
96
+ },
97
+ {
98
+ name: "list_motion_assets",
99
+ description: "Lists motion/brand assets published from the Bloom studio (animated logo treatments, generative scenes, living data murals) across all brand sections, with their public URLs.",
100
+ inputSchema: { type: "object", properties: {}, required: [] },
101
+ },
102
+ {
103
+ name: "get_motion_asset",
104
+ description: "Returns a published Bloom motion asset's metadata, story, and public URLs (video + poster) for embedding. Use list_motion_assets to see available ids.",
105
+ inputSchema: {
106
+ type: "object",
107
+ properties: { asset_id: { type: "string", description: "Registry id of the motion asset." } },
108
+ required: ["asset_id"],
109
+ },
110
+ },
111
+ ];
112
+ export async function handleBloomTool(name, args) {
113
+ switch (name) {
114
+ case "list_dataviz_themes": {
115
+ const rows = [];
116
+ for (const { dir, section } of sourceDirs("dataviz"))
117
+ for (const t of itemsInDir(dir))
118
+ rows.push({
119
+ id: t.id,
120
+ label: t.label,
121
+ brand: t.brand,
122
+ section,
123
+ scope: t.scope?.kind === "component" ? `single component (${t.scope.form})` : "full library",
124
+ publishedAt: t.publishedAt,
125
+ });
126
+ if (!rows.length)
127
+ return text("No data-viz languages have been published yet. They arrive via Bloom publish PRs.");
128
+ return text(`# Bloom data-viz languages (${rows.length})\n\n` +
129
+ JSON.stringify(rows, null, 2) +
130
+ `\n\nCall get_dataviz_theme with a theme_id for tokens + palettes + Recharts config.`);
131
+ }
132
+ case "get_dataviz_theme": {
133
+ const { theme_id, include = "all" } = args || {};
134
+ if (!theme_id)
135
+ return errorText("theme_id is required — call list_dataviz_themes for ids.");
136
+ let payload = null;
137
+ const known = [];
138
+ for (const { dir } of sourceDirs("dataviz")) {
139
+ for (const t of itemsInDir(dir)) {
140
+ known.push(t.id);
141
+ if (t.id === theme_id)
142
+ payload = t;
143
+ }
144
+ if (payload)
145
+ break;
146
+ }
147
+ if (!payload)
148
+ return errorText(`Unknown theme '${theme_id}'. Available: ${known.join(", ") || "(none)"}`);
149
+ const parts = [`# ${payload.label} — Bloom data-viz language (${payload.brand})`];
150
+ if (payload.story?.description)
151
+ parts.push(payload.story.description);
152
+ if (include === "all" || include === "theme")
153
+ parts.push(`## Theme JSON (bloom-viz-theme/v1)\n\n\`\`\`json\n${JSON.stringify(payload.theme, null, 2)}\n\`\`\``);
154
+ if (include === "all" || include === "tokens")
155
+ parts.push(`## Tokens CSS\n\n\`\`\`css\n${payload.tokensCss || "/* not included */"}\n\`\`\``);
156
+ if (include === "all" || include === "recharts")
157
+ parts.push(`## Recharts config (for @/components/ui/chart)\n\n\`\`\`ts\n${payload.recharts || "// not included"}\n\`\`\``);
158
+ parts.push(DATAVIZ_USAGE);
159
+ parts.push(`Live specimens: ${SITE_BASE_URL}/dataviz`);
160
+ return text(parts.join("\n\n"));
161
+ }
162
+ case "list_motion_assets": {
163
+ const rows = [];
164
+ for (const { dir, section } of sourceDirs("motion"))
165
+ for (const a of itemsInDir(dir))
166
+ rows.push({
167
+ id: a.id,
168
+ name: a.name,
169
+ kind: a.kind,
170
+ brand: a.brand,
171
+ section,
172
+ url: `${SITE_BASE_URL}${a.file}`,
173
+ poster: a.poster ? `${SITE_BASE_URL}${a.poster}` : null,
174
+ publishedAt: a.publishedAt,
175
+ });
176
+ if (!rows.length)
177
+ return text("No motion assets have been published yet. They arrive via Bloom publish PRs.");
178
+ return text(`# Bloom motion assets (${rows.length})\n\n` + JSON.stringify(rows, null, 2));
179
+ }
180
+ case "get_motion_asset": {
181
+ const { asset_id } = args || {};
182
+ if (!asset_id)
183
+ return errorText("asset_id is required — call list_motion_assets for ids.");
184
+ let a = null;
185
+ for (const { dir } of sourceDirs("motion")) {
186
+ a = itemsInDir(dir).find((x) => x.id === asset_id);
187
+ if (a)
188
+ break;
189
+ }
190
+ if (!a)
191
+ return errorText(`Unknown motion asset '${asset_id}'.`);
192
+ const lines = [
193
+ `# ${a.name} (${a.kind})`,
194
+ a.story?.description || "",
195
+ "",
196
+ `- Video: ${SITE_BASE_URL}${a.file}`,
197
+ a.poster ? `- Poster: ${SITE_BASE_URL}${a.poster}` : "",
198
+ `- Brand: ${a.brand} · Published ${a.publishedAt}`,
199
+ "",
200
+ `Embed:\n\`\`\`html\n<video src="${SITE_BASE_URL}${a.file}"${a.poster ? ` poster="${SITE_BASE_URL}${a.poster}"` : ""} autoplay loop muted playsinline></video>\n\`\`\``,
201
+ ].filter(Boolean);
202
+ return text(lines.join("\n"));
203
+ }
204
+ default:
205
+ return null;
206
+ }
207
+ }
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import * as path from "path";
7
7
  import * as os from "os";
8
8
  import { fileURLToPath } from "url";
9
9
  import { execSync } from "child_process";
10
+ import { BLOOM_TOOLS, handleBloomTool } from "./bloom-assets.js";
10
11
  // sharp is dynamically imported — optional for .mcpb builds where native modules aren't bundled
11
12
  import { formatCodeBlock, } from "./utils/response-formatter.js";
12
13
  import { RESPONSIVE_BREAKPOINTS, SECTION_PATTERNS, PAGE_COMPOSITIONS, DESIGN_REFERENCE_URLS, COMPOSITION_PRINCIPLES, ANIMATION_SYSTEM, DOCUMENT_PATTERNS, detectLayoutType, detectOutputType, } from "./layout-references.js";
@@ -1501,15 +1502,15 @@ function getImageMimeType(filePath) {
1501
1502
  * Dark = logo for dark backgrounds (typically lighter/reversed logo)
1502
1503
  */
1503
1504
  const LOGO_MAP = {
1504
- // Stacked lockup — better proportions at typical header/hero sizes (h-8 to h-16)
1505
+ // Sonance standalone logo — the default brand mark (no tri-brand lockup)
1505
1506
  default: {
1506
- light: "/logos/sonance-james-iport/Sonance_James_IPORT_LockupStacked_Dark.png",
1507
- dark: "/logos/sonance-james-iport/Sonance_James_IPORT_LockupStacked_Light.png",
1507
+ light: "/logos/sonance/Sonance_Logo_2C_Dark_RGB.png",
1508
+ dark: "/logos/sonance/Sonance_Logo_2C_Light_RGB.png",
1508
1509
  },
1509
1510
  // Horizontal lockup — for navbars/footers where a wide, thin logo is intentional
1510
1511
  "default-horizontal": {
1511
- light: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png",
1512
- dark: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png",
1512
+ light: "/logos/sonance/Sonance_Logo_2C_Dark_RGB.png",
1513
+ dark: "/logos/sonance/Sonance_Logo_2C_Light_RGB.png",
1513
1514
  },
1514
1515
  sonance: {
1515
1516
  light: "/logos/sonance/Sonance_Logo_2C_Dark_RGB.png",
@@ -2804,6 +2805,8 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
2804
2805
  server.setRequestHandler(ListToolsRequestSchema, async () => {
2805
2806
  return {
2806
2807
  tools: [
2808
+ // Bloom studio assets (dataviz languages + motion) — data-driven, see bloom-assets.ts
2809
+ ...BLOOM_TOOLS,
2807
2810
  {
2808
2811
  name: "get_brand_guidelines",
2809
2812
  description: "Returns the complete Sonance Brand Guidelines including colors, typography rules, logo usage, and design principles for Sonance, IPORT, and Blaze Audio brands.",
@@ -2946,7 +2949,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
2946
2949
  logo_preference: {
2947
2950
  type: "string",
2948
2951
  enum: ["default", "sonance", "iport", "blaze", "james"],
2949
- description: "Which logo to use. 'default' = Sonance+James+IPORT lockup. Or specify 'sonance', 'iport', 'blaze', or 'james' for individual brand logos. Defaults to 'default' if not specified.",
2952
+ description: "Which logo to use. 'default' = standalone Sonance logo. Or specify 'sonance', 'iport', 'blaze', or 'james' for individual brand logos. Defaults to 'default' if not specified.",
2950
2953
  },
2951
2954
  output_type: {
2952
2955
  type: "string",
@@ -3039,7 +3042,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
3039
3042
  },
3040
3043
  {
3041
3044
  name: "rebrand_document",
3042
- description: "Returns universal rebranding instructions for converting ANY existing document to a specific brand (Sonance, IPORT, or Blaze) with mandatory content preservation workflow, non-Latin character handling, page break management, and AUTO-EMBEDDED logo variants. Provides color replacements, font substitutions, and logo placement rules. Default: Sonance+James+IPORT lockup for Sonance brand.",
3045
+ description: "Returns universal rebranding instructions for converting ANY existing document to a specific brand (Sonance, IPORT, or Blaze) with mandatory content preservation workflow, non-Latin character handling, page break management, and AUTO-EMBEDDED logo variants. Provides color replacements, font substitutions, and logo placement rules. Default: standalone Sonance logo for Sonance brand.",
3043
3046
  inputSchema: {
3044
3047
  type: "object",
3045
3048
  properties: {
@@ -3209,6 +3212,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
3209
3212
  });
3210
3213
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
3211
3214
  const { name, arguments: args } = request.params;
3215
+ // Bloom studio asset tools are handled in their own module (bloom-assets.ts)
3216
+ const bloomResult = await handleBloomTool(name, args);
3217
+ if (bloomResult)
3218
+ return bloomResult;
3212
3219
  switch (name) {
3213
3220
  case "get_brand_guidelines": {
3214
3221
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonance-brand-mcp",
3
- "version": "1.4.3",
3
+ "version": "1.4.7",
4
4
  "description": "MCP Server for Sonance Brand Guidelines and Component Library - gives Claude instant access to brand colors, typography, and UI components.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",