Spec
Intent
Some measurements are easier to reason about as a clock face — render a tick-ringed dial with a needle for that intuition.
Acceptance Criteria
- [ ] 270-degree dial with tick marks at intervals
- [ ] Hard, angular needle (no shadow, no curve)
- [ ] Numeric readout centre
- [ ] Negative: does NOT smooth needle motion
Constraints
- Pure SVG
- Theme-aware tokens
// dial-gauge.astro
---
import type { CompendiumMeta } from '../../../data/types';
export const meta: CompendiumMeta = {
id: 703,
slug: 'dial-gauge',
name: 'Dial Gauge',
category: 'metrics-telemetry',
subcategory: 'gauges',
pillar: 'de',
description: 'Clock-face style dial with tick marks and a hard needle — analogue feel for analogue thinkers.',
spec: `## Intent
Some measurements are easier to reason about as a clock face — render a tick-ringed dial with a needle for that intuition.
## Acceptance Criteria
- [ ] 270-degree dial with tick marks at intervals
- [ ] Hard, angular needle (no shadow, no curve)
- [ ] Numeric readout centre
- [ ] Negative: does NOT smooth needle motion
## Constraints
- Pure SVG
- Theme-aware tokens`,
tags: ['svg', 'gauge', 'real-content', 'responsive'],
status: 'stable',
health: { ics: 87, a11y: 'aa', themed: true, animated: false },
};
const value = 47;
const max = 100;
const startAngle = 135;
const sweep = 270;
const angle = startAngle + (value / max) * sweep;
const rad = (angle * Math.PI) / 180;
const cx = 100;
const cy = 100;
const needleR = 62;
const tipX = cx + Math.cos(rad) * needleR;
const tipY = cy + Math.sin(rad) * needleR;
const ticks = Array.from({ length: 11 }, (_, i) => {
const a = startAngle + (i / 10) * sweep;
const r = (a * Math.PI) / 180;
return {
x1: cx + Math.cos(r) * 72,
y1: cy + Math.sin(r) * 72,
x2: cx + Math.cos(r) * 82,
y2: cy + Math.sin(r) * 82,
};
});
---
<div data-this-component class="w-full bg-paper text-black border-4 border-black p-4 flex items-center justify-center overflow-hidden">
<svg viewBox="0 0 200 200" class="w-full max-w-[220px] block" aria-label={`Dial value ${value}`}>
<circle cx={cx} cy={cy} r="84" fill="none" stroke="var(--color-ink)" stroke-width="3" />
{ticks.map((t, i) => (
<line x1={t.x1} y1={t.y1} x2={t.x2} y2={t.y2} stroke="var(--color-ink)" stroke-width={i % 5 === 0 ? 3 : 1.5} />
))}
<line x1={cx} y1={cy} x2={tipX} y2={tipY} stroke="var(--color-de)" stroke-width="5" />
<circle cx={cx} cy={cy} r="8" fill="var(--color-ink)" />
<circle cx={cx} cy={cy} r="3" fill="var(--color-paper)" />
<text x={cx} y="160" text-anchor="middle" font-family="Inter, sans-serif" font-weight="900" font-size="22" fill="var(--color-ink)">{value}</text>
<text x={cx} y="178" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" letter-spacing="2" fill="var(--color-ink)" opacity="0.6">PSI</text>
</svg>
</div>