{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "core-transport-controls",
  "type": "registry:ui",
  "title": "Transport Controls",
  "description": "The transport bar, composed rather than reinvented: play, stop, loop and an optional record built from glow-button's toggle and momentary modes, a monospace tempo readout you drag, wheel, arrow or double-click to reset, a tap-tempo key that keeps only timestamps, and a segmented note-division picker printing the current division's length and rate at the current tempo. The beat indicator pulses from a phase or beat prop and nothing else — the host owns the clock — and every control independently becomes a lamp when its callback is absent, so with no callbacks at all it is a status display. The tempo readout has a second face: `tempoDisplay=\"segment\"` puts the seven-segment display off a drum machine's front panel behind the same drag, wheel and arrow keys.",
  "dependencies": [
    "maruaudio@>=0.0.3 <0.1.0"
  ],
  "registryDependencies": [
    "https://pluginui.com/r/core-glow-button.json",
    "https://pluginui.com/r/core-segment-display.json"
  ],
  "files": [
    {
      "path": "components/ui/transport-controls.tsx",
      "content": "/**\n * Source: https://maru.audio/ui-kit/transport-controls\n * Copyright © [Maru](https://maru.audio)\n *\n * Maru UI Kit — https://maru.audio/ui-kit\n *\n * Installed from the Maru UI registry. This file is yours now: edit it\n * freely, it will not be overwritten unless you re-run the add command.\n *\n * Keep this item’s companion files and CSS imports together. Dependencies\n * declared by the registry are installed with the source.\n */\n/**\n * TransportControls — play / stop / loop (/ record), a tempo, and a division.\n *\n * The bar across the top of every sequencer-shaped thing. It is deliberately a\n * COMPOSITION rather than new button code: the four transport keys are\n * `ui/glow-button` at a small size, in the modes it already has — `toggle` for\n * play, loop and record, `momentary` for stop — so the halo, the LED bezel, the\n * press-in seat, the keyboard behaviour and the `onInteraction` unlock are the\n * ones that shipped and were tested there. The backlog asked for a worked\n * composition example (`todo/ui-kit/component-expansion.md` item 5), the same\n * way `envelope-editor` is one for knobs, and a second button implementation\n * would have been the opposite of that.\n *\n * All the arithmetic lives in `lib/musical-time`, which is pure and tested in\n * Node against verbatim copies of the shipped conversions. This file is the\n * face: it decides where things sit and what turns them, and computes nothing\n * a test could disagree with.\n *\n * ## Who owns what\n *\n * The HOST owns the clock. The beat indicator pulses from a `phase` (or a\n * fractional `beat`) prop and from nothing else — no interval, no rAF stepping\n * a counter, no \"we know the tempo so we can guess where the beat is\". That is\n * the same rule `lfo-visualizer` follows with phase and `step-grid` follows\n * with its playhead, and it is the rule because a display timer and an audio\n * clock disagree within seconds. A transport whose light is on the beat is one\n * whose light is driven by the thing making the sound.\n *\n * The HOST owns every value. `playing`, `loop`, `recording`, `tempo` and\n * `division` are props; each change reports out and nothing latches locally.\n * In particular STOP does not flip `playing` behind your back — it reports, and\n * whether stopping also rewinds, disarms the record or does nothing is the\n * host's decision to make and the host's state to change.\n *\n * The only state kept here is transient GESTURE state: which key is physically\n * down (inside `glow-button`), the drag anchor, and the tap-tempo timestamp\n * buffer. The buffer is not a copy of the tempo — it is the gesture in\n * progress, and the tempo it produces leaves immediately through\n * `onTempoChange`.\n *\n * ## Degenerate by omission\n *\n * Every control is independently read-only when its callback is absent: no\n * `onLoopChange` and the loop key becomes a lamp that reports the loop state\n * instead of a button that changes it. Pass no callbacks at all and the whole\n * thing is a status display — which is what a \"now playing\" strip beside a\n * rendered result wants, and it is the same call `step-grid` makes when it has\n * no `onToggle`.\n *\n * ## The tempo readout\n *\n * A monospace number you can drag vertically (up is faster), wheel over,\n * arrow-key, and double-click to send back to `defaultTempo` — the same\n * gesture set the kit's faders have, using the same `use-surface-pointer` /\n * `use-wheel` primitives and `knob-core`'s `snapToStep` so a tempo lands on\n * whole numbers exactly as a knob's value lands on its step. Shift is the fine\n * pass: four times slower and on a tenth-bpm step, re-anchored where Shift went\n * down so pressing it mid-drag does not jump the number.\n *\n * The TAP key feeds `tapTempo` from the lib. It keeps only the timestamps; the\n * bpm goes straight out through `onTempoChange`, unrounded, because a host that\n * wants whole numbers rounds and one that does not should not have to undo it.\n *\n * `tempoDisplay=\"segment\"` swaps the numeral for `segment-display` — the\n * seven-segment readout off a drum machine, ghost bars and all. It is a skin\n * and nothing more: the drag, the wheel, the arrows and the double-click reset\n * stay on the wrapper around it, so there is one control with two faces rather\n * than two controls. The panel comes up to full brightness while the drag is\n * live, which is the hardware's own way of saying it is being driven.\n *\n * ## The division picker\n *\n * The \"sequencer quarters\" ask: a segmented row of divisions from the lib's\n * table, with the current one's LENGTH and RATE printed beside it at the\n * current tempo. A division picker without that readout is four abbreviations;\n * with it, `1/8T` at 96 bpm is visibly 208 ms and 4.80 steps a second, which is\n * the number that has to match the delay you are trying to hit.\n *\n * Styling comes from CSS custom properties — see `lib/control-theme` — with a\n * `color` prop to override the accent for one instance. SSR-safe: every DOM\n * access is inside a handler or an effect.\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\"\nimport type { CSSProperties, KeyboardEvent, SyntheticEvent } from \"react\"\nimport { token } from \"maruaudio/ui/shared/theme\"\nimport { snapToStep } from \"maruaudio/ui/shared/knob-core\"\nimport { formatEchoTime } from \"maruaudio/ui/shared/echo-train\"\nimport {\n  COMMON_DIVISIONS,\n  DEFAULT_TEMPO,\n  TAP_WINDOW,\n  TEMPO_MAX,\n  TEMPO_MIN,\n  clampTempo,\n  formatTempo,\n  noteDivision,\n  noteDivisionHz,\n  noteDivisionSeconds,\n  tapRun,\n  tapTempo,\n  type NoteDivisionId,\n} from \"maruaudio/ui/shared/musical-time\"\nimport {\n  SegmentDisplay,\n} from \"@/components/ui/segment-display\"\nimport type { DisplayPresetId } from \"maruaudio/ui/shared/display-chrome\"\nimport {\n  GlowButton,\n  pulseIntensity,\n  wrapPhase,\n} from \"@/components/ui/glow-button\"\nimport {\n  useFirstInteraction,\n  type InteractionEvent,\n} from \"maruaudio/ui/shared/use-first-interaction\"\nimport { useSurfacePointer } from \"maruaudio/ui/shared/use-surface-pointer\"\nimport { useWheel, wheelDeltaForAxis } from \"maruaudio/ui/shared/use-wheel\"\n\n/**\n * Pixels of vertical drag per bpm. Three is the number that lets a 40 bpm move\n * happen without a second grab and still lets you land on a single beat.\n */\nconst TEMPO_DRAG_PX_PER_BPM = 3\n\n/** Shift's factor, matching every other fine pass in the kit. */\nconst FINE_FACTOR = 4\n\n/** Captions on the four keys. Overridable — some consoles want glyphs. */\nexport interface TransportLabels {\n  play?: string\n  stop?: string\n  loop?: string\n  record?: string\n  tap?: string\n}\n\nconst DEFAULT_LABELS: Required<TransportLabels> = {\n  play: \"PLAY\",\n  stop: \"STOP\",\n  loop: \"LOOP\",\n  record: \"REC\",\n  tap: \"TAP\",\n}\n\nexport interface TransportControlsProps {\n  /** Rolling or not. Controlled — the play key reports, it does not latch. */\n  playing?: boolean\n  /** Report a play/pause toggle. Absent makes the play key a lamp. */\n  onPlayingChange?: (playing: boolean) => void\n  /**\n   * The stop key was pressed. Momentary, and purely a report: whether stopping\n   * also clears `playing`, rewinds or disarms the record is the host's call.\n   * Absent hides the key entirely — a transport with no stop is a valid one.\n   */\n  onStop?: (event: SyntheticEvent) => void\n\n  /** Loop state. */\n  loop?: boolean\n  onLoopChange?: (loop: boolean) => void\n\n  /** Record arm state. */\n  recording?: boolean\n  /**\n   * Report a record-arm toggle. Its ABSENCE hides the record key: a transport\n   * that cannot record should not show a key that pretends it can.\n   */\n  onRecordChange?: (recording: boolean) => void\n\n  /** Tempo in bpm, 20..300. Controlled; defaults to `defaultTempo`. */\n  tempo?: number\n  /** Where a double-click on the readout sends the tempo. Defaults to 120. */\n  defaultTempo?: number\n  /** Report a tempo change (drag, wheel, arrows, tap). Absent makes it a readout. */\n  onTempoChange?: (tempo: number) => void\n  /** Step a drag, a wheel notch or an arrow key lands on. Defaults to 1 bpm. */\n  tempoStep?: number\n  /** Step the same gestures land on with Shift held. Defaults to 0.1 bpm. */\n  tempoFineStep?: number\n\n  /** The current note division. */\n  division?: NoteDivisionId\n  /** Which divisions the picker offers. Defaults to 1/4, 1/8, 1/8T, 1/16. */\n  divisions?: readonly NoteDivisionId[]\n  /** Report a division change. Absent makes the row a set of lamps. */\n  onDivisionChange?: (division: NoteDivisionId) => void\n  /** Print the current division's length and rate beside the row. Default true. */\n  showDivisionReadout?: boolean\n\n  /**\n   * Position inside the current beat, 0..1, from the host's clock. The ONLY\n   * thing that lights the beat indicator — see the header.\n   */\n  phase?: number | null\n  /**\n   * Absolute beat index from the host's clock, fractional. Supplies the phase\n   * when `phase` is absent, and tells the indicator which hits are downbeats.\n   */\n  beat?: number | null\n  /** Beats per bar, for the downbeat accent. Defaults to 4. */\n  beatsPerBar?: number\n  /** Draw the beat indicator. Default true. */\n  showBeat?: boolean\n\n  /** Draw the tap-tempo key. Default true when `onTempoChange` is present. */\n  showTap?: boolean\n  /** Draw the tempo readout. Default true. */\n  showTempo?: boolean\n  /**\n   * How the tempo reads. `\"text\"` is the numeral in the kit's mono face;\n   * `\"segment\"` swaps in the seven-segment readout off a drum machine's front\n   * panel — same gesture surface, same drag, same keys, just the hardware's\n   * own face on it. The digits are the only thing that changes.\n   */\n  tempoDisplay?: \"text\" | \"segment\"\n  /** Panel skin for `tempoDisplay=\"segment\"`. See `DISPLAY_PRESETS`. */\n  tempoPreset?: DisplayPresetId\n\n  /** Key diameter in pixels. Defaults to 52. */\n  size?: number\n  /** Override the accent colour for this instance. */\n  color?: string\n  /**\n   * The record key's colour. Red since 1955, and the one colour the kit's\n   * theme has no token for — expressed as an opt-in variable with a red\n   * fallback, so a skin CAN claim `--uikit-record` without `control-theme`\n   * having to grow a token this wave.\n   */\n  recordColor?: string\n\n  labels?: TransportLabels\n  disabled?: boolean\n  /** Fires once, on the first gesture of any kind — resume an AudioContext here. */\n  onInteraction?: (event: InteractionEvent) => void\n\n  className?: string\n  style?: CSSProperties\n  \"aria-label\"?: string\n}\n\nexport function TransportControls({\n  playing = false,\n  onPlayingChange,\n  onStop,\n  loop = false,\n  onLoopChange,\n  recording = false,\n  onRecordChange,\n  tempo,\n  defaultTempo = DEFAULT_TEMPO,\n  onTempoChange,\n  tempoStep = 1,\n  tempoFineStep = 0.1,\n  division = \"1/8\",\n  divisions = COMMON_DIVISIONS,\n  onDivisionChange,\n  showDivisionReadout = true,\n  phase,\n  beat,\n  beatsPerBar = 4,\n  showBeat = true,\n  showTap,\n  showTempo = true,\n  tempoDisplay = \"text\",\n  tempoPreset = \"led-red\",\n  size = 52,\n  color,\n  recordColor = \"var(--uikit-record, #ef4444)\",\n  labels,\n  disabled = false,\n  onInteraction,\n  className,\n  style,\n  \"aria-label\": ariaLabel,\n}: TransportControlsProps) {\n  const accent = color ?? token.accent\n  const caption = { ...DEFAULT_LABELS, ...labels }\n  const diameter = Math.max(28, size)\n\n  const current = clampTempo(tempo ?? defaultTempo)\n  const tempoEditable = !!onTempoChange && !disabled\n  const segmentTempo = tempoDisplay === \"segment\"\n  const tapVisible = showTap ?? !!onTempoChange\n\n  const { notify } = useFirstInteraction(onInteraction)\n\n  /* ------------------------------------------------------------- the tempo */\n\n  const readoutRef = useRef<HTMLDivElement | null>(null)\n  // The live tempo, for handlers bound for the life of a gesture.\n  const latest = useRef(current)\n  latest.current = current\n\n  const commitTempo = useCallback(\n    (next: number) => {\n      if (!onTempoChange) return\n      onTempoChange(clampTempo(next))\n    },\n    [onTempoChange],\n  )\n\n  /**\n   * Where the drag started, and in which mode. Re-taken when Shift is pressed\n   * or released mid-gesture so the number does not jump as the sensitivity\n   * changes — the same anchor discipline `fader` uses.\n   */\n  const anchor = useRef<{ y: number; tempo: number; fine: boolean } | null>(null)\n\n  const drag = useSurfacePointer(\n    (state) => {\n      if (state.first) {\n        notify(state.event)\n        anchor.current = { y: state.clientY, tempo: latest.current, fine: state.fine }\n        return\n      }\n      const start =\n        anchor.current && anchor.current.fine === state.fine\n          ? anchor.current\n          : (anchor.current = { y: state.clientY, tempo: latest.current, fine: state.fine })\n\n      // Up is faster: y grows downward in the DOM and tempo does not.\n      const moved = start.y - state.clientY\n      const perBpm = TEMPO_DRAG_PX_PER_BPM * (state.fine ? FINE_FACTOR : 1)\n      const step = state.fine ? tempoFineStep : tempoStep\n      commitTempo(\n        snapToStep(start.tempo + moved / perBpm, TEMPO_MIN, TEMPO_MAX, step > 0 ? step : undefined),\n      )\n    },\n    { disabled: !tempoEditable, onEnd: () => (anchor.current = null) },\n  )\n\n  const wheel = useRef<{ value: number; fine: boolean } | null>(null)\n  const { wheeling } = useWheel(\n    readoutRef,\n    (state) => {\n      notify(state.event)\n      const fine = state.event.shiftKey\n      const step = (fine ? tempoFineStep : tempoStep) || 1\n      // Keep fractional scroll travel until the burst ends. Re-anchor when\n      // switching fine mode, matching the pointer gesture's modifier behavior.\n      const start = wheel.current && wheel.current.fine === fine\n        ? wheel.current\n        : (wheel.current = { value: latest.current, fine })\n      start.value = clampTempo(start.value + wheelDeltaForAxis(state, \"y\") * step)\n      commitTempo(snapToStep(start.value, TEMPO_MIN, TEMPO_MAX, step))\n    },\n    { disabled: !tempoEditable },\n  )\n\n  useEffect(() => {\n    if (!wheeling) wheel.current = null\n  }, [wheeling])\n\n  const handleTempoKey = (event: KeyboardEvent<HTMLDivElement>) => {\n    if (!tempoEditable) return\n    const fine = event.shiftKey\n    const step = (fine ? tempoFineStep : tempoStep) || 1\n    const large = step * 10\n    let next: number | null = null\n    if (event.key === \"ArrowUp\" || event.key === \"ArrowRight\") next = current + step\n    else if (event.key === \"ArrowDown\" || event.key === \"ArrowLeft\") next = current - step\n    else if (event.key === \"PageUp\") next = current + large\n    else if (event.key === \"PageDown\") next = current - large\n    else if (event.key === \"Home\") next = TEMPO_MIN\n    else if (event.key === \"End\") next = TEMPO_MAX\n    if (next === null) return\n    notify(event)\n    event.preventDefault()\n    commitTempo(snapToStep(next, TEMPO_MIN, TEMPO_MAX, step))\n  }\n\n  /* ---------------------------------------------------------------- the tap */\n\n  // Timestamps only. Transient gesture state, never a second copy of the tempo:\n  // the bpm this produces leaves through onTempoChange in the same tick.\n  const taps = useRef<number[]>([])\n  const [tapsInRun, setTapsInRun] = useState(0)\n\n  const handleTap = (event: SyntheticEvent) => {\n    if (!tempoEditable) return\n    notify(event)\n    const now =\n      typeof performance !== \"undefined\" && typeof performance.now === \"function\"\n        ? performance.now()\n        : Date.now()\n    const buffer = [...taps.current, now].slice(-TAP_WINDOW)\n    taps.current = buffer\n    // tapRun stops at a gap, so this counts the CURRENT phrase — a tap after a\n    // long pause reads as tap 1 without the buffer being cleared.\n    setTapsInRun(tapRun(buffer).length + 1)\n    const tapped = tapTempo(buffer)\n    if (tapped !== null) commitTempo(tapped)\n  }\n\n  /* --------------------------------------------------------------- the beat */\n\n  const beatPhase =\n    phase !== undefined && phase !== null\n      ? wrapPhase(phase)\n      : beat !== undefined && beat !== null && Number.isFinite(beat)\n        ? wrapPhase(beat)\n        : null\n  const bars = Number.isFinite(beatsPerBar) && beatsPerBar > 0 ? Math.floor(beatsPerBar) : 4\n  const downbeat =\n    beat !== undefined && beat !== null && Number.isFinite(beat)\n      ? Math.floor(((Math.floor(beat) % bars) + bars) % bars) === 0\n      : false\n  // No clock, no light: an idle floor rather than a lamp implying a beat.\n  const beatIntensity = beatPhase === null ? 0.12 : pulseIntensity(beatPhase, 0.12)\n\n  /* ----------------------------------------------------------- the division */\n\n  const divisionRow = useMemo(\n    () => divisions.map((id) => noteDivision(id)).filter((d): d is NonNullable<typeof d> => !!d),\n    [divisions],\n  )\n  const activeDivision = noteDivision(division)\n  const divisionSeconds = noteDivisionSeconds(current, division)\n  const divisionHz = noteDivisionHz(current, division)\n\n  /* -------------------------------------------------------------- rendering */\n\n  const keySize = diameter\n  const label =\n    ariaLabel ??\n    `Transport: ${playing ? \"playing\" : \"stopped\"}${loop ? \", looping\" : \"\"}${\n      recording ? \", recording\" : \"\"\n    }, ${formatTempo(current)} bpm, ${activeDivision?.name ?? \"custom\"} division`\n\n  /** A key with no callback: the same light, none of the affordance. */\n  const lamp = (text: string, lit: boolean, tint: string, name: string) => (\n    <span\n      role=\"img\"\n      aria-label={`${name}: ${lit ? \"on\" : \"off\"}`}\n      style={{\n        width: keySize,\n        height: keySize,\n        borderRadius: 999,\n        display: \"inline-flex\",\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        border: `1px solid ${token.border}`,\n        background: `radial-gradient(circle at 50% 30%, color-mix(in srgb, ${tint} ${\n          lit ? 26 : 8\n        }%, ${token.cap}), rgba(0,0,0,0.55))`,\n        boxShadow: lit\n          ? `0 0 ${Math.round(keySize * 0.22)}px color-mix(in srgb, ${tint} 40%, transparent)`\n          : \"none\",\n        color: lit ? token.text : token.textMuted,\n        fontSize: 11,\n        fontWeight: 700,\n        letterSpacing: \"0.14em\",\n        userSelect: \"none\",\n      }}\n    >\n      {text}\n    </span>\n  )\n\n  return (\n    <div\n      role=\"group\"\n      aria-label={label}\n      className={className}\n      style={{\n        display: \"inline-flex\",\n        flexDirection: \"column\",\n        gap: 12,\n        padding: 12,\n        borderRadius: token.radius,\n        border: `1px solid ${token.border}`,\n        background: token.surface,\n        fontFamily: token.font,\n        opacity: disabled ? 0.55 : 1,\n        ...style,\n      }}\n    >\n      {/* keys, beat, tempo */}\n      <div style={{ display: \"flex\", alignItems: \"center\", gap: 12, flexWrap: \"wrap\" }}>\n        <div style={{ display: \"inline-flex\", alignItems: \"center\", gap: 8 }}>\n          {onPlayingChange\n            ? (\n                <GlowButton\n                  mode=\"toggle\"\n                  pressed={playing}\n                  onPressedChange={onPlayingChange}\n                  onInteraction={notify}\n                  size={keySize}\n                  color={accent}\n                  disabled={disabled}\n                  aria-label=\"Play\"\n                >\n                  {caption.play}\n                </GlowButton>\n              )\n            : lamp(caption.play, playing, accent, \"Play\")}\n\n          {onStop && (\n            <GlowButton\n              mode=\"momentary\"\n              onPress={onStop}\n              onInteraction={notify}\n              size={keySize}\n              color={token.textMuted}\n              disabled={disabled}\n              aria-label=\"Stop\"\n            >\n              {caption.stop}\n            </GlowButton>\n          )}\n\n          {onLoopChange\n            ? (\n                <GlowButton\n                  mode=\"toggle\"\n                  pressed={loop}\n                  onPressedChange={onLoopChange}\n                  onInteraction={notify}\n                  size={keySize}\n                  color={token.accent2}\n                  disabled={disabled}\n                  aria-label=\"Loop\"\n                >\n                  {caption.loop}\n                </GlowButton>\n              )\n            : lamp(caption.loop, loop, token.accent2, \"Loop\")}\n\n          {/* No onRecordChange, no record key: a transport that cannot record\n              should not show one that pretends it can. */}\n          {onRecordChange && (\n            <GlowButton\n              mode=\"toggle\"\n              pressed={recording}\n              onPressedChange={onRecordChange}\n              onInteraction={notify}\n              size={keySize}\n              color={recordColor}\n              disabled={disabled}\n              aria-label=\"Record\"\n            >\n              {caption.record}\n            </GlowButton>\n          )}\n        </div>\n\n        {showBeat && (\n          <span\n            role=\"img\"\n            aria-label={\n              beatPhase === null\n                ? \"Beat indicator: no clock\"\n                : `Beat indicator${downbeat ? \", downbeat\" : \"\"}`\n            }\n            style={{\n              width: 14,\n              height: 14,\n              borderRadius: 999,\n              flex: \"0 0 auto\",\n              background: `color-mix(in srgb, ${\n                downbeat ? accent : token.accent2\n              } ${Math.round(15 + 85 * beatIntensity)}%, transparent)`,\n              boxShadow:\n                beatPhase === null\n                  ? \"none\"\n                  : `0 0 ${Math.round(2 + 12 * beatIntensity)}px color-mix(in srgb, ${\n                      downbeat ? accent : token.accent2\n                    } ${Math.round(70 * beatIntensity)}%, transparent)`,\n              border: `1px solid ${token.border}`,\n              transform: `scale(${(0.82 + 0.22 * beatIntensity).toFixed(3)})`,\n            }}\n          />\n        )}\n\n        {showTempo && (\n          <div style={{ display: \"inline-flex\", alignItems: \"center\", gap: 8 }}>\n            <div\n              ref={readoutRef}\n              // A spinbutton rather than a slider: the value is a number you\n              // read, and its arrow keys are the ones already wired below.\n              role={tempoEditable ? \"spinbutton\" : \"status\"}\n              aria-label=\"Tempo\"\n              aria-valuenow={tempoEditable ? current : undefined}\n              aria-valuemin={tempoEditable ? TEMPO_MIN : undefined}\n              aria-valuemax={tempoEditable ? TEMPO_MAX : undefined}\n              aria-valuetext={tempoEditable ? `${formatTempo(current)} bpm` : undefined}\n              tabIndex={tempoEditable ? 0 : undefined}\n              onPointerDown={drag.onPointerDown}\n              onKeyDown={handleTempoKey}\n              onDoubleClick={() => tempoEditable && commitTempo(defaultTempo)}\n              className=\"uikit-control\"\n              style={{\n                display: \"inline-flex\",\n                alignItems: \"baseline\",\n                gap: 4,\n                padding: segmentTempo ? 0 : \"4px 10px\",\n                borderRadius: token.radius,\n                border: segmentTempo ? \"none\" : `1px solid ${token.border}`,\n                background: segmentTempo\n                  ? \"transparent\"\n                  : `color-mix(in srgb, ${token.text} 4%, transparent)`,\n                fontFamily: token.fontMono,\n                color: token.text,\n                cursor: tempoEditable ? \"ns-resize\" : \"default\",\n                borderColor: drag.dragging ? accent : token.border,\n                touchAction: \"none\",\n                userSelect: \"none\",\n              }}\n            >\n              {segmentTempo\n                ? (\n                    // The readout is the only thing that changes: the drag, the\n                    // wheel, the arrows and the double-click reset all still\n                    // live on the wrapper above, so the segment face is a skin\n                    // rather than a second control.\n                    <SegmentDisplay\n                      type=\"seven\"\n                      value={formatTempo(current)}\n                      digits={4}\n                      align=\"right\"\n                      size={Math.round(keySize * 0.46)}\n                      preset={tempoPreset}\n                      frame\n                      // A tempo you can drag is a tempo that is being driven,\n                      // and the panel says so by coming up to full.\n                      brightness={drag.dragging ? 1 : 0.88}\n                      aria-label={`${formatTempo(current)} bpm`}\n                    />\n                  )\n                : (\n                    <>\n                      <span style={{ fontSize: Math.round(keySize * 0.42), fontWeight: 600 }}>\n                        {formatTempo(current)}\n                      </span>\n                      <span\n                        style={{\n                          fontSize: 9,\n                          letterSpacing: \"0.14em\",\n                          color: token.textMuted,\n                          fontFamily: token.font,\n                        }}\n                      >\n                        BPM\n                      </span>\n                    </>\n                  )}\n            </div>\n\n            {tapVisible && (\n              <GlowButton\n                mode=\"momentary\"\n                onPress={handleTap}\n                onInteraction={notify}\n                size={Math.round(keySize * 0.72)}\n                color={token.accent2}\n                disabled={disabled || !tempoEditable}\n                aria-label=\"Tap tempo\"\n              >\n                {tapsInRun > 1 ? String(tapsInRun) : caption.tap}\n              </GlowButton>\n            )}\n          </div>\n        )}\n      </div>\n\n      {/* the division row and what it means at this tempo */}\n      {divisionRow.length > 0 && (\n        <div style={{ display: \"flex\", alignItems: \"center\", gap: 10, flexWrap: \"wrap\" }}>\n          <div\n            role={onDivisionChange ? \"radiogroup\" : \"group\"}\n            aria-label=\"Note division\"\n            style={{\n              display: \"inline-flex\",\n              padding: 2,\n              gap: 2,\n              borderRadius: token.radius,\n              border: `1px solid ${token.border}`,\n              background: `color-mix(in srgb, ${token.text} 4%, transparent)`,\n            }}\n          >\n            {divisionRow.map((option) => {\n              const active = option.id === division\n              const chip: CSSProperties = {\n                minWidth: 40,\n                padding: \"3px 8px\",\n                // A literal rather than the radius token: these chips sit\n                // INSIDE a token-rounded frame with 2 px of inset, so\n                // inheriting its radius would round them past their own corner.\n                borderRadius: 5,\n                border: \"none\",\n                background: active\n                  ? `color-mix(in srgb, ${accent} 26%, transparent)`\n                  : \"transparent\",\n                color: active ? token.text : token.textMuted,\n                fontFamily: token.fontMono,\n                fontSize: 11,\n                letterSpacing: \"0.02em\",\n                textAlign: \"center\",\n                userSelect: \"none\",\n              }\n              return onDivisionChange ? (\n                <button\n                  key={option.id}\n                  type=\"button\"\n                  role=\"radio\"\n                  aria-checked={active}\n                  aria-label={`${option.name} note`}\n                  disabled={disabled}\n                  onClick={(event) => {\n                    notify(event)\n                    onDivisionChange(option.id)\n                  }}\n                  className=\"uikit-control\"\n                  style={{ ...chip, cursor: disabled ? \"not-allowed\" : \"pointer\" }}\n                >\n                  {option.label}\n                </button>\n              ) : (\n                <span key={option.id} aria-label={`${option.name} note`} style={chip}>\n                  {option.label}\n                </span>\n              )\n            })}\n          </div>\n\n          {/* The readout that makes a division mean something: the same\n              interval in the two units a host actually wires to. */}\n          {showDivisionReadout && (\n            <span\n              style={{\n                fontFamily: token.fontMono,\n                fontSize: 10,\n                color: token.textMuted,\n                whiteSpace: \"nowrap\",\n              }}\n            >\n              {formatEchoTime(divisionSeconds)} · {divisionHz.toFixed(2)} Hz\n            </span>\n          )}\n        </div>\n      )}\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@ui/transport-controls.tsx"
    }
  ]
}
