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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPEvmMbac2fCvKXpQcovj8
This commit is contained in:
Claude
2026-08-05 12:41:00 +00:00
parent 076ba185bd
commit a3ab9f8a79
13 changed files with 668 additions and 252 deletions
+18 -13
View File
@@ -1,7 +1,7 @@
---
import { getCollection } from 'astro:content';
import BaseLayout from '../layouts/BaseLayout.astro';
import { severityFlames } from '../lib/charts';
import { severityUnits, tagModifier } from '../lib/charts';
import { href } from '../lib/url';
const sins = (await getCollection('sins', ({ data }) => !data.draft)).sort((a, b) => {
@@ -13,10 +13,11 @@ const sins = (await getCollection('sins', ({ data }) => !data.draft)).sort((a, b
<BaseLayout title="Chart Sins">
<section class="wrap hero">
<h1>A catalogue of chart sins.</h1>
<p>
Every entry is a data-visualization mistake in the wild: the chart that
misleads, why it fools the eye, and the honest version that fixes it.
<p class="hero__eyebrow">// a catalogue of data-visualization sins</p>
<h1 class="expressive-display">Charts that lie,<br />and the honest fix.</h1>
<p class="hero__lede">
Each entry is a data-visualization mistake in the wild: the chart that
misleads, why it fools the eye, and the honest version that repents.
</p>
</section>
@@ -24,15 +25,19 @@ const sins = (await getCollection('sins', ({ data }) => !data.draft)).sort((a, b
<div class="gallery">
{
sins.map((sin) => (
<a class="card" href={href(`sins/${sin.id}/`)}>
<span class="pill">{sin.data.category}</span>
<h3>{sin.data.title}</h3>
<p>{sin.data.summary}</p>
<div class="meta">
<span class="severity" title={`Severity ${sin.data.severity} of 5`}>
{severityFlames(sin.data.severity)}
<a class="tile" href={href(`sins/${sin.id}/`)}>
<span class={`tag tag--${tagModifier(sin.data.category)}`}>{sin.data.category}</span>
<h2 class="tile__title">{sin.data.title}</h2>
<p class="tile__summary">{sin.data.summary}</p>
<div class="tile__meta">
<span class="severity" aria-label={`Severity ${sin.data.severity} of 5`}>
<span class="severity__meter" aria-hidden="true">
{severityUnits(sin.data.severity).map((on) => (
<span class={`severity__unit${on ? ' severity__unit--on' : ''}`} />
))}
</span>
</span>
<span class="pill">Read →</span>
<span class="tile__go">Read →</span>
</div>
</a>
))
+22 -15
View File
@@ -2,7 +2,7 @@
import { getCollection, render } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
import VegaChart from '../../components/VegaChart.astro';
import { getChartSpec, severityFlames } from '../../lib/charts';
import { getChartSpec, severityUnits, tagModifier } from '../../lib/charts';
import { href } from '../../lib/url';
export async function getStaticPaths() {
@@ -22,24 +22,33 @@ const fixedSpec = getChartSpec(sin.data.fixedChart);
<BaseLayout title={`${sin.data.title} — Chart Sins`} description={sin.data.summary}>
<article class="wrap sin">
<header>
<span class="pill">{sin.data.category}</span>
<h1>{sin.data.title}</h1>
<span class="severity" title={`Severity ${sin.data.severity} of 5`}>
{severityFlames(sin.data.severity)}
</span>
<div class="tags">
{sin.data.tags.map((t) => <span class="pill">{t}</span>)}
<nav class="breadcrumb"><a href={href()}>All sins</a> / {sin.data.title}</nav>
<header class="sin__head">
<div class="sin__meta">
<span class={`tag tag--${tagModifier(sin.data.category)}`}>{sin.data.category}</span>
<span class="severity" aria-label={`Severity ${sin.data.severity} of 5`}>
<span class="severity__meter" aria-hidden="true">
{severityUnits(sin.data.severity).map((on) => (
<span class={`severity__unit${on ? ' severity__unit--on' : ''}`} />
))}
</span>
Severity {sin.data.severity}/5
</span>
</div>
<h1 class="sin__title expressive-heading">{sin.data.title}</h1>
<div class="sin__tags">
{sin.data.tags.map((t) => <span class="tag">{t}</span>)}
</div>
</header>
<div class="compare">
<div class="panel bad">
<h2>The Sin</h2>
<div class="panel panel--bad">
<p class="panel__label">The Sin</p>
<VegaChart spec={badSpec} title={`Misleading chart: ${sin.data.title}`} />
</div>
<div class="panel good">
<h2>The Fix</h2>
<div class="panel panel--good">
<p class="panel__label">The Fix</p>
<VegaChart spec={fixedSpec} title={`Honest chart: ${sin.data.title}`} />
</div>
</div>
@@ -47,7 +56,5 @@ const fixedSpec = getChartSpec(sin.data.fixedChart);
<div class="prose">
<Content />
</div>
<a class="back-link" href={href()}>← All sins</a>
</article>
</BaseLayout>