# Animation accessibility and keyboard support

This milestone now includes a focused accessibility layer for animation and guided-tour controls. It is intentionally UI-framework-light at the core (`src/animations/animationAccessibility.ts`) so the same shortcut, reduced-motion, and live-region logic can be used by the transport bar, guided-tour overlay, procedural demo page, and future machine viewers.

## What the layer covers

- Standard keyboard commands for playback, deterministic stepping, seek, RPM, time scale, tour navigation, labels, exploded view, and shortcut help.
- Safe shortcut matching that does **not** hijack typing in inputs, textareas, contenteditable regions, ARIA textboxes, spinbuttons, or sliders.
- A reusable React scope component for focus-bounded keyboard control.
- Live-region announcement helpers for playback, RPM, speed, tour step, progress, highlighted part, and reduced-motion state.
- A reduced-motion policy helper that distinguishes autoplay, user-initiated playback, essential explanatory motion, continuous reduced playback, and static/step-through fallbacks.
- Unit coverage for shortcut matching, editable target protection, command dispatch, ARIA shortcut formatting, reduced-motion planning, announcements, and slider-value clamping.

## Default shortcut map

| Shortcut | Command |
| --- | --- |
| `Space` / `K` | Play or pause animation |
| `R` | Restart animation cycle |
| `.` | Step forward |
| `,` | Step backward |
| `←` / `→` | Small seek backward / forward |
| `Shift + ←` / `Shift + →` | Large seek backward / forward |
| `Alt + ↑` / `Alt + ↓` | Increase / decrease RPM |
| `]` / `[` | Increase / decrease time scale |
| `N` / `P` | Next / previous guided-tour step |
| `L` | Toggle labels |
| `E` | Toggle exploded view |
| `?` | Toggle shortcut help |

These shortcuts should be attached to a focused viewer or controls region rather than globally wherever possible. This keeps orbit controls, browser shortcuts, and form controls predictable.

## React integration pattern

`src/components/animation/AnimationAccessibilityPanel.tsx` provides a ready-to-use keyboard scope, shortcut help panel, and live-region components.

```tsx
import {
  AnimationKeyboardScope,
  AnimationShortcutHelpPanel,
  AnimationStatusLiveRegion,
} from '../components/animation/AnimationAccessibilityPanel';

function DemoControls() {
  return (
    <AnimationKeyboardScope
      actions={{
        togglePlayback: () => player.toggle(),
        restart: () => player.restart(),
        stepForward: () => player.step(1),
        stepBackward: () => player.step(-1),
        increaseRpm: () => player.setRpm(player.rpm + 25),
        decreaseRpm: () => player.setRpm(player.rpm - 25),
        nextTourStep: () => tour.next(),
        previousTourStep: () => tour.previous(),
      }}
      describedBy="animation-shortcut-help"
      className="outline-none focus-visible:ring-2 focus-visible:ring-cyan-300"
    >
      <AnimationStatusLiveRegion
        state={{
          machineName: machine.name,
          playbackState: player.isPlaying ? 'playing' : 'paused',
          rpm: player.rpm,
          timeScale: player.timeScale,
          progress: player.progress,
          tourTitle: tour.title,
          currentStep: tour.currentStep + 1,
          totalSteps: tour.steps.length,
          highlightedPartName: tour.highlightedPartName,
        }}
      />

      <TransportControls />
      <AnimationShortcutHelpPanel id="animation-shortcut-help" open={showHelp} />
    </AnimationKeyboardScope>
  );
}
```

## Reduced-motion behavior

Use `createReducedMotionAnimationPlan` before autoplay or continuous looping starts:

- No reduced-motion preference: keep normal requested playback speed.
- Reduced motion + no user intent: render a static snapshot and do not autoplay.
- Reduced motion + user intent: prefer deterministic step-through mode.
- Reduced motion + explicitly allowed continuous playback: cap time scale and RPM multiplier.
- Essential explanatory motion: allow capped continuous playback, but still avoid auto-start unless the user requested motion.

This preserves the educational value of mechanisms such as Geneva dwell, valve timing, or pump impeller flow while respecting users who have requested less motion.

## Live-region guidance

Announcements should be concise and state changes should be meaningful. Good examples:

- `Four-stroke petrol engine animation playing. 850 RPM, 0.5× speed, 42% through cycle.`
- `Guided tour, step 3 of 6, Exhaust valve opens. Highlighted part: exhaust cam lobe.`
- `Animation paused. Step-through mode, reduced motion.`

Avoid announcing every animation frame. Announce on transport state changes, RPM/time-scale commits, tour step changes, highlight changes, and explicit step-through actions.

## Validation

The added tests live in `src/animations/animationAccessibility.test.ts`. They protect keyboard matching edge cases, editable-control safety, reduced-motion planning, announcement formatting, and clamped slider values so future transport or tour UI changes can reuse this behavior without reintroducing accessibility regressions.
