# Standings: Launch Day

> A launch board on the day, ranked by live votes, where a fast riser works its way to the top.

A shadcn/ui block from the [@keyframeui](https://keyframeui.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/keyframeui/standings-launch-demo).

- Page, with a live preview: https://shadcn.exchange/r/keyframeui/standings-launch-demo
- Screenshot: https://shadcn.exchange/shot/keyframeui/standings-launch-demo/light.png
- Registry item: https://keyframeui.com/r/standings-launch-demo.json
- Categories: data, chart, motion
- Listed: 2026-11-02, source last changed 2026-11-02

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/keyframeui/standings-launch-demo.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `https://keyframeui.com/r/standings.json`

## Notes from the registry

Live preview, examples and props: https://keyframeui.com/components/standings#standings-launch-demo

## Files

### `registry/demos/standings-launch-demo.tsx`

```tsx
"use client";

import * as React from "react";

import { Standings, type StandingsEntry } from "@/components/ui/standings";

const TICK = 900; // ms between vote updates
const DAY = 30; // updates before the board starts a new day

type Product = { id: string; label: string; detail: string; from: string; to: string; votes: number; pace: [number, number]; you?: boolean };

// Launch day on Dawnlist, a board for new products. Inkwell launches fourth
// and gathers votes faster than the products above it.
const PRODUCTS: Product[] = [
  { id: "parcel", label: "Parcel Pigeon", detail: "Shipping labels in one click", from: "#38bdf8", to: "#0369a1", votes: 412, pace: [5, 13] },
  { id: "quietly", label: "Quietly", detail: "Noise cancelling for calls", from: "#a3e635", to: "#4d7c0f", votes: 388, pace: [5, 14] },
  { id: "moss", label: "Moss", detail: "Plant care, on a schedule", from: "#4ade80", to: "#15803d", votes: 351, pace: [4, 12] },
  { id: "inkwell", label: "Inkwell", detail: "An AI editor for long documents", from: "#ff8a3d", to: "#ff3d00", votes: 296, pace: [17, 29], you: true },
  { id: "tidy", label: "Tidy Tabs", detail: "Your tabs, sorted by project", from: "#c084fc", to: "#7e22ce", votes: 240, pace: [3, 11] },
  { id: "brightwave", label: "Brightwave", detail: "Podcast clips for social", from: "#f472b6", to: "#be185d", votes: 198, pace: [3, 10] },
];

const START = Object.fromEntries(PRODUCTS.map((p) => [p.id, p.votes]));

/** A small deterministic random, so every day plays out the same way. */
function random(seed: number) {
  const x = Math.sin(seed * 12.9898) * 43758.5453;
  return x - Math.floor(x);
}

function Mark({ from, to, label }: { from: string; to: string; label: string }) {
  return (
    <span
      className="grid size-full place-items-center text-[13px] font-semibold text-white"
      style={{ backgroundImage: `linear-gradient(150deg, ${from}, ${to})` }}
    >
      {label[0]}
    </span>
  );
}

const clock = (tick: number) => {
  const minutes = 9 * 60 + 12 + tick * 17;
  return `${Math.floor(minutes / 60)}:${String(minutes % 60).padStart(2, "0")}`;
};

export function StandingsLaunchDemo() {
  const ref = React.useRef<HTMLDivElement>(null);
  const [tick, setTick] = React.useState(0);
  const [votes, setVotes] = React.useState(START);

  // Votes come in while the board is on screen.
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    let timer = 0;
    let seen = false;
    let n = 0;
    let current = START;
    const run = () => {
      window.clearInterval(timer);
      timer = window.setInterval(() => {
        n += 1;
        if (n > DAY) {
          n = 0;
          current = START;
        } else {
          const next: Record<string, number> = {};
          for (const p of PRODUCTS) {
            // Inkwell's early rush settles once it's had its morning.
            const [lo, hi] = p.you && n > 14 ? [6, 15] : p.pace;
            next[p.id] = current[p.id] + Math.round(lo + (hi - lo) * random(n * 31 + p.votes));
          }
          current = next;
        }
        setTick(n);
        setVotes(current);
      }, TICK);
    };
    const io = new IntersectionObserver(([entry]) => {
      seen = entry.isIntersecting;
      if (seen && !document.hidden) run();
      else window.clearInterval(timer);
    });
    io.observe(el);
    const onVisibility = () => (seen && !document.hidden ? run() : window.clearInterval(timer));
    document.addEventListener("visibilitychange", onVisibility);
    return () => {
      window.clearInterval(timer);
      io.disconnect();
      document.removeEventListener("visibilitychange", onVisibility);
    };
  }, []);

  const entries = React.useMemo<StandingsEntry[]>(
    () =>
      PRODUCTS.map((p) => ({
        id: p.id,
        label: p.label,
        detail: p.detail,
        you: p.you,
        value: votes[p.id],
        icon: <Mark from={p.from} to={p.to} label={p.label} />,
      })),
    [votes],
  );

  return (
    <div
      ref={ref}
      className="w-full max-w-xl rounded-2xl border border-zinc-950/[0.08] bg-white text-zinc-950 shadow-[0_1px_2px_rgb(0_0_0/0.04),0_12px_32px_-12px_rgb(0_0_0/0.12)] dark:border-white/10 dark:bg-zinc-950 dark:text-zinc-50"
    >
      <div className="flex items-center justify-between gap-3 border-b border-zinc-950/[0.07] px-5 py-4 dark:border-white/[0.08]">
        <div className="min-w-0">
          <p className="text-[13px] font-semibold tracking-[-0.01em]">Launching today on Dawnlist</p>
          <p className="mt-0.5 text-xs text-zinc-500 dark:text-zinc-400">Ranked by votes · resets at midnight</p>
        </div>
        <p className="flex shrink-0 items-center gap-2 rounded-full border border-zinc-950/[0.08] py-1 pr-2.5 pl-2 text-xs font-medium tabular-nums dark:border-white/10">
          <span className="size-1.5 rounded-full bg-rose-500" aria-hidden />
          Live · {clock(tick)}
        </p>
      </div>
      <Standings
        aria-label="Today's launches, ranked by votes"
        entries={entries}
        className="px-2 pt-1 pb-2 [--standings-surface:#ffffff] dark:[--standings-surface:#09090b]"
      />
    </div>
  );
}
```
