{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"segmented-control-clean","type":"registry:ui","title":"Segmented Control (Clean)","description":"Switch between two to six options – a raised pill or underline that glides, stretches a little and can be dragged across, with icons, counts and badges","dependencies":[],"files":[{"path":"registry/segmented-control-clean/segmented-control-clean.tsx","type":"registry:ui","content":"\"use client\";\n\nimport {\n  useEffect,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n  type KeyboardEvent,\n  type PointerEvent as ReactPointerEvent,\n  type ReactNode,\n} from \"react\";\n\n/*\n * Segmented Control (Clean) – switch between two to six options, in a single self-contained file.\n * Clean style: \"buttons\" (the default): separate quiet chips with a hairline, the active one filled light gray;\n * \"tray\": a compact light-gray track with a white raised pill; \"underline\": text tabs with a sliding line.\n * The indicator slides to the picked option, short and crisp – its leading edge runs a touch ahead.\n * Labels keep one weight, so nothing shifts; when a label or count changes the control morphs to its new size.\n * Arrow keys, Home and End move and select; press the pill and drag it across the options to pick as you go.\n * If it doesn't fit (phones), it scrolls sideways with soft edges and keeps the active option in view.\n * The styles are injected below, so no extra files are needed.\n */\n\nexport type SegmentOption<T extends string = string> = {\n  value: T;\n  label?: ReactNode;\n  icon?: ReactNode;\n  /** A small gray number after the label, e.g. unread items */\n  count?: number | string;\n  /** A small tinted tag after the label, e.g. \"Save 20%\" */\n  badge?: ReactNode;\n  disabled?: boolean;\n  /** Accessible name – needed when the option only shows an icon */\n  ariaLabel?: string;\n  /** With kind=\"tabs\": the id of the panel this tab shows */\n  controls?: string;\n};\n\nexport type SegmentedControlProps<T extends string = string> = {\n  options: SegmentOption<T>[];\n  /** Controlled value */\n  value?: T;\n  /** Starting value when uncontrolled – defaults to the first enabled option */\n  defaultValue?: T;\n  onChange?: (value: T) => void;\n  size?: \"sm\" | \"md\" | \"lg\";\n  /** \"buttons\" = separate chips, the active one filled; \"tray\" = light track with a raised pill; \"underline\" = text tabs with a sliding line */\n  variant?: \"buttons\" | \"tray\" | \"underline\";\n  /** Fill the container with equal segments */\n  fullWidth?: boolean;\n  /** \"radio\" (a radiogroup, the default) or \"tabs\" (a tablist that switches panels) */\n  kind?: \"radio\" | \"tabs\";\n  disabled?: boolean;\n  \"aria-label\"?: string;\n  \"aria-labelledby\"?: string;\n  className?: string;\n};\n\nexport function SegmentedControl<T extends string = string>({\n  options: rawOptions,\n  value,\n  defaultValue,\n  onChange,\n  size: sizeProp = \"md\",\n  variant: variantProp = \"buttons\",\n  fullWidth = false,\n  kind = \"radio\",\n  disabled = false,\n  \"aria-label\": ariaLabel,\n  \"aria-labelledby\": ariaLabelledby,\n  className,\n}: SegmentedControlProps<T>) {\n  // Gaps and repeated values are dropped – one value, one option\n  const options = useMemo(() => {\n    const seen = new Set<string>();\n    return (Array.isArray(rawOptions) ? rawOptions : []).filter((o): o is SegmentOption<T> => {\n      if (!o || o.value == null || seen.has(String(o.value))) return false;\n      seen.add(String(o.value));\n      return true;\n    });\n  }, [rawOptions]);\n  // Unknown sizes and variants fall back to the defaults\n  const size = sizeProp === \"sm\" || sizeProp === \"lg\" ? sizeProp : \"md\";\n  const variant = variantProp === \"tray\" || variantProp === \"underline\" ? variantProp : \"buttons\";\n  const [inner, setInner] = useState<T | undefined>(() => defaultValue ?? options.find((o) => !o.disabled)?.value);\n  const current = value !== undefined ? value : inner;\n\n  const root = useRef<HTMLDivElement>(null);\n  const scroller = useRef<HTMLDivElement>(null);\n  const track = useRef<HTMLDivElement>(null);\n  const pill = useRef<HTMLSpanElement>(null);\n  // Latest value without waiting for a render – dragging picks several options in one frame\n  const latest = useRef(current);\n  latest.current = current;\n  const gliding = useRef(false);\n  const glideTimer = useRef(0);\n  const placed = useRef<T | undefined>(undefined);\n  const press = useRef<{ id: number; touch: boolean; drag: boolean; x: number; moved: boolean } | null>(null);\n\n  const buttons = () => Array.from(track.current?.querySelectorAll<HTMLElement>(\":scope > .ui-segc__opt\") ?? []);\n  const activeIndex = options.findIndex((o) => o.value === current);\n  const tabbable = activeIndex >= 0 ? activeIndex : options.findIndex((o) => !o.disabled);\n\n  const select = (v: T) => {\n    const opt = options.find((o) => o.value === v);\n    if (!opt || opt.disabled || disabled || v === latest.current) return;\n    latest.current = v;\n    if (value === undefined) setInner(v);\n    onChange?.(v);\n  };\n\n  /** Buttons get the width of their content, so a changed label or count makes them – and the control – glide to the new size */\n  const measure = () => {\n    const els = buttons();\n    if (!els.length || !track.current) return;\n    const cs = getComputedStyle(els[0]);\n    const pad = parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight);\n    const widths = els.map((b) => (b.firstElementChild as HTMLElement).offsetWidth + pad);\n    if (fullWidth) {\n      track.current.style.setProperty(\"--ui-segc-min\", `${Math.max(...widths)}px`);\n      els.forEach((b) => (b.style.width = \"\"));\n    } else els.forEach((b, i) => (b.style.width = `${widths[i]}px`));\n  };\n\n  /** Moves the pill (or line) under the active option. Without `animate` it follows at once – unless a glide is still running */\n  const place = (animate: boolean) => {\n    const t = track.current;\n    const p = pill.current;\n    if (!t || !p) return;\n    const btn = t.querySelector<HTMLElement>(\":scope > .ui-segc__opt[data-active]\");\n    if (!btn || !btn.offsetWidth) {\n      p.style.opacity = \"0\";\n      return;\n    }\n    let x = btn.offsetLeft;\n    let w = btn.offsetWidth;\n    // The line sits under the text only, not under the whole button\n    if (variant === \"underline\") {\n      const content = btn.firstElementChild as HTMLElement;\n      x += content.offsetLeft;\n      w = content.offsetWidth;\n    }\n    const instant = !p.dataset.ready || (!animate && !gliding.current);\n    const prev = parseFloat(p.style.left) || 0;\n    // The edge facing the target leads, the other one trails – the stretch\n    if (!instant && x !== prev) p.dataset.dir = x > prev ? \"right\" : \"left\";\n    if (instant) p.style.transition = \"none\";\n    p.style.opacity = \"1\";\n    p.style.left = `${x}px`;\n    p.style.right = `${t.clientWidth - x - w}px`;\n    if (instant) {\n      p.dataset.ready = \"1\";\n      void p.offsetWidth;\n      p.style.transition = \"\";\n    }\n  };\n\n  /** Soft edges only on the sides that can scroll */\n  const fades = () => {\n    const s = scroller.current;\n    if (!s) return;\n    const max = s.scrollWidth - s.clientWidth;\n    root.current?.toggleAttribute(\"data-overflow\", max > 1);\n    s.style.setProperty(\"--ui-segc-fade-l\", s.scrollLeft > 1 ? \"24px\" : \"0px\");\n    s.style.setProperty(\"--ui-segc-fade-r\", s.scrollLeft < max - 1 ? \"24px\" : \"0px\");\n  };\n\n  /** Keeps the active option in view when the control scrolls */\n  const reveal = (smooth: boolean) => {\n    const s = scroller.current;\n    const t = track.current;\n    const btn = t?.querySelector<HTMLElement>(\":scope > .ui-segc__opt[data-active]\");\n    if (!s || !t || !btn || s.scrollWidth <= s.clientWidth + 1) return;\n    const left = t.offsetLeft + btn.offsetLeft - (s.clientWidth - btn.offsetWidth) / 2;\n    s.scrollTo({ left: Math.max(0, left), behavior: smooth ? \"smooth\" : \"auto\" });\n  };\n\n  // Sizes: measured on mount and whenever a label, the container or the font changes\n  useLayoutEffect(() => {\n    const t = track.current;\n    const s = scroller.current;\n    if (!t || !s) return;\n    const sync = () => {\n      measure();\n      place(false);\n      fades();\n    };\n    sync();\n    // Width transitions only after the first measure, so nothing grows in on load\n    const ready = requestAnimationFrame(() => root.current?.setAttribute(\"data-ready\", \"\"));\n    // One frame later, so setting widths inside the observer never loops\n    let frame = 0;\n    const ro = new ResizeObserver(() => {\n      cancelAnimationFrame(frame);\n      frame = requestAnimationFrame(sync);\n    });\n    ro.observe(s);\n    ro.observe(t);\n    t.querySelectorAll(\".ui-segc__opt, .ui-segc__content\").forEach((el) => ro.observe(el));\n    return () => {\n      ro.disconnect();\n      cancelAnimationFrame(frame);\n      cancelAnimationFrame(ready);\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [options.length, fullWidth, variant, size]);\n\n  // A new value: the pill glides there, focus follows if it was inside, and a scrolling control brings it into view\n  useLayoutEffect(() => {\n    if (placed.current === current) return;\n    const first = placed.current === undefined;\n    placed.current = current;\n    if (!first) {\n      gliding.current = true;\n      clearTimeout(glideTimer.current);\n      glideTimer.current = window.setTimeout(() => (gliding.current = false), 240);\n    }\n    place(!first);\n    reveal(!first);\n    const t = track.current;\n    if (!first && t && t.contains(document.activeElement)) buttons()[activeIndex]?.focus({ preventScroll: true });\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [current]);\n\n  useEffect(() => () => clearTimeout(glideTimer.current), []);\n\n  /* ---------- Keyboard ---------- */\n\n  const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n    const enabled = options.filter((o) => !o.disabled);\n    if (!enabled.length || disabled) return;\n    const prevKeys = kind === \"radio\" ? [\"ArrowLeft\", \"ArrowUp\"] : [\"ArrowLeft\"];\n    const nextKeys = kind === \"radio\" ? [\"ArrowRight\", \"ArrowDown\"] : [\"ArrowRight\"];\n    const at = enabled.findIndex((o) => o.value === latest.current);\n    let target: SegmentOption<T> | undefined;\n    if (prevKeys.includes(e.key)) target = enabled[(at - 1 + enabled.length) % enabled.length];\n    else if (nextKeys.includes(e.key)) target = enabled[(at + 1) % enabled.length];\n    else if (e.key === \"Home\") target = enabled[0];\n    else if (e.key === \"End\") target = enabled[enabled.length - 1];\n    if (!target) return;\n    e.preventDefault();\n    select(target.value);\n    buttons()[options.indexOf(target)]?.focus();\n  };\n\n  /* ---------- Pointer: press feedback and dragging across options ---------- */\n\n  /** The option under (or nearest to) a horizontal pointer position */\n  const optionAt = (clientX: number) => {\n    let best = -1;\n    let dist = Infinity;\n    buttons().forEach((b, i) => {\n      const r = b.getBoundingClientRect();\n      const d = clientX < r.left ? r.left - clientX : clientX > r.right ? clientX - r.right : 0;\n      if (d < dist) {\n        dist = d;\n        best = i;\n      }\n    });\n    return options[best];\n  };\n\n  const endPress = () => {\n    press.current = null;\n    root.current?.removeAttribute(\"data-pressed\");\n  };\n\n  const onPointerDown = (e: ReactPointerEvent<HTMLDivElement>) => {\n    if (disabled || e.button !== 0) return;\n    const opt = optionAt(e.clientX);\n    if (!opt || opt.disabled) return;\n    const touch = e.pointerType === \"touch\";\n    // Touch: picks on lift (so swiping to scroll doesn't pick), but the pill itself can be dragged right away\n    const drag = !touch || opt.value === latest.current;\n    press.current = { id: e.pointerId, touch, drag, x: e.clientX, moved: false };\n    if (drag) root.current?.setAttribute(\"data-pressed\", \"\");\n    if (!touch) {\n      select(opt.value);\n      try {\n        e.currentTarget.setPointerCapture(e.pointerId);\n      } catch {\n        /* capture is a nicety */\n      }\n    }\n  };\n\n  const onPointerMove = (e: ReactPointerEvent<HTMLDivElement>) => {\n    const p = press.current;\n    if (!p || p.id !== e.pointerId) return;\n    if (!p.moved && Math.abs(e.clientX - p.x) < (p.touch ? 8 : 3)) return;\n    p.moved = true;\n    if (!p.drag) return;\n    const opt = optionAt(e.clientX);\n    if (opt && !opt.disabled) select(opt.value);\n  };\n\n  const onPointerUp = (e: ReactPointerEvent<HTMLDivElement>) => {\n    const p = press.current;\n    if (!p || p.id !== e.pointerId) return;\n    if (p.touch && !p.moved) {\n      const opt = optionAt(e.clientX);\n      if (opt) select(opt.value);\n    }\n    endPress();\n  };\n\n  const isTabs = kind === \"tabs\";\n\n  return (\n    <div\n      ref={root}\n      className={[\"ui-segc\", className].filter(Boolean).join(\" \")}\n      data-size={size}\n      data-variant={variant}\n      data-full={fullWidth || undefined}\n      data-disabled={disabled || undefined}\n    >\n      {/* React 19 hoists this into <head> once, no matter how many controls render */}\n      <style href=\"ui-segmented-control-clean\" precedence=\"default\">\n        {css}\n      </style>\n\n      <div ref={scroller} className=\"ui-segc__scroller\" onScroll={fades}>\n        <div\n          ref={track}\n          className=\"ui-segc__track\"\n          role={isTabs ? \"tablist\" : \"radiogroup\"}\n          aria-label={ariaLabel}\n          aria-labelledby={ariaLabelledby}\n          aria-disabled={disabled || undefined}\n          aria-orientation={isTabs ? \"horizontal\" : undefined}\n          onKeyDown={onKeyDown}\n          onPointerDown={onPointerDown}\n          onPointerMove={onPointerMove}\n          onPointerUp={onPointerUp}\n          onPointerCancel={endPress}\n          onLostPointerCapture={endPress}\n        >\n          <span ref={pill} className=\"ui-segc__pill\" aria-hidden=\"true\" />\n          {options.map((o, i) => {\n            const active = i === activeIndex;\n            // Without a label or icon the value names the option; NaN counts show nothing\n            const label = o.label ?? (o.icon ? null : String(o.value));\n            const count = countText(o.count);\n            return (\n              <button\n                key={String(o.value)}\n                type=\"button\"\n                className=\"ui-segc__opt\"\n                role={isTabs ? \"tab\" : \"radio\"}\n                aria-checked={isTabs ? undefined : active}\n                aria-selected={isTabs ? active : undefined}\n                aria-controls={isTabs ? o.controls : undefined}\n                aria-label={o.ariaLabel}\n                data-active={active || undefined}\n                disabled={o.disabled || disabled}\n                tabIndex={i === tabbable ? 0 : -1}\n                // Keyboard (Enter / Space) and screen readers – pointers are handled on the track\n                onClick={() => select(o.value)}\n              >\n                <span className=\"ui-segc__content\">\n                  {o.icon && (\n                    <span className=\"ui-segc__icon\" onErrorCapture={(e) => ((e.target as HTMLElement).style.visibility = \"hidden\")}>\n                      {o.icon}\n                    </span>\n                  )}\n                  {/* One weight for both states – only the color changes, so nothing shifts */}\n                  {label != null && (\n                    <span className=\"ui-segc__label\" dir=\"auto\">\n                      {label}\n                    </span>\n                  )}\n                  {count != null && count !== \"\" && (\n                    <span key={String(count)} className=\"ui-segc__count\">\n                      {count}\n                    </span>\n                  )}\n                  {o.badge != null && <span className=\"ui-segc__badge\">{o.badge}</span>}\n                </span>\n              </button>\n            );\n          })}\n        </div>\n      </div>\n    </div>\n  );\n}\n\n\n/** 1284 → \"1,284\"; NaN and Infinity show nothing */\nconst countText = (c: number | string | undefined) => (typeof c === \"number\" ? (Number.isFinite(c) ? c.toLocaleString(\"en-US\") : null) : c);\n\nconst css = /* css */ `\n/* Animatable custom properties: the equal segment width and the soft edges glide too */\n@property --ui-segc-min { syntax: \"<length>\"; inherits: true; initial-value: 0px; }\n@property --ui-segc-fade-l { syntax: \"<length>\"; inherits: false; initial-value: 0px; }\n@property --ui-segc-fade-r { syntax: \"<length>\"; inherits: false; initial-value: 0px; }\n\n.ui-segc {\n  --ui-segc-ink: var(--aui-ink, rgba(0, 0, 0, 0.88));\n  --ui-segc-text: var(--aui-muted, rgba(0, 0, 0, 0.61));\n  --ui-segc-hover-text: rgba(0, 0, 0, 0.75);\n  --ui-segc-line: var(--aui-border, rgba(0, 0, 0, 0.06));\n  --ui-segc-wash: var(--aui-subtle, rgba(0, 0, 0, 0.03));\n  --ui-segc-fill: rgba(0, 0, 0, 0.06);\n  --ui-segc-track: var(--aui-subtle, rgba(0, 0, 0, 0.04));\n  --ui-segc-card: var(--aui-surface, #ffffff);\n  --ui-segc-card-shadow: 0 0 0 0.5px rgba(0, 0, 0, 0.06), 0 1px 1px -1px rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.05);\n  --ui-segc-accent: rgba(0, 96, 226, 0.765);\n  --ui-segc-accent-soft: rgba(0, 96, 226, 0.08);\n  --ui-segc-focus: var(--aui-focus, rgba(0, 96, 226, 0.7));\n  --ui-segc-ease: cubic-bezier(0.25, 0.9, 0.3, 1);\n  /* Size: md – 28px chips, 4px 10px */\n  --ui-segc-h: 28px;\n  --ui-segc-px: 10px;\n  --ui-segc-fs: 13px;\n  --ui-segc-lh: 20px;\n  --ui-segc-icon: 16px;\n  --ui-segc-gap: 8px;\n  --ui-segc-pad: 2px;\n  --ui-segc-ring: 4px;\n  box-sizing: border-box;\n  display: inline-flex;\n  max-width: 100%;\n  min-width: 0;\n  vertical-align: middle;\n  border-radius: calc(var(--aui-radius, 7px) + 1px);\n  font-family: var(--aui-font, \"Inter\", \"InterVariable\", ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif);\n  font-size: var(--ui-segc-fs);\n  font-weight: 500;\n  line-height: var(--ui-segc-lh);\n  color: var(--ui-segc-text);\n  -webkit-font-smoothing: antialiased;\n}\n.ui-segc[data-size=\"sm\"] { --ui-segc-h: 24px; --ui-segc-px: 8px; --ui-segc-fs: 12px; --ui-segc-lh: 16px; --ui-segc-icon: 14px; --ui-segc-gap: 6px; }\n.ui-segc[data-size=\"lg\"] { --ui-segc-h: 32px; --ui-segc-px: 12px; --ui-segc-icon: 16px; --ui-segc-gap: 8px; }\n.ui-segc[data-full] { display: flex; width: 100%; }\n.ui-segc[data-disabled] { opacity: 0.5; pointer-events: none; }\n.ui-segc *, .ui-segc *::before, .ui-segc *::after { box-sizing: border-box; }\n:where(.ui-segc) button { margin: 0; padding: 0; border: 0; background: none; font: inherit; color: inherit; cursor: pointer; }\n:where(.ui-segc) svg { display: block; flex: none; }\n\n/* Scrolls sideways only when it doesn't fit; the padding keeps focus rings from being cut off */\n.ui-segc__scroller {\n  position: relative;\n  flex: 1;\n  min-width: 0;\n  overflow-x: auto;\n  overflow-y: hidden;\n  padding: var(--ui-segc-pad);\n  border-radius: inherit;\n  overscroll-behavior-x: contain;\n  scrollbar-width: none;\n  -webkit-mask-image: linear-gradient(to right, transparent, #000 var(--ui-segc-fade-l), #000 calc(100% - var(--ui-segc-fade-r)), transparent);\n  mask-image: linear-gradient(to right, transparent, #000 var(--ui-segc-fade-l), #000 calc(100% - var(--ui-segc-fade-r)), transparent);\n  transition: --ui-segc-fade-l 160ms ease, --ui-segc-fade-r 160ms ease;\n}\n.ui-segc__scroller::-webkit-scrollbar { display: none; }\n\n.ui-segc__track {\n  position: relative;\n  display: flex;\n  width: max-content;\n  min-width: 100%;\n  /* Horizontal drags move the indicator; vertical ones still scroll the page */\n  touch-action: pan-y;\n  -webkit-user-select: none;\n  user-select: none;\n  -webkit-tap-highlight-color: transparent;\n}\n/* When it scrolls, swipes scroll it instead of dragging the indicator */\n.ui-segc[data-overflow] .ui-segc__track { touch-action: pan-x pan-y; }\n/* Equal segments, each as wide as the widest one needs */\n.ui-segc[data-full] .ui-segc__track { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(var(--ui-segc-min), 1fr); }\n.ui-segc[data-ready] .ui-segc__track { transition: --ui-segc-min 200ms var(--ui-segc-ease); }\n\n/* An option */\n.ui-segc__opt {\n  position: relative;\n  z-index: 1;\n  display: flex;\n  flex: none;\n  align-items: center;\n  justify-content: center;\n  height: var(--ui-segc-h);\n  padding: 0 var(--ui-segc-px);\n  overflow: clip;\n  border-radius: calc(var(--aui-radius, 7px) - 1px);\n  white-space: nowrap;\n  /* Hover washes are short and crisp, in and out */\n  transition: color 90ms ease, background-color 90ms ease;\n}\n.ui-segc[data-ready]:not([data-full]) .ui-segc__opt { transition: color 90ms ease, background-color 90ms ease, width 200ms var(--ui-segc-ease); }\n.ui-segc__opt[data-active] { color: var(--ui-segc-ink); }\n.ui-segc__opt:disabled { opacity: 0.4; cursor: not-allowed; }\n.ui-segc__opt:focus-visible { outline: 2px solid var(--ui-segc-focus); outline-offset: -2px; }\n@media (hover: hover) {\n  .ui-segc__opt:not([data-active]):not(:disabled):hover { background-color: var(--ui-segc-wash); }\n}\n\n.ui-segc__content { display: inline-flex; flex: none; align-items: center; gap: 6px; }\n.ui-segc[data-size=\"sm\"] .ui-segc__content { gap: 5px; }\n.ui-segc__icon { display: grid; place-items: center; width: var(--ui-segc-icon); height: var(--ui-segc-icon); color: var(--aui-muted, rgba(0, 0, 0, 0.447)); transition: color 90ms ease; }\n.ui-segc__opt[data-active] .ui-segc__icon { color: var(--ui-segc-ink); }\n.ui-segc__icon svg, .ui-segc__icon img { width: 100%; height: 100%; }\n/* Images fill the tile without leaving it, whatever their shape */\n.ui-segc__icon { overflow: hidden; }\n.ui-segc__icon img { display: block; object-fit: contain; }\n.ui-segc__label { display: block; }\n\n.ui-segc__count {\n  display: inline-grid;\n  place-items: center;\n  min-width: 16px;\n  height: 16px;\n  padding: 0 4px;\n  border-radius: var(--aui-radius-sm, 5px);\n  background: var(--ui-segc-fill);\n  font-size: 11px;\n  line-height: 16px;\n  font-weight: 500;\n  font-variant-numeric: tabular-nums;\n  color: var(--ui-segc-text);\n  animation: ui-segc-in 200ms var(--ui-segc-ease) both;\n}\n.ui-segc__opt[data-active] .ui-segc__count { background: var(--ui-segc-card); box-shadow: 0 0 0 0.5px var(--ui-segc-line); color: var(--ui-segc-ink); }\n.ui-segc[data-size=\"sm\"] .ui-segc__count { min-width: 14px; height: 14px; padding: 0 3px; border-radius: calc(var(--aui-radius-sm, 5px) - 1px); font-size: 10px; line-height: 14px; }\n.ui-segc__badge {\n  display: inline-flex;\n  align-items: center;\n  height: 18px;\n  padding: 0 6px;\n  border-radius: var(--aui-radius-sm, 5px);\n  background: var(--ui-segc-accent-soft);\n  font-size: 11px;\n  line-height: 16px;\n  font-weight: 500;\n  color: var(--ui-segc-accent);\n}\n.ui-segc[data-size=\"sm\"] .ui-segc__badge { height: 16px; padding: 0 5px; font-size: 10px; }\n\n/* The indicator: left and right edges slide separately – the one facing the target leads a touch. Short and crisp */\n.ui-segc__pill {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  left: 0;\n  right: 100%;\n  z-index: 0;\n  border-radius: calc(var(--aui-radius, 7px) - 1px);\n  background: var(--ui-segc-fill);\n  opacity: 0;\n  pointer-events: none;\n  transition: left 200ms var(--ui-segc-ease), right 200ms var(--ui-segc-ease), scale 120ms var(--ui-segc-ease), opacity 120ms ease;\n}\n.ui-segc__pill[data-dir=\"right\"] { transition-duration: 220ms, 160ms, 120ms, 120ms; }\n.ui-segc__pill[data-dir=\"left\"] { transition-duration: 160ms, 220ms, 120ms, 120ms; }\n/* Press feedback: the indicator gives a little while held or dragged */\n.ui-segc[data-pressed]:not([data-variant=\"underline\"]) .ui-segc__pill { scale: 0.97; }\n\n/* Buttons (default): separate chips with a hairline; the light-gray fill slides to the active one.\n   The control reaches a few px past its box so focus rings are never clipped, and still lines up with its neighbours */\n.ui-segc[data-variant=\"buttons\"] { --ui-segc-pad: var(--ui-segc-ring); margin: calc(var(--ui-segc-ring) * -1); max-width: calc(100% + var(--ui-segc-ring) * 2); }\n.ui-segc[data-variant=\"buttons\"][data-full] { width: calc(100% + var(--ui-segc-ring) * 2); }\n.ui-segc[data-variant=\"buttons\"] .ui-segc__track { gap: var(--ui-segc-gap); }\n.ui-segc[data-variant=\"buttons\"] .ui-segc__opt { box-shadow: inset 0 0 0 0.5px var(--ui-segc-line); }\n.ui-segc[data-variant=\"buttons\"] .ui-segc__opt:focus-visible { outline-offset: 2px; }\n\n/* Tray: a compact light-gray track with a white raised pill */\n.ui-segc[data-variant=\"tray\"] { background: var(--ui-segc-track); }\n.ui-segc[data-variant=\"tray\"] .ui-segc__opt { height: calc(var(--ui-segc-h) - var(--ui-segc-pad) * 2); }\n.ui-segc[data-variant=\"tray\"] .ui-segc__pill { background: var(--ui-segc-card); box-shadow: var(--ui-segc-card-shadow); }\n\n/* Underline: text tabs on a hairline */\n.ui-segc[data-variant=\"underline\"] { --ui-segc-px: 2px; --ui-segc-gap: 20px; border-radius: 0; box-shadow: inset 0 -0.5px 0 var(--ui-segc-line); }\n.ui-segc[data-variant=\"underline\"] .ui-segc__scroller { padding: 0; }\n.ui-segc[data-variant=\"underline\"]:not([data-full]) .ui-segc__track { gap: var(--ui-segc-gap); }\n.ui-segc[data-variant=\"underline\"][data-full] { --ui-segc-px: 10px; }\n.ui-segc[data-variant=\"underline\"] .ui-segc__opt { height: calc(var(--ui-segc-h) + 6px); }\n.ui-segc[data-variant=\"underline\"] .ui-segc__opt:focus-visible { outline-offset: -2px; }\n.ui-segc[data-variant=\"underline\"] .ui-segc__pill { top: auto; height: 1.5px; border-radius: 1.5px 1.5px 0 0; background: var(--ui-segc-ink); }\n@media (hover: hover) {\n  .ui-segc[data-variant=\"underline\"] .ui-segc__opt:not([data-active]):not(:disabled):hover { background-color: transparent; color: var(--ui-segc-hover-text); }\n}\n\n@keyframes ui-segc-in { from { opacity: 0; transform: translateY(2px); filter: blur(2px); } to { opacity: 1; transform: none; filter: none; } }\n\n/* Touch screens: every option at least 40px tall */\n@media (hover: none) and (pointer: coarse) {\n  .ui-segc[data-size] { --ui-segc-h: 40px; }\n  .ui-segc[data-size=\"lg\"] { --ui-segc-h: 44px; }\n  .ui-segc[data-variant=\"tray\"][data-size] { --ui-segc-h: 44px; }\n  .ui-segc[data-variant=\"underline\"] .ui-segc__opt { height: var(--ui-segc-h); }\n}\n@media (prefers-reduced-motion: reduce) {\n  .ui-segc *, .ui-segc *::before, .ui-segc *::after { animation: none !important; transition: none !important; }\n}\n\n/* Dark mode: follows a .dark class or data-theme=\"dark\" on any ancestor, e.g. <html> */\n:where(.dark, [data-theme=\"dark\"]) .ui-segc {\n  --ui-segc-ink: var(--aui-ink, rgba(255, 255, 255, 0.9));\n  --ui-segc-text: var(--aui-muted, rgba(255, 255, 255, 0.62));\n  --ui-segc-hover-text: rgba(255, 255, 255, 0.78);\n  --ui-segc-line: var(--aui-border, rgba(255, 255, 255, 0.08));\n  --ui-segc-wash: var(--aui-subtle, rgba(255, 255, 255, 0.04));\n  --ui-segc-fill: rgba(255, 255, 255, 0.08);\n  --ui-segc-track: var(--aui-subtle, rgba(255, 255, 255, 0.05));\n  --ui-segc-card: var(--aui-surface, #2c2c2f);\n  --ui-segc-card-shadow: 0 0 0 0.5px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(0, 0, 0, 0.4);\n  --ui-segc-accent: var(--aui-blue, #4fb0fe);\n  --ui-segc-accent-soft: rgba(77, 141, 255, 0.14);\n  --ui-segc-focus: var(--aui-focus, rgba(77, 141, 255, 0.8));\n}\n:where(.dark, [data-theme=\"dark\"]) .ui-segc__icon { color: var(--aui-muted, rgba(255, 255, 255, 0.46)); }\n:where(.dark, [data-theme=\"dark\"]) .ui-segc__opt[data-active] .ui-segc__icon { color: var(--ui-segc-ink); }\n`;\n"}]}