Spec
Intent
A small "system is alive" indicator. The DoCoDeGo cycle is continuous; this is the heartbeat trace that proves it. Mono-typography, ECG line, current cycle id.
Acceptance Criteria
[ ] Animated heartbeat trace (CSS, respects prefers-reduced-motion)
[ ] Cycle id + elapsed time visible
[ ] BPM-style mono readout
Constraints
Pure CSS animation
Theme-aware stroke
// cycle-tick.astro copy
---
import type { CompendiumMeta } from '../../../data/types';
export const meta: CompendiumMeta = {
id: 31,
slug: 'cycle-tick',
name: 'Cycle Tick',
category: 'maturity-process',
subcategory: 'indicators',
pillar: 'de',
description: 'A heartbeat-style pulse showing an ongoing DoCoDeGo cycle is alive and converging.',
spec: `## Intent
A small "system is alive" indicator. The DoCoDeGo cycle is continuous; this is the heartbeat trace that proves it. Mono-typography, ECG line, current cycle id.
## Acceptance Criteria
- [ ] Animated heartbeat trace (CSS, respects prefers-reduced-motion)
- [ ] Cycle id + elapsed time visible
- [ ] BPM-style mono readout
## Constraints
- Pure CSS animation
- Theme-aware stroke`,
tags: ['svg', 'animated', 'reduced-motion', 'real-content', 'dark-mode-tested'],
status: 'stable',
health: { ics: 85, a11y: 'aa', themed: true, animated: true },
};
const cycle = 'CYCLE-042';
const elapsed = '04D 11H';
const bpm = 38;
---
<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">// pulse</span>
<span class="flex items-center gap-1.5">
<span class="dot inline-block w-2 h-2 bg-attention" aria-hidden="true"></span>
<span class="font-mono text-[10px] uppercase tracking-widest">live</span>
</span>
</header>
<div class="p-3">
<div class="border-2 border-ink bg-paper relative overflow-hidden h-14" role="img" aria-label={`${cycle} live, ${bpm} per day`}>
<svg viewBox="0 0 200 50" preserveAspectRatio="none" class="absolute inset-0 w-[200%] h-full ecg">
<path
d="M 0 25 L 30 25 L 35 25 L 40 10 L 45 40 L 50 18 L 55 25 L 100 25 L 130 25 L 135 25 L 140 10 L 145 40 L 150 18 L 155 25 L 200 25"
fill="none"
stroke="var(--color-de)"
stroke-width="2"
vector-effect="non-scaling-stroke"
/>
</svg>
</div>
<div class="mt-2 grid grid-cols-3 gap-2 font-mono text-[10px] uppercase tracking-widest">
<div class="min-w-0"><span class="opacity-60 block">cycle</span><span class="font-black text-xs truncate block">{cycle}</span></div>
<div class="min-w-0"><span class="opacity-60 block">elapsed</span><span class="font-black text-xs tabular-nums">{elapsed}</span></div>
<div class="min-w-0"><span class="opacity-60 block">rate / d</span><span class="font-black text-xs tabular-nums">{bpm}</span></div>
</div>
</div>
</div>
<style>
[data-this-component] .ecg { animation: ecg-scroll 3s linear infinite; }
[data-this-component] .dot { animation: dot-blink 1.2s steps(2,end) infinite; }
@keyframes ecg-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@keyframes dot-blink { 50% { opacity: 0.15; } }
@media (prefers-reduced-motion: reduce) {
[data-this-component] .ecg, [data-this-component] .dot { animation: none; }
}
</style>