From a3ab9f8a79a285992e34663001444af2aebdfd54 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 12:41:00 +0000 Subject: [PATCH] Restyle on the Carbon Design System Adopt Carbon at the design-token level (the component libraries ship JS and target app UIs; this is a static content site): - IBM Plex Sans/Mono, self-hosted via @fontsource (no external requests) - Carbon color tokens + layered White / Gray 100 themes - 8px spacing scale, productive/expressive type, motion tokens, square geometry, 2px focus ring - UI-shell header, tile grid, Carbon tags, geometric severity meter, breadcrumb, colophon crediting Carbon - Charts themed with IBM Plex + Carbon's data-visualization palette (validated colorblind-safe/contrast) on a fixed light canvas so the build-time SVG stays legible in both themes Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01XPEvmMbac2fCvKXpQcovj8 --- README.md | 19 +- package.json | 2 + src/charts/dual-axis-bad.json | 8 +- src/charts/dual-axis-fixed.json | 4 +- src/charts/pie-overload-fixed.json | 2 +- src/charts/truncated-axis-bad.json | 2 +- src/charts/truncated-axis-fixed.json | 2 +- src/components/VegaChart.astro | 94 ++-- src/layouts/BaseLayout.astro | 42 +- src/lib/charts.ts | 23 +- src/pages/index.astro | 31 +- src/pages/sins/[...slug].astro | 37 +- src/styles/global.css | 654 ++++++++++++++++++++------- 13 files changed, 668 insertions(+), 252 deletions(-) diff --git a/README.md b/README.md index 4860d11..e957192 100644 --- a/README.md +++ b/README.md @@ -3,21 +3,34 @@ A catalogue of data-visualization sins — for each one: the chart that misleads, why it fools the eye, and the honest version that fixes it. -Built with [Astro](https://astro.build). Charts are authored as +Built with [Astro](https://astro.build) and styled on the +[Carbon Design System](https://carbondesignsystem.com). Charts are authored as [Vega-Lite](https://vega.github.io/vega-lite/) specs and rendered to **static SVG at build time**, so pages ship with zero client-side JavaScript. ## Tech stack -| Concern | Choice | +| Concern | Choice | | ---------- | --------------------------------------------------- | | Framework | Astro 5 (static output) | | Content | Markdown/MDX via Astro Content Collections | | Charts | Vega-Lite specs → SVG at build (headless Vega) | -| Styling | Plain scoped CSS + one global stylesheet | +| Design | Carbon Design System — tokens, type scale, spacing, motion, themes | +| Typeface | IBM Plex Sans / Mono (self-hosted via `@fontsource`) | +| Styling | Token-driven CSS (`src/styles/global.css`) | | Hosting | GitHub Pages (via GitHub Actions) | | Language | TypeScript | +### On the Carbon adoption + +Carbon ships React/web-component libraries built for application UIs and they +ship JavaScript; this is a static content site, so instead of the component +runtime we adopt Carbon at the **design-token level** — its color tokens and +White/Gray-100 themes, the 8px spacing scale, the productive/expressive type +distinction (IBM Plex), motion tokens, square geometry, the focus ring, and +Carbon's **data-visualization palette** for the charts (validated for +colorblind-safety and contrast on the chart surface). + ## Project layout ``` diff --git a/package.json b/package.json index ecd0725..313bed9 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ }, "dependencies": { "@astrojs/mdx": "^4.2.0", + "@fontsource/ibm-plex-mono": "^5.3.0", + "@fontsource/ibm-plex-sans": "^5.3.0", "astro": "^5.6.0", "vega": "^5.30.0", "vega-lite": "^5.21.0" diff --git a/src/charts/dual-axis-bad.json b/src/charts/dual-axis-bad.json index dcce7db..4694d27 100644 --- a/src/charts/dual-axis-bad.json +++ b/src/charts/dual-axis-bad.json @@ -19,15 +19,15 @@ }, "layer": [ { - "mark": { "type": "line", "color": "#c0392b", "point": true }, + "mark": { "type": "line", "color": "#fa4d56", "point": true }, "encoding": { - "y": { "field": "icecream", "type": "quantitative", "title": "Ice cream sales", "axis": { "titleColor": "#c0392b" } } + "y": { "field": "icecream", "type": "quantitative", "title": "Ice cream sales", "axis": { "titleColor": "#fa4d56" } } } }, { - "mark": { "type": "line", "color": "#2980b9", "point": true }, + "mark": { "type": "line", "color": "#1192e8", "point": true }, "encoding": { - "y": { "field": "sharks", "type": "quantitative", "title": "Shark attacks", "axis": { "titleColor": "#2980b9" } } + "y": { "field": "sharks", "type": "quantitative", "title": "Shark attacks", "axis": { "titleColor": "#1192e8" } } } } ], diff --git a/src/charts/dual-axis-fixed.json b/src/charts/dual-axis-fixed.json index 65d10ad..9cbd493 100644 --- a/src/charts/dual-axis-fixed.json +++ b/src/charts/dual-axis-fixed.json @@ -16,7 +16,7 @@ { "width": 360, "height": 110, - "mark": { "type": "line", "color": "#c0392b", "point": true }, + "mark": { "type": "line", "color": "#fa4d56", "point": true }, "encoding": { "x": { "field": "month", "type": "ordinal", "sort": null, "axis": { "title": null, "labels": false } }, "y": { "field": "icecream", "type": "quantitative", "scale": { "zero": true }, "title": "Ice cream" } @@ -25,7 +25,7 @@ { "width": 360, "height": 110, - "mark": { "type": "line", "color": "#2980b9", "point": true }, + "mark": { "type": "line", "color": "#1192e8", "point": true }, "encoding": { "x": { "field": "month", "type": "ordinal", "sort": null, "axis": { "title": null, "labelAngle": 0 } }, "y": { "field": "sharks", "type": "quantitative", "scale": { "zero": true }, "title": "Shark attacks" } diff --git a/src/charts/pie-overload-fixed.json b/src/charts/pie-overload-fixed.json index 00d43f8..d6ae119 100644 --- a/src/charts/pie-overload-fixed.json +++ b/src/charts/pie-overload-fixed.json @@ -14,7 +14,7 @@ { "browser": "Other", "share": 2 } ] }, - "mark": { "type": "bar", "color": "#1e8b5b" }, + "mark": { "type": "bar", "color": "#198038" }, "encoding": { "y": { "field": "browser", "type": "nominal", "sort": "-x", "axis": { "title": null } }, "x": { "field": "share", "type": "quantitative", "title": "Share (%)" } diff --git a/src/charts/truncated-axis-bad.json b/src/charts/truncated-axis-bad.json index 4c1649b..4d740b6 100644 --- a/src/charts/truncated-axis-bad.json +++ b/src/charts/truncated-axis-bad.json @@ -12,7 +12,7 @@ { "month": "Jun", "revenue": 98 } ] }, - "mark": { "type": "bar", "color": "#c0392b" }, + "mark": { "type": "bar", "color": "#da1e28" }, "encoding": { "x": { "field": "month", diff --git a/src/charts/truncated-axis-fixed.json b/src/charts/truncated-axis-fixed.json index 9929bdc..020ae1e 100644 --- a/src/charts/truncated-axis-fixed.json +++ b/src/charts/truncated-axis-fixed.json @@ -12,7 +12,7 @@ { "month": "Jun", "revenue": 98 } ] }, - "mark": { "type": "bar", "color": "#1e8b5b" }, + "mark": { "type": "bar", "color": "#198038" }, "encoding": { "x": { "field": "month", diff --git a/src/components/VegaChart.astro b/src/components/VegaChart.astro index 4fc9fbe..b7e8c2f 100644 --- a/src/components/VegaChart.astro +++ b/src/components/VegaChart.astro @@ -1,39 +1,76 @@ --- -// Build-time Vega-Lite renderer. +// Build-time Vega-Lite renderer, themed on Carbon. // -// This component runs only during `astro build` (and dev SSR), never in the -// browser. It compiles a Vega-Lite spec to Vega, renders it to a static SVG -// string with Vega's headless view, and inlines that SVG. The result ships as -// plain markup — zero client-side JavaScript, no Vega runtime downloaded. +// Runs only during `astro build` (and dev SSR), never in the browser: it +// compiles a Vega-Lite spec to Vega with a Carbon theme (IBM Plex type + +// Carbon's data-visualization palette), renders it headlessly to a static SVG +// string, and inlines it. Zero client-side JavaScript, no Vega runtime shipped. // -// To make a specific chart interactive later, you would swap this component -// out for a client-side vega-embed island on that page only. +// The chart sits on a fixed light Carbon canvas (--chart-canvas) in both +// themes, so this single baked render keeps AA contrast in light and dark. import * as vega from 'vega'; -import { compile, type TopLevelSpec } from 'vega-lite'; +import { compile, type TopLevelSpec, type Config } from 'vega-lite'; interface Props { - /** A Vega-Lite spec (object). Do not include $schema/width/height/config. */ spec: TopLevelSpec; - /** Accessible description of what the chart shows. */ title: string; - /** Optional caption rendered under the chart. */ caption?: string; } const { spec, title, caption } = Astro.props; +// Carbon Vega theme — the design-system parameters the renderer consumes. +const carbonTheme: Config = { + font: 'IBM Plex Sans', + background: 'transparent', + view: { stroke: null }, + title: { + color: '#161616', + font: 'IBM Plex Sans', + fontSize: 13, + fontWeight: 600, + anchor: 'start', + dy: -8, + }, + axis: { + labelFont: 'IBM Plex Sans', + labelFontSize: 11, + labelColor: '#525252', + titleFont: 'IBM Plex Sans', + titleFontSize: 12, + titleFontWeight: 600, + titleColor: '#393939', + gridColor: '#e0e0e0', + domainColor: '#8d8d8d', + tickColor: '#8d8d8d', + labelPadding: 4, + }, + legend: { + labelFont: 'IBM Plex Sans', + labelFontSize: 11, + labelColor: '#525252', + titleFont: 'IBM Plex Sans', + titleFontSize: 12, + titleColor: '#393939', + symbolType: 'square', + }, + // Carbon data-visualization categorical palette (validated: passes the + // dataviz colorblind/contrast checks on the --chart-canvas surface). + range: { + category: ['#8a3ffc', '#009d9a', '#fa4d56', '#0f62fe', '#24a148', '#d02670', '#b28600', '#1192e8'], + }, +}; + let svg = ''; let error: string | null = null; try { - // Compile Vega-Lite -> Vega, then render headlessly to SVG. - const vgSpec = compile(spec as TopLevelSpec).spec; + const vgSpec = compile(spec, { config: carbonTheme }).spec; const view = new vega.View(vega.parse(vgSpec), { renderer: 'none' }).initialize(); svg = await view.toSVG(); view.finalize(); } catch (e) { error = e instanceof Error ? e.message : String(e); - // Fail loudly in the build log, but don't crash the whole build. console.error(`[VegaChart] failed to render "${title}": ${error}`); } --- @@ -49,32 +86,3 @@ try { )} {caption &&
{caption}
} - - diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index ee6d79c..236f57a 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -1,4 +1,9 @@ --- +// IBM Plex, self-hosted via @fontsource (no external requests) — Carbon's typeface. +import '@fontsource/ibm-plex-sans/300.css'; +import '@fontsource/ibm-plex-sans/400.css'; +import '@fontsource/ibm-plex-sans/600.css'; +import '@fontsource/ibm-plex-mono/400.css'; import '../styles/global.css'; import { href } from '../lib/url'; @@ -7,7 +12,8 @@ interface Props { description?: string; } -const { title, description = 'A catalogue of data-visualization sins.' } = Astro.props; +const { title, description = 'A catalogue of data-visualization sins, styled on the Carbon Design System.' } = + Astro.props; --- @@ -19,20 +25,38 @@ const { title, description = 'A catalogue of data-visualization sins.' } = Astro {title} -