# Playhead Reel: Changelog

> Six releases as live UI mockups rather than screenshots, so they stay sharp and follow your theme. The header shows the version and date, and a caption for the screen under the playhead. Tab to the playhead and use the arrow keys: Shift jumps a release.

A shadcn/ui block from the [@keyframeui](https://keyframeui.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/keyframeui/playhead-reel-changelog-demo).

- Page, with a live preview: https://shadcn.exchange/r/keyframeui/playhead-reel-changelog-demo
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/keyframeui/playhead-reel-changelog-demo-light-264ec2d8dadc841c.png
- Registry item: https://keyframeui.com/r/playhead-reel-changelog-demo.json
- Categories: gallery, scroll, timeline
- Listed: 2026-10-14, source last changed 2026-10-15

## Install

```bash
npx shadcn@latest add https://keyframeui.com/r/playhead-reel-changelog-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/playhead-reel.json`

## Notes from the registry

Live preview, examples and props: https://keyframeui.com/components/playhead-reel#playhead-reel-changelog-demo

## Files

### `registry/demos/playhead-reel-changelog-demo.tsx`

```tsx
"use client";

import * as React from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";

import { cn } from "@/lib/utils";
import { PlayheadReel, type PlayheadReelGroup } from "@/components/ui/playhead-reel";

type Accent = { badge: string; solid: string; soft: string; text: string };

const accents = {
  indigo: {
    badge: "bg-indigo-500/10 text-indigo-700 dark:text-indigo-300",
    solid: "bg-indigo-500",
    soft: "bg-indigo-500/15",
    text: "text-indigo-500",
  },
  emerald: {
    badge: "bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
    solid: "bg-emerald-500",
    soft: "bg-emerald-500/15",
    text: "text-emerald-500",
  },
  amber: {
    badge: "bg-amber-500/10 text-amber-700 dark:text-amber-300",
    solid: "bg-amber-500",
    soft: "bg-amber-500/15",
    text: "text-amber-500",
  },
  rose: {
    badge: "bg-rose-500/10 text-rose-700 dark:text-rose-300",
    solid: "bg-rose-500",
    soft: "bg-rose-500/15",
    text: "text-rose-500",
  },
  sky: {
    badge: "bg-sky-500/10 text-sky-700 dark:text-sky-300",
    solid: "bg-sky-500",
    soft: "bg-sky-500/15",
    text: "text-sky-500",
  },
  violet: {
    badge: "bg-violet-500/10 text-violet-700 dark:text-violet-300",
    solid: "bg-violet-500",
    soft: "bg-violet-500/15",
    text: "text-violet-500",
  },
} satisfies Record<string, Accent>;

type Release = PlayheadReelGroup & { title: string; date: string; accent: Accent };

const screen = (alt: string, content: React.ReactNode, aspect = 4 / 3) => ({ alt, content, aspect });

const releases: Release[] = [
  {
    label: "v2.0",
    title: "A faster editor",
    date: "January 2025",
    accent: accents.indigo,
    items: [
      screen("Pages open instantly, even with thousands of blocks.", <EditorScreen accent={accents.indigo} />),
      screen("Type / to insert any block.", <SlashScreen accent={accents.indigo} />),
      screen("The editor now works on phones.", <PhoneScreen accent={accents.indigo} />, 3 / 4),
    ],
  },
  {
    label: "v2.1",
    title: "Comments",
    date: "March 2025",
    accent: accents.emerald,
    items: [
      screen("Comment on any block and reply in threads.", <ThreadScreen accent={accents.emerald} />),
      screen("@mention a teammate to pull them in.", <MentionsScreen accent={accents.emerald} />, 3 / 4),
      screen("Resolve a thread when it's done.", <ResolveScreen accent={accents.emerald} />),
    ],
  },
  {
    label: "v2.2",
    title: "Insights",
    date: "May 2025",
    accent: accents.amber,
    items: [
      screen("See who's active each week.", <BarsScreen accent={accents.amber} />),
      screen("Spot trends over any range.", <TrendScreen accent={accents.amber} />, 3 / 2),
      screen("Export any report as CSV, PDF or PNG.", <ExportScreen accent={accents.amber} />, 1),
    ],
  },
  {
    label: "v2.3",
    title: "Boards",
    date: "July 2025",
    accent: accents.rose,
    items: [
      screen("Drag work across columns.", <BoardScreen accent={accents.rose} />, 3 / 2),
      screen("See every deadline on a calendar.", <CalendarScreen accent={accents.rose} />, 1),
      screen("Plan projects on a timeline.", <TimelineScreen accent={accents.rose} />),
    ],
  },
  {
    label: "v2.4",
    title: "Command palette",
    date: "September 2025",
    accent: accents.sky,
    items: [
      screen("Search pages, actions and settings from ⌘K.", <PaletteScreen accent={accents.sky} />),
      screen("Every action has a shortcut.", <ShortcutsScreen />, 3 / 4),
      screen("Dark mode follows your system.", <ThemeScreen accent={accents.sky} />),
    ],
  },
  {
    label: "v2.5",
    title: "Teams",
    date: "November 2025",
    accent: accents.violet,
    items: [
      screen("Roles for owners, admins and members.", <MembersScreen accent={accents.violet} />),
      screen("Invite a whole team at once.", <InviteScreen accent={accents.violet} />, 1),
      screen("An audit log of every change.", <AuditScreen />, 3 / 2),
    ],
  },
];

export function PlayheadReelChangelogDemo() {
  const scrollRef = React.useRef<HTMLDivElement>(null);
  const [view, setView] = React.useState({ group: 0, item: 0, direction: 1 });
  const release = releases[view.group];
  const caption = release.items[view.item]?.alt;

  return (
    <div className="relative w-full overflow-hidden rounded-xl border border-zinc-200 bg-zinc-50 text-zinc-900 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-100">
      <div ref={scrollRef} className="relative h-[var(--demo-height,38rem)] overflow-y-auto overscroll-contain">
        <PlayheadReel
          groups={releases}
          containerRef={scrollRef}
          stageClassName="h-[var(--demo-height,38rem)]"
          itemClassName="rounded-xl ring-1 ring-zinc-900/10 dark:ring-white/10"
          thumbHeight={36}
          onActiveChange={({ group, item }) =>
            setView((prev) => ({
              group,
              item,
              direction: group * 100 + item >= prev.group * 100 + prev.item ? 1 : -1,
            }))
          }
          header={
            <header className="px-4 pt-5 pb-4 sm:px-8 sm:pt-7">
              <div className="flex items-center justify-between gap-4 font-mono text-[11px] tabular-nums">
                <div className="flex items-center gap-2">
                  <span className="tracking-widest text-zinc-500 uppercase">Changelog</span>
                  <span
                    className={cn(
                      "rounded-full px-2 py-0.5 font-medium transition-colors duration-300",
                      release.accent.badge,
                    )}
                  >
                    {release.label}
                  </span>
                </div>
                <span className="text-zinc-500">{release.date}</span>
              </div>
              <Roll id={view.group} direction={view.direction} className="mt-2">
                <h2 className="truncate text-2xl leading-[1.2] font-semibold tracking-tight sm:text-4xl">
                  {release.title}
                </h2>
              </Roll>
              <Roll id={view.group * 100 + view.item} direction={view.direction} className="mt-1">
                <p className="truncate text-sm leading-6 text-zinc-500">{caption}</p>
              </Roll>
            </header>
          }
        />
      </div>
    </div>
  );
}

/** Rolls its content out and the next one in, up when moving forward and down when going back. */
function Roll({
  id,
  direction,
  className,
  children,
}: {
  id: number;
  direction: number;
  className?: string;
  children: React.ReactNode;
}) {
  const reduceMotion = useReducedMotion();
  return (
    <div className={cn("relative overflow-hidden", className)}>
      <AnimatePresence mode="popLayout" initial={false} custom={direction}>
        <motion.div
          key={id}
          custom={direction}
          variants={{
            enter: (d: number) => (reduceMotion ? { opacity: 0 } : { y: `${d * 100}%` }),
            center: { y: 0, opacity: 1 },
            exit: (d: number) => (reduceMotion ? { opacity: 0 } : { y: `${d * -100}%` }),
          }}
          initial="enter"
          animate="center"
          exit="exit"
          transition={{ duration: reduceMotion ? 0.2 : 0.5, ease: [0.23, 1, 0.32, 1] }}
        >
          {children}
        </motion.div>
      </AnimatePresence>
    </div>
  );
}

/* ── Screens ─────────────────────────────────────────────
 * Small product mockups in plain markup. Each fills its item
 * at the item's open size, so text stays crisp when open. */

function Window({ title, children, className }: { title?: string; children: React.ReactNode; className?: string }) {
  return (
    <div className="flex size-full flex-col bg-white text-[10px] leading-tight text-zinc-900 dark:bg-zinc-900 dark:text-zinc-100">
      <div className="flex shrink-0 items-center gap-1 border-b border-zinc-200 px-2.5 py-1.5 dark:border-zinc-800">
        <span className="size-1.5 rounded-full bg-zinc-300 dark:bg-zinc-700" />
        <span className="size-1.5 rounded-full bg-zinc-300 dark:bg-zinc-700" />
        <span className="size-1.5 rounded-full bg-zinc-300 dark:bg-zinc-700" />
        {title ? <span className="ml-1.5 truncate text-[9px] text-zinc-400">{title}</span> : null}
      </div>
      <div className={cn("relative min-h-0 flex-1 overflow-hidden p-3", className)}>{children}</div>
    </div>
  );
}

function Bar({ width, className }: { width: string; className?: string }) {
  return (
    <span
      className={cn("block h-1.5 shrink-0 rounded-full bg-zinc-200 dark:bg-zinc-800", className)}
      style={{ width }}
    />
  );
}

function Avatar({ name, className }: { name: string; className: string }) {
  return (
    <span
      className={cn(
        "grid size-5 shrink-0 place-items-center rounded-full text-[8px] font-semibold text-white",
        className,
      )}
    >
      {name}
    </span>
  );
}

function Kbd({ children }: { children: React.ReactNode }) {
  return (
    <kbd className="rounded border border-zinc-200 bg-zinc-50 px-1 font-mono text-[8px] text-zinc-500 dark:border-zinc-700 dark:bg-zinc-800">
      {children}
    </kbd>
  );
}

function EditorScreen({ accent }: { accent: Accent }) {
  return (
    <Window title="Launch plan">
      <div className="flex gap-1">
        {["B", "I", "U", "H1", "H2", "“ ”"].map((tool) => (
          <span
            key={tool}
            className="grid h-4 min-w-4 place-items-center rounded bg-zinc-100 px-1 text-[8px] font-semibold text-zinc-500 dark:bg-zinc-800"
          >
            {tool}
          </span>
        ))}
      </div>
      <p className="mt-3 text-[14px] font-semibold tracking-tight">Launch plan</p>
      <div className="mt-2 space-y-1.5">
        <Bar width="94%" />
        <Bar width="82%" />
        <Bar width="88%" />
      </div>
      <div className="mt-2 flex items-center gap-1">
        <Bar width="46%" className={accent.soft} />
        <span className={cn("h-3 w-px", accent.solid)} />
      </div>
    </Window>
  );
}

function SlashScreen({ accent }: { accent: Accent }) {
  const blocks = ["Heading", "To-do list", "Image", "Code", "Quote"];
  return (
    <Window title="Launch plan">
      <div className="space-y-1.5">
        <Bar width="78%" />
        <Bar width="64%" />
      </div>
      <p className="mt-2 font-mono text-zinc-400">/</p>
      <div className="absolute top-12 left-6 w-32 rounded-lg border border-zinc-200 bg-white p-1 shadow-lg dark:border-zinc-700 dark:bg-zinc-800">
        {blocks.map((block, i) => (
          <div
            key={block}
            className={cn("flex items-center gap-1.5 rounded px-1.5 py-1", i === 1 && accent.soft)}
          >
            <span className="size-3 rounded-sm border border-zinc-300 dark:border-zinc-600" />
            {block}
          </div>
        ))}
      </div>
    </Window>
  );
}

function PhoneScreen({ accent }: { accent: Accent }) {
  return (
    <div className="grid size-full place-items-center bg-zinc-100 p-4 dark:bg-zinc-800">
      <div className="flex h-full max-h-80 w-full max-w-40 flex-col gap-2 rounded-[1.25rem] border-4 border-zinc-900 bg-white p-3 text-[10px] dark:border-zinc-950 dark:bg-zinc-900">
        <span className="mx-auto h-1 w-8 rounded-full bg-zinc-300 dark:bg-zinc-700" />
        <p className="mt-1 text-[12px] font-semibold tracking-tight">Launch plan</p>
        <Bar width="92%" />
        <Bar width="76%" />
        <Bar width="84%" />
        <div className="mt-auto flex justify-between">
          <span className={cn("h-5 w-14 rounded-full", accent.soft)} />
          <span className={cn("size-5 rounded-full", accent.solid)} />
        </div>
      </div>
    </div>
  );
}

function ThreadScreen({ accent }: { accent: Accent }) {
  const comments = [
    { who: "MA", name: "Maya", color: "bg-orange-400", widths: ["80%", "56%"] },
    { who: "JO", name: "Jonas", color: "bg-sky-500", widths: ["68%"] },
  ];
  return (
    <Window title="Comments">
      <div className="space-y-3">
        {comments.map((comment) => (
          <div key={comment.who} className="flex gap-2">
            <Avatar name={comment.who} className={comment.color} />
            <div className="min-w-0 flex-1 space-y-1">
              <p className="font-semibold">
                {comment.name} <span className="font-normal text-zinc-400">2m</span>
              </p>
              {comment.widths.map((width) => (
                <Bar key={width} width={width} />
              ))}
            </div>
          </div>
        ))}
        <div className="flex items-center gap-2 rounded-md border border-zinc-200 px-2 py-1.5 dark:border-zinc-700">
          <span className="text-zinc-400">Reply…</span>
          <span className={cn("ml-auto rounded px-1.5 py-0.5 font-medium text-white", accent.solid)}>Send</span>
        </div>
      </div>
    </Window>
  );
}

function MentionsScreen({ accent }: { accent: Accent }) {
  const notes = [
    { who: "MA", color: "bg-orange-400", text: "Maya mentioned you" },
    { who: "JO", color: "bg-sky-500", text: "Jonas replied" },
    { who: "RI", color: "bg-fuchsia-500", text: "Rin mentioned you" },
  ];
  return (
    <div className="flex size-full flex-col justify-center gap-2 bg-zinc-100 p-3 text-[10px] dark:bg-zinc-800">
      {notes.map((note, i) => (
        <div
          key={note.who}
          className="flex items-center gap-2 rounded-lg bg-white p-2 shadow-sm dark:bg-zinc-900"
          style={{ opacity: 1 - i * 0.2 }}
        >
          <Avatar name={note.who} className={note.color} />
          <div className="min-w-0 flex-1">
            <p className="truncate font-semibold">{note.text}</p>
            <p className="truncate text-zinc-400">in Launch plan</p>
          </div>
          {i === 0 ? <span className={cn("size-1.5 rounded-full", accent.solid)} /> : null}
        </div>
      ))}
    </div>
  );
}

function ResolveScreen({ accent }: { accent: Accent }) {
  return (
    <Window title="Comments">
      <div className="rounded-lg border border-zinc-200 p-2.5 dark:border-zinc-700">
        <div className="flex items-center gap-2">
          <Avatar name="JO" className="bg-sky-500" />
          <p className="font-semibold">Jonas</p>
          <span className={cn("ml-auto flex items-center gap-1 rounded-full px-1.5 py-0.5 font-medium", accent.badge)}>
            <svg viewBox="0 0 12 12" className="size-2.5" aria-hidden>
              <path d="M2.5 6.5l2.2 2L9.5 3.5" fill="none" stroke="currentColor" strokeWidth="1.6" />
            </svg>
            Resolved
          </span>
        </div>
        <div className="mt-2 space-y-1.5 opacity-50">
          <Bar width="86%" />
          <Bar width="60%" />
        </div>
      </div>
    </Window>
  );
}

function BarsScreen({ accent }: { accent: Accent }) {
  const heights = [38, 52, 44, 61, 58, 70, 66, 74, 69, 82, 78, 92];
  return (
    <Window title="Insights">
      <div className="flex h-full flex-col">
        <p className="text-zinc-400">Weekly active</p>
        <div className="flex items-baseline gap-1.5">
          <p className="text-[18px] font-semibold tracking-tight tabular-nums">12,480</p>
          <span className={cn("rounded-full px-1.5 font-medium", accent.badge)}>+8.2%</span>
        </div>
        <div className="mt-2 flex min-h-0 flex-1 items-end gap-1">
          {heights.map((height, i) => (
            <span
              key={i}
              className={cn("flex-1 rounded-sm", i === heights.length - 1 ? accent.solid : accent.soft)}
              style={{ height: `${height}%` }}
            />
          ))}
        </div>
      </div>
    </Window>
  );
}

function TrendScreen({ accent }: { accent: Accent }) {
  const points = [62, 58, 64, 55, 49, 52, 44, 38, 42, 33, 28, 30, 22, 18];
  const line = points.map((y, i) => `${(i / (points.length - 1)) * 100},${y}`).join(" ");
  return (
    <Window title="Insights · Last 90 days">
      <div className="flex h-full flex-col">
        <div className="flex gap-1">
          {["7d", "30d", "90d"].map((range) => (
            <span
              key={range}
              className={cn(
                "rounded px-1.5 py-0.5 font-medium",
                range === "90d" ? accent.badge : "text-zinc-400",
              )}
            >
              {range}
            </span>
          ))}
        </div>
        <svg viewBox="0 0 100 70" preserveAspectRatio="none" className={cn("mt-2 min-h-0 w-full flex-1", accent.text)} aria-hidden>
          <polygon points={`0,70 ${line} 100,70`} fill="currentColor" opacity="0.12" />
          <polyline points={line} fill="none" stroke="currentColor" strokeWidth="1.5" vectorEffect="non-scaling-stroke" />
        </svg>
      </div>
    </Window>
  );
}

function ExportScreen({ accent }: { accent: Accent }) {
  const formats = ["CSV", "PDF", "PNG"];
  return (
    <div className="grid size-full place-items-center bg-zinc-100 p-4 text-[10px] dark:bg-zinc-800">
      <div className="w-full max-w-48 rounded-xl bg-white p-3 shadow-lg dark:bg-zinc-900">
        <p className="text-[12px] font-semibold">Export report</p>
        <div className="mt-2 space-y-1">
          {formats.map((format, i) => (
            <div
              key={format}
              className={cn(
                "flex items-center gap-2 rounded-md border px-2 py-1.5",
                i === 1 ? "border-transparent " + accent.soft : "border-zinc-200 dark:border-zinc-700",
              )}
            >
              <span
                className={cn(
                  "size-2.5 rounded-full border",
                  i === 1 ? "border-4 border-current " + accent.text : "border-zinc-300 dark:border-zinc-600",
                )}
              />
              {format}
            </div>
          ))}
        </div>
        <div className={cn("mt-2.5 rounded-md py-1.5 text-center font-medium text-white", accent.solid)}>Export</div>
      </div>
    </div>
  );
}

function BoardScreen({ accent }: { accent: Accent }) {
  const columns = [
    { name: "To do", cards: 3 },
    { name: "Doing", cards: 2 },
    { name: "Done", cards: 2 },
  ];
  return (
    <Window title="Boards · Website">
      <div className="grid grid-cols-3 items-start gap-2">
        {columns.map((column, c) => (
          <div key={column.name} className="space-y-1.5 rounded-lg bg-zinc-100 p-1.5 dark:bg-zinc-800">
            <p className="font-semibold text-zinc-500">{column.name}</p>
            {Array.from({ length: column.cards }, (_, i) => (
              <div key={i} className="space-y-1 rounded-md bg-white p-1.5 shadow-sm dark:bg-zinc-900">
                <Bar width={`${70 + ((i + c) % 3) * 10}%`} />
                {c === 1 && i === 0 ? <span className={cn("block h-1.5 w-8 rounded-full", accent.solid)} /> : null}
              </div>
            ))}
          </div>
        ))}
      </div>
    </Window>
  );
}

function CalendarScreen({ accent }: { accent: Accent }) {
  const events: Record<number, string> = { 4: "Review", 11: "Launch", 17: "Retro", 24: "Ship" };
  return (
    <Window title="July">
      <div className="grid grid-cols-7 gap-0.5 text-center text-[8px]">
        {["M", "T", "W", "T", "F", "S", "S"].map((day, i) => (
          <span key={i} className="pb-0.5 text-zinc-400">
            {day}
          </span>
        ))}
        {Array.from({ length: 28 }, (_, i) => (
          <div key={i} className="flex aspect-square flex-col items-center gap-0.5 rounded bg-zinc-50 pt-0.5 dark:bg-zinc-800/60">
            <span className="text-zinc-500 tabular-nums">{i + 1}</span>
            {events[i + 1] ? <span className={cn("h-1 w-3/4 rounded-full", accent.solid)} /> : null}
          </div>
        ))}
      </div>
    </Window>
  );
}

function TimelineScreen({ accent }: { accent: Accent }) {
  const rows = [
    { name: "Research", start: 0, span: 30 },
    { name: "Design", start: 22, span: 34 },
    { name: "Build", start: 48, span: 40 },
    { name: "Launch", start: 84, span: 14 },
  ];
  return (
    <Window title="Timeline · Q3">
      <div className="space-y-2.5">
        {rows.map((row, i) => (
          <div key={row.name} className="flex items-center gap-2">
            <span className="w-12 shrink-0 text-zinc-500">{row.name}</span>
            <div className="relative h-3 flex-1 rounded bg-zinc-100 dark:bg-zinc-800">
              <span
                className={cn("absolute inset-y-0 rounded", i === 2 ? accent.solid : accent.soft)}
                style={{ left: `${row.start}%`, width: `${row.span}%` }}
              />
            </div>
          </div>
        ))}
      </div>
    </Window>
  );
}

function PaletteScreen({ accent }: { accent: Accent }) {
  const actions = ["New document", "Go to Insights", "Invite people", "Toggle dark mode"];
  return (
    <div className="grid size-full place-items-center bg-zinc-900/5 p-4 text-[10px] dark:bg-black/40">
      <div className="w-full max-w-56 overflow-hidden rounded-xl bg-white shadow-xl ring-1 ring-zinc-900/10 dark:bg-zinc-900 dark:ring-white/10">
        <div className="flex items-center gap-2 border-b border-zinc-200 px-2.5 py-2 dark:border-zinc-800">
          <span className="text-zinc-400">Search or jump to…</span>
          <span className="ml-auto">
            <Kbd>⌘K</Kbd>
          </span>
        </div>
        <div className="p-1">
          {actions.map((action, i) => (
            <div key={action} className={cn("flex items-center gap-2 rounded-md px-2 py-1.5", i === 0 && accent.soft)}>
              <span className="size-2.5 rounded-sm border border-zinc-300 dark:border-zinc-600" />
              {action}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function ShortcutsScreen() {
  const shortcuts = [
    ["Search", ["⌘", "K"]],
    ["New page", ["⌘", "N"]],
    ["Comment", ["⌘", "⇧", "M"]],
    ["Bold", ["⌘", "B"]],
    ["Go to board", ["G", "B"]],
    ["Help", ["?"]],
  ] as const;
  return (
    <Window title="Shortcuts">
      <div className="space-y-2">
        {shortcuts.map(([name, keys]) => (
          <div key={name} className="flex items-center justify-between gap-2">
            <span>{name}</span>
            <span className="flex gap-0.5">
              {keys.map((key) => (
                <Kbd key={key}>{key}</Kbd>
              ))}
            </span>
          </div>
        ))}
      </div>
    </Window>
  );
}

function ThemeScreen({ accent }: { accent: Accent }) {
  return (
    <div className="grid size-full grid-cols-2 text-[10px]">
      {[false, true].map((dark) => (
        <div
          key={String(dark)}
          className={cn("flex flex-col gap-1.5 p-3", dark ? "bg-zinc-900 text-zinc-100" : "bg-white text-zinc-900")}
        >
          <p className="font-semibold">{dark ? "Dark" : "Light"}</p>
          <span className={cn("block h-1.5 w-11/12 rounded-full", dark ? "bg-zinc-700" : "bg-zinc-200")} />
          <span className={cn("block h-1.5 w-2/3 rounded-full", dark ? "bg-zinc-700" : "bg-zinc-200")} />
          <span className={cn("mt-auto block h-4 w-10 rounded-full", accent.solid)} />
        </div>
      ))}
    </div>
  );
}

function MembersScreen({ accent }: { accent: Accent }) {
  const members = [
    { who: "MA", name: "Maya Ito", color: "bg-orange-400", role: "Owner" },
    { who: "JO", name: "Jonas Berg", color: "bg-sky-500", role: "Admin" },
    { who: "RI", name: "Rin Okafor", color: "bg-fuchsia-500", role: "Member" },
    { who: "LU", name: "Luca Sato", color: "bg-emerald-500", role: "Member" },
  ];
  return (
    <Window title="Members">
      <div className="space-y-2">
        {members.map((member) => (
          <div key={member.who} className="flex items-center gap-2">
            <Avatar name={member.who} className={member.color} />
            <span className="min-w-0 flex-1 truncate font-medium">{member.name}</span>
            <span
              className={cn(
                "rounded-full px-1.5 py-0.5",
                member.role === "Owner" ? accent.badge : "bg-zinc-100 text-zinc-500 dark:bg-zinc-800",
              )}
            >
              {member.role}
            </span>
          </div>
        ))}
      </div>
    </Window>
  );
}

function InviteScreen({ accent }: { accent: Accent }) {
  return (
    <div className="grid size-full place-items-center bg-zinc-100 p-4 text-[10px] dark:bg-zinc-800">
      <div className="w-full max-w-52 rounded-xl bg-white p-3 shadow-lg dark:bg-zinc-900">
        <p className="text-[12px] font-semibold">Invite to Acme</p>
        <div className="mt-2 flex flex-wrap gap-1 rounded-md border border-zinc-200 p-1.5 dark:border-zinc-700">
          {["rin@acme.co", "luca@acme.co", "ana@acme.co"].map((email) => (
            <span key={email} className={cn("rounded px-1.5 py-0.5", accent.badge)}>
              {email}
            </span>
          ))}
        </div>
        <div className="mt-2 flex gap-1.5">
          <span className="flex-1 rounded-md border border-zinc-200 px-2 py-1.5 text-zinc-500 dark:border-zinc-700">
            Member ▾
          </span>
          <span className={cn("rounded-md px-2 py-1.5 font-medium text-white", accent.solid)}>Send</span>
        </div>
      </div>
    </div>
  );
}

function AuditScreen() {
  const entries = [
    ["09:41", "Maya changed Rin's role to Admin"],
    ["09:12", "Jonas exported Insights as PDF"],
    ["08:57", "Luca joined the workspace"],
    ["08:30", "Maya turned on SSO"],
  ];
  return (
    <Window title="Audit log">
      <div className="divide-y divide-zinc-100 dark:divide-zinc-800">
        {entries.map(([time, text]) => (
          <div key={time} className="flex gap-3 py-1.5">
            <span className="font-mono text-zinc-400 tabular-nums">{time}</span>
            <span className="truncate">{text}</span>
          </div>
        ))}
      </div>
    </Window>
  );
}
```
