@opendirectory.dev/skills 0.1.58 → 0.1.60

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,487 @@
1
+ # Chart Library — graphic-chart (ECharts v6)
2
+
3
+ 8 chart types with full Apache ECharts v6 config specs. Read this before generating HTML in Step 3.
4
+
5
+ CDN:
6
+ ```html
7
+ <script src="https://cdn.jsdelivr.net/npm/echarts@6.0.0/dist/echarts.min.js"></script>
8
+ ```
9
+
10
+ **No plugins needed.** ECharts v6 has built-in data labels, annotations (markPoint/markLine/markArea), and all chart types. No separate CDN scripts.
11
+
12
+ ---
13
+
14
+ ## Universal Init Pattern (apply to ALL chart types)
15
+
16
+ ```javascript
17
+ window.__chartReady = false;
18
+
19
+ document.fonts.ready.then(() => {
20
+ const chart = echarts.init(document.getElementById('chart'), null, { renderer: 'canvas' });
21
+
22
+ // CRITICAL: register BEFORE setOption — ECharts bug #14101/#17500
23
+ chart.on('finished', () => { window.__chartReady = true; });
24
+ chart.on('rendered', () => {
25
+ clearTimeout(window.__renderDebounce);
26
+ window.__renderDebounce = setTimeout(() => { window.__chartReady = true; }, 100);
27
+ });
28
+
29
+ const option = {
30
+ animation: false, // instant render for Playwright screenshot
31
+ backgroundColor: 'transparent',
32
+ // ... chart-specific config
33
+ };
34
+
35
+ chart.setOption(option); // ALWAYS last
36
+ });
37
+ ```
38
+
39
+ ---
40
+
41
+ ## ECharts Structure vs Chart.js (LLM trap — read this)
42
+
43
+ | Concept | Chart.js | ECharts |
44
+ |---|---|---|
45
+ | Chart structure | `{ type, data: {labels, datasets}, options }` | **Flat:** `{ xAxis, yAxis, series, grid, title, legend }` |
46
+ | Category labels | `data.labels: ['Q1','Q2']` | `xAxis: { type: 'category', data: ['Q1','Q2'] }` |
47
+ | Series data | `datasets: [{ data: [...] }]` | `series: [{ type: 'bar', data: [...] }]` |
48
+ | Highlight bar | `backgroundColor: array` | `data: [{ value: N, itemStyle: { color } }]` per item |
49
+ | Doughnut | `type: 'doughnut'` | `type: 'pie'` + `radius: ['40%','70%']` |
50
+ | Area | `fill: 'origin'` | `areaStyle: {}` on series |
51
+ | Data labels | `chartjs-plugin-datalabels` (external) | `label: { show: true }` built into every series |
52
+
53
+ ---
54
+
55
+ ## 1. bar
56
+
57
+ ```javascript
58
+ {
59
+ animation: false,
60
+ backgroundColor: 'transparent',
61
+ grid: {
62
+ left: '8%', right: '8%', top: 80, bottom: 60,
63
+ containLabel: true,
64
+ },
65
+ xAxis: {
66
+ type: 'category', // REQUIRED — not inferred
67
+ data: ['Q1', 'Q2', 'Q3', 'Q4'],
68
+ axisLine: { lineStyle: { color: 'rgba(255,255,255,0.15)' } },
69
+ axisTick: { show: false },
70
+ axisLabel: {
71
+ color: 'var(--text-muted)',
72
+ fontFamily: 'var(--font-body)',
73
+ fontSize: 14,
74
+ },
75
+ },
76
+ yAxis: {
77
+ type: 'value',
78
+ splitLine: { lineStyle: { color: 'rgba(255,255,255,0.07)' } },
79
+ axisLabel: {
80
+ color: 'var(--text-muted)',
81
+ fontFamily: 'var(--font-body)',
82
+ fontSize: 12,
83
+ formatter: (v) => '$' + v + 'k', // customize per data
84
+ },
85
+ },
86
+ series: [{
87
+ type: 'bar',
88
+ name: 'Revenue',
89
+ barMaxWidth: 72,
90
+ itemStyle: {
91
+ color: palette[0],
92
+ borderRadius: [4, 4, 0, 0], // rounded top corners
93
+ },
94
+ // Highlight one bar — per-item override
95
+ data: [
96
+ { value: 420, itemStyle: { color: palette[1], borderRadius: [4,4,0,0] } },
97
+ { value: 510, itemStyle: { color: palette[1], borderRadius: [4,4,0,0] } },
98
+ { value: 480, itemStyle: { color: palette[1], borderRadius: [4,4,0,0] } },
99
+ { value: 780, itemStyle: { color: palette[0], borderRadius: [4,4,0,0] } }, // highlighted
100
+ ],
101
+ // Built-in data label — no plugin
102
+ label: {
103
+ show: false, // set true to show value on bar
104
+ position: 'top',
105
+ formatter: '{c}',
106
+ color: 'var(--text)',
107
+ fontFamily: 'var(--font-body)',
108
+ fontSize: 13,
109
+ },
110
+ // Highlight annotation — label above bar with callout
111
+ markPoint: {
112
+ symbol: 'pin',
113
+ symbolSize: 0, // hide symbol, show label only
114
+ label: {
115
+ show: true,
116
+ formatter: '$780k',
117
+ backgroundColor: palette[0],
118
+ color: '#000',
119
+ borderRadius: 4,
120
+ padding: [6, 10],
121
+ fontWeight: 'bold',
122
+ fontSize: 13,
123
+ },
124
+ data: [{ coord: ['Q4', 780] }], // [categoryLabel, value]
125
+ },
126
+ }]
127
+ }
128
+ ```
129
+
130
+ **Horizontal bar:** Swap axes — put `type: 'category'` on `yAxis`, `type: 'value'` on `xAxis`.
131
+
132
+ **Multi-series bar:**
133
+ ```javascript
134
+ series: [
135
+ { type: 'bar', name: '2023', data: [...], itemStyle: { color: palette[0], borderRadius: [4,4,0,0] } },
136
+ { type: 'bar', name: '2024', data: [...], itemStyle: { color: palette[1], borderRadius: [4,4,0,0] } },
137
+ ]
138
+ legend: { show: true, bottom: 0, textStyle: { color: 'var(--text)' } }
139
+ ```
140
+
141
+ **Stacked bar:** Add `stack: 'total'` on each series.
142
+
143
+ **Dark vs light grid color:**
144
+ - Dark presets: `splitLine: { lineStyle: { color: 'rgba(255,255,255,0.07)' } }`
145
+ - Light presets: `splitLine: { lineStyle: { color: 'rgba(0,0,0,0.08)' } }`
146
+
147
+ ---
148
+
149
+ ## 2. line
150
+
151
+ ```javascript
152
+ {
153
+ animation: false,
154
+ backgroundColor: 'transparent',
155
+ grid: { left: '8%', right: '8%', top: 80, bottom: 60, containLabel: true },
156
+ xAxis: {
157
+ type: 'category',
158
+ data: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
159
+ boundaryGap: false, // line starts at first point, not center of band
160
+ axisLine: { lineStyle: { color: 'rgba(255,255,255,0.15)' } },
161
+ axisTick: { show: false },
162
+ axisLabel: { color: 'var(--text-muted)', fontSize: 12 },
163
+ },
164
+ yAxis: {
165
+ type: 'value',
166
+ splitLine: { lineStyle: { color: 'rgba(255,255,255,0.07)' } },
167
+ axisLabel: { color: 'var(--text-muted)', fontSize: 12, formatter: '{value}k' },
168
+ },
169
+ series: [{
170
+ type: 'line',
171
+ name: 'ARR ($k)',
172
+ data: [12, 18, 22, 25, 31, 38, 44, 52, 61, 68, 78, 95],
173
+ smooth: true, // natural curve (vs false = straight segments)
174
+ lineStyle: { color: palette[0], width: 2.5 },
175
+ itemStyle: { color: palette[0] },
176
+ symbolSize: 8,
177
+ symbol: 'circle',
178
+ // Highlight specific point (e.g. December)
179
+ markPoint: {
180
+ symbolSize: 0,
181
+ label: {
182
+ show: true,
183
+ formatter: '$95k',
184
+ backgroundColor: palette[0],
185
+ color: '#000',
186
+ borderRadius: 4,
187
+ padding: [6, 10],
188
+ fontWeight: 'bold',
189
+ fontSize: 13,
190
+ },
191
+ data: [{ coord: ['Dec', 95] }],
192
+ },
193
+ }]
194
+ }
195
+ ```
196
+
197
+ ---
198
+
199
+ ## 3. area
200
+
201
+ **No `type: 'area'` in ECharts — it's `type: 'line'` with `areaStyle`.**
202
+
203
+ ```javascript
204
+ series: [{
205
+ type: 'line', // NOT 'area'
206
+ smooth: true,
207
+ lineStyle: { color: palette[0], width: 2 },
208
+ itemStyle: { color: palette[0] },
209
+ areaStyle: { // THIS is what makes it an area chart
210
+ color: {
211
+ type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
212
+ colorStops: [
213
+ { offset: 0, color: paletteAlpha[0] }, // top: semi-transparent
214
+ { offset: 1, color: 'rgba(0,0,0,0)' }, // bottom: fully transparent
215
+ ]
216
+ }
217
+ },
218
+ data: [5, 8, 12, 10, 15, 22, 28, 25, 32, 38, 45, 52],
219
+ symbolSize: 6,
220
+ }]
221
+ ```
222
+
223
+ ---
224
+
225
+ ## 4. pie
226
+
227
+ **CRITICAL sizing:** Pie fills 100% of container. Always use body `padding: 64px 80px` and `.chart-container { max-height: 860px }` — see SKILL.md rule #12.
228
+
229
+ ```javascript
230
+ {
231
+ animation: false,
232
+ backgroundColor: 'transparent',
233
+ // No xAxis/yAxis for pie
234
+ legend: {
235
+ orient: 'vertical',
236
+ right: '5%',
237
+ top: 'center',
238
+ textStyle: {
239
+ color: 'var(--text)',
240
+ fontFamily: 'var(--font-body)',
241
+ fontSize: 20, // 20px = readable at 2× retina output
242
+ },
243
+ itemGap: 20,
244
+ formatter: (name) => {
245
+ // Include % in legend — requires access to data
246
+ return name; // customize with data lookup if needed
247
+ },
248
+ },
249
+ series: [{
250
+ type: 'pie',
251
+ radius: '65%', // size as % of container
252
+ center: ['42%', '50%'], // shift left to give legend room
253
+ data: [
254
+ { value: 45, name: 'Product A', itemStyle: { color: palette[0] } },
255
+ { value: 28, name: 'Product B', itemStyle: { color: palette[1] } },
256
+ { value: 17, name: 'Product C', itemStyle: { color: palette[2] } },
257
+ { value: 10, name: 'Other', itemStyle: { color: palette[3] } },
258
+ ],
259
+ // Built-in labels — no plugin needed
260
+ label: {
261
+ show: true,
262
+ formatter: '{b}\n{d}%', // name + percentage
263
+ color: '#09090B', // dark text on bright palette colors
264
+ fontFamily: 'var(--font-body)',
265
+ fontSize: 18,
266
+ fontWeight: 'bold',
267
+ minMargin: 5,
268
+ },
269
+ labelLine: {
270
+ show: true,
271
+ length: 15,
272
+ length2: 10,
273
+ lineStyle: { color: 'rgba(255,255,255,0.3)' },
274
+ },
275
+ // Hide label on tiny slices (< 12%)
276
+ labelFilter: (params) => params.data.value >= 12,
277
+ itemStyle: { borderColor: 'var(--bg)', borderWidth: 3 },
278
+ emphasis: { scale: true, scaleSize: 6 },
279
+ }]
280
+ }
281
+ ```
282
+
283
+ **No annotation plugin for pie** — ECharts pie doesn't support markPoint/markLine.
284
+
285
+ If > 7 segments, consolidate smallest as "Other".
286
+
287
+ ---
288
+
289
+ ## 5. doughnut
290
+
291
+ Same as pie + `radius: ['40%', '70%']`. No `type: 'doughnut'` in ECharts.
292
+
293
+ ```javascript
294
+ series: [{
295
+ type: 'pie',
296
+ radius: ['40%', '68%'], // [inner, outer] — creates the hole
297
+ center: ['42%', '50%'],
298
+ // All other config same as pie
299
+ }]
300
+ ```
301
+
302
+ **Optional center label:**
303
+ ```javascript
304
+ // Register as a custom ECharts plugin after chart init:
305
+ chart.setOption({
306
+ graphic: [{
307
+ type: 'text',
308
+ left: 'center',
309
+ top: 'center',
310
+ style: {
311
+ text: '73%',
312
+ font: 'bold 32px Inter, sans-serif',
313
+ fill: 'var(--text)',
314
+ textAlign: 'center',
315
+ }
316
+ }]
317
+ });
318
+ ```
319
+
320
+ ---
321
+
322
+ ## 6. scatter
323
+
324
+ ```javascript
325
+ {
326
+ animation: false,
327
+ backgroundColor: 'transparent',
328
+ grid: { left: '8%', right: '8%', top: 80, bottom: 60, containLabel: true },
329
+ xAxis: {
330
+ type: 'value', // value axis — not category
331
+ splitLine: { lineStyle: { color: 'rgba(255,255,255,0.07)' } },
332
+ axisLabel: { color: 'var(--text-muted)', fontSize: 12 },
333
+ },
334
+ yAxis: {
335
+ type: 'value',
336
+ splitLine: { lineStyle: { color: 'rgba(255,255,255,0.07)' } },
337
+ axisLabel: { color: 'var(--text-muted)', fontSize: 12 },
338
+ },
339
+ series: [{
340
+ type: 'scatter',
341
+ symbolSize: 12,
342
+ itemStyle: { color: palette[0], opacity: 0.85 },
343
+ data: [[1.5, 2.3], [2.0, 3.1], [3.5, 1.8], [4.2, 4.5]], // [x, y] pairs
344
+ emphasis: { scale: true },
345
+ }]
346
+ }
347
+ ```
348
+
349
+ Highlight a specific point:
350
+ ```javascript
351
+ data: [
352
+ [1.5, 2.3],
353
+ { value: [4.2, 4.5], itemStyle: { color: palette[0], borderColor: '#fff', borderWidth: 2 } }
354
+ ]
355
+ ```
356
+
357
+ ---
358
+
359
+ ## 7. radar
360
+
361
+ ```javascript
362
+ {
363
+ animation: false,
364
+ backgroundColor: 'transparent',
365
+ radar: {
366
+ shape: 'circle', // or 'polygon'
367
+ indicator: [
368
+ { name: 'Speed', max: 100 },
369
+ { name: 'Accuracy', max: 100 },
370
+ { name: 'Strength', max: 100 },
371
+ { name: 'Stamina', max: 100 },
372
+ { name: 'Agility', max: 100 },
373
+ ],
374
+ radius: '65%',
375
+ center: ['50%', '55%'],
376
+ axisName: { color: 'var(--text)', fontSize: 13 },
377
+ splitLine: { lineStyle: { color: 'rgba(255,255,255,0.12)' } },
378
+ splitArea: { show: false },
379
+ axisLine: { lineStyle: { color: 'rgba(255,255,255,0.15)' } },
380
+ },
381
+ legend: {
382
+ bottom: 0,
383
+ textStyle: { color: 'var(--text)', fontSize: 14 },
384
+ },
385
+ series: [{
386
+ type: 'radar',
387
+ data: [{
388
+ name: 'Team A',
389
+ value: [85, 92, 70, 88, 76],
390
+ lineStyle: { color: palette[0], width: 2 },
391
+ itemStyle: { color: palette[0] },
392
+ areaStyle: { color: paletteAlpha[0] },
393
+ }]
394
+ }]
395
+ }
396
+ ```
397
+
398
+ ---
399
+
400
+ ## 8. treemap
401
+
402
+ ```javascript
403
+ {
404
+ animation: false,
405
+ backgroundColor: 'transparent',
406
+ series: [{
407
+ type: 'treemap',
408
+ width: '100%',
409
+ height: '100%',
410
+ roam: false, // disable pan/zoom (static PNG)
411
+ nodeClick: false,
412
+ breadcrumb: { show: false },
413
+ label: {
414
+ show: true,
415
+ formatter: '{b}\n{c}', // name + value
416
+ fontSize: 14,
417
+ fontFamily: 'var(--font-body)',
418
+ color: '#fff',
419
+ },
420
+ itemStyle: {
421
+ borderColor: 'var(--bg)',
422
+ borderWidth: 2,
423
+ gapWidth: 2,
424
+ },
425
+ levels: [{
426
+ itemStyle: { borderWidth: 3, borderColor: 'var(--bg)' },
427
+ upperLabel: { show: false },
428
+ }],
429
+ data: [
430
+ { name: 'Category A', value: 45, itemStyle: { color: palette[0] } },
431
+ { name: 'Category B', value: 28, itemStyle: { color: palette[1] } },
432
+ { name: 'Category C', value: 17, itemStyle: { color: palette[2] } },
433
+ { name: 'Other', value: 10, itemStyle: { color: palette[3] } },
434
+ ],
435
+ }]
436
+ }
437
+ ```
438
+
439
+ ---
440
+
441
+ ## Highlight Patterns (applies to bar, line, scatter)
442
+
443
+ **Per-item color highlight (most common):**
444
+ ```javascript
445
+ data: [
446
+ 420, 510, 480,
447
+ { value: 780, itemStyle: { color: palette[0], borderRadius: [4,4,0,0] } } // highlighted
448
+ ]
449
+ ```
450
+
451
+ **MarkPoint callout label above bar:**
452
+ ```javascript
453
+ markPoint: {
454
+ symbolSize: 0, // invisible pin, just show the label
455
+ data: [{ coord: ['Q4', 780] }],
456
+ label: {
457
+ show: true,
458
+ formatter: '$780k',
459
+ backgroundColor: palette[0],
460
+ color: '#000',
461
+ fontWeight: 'bold',
462
+ fontSize: 14,
463
+ fontFamily: 'var(--font-body)',
464
+ borderRadius: 4,
465
+ padding: [6, 12],
466
+ },
467
+ }
468
+ ```
469
+
470
+ **MarkArea background highlight:**
471
+ ```javascript
472
+ markArea: {
473
+ silent: true,
474
+ itemStyle: { color: 'rgba(250,204,21,0.1)', borderColor: 'rgba(250,204,21,0.5)', borderWidth: 1 },
475
+ data: [[{ xAxis: 'Q4' }, { xAxis: 'Q4' }]],
476
+ }
477
+ ```
478
+
479
+ **MarkLine (average, target, threshold):**
480
+ ```javascript
481
+ markLine: {
482
+ silent: true,
483
+ lineStyle: { type: 'dashed', color: 'rgba(255,255,255,0.4)', width: 1.5 },
484
+ label: { formatter: 'Avg: {c}', color: 'var(--text-muted)' },
485
+ data: [{ type: 'average' }],
486
+ }
487
+ ```
@@ -0,0 +1,219 @@
1
+ # Style Presets — graphic-chart
2
+
3
+ 5 recommended presets for data visualization charts. Each includes CSS tokens AND a data color palette — both are required.
4
+
5
+ The CSS tokens control: background, typography, grid lines, axis labels.
6
+ The data palette controls: bar/line/point colors and pie segment fills.
7
+
8
+ Read this file before generating HTML in Step 3. Apply ALL tokens from the chosen preset.
9
+
10
+ ---
11
+
12
+ ## 1. clean-slate
13
+
14
+ **Character:** Professional. Enterprise-safe. High-trust. Works for any audience.
15
+
16
+ ```css
17
+ :root {
18
+ --bg: #FFFFFF;
19
+ --bg-elevated: #F8FAFC;
20
+ --text: #0F172A;
21
+ --text-muted: #64748B;
22
+ --accent: #3B82F6;
23
+ --divider: #E2E8F0;
24
+ --font-display: 'Plus Jakarta Sans', sans-serif;
25
+ --font-body: 'Plus Jakarta Sans', sans-serif;
26
+ }
27
+ ```
28
+
29
+ Font CDN:
30
+ ```html
31
+ <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
32
+ ```
33
+
34
+ Data palette (ordered by visual hierarchy):
35
+ ```javascript
36
+ const palette = ['#3B82F6','#10B981','#F59E0B','#EF4444','#8B5CF6','#EC4899','#06B6D4'];
37
+ const paletteAlpha = ['rgba(59,130,246,0.15)','rgba(16,185,129,0.15)','rgba(245,158,11,0.15)','rgba(239,68,68,0.15)','rgba(139,92,246,0.15)','rgba(236,72,153,0.15)','rgba(6,182,212,0.15)'];
38
+ ```
39
+
40
+ Grid line color: `rgba(0,0,0,0.07)`
41
+ Axis tick color: `#64748B`
42
+
43
+ **Best for:** B2B reports, LinkedIn posts, investor decks, any content requiring professional restraint.
44
+
45
+ ---
46
+
47
+ ## 2. midnight-editorial
48
+
49
+ **Character:** Editorial. Premium dark. Authoritative.
50
+
51
+ ```css
52
+ :root {
53
+ --bg: #111111;
54
+ --bg-elevated: #1A1A1A;
55
+ --text: #F5F5F5;
56
+ --text-muted: #9CA3AF;
57
+ --accent: #D8F90A;
58
+ --divider: #2D2D2D;
59
+ --font-display: 'Instrument Serif', serif;
60
+ --font-body: 'Inter', sans-serif;
61
+ }
62
+ ```
63
+
64
+ Font CDN:
65
+ ```html
66
+ <link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
67
+ ```
68
+
69
+ Data palette:
70
+ ```javascript
71
+ const palette = ['#D8F90A','#60A5FA','#34D399','#FB923C','#C084FC','#F472B6','#38BDF8'];
72
+ const paletteAlpha = ['rgba(216,249,10,0.18)','rgba(96,165,250,0.18)','rgba(52,211,153,0.18)','rgba(251,146,60,0.18)','rgba(192,132,252,0.18)','rgba(244,114,182,0.18)','rgba(56,189,248,0.18)'];
73
+ ```
74
+
75
+ Grid line color: `rgba(255,255,255,0.07)`
76
+ Axis tick color: `#9CA3AF`
77
+
78
+ **Best for:** Thought leadership content, editorial publications, agency presentations, premium brand charts.
79
+
80
+ ---
81
+
82
+ ## 3. matt-gray
83
+
84
+ **Character:** Neutral. Sophisticated. Board-room-safe.
85
+
86
+ ```css
87
+ :root {
88
+ --bg: #F5F5F0;
89
+ --bg-elevated: #EBEBЕ6;
90
+ --text: #1C1C1C;
91
+ --text-muted: #737373;
92
+ --accent: #1C1C1C;
93
+ --divider: #D4D4C8;
94
+ --font-display: 'DM Serif Display', serif;
95
+ --font-body: 'DM Sans', sans-serif;
96
+ }
97
+ ```
98
+
99
+ Font CDN:
100
+ ```html
101
+ <link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=DM+Sans:wght@400;500;700&display=swap" rel="stylesheet">
102
+ ```
103
+
104
+ Data palette (rich single-accent approach for this neutral style):
105
+ ```javascript
106
+ const palette = ['#1C1C1C','#525252','#737373','#A3A3A3','#D4D4D4','#404040','#262626'];
107
+ // For multi-series, add a single accent color:
108
+ // const palette = ['#1C1C1C','#D97706','#525252','#92400E','#A3A3A3','#404040','#262626'];
109
+ ```
110
+
111
+ Grid line color: `rgba(0,0,0,0.09)`
112
+ Axis tick color: `#737373`
113
+
114
+ **Best for:** Internal reviews, board materials, consultancy reports, mixed professional audiences.
115
+
116
+ ---
117
+
118
+ ## 4. electric-burst
119
+
120
+ **Character:** Bold. High-contrast. Energy.
121
+
122
+ ```css
123
+ :root {
124
+ --bg: #09090B;
125
+ --bg-elevated: #18181B;
126
+ --text: #FAFAFA;
127
+ --text-muted: #A1A1AA;
128
+ --accent: #FACC15;
129
+ --divider: #27272A;
130
+ --font-display: 'Space Grotesk', sans-serif;
131
+ --font-body: 'DM Sans', sans-serif;
132
+ }
133
+ ```
134
+
135
+ Font CDN:
136
+ ```html
137
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=DM+Sans:wght@400;500&display=swap" rel="stylesheet">
138
+ ```
139
+
140
+ Data palette:
141
+ ```javascript
142
+ const palette = ['#FACC15','#60A5FA','#4ADE80','#F87171','#A78BFA','#FB923C','#34D399'];
143
+ const paletteAlpha = ['rgba(250,204,21,0.18)','rgba(96,165,250,0.18)','rgba(74,222,128,0.18)','rgba(248,113,113,0.18)','rgba(167,139,250,0.18)','rgba(251,146,60,0.18)','rgba(52,211,153,0.18)'];
144
+ ```
145
+
146
+ Grid line color: `rgba(255,255,255,0.07)`
147
+ Axis tick color: `#A1A1AA`
148
+
149
+ **Accent usage rule for electric-burst:** Yellow (`#FACC15`) on the primary dataset or highlight annotation only. Use blue/green for secondary series. Yellow everywhere = no yellow.
150
+
151
+ **Best for:** Growth metrics, social media content, startup metrics, bold data stories for tech audiences.
152
+
153
+ ---
154
+
155
+ ## 5. brutalist
156
+
157
+ **Character:** Raw. Stark. Confrontational. Zero decoration.
158
+
159
+ ```css
160
+ :root {
161
+ --bg: #FFFFFF;
162
+ --bg-elevated: #F5F5F5;
163
+ --text: #000000;
164
+ --text-muted: #666666;
165
+ --accent: #FF0000;
166
+ --divider: #000000;
167
+ --font-display: 'Space Mono', monospace;
168
+ --font-body: 'Space Mono', monospace;
169
+ }
170
+ ```
171
+
172
+ Font CDN:
173
+ ```html
174
+ <link href="https://fonts.googleapis.com/css2?family=Space+Mono:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet">
175
+ ```
176
+
177
+ Data palette:
178
+ ```javascript
179
+ const palette = ['#000000','#FF0000','#555555','#888888','#BBBBBB','#333333','#777777'];
180
+ const paletteAlpha = ['rgba(0,0,0,0.15)','rgba(255,0,0,0.15)','rgba(85,85,85,0.15)','rgba(136,136,136,0.15)','rgba(187,187,187,0.15)','rgba(51,51,51,0.15)','rgba(119,119,119,0.15)'];
181
+ ```
182
+
183
+ Grid line color: `rgba(0,0,0,0.15)`
184
+ Axis tick color: `#666666`
185
+
186
+ **Chart.js defaults override for brutalist:**
187
+ ```javascript
188
+ Chart.defaults.font.family = 'Space Mono, monospace';
189
+ Chart.defaults.color = '#666666';
190
+ Chart.defaults.borderColor = 'rgba(0,0,0,0.15)';
191
+ ```
192
+
193
+ **Best for:** Design-forward agencies, bold comparisons, impact charts where the data itself is the aesthetic.
194
+
195
+ ---
196
+
197
+ ## Chart.js Global Defaults — Set Before Chart Init
198
+
199
+ Apply immediately after `document.fonts.ready.then(() => {`:
200
+ ```javascript
201
+ Chart.defaults.font.family = getComputedStyle(document.documentElement)
202
+ .getPropertyValue('--font-body').trim() || 'system-ui, sans-serif';
203
+ Chart.defaults.color = '#64748B'; // use --text-muted value for each preset
204
+ Chart.defaults.borderColor = '#E2E8F0'; // use --divider value for each preset
205
+ ```
206
+
207
+ Per-preset `Chart.defaults.color` values:
208
+ - clean-slate: `#64748B`
209
+ - midnight-editorial: `#9CA3AF`
210
+ - matt-gray: `#737373`
211
+ - electric-burst: `#A1A1AA`
212
+ - brutalist: `#666666`
213
+
214
+ Per-preset `Chart.defaults.borderColor` values:
215
+ - clean-slate: `rgba(226,232,240,1)` → `#E2E8F0`
216
+ - midnight-editorial: `rgba(45,45,45,1)` → `#2D2D2D`
217
+ - matt-gray: `rgba(212,212,200,1)` → `#D4D4C8`
218
+ - electric-burst: `rgba(39,39,42,1)` → `#27272A`
219
+ - brutalist: `rgba(0,0,0,0.15)`