mirror of
https://github.com/olehomelchenko/chart-sins.git
synced 2026-08-08 02:22:43 +00:00
Reframe as a linkable, cited callout ("nohello for charts")
Make each sin a self-contained page you send instead of re-explaining: - Per-sin "poke" one-liner (the shareable callout) - Citations system: a canonical bibliography (src/lib/references.ts) of the field's giants — Munzner, Knaflic, Tufte, Cairo, Few, Cleveland & McGill, Wilke, Healy, Calling Bullshit, data-to-viz, Junk Charts, Spurious Correlations, FT Visual Vocabulary — cited per sin with a relevance note - "Don't take our word for it" receipts block on each sin - /canon page: the full annotated bibliography - Share control: copy-link "send them the receipt" (progressive enhancement) - Restructured sin page: poke -> proof -> why -> receipts -> share - Header nav (Sins / Canon); homepage reframed to the "send the link" mission Also fix the truncated-axis bad chart: clamp the [90,100] domain so the bar baseline doesn't overflow thousands of pixels off-canvas. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XPEvmMbac2fCvKXpQcovj8
This commit is contained in:
@@ -23,7 +23,7 @@
|
||||
"y": {
|
||||
"field": "revenue",
|
||||
"type": "quantitative",
|
||||
"scale": { "domain": [90, 100] },
|
||||
"scale": { "domain": [90, 100], "zero": false, "clamp": true },
|
||||
"title": "Revenue ($M)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
import { getReference, citeLabel } from '../lib/references';
|
||||
|
||||
interface Props {
|
||||
citations: { key: string; note?: string }[];
|
||||
}
|
||||
const { citations } = Astro.props;
|
||||
const resolved = citations.map((c) => ({ ...c, ref: getReference(c.key) }));
|
||||
---
|
||||
|
||||
{
|
||||
resolved.length > 0 && (
|
||||
<section class="receipts" aria-labelledby="receipts-h">
|
||||
<h2 id="receipts-h" class="receipts__title">Don’t take our word for it</h2>
|
||||
<p class="receipts__lede">The giants who say the same thing, so you don’t have to argue:</p>
|
||||
<ol class="receipts__list">
|
||||
{resolved.map(({ ref, note }) => (
|
||||
<li class="receipt">
|
||||
<div class="receipt__cite">
|
||||
{ref.url ? (
|
||||
<a href={ref.url} rel="noopener" class="receipt__work">
|
||||
{citeLabel(ref)}
|
||||
</a>
|
||||
) : (
|
||||
<span class="receipt__work">{citeLabel(ref)}</span>
|
||||
)}
|
||||
<span class={`receipt__kind receipt__kind--${ref.kind}`}>{ref.kind}</span>
|
||||
{ref.free && <span class="receipt__free">free online</span>}
|
||||
</div>
|
||||
{note && <p class="receipt__note">{note}</p>}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
// The "send this to them" mechanic — the whole point of the site.
|
||||
// Progressive enhancement: the URL is always visible and selectable; the
|
||||
// button just makes copying a one-click affair when JS is available.
|
||||
interface Props {
|
||||
url: string;
|
||||
}
|
||||
const { url } = Astro.props;
|
||||
---
|
||||
|
||||
<aside class="share" aria-label="Share this sin">
|
||||
<p class="share__prompt">Caught someone mid-sin? Send them the receipt:</p>
|
||||
<div class="share__row">
|
||||
<input class="share__url" type="text" readonly value={url} aria-label="Link to this page" />
|
||||
<button class="share__btn" type="button" data-url={url}>Copy link</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<script>
|
||||
for (const btn of document.querySelectorAll<HTMLButtonElement>('.share__btn')) {
|
||||
btn.addEventListener('click', async () => {
|
||||
const url = btn.dataset.url ?? '';
|
||||
const done = () => {
|
||||
const label = btn.textContent;
|
||||
btn.textContent = 'Copied ✓';
|
||||
btn.classList.add('is-copied');
|
||||
setTimeout(() => {
|
||||
btn.textContent = label;
|
||||
btn.classList.remove('is-copied');
|
||||
}, 1600);
|
||||
};
|
||||
try {
|
||||
await navigator.clipboard.writeText(url);
|
||||
done();
|
||||
} catch {
|
||||
const input = btn.previousElementSibling as HTMLInputElement | null;
|
||||
input?.select();
|
||||
document.execCommand('copy');
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -11,6 +11,13 @@ const sins = defineCollection({
|
||||
title: z.string(),
|
||||
// Short one-line summary shown on the index gallery.
|
||||
summary: z.string(),
|
||||
// The cheeky one-line callout — the thing you send the sinner.
|
||||
poke: z.string(),
|
||||
// Citations into the canon (src/lib/references.ts) with a per-sin note on
|
||||
// why that source is relevant. This is the "don't take our word for it".
|
||||
citations: z
|
||||
.array(z.object({ key: z.string(), note: z.string().optional() }))
|
||||
.default([]),
|
||||
// Grouping, e.g. "Misleading Scales", "Chart-Type Abuse".
|
||||
category: z.string(),
|
||||
// 1 (venial) … 5 (mortal). Drives the flame rating in the UI.
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
---
|
||||
title: "The Dual-Axis Deception"
|
||||
summary: "Two unrelated series on two independent axes, engineered to look correlated."
|
||||
poke: "Two y-axes, tuned until the lines kiss. Congratulations — you can now make anything 'cause' anything."
|
||||
category: "False Relationships"
|
||||
severity: 5
|
||||
tags: ["dual axis", "correlation", "line chart"]
|
||||
badChart: "dual-axis-bad"
|
||||
fixedChart: "dual-axis-fixed"
|
||||
date: 2026-08-05
|
||||
citations:
|
||||
- key: datatoviz
|
||||
note: "Lists “using two Y axes” as a top caveat — a way to manipulate the story."
|
||||
- key: spurious
|
||||
note: "Thousands of nonsense correlations: proof that “they move together” proves nothing."
|
||||
- key: howchartslie
|
||||
note: "Cairo on how aligned axes manufacture relationships that aren’t there."
|
||||
- key: swd
|
||||
note: "Knaflic’s standing advice: avoid the second axis; separate or index instead."
|
||||
---
|
||||
|
||||
## The sin
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
---
|
||||
title: "Pie Chart Overload"
|
||||
summary: "Eight near-identical slices no human eye can rank. A bar chart was right there."
|
||||
poke: "Eight slices, three of them the same size. Nobody can read this. A bar chart could."
|
||||
category: "Chart-Type Abuse"
|
||||
severity: 3
|
||||
tags: ["pie chart", "part-to-whole", "comparison"]
|
||||
badChart: "pie-overload-bad"
|
||||
fixedChart: "pie-overload-fixed"
|
||||
date: 2026-08-05
|
||||
citations:
|
||||
- key: fewpies
|
||||
note: "“Save the Pies for Dessert” — the definitive, free takedown of the overloaded pie."
|
||||
- key: clevelandmcgill
|
||||
note: "Their ranking shows angle and area are judged far worse than position along a scale."
|
||||
- key: junkcharts
|
||||
note: "Kaiser Fung’s running #onelesspie campaign exists for precisely this chart."
|
||||
- key: datatoviz
|
||||
note: "Pie charts are the first entry in its catalogue of caveats."
|
||||
---
|
||||
|
||||
## The sin
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
---
|
||||
title: "The Truncated Y-Axis"
|
||||
summary: "Chopping the baseline off a bar chart to make small differences look enormous."
|
||||
poke: "You started the y-axis at 90. That's not a bar chart — it's a magic trick."
|
||||
category: "Misleading Scales"
|
||||
severity: 4
|
||||
tags: ["bar chart", "axis", "exaggeration"]
|
||||
badChart: "truncated-axis-bad"
|
||||
fixedChart: "truncated-axis-fixed"
|
||||
date: 2026-08-05
|
||||
citations:
|
||||
- key: callingbullshit
|
||||
note: "Their “Misleading Axes” module is the canonical explainer for exactly this move."
|
||||
- key: clevelandmcgill
|
||||
note: "The perception research: readers compare bar length, so length must map to value from zero."
|
||||
- key: tufte
|
||||
note: "The “Lie Factor” puts an actual number on how much the graphic exaggerates."
|
||||
- key: howchartslie
|
||||
note: "Cairo devotes a chapter to scale manipulation like this one."
|
||||
---
|
||||
|
||||
## The sin
|
||||
|
||||
@@ -32,7 +32,10 @@ const { title, description = 'A catalogue of data-visualization sins, styled on
|
||||
<a class="cds-header__name" href={href()}>
|
||||
Chart Sins <span class="mono">/ honest data-viz</span>
|
||||
</a>
|
||||
<span class="cds-header__tag">built on Carbon</span>
|
||||
<nav class="cds-header__nav" aria-label="Primary">
|
||||
<a href={href()}>Sins</a>
|
||||
<a href={href('canon/')}>Canon</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
// The Canon — the shoulders of giants Chart Sins stands on.
|
||||
//
|
||||
// Each sin cites into this list by key, so a page can say "don't take our word
|
||||
// for it" and point at the authority that actually condemns the practice.
|
||||
// Add a reference once here; cite it from any number of sins.
|
||||
|
||||
export type RefKind = 'book' | 'site' | 'paper';
|
||||
|
||||
export interface Reference {
|
||||
authors: string;
|
||||
title: string;
|
||||
/** Publisher / journal / site, for books and papers. */
|
||||
work?: string;
|
||||
year?: number;
|
||||
url?: string;
|
||||
kind: RefKind;
|
||||
/** One line on why it belongs in the canon. */
|
||||
blurb: string;
|
||||
/** True for the free-to-read-online works, surfaced as a hint. */
|
||||
free?: boolean;
|
||||
}
|
||||
|
||||
export const REFERENCES = {
|
||||
// --- books ---
|
||||
munzner: {
|
||||
authors: 'Tamara Munzner',
|
||||
title: 'Visualization Analysis & Design',
|
||||
work: 'CRC Press',
|
||||
year: 2014,
|
||||
url: 'https://www.cs.ubc.ca/~tmm/vadbook/',
|
||||
kind: 'book',
|
||||
blurb: 'The rigorous framework: what channels the eye reads accurately, and why position beats angle, length, and area.',
|
||||
},
|
||||
swd: {
|
||||
authors: 'Cole Nussbaumer Knaflic',
|
||||
title: 'Storytelling with Data',
|
||||
work: 'Wiley',
|
||||
year: 2015,
|
||||
url: 'https://www.storytellingwithdata.com/books',
|
||||
kind: 'book',
|
||||
blurb: 'The practitioner’s bible on decluttering charts and directing attention honestly.',
|
||||
},
|
||||
tufte: {
|
||||
authors: 'Edward R. Tufte',
|
||||
title: 'The Visual Display of Quantitative Information',
|
||||
work: 'Graphics Press (2nd ed.)',
|
||||
year: 2001,
|
||||
url: 'https://www.edwardtufte.com/book/the-visual-display-of-quantitative-information/',
|
||||
kind: 'book',
|
||||
blurb: 'Origin of the “Lie Factor,” chartjunk, and the data-ink ratio.',
|
||||
},
|
||||
howchartslie: {
|
||||
authors: 'Alberto Cairo',
|
||||
title: 'How Charts Lie',
|
||||
work: 'W. W. Norton',
|
||||
year: 2019,
|
||||
url: 'http://www.thefunctionalart.com/',
|
||||
kind: 'book',
|
||||
blurb: 'A whole book on exactly this: the tricks charts use to mislead, and how to read past them.',
|
||||
},
|
||||
fewpies: {
|
||||
authors: 'Stephen Few',
|
||||
title: 'Save the Pies for Dessert',
|
||||
work: 'Perceptual Edge',
|
||||
year: 2007,
|
||||
url: 'https://www.perceptualedge.com/articles/visual_business_intelligence/save_the_pies_for_dessert.pdf',
|
||||
kind: 'paper',
|
||||
blurb: 'The definitive takedown of the pie chart, from the field’s most stubborn advocate for clarity.',
|
||||
free: true,
|
||||
},
|
||||
wilke: {
|
||||
authors: 'Claus O. Wilke',
|
||||
title: 'Fundamentals of Data Visualization',
|
||||
work: 'O’Reilly',
|
||||
year: 2019,
|
||||
url: 'https://clauswilke.com/dataviz/',
|
||||
kind: 'book',
|
||||
blurb: 'Free, thorough, and organized by problem — a per-topic reference for doing it right.',
|
||||
free: true,
|
||||
},
|
||||
healy: {
|
||||
authors: 'Kieran Healy',
|
||||
title: 'Data Visualization: A Practical Introduction',
|
||||
work: 'Princeton University Press',
|
||||
year: 2018,
|
||||
url: 'https://socviz.co/',
|
||||
kind: 'book',
|
||||
blurb: 'Free online; strong on why perception, not taste, decides what works.',
|
||||
free: true,
|
||||
},
|
||||
callingbullshit: {
|
||||
authors: 'Carl T. Bergstrom & Jevin D. West',
|
||||
title: 'Calling Bullshit',
|
||||
work: 'Random House',
|
||||
year: 2020,
|
||||
url: 'https://www.callingbullshit.org/tools/tools_misleading_axes.html',
|
||||
kind: 'book',
|
||||
blurb: 'Book and free course; the “Misleading Axes” module is required reading for axis crimes.',
|
||||
free: true,
|
||||
},
|
||||
huff: {
|
||||
authors: 'Darrell Huff',
|
||||
title: 'How to Lie with Statistics',
|
||||
work: 'W. W. Norton',
|
||||
year: 1954,
|
||||
kind: 'book',
|
||||
blurb: 'The 1954 classic that named the game; the truncated axis is right there in it.',
|
||||
},
|
||||
clevelandmcgill: {
|
||||
authors: 'William S. Cleveland & Robert McGill',
|
||||
title: 'Graphical Perception: Theory, Experimentation, and Application',
|
||||
work: 'Journal of the American Statistical Association',
|
||||
year: 1984,
|
||||
url: 'https://www.jstor.org/stable/2288400',
|
||||
kind: 'paper',
|
||||
blurb: 'The experiments proving people judge position accurately and angle/area poorly — the root of most of these sins.',
|
||||
},
|
||||
schwabish: {
|
||||
authors: 'Jonathan Schwabish',
|
||||
title: 'Better Data Visualizations',
|
||||
work: 'Columbia University Press',
|
||||
year: 2021,
|
||||
url: 'https://policyviz.com/books/',
|
||||
kind: 'book',
|
||||
blurb: 'A modern, chart-by-chart guide to which form fits which job.',
|
||||
},
|
||||
|
||||
// --- sites ---
|
||||
datatoviz: {
|
||||
authors: 'Yan Holtz & Conor Healy',
|
||||
title: 'From Data to Viz — Caveats',
|
||||
work: 'data-to-viz.com',
|
||||
url: 'https://www.data-to-viz.com/caveats.html',
|
||||
kind: 'site',
|
||||
blurb: 'A browsable catalogue of common dataviz mistakes, each with a fix.',
|
||||
free: true,
|
||||
},
|
||||
junkcharts: {
|
||||
authors: 'Kaiser Fung',
|
||||
title: 'Junk Charts',
|
||||
work: 'junkcharts.com',
|
||||
url: 'https://junkcharts.typepad.com/',
|
||||
kind: 'site',
|
||||
blurb: 'Fifteen years of chart critique-and-redesign, plus the #onelesspie campaign.',
|
||||
free: true,
|
||||
},
|
||||
spurious: {
|
||||
authors: 'Tyler Vigen',
|
||||
title: 'Spurious Correlations',
|
||||
work: 'tylervigen.com',
|
||||
url: 'https://www.tylervigen.com/spurious-correlations',
|
||||
kind: 'site',
|
||||
blurb: 'Thousands of nonsense correlations — the canonical proof that “they move together” proves nothing.',
|
||||
free: true,
|
||||
},
|
||||
ftvisvocab: {
|
||||
authors: 'Financial Times Visual Journalism',
|
||||
title: 'Visual Vocabulary',
|
||||
work: 'FT',
|
||||
url: 'https://github.com/Financial-Times/chart-doctor/tree/main/visual-vocabulary',
|
||||
kind: 'site',
|
||||
blurb: 'A cheat sheet mapping what you want to say to the chart that says it.',
|
||||
free: true,
|
||||
},
|
||||
} as const satisfies Record<string, Reference>;
|
||||
|
||||
export type ReferenceKey = keyof typeof REFERENCES;
|
||||
|
||||
/** Resolve a citation key; throws at build time if it isn't in the canon. */
|
||||
export function getReference(key: string): Reference {
|
||||
const ref = (REFERENCES as Record<string, Reference>)[key];
|
||||
if (!ref) {
|
||||
throw new Error(
|
||||
`Reference "${key}" is not in the canon (src/lib/references.ts). Known: ${Object.keys(REFERENCES).join(', ')}`,
|
||||
);
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
/** A short "Author, Title (Year)" label. */
|
||||
export function citeLabel(ref: Reference): string {
|
||||
return `${ref.authors}, ${ref.title}${ref.year ? ` (${ref.year})` : ''}`;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import { REFERENCES, citeLabel, type RefKind } from '../lib/references';
|
||||
|
||||
const groups: { kind: RefKind; heading: string; blurb: string }[] = [
|
||||
{ kind: 'book', heading: 'The books', blurb: 'The shelf every chart argument can lean on.' },
|
||||
{ kind: 'paper', heading: 'The papers & essays', blurb: 'The primary sources — where the evidence actually lives.' },
|
||||
{ kind: 'site', heading: 'The sites', blurb: 'Living references you can link straight into a thread.' },
|
||||
];
|
||||
|
||||
const byKind = (kind: RefKind) => Object.values(REFERENCES).filter((r) => r.kind === kind);
|
||||
---
|
||||
|
||||
<BaseLayout title="The Canon — Chart Sins" description="The books, papers, and sites Chart Sins stands on.">
|
||||
<section class="wrap hero">
|
||||
<p class="hero__eyebrow">// the shoulders we stand on</p>
|
||||
<h1 class="expressive-display">The Canon</h1>
|
||||
<p class="hero__lede">
|
||||
Every sin here cites its sources. This is the whole shelf in one place —
|
||||
the giants of data visualization who already made the case, so you can
|
||||
link the receipt instead of relitigating it.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div class="wrap canon">
|
||||
{
|
||||
groups.map((g) => (
|
||||
<section class="canon__group">
|
||||
<h2 class="canon__heading productive-heading-03">{g.heading}</h2>
|
||||
<p class="helper">{g.blurb}</p>
|
||||
<ul class="canon__list">
|
||||
{byKind(g.kind).map((ref) => (
|
||||
<li class="canon__item">
|
||||
<div class="canon__cite">
|
||||
{ref.url ? (
|
||||
<a href={ref.url} rel="noopener" class="canon__work">{citeLabel(ref)}</a>
|
||||
) : (
|
||||
<span class="canon__work">{citeLabel(ref)}</span>
|
||||
)}
|
||||
{ref.work && <span class="canon__pub">{ref.work}</span>}
|
||||
{ref.free && <span class="receipt__free">free online</span>}
|
||||
</div>
|
||||
<p class="canon__blurb">{ref.blurb}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</BaseLayout>
|
||||
@@ -13,11 +13,13 @@ const sins = (await getCollection('sins', ({ data }) => !data.draft)).sort((a, b
|
||||
|
||||
<BaseLayout title="Chart Sins">
|
||||
<section class="wrap hero">
|
||||
<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__eyebrow">// stop re-explaining the same chart mistake</p>
|
||||
<h1 class="expressive-display">Someone made a<br />misleading chart again.</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.
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import { getCollection, render } from 'astro:content';
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import VegaChart from '../../components/VegaChart.astro';
|
||||
import Citations from '../../components/Citations.astro';
|
||||
import ShareLink from '../../components/ShareLink.astro';
|
||||
import { getChartSpec, severityUnits, tagModifier } from '../../lib/charts';
|
||||
import { href } from '../../lib/url';
|
||||
|
||||
@@ -18,9 +20,12 @@ const { Content } = await render(sin);
|
||||
|
||||
const badSpec = getChartSpec(sin.data.badChart);
|
||||
const fixedSpec = getChartSpec(sin.data.fixedChart);
|
||||
|
||||
// Canonical, shareable URL for this sin — the thing you paste into a thread.
|
||||
const pageUrl = new URL(href(`sins/${sin.id}/`), Astro.site).toString();
|
||||
---
|
||||
|
||||
<BaseLayout title={`${sin.data.title} — Chart Sins`} description={sin.data.summary}>
|
||||
<BaseLayout title={`${sin.data.title} — Chart Sins`} description={sin.data.poke}>
|
||||
<article class="wrap sin">
|
||||
<nav class="breadcrumb"><a href={href()}>All sins</a> / {sin.data.title}</nav>
|
||||
|
||||
@@ -37,6 +42,7 @@ const fixedSpec = getChartSpec(sin.data.fixedChart);
|
||||
</span>
|
||||
</div>
|
||||
<h1 class="sin__title expressive-heading">{sin.data.title}</h1>
|
||||
<p class="sin__poke">{sin.data.poke}</p>
|
||||
<div class="sin__tags">
|
||||
{sin.data.tags.map((t) => <span class="tag">{t}</span>)}
|
||||
</div>
|
||||
@@ -52,9 +58,18 @@ const fixedSpec = getChartSpec(sin.data.fixedChart);
|
||||
<VegaChart spec={fixedSpec} title={`Honest chart: ${sin.data.title}`} />
|
||||
</div>
|
||||
</div>
|
||||
<p class="compare__note">Same numbers, both charts. Only the design changed.</p>
|
||||
|
||||
<div class="prose">
|
||||
<Content />
|
||||
</div>
|
||||
|
||||
<Citations citations={sin.data.citations} />
|
||||
|
||||
<ShareLink url={pageUrl} />
|
||||
|
||||
<p class="more-canon">
|
||||
Building a case? See the <a href={href('canon/')}>full canon →</a>
|
||||
</p>
|
||||
</article>
|
||||
</BaseLayout>
|
||||
|
||||
+201
-4
@@ -235,10 +235,20 @@ code {
|
||||
font-family: var(--font-mono);
|
||||
letter-spacing: 0;
|
||||
}
|
||||
@media (max-width: 40rem) {
|
||||
.cds-header__tag {
|
||||
display: none;
|
||||
}
|
||||
.cds-header__nav {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: var(--spacing-06);
|
||||
}
|
||||
.cds-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 {
|
||||
color: var(--shell-text);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ *\
|
||||
@@ -527,6 +537,193 @@ code {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* the poke — the shareable one-liner under the title */
|
||||
.sin__poke {
|
||||
font-size: 1.375rem;
|
||||
line-height: 1.35;
|
||||
font-weight: 300;
|
||||
color: var(--text-primary);
|
||||
max-width: 42rem;
|
||||
margin: var(--spacing-05) 0 0;
|
||||
}
|
||||
.compare__note {
|
||||
margin: calc(-1 * var(--spacing-05)) 0 0;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-helper);
|
||||
}
|
||||
|
||||
/* the receipts — citations block */
|
||||
.receipts {
|
||||
margin-top: var(--spacing-09);
|
||||
padding-top: var(--spacing-07);
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
}
|
||||
.receipts__title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 400;
|
||||
margin: 0;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.receipts__lede {
|
||||
color: var(--text-secondary);
|
||||
margin: var(--spacing-03) 0 var(--spacing-06);
|
||||
}
|
||||
.receipts__list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: 1px;
|
||||
background: var(--border-subtle);
|
||||
border: 1px solid var(--border-subtle);
|
||||
}
|
||||
.receipt {
|
||||
background: var(--layer-01);
|
||||
padding: var(--spacing-05) var(--spacing-06);
|
||||
}
|
||||
.receipt__cite {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-03);
|
||||
}
|
||||
.receipt__work {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
a.receipt__work {
|
||||
color: var(--link-primary);
|
||||
}
|
||||
.receipt__kind {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.6875rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--text-helper);
|
||||
border: 1px solid var(--border-subtle);
|
||||
padding: 0 var(--spacing-02);
|
||||
}
|
||||
.receipt__free {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.6875rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--support-success);
|
||||
}
|
||||
.receipt__note {
|
||||
margin: var(--spacing-03) 0 0;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9375rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* the share box */
|
||||
.share {
|
||||
margin-top: var(--spacing-08);
|
||||
padding: var(--spacing-06);
|
||||
background: var(--layer-01);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-left: 3px solid var(--interactive);
|
||||
}
|
||||
.share__prompt {
|
||||
margin: 0 0 var(--spacing-04);
|
||||
font-weight: 600;
|
||||
}
|
||||
.share__row {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
max-width: 34rem;
|
||||
}
|
||||
.share__url {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.8125rem;
|
||||
padding: var(--spacing-04) var(--spacing-05);
|
||||
background: var(--background);
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid var(--border-strong);
|
||||
border-right: 0;
|
||||
}
|
||||
.share__url:focus-visible {
|
||||
outline: 2px solid var(--focus);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
.share__btn {
|
||||
flex: 0 0 auto;
|
||||
font-family: var(--font-sans);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-on-color);
|
||||
background: var(--interactive);
|
||||
border: 1px solid var(--interactive);
|
||||
padding-inline: var(--spacing-05);
|
||||
cursor: pointer;
|
||||
transition: background var(--dur-fast) var(--ease-productive);
|
||||
}
|
||||
.share__btn:hover {
|
||||
background: var(--link-hover);
|
||||
}
|
||||
.share__btn.is-copied {
|
||||
background: var(--support-success);
|
||||
border-color: var(--support-success);
|
||||
}
|
||||
|
||||
.more-canon {
|
||||
margin-top: var(--spacing-07);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
/* canon page */
|
||||
.canon {
|
||||
padding-block: var(--spacing-08) var(--spacing-10);
|
||||
}
|
||||
.canon__group {
|
||||
margin-bottom: var(--spacing-09);
|
||||
}
|
||||
.canon__heading {
|
||||
margin: 0;
|
||||
}
|
||||
.canon__list {
|
||||
list-style: none;
|
||||
margin: var(--spacing-05) 0 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: 1px;
|
||||
background: var(--border-subtle);
|
||||
border: 1px solid var(--border-subtle);
|
||||
}
|
||||
.canon__item {
|
||||
background: var(--layer-01);
|
||||
padding: var(--spacing-05) var(--spacing-06);
|
||||
}
|
||||
.canon__cite {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-03);
|
||||
}
|
||||
.canon__work {
|
||||
font-weight: 600;
|
||||
font-size: 1.0625rem;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
a.canon__work {
|
||||
color: var(--link-primary);
|
||||
}
|
||||
.canon__pub {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-helper);
|
||||
}
|
||||
.canon__blurb {
|
||||
margin: var(--spacing-03) 0 0;
|
||||
color: var(--text-secondary);
|
||||
max-width: 44rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ *\
|
||||
Footer + colophon
|
||||
\* ------------------------------------------------------------------ */
|
||||
|
||||
Reference in New Issue
Block a user