Remove internal build decisions from visitor-facing content

The public site (and DOM) shouldn't advertise our tooling choices:

- Drop the design-system credit and typeface note from the footer; replace
  with visitor-useful copy ("Built to be sent" / "The canon")
- Remove the external-inspiration reference from the homepage lede
- Neutralize the meta description
- Rename the `cds-` CSS class prefix to `site-`
- Scrub internal names from source comments and the chart theme variable

Functional font-family references are kept; only the advertising is removed.

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 13:40:59 +00:00
parent e0d3f9f773
commit f010ec6a3c
5 changed files with 67 additions and 72 deletions
+11 -11
View File
@@ -1,13 +1,13 @@
--- ---
// Build-time Vega-Lite renderer, themed on Carbon. // Build-time Vega-Lite renderer.
// //
// Runs only during `astro build` (and dev SSR), never in the browser: it // 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 + // compiles a Vega-Lite spec to Vega with the site theme, renders it headlessly
// Carbon's data-visualization palette), renders it headlessly to a static SVG // to a static SVG string, and inlines it. Zero client-side JavaScript, no Vega
// string, and inlines it. Zero client-side JavaScript, no Vega runtime shipped. // runtime shipped.
// //
// The chart sits on a fixed light Carbon canvas (--chart-canvas) in both // The chart sits on a fixed light canvas (--chart-canvas) in both themes, so
// themes, so this single baked render keeps AA contrast in light and dark. // this single baked render keeps AA contrast in light and dark.
import * as vega from 'vega'; import * as vega from 'vega';
import { compile, type TopLevelSpec, type Config } from 'vega-lite'; import { compile, type TopLevelSpec, type Config } from 'vega-lite';
@@ -19,8 +19,8 @@ interface Props {
const { spec, title, caption } = Astro.props; const { spec, title, caption } = Astro.props;
// Carbon Vega theme — the design-system parameters the renderer consumes. // Chart theme — the visual parameters the renderer consumes.
const carbonTheme: Config = { const chartTheme: Config = {
font: 'IBM Plex Sans', font: 'IBM Plex Sans',
background: 'transparent', background: 'transparent',
view: { stroke: null }, view: { stroke: null },
@@ -54,8 +54,8 @@ const carbonTheme: Config = {
titleColor: '#393939', titleColor: '#393939',
symbolType: 'square', symbolType: 'square',
}, },
// Carbon data-visualization categorical palette (validated: passes the // Categorical palette (validated colorblind-safe/contrast on the
// dataviz colorblind/contrast checks on the --chart-canvas surface). // --chart-canvas surface).
range: { range: {
category: ['#8a3ffc', '#009d9a', '#fa4d56', '#0f62fe', '#24a148', '#d02670', '#b28600', '#1192e8'], category: ['#8a3ffc', '#009d9a', '#fa4d56', '#0f62fe', '#24a148', '#d02670', '#b28600', '#1192e8'],
}, },
@@ -65,7 +65,7 @@ let svg = '';
let error: string | null = null; let error: string | null = null;
try { try {
const vgSpec = compile(spec, { config: carbonTheme }).spec; const vgSpec = compile(spec, { config: chartTheme }).spec;
const view = new vega.View(vega.parse(vgSpec), { renderer: 'none' }).initialize(); const view = new vega.View(vega.parse(vgSpec), { renderer: 'none' }).initialize();
svg = await view.toSVG(); svg = await view.toSVG();
view.finalize(); view.finalize();
+15 -18
View File
@@ -1,5 +1,5 @@
--- ---
// IBM Plex, self-hosted via @fontsource (no external requests) — Carbon's typeface. // Self-hosted webfonts via @fontsource (no external requests).
import '@fontsource/ibm-plex-sans/300.css'; import '@fontsource/ibm-plex-sans/300.css';
import '@fontsource/ibm-plex-sans/400.css'; import '@fontsource/ibm-plex-sans/400.css';
import '@fontsource/ibm-plex-sans/600.css'; import '@fontsource/ibm-plex-sans/600.css';
@@ -12,7 +12,7 @@ interface Props {
description?: string; description?: string;
} }
const { title, description = 'A catalogue of data-visualization sins, styled on the Carbon Design System.' } = const { title, description = 'A catalogue of data-visualization sins — the deceptive chart, why it misleads, and the honest fix.' } =
Astro.props; Astro.props;
--- ---
@@ -27,12 +27,12 @@ const { title, description = 'A catalogue of data-visualization sins, styled on
<body> <body>
<a class="skip-link" href="#main">Skip to content</a> <a class="skip-link" href="#main">Skip to content</a>
<header class="cds-header"> <header class="site-header">
<div class="cds-header__inner"> <div class="site-header__inner">
<a class="cds-header__name" href={href()}> <a class="site-header__name" href={href()}>
Chart&nbsp;Sins <span class="mono">/ honest data-viz</span> Chart&nbsp;Sins <span class="mono">/ honest data-viz</span>
</a> </a>
<nav class="cds-header__nav" aria-label="Primary"> <nav class="site-header__nav" aria-label="Primary">
<a href={href()}>Sins</a> <a href={href()}>Sins</a>
<a href={href('canon/')}>Canon</a> <a href={href('canon/')}>Canon</a>
</nav> </nav>
@@ -43,22 +43,19 @@ const { title, description = 'A catalogue of data-visualization sins, styled on
<slot /> <slot />
</main> </main>
<footer class="cds-footer"> <footer class="site-footer">
<div class="wrap cds-footer__inner"> <div class="wrap site-footer__inner">
<div class="cds-footer__col"> <div class="site-footer__col">
<strong>Chart Sins</strong> <strong>Chart Sins</strong>
<p>An educational catalogue of data-visualization mistakes — the bad chart, why it deceives, and the honest fix.</p> <p>An educational catalogue of data-visualization mistakes — the bad chart, why it deceives, and the honest fix.</p>
</div> </div>
<div class="cds-footer__col"> <div class="site-footer__col">
<strong>Design</strong> <strong>Built to be sent</strong>
<p> <p>Every sin has a permanent link and cites its sources — made to share, not to re-explain.</p>
Built on the <a href="https://carbondesignsystem.com" rel="noopener">Carbon Design System</a>'s
principles — open, modular, consistent, inclusive. Typeface: IBM&nbsp;Plex.
</p>
</div> </div>
<div class="cds-footer__col"> <div class="site-footer__col">
<strong>Charts</strong> <strong>The canon</strong>
<p>Authored in Vega-Lite, rendered to static SVG at build time on Carbon's data-visualization palette.</p> <p>Each entry stands on the shoulders of the field's giants. <a href={href('canon/')}>See the sources →</a></p>
</div> </div>
</div> </div>
</footer> </footer>
+3 -3
View File
@@ -23,12 +23,12 @@ export function getChartSpec(name: string): TopLevelSpec {
return spec; return spec;
} }
/** A 15 severity as five booleans, for the Carbon-style severity meter. */ /** A 15 severity as five booleans, for the severity meter. */
export function severityUnits(severity: number): boolean[] { export function severityUnits(severity: number): boolean[] {
return Array.from({ length: 5 }, (_, i) => i < severity); return Array.from({ length: 5 }, (_, i) => i < severity);
} }
// Stable category -> Carbon tag color. Known categories get a hand-picked hue; // Stable category -> tag color. Known categories get a hand-picked hue;
// anything new hashes to a stable colour so the palette never churns. // anything new hashes to a stable colour so the palette never churns.
const TAG_COLORS = ['purple', 'teal', 'red', 'blue', 'magenta'] as const; const TAG_COLORS = ['purple', 'teal', 'red', 'blue', 'magenta'] as const;
const CATEGORY_TAG: Record<string, (typeof TAG_COLORS)[number]> = { const CATEGORY_TAG: Record<string, (typeof TAG_COLORS)[number]> = {
@@ -37,7 +37,7 @@ const CATEGORY_TAG: Record<string, (typeof TAG_COLORS)[number]> = {
'Chart-Type Abuse': 'purple', 'Chart-Type Abuse': 'purple',
}; };
/** Map a category label to a Carbon tag colour modifier. */ /** Map a category label to a tag colour modifier. */
export function tagModifier(category: string): (typeof TAG_COLORS)[number] { export function tagModifier(category: string): (typeof TAG_COLORS)[number] {
if (CATEGORY_TAG[category]) return CATEGORY_TAG[category]; if (CATEGORY_TAG[category]) return CATEGORY_TAG[category];
let hash = 0; let hash = 0;
+1 -2
View File
@@ -18,8 +18,7 @@ const sins = (await getCollection('sins', ({ data }) => !data.draft)).sort((a, b
<p class="hero__lede"> <p class="hero__lede">
Dont type the whole explanation for the hundredth time — send the link. Dont type the whole explanation for the hundredth time — send the link.
Each sin shows the deceptive chart beside its honest twin, names the tell, Each sin shows the deceptive chart beside its honest twin, names the tell,
and cites the giants who back you up. Its <a href="https://nohello.net" rel="noopener">nohello.net</a>, and cites the giants who back you up.
for bad charts.
</p> </p>
</section> </section>
+37 -38
View File
@@ -1,21 +1,20 @@
/* /*
Chart Sins — styled on the Carbon Design System. Chart Sins — token-driven style layer.
This is a token-driven layer, the way Carbon itself is built: - Color/theme tokens are layered (background -> layer-01 -> layer-02) with
- Color/theme tokens mirror Carbon's White + Gray 100 themes, layered a light and a dark theme.
(background -> layer-01 -> layer-02) rather than picked ad hoc. - Spacing follows an 8px mini-unit scale.
- Spacing follows Carbon's 8px mini-unit scale ($spacing-01..13). - Type is split into "productive" (UI/reading) and "expressive" (large,
- Type is IBM Plex, split into Carbon's "productive" (UI/reading) and light-weight display) roles.
"expressive" (large, light-weight display) moments. - Motion uses shared easing + duration tokens.
- Motion uses Carbon's productive easing + duration tokens. - Geometry: square corners, 1px subtle borders, a 2px focus ring.
- Geometry is Carbon: square corners, 1px subtle borders, a 2px focus ring.
*/ */
/* ------------------------------------------------------------------ *\ /* ------------------------------------------------------------------ *\
Tokens — White theme (light) is the default :root. Tokens — White theme (light) is the default :root.
\* ------------------------------------------------------------------ */ \* ------------------------------------------------------------------ */
:root { :root {
/* --- spacing scale (Carbon $spacing-01 .. 13) --- */ /* --- spacing scale (8px mini-unit scale) --- */
--spacing-01: 0.125rem; --spacing-01: 0.125rem;
--spacing-02: 0.25rem; --spacing-02: 0.25rem;
--spacing-03: 0.5rem; --spacing-03: 0.5rem;
@@ -30,7 +29,7 @@
--spacing-12: 6rem; --spacing-12: 6rem;
--spacing-13: 10rem; --spacing-13: 10rem;
/* --- motion (Carbon) --- */ /* --- motion --- */
--ease-productive: cubic-bezier(0.2, 0, 0.38, 0.9); --ease-productive: cubic-bezier(0.2, 0, 0.38, 0.9);
--ease-expressive: cubic-bezier(0.4, 0.14, 0.3, 1); --ease-expressive: cubic-bezier(0.4, 0.14, 0.3, 1);
--dur-fast: 110ms; --dur-fast: 110ms;
@@ -39,7 +38,7 @@
/* --- layout --- */ /* --- layout --- */
--page-max: 82rem; --page-max: 82rem;
--measure: 40rem; /* Carbon reading measure ~640px */ --measure: 40rem; /* reading measure ~640px */
/* --- color tokens: White theme --- */ /* --- color tokens: White theme --- */
--background: #ffffff; --background: #ffffff;
@@ -59,13 +58,13 @@
--support-error: #da1e28; --support-error: #da1e28;
--support-success: #198038; --support-success: #198038;
/* header/footer shell is always dark in Carbon */ /* header/footer shell is always dark in both themes */
--shell-bg: #161616; --shell-bg: #161616;
--shell-border: #393939; --shell-border: #393939;
--shell-text: #f4f4f4; --shell-text: #f4f4f4;
--shell-text-secondary: #a8a8a8; --shell-text-secondary: #a8a8a8;
/* chart canvas — a fixed light Carbon layer in BOTH themes, so the /* chart canvas — a fixed light layer in BOTH themes, so the
build-time-baked chart ink keeps AA contrast without theme-reactive SVG */ build-time-baked chart ink keeps AA contrast without theme-reactive SVG */
--chart-canvas: #f4f4f4; --chart-canvas: #f4f4f4;
@@ -115,7 +114,7 @@ html {
body { body {
margin: 0; margin: 0;
/* Carbon productive body-02 */ /* base body text */
font-size: 1rem; font-size: 1rem;
line-height: 1.5; line-height: 1.5;
letter-spacing: 0; letter-spacing: 0;
@@ -133,7 +132,7 @@ a:hover {
border-bottom-color: currentColor; border-bottom-color: currentColor;
} }
/* Carbon focus ring — 2px, high-contrast, square */ /* focus ring — 2px, high-contrast, square */
:where(a, button, [tabindex]):focus-visible { :where(a, button, [tabindex]):focus-visible {
outline: 2px solid var(--focus); outline: 2px solid var(--focus);
outline-offset: 2px; outline-offset: 2px;
@@ -149,10 +148,10 @@ code {
} }
/* ------------------------------------------------------------------ *\ /* ------------------------------------------------------------------ *\
Type scale — productive + expressive (Carbon) Type scale — productive + expressive
\* ------------------------------------------------------------------ */ \* ------------------------------------------------------------------ */
.expressive-display { .expressive-display {
font-weight: 300; /* Carbon expressive uses light weight at large sizes */ font-weight: 300; /* expressive uses light weight at large sizes */
font-size: clamp(2.25rem, 1.4rem + 4.2vw, 4rem); font-size: clamp(2.25rem, 1.4rem + 4.2vw, 4rem);
line-height: 1.07; line-height: 1.07;
letter-spacing: -0.02em; letter-spacing: -0.02em;
@@ -196,16 +195,16 @@ code {
} }
/* ------------------------------------------------------------------ *\ /* ------------------------------------------------------------------ *\
UI Shell header — Carbon's header is dark in every theme Header — dark in every theme
\* ------------------------------------------------------------------ */ \* ------------------------------------------------------------------ */
.cds-header { .site-header {
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 10; z-index: 10;
background: var(--shell-bg); background: var(--shell-bg);
border-bottom: 1px solid var(--shell-border); border-bottom: 1px solid var(--shell-border);
} }
.cds-header__inner { .site-header__inner {
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--spacing-05); gap: var(--spacing-05);
@@ -214,7 +213,7 @@ code {
margin-inline: auto; margin-inline: auto;
padding-inline: var(--spacing-05); padding-inline: var(--spacing-05);
} }
.cds-header__name { .site-header__name {
color: var(--shell-text); color: var(--shell-text);
font-weight: 600; font-weight: 600;
font-size: 1rem; font-size: 1rem;
@@ -222,37 +221,37 @@ code {
text-decoration: none; text-decoration: none;
border: 0; border: 0;
} }
.cds-header__name:hover { .site-header__name:hover {
color: var(--shell-text); color: var(--shell-text);
} }
.cds-header__name .mono { .site-header__name .mono {
font-family: var(--font-mono); font-family: var(--font-mono);
font-weight: 400; font-weight: 400;
} }
.cds-header__tag { .site-header__tag {
color: var(--shell-text-secondary); color: var(--shell-text-secondary);
font-size: 0.8125rem; font-size: 0.8125rem;
font-family: var(--font-mono); font-family: var(--font-mono);
letter-spacing: 0; letter-spacing: 0;
} }
.cds-header__nav { .site-header__nav {
margin-left: auto; margin-left: auto;
display: flex; display: flex;
gap: var(--spacing-06); gap: var(--spacing-06);
} }
.cds-header__nav a { .site-header__nav a {
color: var(--shell-text-secondary); color: var(--shell-text-secondary);
font-size: 0.875rem; font-size: 0.875rem;
text-decoration: none; text-decoration: none;
border: 0; border: 0;
padding-block: var(--spacing-02); padding-block: var(--spacing-02);
} }
.cds-header__nav a:hover { .site-header__nav a:hover {
color: var(--shell-text); color: var(--shell-text);
} }
/* ------------------------------------------------------------------ *\ /* ------------------------------------------------------------------ *\
Tag (Carbon tags are the one rounded element) Tag (the one rounded element)
\* ------------------------------------------------------------------ */ \* ------------------------------------------------------------------ */
.tag { .tag {
display: inline-flex; display: inline-flex;
@@ -268,7 +267,7 @@ code {
color: var(--text-secondary); color: var(--text-secondary);
border: 1px solid transparent; border: 1px solid transparent;
} }
/* Carbon tag color pairings (light bg + readable ink) */ /* tag color pairings (light bg + readable ink) */
.tag--purple { background: #e8daff; color: #491d8b; } .tag--purple { background: #e8daff; color: #491d8b; }
.tag--teal { background: #9ef0f0; color: #005d5d; } .tag--teal { background: #9ef0f0; color: #005d5d; }
.tag--red { background: #ffd7d9; color: #a2191f; } .tag--red { background: #ffd7d9; color: #a2191f; }
@@ -283,7 +282,7 @@ code {
} }
/* ------------------------------------------------------------------ *\ /* ------------------------------------------------------------------ *\
Severity meter — 5 geometric units (Carbon-flavored, not emoji) Severity meter — 5 geometric units (not emoji)
\* ------------------------------------------------------------------ */ \* ------------------------------------------------------------------ */
.severity { .severity {
display: inline-flex; display: inline-flex;
@@ -335,7 +334,7 @@ code {
.gallery { .gallery {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr)); grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
gap: 1px; /* Carbon tiles sit in a 1px grid of border color */ gap: 1px; /* tiles sit in a 1px grid of border color */
background: var(--border-subtle); background: var(--border-subtle);
border: 1px solid var(--border-subtle); border: 1px solid var(--border-subtle);
margin-block: var(--spacing-09) var(--spacing-10); margin-block: var(--spacing-09) var(--spacing-10);
@@ -475,7 +474,7 @@ code {
background: var(--support-success); background: var(--support-success);
} }
/* build-time chart, on a fixed light Carbon canvas */ /* build-time chart, on a fixed light canvas */
.vega-chart { .vega-chart {
margin: 0; margin: 0;
} }
@@ -511,7 +510,7 @@ code {
padding: 0; padding: 0;
} }
/* long-form prose — Carbon reading measure + productive type */ /* long-form prose — reading measure + productive type */
.prose { .prose {
max-width: var(--measure); max-width: var(--measure);
margin-top: var(--spacing-08); margin-top: var(--spacing-08);
@@ -727,12 +726,12 @@ a.canon__work {
/* ------------------------------------------------------------------ *\ /* ------------------------------------------------------------------ *\
Footer + colophon Footer + colophon
\* ------------------------------------------------------------------ */ \* ------------------------------------------------------------------ */
.cds-footer { .site-footer {
background: var(--layer-01); background: var(--layer-01);
border-top: 1px solid var(--border-subtle); border-top: 1px solid var(--border-subtle);
margin-top: var(--spacing-12); margin-top: var(--spacing-12);
} }
.cds-footer__inner { .site-footer__inner {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: var(--spacing-05) var(--spacing-09); gap: var(--spacing-05) var(--spacing-09);
@@ -740,13 +739,13 @@ a.canon__work {
font-size: 0.8125rem; font-size: 0.8125rem;
color: var(--text-secondary); color: var(--text-secondary);
} }
.cds-footer__col strong { .site-footer__col strong {
display: block; display: block;
color: var(--text-primary); color: var(--text-primary);
font-weight: 600; font-weight: 600;
margin-bottom: var(--spacing-03); margin-bottom: var(--spacing-03);
} }
.cds-footer__col p { .site-footer__col p {
margin: 0; margin: 0;
max-width: 22rem; max-width: 22rem;
line-height: 1.5; line-height: 1.5;