Add real-world specimens and cross-links between sins

Two new levers on a sin page. `wild[]` renders credited, linked
specimens between the explanation and the citations — first the
argument, then proof people actually publish this, then the sources.
`related[]` cross-links neighbouring sins and is resolved at build, so a
typo fails the build instead of shipping a dead link.

Both arrive with the first specimen: a Hungarian primary graphic whose
three slices read 38/22/19 — 79% — drawn as a full circle, so every
share is inflated by about 1.27x. Measured off the image, the 38% slice
occupies 173.6 degrees, or 48.3% of the pie.

Our published pie sin is about too many slices, so it didn't cover this;
"Pie Slices That Don't Sum to the Whole" is a new severity-5 page whose
before/after is two pies differing only in whether the remainder is
present. That sin is an omission, so `compareNote` replaces the standard
"same numbers, both charts" line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ULE5RRxdQE1ebwefEd2eCM
This commit is contained in:
Claude
2026-08-05 20:45:23 +00:00
parent 444b89660f
commit 673f265398
17 changed files with 453 additions and 15 deletions
+8
View File
@@ -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
+47 -12
View File
@@ -17,7 +17,7 @@ runs 25 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
+2
View File
@@ -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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

+24
View File
@@ -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" }
}
}
}
+23
View File
@@ -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" }
}
}
}
+36
View File
@@ -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 && (
<nav class="related" aria-labelledby="related-h">
<h2 id="related-h" class="related__title">Often found together</h2>
<ul class="related__list">
{sins.map((s) => (
<li class="related__item">
<a class="related__link" href={href(`sins/${s.id}/`)}>
<span class="related__name">{s.data.title}</span>
<span class="related__summary">{s.data.summary}</span>
</a>
<span class="related__sev" aria-label={`Severity ${s.data.severity} of 5`}>
{severityUnits(s.data.severity).map((on) => (
<i class={`related__unit${on ? ' related__unit--on' : ''}`} />
))}
</span>
</li>
))}
</ul>
</nav>
)
}
+65
View File
@@ -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 && (
<section class="wild" aria-labelledby="wild-h">
<h2 id="wild-h" class="wild__title">Caught in the wild</h2>
<p class="wild__lede">
{examples.length === 1
? 'Not a mock-up. Someone published this.'
: 'Not mock-ups. Someone published each of these.'}
</p>
<ol class="wild__list">
{examples.map((ex) => (
<li class="wild__item">
{ex.image && (
<figure class="wild__figure">
<img
class="wild__img"
src={href(`wild/${ex.image}`)}
alt={ex.alt ?? ex.caption}
loading="lazy"
decoding="async"
/>
</figure>
)}
<p class="wild__caption">{ex.caption}</p>
<p class="wild__note">{ex.note}</p>
<p class="wild__credit">
<span>{ex.credit}</span>
{ex.sourceUrl && (
<>
{' · '}
<a href={ex.sourceUrl} rel="noopener">
{ex.sourceLabel ?? 'source'}
</a>
</>
)}
</p>
</li>
))}
</ol>
</section>
)
}
+23
View File
@@ -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),
+3
View File
@@ -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°.”"
@@ -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."
+80
View File
@@ -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.
+3
View File
@@ -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."
+2
View File
@@ -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."
+3
View File
@@ -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."
+21 -3
View File
@@ -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();
<VegaChart spec={fixedSpec} title={`Honest chart: ${sin.data.title}`} />
</div>
</div>
<p class="compare__note">Same numbers, both charts. Only the design changed.</p>
<p class="compare__note">{sin.data.compareNote ?? 'Same numbers, both charts. Only the design changed.'}</p>
<div class="prose">
<Content />
</div>
<WildExamples examples={sin.data.wild} />
<Citations citations={sin.data.citations} />
<RelatedSins sins={related} />
<ShareLink url={pageUrl} />
<p class="more-refs">
+110
View File
@@ -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);