Skip to main content
Compendium / Maturity & Process / Instruments / Velocity Meter
#032

Velocity Meter

Analog-style dial showing AI generation velocity — specs per day, PRs per day, files per hour.

STABLE DE
#svg #gauge #animated #reduced-motion #real-content #dark-mode-tested
Live
// velocity gen rate
0 5 10 15 20 14 specs / day
Spec

Intent

A bakelite-instrument dial that reads generation velocity. The needle sweeps a 240° arc from 0 to redline. Ticks every 25%. The reading is "specs/day" — the rate at which intent enters the system.

Acceptance Criteria

  • [ ] 240° arc with tick marks and labels
  • [ ] Needle position maps proportionally to value
  • [ ] Numeric readout + unit below the dial

Constraints

  • Pure SVG, no runtime
  • Theme-aware via tokens
// velocity-meter.astro
---
import type { CompendiumMeta } from '../../../data/types';

export const meta: CompendiumMeta = {
  id: 32,
  slug: 'velocity-meter',
  name: 'Velocity Meter',
  category: 'maturity-process',
  subcategory: 'instruments',
  pillar: 'de',
  description: 'Analog-style dial showing AI generation velocity — specs per day, PRs per day, files per hour.',
  spec: `## Intent
A bakelite-instrument dial that reads generation velocity. The needle sweeps a 240° arc from 0 to redline. Ticks every 25%. The reading is "specs/day" — the rate at which intent enters the system.

## Acceptance Criteria
- [ ] 240° arc with tick marks and labels
- [ ] Needle position maps proportionally to value
- [ ] Numeric readout + unit below the dial

## Constraints
- Pure SVG, no runtime
- Theme-aware via tokens`,
  tags: ['svg', 'gauge', 'animated', 'reduced-motion', 'real-content', 'dark-mode-tested'],
  status: 'stable',
  health: { ics: 90, a11y: 'aa', themed: true, animated: true },
};

const value = 14;     // specs/day
const max = 20;
const pct = Math.min(value / max, 1);
const angle = -120 + pct * 240;
const ticks = [0, 25, 50, 75, 100].map((p) => ({ a: -120 + (p / 100) * 240, n: Math.round((p / 100) * max) }));
---

<div data-this-component class="w-full bg-paper text-ink border-4 border-ink overflow-hidden">
  <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">// velocity</span>
    <span class="font-mono text-[10px] uppercase tracking-widest">gen rate</span>
  </header>
  <div class="p-3 flex flex-col items-center">
    <svg viewBox="0 0 200 140" class="w-full max-w-[240px] block" role="img" aria-label={`Velocity ${value} specs per day`}>
      <path d="M 30 110 A 70 70 0 1 1 170 110" fill="none" stroke="var(--color-ink)" stroke-width="3" />
      <path d="M 30 110 A 70 70 0 0 1 100 40" fill="none" stroke="var(--color-success)" stroke-width="6" opacity="0.85" />
      <path d="M 100 40 A 70 70 0 0 1 140 52" fill="none" stroke="var(--color-do)" stroke-width="6" opacity="0.85" />
      <path d="M 140 52 A 70 70 0 0 1 170 110" fill="none" stroke="var(--color-danger)" stroke-width="6" opacity="0.85" />
      {ticks.map((t) => {
        const rad = ((t.a - 90) * Math.PI) / 180;
        const x1 = 100 + Math.cos(rad) * 60, y1 = 100 + Math.sin(rad) * 60;
        const x2 = 100 + Math.cos(rad) * 72, y2 = 100 + Math.sin(rad) * 72;
        const lx = 100 + Math.cos(rad) * 50, ly = 100 + Math.sin(rad) * 50;
        return (
          <>
            <line x1={x1} y1={y1} x2={x2} y2={y2} stroke="var(--color-ink)" stroke-width="2" />
            <text x={lx} y={ly + 3} text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="7" fill="var(--color-ink)" opacity="0.7">{t.n}</text>
          </>
        );
      })}
      <g transform={`rotate(${angle} 100 100)`} class="needle">
        <polygon points="100,38 96,104 104,104" fill="var(--color-ink)" />
        <circle cx="100" cy="100" r="6" fill="var(--color-de)" stroke="var(--color-ink)" stroke-width="2" />
      </g>
      <text x="100" y="130" text-anchor="middle" font-family="Inter, sans-serif" font-weight="900" font-size="20" fill="var(--color-ink)">{value}</text>
    </svg>
    <span class="font-mono text-[10px] uppercase tracking-widest opacity-60 -mt-1">specs / day</span>
  </div>
</div>

<style>
  [data-this-component] .needle { transition: transform 800ms cubic-bezier(0.2,0,0.13,1.4); }
  @media (prefers-reduced-motion: reduce) {
    [data-this-component] .needle { transition: none; }
  }
</style>