Spec
Intent
Map an inventory of identified risks across the likelihood/severity plane so the governor sees concentration at a glance. Cells in the top-right quadrant demand a control, not a comment.
Acceptance Criteria
[ ] 3×3 grid with axis labels (likelihood, severity)
[ ] Cell colour graded from success → attention → danger
[ ] Risk counts shown per cell
[ ] Top-right cell is unambiguously the worst
Constraints
No JS; pure layout
Reads in both light and dark themes
// risk-heatmap.astro copy
---
import type { CompendiumMeta } from '../../../data/types';
export const meta: CompendiumMeta = {
id: 38,
slug: 'risk-heatmap',
name: 'Risk Heatmap',
category: 'governance-risk',
subcategory: 'signoff',
pillar: 'go',
description: '3×3 likelihood × severity grid — the canonical "is this worth losing sleep over" visual.',
spec: `## Intent
Map an inventory of identified risks across the likelihood/severity plane so the governor sees concentration at a glance. Cells in the top-right quadrant demand a control, not a comment.
## Acceptance Criteria
- [ ] 3×3 grid with axis labels (likelihood, severity)
- [ ] Cell colour graded from success → attention → danger
- [ ] Risk counts shown per cell
- [ ] Top-right cell is unambiguously the worst
## Constraints
- No JS; pure layout
- Reads in both light and dark themes`,
tags: ['chart', 'real-content', 'dark-mode-tested'],
status: 'stable',
health: { ics: 80, a11y: 'aa', themed: true, animated: false },
};
const cells: { count: number; tone: string }[][] = [
[{count:0, tone:'bg-attention'}, {count:1, tone:'bg-danger'}, {count:3, tone:'bg-danger'}],
[{count:1, tone:'bg-success'}, {count:2, tone:'bg-attention'}, {count:2, tone:'bg-danger'}],
[{count:4, tone:'bg-success'}, {count:1, tone:'bg-success'}, {count:1, tone:'bg-attention'}],
];
const rowLabels = ['HIGH', 'MED', 'LOW'];
const colLabels = ['LOW', 'MED', 'HIGH'];
---
<div data-this-component class="w-full bg-paper text-black border-4 border-black p-4 overflow-hidden">
<div class="flex items-baseline justify-between mb-2">
<p class="font-mono text-[10px] uppercase tracking-widest opacity-60">Risk register · 15 items</p>
<p class="font-black text-[10px] uppercase tracking-widest">Sev × Likelihood</p>
</div>
<div class="flex gap-2">
<div class="flex flex-col justify-between text-[9px] font-mono uppercase tracking-widest opacity-70 py-1">
{rowLabels.map(l => <span>{l}</span>)}
</div>
<div class="flex-1">
<div class="grid grid-cols-3 gap-1">
{cells.flat().map(c => (
<div class:list={['aspect-square border-2 border-black flex items-center justify-center', c.tone]}>
<span class="font-black text-lg text-white">{c.count}</span>
</div>
))}
</div>
<div class="mt-1 grid grid-cols-3 text-[9px] font-mono uppercase tracking-widest opacity-70 text-center">
{colLabels.map(l => <span>{l}</span>)}
</div>
</div>
</div>
<p class="mt-3 text-[10px] font-mono uppercase tracking-widest opacity-60">→ Likelihood · ↑ Severity</p>
</div>