{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "standings",
  "type": "registry:ui",
  "title": "Standings",
  "description": "A ranking that races, like a timing tower. The place numbers stay put and the rows slide past them: when a score overtakes the one above, the two rows trade places, the climbing one passing over the other on a soft shadow, and a chip counts the places each has gained. Give it periods and it plays through them week by week, with a timeline to scrub back and forth; give it new scores and the rows race to the new order.",
  "author": "KeyframeUI <https://keyframeui.com>",
  "categories": [
    "data",
    "chart",
    "motion"
  ],
  "docs": "Live preview, examples and props: https://keyframeui.com/components/standings",
  "dependencies": [
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "components/ui/standings.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Keep:\n * - One frame loop (`createStandings`) owns every moving value and writes transforms and text\n *   straight to the DOM; React renders the rows once and only hears back when a period settles\n *   or the order changes.\n * - The place numbers never move, the rows do: each slides by `transform` on a critically damped\n *   spring (damping `2 * sqrt(stiffness)`), so a swap never overshoots.\n * - A row going up passes over the other: z-index 3 while rising, 1 while falling, 2 at rest, and\n *   its shadow's opacity follows its upward speed.\n * - The loop sleeps once every row has landed; playing pauses off screen and on a hidden tab and\n *   picks up where it was.\n * - Reduced motion: no autoplay, it rests on the last period, and a row that changes place jumps\n *   there and fades in; the timeline still steps and scrubs.\n */\nconst TIMING = {\n  /** How fast the race runs, as a multiple of the default pace below (the `speed` prop). */\n  speed: 1,\n  /** A row's critically damped spring to its new place: lands in ~0.4s (the `stiffness` prop). */\n  stiffness: 260,\n  /** On screen before autoplay starts, in ms. */\n  start: 450,\n  // At the default pace; `speed` divides these.\n  /** A period while it plays, in ms. A number `autoplay` replaces it. */\n  period: 1000,\n  /** Racing to a clicked period, in ms: `base` plus `per` for each period crossed, up to `max`. */\n  jump: { base: 520, per: 160, max: 1600 },\n  /** Settling on the nearest period after a drag, in ms. */\n  settle: 240,\n  /** On the last period before `loop` rewinds, in ms. */\n  hold: 2600,\n  /** The rewind, in ms. */\n  rewind: 1100,\n  /** On the first period before a loop plays again, in ms. */\n  rest: 700,\n  // At any pace.\n  /** Time constant of the scrub following the pointer, in seconds. */\n  follow: 0.06,\n  /** Each score gliding to new `entries`, in ms. */\n  blend: 600,\n  /** The change chip's pop, in ms, and its curve. */\n  pop: { duration: 220, easing: \"cubic-bezier(0.23, 1, 0.32, 1)\" },\n  /** A row fading in at its new place under reduced motion, in ms, and its curve. */\n  fade: { duration: 200, easing: \"ease-out\" },\n  /** The spring's integration step, in seconds. */\n  substep: 0.004,\n  /** The longest step a frame may take, in seconds, so a slow frame can't jump the race. */\n  maxStep: 0.064,\n  /** A place number taking the accent under your row, as a Tailwind class. */\n  rank: \"duration-200\",\n  /** The play button's hover, as a Tailwind class. */\n  button: \"duration-150\",\n  /** A timeline tick filling as the race passes it, as a Tailwind class. */\n  tick: \"duration-200\",\n  /** A period label showing as the current one, as a Tailwind class. */\n  label: \"duration-200\",\n} as const;\n\n/* ─────────────────────────────────────────────────────────\n * STANDINGS STORYBOARD\n *\n *  rest      a ranking in fixed slots, like a race's timing\n *            tower: the place numbers never move, the rows do.\n *            Each row has its mark, its name, a bar, its score\n *            and a chip with the places it has gained. Your row\n *            is tinted in the accent\n *  view      with `periods`, once 35% of it is on screen it\n *            waits 450ms, then plays through them, a second a\n *            period. Scores ease from one period into the next\n *            and settle on each, so the race moves week by week\n *  overtake  when a score passes the one above it, the two rows\n *            trade places on a critically damped spring (lands\n *            in about 0.4s). The row going up passes over the\n *            other, raised on a soft shadow; the row going down\n *            slides under it. The place number it lands on stays\n *            put, and turns the accent when the row is yours\n *  chip      the places gained since the first period pop in,\n *            green going up and red going down, 220ms\n *  scrub     the timeline under it: drag and the scores follow\n *            the pointer, with a detent at each period, and the\n *            rows swap as they cross; let go and it settles on\n *            the nearest one in 240ms. A click races to that\n *            period: 520ms plus 160ms for each one it crosses\n *  data      new `entries` (a benchmark tab, a live vote): each\n *            score glides from where it is to its new value in\n *            600ms, and the rows re-race on the way\n *  loop      `loop`: 2.6s on the last period, then the race\n *            rewinds in 1.1s, the rows sliding back down, rests\n *            0.7s and plays again\n *  sleep     the frame loop stops once every row has landed.\n *            Playing pauses off screen and on a hidden tab, and\n *            picks up where it was\n *  keys      the timeline is a slider: arrows step a period,\n *            Home and End go to either end. The button plays,\n *            pauses and replays\n *  reduced   no autoplay: it rests on the last period, the\n *            result. Rows don't travel; a row that changes place\n *            fades in at the new one over 200ms. The timeline\n *            still steps and scrubs\n * ───────────────────────────────────────────────────────── */\n\nconst STANDINGS = {\n  start: { amount: 0.35 }, // share on screen before autoplay's wait starts\n  land: { distance: 0.25, speed: 4 }, // px, px/s: a row nearer and slower than this has landed\n  lift: 600, // px/s of upward speed at which a passing row's shadow is at full strength\n} as const;\n\nconst clamp = (v: number, lo: number, hi: number) => (v < lo ? lo : v > hi ? hi : v);\n/** cubic-bezier(0.65, 0, 0.35, 1): races and rewinds. */\nconst inOut = (p: number) => (p < 0.5 ? 4 * p * p * p : 1 - Math.pow(-2 * p + 2, 3) / 2);\n/** An ease-out cubic: a score gliding to new data starts at once. */\nconst out = (p: number) => 1 - Math.pow(1 - p, 3);\n/** Within a period: leave one, settle on the next. Also the scrub's detents. */\nconst smooth = (f: number) => f * f * (3 - 2 * f);\n\nconst numbers = new Intl.NumberFormat(\"en-US\", { maximumFractionDigits: 0 });\nconst defaultFormat = (value: number) => numbers.format(value);\n\nfunction ordinal(n: number) {\n  const suffix = [\"th\", \"st\", \"nd\", \"rd\"];\n  const v = n % 100;\n  return n + (suffix[(v - 20) % 10] ?? suffix[v] ?? suffix[0]);\n}\n\n/** An entry's score at `t`, a position along the periods (1.5 is halfway from the second to the third). */\nfunction valueAt(value: number | number[], t: number, count: number) {\n  if (typeof value === \"number\") return value;\n  if (!value.length) return 0;\n  if (count < 2) return value[value.length - 1];\n  const i = Math.floor(t);\n  const a = value[Math.min(i, value.length - 1)];\n  const b = value[Math.min(i + 1, value.length - 1)];\n  return a + (b - a) * smooth(t - i);\n}\n\n/** Ids from first place to last. Ties keep the order they had. */\nfunction rank(ids: string[], score: (id: string) => number, best: Best, before: Map<string, number>) {\n  return [...ids].sort((a, b) => {\n    const d = best === \"lowest\" ? score(a) - score(b) : score(b) - score(a);\n    return d || (before.get(a) ?? 0) - (before.get(b) ?? 0);\n  });\n}\n\nconst indexOf = (order: string[]) => new Map(order.map((id, i) => [id, i]));\n\nfunction peak(entries: StandingsEntry[]) {\n  let top = 0;\n  for (const e of entries) for (const v of typeof e.value === \"number\" ? [e.value] : e.value) top = Math.max(top, v);\n  return top || 1;\n}\n\nconst REDUCED = \"(prefers-reduced-motion: reduce)\";\nconst subscribeReduced = (onChange: () => void) => {\n  const query = window.matchMedia(REDUCED);\n  query.addEventListener(\"change\", onChange);\n  return () => query.removeEventListener(\"change\", onChange);\n};\n\n/** Reduced motion from the media query, false on the server and in the hydration render. */\nfunction useReduced() {\n  return React.useSyncExternalStore(\n    subscribeReduced,\n    () => window.matchMedia(REDUCED).matches,\n    () => false,\n  );\n}\n\nexport interface StandingsEntry {\n  /** Stable id: a row keeps its element (and its place in the race) across data changes. */\n  id: string;\n  /** The name in the row. */\n  label: string;\n  /** A second line under the name, such as the developer or the model's size. */\n  detail?: string;\n  /** A small mark before the name: an app icon, a logo, an avatar. */\n  icon?: React.ReactNode;\n  /** The score. One number, or one per period. */\n  value: number | number[];\n  /** Your row: tinted in the accent, tagged \"You\" and named when the standings are announced. */\n  you?: boolean;\n}\n\ntype Best = \"highest\" | \"lowest\";\ntype Status = \"idle\" | \"playing\" | \"ended\";\n\nexport interface StandingsProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\"> {\n  /** The rows, in any order: they're ranked by score. */\n  entries: StandingsEntry[];\n  /** Labels for the periods an entry's `value` array runs over (\"W1\", \"Mar\"). With two or more, a timeline under the rows plays and scrubs through them. */\n  periods?: string[];\n  /** The period to show, controlled. The rows race to it when it changes. */\n  period?: number;\n  /** The period to start on, uncontrolled. Defaults to the first when it autoplays, otherwise the last. */\n  defaultPeriod?: number;\n  /** Called with the period a visitor picks, or that autoplay reaches. */\n  onPeriodChange?: (period: number) => void;\n  /** Called with the ids in their new order, the moment a row overtakes another. */\n  onOrderChange?: (ids: string[]) => void;\n  /** Play through the periods once the standings scroll into view. A number sets the ms per period. */\n  autoplay?: boolean | number;\n  /** After the last period, hold, rewind and play again. */\n  loop?: boolean;\n  /** Which end of the scores ranks first: `\"lowest\"` for times and prices. */\n  best?: Best;\n  /** The score that fills a bar. Defaults to the highest score in `entries`. */\n  max?: number;\n  /** How a score is written. Defaults to a rounded number with thousands separators. */\n  format?: (value: number) => string;\n  /** Show a chip with the places each row has gained since the first period (or, without periods, since it first rendered). */\n  change?: boolean;\n  /** How fast the race runs along the timeline, as a multiple of the default pace: playing each period, racing to a clicked one, and the loop's hold, rewind and rest. `2` is twice as fast. */\n  speed?: number;\n  /** Stiffness of the critically damped spring a row rides to its new place. Higher lands sooner. */\n  stiffness?: number;\n}\n\ntype Parts = {\n  el: HTMLElement;\n  bar: HTMLElement | null;\n  value: HTMLElement | null;\n  chip: HTMLElement | null;\n  count: HTMLElement | null;\n  lift: HTMLElement | null;\n};\n\ntype Row = Parts & {\n  y: number;\n  drawn: number;\n  v: number;\n  slot: number;\n  shown: number;\n  text: string;\n  scale: number;\n  delta: number;\n  z: number;\n  lifted: number;\n};\n\ntype Data = {\n  entries: StandingsEntry[];\n  count: number;\n  best: Best;\n  max: number;\n  format: (value: number) => string;\n  you: string | null;\n};\n\ntype Listeners = {\n  onPeriod: (period: number, reason: \"auto\" | \"user\") => void;\n  onStatus: (status: Status) => void;\n  onOrder: (ids: string[]) => void;\n};\n\ntype Mode =\n  | { kind: \"idle\" }\n  | { kind: \"play\" }\n  | { kind: \"jump\"; from: number; to: number; elapsed: number; duration: number; then?: () => void }\n  | { kind: \"hold\"; left: number; then: () => void }\n  | { kind: \"drag\"; target: number };\n\n/* ── Engine ─────────────────────────────────────────────── */\n// One frame loop owns every moving value: the position along the periods,\n// each score, each row's spring, the bars, the chips and the timeline. It\n// writes transforms and text straight to the DOM; React only renders the\n// rows once and hears back when a period settles.\n\nfunction createStandings() {\n  let root: HTMLElement | null = null;\n  const rows = new Map<string, Row>();\n  let ranks: HTMLElement[] = [];\n  let ticks: HTMLElement[] = [];\n  let labels: HTMLElement[] = [];\n  let fill: HTMLElement | null = null;\n  let thumb: HTMLElement | null = null;\n\n  let data: Data | null = null;\n  let t = 0;\n  let rowH = 0;\n  let order: string[] = [];\n  let baseline = new Map<string, number>();\n  let blend: { from: Map<string, number>; elapsed: number } | null = null;\n  let mode: Mode = { kind: \"idle\" };\n  let auto = false; // playing on its own (autoplay or loop), until a visitor takes over\n  let looping = false;\n  let periodMs: number = TIMING.period;\n  let pace: number = TIMING.speed; // `speed`: every move along the timeline runs at 1 / pace of its time\n  let stiffness: number = TIMING.stiffness;\n  let reduced = false;\n  let visible = true;\n  let paused: Mode | null = null; // what was running when it went off screen\n  let raf = 0;\n  let last = 0;\n  let reported = -1;\n  let passed = -1;\n  let current = -1;\n  let youSlot = -1;\n  let status: Status = \"idle\";\n  let listeners: Listeners | null = null;\n\n  const lastIndex = () => Math.max(0, (data?.count ?? 1) - 1);\n  const timeline = () => (data?.count ?? 0) > 1;\n\n  function score(id: string, entries: StandingsEntry[], at: number) {\n    const e = entries.find((x) => x.id === id);\n    return e ? valueAt(e.value, at, data?.count ?? 0) : 0;\n  }\n\n  function setStatus(next: Status) {\n    if (next === status) return;\n    status = next;\n    listeners?.onStatus(next);\n  }\n\n  function wake() {\n    if (!raf && root) raf = requestAnimationFrame(frame);\n  }\n\n  function frame(now: number) {\n    raf = 0;\n    const dt = last ? Math.min((now - last) / 1000, TIMING.maxStep) : 1 / 60;\n    last = now;\n    let busy = advance(dt);\n    busy = place(dt) || busy;\n    if (busy) raf = requestAnimationFrame(frame);\n    else last = 0;\n  }\n\n  /** Moves `t` along for whatever is running. Returns whether anything still is. */\n  function advance(dt: number) {\n    const end = lastIndex();\n    switch (mode.kind) {\n      case \"play\": {\n        t = Math.min(end, t + (dt * 1000 * pace) / periodMs);\n        if (t >= end) finished();\n        return true;\n      }\n      case \"jump\": {\n        mode.elapsed += dt * 1000;\n        const p = mode.duration <= 0 ? 1 : clamp(mode.elapsed / mode.duration, 0, 1);\n        t = mode.from + (mode.to - mode.from) * inOut(p);\n        if (p >= 1) {\n          t = mode.to;\n          const then = mode.then;\n          mode = { kind: \"idle\" };\n          if (then) then();\n          else settle(\"user\");\n        }\n        return true;\n      }\n      case \"hold\": {\n        mode.left -= dt * 1000;\n        if (mode.left <= 0) {\n          const then = mode.then;\n          mode = { kind: \"idle\" };\n          then();\n        }\n        return true;\n      }\n      case \"drag\": {\n        const target = mode.target;\n        t += (target - t) * (1 - Math.exp(-dt / TIMING.follow));\n        if (Math.abs(target - t) < 0.0005) t = target;\n        return t !== target;\n      }\n      default:\n        return false;\n    }\n  }\n\n  function finished() {\n    const end = lastIndex();\n    t = end;\n    mode = { kind: \"idle\" };\n    report(\"auto\");\n    if (auto && looping) {\n      mode = {\n        kind: \"hold\",\n        left: reduced ? Infinity : TIMING.hold / pace,\n        then: () => {\n          mode = {\n            kind: \"jump\",\n            from: end,\n            to: 0,\n            elapsed: 0,\n            duration: TIMING.rewind / pace,\n            then: () => {\n              report(\"auto\");\n              mode = { kind: \"hold\", left: TIMING.rest / pace, then: () => (mode = { kind: \"play\" }) };\n            },\n          };\n        },\n      };\n      return;\n    }\n    auto = false;\n    setStatus(\"ended\");\n  }\n\n  /** Tells React which period is showing. */\n  function report(reason: \"auto\" | \"user\") {\n    const p = Math.round(t);\n    if (p === reported) return;\n    reported = p;\n    listeners?.onPeriod(p, reason);\n  }\n\n  function settle(reason: \"auto\" | \"user\") {\n    report(reason);\n    setStatus(timeline() && Math.round(t) >= lastIndex() ? \"ended\" : \"idle\");\n  }\n\n  /** Scores, order, springs and every write for this frame. Returns whether anything still moves. */\n  function place(dt: number) {\n    if (!data) return false;\n    let busy = false;\n    const { entries, count, best, max, format } = data;\n\n    let k = 1;\n    if (blend) {\n      blend.elapsed += dt * 1000;\n      k = out(clamp(blend.elapsed / TIMING.blend, 0, 1));\n      if (blend.elapsed >= TIMING.blend) blend = null;\n      else busy = true;\n    }\n    for (const e of entries) {\n      const row = rows.get(e.id);\n      if (!row) continue;\n      const target = valueAt(e.value, t, count);\n      const from = blend?.from.get(e.id);\n      row.shown = from === undefined ? target : from + (target - from) * k;\n    }\n\n    const next = rank(\n      entries.map((e) => e.id),\n      (id) => rows.get(id)?.shown ?? 0,\n      best,\n      indexOf(order),\n    );\n    if (next.join(\"\\u0000\") !== order.join(\"\\u0000\")) {\n      const before = indexOf(order);\n      order = next;\n      next.forEach((id, slot) => {\n        const row = rows.get(id);\n        if (!row) return;\n        row.slot = slot;\n        if (reduced && before.has(id) && before.get(id) !== slot) {\n          row.el.animate([{ opacity: 0.25 }, { opacity: 1 }], TIMING.fade);\n        }\n      });\n      listeners?.onOrder(next);\n    }\n\n    // The place number under your row takes the accent.\n    const you = data.you ? order.indexOf(data.you) : -1;\n    if (you !== youSlot) {\n      ranks[youSlot]?.removeAttribute(\"data-you\");\n      ranks[you]?.setAttribute(\"data-you\", \"\");\n      youSlot = you;\n    }\n\n    const k2 = stiffness;\n    const c2 = 2 * Math.sqrt(k2);\n    for (const row of rows.values()) {\n      const target = row.slot * rowH;\n      if (reduced || !rowH) {\n        row.y = target;\n        row.v = 0;\n      } else if (Math.abs(row.y - target) > STANDINGS.land.distance || Math.abs(row.v) > STANDINGS.land.speed) {\n        const n = Math.max(1, Math.ceil(dt / TIMING.substep));\n        const h = dt / n;\n        for (let i = 0; i < n; i++) {\n          row.v += (-k2 * (row.y - target) - c2 * row.v) * h;\n          row.y += row.v * h;\n        }\n        busy = true;\n      } else {\n        row.y = target;\n        row.v = 0;\n      }\n      if (!(Math.abs(row.y - row.drawn) <= 0.01)) {\n        row.drawn = row.y;\n        row.el.style.transform = `translateY(${row.y.toFixed(2)}px)`;\n      }\n\n      // A row on its way up passes over the others, on a shadow as strong as its speed.\n      const lifted = row.v < 0 ? clamp(-row.v / STANDINGS.lift, 0, 1) : 0;\n      if (Math.abs(lifted - row.lifted) > 0.01 || (lifted === 0 && row.lifted !== 0)) {\n        row.lifted = lifted;\n        if (row.lift) row.lift.style.opacity = lifted.toFixed(3);\n      }\n      const z = row.v < -20 ? 3 : row.v > 20 ? 1 : 2;\n      if (z !== row.z) {\n        row.z = z;\n        row.el.style.zIndex = String(z);\n      }\n\n      const scale = clamp(row.shown / max, 0, 1);\n      if (Math.abs(scale - row.scale) > 0.0005) {\n        row.scale = scale;\n        if (row.bar) row.bar.style.transform = `translateX(${((scale - 1) * 100).toFixed(2)}%)`;\n      }\n      const text = format(row.shown);\n      if (text !== row.text) {\n        row.text = text;\n        if (row.value) row.value.textContent = text;\n      }\n    }\n\n    // Places gained since the baseline order.\n    for (const [id, row] of rows) {\n      const base = baseline.get(id);\n      const delta = base === undefined ? 0 : base - row.slot;\n      if (delta === row.delta) continue;\n      row.delta = delta;\n      if (row.chip) row.chip.dataset.dir = delta > 0 ? \"up\" : delta < 0 ? \"down\" : \"flat\";\n      if (row.count) row.count.textContent = delta === 0 ? \"–\" : String(Math.abs(delta));\n      if (row.chip && !reduced) {\n        row.chip.animate(\n          [\n            { transform: \"scale(0.8)\", opacity: 0.3 },\n            { transform: \"scale(1)\", opacity: 1 },\n          ],\n          TIMING.pop,\n        );\n      }\n    }\n\n    paintTimeline();\n    if (mode.kind === \"play\") report(\"auto\");\n    return busy;\n  }\n\n  function paintTimeline() {\n    if (!timeline()) return;\n    const p = clamp(t / lastIndex(), 0, 1);\n    if (fill) fill.style.transform = `scaleX(${p.toFixed(4)})`;\n    if (thumb) thumb.style.transform = `translateX(${(p * 100).toFixed(3)}%)`;\n    const reached = Math.floor(t + 0.001);\n    if (reached !== passed) {\n      passed = reached;\n      ticks.forEach((tick, i) => tick.toggleAttribute(\"data-passed\", i <= reached));\n    }\n    const near = Math.round(t);\n    if (near !== current) {\n      labels[current]?.removeAttribute(\"data-current\");\n      labels[near]?.setAttribute(\"data-current\", \"\");\n      current = near;\n    }\n  }\n\n  function measure() {\n    const first = rows.values().next().value;\n    const h = first ? first.el.offsetHeight : 0;\n    if (!h || h === rowH) return;\n    rowH = h;\n    // A new row height is a new layout, not a move: everything lands at once.\n    for (const row of rows.values()) {\n      row.y = row.slot * rowH;\n      row.v = 0;\n    }\n    wake();\n  }\n\n  function stop() {\n    auto = false;\n    paused = null;\n    mode = { kind: \"idle\" };\n  }\n\n  return {\n    listen(next: Listeners) {\n      listeners = next;\n    },\n\n    /** Picks up the rendered rows and the data they show. Called after every render that changes either. */\n    sync(el: HTMLElement, next: Data, start: number) {\n      const first = !data;\n      root = el;\n      const changed =\n        !data ||\n        data.count !== next.count ||\n        data.best !== next.best ||\n        data.max !== next.max ||\n        data.entries.length !== next.entries.length ||\n        next.entries.some((e) => {\n          const prev = data?.entries.find((x) => x.id === e.id);\n          if (!prev) return true;\n          const a = typeof prev.value === \"number\" ? [prev.value] : prev.value;\n          const b = typeof e.value === \"number\" ? [e.value] : e.value;\n          return a.length !== b.length || a.some((v, i) => v !== b[i]);\n        });\n      const reformat = data?.format !== next.format;\n\n      if (first) t = start;\n      if (changed && !first) {\n        blend = { from: new Map([...rows].map(([id, row]) => [id, row.shown])), elapsed: 0 };\n      }\n      data = next;\n\n      const ids = next.entries.map((e) => e.id);\n      if (first || changed) {\n        const at0 = rank(ids, (id) => score(id, next.entries, 0), next.best, indexOf(ids));\n        if (next.count > 1) baseline = indexOf(at0);\n        else if (first) baseline = indexOf(rank(ids, (id) => score(id, next.entries, t), next.best, indexOf(ids)));\n      }\n      if (first) order = rank(ids, (id) => score(id, next.entries, t), next.best, indexOf(ids));\n      else order = order.filter((id) => ids.includes(id)).concat(ids.filter((id) => !order.includes(id)));\n\n      const live = new Set<string>();\n      el.querySelectorAll<HTMLElement>(\"[data-standings-row]\").forEach((rowEl) => {\n        const id = rowEl.dataset.id;\n        if (!id) return;\n        live.add(id);\n        const parts: Parts = {\n          el: rowEl,\n          bar: rowEl.querySelector(\"[data-bar]\"),\n          value: rowEl.querySelector(\"[data-value]\"),\n          chip: rowEl.querySelector(\"[data-chip]\"),\n          count: rowEl.querySelector(\"[data-count]\"),\n          lift: rowEl.querySelector(\"[data-lift]\"),\n        };\n        const row = rows.get(id);\n        if (row) {\n          Object.assign(row, parts);\n          return;\n        }\n        const slot = Math.max(0, order.indexOf(id));\n        const shown = score(id, next.entries, t);\n        rows.set(id, {\n          ...parts,\n          y: slot * rowH,\n          drawn: NaN,\n          v: 0,\n          slot,\n          shown,\n          text: next.format(shown),\n          scale: clamp(shown / next.max, 0, 1),\n          delta: Number(rowEl.querySelector<HTMLElement>(\"[data-chip]\")?.dataset.delta ?? 0),\n          z: 2,\n          lifted: 0,\n        });\n      });\n      for (const id of [...rows.keys()]) if (!live.has(id)) rows.delete(id);\n\n      ranks = [...el.querySelectorAll<HTMLElement>(\"[data-rank]\")];\n      ticks = [...el.querySelectorAll<HTMLElement>(\"[data-tick]\")];\n      labels = [...el.querySelectorAll<HTMLElement>(\"[data-period-label]\")];\n      fill = el.querySelector(\"[data-fill]\");\n      thumb = el.querySelector(\"[data-thumb]\");\n      youSlot = ranks.findIndex((r) => r.hasAttribute(\"data-you\"));\n      passed = current = -1;\n      // React may have rewritten a row's styles or text on this render; draw it all again.\n      for (const row of rows.values()) {\n        row.drawn = NaN;\n        row.scale = -1;\n        row.z = -1;\n        row.lifted = -1;\n        if (reformat) row.text = \"\";\n      }\n\n      measure();\n      if (first) {\n        reported = Math.round(t);\n        setStatus(timeline() && Math.round(t) >= lastIndex() && !auto ? \"ended\" : \"idle\");\n      }\n      // Draw now, before the browser paints this render.\n      place(0);\n      wake();\n    },\n\n    resize() {\n      measure();\n    },\n\n    /** `speed` and `stiffness`; a race already under way picks them up on its next frame. */\n    setFeel(nextSpeed: number, nextStiffness: number) {\n      pace = nextSpeed;\n      stiffness = nextStiffness;\n      wake();\n    },\n\n    /** `rest`: nothing has played or been touched yet, so reduced motion shows the result at once. */\n    setReduced(next: boolean, rest: boolean) {\n      reduced = next;\n      if (next && timeline() && (mode.kind === \"play\" || (rest && mode.kind === \"idle\" && t !== lastIndex()))) {\n        stop();\n        t = lastIndex();\n        settle(\"auto\");\n      }\n      wake();\n    },\n\n    /** Plays on its own when it first comes into view. A visitor's input since cancels it. */\n    autoplay(ms: number, loop: boolean, touched: boolean) {\n      if (touched || !timeline()) return;\n      periodMs = ms;\n      looping = loop;\n      auto = true;\n      if (reduced) {\n        auto = false;\n        t = lastIndex();\n        settle(\"auto\");\n        wake();\n        return;\n      }\n      mode = t >= lastIndex() ? { kind: \"jump\", from: t, to: 0, elapsed: 0, duration: 0, then: () => (mode = { kind: \"play\" }) } : { kind: \"play\" };\n      setStatus(\"playing\");\n      wake();\n    },\n\n    toggle(ms: number, loop: boolean) {\n      if (status === \"playing\") {\n        stop();\n        settle(\"user\");\n        return;\n      }\n      periodMs = ms;\n      looping = loop;\n      auto = loop;\n      const play = () => (mode = { kind: \"play\" });\n      if (t >= lastIndex() - 0.001) {\n        mode = { kind: \"jump\", from: t, to: 0, elapsed: 0, duration: reduced ? 0 : TIMING.rewind / pace, then: play };\n      } else play();\n      setStatus(\"playing\");\n      wake();\n    },\n\n    /** Races to a period: a click, a key, a controlled `period`. */\n    goTo(to: number) {\n      if (!data) return;\n      const target = clamp(Math.round(to), 0, lastIndex());\n      if (mode.kind === \"jump\" && mode.to === target && !mode.then) return;\n      if (mode.kind === \"idle\" && t === target) return;\n      stop();\n      const crossed = Math.abs(target - t);\n      const duration = reduced\n        ? 0\n        : crossed < 1\n          ? TIMING.settle / pace\n          : Math.min(TIMING.jump.max, TIMING.jump.base + TIMING.jump.per * (crossed - 1)) / pace;\n      mode = { kind: \"jump\", from: t, to: target, elapsed: 0, duration };\n      setStatus(\"idle\");\n      wake();\n    },\n\n    /** The pointer on the timeline, as a position along the periods. */\n    drag(to: number) {\n      stop();\n      mode = { kind: \"drag\", target: clamp(to, 0, lastIndex()) };\n      if (reduced) t = mode.target;\n      setStatus(\"idle\");\n      wake();\n    },\n\n    release() {\n      if (mode.kind !== \"drag\") return;\n      const target = Math.round(mode.target);\n      mode = { kind: \"jump\", from: t, to: target, elapsed: 0, duration: reduced ? 0 : TIMING.settle / pace };\n      wake();\n    },\n\n    position() {\n      return t;\n    },\n\n    setVisible(next: boolean) {\n      if (next === visible) return;\n      visible = next;\n      if (!next && (mode.kind === \"play\" || mode.kind === \"hold\" || (mode.kind === \"jump\" && mode.then))) {\n        paused = mode;\n        mode = { kind: \"idle\" };\n      } else if (next && paused) {\n        mode = paused;\n        paused = null;\n        wake();\n      }\n    },\n\n    disconnect() {\n      if (raf) cancelAnimationFrame(raf);\n      raf = 0;\n      last = 0;\n      root = null;\n    },\n  };\n}\n\n/* ── Glyphs ─────────────────────────────────────────────── */\n\nfunction PlayIcon({ status }: { status: Status }) {\n  if (status === \"playing\") {\n    return (\n      <svg viewBox=\"0 0 12 12\" className=\"size-3\" fill=\"currentColor\" aria-hidden>\n        <rect x=\"2.5\" y=\"2\" width=\"2.5\" height=\"8\" rx=\"0.75\" />\n        <rect x=\"7\" y=\"2\" width=\"2.5\" height=\"8\" rx=\"0.75\" />\n      </svg>\n    );\n  }\n  if (status === \"ended\") {\n    return (\n      <svg viewBox=\"0 0 12 12\" className=\"size-3\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" aria-hidden>\n        <path d=\"M2.5 6a3.5 3.5 0 1 0 1.1-2.55\" strokeLinecap=\"round\" />\n        <path d=\"M2.4 1.8v2.1h2.1\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n      </svg>\n    );\n  }\n  return (\n    <svg viewBox=\"0 0 12 12\" className=\"ml-px size-3\" fill=\"currentColor\" aria-hidden>\n      <path d=\"M3.5 2.2v7.6a.6.6 0 0 0 .9.5l6-3.8a.6.6 0 0 0 0-1L4.4 1.7a.6.6 0 0 0-.9.5Z\" />\n    </svg>\n  );\n}\n\n/* ── Standings ──────────────────────────────────────────── */\n\nexport function Standings({\n  entries,\n  periods,\n  period,\n  defaultPeriod,\n  onPeriodChange,\n  onOrderChange,\n  autoplay = true,\n  loop = false,\n  best = \"highest\",\n  max,\n  format = defaultFormat,\n  change = true,\n  speed = TIMING.speed,\n  stiffness = TIMING.stiffness,\n  className,\n  ...props\n}: StandingsProps) {\n  const rootRef = React.useRef<HTMLDivElement>(null);\n  const trackRef = React.useRef<HTMLDivElement>(null);\n  const touched = React.useRef(false);\n  const reduced = useReduced();\n\n  const count = periods?.length ?? 0;\n  const timeline = count > 1;\n  const lastIndex = Math.max(0, count - 1);\n  const controlled = period !== undefined;\n  const periodMs = typeof autoplay === \"number\" ? autoplay : TIMING.period;\n  const top = React.useMemo(() => max ?? peak(entries), [max, entries]);\n  const you = entries.find((e) => e.you)?.id ?? null;\n\n  const [engine] = React.useState(createStandings);\n  const [start] = React.useState(() =>\n    clamp(Math.round(period ?? defaultPeriod ?? (autoplay ? 0 : lastIndex)), 0, lastIndex),\n  );\n  // What the server renders, and what React keeps writing: the rows as they\n  // stand at the first period shown. The engine moves them from there.\n  const [first] = React.useState(() => {\n    const ids = entries.map((e) => e.id);\n    const at = (id: string, t: number) => {\n      const e = entries.find((x) => x.id === id);\n      return e ? valueAt(e.value, t, count) : 0;\n    };\n    const order = rank(ids, (id) => at(id, start), best, indexOf(ids));\n    const base = indexOf(count > 1 ? rank(ids, (id) => at(id, 0), best, indexOf(ids)) : order);\n    const rows = new Map(\n      ids.map((id) => {\n        const slot = order.indexOf(id);\n        const value = at(id, start);\n        return [id, { slot, text: format(value), scale: clamp(value / top, 0, 1), delta: (base.get(id) ?? slot) - slot }];\n      }),\n    );\n    return { order, rows };\n  });\n\n  const [shown, setShown] = React.useState(start);\n  const [status, setStatus] = React.useState<Status>(\n    timeline && !autoplay && start >= lastIndex ? \"ended\" : \"idle\",\n  );\n  const [ranking, setRanking] = React.useState(first.order);\n  const [announcement, setAnnouncement] = React.useState(\"\");\n\n  // The engine calls these from its frame loop.\n  React.useEffect(() => {\n    engine.listen({\n      onPeriod: (p, reason) => {\n        setShown(p);\n        if (reason === \"user\" || !controlled) onPeriodChange?.(p);\n      },\n      onStatus: setStatus,\n      onOrder: (ids) => {\n        setRanking(ids);\n        onOrderChange?.(ids);\n      },\n    });\n  }, [engine, controlled, onPeriodChange, onOrderChange]);\n\n  React.useLayoutEffect(() => {\n    const el = rootRef.current;\n    if (!el) return;\n    engine.sync(el, { entries, count, best, max: top, format, you }, start);\n  }, [engine, entries, count, best, top, format, you, start]);\n\n  React.useEffect(() => {\n    const el = rootRef.current;\n    if (!el) return;\n    const ro = new ResizeObserver(() => engine.resize());\n    ro.observe(el);\n    let timer = 0;\n    let seen = false;\n    let inView = true;\n    const io = new IntersectionObserver(\n      ([entry]) => {\n        inView = entry.isIntersecting;\n        engine.setVisible(inView && !document.hidden);\n        if (inView && entry.intersectionRatio >= STANDINGS.start.amount && !seen) {\n          seen = true;\n          if (autoplay && !controlled) {\n            timer = window.setTimeout(() => engine.autoplay(periodMs, loop, touched.current), TIMING.start);\n          }\n        }\n      },\n      { threshold: [0, STANDINGS.start.amount] },\n    );\n    io.observe(el);\n    const onVisibility = () => engine.setVisible(inView && !document.hidden);\n    document.addEventListener(\"visibilitychange\", onVisibility);\n    return () => {\n      window.clearTimeout(timer);\n      ro.disconnect();\n      io.disconnect();\n      document.removeEventListener(\"visibilitychange\", onVisibility);\n    };\n  }, [engine, autoplay, controlled, periodMs, loop]);\n\n  React.useEffect(\n    () => engine.setReduced(reduced, autoplay !== false && !controlled && !touched.current),\n    [engine, reduced, autoplay, controlled],\n  );\n\n  React.useLayoutEffect(\n    () => engine.setFeel(Math.max(0.05, speed), Math.max(1, stiffness)),\n    [engine, speed, stiffness],\n  );\n  React.useEffect(() => {\n    if (controlled) engine.goTo(period);\n  }, [engine, controlled, period]);\n  React.useEffect(() => () => engine.disconnect(), [engine]);\n\n  // Announce your place when a visitor moves the timeline or the data changes.\n  const youEntry = entries.find((e) => e.you);\n  const youPlace = youEntry ? ranking.indexOf(youEntry.id) + 1 : 0;\n  const lastAnnounced = React.useRef({ place: youPlace, shown });\n  React.useEffect(() => {\n    if (!youEntry || !touched.current) return;\n    if (lastAnnounced.current.place === youPlace && lastAnnounced.current.shown === shown) return;\n    lastAnnounced.current = { place: youPlace, shown };\n    const when = periods?.[shown];\n    const id = window.setTimeout(\n      () => setAnnouncement(`${when ? `${when}: ` : \"\"}${youEntry.label}, ${ordinal(youPlace)} of ${entries.length}.`),\n      0,\n    );\n    return () => window.clearTimeout(id);\n  }, [youEntry, youPlace, shown, periods, entries.length]);\n\n  const take = () => {\n    touched.current = true;\n  };\n\n  const positionAt = (clientX: number) => {\n    const rect = trackRef.current?.getBoundingClientRect();\n    if (!rect || !rect.width) return 0;\n    return clamp((clientX - rect.left) / rect.width, 0, 1) * lastIndex;\n  };\n\n  const drag = React.useRef<{ id: number; x: number; moved: boolean } | null>(null);\n\n  const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {\n    if (e.button !== 0) return;\n    take();\n    e.currentTarget.setPointerCapture(e.pointerId);\n    drag.current = { id: e.pointerId, x: e.clientX, moved: false };\n    engine.goTo(positionAt(e.clientX));\n  };\n  const onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {\n    const d = drag.current;\n    if (!d || d.id !== e.pointerId) return;\n    if (!d.moved && Math.abs(e.clientX - d.x) < 3) return;\n    d.moved = true;\n    engine.drag(positionAt(e.clientX));\n  };\n  const onPointerUp = (e: React.PointerEvent<HTMLDivElement>) => {\n    const d = drag.current;\n    if (!d || d.id !== e.pointerId) return;\n    drag.current = null;\n    if (d.moved) engine.release();\n    onPeriodChange?.(Math.round(positionAt(e.clientX)));\n  };\n\n  const step = (to: number) => {\n    take();\n    const target = clamp(to, 0, lastIndex);\n    engine.goTo(target);\n    onPeriodChange?.(target);\n  };\n  const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n    const t = engine.position();\n    const keys: Record<string, number> = {\n      ArrowRight: Math.floor(t + 0.001) + 1,\n      ArrowUp: Math.floor(t + 0.001) + 1,\n      PageUp: Math.floor(t + 0.001) + 1,\n      ArrowLeft: Math.ceil(t - 0.001) - 1,\n      ArrowDown: Math.ceil(t - 0.001) - 1,\n      PageDown: Math.ceil(t - 0.001) - 1,\n      Home: 0,\n      End: lastIndex,\n    };\n    if (!(e.key in keys)) return;\n    e.preventDefault();\n    step(keys[e.key]);\n  };\n\n  const byId = new Map(entries.map((e) => [e.id, e]));\n  const startP = lastIndex ? start / lastIndex : 0;\n\n  return (\n    <div\n      ref={rootRef}\n      role=\"group\"\n      className={cn(\n        \"@container/standings relative [--standings-accent:#ff5a1f] [--standings-line:color-mix(in_oklab,currentColor_9%,transparent)] [--standings-row:3.25rem] [--standings-surface:#ffffff] dark:[--standings-surface:#09090b]\",\n        className,\n      )}\n      {...props}\n    >\n      <div className=\"flex\" aria-hidden>\n        <div className=\"w-9 shrink-0 @[24rem]/standings:w-11\">\n          {entries.map((e, i) => (\n            <div\n              key={e.id}\n              data-rank=\"\"\n              {...(first.rows.get(you ?? \"\")?.slot === i ? { \"data-you\": \"\" } : {})}\n              className={`flex h-[var(--standings-row)] items-center justify-center text-[13px] font-medium tabular-nums opacity-45 shadow-[inset_0_-1px_0_var(--standings-line)] transition-[color,opacity] ${TIMING.rank} data-[you]:text-[var(--standings-accent)] data-[you]:opacity-100`}\n            >\n              {i + 1}\n            </div>\n          ))}\n        </div>\n        <div className=\"relative min-w-0 flex-1\" style={{ height: `calc(var(--standings-row) * ${entries.length})` }}>\n          {entries.map((entry) => {\n            const seed = first.rows.get(entry.id);\n            const slot = seed?.slot ?? entries.indexOf(entry);\n            const scale = seed?.scale ?? clamp(valueAt(entry.value, start, count) / top, 0, 1);\n            const delta = seed?.delta ?? 0;\n            return (\n              <div\n                key={entry.id}\n                data-standings-row=\"\"\n                data-id={entry.id}\n                className={cn(\n                  \"absolute inset-x-0 top-0 flex h-[var(--standings-row)] items-center gap-3 pr-2 pl-1 shadow-[inset_0_-1px_0_var(--standings-line)] will-change-transform @[24rem]/standings:pr-3\",\n                  entry.you\n                    ? \"bg-[color-mix(in_oklab,var(--standings-accent)_9%,var(--standings-surface))]\"\n                    : \"bg-[var(--standings-surface)]\",\n                )}\n                style={{ transform: `translateY(calc(var(--standings-row) * ${slot}))`, zIndex: 2 }}\n              >\n                <span\n                  data-lift=\"\"\n                  className=\"pointer-events-none absolute inset-0 opacity-0 shadow-[0_10px_24px_-10px_rgb(0_0_0/0.35),0_2px_6px_-2px_rgb(0_0_0/0.12)] will-change-[opacity] dark:shadow-[0_12px_28px_-8px_rgb(0_0_0/0.9),0_0_0_1px_rgb(255_255_255/0.08)]\"\n                />\n                {entry.you ? (\n                  <span className=\"absolute inset-y-0 left-0 w-0.5 bg-[var(--standings-accent)]\" />\n                ) : null}\n                {entry.icon ? (\n                  <span className=\"relative grid size-8 shrink-0 place-items-center overflow-hidden rounded-[9px]\">\n                    {entry.icon}\n                  </span>\n                ) : null}\n                <span className=\"relative min-w-0 flex-1 leading-tight\">\n                  <span className=\"flex min-w-0 items-center gap-1.5\">\n                    <span className={cn(\"truncate text-sm\", entry.you ? \"font-semibold\" : \"font-medium\")}>\n                      {entry.label}\n                    </span>\n                    {entry.you ? (\n                      <span className=\"hidden shrink-0 rounded-full bg-[var(--standings-accent)] px-1.5 py-px text-[10px] font-semibold tracking-wide text-white uppercase @[24rem]/standings:inline\">\n                        You\n                      </span>\n                    ) : null}\n                  </span>\n                  {entry.detail ? (\n                    <span className=\"mt-0.5 hidden truncate text-xs opacity-55 @[20rem]/standings:block\">\n                      {entry.detail}\n                    </span>\n                  ) : null}\n                </span>\n                <span className=\"relative hidden h-1.5 w-[clamp(3.5rem,22cqi,9rem)] shrink-0 overflow-hidden rounded-full bg-current/10 @[26rem]/standings:block\">\n                  <span\n                    data-bar=\"\"\n                    className={cn(\n                      \"absolute inset-0 rounded-full will-change-transform\",\n                      entry.you ? \"bg-[var(--standings-accent)]\" : \"bg-current/35\",\n                    )}\n                    style={{ transform: `translateX(${((scale - 1) * 100).toFixed(2)}%)` }}\n                  />\n                </span>\n                <span data-value=\"\" className=\"relative min-w-[6ch] text-right text-sm font-medium tabular-nums\">\n                  {seed?.text ?? format(valueAt(entry.value, start, count))}\n                </span>\n                {change ? (\n                  <span\n                    data-chip=\"\"\n                    data-delta={delta}\n                    data-dir={delta > 0 ? \"up\" : delta < 0 ? \"down\" : \"flat\"}\n                    className=\"group/chip relative hidden w-9 shrink-0 items-center justify-end gap-0.5 text-xs font-semibold tabular-nums data-[dir=down]:text-rose-600 data-[dir=flat]:opacity-35 data-[dir=up]:text-emerald-600 @[18rem]/standings:inline-flex dark:data-[dir=down]:text-rose-400 dark:data-[dir=up]:text-emerald-400\"\n                  >\n                    <svg\n                      viewBox=\"0 0 8 8\"\n                      className=\"size-2 group-data-[dir=down]/chip:rotate-180 group-data-[dir=flat]/chip:hidden\"\n                      fill=\"currentColor\"\n                    >\n                      <path d=\"M4 1.2 7.2 6.4H.8Z\" />\n                    </svg>\n                    <span data-count=\"\">{delta === 0 ? \"–\" : Math.abs(delta)}</span>\n                  </span>\n                ) : null}\n              </div>\n            );\n          })}\n        </div>\n      </div>\n\n      {timeline ? (\n        <div className=\"mt-3 flex items-start gap-3 pr-1 pl-0.5\">\n          <button\n            type=\"button\"\n            onClick={() => {\n              take();\n              engine.toggle(periodMs, loop);\n            }}\n            aria-label={status === \"playing\" ? \"Pause\" : status === \"ended\" ? \"Replay\" : \"Play\"}\n            className={`grid size-8 shrink-0 place-items-center rounded-full border border-current/15 transition-colors ${TIMING.button} hover:bg-current/5 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--standings-accent)]`}\n          >\n            <PlayIcon status={status} />\n          </button>\n          <div className=\"min-w-0 flex-1\">\n            <div\n              role=\"slider\"\n              tabIndex={0}\n              aria-label=\"Period\"\n              aria-valuemin={0}\n              aria-valuemax={lastIndex}\n              aria-valuenow={shown}\n              aria-valuetext={periods?.[shown]}\n              onPointerDown={onPointerDown}\n              onPointerMove={onPointerMove}\n              onPointerUp={onPointerUp}\n              onPointerCancel={onPointerUp}\n              onKeyDown={onKeyDown}\n              className=\"group/slider relative h-8 cursor-pointer touch-none outline-none select-none\"\n            >\n              <div ref={trackRef} className=\"absolute inset-x-1.5 top-1/2 h-0\">\n                <span className=\"absolute inset-x-0 -top-px h-[3px] overflow-hidden rounded-full bg-current/10\">\n                  <span\n                    data-fill=\"\"\n                    className=\"absolute inset-0 origin-left bg-[var(--standings-accent)] will-change-transform\"\n                    style={{ transform: `scaleX(${startP})` }}\n                  />\n                </span>\n                {periods?.map((label, i) => (\n                  <span\n                    key={label + i}\n                    data-tick=\"\"\n                    {...(i <= start ? { \"data-passed\": \"\" } : {})}\n                    className={`absolute -top-[3.5px] size-2 -translate-x-1/2 rounded-full border-2 border-[var(--standings-surface)] bg-[color-mix(in_oklab,currentColor_22%,var(--standings-surface))] transition-colors ${TIMING.tick} data-[passed]:bg-[var(--standings-accent)]`}\n                    style={{ left: `${(i / lastIndex) * 100}%` }}\n                  />\n                ))}\n                <span\n                  data-thumb=\"\"\n                  className=\"pointer-events-none absolute inset-x-0 top-0 will-change-transform\"\n                  style={{ transform: `translateX(${startP * 100}%)` }}\n                >\n                  <span className=\"absolute top-0 left-0 size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-[2.5px] border-[var(--standings-accent)] bg-[var(--standings-surface)] shadow-[0_1px_3px_rgb(0_0_0/0.2)] ring-[var(--standings-accent)]/30 group-focus-visible/slider:ring-4\" />\n                </span>\n              </div>\n            </div>\n            <div className=\"relative mx-1.5 -mt-1 h-4 text-[11px] leading-4 tabular-nums\" aria-hidden>\n              {periods?.map((label, i) => (\n                <span\n                  key={label + i}\n                  data-period-label=\"\"\n                  {...(i === start ? { \"data-current\": \"\" } : {})}\n                  className={cn(\n                    `absolute top-0 whitespace-nowrap opacity-0 transition-opacity ${TIMING.label} data-[current]:font-medium data-[current]:opacity-100 @[26rem]/standings:opacity-45 @[26rem]/standings:data-[current]:opacity-100`,\n                    i === 0 ? \"\" : i === lastIndex ? \"-translate-x-full\" : \"-translate-x-1/2\",\n                  )}\n                  style={{ left: `${(i / lastIndex) * 100}%` }}\n                >\n                  {label}\n                </span>\n              ))}\n            </div>\n          </div>\n        </div>\n      ) : null}\n\n      <ol className=\"sr-only\">\n        {ranking.map((id) => {\n          const e = byId.get(id);\n          if (!e) return null;\n          return (\n            <li key={id}>\n              {e.label}\n              {e.you ? \" (you)\" : \"\"}, {format(valueAt(e.value, shown, count))}\n            </li>\n          );\n        })}\n      </ol>\n      <p className=\"sr-only\" aria-live=\"polite\">\n        {announcement}\n      </p>\n    </div>\n  );\n}\n"
    }
  ]
}