diff --git a/CLAUDE.md b/CLAUDE.md index d06f934..a5e9fcb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -88,6 +88,14 @@ Each sin page follows one shape: **poke → proof → why → the fix → receip `badChart`/`fixedChart` are the spec basenames (no `.json`). `citations[].key` must exist in `src/lib/references.ts` (build throws otherwise). 3. New reference? Add it once to `src/lib/references.ts`, then cite its key. + Optional frontmatter worth knowing about: + - `wild[]` — real specimens of the sin, rendered above the citations. Always + `credit` the original and link `sourceUrl` where we found it. A chart that + commits two sins goes on both pages with a different `note` on each. + - `related[]` — slugs of neighbouring sins; validated at build, so a typo or + a renamed slug fails the build rather than shipping a dead link. + - `compareNote` — overrides "Same numbers, both charts," which is wrong on a + sin whose whole point is an omission. 4. `npm run dev` and check. The OG card for the sin is generated automatically. ## Design & rendering diff --git a/docs/content-backlog.md b/docs/content-backlog.md index 8576dc2..c141739 100644 --- a/docs/content-backlog.md +++ b/docs/content-backlog.md @@ -17,7 +17,7 @@ runs 2–5 on purpose. ## Sources in play -`src/lib/references.ts` currently holds 24 entries, 20 of them cited by the five +`src/lib/references.ts` currently holds 24 entries, 21 of them cited by the 6 published sins. This table is a **convenience, not a scoreboard** — it's here so you can spot a source that fits a sin you're drafting, not so we can drive the right-hand column to zero blanks. An uncited reference is not a debt. @@ -29,26 +29,26 @@ seven sources, give it seven and add whatever's missing to the file. | --- | --- | | `ftvisvocab` | — | | `healy` | — | -| `munzner` | — | | `schwabish` | — | | `fewdualaxes` | dual-axis-correlation | -| `fewpies` | too-many-pie-slices | | `huff` | truncated-y-axis | | `isenberg2011` | dual-axis-correlation | +| `munzner` | incomplete-pie | | `shapeparameter` | aspect-ratio | | `showmenumbers` | too-many-pie-slices | | `spurious` | dual-axis-correlation | | `swd` | dual-axis-correlation | | `tufte` | truncated-y-axis | -| `vizwtf` | inverted-y-axis | | `callingbullshit` | inverted-y-axis, truncated-y-axis | -| `datatoviz` | dual-axis-correlation, too-many-pie-slices | -| `datawrapper` | dual-axis-correlation, too-many-pie-slices | | `eagereyes` | aspect-ratio, too-many-pie-slices | +| `fewpies` | incomplete-pie, too-many-pie-slices | | `junkcharts` | inverted-y-axis, too-many-pie-slices | | `truthfulart` | aspect-ratio, dual-axis-correlation | +| `vizwtf` | incomplete-pie, inverted-y-axis | | `wilke` | too-many-pie-slices, truncated-y-axis | | `clevelandmcgill` | aspect-ratio, too-many-pie-slices, truncated-y-axis | +| `datatoviz` | dual-axis-correlation, incomplete-pie, too-many-pie-slices | +| `datawrapper` | dual-axis-correlation, incomplete-pie, too-many-pie-slices | | `economistmistakes` | dual-axis-correlation, inverted-y-axis, truncated-y-axis | | `howchartslie` | aspect-ratio, dual-axis-correlation, inverted-y-axis, truncated-y-axis | @@ -74,7 +74,32 @@ we can build. **Citations:** `huff` (the original con), `callingbullshit`, `howchartslie`, `junkcharts`. -### 2. Pie Slices That Sum to More Than 100% — *Impossible Wholes*, severity 5 +### 2. Results From a Hand-Picked Subgroup — *Deceptive Framing*, severity 4 + +**Poke:** "You didn't pick the wrong chart, you picked the wrong people. The +number is real; the group it describes was chosen after the fact." + +The population-shaped twin of entry 1. Rather than trimming the time window, you +report the slice of the audience, region or segment where the number flatters — +often labelled honestly, which is what makes it feel above board. + +The published `incomplete-pie` specimen is **also an example of this one**: it +reports the primary "in the countryside," excluding the capital, on a graphic +whose banner argues the capital can't win. Reuse the same image here with a note +about this failure mode — that's what the `wild` array is for, and it's the case +for keeping these as two pages rather than one. + +Worth its own page rather than folding into entry 1: the tell is different (no +axis to inspect — you have to know what got left out), and the fix is different +(report the whole population, or every subgroup side by side, not just the +flattering one). + +**Charts:** bad = one subgroup's result. Fixed = every subgroup plus the overall +figure, same underlying data. + +**Citations:** `callingbullshit`, `howchartslie`, `huff`, `vizwtf`. + +### 3. Pie Slices That Sum to More Than 100% — *Impossible Wholes*, severity 5 **Poke:** "Your slices add up to 180%. A pie has one job — dividing a whole — and this data has no whole to divide." @@ -97,7 +122,7 @@ pie-chart post says "one pie chart can only show one total and its shares," and it does *not* separately argue that parts must be mutually exclusive. Don't stretch it further than that. Plus `datatoviz`, `junkcharts`. -### 3. A Pie Chart of Rates or Averages — *Impossible Wholes*, severity 5 +### 4. A Pie Chart of Rates or Averages — *Impossible Wholes*, severity 5 **Poke:** "You made a pie out of averages. Adding them together produces a number that means nothing, and that number is your denominator." @@ -120,7 +145,7 @@ same averages, plus the volume behind each one so the weighting is visible. or it is nothing), `datawrapper` (same one-total premise as above), `schwabish`, `ftvisvocab`. -### 4. Group Averages Shown Without the Distribution — *Hiding the Data*, severity 4 +### 5. Group Averages Shown Without the Distribution — *Hiding the Data*, severity 4 **Poke:** "Four bars, four averages, and no idea whether the difference is real." @@ -133,7 +158,7 @@ observations, means still marked. **Citations:** `wilke`, `healy`, `schwabish`, `datatoviz`. -### 5. Bubbles Sized by Radius Instead of Area — *Perceptual Traps*, severity 4 +### 6. Bubbles Sized by Radius Instead of Area — *Perceptual Traps*, severity 4 **Poke:** "You doubled the number and quadrupled the ink." @@ -148,7 +173,7 @@ position does the work. **Citations:** `munzner`, `clevelandmcgill`, `tufte`, `howchartslie`. -### 6. Stacked Segments Without a Shared Baseline — *Perceptual Traps*, severity 3 +### 7. Stacked Segments Without a Shared Baseline — *Perceptual Traps*, severity 3 **Poke:** "Only the bottom band of a stacked chart has a straight edge to measure against. The rest are guesses." @@ -175,7 +200,7 @@ out to its own zero baseline), totals preserved. **Citations:** `datawrapper`, `wilke`, `munzner`, `ftvisvocab`. -### 7. Categories Sorted Alphabetically Instead of by Value — *Sloppy Craft*, severity 2 +### 8. Categories Sorted Alphabetically Instead of by Value — *Sloppy Craft*, severity 2 **Poke:** "Your categories are sorted by name. Nobody wants to know which region starts with A." @@ -210,6 +235,16 @@ rediscovering them: too: the fixed chart needs a sequential ramp validated on `--chart-canvas` in both themes. Don't draft this one until a real source is in hand. +## Two new levers, as of the first wild specimen + +- **`wild`** — real examples, credited and linked, rendered above the citations. + Zero, one or many per sin. A specimen that commits two sins belongs on both + pages, with a different note on each; that's a reason to split sins, not merge + them. +- **`related`** — cross-links between sins, validated at build. Charts rarely + break one rule at a time, and the person you sent a link to may need the + neighbouring page more than the one they landed on. + ## Conventions reminder Note what the two pie entries above do and don't say. Neither condemns the pie diff --git a/docs/source-requests.md b/docs/source-requests.md index 9acfac4..7892633 100644 --- a/docs/source-requests.md +++ b/docs/source-requests.md @@ -37,6 +37,8 @@ Two standing rules for step 3: | 11 | Robert Kosara, *Understanding Pie Charts* (eagereyes) | URL, and what his experiments actually found. We lean on this to justify not condemning pie charts outright (claim V7). | | 12 | The Economist, "Mistakes, we've drawn a few" | **Canonical URL only** — the content arrived and is settled below, but `economistmistakes` still ships without a link. | +| 13 | `viz.wtf/post/664318320965795840/primary-election-result` | The post's caption and date. viz.wtf 403s automated fetches, so the specimen on `incomplete-pie` is credited from the logos legible in the image itself (Demokratikus Koalíció / Dobrev Klára) rather than from the post. The arithmetic and the measured arc angles are ours and don't depend on it. | + ## Claims to verify Live on the site, resting on recall rather than a source anyone opened. diff --git a/public/wild/hu-primary-countryside.jpg b/public/wild/hu-primary-countryside.jpg new file mode 100644 index 0000000..870e0f5 Binary files /dev/null and b/public/wild/hu-primary-countryside.jpg differ diff --git a/src/charts/incomplete-pie-bad.json b/src/charts/incomplete-pie-bad.json new file mode 100644 index 0000000..516ff42 --- /dev/null +++ b/src/charts/incomplete-pie-bad.json @@ -0,0 +1,24 @@ +{ + "title": "Vote Share by Candidate (%)", + "width": 300, + "height": 260, + "data": { + "values": [ + { "candidate": "Alvarez", "share": 38 }, + { "candidate": "Boone", "share": 22 }, + { "candidate": "Chen", "share": 19 }, + { "candidate": "Everyone else", "share": 21 } + ] + }, + "transform": [{ "filter": "datum.candidate !== 'Everyone else'" }], + "mark": { "type": "arc" }, + "encoding": { + "theta": { "field": "share", "type": "quantitative" }, + "color": { + "field": "candidate", + "type": "nominal", + "sort": ["Alvarez", "Boone", "Chen", "Everyone else"], + "legend": { "title": "Candidate" } + } + } +} diff --git a/src/charts/incomplete-pie-fixed.json b/src/charts/incomplete-pie-fixed.json new file mode 100644 index 0000000..ac1822c --- /dev/null +++ b/src/charts/incomplete-pie-fixed.json @@ -0,0 +1,23 @@ +{ + "title": "Vote Share by Candidate (%)", + "width": 300, + "height": 260, + "data": { + "values": [ + { "candidate": "Alvarez", "share": 38 }, + { "candidate": "Boone", "share": 22 }, + { "candidate": "Chen", "share": 19 }, + { "candidate": "Everyone else", "share": 21 } + ] + }, + "mark": { "type": "arc" }, + "encoding": { + "theta": { "field": "share", "type": "quantitative" }, + "color": { + "field": "candidate", + "type": "nominal", + "sort": ["Alvarez", "Boone", "Chen", "Everyone else"], + "legend": { "title": "Candidate" } + } + } +} diff --git a/src/components/RelatedSins.astro b/src/components/RelatedSins.astro new file mode 100644 index 0000000..74d22c0 --- /dev/null +++ b/src/components/RelatedSins.astro @@ -0,0 +1,36 @@ +--- +// "While you're here" — the other sins a chart like this usually commits. +// Charts rarely break one rule at a time, and the person you sent this to may +// need the neighbouring page more than this one. Renders nothing when empty. +import { href } from '../lib/url'; +import { severityUnits } from '../lib/charts'; + +interface Props { + sins: { id: string; data: { title: string; summary: string; severity: number } }[]; +} + +const { sins } = Astro.props; +--- + +{ + sins.length > 0 && ( + + ) +} diff --git a/src/components/WildExamples.astro b/src/components/WildExamples.astro new file mode 100644 index 0000000..6697bde --- /dev/null +++ b/src/components/WildExamples.astro @@ -0,0 +1,65 @@ +--- +// Real specimens of a sin, published by someone else. Sits between the +// explanation and the citations: first the argument, then proof it happens, +// then the sources. Renders nothing when a sin has no examples yet. +import { href } from '../lib/url'; + +interface WildExample { + caption: string; + note: string; + image?: string; + alt?: string; + credit: string; + sourceUrl?: string; + sourceLabel?: string; +} + +interface Props { + examples: WildExample[]; +} + +const { examples } = Astro.props; +--- + +{ + examples.length > 0 && ( +
+

Caught in the wild

+

+ {examples.length === 1 + ? 'Not a mock-up. Someone published this.' + : 'Not mock-ups. Someone published each of these.'} +

+
    + {examples.map((ex) => ( +
  1. + {ex.image && ( +
    + {ex.alt +
    + )} +

    {ex.caption}

    +

    {ex.note}

    +

    + {ex.credit} + {ex.sourceUrl && ( + <> + {' · '} + + {ex.sourceLabel ?? 'source'} + + + )} +

    +
  2. + ))} +
+
+ ) +} diff --git a/src/content.config.ts b/src/content.config.ts index 3fde162..e04d2c4 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -27,6 +27,29 @@ const sins = defineCollection({ // Basenames of Vega-Lite specs in src/charts/ (without .json). badChart: z.string(), fixedChart: z.string(), + // Overrides the line under the before/after pair. Default asserts the two + // charts share one dataset; set it when the sin *is* an omission and that + // wording would be wrong. + compareNote: z.string().optional(), + // Real specimens of this sin, published by someone else and found in the + // wild. Zero, one or many. Rendered above the citations. Always credit the + // original and link where it was collected when we can. + wild: z + .array( + z.object({ + caption: z.string(), + note: z.string(), + image: z.string().optional(), + alt: z.string().optional(), + credit: z.string(), + sourceUrl: z.string().optional(), + sourceLabel: z.string().optional(), + }), + ) + .default([]), + // Slugs of other sins worth reading next — a chart usually commits more + // than one. Validated at build; an unknown slug throws. + related: z.array(z.string()).default([]), // Optional publish date; unset entries sort last. date: z.coerce.date().optional(), draft: z.boolean().default(false), diff --git a/src/content/sins/aspect-ratio.md b/src/content/sins/aspect-ratio.md index c429093..78566cb 100644 --- a/src/content/sins/aspect-ratio.md +++ b/src/content/sins/aspect-ratio.md @@ -8,6 +8,9 @@ tags: ["line chart", "aspect ratio", "exaggeration"] badChart: "aspect-ratio-bad" fixedChart: "aspect-ratio-fixed" date: 2026-08-05 +related: + - truncated-y-axis + - dual-axis-correlation citations: - key: shapeparameter note: "The paper that made this measurable: a graph's width-to-height ratio changes how slopes are judged, and gave us “bank the average slope toward 45°.”" diff --git a/src/content/sins/dual-axis-correlation.md b/src/content/sins/dual-axis-correlation.md index d688829..c6cd222 100644 --- a/src/content/sins/dual-axis-correlation.md +++ b/src/content/sins/dual-axis-correlation.md @@ -8,6 +8,9 @@ tags: ["dual axis", "correlation", "line chart"] badChart: "dual-axis-bad" fixedChart: "dual-axis-fixed" date: 2026-08-05 +related: + - aspect-ratio + - truncated-y-axis citations: - key: datatoviz note: "Lists “using two Y axes” as a top caveat — a way to manipulate the story." diff --git a/src/content/sins/incomplete-pie.md b/src/content/sins/incomplete-pie.md new file mode 100644 index 0000000..340f397 --- /dev/null +++ b/src/content/sins/incomplete-pie.md @@ -0,0 +1,80 @@ +--- +title: "Pie Slices That Don't Sum to the Whole" +summary: "Four-fifths of a result, drawn as if it were all of it — so every share on the chart comes out too big." +poke: "Your slices don't add up to the whole, but they fill the circle anyway. Every number on that chart is bigger than the truth." +category: "Impossible Wholes" +severity: 5 +tags: ["pie chart", "part-to-whole", "share"] +badChart: "incomplete-pie-bad" +fixedChart: "incomplete-pie-fixed" +compareNote: "One set of results in both charts. The sinful one just leaves a slice out." +date: 2026-08-05 +wild: + - caption: "Hungarian opposition primary, 2021 — results “in the countryside.”" + note: "The three labelled slices read 38%, 22% and 19%: 79% in all. The remaining 21% — every other candidate — isn't drawn, and the circle is full anyway, so each share was quietly scaled up by a factor of about 1.27. Measuring the image, the 38% slice occupies 173.6° of arc, which is 48.3% of the pie. The graphic argues its own candidate is the one who can win outside the capital, and it draws her a whisker short of half the vote when she polled 38%." + image: "hu-primary-countryside.jpg" + alt: "Pie chart headed AZ ELLENZÉKI ELŐVÁLASZTÁS VÉGEREDMÉNYE VIDÉKEN, with three slices labelled Dobrev Klára 38%, Karácsony Gergely 22%, and Márki-Zay Péter 19%, filling the whole circle." + credit: "Campaign graphic by Demokratikus Koalíció / Dobrev Klára" + sourceLabel: "collected by WTF Visualizations" + sourceUrl: "https://viz.wtf/post/664318320965795840/primary-election-result" +related: + - too-many-pie-slices +citations: + - key: datawrapper + note: "Their pie-chart guidance starts from the premise this chart breaks: “one pie chart can only show one total and its shares.”" + - key: munzner + note: "Part-to-whole is a composition of a whole. Remove part of the whole and the encoding no longer means anything." + - key: fewpies + note: "Few's case against the pie, which lands hardest when the circle isn't even a real total." + - key: vizwtf + note: "Where the specimen above was collected — the archive exists because this ships to real audiences." + - key: datatoviz + note: "Its pie-chart caveats cover the arithmetic a pie quietly assumes." +--- + +## The sin + +A pie chart makes exactly one promise: the circle is everything, and each wedge +is its share of that everything. Every reading depends on it. "Nearly half" +means nearly half *of the total* — otherwise the phrase is empty. + +So when the parts don't add up to the whole, the tool has two options, and both +are bad. Either it leaves a gap in the circle — which nobody ships — or it +scales the parts up until they close it. Charting libraries do the second +silently, because they compute each angle as a fraction of *the values you +gave them*, not of the total you meant. + +Above, three candidates hold 38%, 22% and 19% of the vote. That's 79%. The +remaining 21% went to people not shown, and dropping them doesn't shrink the +chart — it inflates everyone left in it. The leading candidate's 38% is drawn as +48% of the circle. + +## Why it deceives + +Nothing signals the omission. The circle is closed, the percentages are printed +correctly next to the slices, and the labels and the geometry disagree with each +other in a way almost nobody checks — because checking means adding three +numbers and noticing they don't reach 100. + +The reader is doing what the chart asked: comparing areas. The areas are wrong. +And because every slice is inflated by the same factor, the chart stays +internally consistent — it looks exactly like a well-formed pie right up until +you do the arithmetic. + +This is worse than a hard-to-read chart. A crowded pie makes you guess; this one +answers confidently and wrong. + +## The repentance + +Include the whole whole. If the small categories are too numerous or too dull to +list, that's what an **"Other" wedge** is for — it costs one slice and one label, +and it makes every other slice true. The fixed chart above does nothing more +clever than that. + +If you can't account for the remainder — a poll with undecideds, a total that +genuinely isn't known — then you don't have a part-to-whole relationship, and a +pie is the wrong form. Use a bar chart, where each value stands on its own +baseline and nothing has to sum to anything. + +And if you're tempted to drop the remainder because it makes your number look +better: that's not a charting decision. diff --git a/src/content/sins/inverted-y-axis.md b/src/content/sins/inverted-y-axis.md index be59196..880b5f0 100644 --- a/src/content/sins/inverted-y-axis.md +++ b/src/content/sins/inverted-y-axis.md @@ -8,6 +8,9 @@ tags: ["axis", "area chart", "direction"] badChart: "inverted-axis-bad" fixedChart: "inverted-axis-fixed" date: 2026-08-05 +related: + - truncated-y-axis + - aspect-ratio citations: - key: callingbullshit note: "Their “Misleading Axes” module collects the axis manipulations, this one included." diff --git a/src/content/sins/too-many-pie-slices.md b/src/content/sins/too-many-pie-slices.md index bfe54e3..53ea0dc 100644 --- a/src/content/sins/too-many-pie-slices.md +++ b/src/content/sins/too-many-pie-slices.md @@ -8,6 +8,8 @@ tags: ["pie chart", "part-to-whole", "comparison"] badChart: "pie-overload-bad" fixedChart: "pie-overload-fixed" date: 2026-08-05 +related: + - incomplete-pie citations: - key: fewpies note: "“Save the Pies for Dessert” — the definitive, free takedown of the overloaded pie." diff --git a/src/content/sins/truncated-y-axis.md b/src/content/sins/truncated-y-axis.md index c868add..884ccf6 100644 --- a/src/content/sins/truncated-y-axis.md +++ b/src/content/sins/truncated-y-axis.md @@ -8,6 +8,9 @@ tags: ["bar chart", "axis", "exaggeration"] badChart: "truncated-axis-bad" fixedChart: "truncated-axis-fixed" date: 2026-08-05 +related: + - aspect-ratio + - inverted-y-axis citations: - key: callingbullshit note: "Their “Misleading Axes” module is the standard explainer for exactly this move." diff --git a/src/pages/sins/[...slug].astro b/src/pages/sins/[...slug].astro index 3ce0faf..ed31ce2 100644 --- a/src/pages/sins/[...slug].astro +++ b/src/pages/sins/[...slug].astro @@ -2,6 +2,8 @@ import { getCollection, render } from 'astro:content'; import BaseLayout from '../../layouts/BaseLayout.astro'; import VegaChart from '../../components/VegaChart.astro'; +import WildExamples from '../../components/WildExamples.astro'; +import RelatedSins from '../../components/RelatedSins.astro'; import Citations from '../../components/Citations.astro'; import ShareLink from '../../components/ShareLink.astro'; import { getChartSpec, severityUnits, tagModifier } from '../../lib/charts'; @@ -11,11 +13,23 @@ export async function getStaticPaths() { const sins = await getCollection('sins', ({ data }) => !data.draft); return sins.map((sin) => ({ params: { slug: sin.id }, - props: { sin }, + props: { sin, all: sins }, })); } -const { sin } = Astro.props; +const { sin, all } = Astro.props; + +// Resolve `related` slugs against the collection, failing the build on a typo +// or a slug that was renamed out from under us. +const related = sin.data.related.map((slug) => { + const target = all.find((s) => s.id === slug); + if (!target) { + throw new Error( + `Sin "${sin.id}" lists related sin "${slug}", which doesn't exist. Known: ${all.map((s) => s.id).join(', ')}`, + ); + } + return target; +}); const { Content } = await render(sin); const badSpec = getChartSpec(sin.data.badChart); @@ -59,14 +73,18 @@ const ogImage = new URL(href(`og/${sin.id}.png`), Astro.site).toString(); -

Same numbers, both charts. Only the design changed.

+

{sin.data.compareNote ?? 'Same numbers, both charts. Only the design changed.'}

+ + + +

diff --git a/src/styles/global.css b/src/styles/global.css index 1e49447..c659dbb 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -675,6 +675,116 @@ a.receipt__work { font-size: 0.875rem; } +/* often found together — cross-links between sins */ +.related { + margin-top: var(--spacing-09); + padding-top: var(--spacing-07); + border-top: 1px solid var(--border-subtle); +} +.related__title { + font-size: 1.5rem; + font-weight: 400; + margin: 0 0 var(--spacing-05); + letter-spacing: -0.01em; +} +.related__list { + list-style: none; + margin: 0; + padding: 0; + display: grid; + gap: 1px; + background: var(--border-subtle); + border: 1px solid var(--border-subtle); +} +.related__item { + background: var(--layer-01); + padding: var(--spacing-05) var(--spacing-06); + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--spacing-05); +} +.related__link { + text-decoration: none; + display: grid; + gap: var(--spacing-02); +} +.related__name { + font-weight: 600; + color: var(--link-primary); +} +.related__link:hover .related__name { + text-decoration: underline; +} +.related__summary { + font-size: 0.875rem; + color: var(--text-secondary); +} +.related__sev { + display: inline-flex; + gap: 3px; + flex: none; +} +.related__unit { + width: 6px; + height: 14px; + background: var(--border-subtle); +} +.related__unit--on { + background: var(--support-error); +} + +/* caught in the wild — real specimens */ +.wild { + margin-top: var(--spacing-09); + padding-top: var(--spacing-07); + border-top: 1px solid var(--border-subtle); +} +.wild__title { + font-size: 1.5rem; + font-weight: 400; + margin: 0; + letter-spacing: -0.01em; +} +.wild__lede { + color: var(--text-secondary); + margin: var(--spacing-03) 0 var(--spacing-06); +} +.wild__list { + list-style: none; + margin: 0; + padding: 0; + display: grid; + gap: var(--spacing-06); +} +.wild__item { + background: var(--layer-01); + border: 1px solid var(--border-subtle); + padding: var(--spacing-05) var(--spacing-06) var(--spacing-06); +} +.wild__figure { + margin: 0 0 var(--spacing-05); + background: var(--chart-canvas); +} +.wild__img { + display: block; + width: 100%; + height: auto; +} +.wild__caption { + margin: 0 0 var(--spacing-03); + font-weight: 600; +} +.wild__note { + margin: 0 0 var(--spacing-04); + color: var(--text-secondary); +} +.wild__credit { + margin: 0; + font-size: 0.75rem; + color: var(--text-helper); +} + /* references page */ .refs { padding-block: var(--spacing-08) var(--spacing-10);