Skip to main content
Compendium / Maturity & Process / Indicators / Cycle Tick
#031

Cycle Tick

A heartbeat-style pulse showing an ongoing DoCoDeGo cycle is alive and converging.

STABLE DE
#svg #animated #reduced-motion #real-content #dark-mode-tested
Live
// pulse live
cycleCYCLE-042
elapsed04D 11H
rate / d38
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
---
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>