mirror of
https://github.com/olehomelchenko/chart-sins.git
synced 2026-08-08 02:22:43 +00:00
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:
@@ -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
|
||||
// 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.
|
||||
// compiles a Vega-Lite spec to Vega with the site theme, renders it headlessly
|
||||
// to a static SVG string, and inlines it. Zero client-side JavaScript, no Vega
|
||||
// runtime shipped.
|
||||
//
|
||||
// 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.
|
||||
// The chart sits on a fixed light 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, type Config } from 'vega-lite';
|
||||
|
||||
@@ -19,8 +19,8 @@ interface Props {
|
||||
|
||||
const { spec, title, caption } = Astro.props;
|
||||
|
||||
// Carbon Vega theme — the design-system parameters the renderer consumes.
|
||||
const carbonTheme: Config = {
|
||||
// Chart theme — the visual parameters the renderer consumes.
|
||||
const chartTheme: Config = {
|
||||
font: 'IBM Plex Sans',
|
||||
background: 'transparent',
|
||||
view: { stroke: null },
|
||||
@@ -54,8 +54,8 @@ const carbonTheme: Config = {
|
||||
titleColor: '#393939',
|
||||
symbolType: 'square',
|
||||
},
|
||||
// Carbon data-visualization categorical palette (validated: passes the
|
||||
// dataviz colorblind/contrast checks on the --chart-canvas surface).
|
||||
// Categorical palette (validated colorblind-safe/contrast on the
|
||||
// --chart-canvas surface).
|
||||
range: {
|
||||
category: ['#8a3ffc', '#009d9a', '#fa4d56', '#0f62fe', '#24a148', '#d02670', '#b28600', '#1192e8'],
|
||||
},
|
||||
@@ -65,7 +65,7 @@ let svg = '';
|
||||
let error: string | null = null;
|
||||
|
||||
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();
|
||||
svg = await view.toSVG();
|
||||
view.finalize();
|
||||
|
||||
@@ -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/400.css';
|
||||
import '@fontsource/ibm-plex-sans/600.css';
|
||||
@@ -12,7 +12,7 @@ interface Props {
|
||||
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;
|
||||
---
|
||||
|
||||
@@ -27,12 +27,12 @@ const { title, description = 'A catalogue of data-visualization sins, styled on
|
||||
<body>
|
||||
<a class="skip-link" href="#main">Skip to content</a>
|
||||
|
||||
<header class="cds-header">
|
||||
<div class="cds-header__inner">
|
||||
<a class="cds-header__name" href={href()}>
|
||||
<header class="site-header">
|
||||
<div class="site-header__inner">
|
||||
<a class="site-header__name" href={href()}>
|
||||
Chart Sins <span class="mono">/ honest data-viz</span>
|
||||
</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('canon/')}>Canon</a>
|
||||
</nav>
|
||||
@@ -43,22 +43,19 @@ const { title, description = 'A catalogue of data-visualization sins, styled on
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<footer class="cds-footer">
|
||||
<div class="wrap cds-footer__inner">
|
||||
<div class="cds-footer__col">
|
||||
<footer class="site-footer">
|
||||
<div class="wrap site-footer__inner">
|
||||
<div class="site-footer__col">
|
||||
<strong>Chart Sins</strong>
|
||||
<p>An educational catalogue of data-visualization mistakes — the bad chart, why it deceives, and the honest fix.</p>
|
||||
</div>
|
||||
<div class="cds-footer__col">
|
||||
<strong>Design</strong>
|
||||
<p>
|
||||
Built on the <a href="https://carbondesignsystem.com" rel="noopener">Carbon Design System</a>'s
|
||||
principles — open, modular, consistent, inclusive. Typeface: IBM Plex.
|
||||
</p>
|
||||
<div class="site-footer__col">
|
||||
<strong>Built to be sent</strong>
|
||||
<p>Every sin has a permanent link and cites its sources — made to share, not to re-explain.</p>
|
||||
</div>
|
||||
<div class="cds-footer__col">
|
||||
<strong>Charts</strong>
|
||||
<p>Authored in Vega-Lite, rendered to static SVG at build time on Carbon's data-visualization palette.</p>
|
||||
<div class="site-footer__col">
|
||||
<strong>The canon</strong>
|
||||
<p>Each entry stands on the shoulders of the field's giants. <a href={href('canon/')}>See the sources →</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
+3
-3
@@ -23,12 +23,12 @@ export function getChartSpec(name: string): TopLevelSpec {
|
||||
return spec;
|
||||
}
|
||||
|
||||
/** A 1–5 severity as five booleans, for the Carbon-style severity meter. */
|
||||
/** A 1–5 severity as five booleans, for the severity meter. */
|
||||
export function severityUnits(severity: number): boolean[] {
|
||||
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.
|
||||
const TAG_COLORS = ['purple', 'teal', 'red', 'blue', 'magenta'] as const;
|
||||
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',
|
||||
};
|
||||
|
||||
/** 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] {
|
||||
if (CATEGORY_TAG[category]) return CATEGORY_TAG[category];
|
||||
let hash = 0;
|
||||
|
||||
@@ -18,8 +18,7 @@ const sins = (await getCollection('sins', ({ data }) => !data.draft)).sort((a, b
|
||||
<p class="hero__lede">
|
||||
Don’t type the whole explanation for the hundredth time — send the link.
|
||||
Each sin shows the deceptive chart beside its honest twin, names the tell,
|
||||
and cites the giants who back you up. It’s <a href="https://nohello.net" rel="noopener">nohello.net</a>,
|
||||
for bad charts.
|
||||
and cites the giants who back you up.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
|
||||
+37
-38
@@ -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 mirror Carbon's White + Gray 100 themes, layered
|
||||
(background -> layer-01 -> layer-02) rather than picked ad hoc.
|
||||
- Spacing follows Carbon's 8px mini-unit scale ($spacing-01..13).
|
||||
- Type is IBM Plex, split into Carbon's "productive" (UI/reading) and
|
||||
"expressive" (large, light-weight display) moments.
|
||||
- Motion uses Carbon's productive easing + duration tokens.
|
||||
- Geometry is Carbon: square corners, 1px subtle borders, a 2px focus ring.
|
||||
- Color/theme tokens are layered (background -> layer-01 -> layer-02) with
|
||||
a light and a dark theme.
|
||||
- Spacing follows an 8px mini-unit scale.
|
||||
- Type is split into "productive" (UI/reading) and "expressive" (large,
|
||||
light-weight display) roles.
|
||||
- Motion uses shared easing + duration tokens.
|
||||
- Geometry: square corners, 1px subtle borders, a 2px focus ring.
|
||||
*/
|
||||
|
||||
/* ------------------------------------------------------------------ *\
|
||||
Tokens — White theme (light) is the default :root.
|
||||
\* ------------------------------------------------------------------ */
|
||||
:root {
|
||||
/* --- spacing scale (Carbon $spacing-01 .. 13) --- */
|
||||
/* --- spacing scale (8px mini-unit scale) --- */
|
||||
--spacing-01: 0.125rem;
|
||||
--spacing-02: 0.25rem;
|
||||
--spacing-03: 0.5rem;
|
||||
@@ -30,7 +29,7 @@
|
||||
--spacing-12: 6rem;
|
||||
--spacing-13: 10rem;
|
||||
|
||||
/* --- motion (Carbon) --- */
|
||||
/* --- motion --- */
|
||||
--ease-productive: cubic-bezier(0.2, 0, 0.38, 0.9);
|
||||
--ease-expressive: cubic-bezier(0.4, 0.14, 0.3, 1);
|
||||
--dur-fast: 110ms;
|
||||
@@ -39,7 +38,7 @@
|
||||
|
||||
/* --- layout --- */
|
||||
--page-max: 82rem;
|
||||
--measure: 40rem; /* Carbon reading measure ~640px */
|
||||
--measure: 40rem; /* reading measure ~640px */
|
||||
|
||||
/* --- color tokens: White theme --- */
|
||||
--background: #ffffff;
|
||||
@@ -59,13 +58,13 @@
|
||||
--support-error: #da1e28;
|
||||
--support-success: #198038;
|
||||
|
||||
/* header/footer shell is always dark in Carbon */
|
||||
/* header/footer shell is always dark in both themes */
|
||||
--shell-bg: #161616;
|
||||
--shell-border: #393939;
|
||||
--shell-text: #f4f4f4;
|
||||
--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 */
|
||||
--chart-canvas: #f4f4f4;
|
||||
|
||||
@@ -115,7 +114,7 @@ html {
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
/* Carbon productive body-02 */
|
||||
/* base body text */
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
letter-spacing: 0;
|
||||
@@ -133,7 +132,7 @@ a:hover {
|
||||
border-bottom-color: currentColor;
|
||||
}
|
||||
|
||||
/* Carbon focus ring — 2px, high-contrast, square */
|
||||
/* focus ring — 2px, high-contrast, square */
|
||||
:where(a, button, [tabindex]):focus-visible {
|
||||
outline: 2px solid var(--focus);
|
||||
outline-offset: 2px;
|
||||
@@ -149,10 +148,10 @@ code {
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ *\
|
||||
Type scale — productive + expressive (Carbon)
|
||||
Type scale — productive + expressive
|
||||
\* ------------------------------------------------------------------ */
|
||||
.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);
|
||||
line-height: 1.07;
|
||||
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;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
background: var(--shell-bg);
|
||||
border-bottom: 1px solid var(--shell-border);
|
||||
}
|
||||
.cds-header__inner {
|
||||
.site-header__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-05);
|
||||
@@ -214,7 +213,7 @@ code {
|
||||
margin-inline: auto;
|
||||
padding-inline: var(--spacing-05);
|
||||
}
|
||||
.cds-header__name {
|
||||
.site-header__name {
|
||||
color: var(--shell-text);
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
@@ -222,37 +221,37 @@ code {
|
||||
text-decoration: none;
|
||||
border: 0;
|
||||
}
|
||||
.cds-header__name:hover {
|
||||
.site-header__name:hover {
|
||||
color: var(--shell-text);
|
||||
}
|
||||
.cds-header__name .mono {
|
||||
.site-header__name .mono {
|
||||
font-family: var(--font-mono);
|
||||
font-weight: 400;
|
||||
}
|
||||
.cds-header__tag {
|
||||
.site-header__tag {
|
||||
color: var(--shell-text-secondary);
|
||||
font-size: 0.8125rem;
|
||||
font-family: var(--font-mono);
|
||||
letter-spacing: 0;
|
||||
}
|
||||
.cds-header__nav {
|
||||
.site-header__nav {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: var(--spacing-06);
|
||||
}
|
||||
.cds-header__nav a {
|
||||
.site-header__nav a {
|
||||
color: var(--shell-text-secondary);
|
||||
font-size: 0.875rem;
|
||||
text-decoration: none;
|
||||
border: 0;
|
||||
padding-block: var(--spacing-02);
|
||||
}
|
||||
.cds-header__nav a:hover {
|
||||
.site-header__nav a:hover {
|
||||
color: var(--shell-text);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ *\
|
||||
Tag (Carbon tags are the one rounded element)
|
||||
Tag (the one rounded element)
|
||||
\* ------------------------------------------------------------------ */
|
||||
.tag {
|
||||
display: inline-flex;
|
||||
@@ -268,7 +267,7 @@ code {
|
||||
color: var(--text-secondary);
|
||||
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--teal { background: #9ef0f0; color: #005d5d; }
|
||||
.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 {
|
||||
display: inline-flex;
|
||||
@@ -335,7 +334,7 @@ code {
|
||||
.gallery {
|
||||
display: grid;
|
||||
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);
|
||||
border: 1px solid var(--border-subtle);
|
||||
margin-block: var(--spacing-09) var(--spacing-10);
|
||||
@@ -475,7 +474,7 @@ code {
|
||||
background: var(--support-success);
|
||||
}
|
||||
|
||||
/* build-time chart, on a fixed light Carbon canvas */
|
||||
/* build-time chart, on a fixed light canvas */
|
||||
.vega-chart {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -511,7 +510,7 @@ code {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* long-form prose — Carbon reading measure + productive type */
|
||||
/* long-form prose — reading measure + productive type */
|
||||
.prose {
|
||||
max-width: var(--measure);
|
||||
margin-top: var(--spacing-08);
|
||||
@@ -727,12 +726,12 @@ a.canon__work {
|
||||
/* ------------------------------------------------------------------ *\
|
||||
Footer + colophon
|
||||
\* ------------------------------------------------------------------ */
|
||||
.cds-footer {
|
||||
.site-footer {
|
||||
background: var(--layer-01);
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
margin-top: var(--spacing-12);
|
||||
}
|
||||
.cds-footer__inner {
|
||||
.site-footer__inner {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-05) var(--spacing-09);
|
||||
@@ -740,13 +739,13 @@ a.canon__work {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.cds-footer__col strong {
|
||||
.site-footer__col strong {
|
||||
display: block;
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
margin-bottom: var(--spacing-03);
|
||||
}
|
||||
.cds-footer__col p {
|
||||
.site-footer__col p {
|
||||
margin: 0;
|
||||
max-width: 22rem;
|
||||
line-height: 1.5;
|
||||
|
||||
Reference in New Issue
Block a user