{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "core-dual-band-selector",
  "type": "registry:ui",
  "title": "Dual Band Selector",
  "description": "The Logic-style \"Low Cut … High Cut\" band bar: two caps on one log or linear frequency track with the band between them lit. Press the band to move both caps at their current width, press the open track to jump the nearer one, Shift for a fine pass, wheel and arrows on the focused cap. The caps stop against each other at a minimum ratio (log) or difference (linear) instead of crossing, and the taper, the gap and the band drag are a pure core with its own tests.",
  "dependencies": [
    "maruaudio@>=0.0.3 <0.1.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/ui/dual-band-selector.tsx",
      "content": "/**\n * Source: https://maru.audio/ui-kit/dual-band-selector\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 * DualBandSelector — the Logic-style \"Low Cut … High Cut\" band bar.\n *\n * One horizontal track from `min` to `max` (20 Hz to 20 kHz, normally), two\n * caps, and a lit band between them. It replaces the pair of knobs a tone\n * section usually gets, and it is a better instrument for the job: the two\n * frequencies are read *against each other* — what matters is where the band\n * sits and how wide it is — and a bar shows that at a glance where two knobs\n * make you compute it.\n *\n * What the gesture layer owes the user:\n *\n * - **Press anywhere.** A press in the open track jumps the nearer cap there\n *   and carries straight on into a drag, the way the kit's faders behave. A\n *   press *between* the caps grabs the band and moves both, keeping its width.\n * - **The caps cannot cross.** They stop against each other at `minGap` — a\n *   ratio on a log track, a difference on a linear one — instead of pushing\n *   their neighbour along, so a drag that runs into the other cap parks there\n *   and the band never inverts.\n * - **Shift is a fine pass**, relative and anchored where Shift went down, so\n *   holding it does not yank the cap back under the cursor.\n * - **Everything is reachable from the keyboard.** Each cap is its own\n *   `role=\"slider\"` in the tab order; arrows step it, Shift steps finely,\n *   Page keys jump, Home/End run it to its limit, and a cap's aria-valuemin /\n *   aria-valuemax report the limit the *other* cap imposes, which is the ARIA\n *   dual-thumb convention.\n *\n * The maths — the taper, the gap, the band drag, the readout format — is\n * `dual-band-model.ts` beside this file and is unit-tested there. Pointer\n * capture is `useSurfacePointer`, the non-passive wheel is `useWheel`, the key\n * decoding and step sizes are the kit's `useKeyboardStep` driven in normalised\n * track space, and the first-gesture report is `useFirstInteraction` — the same\n * four pieces every other control in the kit is built from.\n *\n * `DualBandSelectorHeadless` is the whole of that behaviour with no\n * appearance: it hands a render prop the positions and three prop bags to\n * spread (`trackProps` on the groove, `lowHandleProps` / `highHandleProps` on\n * the caps). `DualBandSelector` is one face for it — bracket caps, a lit band,\n * readouts above each cap — themed through `token.*` with an `accent`\n * override.\n *\n * Controlled only: the caps are engine parameters, and a control that also\n * remembered them would be a second source of truth for a filter that already\n * has one.\n */\n\nimport { useCallback, useRef, useState } from \"react\"\nimport type {\n  CSSProperties,\n  KeyboardEvent as ReactKeyboardEvent,\n  MouseEvent as ReactMouseEvent,\n  PointerEvent as ReactPointerEvent,\n  ReactNode,\n  RefObject,\n} from \"react\"\nimport { positionToValue01 } from \"maruaudio/ui/shared/knob-core\"\nimport { token } from \"maruaudio/ui/shared/theme\"\nimport { useKeyboardStep } from \"maruaudio/ui/shared/use-control\"\nimport { useSurfacePointer } from \"maruaudio/ui/shared/use-surface-pointer\"\nimport { useWheel, wheelDeltaForAxis } from \"maruaudio/ui/shared/use-wheel\"\nimport {\n  useFirstInteraction,\n  type InteractionEvent,\n} from \"maruaudio/ui/shared/use-first-interaction\"\nimport {\n  DUAL_BAND_KEY_STEP,\n  DUAL_BAND_KEY_STEP_FINE,\n  DUAL_BAND_WHEEL_STEP,\n  clampDualBandValue,\n  dragDualBand,\n  dualBandFrom01,\n  dualBandTo01,\n  dualBandZoneAt,\n  formatBandHz,\n  maxLowForHigh,\n  minHighForLow,\n  nearestDualBandHandle,\n  normalizeDualBandPair,\n  resolveDualBandMinGap,\n  setDualBandValue,\n  type DualBandHandle,\n  type DualBandPair,\n  type DualBandScale,\n  type DualBandTaper,\n  type DualBandZone,\n} from \"maruaudio/ui/shared/dual-band-model\"\n\n/** How much slower the Shift fine pass runs than the pointer. */\nconst FINE_FACTOR = 5\n\n/* --------------------------------------------------------------- headless -- */\n\n/** Spread onto the element that draws the groove. */\nexport interface DualBandTrackProps {\n  ref: RefObject<HTMLDivElement | null>\n  onPointerDown: (event: ReactPointerEvent<Element>) => void\n  style: CSSProperties\n}\n\n/** Spread onto the element that draws one cap. */\nexport interface DualBandHandleProps {\n  /**\n   * Collected so a press can move focus to the cap it grabbed. The pointer\n   * gesture calls `preventDefault`, which suppresses the browser's own\n   * click-to-focus, so without this a cap you just dragged would not take the\n   * arrow keys.\n   */\n  ref: (node: HTMLElement | null) => void\n  role: \"slider\"\n  tabIndex: number\n  \"aria-label\": string\n  \"aria-valuemin\": number\n  \"aria-valuemax\": number\n  \"aria-valuenow\": number\n  \"aria-valuetext\": string\n  \"aria-orientation\": \"horizontal\"\n  \"aria-disabled\"?: true\n  onPointerDown: (event: ReactPointerEvent<Element>) => void\n  onKeyDown: (event: ReactKeyboardEvent<Element>) => void\n  onDoubleClick: (event: ReactMouseEvent<Element>) => void\n  onFocus: () => void\n  onBlur: () => void\n  style: CSSProperties\n}\n\n/** What a face is handed on every render. */\nexport interface DualBandRenderState {\n  /** The pair as it will be reported — clamped and gap-corrected. */\n  low: number\n  high: number\n  /** The same two values as positions along the track, in [0..1]. */\n  lowT: number\n  highT: number\n  /** Which gesture is in flight, if any. */\n  dragging: DualBandZone | null\n  /** Which cap holds focus, for a focus ring. */\n  focused: DualBandHandle | null\n  disabled: boolean\n  /** The resolved formatter — the readouts and `aria-valuetext` agree. */\n  formatValue: (value: number) => string\n  trackProps: DualBandTrackProps\n  lowHandleProps: DualBandHandleProps\n  highHandleProps: DualBandHandleProps\n}\n\nexport interface DualBandSelectorHeadlessProps {\n  /** Engine values, e.g. Hz. */\n  low: number\n  high: number\n  min: number\n  max: number\n  /** \"log\" (default) or \"linear\" taper of the track. */\n  taper?: DualBandTaper\n  /** Smallest allowed high/low ratio (log) or difference (linear). */\n  minGap?: number\n  onLowChange: (value: number) => void\n  onHighChange: (value: number) => void\n  /** Gesture bracket for undo coalescing / host beginEdit-endEdit. */\n  onChangeStart?: (handle: DualBandHandle) => void\n  onChangeEnd?: (handle: DualBandHandle) => void\n  /** Fired once, synchronously inside the first gesture of any kind. */\n  onInteraction?: (event: InteractionEvent) => void\n  /** Readouts and `aria-valuetext`. Defaults to the model's Hz format. */\n  formatValue?: (value: number) => string\n  lowLabel?: string\n  highLabel?: string\n  ariaLabel?: string\n  disabled?: boolean\n  /** Double-click a cap resets it to this. */\n  defaultLow?: number\n  defaultHigh?: number\n  children: (state: DualBandRenderState) => ReactNode\n  className?: string\n  style?: CSSProperties\n}\n\n/**\n * The behaviour with no appearance. A face spreads `trackProps` on whatever it\n * draws as the groove and the two handle prop bags on its caps; positions\n * arrive as [0..1] fractions of the track.\n */\nexport function DualBandSelectorHeadless({\n  low,\n  high,\n  min,\n  max,\n  taper = \"log\",\n  minGap,\n  onLowChange,\n  onHighChange,\n  onChangeStart,\n  onChangeEnd,\n  onInteraction,\n  formatValue = formatBandHz,\n  lowLabel = \"Low Cut\",\n  highLabel = \"High Cut\",\n  ariaLabel,\n  disabled = false,\n  defaultLow,\n  defaultHigh,\n  children,\n  className,\n  style,\n}: DualBandSelectorHeadlessProps) {\n  const scale: DualBandScale = { min, max, taper }\n  const gap = resolveDualBandMinGap(minGap, taper)\n  // The host's pair, made legal before anything reads it: a face must never\n  // draw a crossed band because a parameter arrived stale.\n  const pair = normalizeDualBandPair({ low, high }, scale, minGap)\n\n  const trackRef = useRef<HTMLDivElement | null>(null)\n  const capRefs = useRef<Record<DualBandHandle, HTMLElement | null>>({ low: null, high: null })\n  const [activeZone, setActiveZone] = useState<DualBandZone | null>(null)\n  const [focused, setFocused] = useState<DualBandHandle | null>(null)\n\n  // Pointer and wheel handlers are bound outside React's synthetic system and\n  // must read the live pair rather than a stale closure.\n  const live = useRef({ pair, scale, minGap, onLowChange, onHighChange, onChangeStart, onChangeEnd })\n  live.current = { pair, scale, minGap, onLowChange, onHighChange, onChangeStart, onChangeEnd }\n\n  const { notify } = useFirstInteraction(onInteraction)\n\n  /* ------------------------------------------------------------- commit -- */\n\n  /**\n   * Report a new pair to the host, one `onChange` per cap that actually moved.\n   *\n   * The order is not arbitrary. A host that clamps each cap against the other\n   * as it applies them would reject the first of the two when the band moves\n   * toward it, so the cap leading the move is always reported first.\n   */\n  const commit = useCallback((next: DualBandPair) => {\n    const { pair: current, onLowChange: emitLow, onHighChange: emitHigh } = live.current\n    const lowMoved = next.low !== current.low\n    const highMoved = next.high !== current.high\n    if (!lowMoved && !highMoved) return\n    if (next.low > current.low) {\n      if (highMoved) emitHigh(next.high)\n      if (lowMoved) emitLow(next.low)\n    } else {\n      if (lowMoved) emitLow(next.low)\n      if (highMoved) emitHigh(next.high)\n    }\n  }, [])\n\n  /** Which caps a zone's gesture brackets. */\n  const handlesOf = useCallback(\n    (zone: DualBandZone): ReadonlyArray<DualBandHandle> =>\n      zone === \"band\" ? ([\"low\", \"high\"] as const) : ([zone] as const),\n    [],\n  )\n\n  const startGesture = useCallback(\n    (zone: DualBandZone) => {\n      for (const handle of handlesOf(zone)) live.current.onChangeStart?.(handle)\n    },\n    [handlesOf],\n  )\n\n  const endGesture = useCallback(\n    (zone: DualBandZone) => {\n      for (const handle of handlesOf(zone)) live.current.onChangeEnd?.(handle)\n    },\n    [handlesOf],\n  )\n\n  /* ------------------------------------------------------------ pointer -- */\n\n  /** Pointer x as a fraction of the track, or null before the track mounts. */\n  const trackT = useCallback((clientX: number): number | null => {\n    const rect = trackRef.current?.getBoundingClientRect()\n    if (!rect || rect.width === 0) return null\n    return positionToValue01(clientX, rect.left, rect.width)\n  }, [])\n\n  /**\n   * The gesture in flight. `anchor` is re-taken whenever Shift is toggled, so\n   * entering or leaving the fine pass does not teleport the value.\n   */\n  const gesture = useRef<{\n    zone: DualBandZone\n    anchor: { pair: DualBandPair; x: number }\n    fine: boolean\n  } | null>(null)\n\n  const drag = useSurfacePointer(\n    (state) => {\n      const active = gesture.current\n      if (!active) return\n      if (state.first) notify(state.event)\n\n      const rect = trackRef.current?.getBoundingClientRect()\n      const width = rect?.width ?? 0\n      if (width === 0) return\n\n      // Re-anchor on a fine-mode change: the relative pass has to start from\n      // where the value is *now*, not from where the gesture began.\n      if (state.first || state.fine !== active.fine) {\n        active.fine = state.fine\n        active.anchor = { pair: live.current.pair, x: state.clientX }\n      }\n\n      const { scale: liveScale, minGap: liveGap } = live.current\n      const delta01 = (state.clientX - active.anchor.x) / (width * (state.fine ? FINE_FACTOR : 1))\n\n      if (active.zone === \"band\") {\n        commit(dragDualBand(active.anchor.pair, delta01, liveScale))\n        return\n      }\n\n      const handle = active.zone\n      if (state.fine) {\n        const from = dualBandTo01(active.anchor.pair[handle], liveScale)\n        const next = dualBandFrom01(from + delta01, liveScale)\n        commit(setDualBandValue(live.current.pair, handle, next, liveScale, liveGap))\n        return\n      }\n\n      // Coarse: absolute, so the cap lands under the cursor and stays there.\n      const t = trackT(state.clientX)\n      if (t === null) return\n      commit(\n        setDualBandValue(live.current.pair, handle, dualBandFrom01(t, liveScale), liveScale, liveGap),\n      )\n    },\n    {\n      disabled,\n      onEnd: () => {\n        const active = gesture.current\n        gesture.current = null\n        setActiveZone(null)\n        if (active) endGesture(active.zone)\n      },\n    },\n  )\n\n  /** Begin a gesture on a known zone, then let the pointer hook take over. */\n  const beginOn = useCallback(\n    (zone: DualBandZone, event: ReactPointerEvent<Element>) => {\n      if (disabled) return\n      gesture.current = {\n        zone,\n        anchor: { pair: live.current.pair, x: event.clientX },\n        fine: event.shiftKey,\n      }\n      setActiveZone(zone)\n      startGesture(zone)\n      drag.onPointerDown(event)\n      // After the pointer hook, which is where `preventDefault` happens.\n      if (zone !== \"band\") {\n        setFocused(zone)\n        capRefs.current[zone]?.focus()\n      }\n    },\n    [disabled, drag, startGesture],\n  )\n\n  /** A press on the open track: the band moves, or the nearer cap jumps here. */\n  const onTrackPointerDown = useCallback(\n    (event: ReactPointerEvent<Element>) => {\n      if (disabled) return\n      const t = trackT(event.clientX)\n      if (t === null) return\n      const { pair: current, scale: liveScale } = live.current\n      const zone = dualBandZoneAt(\n        t,\n        dualBandTo01(current.low, liveScale),\n        dualBandTo01(current.high, liveScale),\n      )\n      beginOn(zone, event)\n    },\n    [disabled, trackT, beginOn],\n  )\n\n  /** A press on a cap grabs that cap, whatever the zone maths would say. */\n  const onHandlePointerDown = useCallback(\n    (handle: DualBandHandle) => (event: ReactPointerEvent<Element>) => {\n      if (disabled) return\n      // The caps sit inside the track, and a press on one is by definition on\n      // the band's edge — let it bubble and the zone test would grab the band.\n      event.stopPropagation()\n      beginOn(handle, event)\n    },\n    [disabled, beginOn],\n  )\n\n  /* -------------------------------------------------------------- wheel -- */\n\n  useWheel(\n    trackRef,\n    (state) => {\n      notify(state.event)\n      const { pair: current, scale: liveScale, minGap: liveGap } = live.current\n      const t = trackT(state.event.clientX)\n      const handle =\n        t === null\n          ? \"high\"\n          : nearestDualBandHandle(\n              t,\n              dualBandTo01(current.low, liveScale),\n              dualBandTo01(current.high, liveScale),\n            )\n      const notches = wheelDeltaForAxis(state, \"y\")\n      const step = DUAL_BAND_WHEEL_STEP * (state.event.shiftKey ? 1 / FINE_FACTOR : 1)\n      const next = dualBandFrom01(\n        dualBandTo01(current[handle], liveScale) + notches * step,\n        liveScale,\n      )\n      // One notch is one edit as far as a host is concerned: bracket it, so an\n      // undo step is a notch rather than a whole scroll.\n      live.current.onChangeStart?.(handle)\n      commit(setDualBandValue(current, handle, next, liveScale, liveGap))\n      live.current.onChangeEnd?.(handle)\n    },\n    { disabled },\n  )\n\n  /* ----------------------------------------------------------- keyboard -- */\n\n  /**\n   * The kit's shared key decoding, driven in normalised track space: arrows,\n   * Shift, Page keys and Home/End all behave exactly as they do on a knob or a\n   * fader, while the step covers the same slice of a log track at 30 Hz as at\n   * 15 kHz. What it commits is mapped back through the taper and past the gap.\n   */\n  const stepLow = useKeyboardStep({\n    value: dualBandTo01(pair.low, scale),\n    onCommit: (t) => {\n      const { pair: current, scale: liveScale, minGap: liveGap } = live.current\n      live.current.onChangeStart?.(\"low\")\n      commit(setDualBandValue(current, \"low\", dualBandFrom01(t, liveScale), liveScale, liveGap))\n      live.current.onChangeEnd?.(\"low\")\n    },\n    min: 0,\n    max: 1,\n    step: 0,\n    coarseStep: DUAL_BAND_KEY_STEP,\n    fineStep: DUAL_BAND_KEY_STEP_FINE,\n    disabled,\n  })\n\n  const stepHigh = useKeyboardStep({\n    value: dualBandTo01(pair.high, scale),\n    onCommit: (t) => {\n      const { pair: current, scale: liveScale, minGap: liveGap } = live.current\n      live.current.onChangeStart?.(\"high\")\n      commit(setDualBandValue(current, \"high\", dualBandFrom01(t, liveScale), liveScale, liveGap))\n      live.current.onChangeEnd?.(\"high\")\n    },\n    min: 0,\n    max: 1,\n    step: 0,\n    coarseStep: DUAL_BAND_KEY_STEP,\n    fineStep: DUAL_BAND_KEY_STEP_FINE,\n    disabled,\n  })\n\n  const onHandleKeyDown = useCallback(\n    (handle: DualBandHandle) => (event: ReactKeyboardEvent<Element>) => {\n      notify(event)\n      ;(handle === \"low\" ? stepLow : stepHigh)(event)\n    },\n    [notify, stepLow, stepHigh],\n  )\n\n  /* -------------------------------------------------------- double-click -- */\n\n  const onHandleDoubleClick = useCallback(\n    (handle: DualBandHandle) => () => {\n      if (disabled) return\n      const fallback = handle === \"low\" ? defaultLow : defaultHigh\n      if (fallback === undefined) return\n      const { pair: current, scale: liveScale, minGap: liveGap } = live.current\n      live.current.onChangeStart?.(handle)\n      commit(\n        setDualBandValue(\n          current,\n          handle,\n          clampDualBandValue(fallback, liveScale),\n          liveScale,\n          liveGap,\n        ),\n      )\n      live.current.onChangeEnd?.(handle)\n    },\n    [disabled, defaultLow, defaultHigh, commit],\n  )\n\n  /* ------------------------------------------------------------- render -- */\n\n  const handleProps = (handle: DualBandHandle): DualBandHandleProps => {\n    const isLow = handle === \"low\"\n    // The dual-thumb ARIA convention: a cap's reported range ends where the\n    // other cap's gap begins, so a screen reader announces the travel the user\n    // actually has rather than the track's full span.\n    const valueMin = isLow ? min : Math.min(max, Math.max(min, minHighForLow(pair.low, gap, taper)))\n    const valueMax = isLow ? Math.max(min, Math.min(max, maxLowForHigh(pair.high, gap, taper))) : max\n    return {\n      ref: (node: HTMLElement | null) => {\n        capRefs.current[handle] = node\n      },\n      role: \"slider\",\n      tabIndex: disabled ? -1 : 0,\n      \"aria-label\": isLow ? lowLabel : highLabel,\n      \"aria-valuemin\": valueMin,\n      \"aria-valuemax\": valueMax,\n      \"aria-valuenow\": pair[handle],\n      \"aria-valuetext\": formatValue(pair[handle]),\n      \"aria-orientation\": \"horizontal\",\n      ...(disabled ? { \"aria-disabled\": true as const } : {}),\n      onPointerDown: onHandlePointerDown(handle),\n      onKeyDown: onHandleKeyDown(handle),\n      onDoubleClick: onHandleDoubleClick(handle),\n      onFocus: () => setFocused(handle),\n      onBlur: () => setFocused((current) => (current === handle ? null : current)),\n      style: { touchAction: \"none\" },\n    }\n  }\n\n  return (\n    <div\n      className={className}\n      style={style}\n      role=\"group\"\n      aria-label={ariaLabel ?? `${lowLabel} and ${highLabel}`}\n      aria-disabled={disabled || undefined}\n    >\n      {children({\n        low: pair.low,\n        high: pair.high,\n        lowT: dualBandTo01(pair.low, scale),\n        highT: dualBandTo01(pair.high, scale),\n        dragging: drag.dragging ? activeZone : null,\n        focused,\n        disabled,\n        formatValue,\n        trackProps: {\n          ref: trackRef,\n          onPointerDown: onTrackPointerDown,\n          style: { touchAction: \"none\" },\n        },\n        lowHandleProps: handleProps(\"low\"),\n        highHandleProps: handleProps(\"high\"),\n      })}\n    </div>\n  )\n}\n\n/* ----------------------------------------------------------------- styled -- */\n\nexport type DualBandSelectorSize = \"sm\" | \"md\" | \"lg\"\n\ninterface DualBandSizeSpec {\n  /** Groove height. */\n  track: number\n  /** Cap height — taller than the groove, the way a bracket straddles it. */\n  cap: number\n  /** Cap hit area width; the bracket itself is drawn inside it. */\n  grab: number\n  labelFont: number\n  valueFont: number\n}\n\nconst SIZES: Record<DualBandSelectorSize, DualBandSizeSpec> = {\n  sm: { track: 10, cap: 18, grab: 16, labelFont: 8, valueFont: 10 },\n  md: { track: 14, cap: 24, grab: 20, labelFont: 9, valueFont: 11 },\n  lg: { track: 20, cap: 34, grab: 24, labelFont: 10, valueFont: 13 },\n}\n\nexport interface DualBandSelectorProps {\n  /** Engine values, e.g. Hz. */\n  low: number\n  high: number\n  min: number\n  max: number\n  /** \"log\" (default) or \"linear\" taper of the track. */\n  taper?: DualBandTaper\n  /** Smallest allowed high/low ratio (log) or difference (linear). */\n  minGap?: number\n  onLowChange: (value: number) => void\n  onHighChange: (value: number) => void\n  /** Gesture bracket for undo coalescing / host beginEdit-endEdit. */\n  onChangeStart?: (handle: DualBandHandle) => void\n  onChangeEnd?: (handle: DualBandHandle) => void\n  onInteraction?: (event: InteractionEvent) => void\n  formatValue?: (value: number) => string\n  lowLabel?: string\n  highLabel?: string\n  ariaLabel?: string\n  disabled?: boolean\n  /** Colour of the lit band; falls back to the control-theme accent. */\n  accent?: string\n  /** Track length. Fills the container by default. */\n  width?: number | string\n  size?: DualBandSelectorSize\n  /** Double-click a cap resets it to this. */\n  defaultLow?: number\n  defaultHigh?: number\n  style?: CSSProperties\n  className?: string\n}\n\n/**\n * The styled face: readouts above each cap, a lit band between them, and\n * bracket caps — `[` for the low cut, `]` for the high, the way Logic draws\n * the ends of a band.\n */\nexport function DualBandSelector({\n  size = \"md\",\n  accent,\n  width = \"100%\",\n  style,\n  className,\n  ...behaviour\n}: DualBandSelectorProps) {\n  const spec = SIZES[size]\n  const lit = accent ?? token.accent\n  // Half a cap of padding at each end, so a cap on a rail overhangs into the\n  // padding instead of off the control — the track's own box stays the exact\n  // space the pointer maps onto.\n  const inset = spec.grab / 2\n\n  return (\n    <DualBandSelectorHeadless\n      {...behaviour}\n      className={`uikit-control${className ? ` ${className}` : \"\"}`}\n      style={{\n        width,\n        paddingLeft: inset,\n        paddingRight: inset,\n        boxSizing: \"border-box\",\n        display: \"flex\",\n        flexDirection: \"column\",\n        gap: 4,\n        fontFamily: token.font,\n        opacity: behaviour.disabled ? 0.5 : 1,\n        ...style,\n      }}\n    >\n      {({ lowT, highT, low, high, dragging, focused, disabled, formatValue, trackProps, lowHandleProps, highHandleProps }) => {\n        const readout = (\n          handle: DualBandHandle,\n          t: number,\n          value: number,\n          label: string,\n        ) => (\n          <div\n            key={handle}\n            aria-hidden\n            style={{\n              position: \"absolute\",\n              left: `${t * 100}%`,\n              // Slides its own width as the cap crosses the track, so the\n              // readout is centred in the middle and flush at either rail\n              // instead of hanging off the end.\n              transform: `translateX(${-t * 100}%)`,\n              display: \"flex\",\n              flexDirection: \"column\",\n              alignItems: t < 0.5 ? \"flex-start\" : \"flex-end\",\n              lineHeight: 1.1,\n              whiteSpace: \"nowrap\",\n              pointerEvents: \"none\",\n            }}\n          >\n            <span\n              style={{\n                fontSize: spec.labelFont,\n                letterSpacing: \"0.08em\",\n                textTransform: \"uppercase\",\n                color: token.textMuted,\n              }}\n            >\n              {label}\n            </span>\n            <span\n              style={{\n                fontSize: spec.valueFont,\n                fontFamily: token.fontMono,\n                color: focused === handle || dragging === handle ? lit : token.text,\n              }}\n            >\n              {formatValue(value)}\n            </span>\n          </div>\n        )\n\n        const cap = (\n          handle: DualBandHandle,\n          t: number,\n          props: DualBandHandleProps,\n        ) => {\n          const isLow = handle === \"low\"\n          const active = dragging === handle || dragging === \"band\"\n          const ring = focused === handle\n          return (\n            <div\n              {...props}\n              style={{\n                ...props.style,\n                position: \"absolute\",\n                top: (spec.track - spec.cap) / 2,\n                left: `${t * 100}%`,\n                transform: \"translateX(-50%)\",\n                width: spec.grab,\n                height: spec.cap,\n                display: \"flex\",\n                alignItems: \"center\",\n                justifyContent: \"center\",\n                cursor: disabled ? \"not-allowed\" : \"ew-resize\",\n                outline: \"none\",\n                borderRadius: 3,\n                boxShadow: ring ? `0 0 0 2px ${token.focus}` : undefined,\n              }}\n            >\n              {/* The bracket: a stem with a foot at each end, turned toward the\n                  band — \"[\" on the low cap, \"]\" on the high. */}\n              <span\n                aria-hidden\n                style={{\n                  width: Math.max(5, spec.grab * 0.35),\n                  height: \"100%\",\n                  boxSizing: \"border-box\",\n                  borderTop: `2px solid ${lit}`,\n                  borderBottom: `2px solid ${lit}`,\n                  ...(isLow\n                    ? { borderLeft: `3px solid ${lit}` }\n                    : { borderRight: `3px solid ${lit}` }),\n                  filter: active ? `drop-shadow(0 0 4px ${lit})` : undefined,\n                }}\n              />\n            </div>\n          )\n        }\n\n        return (\n          <>\n            {/* Readouts, in their own row so the track keeps its full width. */}\n            <div\n              style={{\n                position: \"relative\",\n                height: spec.labelFont + spec.valueFont + 4,\n              }}\n            >\n              {readout(\"low\", lowT, low, behaviour.lowLabel ?? \"Low Cut\")}\n              {readout(\"high\", highT, high, behaviour.highLabel ?? \"High Cut\")}\n            </div>\n\n            <div\n              {...trackProps}\n              style={{\n                ...trackProps.style,\n                position: \"relative\",\n                height: spec.track,\n                borderRadius: 999,\n                background: token.track,\n                boxShadow: \"inset 0 1px 3px rgba(0,0,0,0.45)\",\n                cursor: disabled ? \"not-allowed\" : \"pointer\",\n              }}\n            >\n              {/* The lit band between the caps. */}\n              <div\n                aria-hidden\n                className={dragging ? undefined : \"uikit-animated\"}\n                style={{\n                  position: \"absolute\",\n                  top: 0,\n                  bottom: 0,\n                  left: `${lowT * 100}%`,\n                  width: `${Math.max(0, highT - lowT) * 100}%`,\n                  borderRadius: 999,\n                  background: `linear-gradient(to right, ${lit}, ${token.accent2})`,\n                  boxShadow: dragging ? `0 0 8px ${lit}` : `0 0 4px ${lit}`,\n                }}\n              />\n              {cap(\"low\", lowT, lowHandleProps)}\n              {cap(\"high\", highT, highHandleProps)}\n            </div>\n          </>\n        )\n      }}\n    </DualBandSelectorHeadless>\n  )\n}\n",
      "type": "registry:component",
      "target": "@ui/dual-band-selector.tsx"
    }
  ]
}
