The Green-To-Red tracker is DoCoDeGo's "shipped vs slipped" memory. Each segment is one cycle. Green = met intent, amber = degraded, red = missed. The strip exposes trend without averaging it away.
Acceptance Criteria
[ ] N segments left-to-right, oldest to newest
[ ] Three states colour-coded: green / amber / red
[ ] Numeric tally of each state in the footer
Constraints
No JS
Segments equal width via CSS grid
// gtr-tracker.astro
---
import type { CompendiumMeta } from '../../../data/types';
export const meta: CompendiumMeta = {
id: 29,
slug: 'gtr-tracker',
name: 'GTR Tracker',
category: 'maturity-process',
subcategory: 'reviews-and-logs',
pillar: 'de',
description: 'Green-to-Red tracker — a horizontal strip of the last N cycles coloured by validation status.',
spec: `## Intent
The Green-To-Red tracker is DoCoDeGo's "shipped vs slipped" memory. Each segment is one cycle. Green = met intent, amber = degraded, red = missed. The strip exposes trend without averaging it away.
## Acceptance Criteria
- [ ] N segments left-to-right, oldest to newest
- [ ] Three states colour-coded: green / amber / red
- [ ] Numeric tally of each state in the footer
## Constraints
- No JS
- Segments equal width via CSS grid`,
tags: ['chart', 'real-content', 'dark-mode-tested', 'responsive'],
status: 'stable',
health: { ics: 87, a11y: 'aa', themed: true, animated: false },
};
type S = 'g' | 'a' | 'r';
const cycles: S[] = ['g','g','a','g','r','g','g','a','g','g','g','a','g','g','g','g','r','g','g','g'];
const tally = {
g: cycles.filter(c=>c==='g').length,
a: cycles.filter(c=>c==='a').length,
r: cycles.filter(c=>c==='r').length,
};
const colorOf = (s: S) =>
s === 'g' ? 'var(--color-success)' : s === 'a' ? 'var(--color-do)' : 'var(--color-danger)';
const labelOf = (s: S) => s === 'g' ? 'met' : s === 'a' ? 'degraded' : 'missed';
---
<div class="w-full bg-paper text-ink border-4 border-ink">
<header class="flex items-baseline justify-between px-3 py-2 border-b-2 border-ink">
<span class="font-mono text-[10px] uppercase tracking-widest opacity-60 truncate">// GTR · last {cycles.length} cycles</span>
<span class="font-mono text-[10px] uppercase tracking-widest shrink-0">now →</span>
</header>
<div class="p-3">
<div
class="grid border-2 border-ink overflow-hidden"
style={`grid-template-columns: repeat(${cycles.length}, minmax(0, 1fr));`}
role="img"
aria-label={`Last ${cycles.length} cycles: ${tally.g} met, ${tally.a} degraded, ${tally.r} missed`}
>
{cycles.map((c, i) => (
<div
class="h-8 border-e border-ink/40 last:border-e-0"
style={`background:${colorOf(c)}`}
title={`Cycle ${i+1}: ${labelOf(c)}`}
></div>
))}
</div>
<div class="mt-2 grid grid-cols-3 gap-1 font-mono text-[10px] uppercase tracking-widest">
<div class="flex items-center gap-1.5 min-w-0">
<span class="inline-block w-2.5 h-2.5 border-2 border-ink bg-success shrink-0"></span>
<span class="truncate">met</span>
<span class="ms-auto font-black tabular-nums">{tally.g}</span>
</div>
<div class="flex items-center gap-1.5 min-w-0">
<span class="inline-block w-2.5 h-2.5 border-2 border-ink bg-do shrink-0"></span>
<span class="truncate">deg</span>
<span class="ms-auto font-black tabular-nums">{tally.a}</span>
</div>
<div class="flex items-center gap-1.5 min-w-0">
<span class="inline-block w-2.5 h-2.5 border-2 border-ink bg-danger shrink-0"></span>
<span class="truncate">miss</span>
<span class="ms-auto font-black tabular-nums">{tally.r}</span>
</div>
</div>
</div>
</div>