Files
chart-sins/src/content.config.ts
T
Claude e0d3f9f773 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
2026-08-05 13:21:07 +00:00

36 lines
1.5 KiB
TypeScript

import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
// Each "sin" is one Markdown/MDX file in src/content/sins/.
// Charts are authored as Vega-Lite JSON specs in src/charts/ and referenced
// here by their basename (without the .json extension). The page template
// renders `badChart` and `fixedChart` side by side automatically.
const sins = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/sins' }),
schema: z.object({
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.
severity: z.number().int().min(1).max(5),
tags: z.array(z.string()).default([]),
// Basenames of Vega-Lite specs in src/charts/ (without .json).
badChart: z.string(),
fixedChart: z.string(),
// Optional publish date; unset entries sort last.
date: z.coerce.date().optional(),
draft: z.boolean().default(false),
}),
});
export const collections = { sins };