Spec
Intent
Make the cost-equation argument visible in three seconds: AI drove the cost of generating code toward zero, but the cost of validating intent did not move. The crossover is when validation overtakes generation as the dominant cost. The hero exists to anchor every argument the framework makes about where leverage now lives.
Acceptance Criteria
[ ] SVG line chart with two series: generation (falling) and validation (flat)
[ ] X-axis labelled with eras (2010 / 2020 / 2025 / 2026)
[ ] Drift surface (red translucent fill) marks the region where generation < validation
[ ] Crossover point marked with a dot and dashed guide
[ ] "The bottleneck moved" callout pinned to the right side
[ ] Lines draw in via stroke-dasharray animation on entrance
[ ] Legend / axis labels are screen-reader friendly
[ ] Respects prefers-reduced-motion — paths render in final state under reduce
Constraints
Pure Astro + Tailwind + inline SVG, no external chart libs
Tokens only — pillar/danger backgrounds, no hex
≤ 250 lines body
// cost-equation-graph.astro copy
---
import type { CompendiumMeta } from '../../../data/types';
export const meta: CompendiumMeta = {
id: 104,
slug: 'cost-equation-graph',
name: 'Cost Equation Graph',
category: 'compositions',
subcategory: 'heroes',
description: 'An animated hero chart showing generation cost collapsing as AI matures while validation cost stays flat — the crossing point is the bottleneck moving, with a "the bottleneck moved" callout pinned to the drift surface.',
spec: `## Intent
Make the cost-equation argument visible in three seconds: AI drove the cost of generating code toward zero, but the cost of validating intent did not move. The crossover is when validation overtakes generation as the dominant cost. The hero exists to anchor every argument the framework makes about where leverage now lives.
## Acceptance Criteria
- [ ] SVG line chart with two series: generation (falling) and validation (flat)
- [ ] X-axis labelled with eras (2010 / 2020 / 2025 / 2026)
- [ ] Drift surface (red translucent fill) marks the region where generation < validation
- [ ] Crossover point marked with a dot and dashed guide
- [ ] "The bottleneck moved" callout pinned to the right side
- [ ] Lines draw in via stroke-dasharray animation on entrance
- [ ] Legend / axis labels are screen-reader friendly
- [ ] Respects prefers-reduced-motion — paths render in final state under reduce
## Constraints
- Pure Astro + Tailwind + inline SVG, no external chart libs
- Tokens only — pillar/danger backgrounds, no hex
- ≤ 250 lines body`,
tags: ['real-content', 'animated', 'reduced-motion', 'chart', 'svg', 'responsive'],
status: 'stable',
kind: 'composition',
aspect: 'full',
health: { ics: 100, a11y: 'aa', themed: true, animated: true },
};
// SVG viewport: 800×360. Plot area is inset.
const xs = [80, 260, 480, 700];
const eras = ['2010', '2020', '2025', '2026'];
// Generation cost falls; validation stays flat.
const gen = [60, 110, 230, 290]; // y values — higher = lower cost (we invert)
const val = [180, 180, 180, 180];
const yFlip = (y: number) => 320 - y; // origin bottom
const genPath = xs.map((x, i) => `${i === 0 ? 'M' : 'L'} ${x} ${yFlip(gen[i])}`).join(' ');
const valPath = xs.map((x, i) => `${i === 0 ? 'M' : 'L'} ${x} ${yFlip(val[i])}`).join(' ');
// Crossover where gen == val: between index 1 (gen=110, val=180) and index 2 (gen=230, val=180).
// Linear interp: t = (180 - 110) / (230 - 110) = 70/120 = 0.583
const t = (180 - 110) / (230 - 110);
const crossX = xs[1] + (xs[2] - xs[1]) * t;
const crossY = yFlip(180);
// Drift surface — region right of crossover where gen rises above val (i.e., gen cost lower than validation cost).
// In our flipped space "above" means smaller y. Generation line is BELOW validation after crossover (smaller cost = larger y in original → smaller y after flip? Actually: gen=230 → yFlip=90, val=180 → yFlip=140. So generation line is ABOVE validation line after crossover. The DRIFT surface fills between them on the right side.)
const driftPath = [
`M ${crossX} ${crossY}`,
`L ${xs[2]} ${yFlip(gen[2])}`,
`L ${xs[3]} ${yFlip(gen[3])}`,
`L ${xs[3]} ${yFlip(val[3])}`,
`L ${crossX} ${crossY}`,
'Z',
].join(' ');
---
<div data-this-component class="w-full bg-paper text-black border-4 border-black p-5 md:p-8 overflow-hidden">
<header class="mb-5 md:mb-6 flex flex-wrap items-start justify-between gap-4">
<div class="max-w-2xl">
<div class="inline-flex items-center gap-2 bg-ink text-paper px-2.5 py-1 border-2 border-black mb-3">
<span class="text-[10px] font-mono uppercase tracking-widest">The Cost Equation</span>
</div>
<h2 class="reveal text-2xl md:text-3xl lg:text-4xl font-black uppercase tracking-tight leading-[1.05]" style="--reveal-i:0">
Generation went to zero.<br/>Validation did not move.
</h2>
<p class="reveal mt-3 max-w-xl text-sm md:text-base text-black opacity-80 leading-snug" style="--reveal-i:1">
The crossover is when validation became the dominant cost. Everything DoCoDeGo asserts compiles from this graph.
</p>
</div>
<ul class="reveal flex flex-col gap-2 text-[11px] md:text-xs font-mono" style="--reveal-i:2">
<li class="inline-flex items-center gap-2">
<span class="inline-block w-6 h-1 bg-co border border-black"></span>
<span class="uppercase tracking-widest">Generation cost</span>
</li>
<li class="inline-flex items-center gap-2">
<span class="inline-block w-6 h-1 bg-go border border-black"></span>
<span class="uppercase tracking-widest">Validation cost</span>
</li>
<li class="inline-flex items-center gap-2">
<span class="inline-block w-3 h-3 bg-danger border border-black"></span>
<span class="uppercase tracking-widest">Drift surface</span>
</li>
</ul>
</header>
<figure class="reveal border-4 border-black bg-paper" style="--reveal-i:3">
<svg viewBox="0 0 800 360" class="w-full h-auto block" role="img" aria-label="Generation cost falls steeply between 2020 and 2026 while validation cost stays flat; the two cross around 2023, and the drift surface fills the region after the crossover.">
<!-- grid -->
<g stroke="currentColor" stroke-width="1" opacity="0.15">
{[80, 140, 200, 260, 320].map((y) => (
<line x1="60" x2="740" y1={y} y2={y} />
))}
</g>
<!-- axes -->
<g stroke="currentColor" stroke-width="3" fill="none">
<line x1="60" y1="40" x2="60" y2="320" />
<line x1="60" y1="320" x2="760" y2="320" />
</g>
<!-- y label -->
<text x="12" y="42" font-family="monospace" font-size="11" fill="currentColor" opacity="0.7">COST</text>
<text x="12" y="320" font-family="monospace" font-size="11" fill="currentColor" opacity="0.7">0</text>
<!-- x ticks + era labels -->
{xs.map((x, i) => (
<>
<line x1={x} y1="320" x2={x} y2="328" stroke="currentColor" stroke-width="3" />
<text x={x} y="346" text-anchor="middle" font-family="monospace" font-size="12" font-weight="700" fill="currentColor">{eras[i]}</text>
</>
))}
<!-- drift surface -->
<path d={driftPath} fill="var(--color-danger)" fill-opacity="0.45" stroke="var(--color-ink)" stroke-width="2" stroke-dasharray="4 4" class="drift" />
<!-- generation line -->
<path d={genPath} stroke="var(--color-co)" stroke-width="6" fill="none" stroke-linecap="square" stroke-linejoin="miter" class="line line-gen" />
<!-- validation line -->
<path d={valPath} stroke="var(--color-go)" stroke-width="6" fill="none" stroke-linecap="square" stroke-linejoin="miter" class="line line-val" />
<!-- crossover marker -->
<line x1={crossX} y1={crossY} x2={crossX} y2="320" stroke="currentColor" stroke-width="2" stroke-dasharray="4 4" opacity="0.6" />
<circle cx={crossX} cy={crossY} r="9" fill="var(--color-paper)" stroke="currentColor" stroke-width="4" class="cross-dot" />
<!-- callout -->
<g class="callout">
<rect x="500" y="60" width="240" height="62" fill="var(--color-ink)" stroke="var(--color-ink)" stroke-width="4" />
<text x="512" y="84" font-family="monospace" font-size="11" font-weight="700" fill="var(--color-paper)" opacity="0.7">CALLOUT</text>
<text x="512" y="108" font-family="sans-serif" font-size="18" font-weight="900" fill="var(--color-paper)" letter-spacing="0.5">THE BOTTLENECK MOVED</text>
<path d="M 510 122 L 480 134 L 510 138 Z" fill="var(--color-ink)" />
</g>
</svg>
</figure>
<footer class="reveal mt-5 md:mt-6 grid grid-cols-1 md:grid-cols-3 gap-3 md:gap-4" style="--reveal-i:4">
<div class="border-4 border-black bg-co text-black px-3 py-2">
<div class="text-[9px] font-mono uppercase tracking-widest opacity-70">Before crossover</div>
<p class="text-xs md:text-sm font-black leading-snug">Writing the code was the work.</p>
</div>
<div class="border-4 border-black bg-go text-black px-3 py-2">
<div class="text-[9px] font-mono uppercase tracking-widest opacity-70">After crossover</div>
<p class="text-xs md:text-sm font-black leading-snug">Proving the code meets intent is the work.</p>
</div>
<div class="border-4 border-black bg-danger text-white px-3 py-2">
<div class="text-[9px] font-mono uppercase tracking-widest opacity-80">If validation lags</div>
<p class="text-xs md:text-sm font-black leading-snug">The drift surface is where incidents come from.</p>
</div>
</footer>
</div>
<style>
[data-this-component] .reveal {
opacity: 0;
transform: translateY(16px);
animation: cpd-104-reveal 540ms cubic-bezier(0.2, 0, 0.13, 1.2) forwards;
animation-delay: calc(var(--reveal-i, 0) * 120ms);
}
@keyframes cpd-104-reveal {
to { opacity: 1; transform: none; }
}
[data-this-component] .line {
stroke-dasharray: 900;
stroke-dashoffset: 900;
animation: cpd-104-draw 1200ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
animation-delay: 600ms;
}
[data-this-component] .line-val { animation-delay: 900ms; }
@keyframes cpd-104-draw { to { stroke-dashoffset: 0; } }
[data-this-component] .drift {
opacity: 0;
animation: cpd-104-fade 600ms ease-out forwards;
animation-delay: 1700ms;
}
[data-this-component] .cross-dot {
opacity: 0;
transform-origin: center;
animation: cpd-104-pop 380ms cubic-bezier(0.2, 0, 0.13, 1.4) forwards;
animation-delay: 1900ms;
}
[data-this-component] .callout {
opacity: 0;
transform: translateX(12px);
transform-origin: center;
animation: cpd-104-callout 500ms ease-out forwards;
animation-delay: 2100ms;
}
@keyframes cpd-104-fade { to { opacity: 1; } }
@keyframes cpd-104-pop { 0% { opacity: 0; } 100% { opacity: 1; } }
@keyframes cpd-104-callout { to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
[data-this-component] .reveal,
[data-this-component] .line,
[data-this-component] .drift,
[data-this-component] .cross-dot,
[data-this-component] .callout {
animation: none;
opacity: 1;
transform: none;
stroke-dashoffset: 0;
}
}
</style>