{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "bitting-cut",
  "title": "Bitting Cut",
  "description": "Every passkey in a sign-in-methods list gets a physical key blade whose six notch depths are cut deterministically from its credential id, so the MacBook passkey and the phone passkey stay visually distinct objects across sessions.",
  "dependencies": [],
  "files": [
    {
      "path": "registry/core/bitting-cut/component.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useId, useRef, useState } from \"react\";\n\n// ---------------------------------------------------------------------------\n// BittingCut — a passkey's credential ID rendered as a physical key blade, so\n// \"the MacBook passkey\" and \"the phone passkey\" are visually distinct objects\n// a person recognizes across sessions, not two identical shield icons.\n//\n// The bitting profile (6 notch depths, quantized to 5 levels — real key-cut\n// vocabulary) is a PURE function of the credential id: same id -> same cuts,\n// every mount, forever. That is the whole point — recognition requires\n// determinism, so there is no randomness anywhere in the depth derivation.\n// Only a freshly-enrolled key (added at runtime, given a fresh random\n// credential id) gets a NEW, still-deterministic-for-that-id profile.\n//\n// Enrollment plays a one-shot 700ms cutter pass: a cutting wheel sweeps the\n// blade once and each notch STEPS to its final depth exactly as the wheel\n// crosses it (a cutting wheel does not tween — it commits), with a brief\n// --foreground burr flash at the instant of each cut. Removal runs the exact\n// same traversal in \"file\" mode — every notch steps back to blank as the\n// wheel passes — before the row collapses out of the list. Reduced motion\n// skips both passes and lands directly on the finished (cut or blank) shape.\n//\n// Real list, real buttons, real text: name / created / last-used are plain\n// DOM text beside the art, the SVG key is aria-hidden, and removal goes\n// through an arm-then-confirm button (mirrors the repo's existing evict\n// pattern) with a polite live-region announcement once it lands.\n// ---------------------------------------------------------------------------\n\nexport interface Passkey {\n  /** stable credential id — the ONLY input the bitting profile is derived from */\n  id: string;\n  /** device or credential label, e.g. \"iPhone 16\" */\n  name: string;\n  /** device glyph shown in the key's bow; inferred from `name` when omitted */\n  device?: \"phone\" | \"laptop\" | \"tablet\" | \"desktop\" | \"key\" | \"generic\";\n  /** epoch ms (or Date) this credential was enrolled */\n  createdAt: number | Date;\n  /** epoch ms (or Date) of last use; null/undefined = never used since enrollment */\n  lastUsedAt?: number | Date | null;\n}\n\nexport interface BittingCutProps {\n  /** the starting set of enrolled passkeys, rendered already-cut (no entrance animation) */\n  initialPasskeys?: Passkey[];\n  /** stop offering \"Add passkey\" once the list reaches this size. default 6 */\n  maxPasskeys?: number;\n  /** accessible name for the list. default \"Passkeys\" */\n  ariaLabel?: string;\n  /** fired once a newly-generated passkey has been appended */\n  onAdd?: (passkey: Passkey) => void;\n  /** fired once a passkey has finished its removal (file + collapse) pass */\n  onRemove?: (id: string) => void;\n  /** extra classes merged onto the rendered root element */\n  className?: string;\n}\n\n// ---- deterministic bitting -------------------------------------------------\n\nconst N_NOTCHES = 6;\nconst N_LEVELS = 5; // depths 0..4\nconst CUT_MS = 700;\nconst ZERO_DEPTHS = new Array(N_NOTCHES).fill(0) as number[];\nconst CENTER_FRACTIONS = Array.from({ length: N_NOTCHES }, (_, i) => (i + 0.5) / N_NOTCHES);\n\nfunction fnv1a(input: string): number {\n  let h = 0x811c9dc5;\n  for (let i = 0; i < input.length; i++) {\n    h ^= input.charCodeAt(i);\n    h = Math.imul(h, 0x01000193);\n  }\n  return h >>> 0;\n}\n\n/** pure function of `credentialId` — identical every call, every mount. */\nfunction bittingDepths(credentialId: string): number[] {\n  const depths: number[] = [];\n  for (let i = 0; i < N_NOTCHES; i++) {\n    depths.push(fnv1a(`${credentialId}#${i}`) % N_LEVELS);\n  }\n  return depths;\n}\n\n// ---- key geometry (single blade path, ring bow) ----------------------------\n\nconst BOW_CX = 16;\nconst BOW_CY = 20;\nconst BOW_R = 11;\nconst X0 = 27; // blade shoulder, meets the bow ring\nconst X1 = 100; // blade tip\nconst TOP = 15;\nconst BASE = 28;\nconst DEPTH_STEP = 2.6;\nconst VIEW_W = 112;\nconst VIEW_H = 40;\n\nfunction round2(v: number): number {\n  return Math.round(v * 100) / 100;\n}\n\n/** the blade silhouette as ONE closed path — stepped cuts, never a smooth curve. */\nfunction bladePath(depths: number[]): string {\n  const n = depths.length;\n  const cellW = (X1 - X0) / n;\n  const xs = Array.from({ length: n + 1 }, (_, i) => X0 + i * cellW);\n  const ys = depths.map((d) => BASE - d * DEPTH_STEP);\n  let d = `M${round2(xs[0])},${TOP} L${round2(xs[n])},${TOP} L${round2(xs[n])},${round2(ys[n - 1])}`;\n  for (let i = n - 1; i >= 0; i--) {\n    d += ` L${round2(xs[i])},${round2(ys[i])}`;\n    if (i > 0) d += ` L${round2(xs[i])},${round2(ys[i - 1])}`;\n  }\n  d += ` L${round2(xs[0])},${BASE} Z`;\n  return d;\n}\n\nfunction notchCenters(): number[] {\n  const cellW = (X1 - X0) / N_NOTCHES;\n  return CENTER_FRACTIONS.map((_, i) => X0 + cellW * (i + 0.5));\n}\n\n// ---- device glyph (lives inside the bow ring) ------------------------------\n\ntype DeviceKind = NonNullable<Passkey[\"device\"]>;\n\nfunction inferDevice(name: string): DeviceKind {\n  const n = name.toLowerCase();\n  if (/ipad|tablet/.test(n)) return \"tablet\";\n  if (/iphone|pixel|galaxy|android|phone/.test(n)) return \"phone\";\n  if (/macbook|laptop|notebook|thinkpad|chromebook/.test(n)) return \"laptop\";\n  if (/imac|mac mini|mac studio|desktop|\\bpc\\b|windows/.test(n)) return \"desktop\";\n  if (/yubikey|titan|security key|\\bkey\\b/.test(n)) return \"key\";\n  return \"generic\";\n}\n\nfunction DeviceGlyph({ kind }: { kind: DeviceKind }) {\n  const stroke = { stroke: \"var(--foreground)\", strokeWidth: 1.1, strokeLinecap: \"round\" as const, strokeLinejoin: \"round\" as const, fill: \"none\" };\n  switch (kind) {\n    case \"phone\":\n      return <rect x={-2.8} y={-5} width={5.6} height={10} rx={1.3} {...stroke} />;\n    case \"tablet\":\n      return <rect x={-4.2} y={-3.2} width={8.4} height={6.4} rx={1} {...stroke} />;\n    case \"laptop\":\n      return (\n        <>\n          <rect x={-3.4} y={-4.2} width={6.8} height={4.6} rx={0.6} {...stroke} />\n          <line x1={-4.6} y1={1.6} x2={4.6} y2={1.6} {...stroke} />\n        </>\n      );\n    case \"desktop\":\n      return (\n        <>\n          <rect x={-3.6} y={-4.2} width={7.2} height={5} rx={0.6} {...stroke} />\n          <line x1={0} y1={0.8} x2={0} y2={2.6} {...stroke} />\n          <line x1={-2.2} y1={2.6} x2={2.2} y2={2.6} {...stroke} />\n        </>\n      );\n    case \"key\":\n      return (\n        <>\n          <rect x={-2.2} y={-4.2} width={4.4} height={6} rx={0.8} {...stroke} />\n          <line x1={-1.4} y1={1.8} x2={-1.4} y2={3.6} {...stroke} />\n          <line x1={1.4} y1={1.8} x2={1.4} y2={3.6} {...stroke} />\n        </>\n      );\n    default:\n      return <rect x={-2.3} y={-2.3} width={4.6} height={4.6} transform=\"rotate(45)\" {...stroke} />;\n  }\n}\n\n// ---- time formatting (SSR-safe: \"now\" arrives after mount) ----------------\n\nconst DAY_MS = 86400000;\n\nfunction formatCreated(ts: number | Date): string {\n  const d = ts instanceof Date ? ts : new Date(ts);\n  return new Intl.DateTimeFormat(\"en-US\", { month: \"long\", day: \"numeric\" }).format(d);\n}\n\nfunction formatLastUsed(lastUsedAt: number | Date | null | undefined, now: number | null): string {\n  if (lastUsedAt == null) return \"Never used\";\n  if (now == null) return \"…\";\n  const ts = lastUsedAt instanceof Date ? lastUsedAt.getTime() : lastUsedAt;\n  const diff = Math.max(0, now - ts);\n  if (diff < DAY_MS) return \"Last used today\";\n  const days = Math.floor(diff / DAY_MS);\n  if (days === 1) return \"Last used yesterday\";\n  if (days < 7) return `Last used ${days} days ago`;\n  const weeks = Math.floor(days / 7);\n  if (weeks < 5) return `Last used ${weeks} week${weeks === 1 ? \"\" : \"s\"} ago`;\n  return `Last used ${formatCreated(ts)}`;\n}\n\nfunction remainWord(n: number): string {\n  return n === 1 ? \"1 remains\" : `${n} remain`;\n}\n\nfunction useReducedMotion(): boolean {\n  const [reduced, setReduced] = useState(false);\n  useEffect(() => {\n    const mq = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n    setReduced(mq.matches);\n    const onChange = () => setReduced(mq.matches);\n    mq.addEventListener(\"change\", onChange);\n    return () => mq.removeEventListener(\"change\", onChange);\n  }, []);\n  return reduced;\n}\n\n// ---- row ---------------------------------------------------------------\n\ntype CutPhase = \"entering\" | \"settled\" | \"filing\" | \"exiting\";\n\ninterface RowState extends Passkey {\n  cutPhase: CutPhase;\n}\n\nconst NOTCH_X = notchCenters();\n\n// KeyArt composes the bow ring, the device glyph, and the blade in one\n// document (single viewBox, single stacking order) and owns the row's cutter\n// animation: a smooth 700ms sweep of the cutter graphic, with each notch's\n// depth stepping (never tweening) to its target the instant the sweep\n// crosses it.\nfunction KeyArt({\n  credentialId,\n  device,\n  cutPhase,\n  reducedMotion,\n  onCutSettled,\n  onFileSettled,\n}: {\n  credentialId: string;\n  device: DeviceKind;\n  cutPhase: CutPhase;\n  reducedMotion: boolean;\n  onCutSettled: () => void;\n  onFileSettled: () => void;\n}) {\n  const target = useRef(bittingDepths(credentialId)).current;\n  const [depths, setDepths] = useState<number[]>(() => (cutPhase === \"settled\" ? target : ZERO_DEPTHS));\n  const [cutterRun, setCutterRun] = useState(false);\n  const flashRefs = useRef<(SVGRectElement | null)[]>([]);\n\n  function flashNotch(i: number) {\n    const el = flashRefs.current[i];\n    if (!el) return;\n    el.style.transition = \"none\";\n    el.style.opacity = \"1\";\n    requestAnimationFrame(() => {\n      el.style.transition = \"opacity 160ms ease-out\";\n      el.style.opacity = \"0\";\n    });\n  }\n\n  useEffect(() => {\n    if (cutPhase !== \"entering\" && cutPhase !== \"filing\") return;\n    const goal = cutPhase === \"entering\" ? target : ZERO_DEPTHS;\n    const settle = cutPhase === \"entering\" ? onCutSettled : onFileSettled;\n\n    if (reducedMotion) {\n      setDepths(goal);\n      settle();\n      return;\n    }\n\n    setCutterRun(false);\n    const raf = requestAnimationFrame(() => setCutterRun(true));\n    const timers: ReturnType<typeof setTimeout>[] = [];\n    CENTER_FRACTIONS.forEach((frac, i) => {\n      timers.push(\n        setTimeout(() => {\n          setDepths((d) => {\n            const next = d.slice();\n            next[i] = goal[i];\n            return next;\n          });\n          flashNotch(i);\n        }, Math.round(frac * CUT_MS))\n      );\n    });\n    timers.push(\n      setTimeout(() => {\n        setCutterRun(false);\n        settle();\n      }, CUT_MS + 30)\n    );\n\n    return () => {\n      cancelAnimationFrame(raf);\n      timers.forEach(clearTimeout);\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [cutPhase]);\n\n  return (\n    <svg aria-hidden=\"true\" viewBox={`0 0 ${VIEW_W} ${VIEW_H}`} width=\"60\" height=\"22\" className=\"shrink-0\">\n      <circle cx={BOW_CX} cy={BOW_CY} r={BOW_R} fill=\"none\" stroke=\"var(--foreground)\" strokeWidth={3} />\n      <g transform={`translate(${BOW_CX} ${BOW_CY})`}>\n        <DeviceGlyph kind={device} />\n      </g>\n      <path d={bladePath(depths)} fill=\"var(--foreground)\" />\n      {NOTCH_X.map((x, i) => (\n        <rect\n          key={i}\n          ref={(el) => {\n            flashRefs.current[i] = el;\n          }}\n          x={x - 0.6}\n          y={TOP - 4}\n          width={1.2}\n          height={BASE - TOP + 8}\n          fill=\"var(--foreground)\"\n          opacity={0}\n        />\n      ))}\n      <g className=\"ns-bc-cutter\" data-run={cutterRun ? \"go\" : \"idle\"}>\n        <line x1={X0} x2={X0} y1={TOP - 5} y2={BASE + 5} stroke=\"var(--foreground)\" strokeWidth={1.4} />\n        <circle cx={X0} cy={TOP - 7} r={2.2} fill=\"var(--foreground)\" />\n      </g>\n    </svg>\n  );\n}\n\nfunction PasskeyRow({\n  row,\n  armed,\n  now,\n  reducedMotion,\n  onCutSettled,\n  onFileSettled,\n  onRequestRemove,\n  onCancelArm,\n}: {\n  row: RowState;\n  armed: boolean;\n  now: number | null;\n  reducedMotion: boolean;\n  onCutSettled: (id: string) => void;\n  onFileSettled: (id: string) => void;\n  onRequestRemove: (id: string) => void;\n  onCancelArm: (id: string) => void;\n}) {\n  const device = row.device ?? inferDevice(row.name);\n  const busy = row.cutPhase !== \"settled\";\n  const label = armed ? `Confirm remove ${row.name}` : `Remove ${row.name}`;\n\n  return (\n    <li\n      data-collapsing={row.cutPhase === \"exiting\"}\n      className=\"ns-bc-row\"\n    >\n      <div className=\"ns-bc-row-clip\">\n        <div className=\"flex items-center gap-3 px-4 py-3\">\n          <KeyArt\n            credentialId={row.id}\n            device={device}\n            cutPhase={row.cutPhase}\n            reducedMotion={reducedMotion}\n            onCutSettled={() => onCutSettled(row.id)}\n            onFileSettled={() => onFileSettled(row.id)}\n          />\n          <div className=\"min-w-0 flex-1\">\n            <p className=\"truncate text-sm font-medium text-foreground\">{row.name}</p>\n            <p className=\"mt-0.5 truncate font-mono text-xs text-ns-muted\">\n              Created {formatCreated(row.createdAt)} · {formatLastUsed(row.lastUsedAt, now)}\n            </p>\n          </div>\n          <button\n            type=\"button\"\n            className=\"ns-bc-btn shrink-0\"\n            data-armed={armed}\n            disabled={busy}\n            aria-label={label}\n            onClick={() => onRequestRemove(row.id)}\n            onBlur={() => onCancelArm(row.id)}\n            onKeyDown={(e) => {\n              if (e.key === \"Escape\" && armed) onCancelArm(row.id);\n            }}\n          >\n            {armed ? \"Confirm\" : \"Remove\"}\n          </button>\n        </div>\n      </div>\n    </li>\n  );\n}\n\n// ---- root ---------------------------------------------------------------\n\nconst DEVICE_POOL: Array<{ name: string; device: DeviceKind }> = [\n  { name: \"iPhone 16\", device: \"phone\" },\n  { name: \"MacBook Pro\", device: \"laptop\" },\n  { name: \"iPad Air\", device: \"tablet\" },\n  { name: \"YubiKey 5C\", device: \"key\" },\n  { name: \"Pixel 9\", device: \"phone\" },\n  { name: \"Mac Studio\", device: \"desktop\" },\n];\n\nfunction newPasskey(existingCount: number): Passkey {\n  const pick = DEVICE_POOL[existingCount % DEVICE_POOL.length];\n  const id =\n    typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n      ? crypto.randomUUID()\n      : `pk-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n  return { id, name: pick.name, device: pick.device, createdAt: Date.now(), lastUsedAt: null };\n}\n\nexport function BittingCut({\n  initialPasskeys,\n  maxPasskeys = 6,\n  ariaLabel = \"Passkeys\",\n  onAdd,\n  onRemove,\n  className = \"\",\n}: BittingCutProps) {\n  const uid = useId();\n  const reducedMotion = useReducedMotion();\n  const [rows, setRows] = useState<RowState[]>(() =>\n    (initialPasskeys ?? []).map((p) => ({ ...p, cutPhase: \"settled\" as CutPhase }))\n  );\n  const [armedId, setArmedId] = useState<string | null>(null);\n  const [announce, setAnnounce] = useState(\"\");\n  const [now, setNow] = useState<number | null>(null);\n\n  useEffect(() => {\n    setNow(Date.now());\n  }, []);\n\n  const atMax = rows.length >= maxPasskeys;\n\n  function handleAdd() {\n    if (atMax) return;\n    const pk = newPasskey(rows.length);\n    setRows((rs) => [...rs, { ...pk, cutPhase: \"entering\" }]);\n    onAdd?.(pk);\n  }\n\n  function handleCutSettled(id: string) {\n    setRows((rs) => rs.map((r) => (r.id === id ? { ...r, cutPhase: \"settled\" } : r)));\n  }\n\n  function handleRequestRemove(id: string) {\n    if (armedId === id) {\n      setArmedId(null);\n      setRows((rs) => rs.map((r) => (r.id === id ? { ...r, cutPhase: \"filing\" } : r)));\n    } else {\n      setArmedId(id);\n    }\n  }\n\n  function handleCancelArm(id: string) {\n    setArmedId((cur) => (cur === id ? null : cur));\n  }\n\n  function handleFileSettled(id: string) {\n    setRows((rs) => rs.map((r) => (r.id === id ? { ...r, cutPhase: \"exiting\" } : r)));\n    const delay = reducedMotion ? 0 : 260;\n    setTimeout(() => {\n      setRows((rs) => {\n        const target = rs.find((r) => r.id === id);\n        const next = rs.filter((r) => r.id !== id);\n        if (target) {\n          onRemove?.(id);\n          setAnnounce(`Passkey ${target.name} removed, ${remainWord(next.length)}.`);\n        }\n        return next;\n      });\n    }, delay);\n  }\n\n  return (\n    <div className={[\"ns-bc\", className].join(\" \")}>\n      <style>{CSS}</style>\n\n      <div className=\"flex items-center justify-between gap-3 pb-3\">\n        <p className=\"font-mono text-[10px] uppercase tracking-[0.2em] text-ns-muted\">Sign-in methods</p>\n        <div className=\"flex items-center gap-2\">\n          {atMax && <span className=\"font-mono text-[10px] text-ns-muted\">Maximum reached</span>}\n          <button\n            type=\"button\"\n            className=\"ns-bc-btn ns-bc-add\"\n            disabled={atMax}\n            aria-label=\"Add passkey\"\n            onClick={handleAdd}\n          >\n            Add passkey\n          </button>\n        </div>\n      </div>\n\n      <ul role=\"list\" aria-label={ariaLabel} id={`${uid}-list`} className=\"ns-bc-list rounded-[12px] border border-border bg-background\">\n        {rows.length === 0 ? (\n          <li className=\"px-4 py-6 text-center font-mono text-xs text-ns-muted\">No passkeys enrolled.</li>\n        ) : (\n          rows.map((row) => (\n            <PasskeyRow\n              key={row.id}\n              row={row}\n              armed={armedId === row.id}\n              now={now}\n              reducedMotion={reducedMotion}\n              onCutSettled={handleCutSettled}\n              onFileSettled={handleFileSettled}\n              onRequestRemove={handleRequestRemove}\n              onCancelArm={handleCancelArm}\n            />\n          ))\n        )}\n      </ul>\n\n      <div role=\"status\" aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n        {announce}\n      </div>\n    </div>\n  );\n}\n\nconst CSS = `\n.ns-bc-list{overflow:hidden}\n.ns-bc-row{display:grid;grid-template-rows:1fr;opacity:1;transition:grid-template-rows 260ms cubic-bezier(.22,1,.36,1),opacity 260ms ease-out}\n.ns-bc-row[data-collapsing=\"true\"]{grid-template-rows:0fr;opacity:0}\n.ns-bc-row-clip{overflow:hidden;min-height:0;transition:background-color 150ms ease-out}\n.ns-bc-row-clip:hover{background:color-mix(in srgb, var(--foreground) 4%, transparent)}\n.ns-bc-row + .ns-bc-row > .ns-bc-row-clip{border-top:1px solid var(--border)}\n.ns-bc-cutter{opacity:0;transform:translateX(0)}\n.ns-bc-cutter[data-run=\"go\"]{opacity:1;transform:translateX(${X1 - X0}px);transition:transform ${CUT_MS}ms cubic-bezier(.4,0,.2,1),opacity 120ms ease-out}\n.ns-bc-btn{display:inline-flex;height:26px;align-items:center;justify-content:center;border-radius:6px;padding:0 10px;border:1px solid var(--border);background:var(--background);color:var(--ns-muted);font-family:var(--font-geist-mono,ui-monospace,monospace);font-size:11px;transition:background-color 150ms ease-out,color 150ms ease-out,border-color 150ms ease-out,box-shadow 150ms ease-out}\n.ns-bc-btn:hover:not(:disabled){color:var(--foreground);border-color:var(--foreground)}\n.ns-bc-btn:disabled{opacity:0.45}\n.ns-bc-btn:focus-visible{outline:2px solid var(--ns-accent);outline-offset:2px}\n.ns-bc-btn[data-armed=\"true\"]{color:var(--ns-accent);border-color:var(--ns-accent)}\n.ns-bc-add{color:var(--foreground)}\n@media (prefers-reduced-motion: reduce){\n  .ns-bc-row,.ns-bc-cutter,.ns-bc-btn,.ns-bc-row-clip{transition:none!important}\n}\n`;\n\nexport default BittingCut;\n",
      "type": "registry:ui",
      "target": "components/ui/bitting-cut.tsx"
    }
  ],
  "cssVars": {
    "theme": {
      "color-ns-muted": "var(--ns-muted)",
      "color-ns-accent": "var(--ns-accent)"
    },
    "light": {
      "ns-muted": "#4d4d4d",
      "ns-accent": "#006bff"
    },
    "dark": {
      "ns-muted": "#8f8f8f"
    }
  },
  "meta": {
    "collection": "core",
    "tags": [
      "passkey",
      "webauthn",
      "settings",
      "security",
      "list",
      "svg",
      "aria-live",
      "accessibility"
    ],
    "instruction": "`<BittingCut initialPasskeys={Passkey[]} maxPasskeys? ariaLabel? onAdd? onRemove? className? />` renders a Settings > Sign-in methods list where every passkey draws its own key: a 112x40 inline SVG per row — a stroked ring bow (fill:none, 3px --foreground stroke, so the ring's centre reads as a literal hole) carrying a small device glyph (phone/laptop/tablet/desktop/hardware-key/generic, inferred from the row's name when `device` isn't set) drawn in --foreground strokes inside that hole, and a solid --foreground blade rendered as ONE closed SVG path whose bottom edge steps through six notch cells at one of five quantized depths. THE BITTING IS DETERMINISTIC: `bittingDepths(credentialId)` FNV-1a-hashes `credentialId#i` for i in 0..5 and takes `hash % 5` per notch — a pure function of the row's `id`, same output every call, every mount, forever. There is no `Math.random()` anywhere in that derivation; decorative randomized teeth are the explicitly forbidden alternative, because the whole point is that the SAME credential looks the SAME key on the next visit. ENROLLMENT: a passkey added at runtime (via the header's real 'Add passkey' button, which fabricates a fresh credential id from `crypto.randomUUID()` and stops offering itself once the list reaches `maxPasskeys`, default 6) mounts with cutPhase 'entering' and plays a 700ms one-shot pass: a cutter graphic (a short vertical line with a small circular head, hidden at rest) sweeps smoothly from the blade's shoulder to its tip on a CSS transform transition, while each of the six notches STEPS — not tweens — to its final hashed depth via a `setTimeout` scheduled at the moment the sweep's linear position crosses that notch's centre (a cutting wheel commits a depth as it passes, it does not interpolate one in), each step also flashing a 1px --foreground rect at that notch's x-position (direct ref style write: opacity 1 with transition:none, then next frame opacity 0 over 160ms — never React state, so the flash can't itself cause a re-render loop). Passkeys present in `initialPasskeys` at mount start at cutPhase 'settled' with their target depths already drawn — no entrance animation, because for a credential enrolled before this page loaded, that key was already cut; only a genuinely NEW enrollment plays the pass. REMOVAL runs the identical mechanism inverted: cutPhase 'filing' sweeps the same cutter across the same blade, but each notch it passes steps back to depth 0 (flat/blank) instead of toward a target — 'flattening the profile to blank' — and once every notch is blank the row transitions to 'exiting', collapsing via `grid-template-rows: 1fr -> 0fr` + opacity over 260ms (mirrors this repo's existing arm-then-confirm row-eviction pattern), after which it is spliced out of state, `onRemove(id)` fires, and a `role=status aria-live=polite` region announces 'Passkey {name} removed, {n remain}' (singular '1 remains', otherwise '{n} remain') — the only thing that ever announces; adding is silent. REMOVE ITSELF is a real per-row button that arms on first press (`data-armed`, accessible name flips from 'Remove {name}' to 'Confirm remove {name}', border/text tint to --ns-accent — the only place --ns-accent appears, since arming is an active interaction state, not decoration) and commits on a second press on the SAME button; losing focus (`onBlur`) or pressing Escape while armed reverts it unconfirmed. The button is `disabled` for the whole entering/filing/exiting span of its own row, so it cannot be re-armed mid-cut or double-filed. TEXT: every row shows the device name, 'Created {Month Day}' and a last-used phrase ('Last used today' / 'yesterday' / 'N days ago' / 'N weeks ago' / a date, or 'Never used' for a freshly enrolled key with no lastUsedAt) as plain sibling text next to the art — the key SVG is `aria-hidden`, decorative, and never the row's sole identifier; a screen reader gets the same facts as the graphic conveys visually. The list is a real `<ul role=\"list\">`/`<li>` structure with an accessible name. Last-used text follows this repo's SSR-safe pattern: `now` starts `null` and is set from `Date.now()` inside a mount effect, so the very first client render never reads the clock during render and can't mismatch a server pass; before that effect fires the row shows an inert '…' placeholder for last-used only (created-date, month/day only, needs no clock and is never a placeholder). REDUCED MOTION (`matchMedia` with a live change listener) makes both the cutting and filing passes land in a single synchronous step — full target depths or full blank depths applied immediately, cutter never becomes visible, row collapse has zero transition duration — so an assistive-tech user or anyone with the OS preference set never waits on an animation to see a stable, legible, already-finished key or an already-gone row. Colors are `--background --foreground --ns-muted --border --ns-accent` only, no hex/rgb/hsl literals anywhere including inside the SVG paths. DOM + SVG + CSS, no canvas, zero dependencies."
  },
  "type": "registry:ui"
}