{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "table-empty",
  "title": "Empty Table",
  "description": "A table that keeps its header and column widths, with rows that spring in.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "button"
  ],
  "files": [
    {
      "path": "components/spectrumui/blocks/empty-states/table-empty.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport { AnimatePresence, motion, useReducedMotion } from 'motion/react';\nimport { cn } from '@/lib/utils';\nimport {\n  EMPTY_FONT,\n  EmptyAction,\n  EmptyState,\n  IconDocument,\n  IconPlus,\n  IconUpload,\n  SPRING_ENTRANCE,\n  SPRING_FLUID,\n  usePropState,\n} from './empty-state-kit';\n\nexport type TableEmptyVariant = 'Overlay' | 'Inline' | 'Loading';\n\nexport interface TableColumn {\n  id: string;\n  label: string;\n  align?: 'left' | 'right';\n  width?: string;\n}\n\nexport interface TableRowSeed {\n  id: string;\n  cells: string[];\n}\n\nexport interface TableEmptyProps {\n  panelTitle?: string;\n  title?: string;\n  description?: string;\n  columns?: TableColumn[];\n  /** Rows the demo inserts, one per press, so the empty state can be left. */\n  seedRows?: TableRowSeed[];\n  addLabel?: string;\n  importLabel?: string;\n  onAdd?: () => void;\n  variant?: TableEmptyVariant;\n  className?: string;\n}\n\nconst COLUMNS: TableColumn[] = [\n  { id: 'customer', label: 'Customer' },\n  { id: 'plan', label: 'Plan', width: '22%' },\n  { id: 'seats', label: 'Seats', align: 'right', width: '14%' },\n  { id: 'mrr', label: 'MRR', align: 'right', width: '18%' },\n];\n\nconst SEED: TableRowSeed[] = [\n  { id: 'r1', cells: ['Northbend Labs', 'Team', '12', '$708'] },\n  { id: 'r2', cells: ['Harbour Freight', 'Pro', '4', '$96'] },\n  { id: 'r3', cells: ['Ostrom & Co', 'Enterprise', '86', '$4,300'] },\n];\n\nexport function TableEmpty({\n  panelTitle = 'Accounts',\n  title = 'No accounts yet',\n  description = 'The table keeps its columns so you can see the shape of what goes here. Add a row by hand or import the ones you already have.',\n  columns = COLUMNS,\n  seedRows = SEED,\n  addLabel = 'Add row',\n  importLabel = 'Import CSV',\n  onAdd,\n  variant = 'Overlay',\n  className,\n}: TableEmptyProps) {\n  const [rows, setRows] = usePropState<TableRowSeed[]>([], variant);\n  const [loading, setLoading] = usePropState(variant === 'Loading', variant);\n  const reduced = useReducedMotion();\n\n  /* The skeleton resolves on a timer; the effect ends the loading state rather\n     than starting it, so nothing is set synchronously on mount. */\n  React.useEffect(() => {\n    if (!loading) return;\n    const timer = setTimeout(() => setLoading(false), reduced ? 250 : 1400);\n    return () => clearTimeout(timer);\n  }, [loading, reduced, setLoading]);\n\n  function addRow() {\n    const next = seedRows[rows.length];\n    if (!next) return;\n    setRows((current) => [...current, next]);\n    onAdd?.();\n  }\n\n  const empty = rows.length === 0;\n\n  return (\n    <section\n      className={cn(\n        EMPTY_FONT,\n        'w-full overflow-hidden rounded-2xl border border-black/[0.08] bg-white',\n        'shadow-[0_1px_2px_rgba(0,0,0,0.04),0_18px_40px_-28px_rgba(0,0,0,0.35)]',\n        'dark:border-white/[0.09] dark:bg-neutral-950 dark:shadow-none',\n        className,\n      )}\n    >\n      <header className=\"flex items-center justify-between gap-3 border-b border-black/[0.06] px-4 py-3 dark:border-white/[0.07]\">\n        <div className=\"min-w-0\">\n          <p className=\"truncate text-[14px] font-semibold tracking-[-0.1px] text-neutral-800 dark:text-neutral-100\">\n            {panelTitle}\n          </p>\n          <p className=\"mt-0.5 font-mono text-[12.5px] tabular-nums text-neutral-600 dark:text-neutral-400\">\n            {loading ? 'loading…' : `${rows.length} rows`}\n          </p>\n        </div>\n        <EmptyAction\n          emphasis=\"secondary\"\n          icon={<IconPlus />}\n          onClick={addRow}\n          disabled={rows.length >= seedRows.length}\n          className=\"h-8 px-3 text-[13.5px]\"\n        >\n          {addLabel}\n        </EmptyAction>\n      </header>\n\n      <div className=\"relative\">\n        <table className=\"w-full table-fixed border-collapse\">\n          <thead>\n            <tr className=\"border-b border-black/[0.06] dark:border-white/[0.07]\">\n              {columns.map((column) => (\n                <th\n                  key={column.id}\n                  scope=\"col\"\n                  style={{ width: column.width }}\n                  className={cn(\n                    'px-4 py-2.5 text-[12.5px] font-medium uppercase tracking-[0.06em] text-neutral-600 dark:text-neutral-400',\n                    column.align === 'right' ? 'text-right' : 'text-left',\n                  )}\n                >\n                  {column.label}\n                </th>\n              ))}\n            </tr>\n          </thead>\n          <tbody>\n            <AnimatePresence initial={false}>\n              {rows.map((row) => (\n                <motion.tr\n                  key={row.id}\n                  layout\n                  initial={reduced ? { opacity: 0 } : { opacity: 0, y: -10 }}\n                  animate={{ opacity: 1, y: 0 }}\n                  exit={{ opacity: 0 }}\n                  transition={SPRING_ENTRANCE}\n                  className=\"border-b border-black/[0.04] last:border-0 dark:border-white/[0.05]\"\n                >\n                  {row.cells.map((cell, index) => (\n                    <td\n                      key={columns[index]?.id ?? index}\n                      className={cn(\n                        'truncate px-4 py-3 text-[14px] text-neutral-700 dark:text-neutral-200',\n                        columns[index]?.align === 'right' && 'text-right font-mono tabular-nums',\n                      )}\n                    >\n                      {cell}\n                    </td>\n                  ))}\n                </motion.tr>\n              ))}\n            </AnimatePresence>\n\n            {/* Ghost rows hold the table's height so the overlay never floats in\n                a 40px strip, and so adding the first row does not jolt the page. */}\n            {empty &&\n              Array.from({ length: 6 }).map((_, index) => (\n                <tr\n                  key={index}\n                  className=\"border-b border-black/[0.04] last:border-0 dark:border-white/[0.05]\"\n                >\n                  {columns.map((column) => (\n                    <td key={column.id} className=\"px-4 py-3.5\">\n                      <motion.span\n                        aria-hidden\n                        initial={{ opacity: 0 }}\n                        animate={\n                          loading && !reduced\n                            ? { opacity: [0.18, 0.4, 0.18] }\n                            : { opacity: Math.max(0.08, 0.28 - index * 0.06) }\n                        }\n                        transition={\n                          loading && !reduced\n                            ? { duration: 1.2, repeat: Infinity, delay: index * 0.1 }\n                            : SPRING_FLUID\n                        }\n                        className={cn(\n                          'block h-2 rounded-full bg-neutral-400 dark:bg-white/40',\n                          column.align === 'right' ? 'ml-auto w-10' : 'w-[70%]',\n                        )}\n                      />\n                    </td>\n                  ))}\n                </tr>\n              ))}\n          </tbody>\n        </table>\n\n        <AnimatePresence>\n          {empty && !loading && (\n            <motion.div\n              initial={{ opacity: 0 }}\n              animate={{ opacity: 1 }}\n              exit={{ opacity: 0 }}\n              transition={SPRING_FLUID}\n              className={cn(\n                'inset-x-0 bottom-0 flex items-center justify-center px-6',\n                variant === 'Inline'\n                  ? 'relative py-10'\n                  : /* Starts below the header row: the column labels are the\n                       one thing an empty table still has to say. */\n                    'absolute top-10 bg-gradient-to-b from-white/85 via-white/98 to-white dark:from-neutral-950/85 dark:via-neutral-950/98 dark:to-neutral-950',\n              )}\n            >\n              <EmptyState\n                icon={<IconDocument />}\n                medallionSize=\"sm\"\n                backdrop=\"none\"\n                title={title}\n                description={description}\n                actions={\n                  <>\n                    <EmptyAction icon={<IconPlus />} onClick={addRow}>\n                      {addLabel}\n                    </EmptyAction>\n                    <EmptyAction emphasis=\"secondary\" icon={<IconUpload />}>\n                      {importLabel}\n                    </EmptyAction>\n                  </>\n                }\n              />\n            </motion.div>\n          )}\n        </AnimatePresence>\n      </div>\n    </section>\n  );\n}\n",
      "type": "registry:block",
      "target": "components/spectrumui/blocks/empty-states/table-empty.tsx"
    },
    {
      "path": "components/spectrumui/blocks/empty-states/empty-state-kit.tsx",
      "content": "/**\n * Everything the empty-state blocks share: the Iconly glyph set, the medallion\n * the glyph sits in, the text anatomy, the action buttons and the small parts\n * that make an empty panel read as part of a product rather than a poster.\n *\n * It ships as a second file inside every block's registry item, so\n * `npx shadcn add @spectrumui/inbox-empty` still writes one working block.\n *\n * Two decisions worth knowing before you edit it:\n *\n *   - The icons are Iconly **Bold** — filled, single-path, `currentColor`.\n *     An empty state has one picture and a lot of white around it, so the glyph\n *     is doing the work a 13px outline arrow could not.\n *   - Nothing loops. Entrances run once when the panel mounts; everything else\n *     is a response to a pointer, a key or a state change. An empty screen that\n *     breathes at you forever is a screen you learn to ignore.\n */\n\n'use client';\n\nimport * as React from 'react';\nimport { motion, useInView, useReducedMotion, type Transition, type Variants } from 'motion/react';\nimport { Button } from '@/components/ui/button';\nimport { cn } from '@/lib/utils';\n\nexport const SPRING_TACTILE: Transition = { type: 'spring', stiffness: 400, damping: 25, mass: 1 };\nexport const SPRING_FLUID: Transition = { type: 'spring', stiffness: 300, damping: 30 };\nexport const SPRING_ENTRANCE: Transition = { type: 'spring', stiffness: 260, damping: 20 };\nexport const SPRING_SNAPPY: Transition = { type: 'spring', stiffness: 500, damping: 28 };\n\n/**\n * Entrances fire when the panel reaches the viewport, not when the page mounts.\n * On a page of twenty-five specimens that is the difference between twenty-four\n * animations the reader never sees and twenty-five that greet them. `once`\n * because a state that re-animates every time you scroll past it is a tic.\n */\nexport const VIEWPORT = { once: true, amount: 0.3 } as const;\n\n/**\n * The built-in `easeOut` and `easeInOut` are too soft to read as deliberate.\n * These are the stronger variants everything duration-based in this file uses.\n */\nexport const EASE_OUT = [0.23, 1, 0.32, 1] as const;\nexport const EASE_IN_OUT = [0.77, 0, 0.175, 1] as const;\n\n/**\n * Inter, with the tracking it actually wants.\n *\n * The family is named through a variable first so a project that already loads\n * Inter (this one does, as `--font-inter`) gets its own optimised face, and a\n * project that does not still resolves to Inter or the system UI font rather\n * than to nothing. Tracking is set per size in `em` so it scales with the text:\n * Inter is drawn loose for small sizes and wants a touch of negative tracking\n * everywhere above a caption.\n */\nexport const EMPTY_FONT =\n  '[font-family:var(--font-inter),Inter,system-ui,sans-serif] antialiased [font-feature-settings:\"cv05\",\"ss01\"]';\n\n/* ── Icons ────────────────────────────────────────────────────────────────── */\n\ntype IconProps = React.SVGProps<SVGSVGElement>;\n\nfunction Bold({ children, ...props }: IconProps & { children: React.ReactNode }) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\" {...props}>\n      {children}\n    </svg>\n  );\n}\n\n/**\n * Two icon styles on purpose, the same split footer-kit makes.\n *\n * The **filled** Iconly glyphs are the heroes: one per block, 24px inside a\n * medallion, where mass is exactly what you want. Everything that sits beside a\n * label — the arrow on a button, the magnifier in a field — is **outline**,\n * because a filled arrow at 15px collapses into a dash with a wedge on it and a\n * filled magnifier reads as a blob with a stick.\n */\nfunction Outline({ children, ...props }: IconProps & { children: React.ReactNode }) {\n  return (\n    <svg\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={1.9}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      aria-hidden=\"true\"\n      {...props}\n    >\n      {children}\n    </svg>\n  );\n}\n\nexport function IconActivity(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 2)\"\n        d=\"M13.45,1.78 C13.41,2.03 13.39,2.28 13.39,2.53 C13.39,4.78 15.21,6.6 17.45,6.6 C17.7,6.6 17.94,6.57 18.19,6.53 L18.19,14.6 C18.19,17.99 16.19,20 12.79,20 L5.4,20 C2,20 0,17.99 0,14.6 L0,7.2 C0,3.8 2,1.78 5.4,1.78 Z M13.65,7.86 C13.38,7.83 13.11,7.95 12.95,8.17 L10.53,11.3 L7.76,9.12 C7.59,8.99 7.39,8.94 7.19,8.96 C6.99,8.99 6.81,9.1 6.69,9.26 L3.73,13.11 L3.67,13.2 C3.5,13.52 3.58,13.93 3.88,14.15 C4.02,14.24 4.17,14.3 4.34,14.3 C4.57,14.31 4.79,14.19 4.93,14 L7.44,10.77 L10.29,12.91 L10.38,12.97 C10.7,13.14 11.1,13.06 11.33,12.76 L14.22,9.03 L14.18,9.05 C14.34,8.83 14.37,8.55 14.26,8.3 C14.15,8.05 13.91,7.88 13.65,7.86 Z M17.59,0 C18.92,0 20,1.08 20,2.41 C20,3.74 18.92,4.82 17.59,4.82 C16.26,4.82 15.18,3.74 15.18,2.41 C15.18,1.08 16.26,0 17.59,0 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconAddUser(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 2.5)\"\n        d=\"M7.5,12.515 C11.567,12.515 15,13.184 15,15.769 C15,18.353 11.545,19 7.5,19 C3.433,19 0,18.33 0,15.746 C0,13.162 3.454,12.515 7.5,12.515 Z M16.999,5 C17.495,5 17.898,5.409 17.898,5.912 L17.898,7.088 L19.101,7.088 C19.596,7.088 20,7.497 20,7.999 C20,8.502 19.596,8.911 19.101,8.911 L17.898,8.911 L17.898,10.088 C17.898,10.591 17.495,11 16.999,11 C16.504,11 16.1,10.591 16.1,10.088 L16.1,8.911 L14.899,8.911 C14.403,8.911 14,8.502 14,7.999 C14,7.497 14.403,7.088 14.899,7.088 L16.1,7.088 L16.1,5.912 C16.1,5.409 16.504,5 16.999,5 Z M7.5,0 C10.255,0 12.463,2.237 12.463,5.027 C12.463,7.817 10.255,10.054 7.5,10.054 C4.745,10.054 2.537,7.817 2.537,5.027 C2.537,2.237 4.745,0 7.5,0 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconArrowRight(props: IconProps) {\n  return (\n    <Outline {...props}>\n      <path d=\"M4.5 12h14M12.5 5.75 18.75 12l-6.25 6.25\" />\n    </Outline>\n  );\n}\n\nexport function IconBag(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(3, 2)\"\n        d=\"M9.005,0 C11.685,0 13.888,2.106 14,4.774 L13.974,4.774 C13.977,4.852 13.962,4.929 13.93,5 L14.086,5 C15.303,5 16.578,5.844 17.089,7.88 L17.144,8.12 L17.913,14.315 C18.467,18.266 16.305,19.927 13.356,19.998 L13.158,20 L4.869,20 C1.872,20 -0.437,18.908 0.07,14.584 L0.105,14.315 L0.883,8.12 C1.266,5.927 2.554,5.062 3.794,5.003 L3.932,5 L4.01,5 C3.997,4.925 3.997,4.849 4.01,4.774 C4.122,2.106 6.325,0 9.005,0 Z M6.097,8.329 C5.609,8.329 5.213,8.737 5.213,9.239 C5.213,9.741 5.609,10.149 6.097,10.149 C6.585,10.149 6.981,9.741 6.981,9.239 L6.974,9.125 C6.919,8.676 6.548,8.329 6.097,8.329 Z M11.886,8.329 C11.398,8.329 11.002,8.737 11.002,9.239 C11.002,9.741 11.398,10.149 11.886,10.149 C12.374,10.149 12.77,9.741 12.77,9.239 C12.77,8.737 12.374,8.329 11.886,8.329 Z M8.966,1.302 C7.042,1.302 5.482,2.857 5.482,4.774 C5.495,4.849 5.495,4.925 5.482,5 L12.493,5 C12.465,4.928 12.451,4.852 12.45,4.774 C12.45,2.857 10.89,1.302 8.966,1.302 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconBookmark(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(4, 2)\"\n        d=\"M11.07,0 C13.78,0 15.97,1.07 16,3.79 L16,18.97 C16,19.14 15.96,19.31 15.88,19.46 C15.75,19.7 15.53,19.88 15.26,19.96 C15,20.04 14.71,20 14.47,19.86 L7.99,16.62 L1.5,19.86 C1.351,19.939 1.18,19.99 1.01,19.99 C0.45,19.99 0,19.53 0,18.97 L0,3.79 C0,1.07 2.2,0 4.9,0 Z M11.75,6.04 L4.22,6.04 C3.79,6.04 3.44,6.39 3.44,6.83 C3.44,7.269 3.79,7.62 4.22,7.62 L11.75,7.62 C12.18,7.62 12.53,7.269 12.53,6.83 C12.53,6.39 12.18,6.04 11.75,6.04 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconBuy(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 2)\"\n        d=\"M5.426,16.908 C6.256,16.908 6.935,17.602 6.935,18.459 C6.935,19.306 6.256,20 5.426,20 C4.587,20 3.907,19.306 3.907,18.459 C3.907,17.602 4.587,16.908 5.426,16.908 Z M16.668,16.908 C17.497,16.908 18.176,17.602 18.176,18.459 C18.176,19.306 17.497,20 16.668,20 C15.828,20 15.149,19.306 15.149,18.459 C15.149,17.602 15.828,16.908 16.668,16.908 Z M0.778,0 L0.88,0.009 L3.263,0.375 C3.603,0.437 3.853,0.722 3.883,1.069 L4.073,3.355 C4.103,3.683 4.362,3.927 4.682,3.927 L18.177,3.927 C18.786,3.927 19.186,4.142 19.585,4.611 C19.985,5.081 20.055,5.754 19.965,6.365 L19.016,13.06 C18.836,14.347 17.757,15.295 16.488,15.295 L5.586,15.295 C4.257,15.295 3.158,14.255 3.048,12.908 L2.129,1.783 L0.62,1.518 C0.221,1.447 -0.059,1.049 0.011,0.64 C0.081,0.223 0.47,-0.054 0.88,0.009 Z M14.889,7.702 L12.121,7.702 C11.702,7.702 11.372,8.039 11.372,8.468 C11.372,8.886 11.702,9.233 12.121,9.233 L14.889,9.233 C15.309,9.233 15.639,8.886 15.639,8.468 C15.639,8.039 15.309,7.702 14.889,7.702 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconCalendar(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(3, 2)\"\n        d=\"M12.65,0 C13.074,0 13.41,0.329 13.411,0.769 L13.412,1.518 C16.167,1.734 17.986,3.611 17.989,6.49 L18,14.916 C18.004,18.054 16.032,19.985 12.872,19.99 L5.152,20 C2.011,20.004 0.015,18.027 0.011,14.88 L0,6.553 C-0.004,3.655 1.752,1.783 4.506,1.53 L4.505,0.781 C4.504,0.341 4.83,0.01 5.264,0.01 C5.699,0.009 6.025,0.339 6.026,0.779 L6.027,1.478 L11.891,1.47 L11.89,0.771 C11.889,0.331 12.215,0.001 12.65,0 Z M13.052,14.192 L13.043,14.192 C12.588,14.203 12.224,14.584 12.234,15.043 C12.235,15.503 12.601,15.882 13.055,15.892 C13.519,15.891 13.894,15.51 13.893,15.04 C13.893,14.571 13.517,14.192 13.052,14.192 Z M4.917,14.193 C4.463,14.213 4.107,14.594 4.108,15.053 C4.129,15.513 4.504,15.873 4.958,15.852 C5.404,15.832 5.758,15.451 5.737,14.991 C5.728,14.542 5.361,14.192 4.917,14.193 Z M8.985,14.188 C8.531,14.209 8.176,14.589 8.176,15.048 C8.197,15.508 8.572,15.867 9.026,15.847 C9.47,15.826 9.826,15.446 9.805,14.985 C9.795,14.537 9.429,14.187 8.985,14.188 Z M4.912,10.595 C4.458,10.615 4.103,10.995 4.104,11.455 C4.124,11.915 4.5,12.275 4.954,12.254 C5.399,12.234 5.753,11.853 5.732,11.393 C5.723,10.944 5.357,10.594 4.912,10.595 Z M8.981,10.56 C8.527,10.58 8.171,10.961 8.172,11.42 C8.192,11.88 8.568,12.239 9.022,12.219 C9.467,12.198 9.821,11.818 9.801,11.358 C9.79,10.909 9.425,10.559 8.981,10.56 Z M13.049,10.565 C12.594,10.575 12.239,10.945 12.24,11.404 L12.24,11.415 C12.25,11.875 12.625,12.224 13.08,12.214 C13.524,12.203 13.879,11.822 13.869,11.362 C13.848,10.923 13.492,10.564 13.049,10.565 Z M11.893,3.009 L6.029,3.017 L6.03,3.826 C6.03,4.257 5.705,4.597 5.27,4.597 C4.836,4.598 4.509,4.259 4.509,3.828 L4.508,3.058 C2.583,3.251 1.518,4.383 1.52,6.551 L1.521,6.862 L16.47,6.842 L16.47,6.492 C16.427,4.343 15.349,3.215 13.414,3.047 L13.415,3.817 C13.415,4.247 13.08,4.588 12.656,4.588 C12.221,4.589 11.894,4.249 11.894,3.819 L11.893,3.009 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconCategory(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 2)\"\n        d=\"M5.92,11.47 C7.33,11.47 8.46,12.611 8.46,14.031 L8.46,17.44 C8.46,18.85 7.33,20 5.92,20 L2.54,20 C1.14,20 0,18.85 0,17.44 L0,14.031 C0,12.611 1.14,11.47 2.54,11.47 Z M17.46,11.47 C18.86,11.47 20,12.611 20,14.031 L20,17.44 C20,18.85 18.86,20 17.46,20 L14.08,20 C12.67,20 11.54,18.85 11.54,17.44 L11.54,14.031 C11.54,12.611 12.67,11.47 14.08,11.47 Z M5.92,0 C7.33,0 8.46,1.15 8.46,2.561 L8.46,5.97 C8.46,7.39 7.33,8.53 5.92,8.53 L2.54,8.53 C1.14,8.53 0,7.39 0,5.97 L0,2.561 C0,1.15 1.14,0 2.54,0 Z M17.46,0 C18.86,0 20,1.15 20,2.561 L20,5.97 C20,7.39 18.86,8.53 17.46,8.53 L14.08,8.53 C12.67,8.53 11.54,7.39 11.54,5.97 L11.54,2.561 C11.54,1.15 12.67,0 14.08,0 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconChart(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 2)\"\n        d=\"M14.669,0 C18.07,0 19.99,1.929 20,5.33 L20,14.67 C20,18.07 18.07,20 14.669,20 L5.33,20 C1.929,20 0,18.07 0,14.67 L0,5.33 C0,1.929 1.929,0 5.33,0 Z M10.5,4.13 C10.219,3.96 9.879,3.96 9.61,4.13 C9.339,4.299 9.19,4.61 9.219,4.92 L9.219,15.11 C9.27,15.54 9.629,15.86 10.049,15.86 C10.48,15.86 10.839,15.54 10.879,15.11 L10.879,4.92 C10.919,4.61 10.77,4.299 10.5,4.13 Z M5.83,7.41 C5.56,7.24 5.219,7.24 4.95,7.41 C4.679,7.58 4.53,7.889 4.56,8.2 L4.56,15.11 C4.599,15.54 4.959,15.86 5.389,15.86 C5.82,15.86 6.179,15.54 6.219,15.11 L6.219,8.2 C6.25,7.889 6.099,7.58 5.83,7.41 Z M15.089,11.04 C14.82,10.87 14.48,10.87 14.2,11.04 C13.929,11.21 13.78,11.509 13.82,11.83 L13.82,15.11 C13.86,15.54 14.219,15.86 14.65,15.86 C15.07,15.86 15.429,15.54 15.48,15.11 L15.48,11.83 C15.509,11.509 15.36,11.21 15.089,11.04 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconChat(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 2)\"\n        d=\"M10.02,0 C15.7,0 20,4.657 20,9.985 C20,16.164 14.96,20 10,20 C8.36,20 6.54,19.559 5.08,18.698 C4.57,18.388 4.14,18.157 3.59,18.338 L1.57,18.938 C1.06,19.099 0.6,18.698 0.75,18.157 L1.42,15.914 C1.53,15.603 1.51,15.273 1.35,15.013 C0.49,13.43 0,11.698 0,10.015 C0,4.747 4.21,0 10.02,0 Z M14.59,8.743 C13.88,8.743 13.31,9.314 13.31,10.025 C13.31,10.726 13.88,11.307 14.59,11.307 C15.3,11.307 15.87,10.726 15.87,10.025 C15.87,9.314 15.3,8.743 14.59,8.743 Z M9.98,8.743 C9.28,8.733 8.7,9.314 8.7,10.015 C8.7,10.726 9.27,11.297 9.98,11.307 C10.69,11.307 11.26,10.726 11.26,10.025 C11.26,9.314 10.69,8.743 9.98,8.743 Z M5.37,8.743 C4.66,8.743 4.09,9.314 4.09,10.025 C4.09,10.726 4.67,11.307 5.37,11.307 C6.08,11.297 6.65,10.726 6.65,10.025 C6.65,9.314 6.08,8.743 5.37,8.743 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconClose(props: IconProps) {\n  return (\n    <Outline {...props}>\n      <path d=\"m6.5 6.5 11 11M17.5 6.5l-11 11\" />\n    </Outline>\n  );\n}\n\nexport function IconCloseSquare(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 2)\"\n        d=\"M14.34,0 C17.73,0 20,2.38 20,5.92 L20,14.091 C20,17.621 17.73,20 14.34,20 L5.67,20 C2.28,20 0,17.621 0,14.091 L0,5.92 C0,2.38 2.28,0 5.67,0 Z M13.01,6.971 C12.67,6.63 12.12,6.63 11.77,6.971 L10,8.75 L8.22,6.971 C7.87,6.63 7.32,6.63 6.98,6.971 C6.64,7.311 6.64,7.871 6.98,8.21 L8.76,9.991 L6.98,11.761 C6.64,12.111 6.64,12.661 6.98,13 C7.15,13.17 7.38,13.261 7.6,13.261 C7.83,13.261 8.05,13.17 8.22,13 L10,11.231 L11.78,13 C11.95,13.181 12.17,13.261 12.39,13.261 C12.62,13.261 12.84,13.17 13.01,13 C13.35,12.661 13.35,12.111 13.01,11.771 L11.23,9.991 L13.01,8.21 C13.35,7.871 13.35,7.311 13.01,6.971 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconCopy(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M15.21,6.101 C15.376,6.1 15.511,5.966 15.5,5.801 C15.377,3.816 14.022,2.5 12.059,2.5 L6.869,2.5 C4.799,2.5 3.419,3.95 3.419,6.1 L3.419,12.79 C3.419,14.799 4.747,16.245 6.689,16.38 C6.855,16.391 6.989,16.256 6.989,16.09 L6.989,11.21 C6.989,8.26 9.069,6.11 11.939,6.11 Z\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M17.135,7.613 L11.94,7.613 C9.879,7.613 8.494,9.058 8.494,11.209 L8.494,17.904 C8.494,20.054 9.879,21.5 11.94,21.5 L17.134,21.5 C19.196,21.5 20.581,20.054 20.581,17.904 L20.581,11.209 C20.581,9.058 19.196,7.613 17.135,7.613 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconDanger(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 3)\"\n        d=\"M8.628,0.353 C9.988,-0.401 11.717,0.094 12.477,1.442 L19.746,14.057 C19.906,14.434 19.976,14.74 19.996,15.058 C20.036,15.801 19.776,16.524 19.266,17.079 C18.756,17.633 18.066,17.96 17.316,18 L2.679,18 C2.369,17.981 2.059,17.911 1.769,17.802 C0.319,17.217 -0.381,15.572 0.209,14.146 L7.528,1.433 C7.778,0.986 8.158,0.601 8.628,0.353 Z M9.998,12.273 C9.518,12.273 9.118,12.669 9.118,13.146 C9.118,13.62 9.518,14.018 9.998,14.018 C10.478,14.018 10.868,13.62 10.868,13.135 C10.868,12.66 10.478,12.273 9.998,12.273 Z M9.998,6.09 C9.518,6.09 9.118,6.476 9.118,6.952 L9.118,9.756 C9.118,10.231 9.518,10.629 9.998,10.629 C10.478,10.629 10.868,10.231 10.868,9.756 L10.868,6.952 C10.868,6.476 10.478,6.09 9.998,6.09 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconDelete(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(3, 2)\"\n        d=\"M15.939,6.697 C16.138,6.697 16.319,6.784 16.462,6.931 C16.596,7.088 16.663,7.283 16.643,7.489 C16.643,7.557 16.11,14.297 15.806,17.134 C15.615,18.875 14.493,19.932 12.809,19.961 C11.515,19.99 10.25,20 9.004,20 C7.681,20 6.388,19.99 5.132,19.961 C3.505,19.922 2.382,18.846 2.201,17.134 C1.888,14.287 1.364,7.557 1.355,7.489 C1.345,7.283 1.411,7.088 1.545,6.931 C1.678,6.784 1.868,6.697 2.069,6.697 Z M11.065,0 C11.949,0 12.738,0.617 12.967,1.497 L13.13,2.227 C13.263,2.822 13.778,3.243 14.371,3.243 L17.287,3.243 C17.676,3.243 18,3.566 18,3.977 L18,4.357 C18,4.758 17.676,5.091 17.287,5.091 L0.714,5.091 C0.324,5.091 0,4.758 0,4.357 L0,3.977 C0,3.566 0.324,3.243 0.714,3.243 L3.63,3.243 C4.222,3.243 4.737,2.822 4.871,2.228 L5.023,1.546 C5.261,0.617 6.041,0 6.935,0 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconDiscovery(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 2)\"\n        d=\"M10,0 C15.52,0 20,4.48 20,10 C20,15.53 15.52,20 10,20 C4.47,20 0,15.53 0,10 C0,4.48 4.47,0 10,0 Z M13.85,6.71 C13.96,6.36 13.64,6.03 13.29,6.14 L8.17,7.74 C7.96,7.81 7.79,7.97 7.73,8.18 L6.13,13.31 C6.02,13.65 6.35,13.98 6.69,13.87 L11.79,12.27 C12,12.21 12.17,12.04 12.23,11.83 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconDocument(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(3, 2)\"\n        d=\"M13.191,0 C16.28,0 18,1.78 18,4.83 L18,15.16 C18,18.26 16.28,20 13.191,20 L4.81,20 C1.77,20 0,18.26 0,15.16 L0,4.83 C0,1.78 1.77,0 4.81,0 Z M5.08,13.74 C4.78,13.71 4.49,13.85 4.33,14.11 C4.17,14.36 4.17,14.69 4.33,14.95 C4.49,15.2 4.78,15.35 5.08,15.31 L12.92,15.31 C13.319,15.27 13.62,14.929 13.62,14.53 C13.62,14.12 13.319,13.78 12.92,13.74 Z M12.92,9.179 L5.08,9.179 C4.649,9.179 4.3,9.53 4.3,9.96 C4.3,10.39 4.649,10.74 5.08,10.74 L12.92,10.74 C13.35,10.74 13.7,10.39 13.7,9.96 C13.7,9.53 13.35,9.179 12.92,9.179 Z M8.069,4.65 L5.08,4.65 L5.08,4.66 C4.649,4.66 4.3,5.01 4.3,5.44 C4.3,5.87 4.649,6.22 5.08,6.22 L8.069,6.22 C8.5,6.22 8.85,5.87 8.85,5.429 C8.85,5 8.5,4.65 8.069,4.65 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconFilter(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 3)\"\n        d=\"M16.561,11.02 C18.461,11.02 20,12.533 20,14.399 C20,16.265 18.461,17.778 16.561,17.778 C14.662,17.778 13.122,16.265 13.122,14.399 C13.122,12.533 14.662,11.02 16.561,11.02 Z M8.083,12.958 C8.916,12.958 9.591,13.622 9.591,14.439 C9.591,15.256 8.916,15.921 8.083,15.921 L1.508,15.921 C0.676,15.921 0,15.256 0,14.439 C0,13.622 0.676,12.958 1.508,12.958 Z M3.439,0 C5.339,0 6.878,1.513 6.878,3.379 C6.878,5.245 5.339,6.758 3.439,6.758 C1.54,6.758 0,5.245 0,3.379 C0,1.513 1.54,0 3.439,0 Z M18.493,1.898 C19.324,1.898 20,2.562 20,3.379 C20,4.196 19.324,4.86 18.493,4.86 L11.918,4.86 C11.086,4.86 10.41,4.196 10.41,3.379 C10.41,2.562 11.086,1.898 11.918,1.898 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconFolder(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 2)\"\n        d=\"M7.05,0 C7.981,-0.01 8.85,0.42 9.42,1.15 L10.3,2.32 C10.58,2.67 11,2.88 11.45,2.88 L14.52,2.88 C18.21,2.88 20.01,4.85 20,8.89 L20,13.76 C20,17.62 17.62,20 13.75,20 L6.24,20 C2.39,20 0,17.62 0,13.75 L0,6.24 C0,2.1 1.84,0 5.47,0 Z M14.63,11.79 L5.37,11.79 C4.95,11.79 4.62,12.12 4.62,12.54 C4.62,12.95 4.95,13.29 5.37,13.29 L14.63,13.29 C15.04,13.29 15.37,12.95 15.37,12.54 C15.37,12.12 15.04,11.79 14.63,11.79 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconGraph(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 2)\"\n        d=\"M6.956,2.96 C7.446,2.868 7.938,3.112 8.151,3.555 C8.205,3.635 8.247,3.722 8.278,3.812 C8.414,5.909 8.559,7.971 8.695,10.032 C8.692,10.246 8.725,10.459 8.794,10.663 C8.957,11.063 9.361,11.317 9.8,11.294 L16.456,10.867 L16.501,10.885 L16.623,10.894 C16.863,10.924 17.088,11.034 17.258,11.207 C17.457,11.409 17.565,11.679 17.561,11.96 C17.298,15.782 14.496,18.976 10.683,19.798 C6.869,20.621 2.96,18.875 1.088,15.514 C0.537,14.546 0.189,13.479 0.064,12.378 C0.016,12.051 -0.005,11.721 0.001,11.391 C0.014,7.327 2.907,3.819 6.956,2.96 Z M10.914,0 C15.484,0.138 19.304,3.456 19.997,7.891 C20.001,7.917 20.001,7.944 19.997,7.971 L19.995,8.096 C19.98,8.261 19.912,8.419 19.8,8.546 C19.659,8.704 19.459,8.801 19.245,8.815 L11.566,9.321 L11.439,9.324 C11.229,9.313 11.028,9.232 10.872,9.091 C10.684,8.922 10.577,8.683 10.578,8.433 L10.062,0.889 L10.062,0.765 C10.072,0.553 10.167,0.353 10.326,0.21 C10.486,0.066 10.697,-0.009 10.914,0 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconHide(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 3.5)\"\n        d=\"M17.17,0.214 C17.463,-0.071 17.922,-0.071 18.205,0.214 C18.497,0.5 18.497,0.972 18.205,1.258 L16.429,3.05 C17.844,4.349 19.044,6.101 19.941,8.208 C20.02,8.395 20.02,8.612 19.941,8.789 C17.853,13.692 14.136,16.626 9.999,16.626 L9.989,16.626 C8.106,16.626 6.301,16.006 4.71,14.873 L2.817,16.783 C2.671,16.931 2.485,17 2.3,17 C2.115,17 1.92,16.931 1.783,16.783 C1.539,16.537 1.5,16.143 1.695,15.858 L1.724,15.819 L16.156,1.258 C16.175,1.238 16.195,1.218 16.204,1.199 C16.224,1.179 16.243,1.159 16.253,1.14 Z M10.001,0.385 C11.397,0.385 12.753,0.72 14.002,1.35 L10.743,4.638 C10.509,4.599 10.255,4.569 10.001,4.569 C7.845,4.569 6.098,6.332 6.098,8.508 C6.098,8.764 6.128,9.019 6.167,9.256 L2.556,12.898 C1.581,11.756 0.732,10.378 0.059,8.793 C-0.02,8.616 -0.02,8.399 0.059,8.212 C2.147,3.309 5.864,0.385 9.992,0.385 Z M13.219,6.289 L12.155,7.362 C12.331,7.696 12.428,8.09 12.428,8.504 C12.428,9.852 11.335,10.955 9.999,10.955 C9.589,10.955 9.199,10.857 8.867,10.679 L7.803,11.753 C8.428,12.176 9.189,12.432 9.999,12.432 C12.145,12.432 13.892,10.67 13.892,8.504 C13.892,7.687 13.638,6.919 13.219,6.289 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconInfoCircle(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 2)\"\n        d=\"M10,0 C15.53,0 20,4.481 20,10 C20,15.521 15.53,20 10,20 C4.48,20 0,15.521 0,10 C0,4.481 4.48,0 10,0 Z M10,12.931 C9.52,12.931 9.13,13.321 9.13,13.801 C9.13,14.281 9.52,14.681 10.01,14.681 C10.49,14.681 10.88,14.281 10.88,13.801 C10.88,13.321 10.49,12.931 10,12.931 Z M10,5.33 C9.52,5.33 9.12,5.731 9.12,6.21 L9.12,10.63 C9.12,11.111 9.52,11.5 10,11.5 C10.48,11.5 10.87,11.111 10.87,10.63 L10.87,6.21 C10.87,5.731 10.48,5.33 10,5.33 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconLock(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(3.5, 2)\"\n        d=\"M8.485,0 C11.553,0 14.023,2.415 14.023,5.396 L14.023,6.929 C15.745,7.467 17,9.026 17,10.888 L17,15.825 C17,18.131 15.089,20 12.732,20 L4.269,20 C1.911,20 0,18.131 0,15.825 L0,10.888 C0,9.026 1.256,7.467 2.977,6.929 L2.977,5.396 C2.987,2.415 5.457,0 8.485,0 Z M8.495,11.384 C8.007,11.384 7.611,11.772 7.611,12.249 L7.611,14.455 C7.611,14.942 8.007,15.329 8.495,15.329 C8.993,15.329 9.389,14.942 9.389,14.455 L9.389,12.249 C9.389,11.772 8.993,11.384 8.495,11.384 Z M8.505,1.739 C6.442,1.739 4.766,3.369 4.756,5.376 L4.756,6.714 L12.244,6.714 L12.244,5.396 C12.244,3.379 10.568,1.739 8.505,1.739 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconMessage(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 3)\"\n        d=\"M14.939,0 C16.28,0 17.57,0.53 18.519,1.481 C19.469,2.43 20,3.71 20,5.05 L20,12.95 C20,15.74 17.73,18 14.939,18 L5.06,18 C2.269,18 0,15.74 0,12.95 L0,5.05 C0,2.26 2.259,0 5.06,0 Z M16.07,5.2 C15.86,5.189 15.66,5.26 15.509,5.4 L11,9 C10.42,9.481 9.589,9.481 9,9 L4.5,5.4 C4.189,5.17 3.759,5.2 3.5,5.47 C3.23,5.74 3.2,6.17 3.429,6.47 L3.56,6.6 L8.11,10.15 C8.67,10.59 9.349,10.83 10.06,10.83 C10.769,10.83 11.46,10.59 12.019,10.15 L16.53,6.54 L16.61,6.46 C16.849,6.17 16.849,5.75 16.599,5.46 C16.46,5.311 16.269,5.22 16.07,5.2 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconNotification(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(3.5, 2)\"\n        d=\"M6.463,17.228 C6.963,17.122 10.009,17.122 10.509,17.228 C10.937,17.327 11.399,17.557 11.399,18.061 C11.374,18.54 11.093,18.965 10.704,19.235 C10.2,19.628 9.609,19.877 8.991,19.966 C8.649,20.011 8.313,20.012 7.983,19.966 C7.364,19.877 6.772,19.628 6.269,19.234 C5.88,18.965 5.599,18.54 5.574,18.061 C5.574,17.557 6.036,17.327 6.463,17.228 Z M8.545,0 C10.625,0 12.75,0.987 14.012,2.625 C14.831,3.679 15.207,4.733 15.207,6.37 L15.207,6.796 C15.207,8.052 15.539,8.793 16.27,9.646 C16.823,10.274 17,11.081 17,11.956 C17,12.83 16.713,13.66 16.137,14.334 C15.384,15.142 14.322,15.657 13.237,15.747 C11.666,15.881 10.094,15.994 8.5,15.994 C6.906,15.994 5.335,15.926 3.764,15.747 C2.678,15.657 1.616,15.142 0.864,14.334 C0.288,13.66 0,12.83 0,11.956 C0,11.081 0.178,10.274 0.73,9.646 C1.484,8.793 1.794,8.052 1.794,6.796 L1.794,6.37 C1.794,4.688 2.213,3.589 3.077,2.512 C4.361,0.942 6.419,0 8.456,0 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconPassword(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 2)\"\n        d=\"M14.335,0 C17.723,0 20,2.378 20,5.917 L20,14.084 C20,17.623 17.723,20 14.334,20 L5.666,20 C2.277,20 0,17.623 0,14.084 L0,5.917 C0,2.378 2.277,0 5.666,0 Z M6.841,7.399 C5.407,7.399 4.24,8.566 4.24,10 C4.24,11.435 5.407,12.602 6.841,12.602 C8.013,12.602 8.995,11.818 9.319,10.75 L11.431,10.75 L11.431,11.852 C11.431,12.266 11.767,12.602 12.181,12.602 C12.595,12.602 12.931,12.266 12.931,11.852 L12.931,10.75 L14.261,10.75 L14.261,11.852 C14.261,12.266 14.597,12.602 15.011,12.602 C15.425,12.602 15.761,12.266 15.761,11.852 L15.761,10 C15.761,9.586 15.425,9.25 15.011,9.25 L9.319,9.25 C8.995,8.183 8.013,7.399 6.841,7.399 Z M6.841,8.899 C7.448,8.899 7.943,9.393 7.943,10.001 C7.943,10.608 7.448,11.102 6.841,11.102 C6.234,11.102 5.739,10.608 5.739,10.001 C5.739,9.393 6.234,8.899 6.841,8.899 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconPlus(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 2)\"\n        d=\"M14.66,0 C18.06,0 20,1.92 20,5.33 L20,14.67 C20,18.06 18.07,20 14.67,20 L5.33,20 C1.92,20 0,18.06 0,14.67 L0,5.33 C0,1.92 1.92,0 5.33,0 Z M9.99,5.51 C9.53,5.51 9.16,5.88 9.16,6.34 L9.16,9.16 L6.33,9.16 C6.11,9.16 5.9,9.25 5.74,9.4 C5.59,9.56 5.5,9.769 5.5,9.99 C5.5,10.45 5.87,10.82 6.33,10.83 L9.16,10.83 L9.16,13.66 C9.16,14.12 9.53,14.49 9.99,14.49 C10.45,14.49 10.82,14.12 10.82,13.66 L10.82,10.83 L13.66,10.83 C14.12,10.82 14.49,10.45 14.49,9.99 C14.49,9.53 14.12,9.16 13.66,9.16 L10.82,9.16 L10.82,6.34 C10.82,5.88 10.45,5.51 9.99,5.51 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconSearch(props: IconProps) {\n  return (\n    <Outline {...props}>\n      <circle cx=\"11\" cy=\"11\" r=\"7\" />\n      <path d=\"m20 20-3.55-3.55\" />\n    </Outline>\n  );\n}\n\nexport function IconSend(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 2)\"\n        d=\"M19.435,0.582 C18.935,0.069 18.195,-0.123 17.505,0.079 L1.408,4.76 C0.68,4.962 0.163,5.543 0.024,6.281 C-0.118,7.032 0.379,7.985 1.027,8.383 L6.06,11.477 C6.576,11.794 7.242,11.714 7.67,11.284 L13.433,5.484 C13.723,5.182 14.203,5.182 14.493,5.484 C14.783,5.776 14.783,6.249 14.493,6.551 L8.72,12.352 C8.292,12.781 8.212,13.451 8.527,13.97 L11.602,19.054 C11.962,19.658 12.583,20 13.263,20 C13.343,20 13.433,20 13.513,19.99 C14.293,19.889 14.914,19.356 15.144,18.601 L19.916,2.525 C20.126,1.84 19.936,1.095 19.435,0.582\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconSetting(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2.5, 2)\"\n        d=\"M10.217,0 C10.973,0 11.658,0.42 12.036,1.04 C12.22,1.34 12.343,1.71 12.312,2.1 C12.292,2.4 12.384,2.7 12.547,2.98 C13.068,3.83 14.223,4.15 15.123,3.67 C16.134,3.09 17.412,3.44 17.994,4.43 L18.679,5.61 C19.272,6.6 18.945,7.87 17.923,8.44 C17.054,8.95 16.748,10.08 17.269,10.94 C17.432,11.21 17.616,11.44 17.902,11.58 C18.26,11.77 18.536,12.07 18.73,12.37 C19.108,12.99 19.078,13.75 18.71,14.42 L17.994,15.62 C17.616,16.26 16.911,16.66 16.185,16.66 C15.828,16.66 15.429,16.56 15.102,16.36 C14.836,16.19 14.53,16.13 14.203,16.13 C13.191,16.13 12.343,16.96 12.312,17.95 C12.312,19.1 11.372,20 10.197,20 L8.807,20 C7.621,20 6.681,19.1 6.681,17.95 C6.661,16.96 5.813,16.13 4.801,16.13 C4.464,16.13 4.157,16.19 3.902,16.36 C3.575,16.56 3.166,16.66 2.818,16.66 C2.082,16.66 1.377,16.26 0.999,15.62 L0.294,14.42 C-0.084,13.77 -0.105,12.99 0.274,12.37 C0.437,12.07 0.744,11.77 1.091,11.58 C1.377,11.44 1.561,11.21 1.735,10.94 C2.246,10.08 1.939,8.95 1.071,8.44 C0.059,7.87 -0.268,6.6 0.314,5.61 L0.999,4.43 C1.592,3.44 2.859,3.09 3.881,3.67 C4.77,4.15 5.925,3.83 6.446,2.98 C6.61,2.7 6.702,2.4 6.681,2.1 C6.661,1.71 6.773,1.34 6.967,1.04 C7.346,0.42 8.03,0.02 8.776,0 Z M9.512,7.18 C7.908,7.18 6.61,8.44 6.61,10.01 C6.61,11.58 7.908,12.83 9.512,12.83 C11.117,12.83 12.384,11.58 12.384,10.01 C12.384,8.44 11.117,7.18 9.512,7.18 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconShieldDone(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(3.5, 2)\"\n        d=\"M9.071,0.1 L15.708,2.327 C16.451,2.575 16.954,3.257 16.958,4.022 L17,10.663 C17.013,12.676 16.279,14.628 14.935,16.158 C14.317,16.86 13.525,17.463 12.513,18.002 L8.945,19.91 C8.833,19.969 8.71,19.999 8.587,20 C8.463,20.001 8.339,19.972 8.228,19.914 L4.627,18.051 C3.604,17.52 2.805,16.926 2.181,16.233 C0.814,14.719 0.055,12.776 0.042,10.76 L0,4.124 C-0.004,3.358 0.489,2.671 1.228,2.413 L7.841,0.106 C8.233,-0.033 8.671,-0.036 9.071,0.1 Z M12.245,7.219 C11.948,6.933 11.47,6.935 11.177,7.225 L7.808,10.545 L6.429,9.219 C6.132,8.934 5.655,8.937 5.361,9.226 C5.068,9.515 5.071,9.98 5.368,10.265 L7.284,12.109 C7.433,12.252 7.626,12.323 7.819,12.321 C8.012,12.32 8.205,12.247 8.352,12.102 L12.251,8.258 C12.544,7.969 12.541,7.504 12.245,7.219 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconShow(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 4)\"\n        d=\"M10,0 C12.068,0 14.029,0.718 15.737,2.046 C17.444,3.365 18.898,5.295 19.941,7.709 C20.02,7.893 20.02,8.107 19.941,8.281 C17.854,13.11 14.137,16 10,16 L9.99,16 C5.863,16 2.146,13.11 0.059,8.281 C-0.02,8.107 -0.02,7.893 0.059,7.709 C2.146,2.88 5.863,0 9.99,0 Z M10,4.121 C7.844,4.121 6.098,5.857 6.098,8 C6.098,10.133 7.844,11.869 10,11.869 C12.146,11.869 13.893,10.133 13.893,8 C13.893,5.857 12.146,4.121 10,4.121 Z M10.001,5.574 C11.338,5.574 12.43,6.66 12.43,7.998 C12.43,9.326 11.338,10.412 10.001,10.412 C8.655,10.412 7.562,9.326 7.562,7.998 C7.562,7.833 7.582,7.678 7.611,7.523 L7.66,7.523 C8.743,7.523 9.621,6.669 9.66,5.603 C9.767,5.583 9.884,5.574 10.001,5.574 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconStar(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 2.5)\"\n        d=\"M15.919,11.82 C15.66,12.071 15.541,12.434 15.6,12.79 L16.489,17.71 C16.564,18.127 16.388,18.549 16.039,18.79 C15.697,19.04 15.242,19.07 14.869,18.87 L10.44,16.56 C10.286,16.478 10.115,16.434 9.94,16.429 L9.669,16.429 C9.575,16.443 9.483,16.473 9.399,16.519 L4.969,18.84 C4.75,18.95 4.502,18.989 4.259,18.95 C3.667,18.838 3.272,18.274 3.369,17.679 L4.259,12.759 C4.318,12.4 4.199,12.035 3.94,11.78 L0.329,8.28 C0.027,7.987 -0.078,7.547 0.06,7.15 C0.194,6.754 0.536,6.465 0.949,6.4 L5.919,5.679 C6.297,5.64 6.629,5.41 6.799,5.07 L8.989,0.58 C9.041,0.48 9.108,0.388 9.189,0.31 L9.279,0.24 C9.326,0.188 9.38,0.145 9.44,0.11 L9.549,0.07 L9.719,0 L10.14,0 C10.516,0.039 10.847,0.264 11.02,0.6 L13.239,5.07 C13.399,5.397 13.71,5.624 14.069,5.679 L19.039,6.4 C19.459,6.46 19.81,6.75 19.949,7.15 C20.08,7.551 19.967,7.991 19.659,8.28 L15.919,11.82 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconSwap(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 3)\"\n        d=\"M15.3,9.829 C16.512,9.829 17.61,9.898 18.285,10.027 C18.296,10.027 18.914,10.154 19.12,10.236 C19.417,10.364 19.669,10.595 19.829,10.885 C19.944,11.117 20,11.362 20,11.617 C19.989,11.883 19.817,12.383 19.737,12.58 C19.235,13.88 17.587,16.363 16.581,17.316 C16.421,17.477 16.227,17.652 16.181,17.698 C15.928,17.895 15.621,18 15.289,18 C14.991,18 14.694,17.907 14.454,17.721 C14.329,17.632 14.147,17.455 14.064,17.371 L14.02,17.327 C12.978,16.353 11.412,13.926 10.909,12.695 C10.898,12.695 10.647,12.082 10.597,11.711 L10.588,11.617 L10.588,11.571 C10.588,11.036 10.885,10.537 11.367,10.282 C11.63,10.143 12.395,10.015 12.407,10.003 C13.093,9.898 14.145,9.829 15.3,9.829 Z M4.706,9.891 C5.185,9.891 5.579,10.256 5.633,10.728 L5.639,10.838 L5.896,15.417 C5.896,16.085 5.363,16.625 4.706,16.625 C4.089,16.625 3.581,16.15 3.52,15.541 L3.514,15.417 L3.772,10.838 C3.772,10.315 4.189,9.891 4.706,9.891 Z M4.712,0 C5.008,0 5.305,0.093 5.546,0.278 C5.65,0.353 5.794,0.489 5.887,0.58 L5.98,0.673 C7.021,1.649 8.588,4.074 9.091,5.304 C9.101,5.304 9.352,5.919 9.403,6.29 L9.412,6.384 L9.412,6.43 C9.412,6.964 9.113,7.463 8.633,7.718 C8.37,7.858 7.605,7.985 7.593,7.997 C6.907,8.102 5.855,8.17 4.7,8.17 C3.488,8.17 2.39,8.102 1.715,7.973 C1.703,7.973 1.086,7.846 0.88,7.764 C0.583,7.637 0.331,7.404 0.171,7.115 C0.056,6.883 0,6.638 0,6.384 C0.011,6.117 0.183,5.618 0.262,5.421 C0.765,4.12 2.412,1.637 3.419,0.685 C3.579,0.523 3.773,0.348 3.819,0.302 C4.07,0.105 4.379,0 4.712,0 Z M15.295,1.375 C15.911,1.375 16.418,1.85 16.479,2.459 L16.485,2.583 L16.229,7.162 C16.229,7.686 15.811,8.11 15.295,8.11 C14.815,8.11 14.421,7.744 14.367,7.272 L14.361,7.162 L14.103,2.583 C14.103,1.915 14.637,1.375 15.295,1.375 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconTickSquare(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 2)\"\n        d=\"M14.34,0 C17.73,0 20,2.38 20,5.92 L20,14.091 C20,17.62 17.73,20 14.34,20 L5.67,20 C2.28,20 0,17.62 0,14.091 L0,5.92 C0,2.38 2.28,0 5.67,0 Z M14.18,7 C13.84,6.66 13.28,6.66 12.94,7 L8.81,11.13 L7.06,9.38 C6.72,9.04 6.16,9.04 5.82,9.38 C5.48,9.72 5.48,10.27 5.82,10.62 L8.2,12.99 C8.37,13.16 8.59,13.24 8.81,13.24 C9.04,13.24 9.26,13.16 9.43,12.99 L14.18,8.24 C14.52,7.9 14.52,7.35 14.18,7 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconTimeCircle(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 2)\"\n        d=\"M10,0 C15.53,0 20,4.48 20,10 C20,15.53 15.53,20 10,20 C4.48,20 0,15.53 0,10 C0,4.48 4.48,0 10,0 Z M9.65,4.93 C9.24,4.93 8.9,5.26 8.9,5.68 L8.9,10.73 C8.9,10.99 9.04,11.23 9.27,11.37 L13.19,13.71 C13.31,13.78 13.44,13.82 13.58,13.82 C13.83,13.82 14.08,13.69 14.22,13.45 C14.43,13.1 14.32,12.64 13.96,12.42 L10.4,10.3 L10.4,5.68 C10.4,5.26 10.06,4.93 9.65,4.93 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconUnlock(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(3.5, 2)\"\n        d=\"M8.479,0 C10.846,0 12.938,1.462 13.711,3.628 C13.792,3.847 13.772,4.085 13.67,4.294 C13.568,4.503 13.387,4.662 13.162,4.732 C12.705,4.89 12.197,4.653 12.024,4.195 C11.506,2.723 10.084,1.739 8.499,1.739 C6.425,1.739 4.749,3.37 4.738,5.367 L4.738,6.709 L4.726,6.711 L12.731,6.711 C15.089,6.711 17,8.581 17,10.887 L17,15.824 C17,18.13 15.089,20 12.731,20 L4.268,20 C1.911,20 0,18.13 0,15.824 L0,10.887 C0,8.99 1.301,7.407 3.071,6.898 L2.96,6.911 L2.96,5.387 C2.98,2.416 5.451,0 8.479,0 Z M8.495,11.383 C8.007,11.383 7.611,11.77 7.611,12.247 L7.611,14.454 C7.611,14.941 8.007,15.328 8.495,15.328 C8.993,15.328 9.389,14.941 9.389,14.454 L9.389,12.247 C9.389,11.77 8.993,11.383 8.495,11.383 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconUpload(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 2)\"\n        d=\"M0,11.287 C0,8.876 1.878,6.896 4.231,6.785 L4.44,6.78 L9.23,6.78 L9.23,12.885 C9.23,13.322 9.57,13.668 10,13.668 C10.385,13.668 10.711,13.378 10.763,12.992 L10.77,12.885 L10.77,6.78 L15.55,6.78 C17.93,6.78 19.885,8.691 19.995,11.094 L20,11.308 L20,16.263 C20,18.684 18.122,20.664 15.769,20.775 L15.56,20.78 L4.45,20.78 C2.06,20.78 0.114,18.86 0.005,16.465 L0,16.253 Z M6.55,4.24 C6.268,3.967 6.243,3.538 6.466,3.236 L6.54,3.15 L9.45,0.23 C9.6,0.08 9.79,0 10,0 C10.16,0 10.32,0.051 10.449,0.148 L10.54,0.23 L13.45,3.15 C13.6,3.3 13.68,3.5 13.68,3.69 C13.68,3.89 13.6,4.09 13.45,4.24 C13.177,4.513 12.748,4.538 12.446,4.314 L12.36,4.24 L10.77,2.64 L10.77,6.78 L9.23,6.78 L9.23,2.64 L7.64,4.24 C7.34,4.54 6.85,4.54 6.55,4.24 Z\"\n      />\n    </Bold>\n  );\n}\n\nexport function IconUsers(props: IconProps) {\n  return (\n    <Bold {...props}>\n      <path\n        transform=\"translate(2, 3)\"\n        d=\"M7.349,11.858 C11.335,11.858 14.699,12.491 14.699,14.94 C14.699,17.388 11.313,18 7.349,18 L7.079,17.999 C3.213,17.97 0,17.311 0,14.917 C0,12.47 3.386,11.858 7.349,11.858 Z M13.57,10.383 C15.085,10.354 16.716,10.559 17.317,10.702 C18.593,10.947 19.432,11.444 19.779,12.169 C20.074,12.763 20.074,13.453 19.779,14.047 C19.248,15.171 17.534,15.532 16.867,15.625 C16.729,15.644 16.619,15.529 16.633,15.393 C16.974,12.281 14.266,10.805 13.566,10.466 C13.536,10.449 13.53,10.426 13.533,10.411 C13.534,10.401 13.547,10.386 13.57,10.383 Z M7.349,0 C10.049,0 12.212,2.119 12.212,4.762 C12.212,7.406 10.049,9.525 7.349,9.525 C4.651,9.525 2.486,7.406 2.486,4.762 C2.486,2.119 4.651,0 7.349,0 Z M13.834,0.795 C16.442,0.795 18.488,3.197 17.791,5.871 C17.32,7.674 15.617,8.87 13.718,8.822 C13.528,8.818 13.341,8.8 13.159,8.77 C13.028,8.747 12.961,8.602 13.036,8.495 C13.761,7.451 14.173,6.195 14.173,4.849 C14.173,3.442 13.723,2.132 12.94,1.056 C12.914,1.023 12.896,0.972 12.921,0.932 C12.941,0.902 12.98,0.885 13.016,0.877 C13.28,0.824 13.551,0.795 13.834,0.795 Z\"\n      />\n    </Bold>\n  );\n}\n\n/* ── Motion ───────────────────────────────────────────────────────────────── */\n\nexport type EmptyTone = 'neutral' | 'positive' | 'caution' | 'critical';\n\nconst TONE_GLYPH: Record<EmptyTone, string> = {\n  neutral: 'text-neutral-800 dark:text-neutral-100',\n  positive: 'text-emerald-600 dark:text-emerald-400',\n  caution: 'text-amber-600 dark:text-amber-400',\n  critical: 'text-red-600 dark:text-red-400',\n};\n\n/**\n * One stagger for the whole set, and a reduced-motion branch that keeps the\n * order but drops the travel — the sequence still reads, nothing slides.\n */\nexport function useReveal() {\n  const reduced = useReducedMotion();\n\n  return React.useMemo(() => {\n    const stack: Variants = {\n      hidden: {},\n      shown: {\n        transition: {\n          staggerChildren: reduced ? 0 : 0.055,\n          delayChildren: reduced ? 0 : 0.04,\n        },\n      },\n    };\n\n    const item: Variants = reduced\n      ? { hidden: { opacity: 0 }, shown: { opacity: 1, transition: { duration: 0.15 } } }\n      : { hidden: { opacity: 0, y: 8 }, shown: { opacity: 1, y: 0, transition: SPRING_ENTRANCE } };\n\n    return { stack, item, reduced };\n  }, [reduced]);\n}\n\n/**\n * Re-seeds a piece of local state when a prop changes.\n *\n * These blocks all keep some state of their own — a request that was sent, a\n * filter that was dropped, rows that were added — and all of them are driven by\n * a `variant` prop on the specimen page. Resetting that state in an effect\n * costs a second render with the stale value painted in between; comparing the\n * key during render is React's own answer and settles before paint.\n */\nexport function usePropState<T>(\n  seed: T,\n  key: unknown,\n): [T, React.Dispatch<React.SetStateAction<T>>] {\n  const [value, setValue] = React.useState(seed);\n  const [seen, setSeen] = React.useState(key);\n\n  if (!Object.is(seen, key)) {\n    setSeen(key);\n    setValue(seed);\n  }\n\n  return [value, setValue];\n}\n\n/* ── Backdrops ────────────────────────────────────────────────────────────── */\n\n/**\n * The thing behind the glyph.\n *\n * Every block in this category gets its own, because twenty-five panels sharing\n * one ambient animation is twenty-five panels that look like one panel. Each\n * backdrop is a different idea about the emptiness in front of it: a search\n * ripples outward, a trash can drips away, an offline panel breaks into shards,\n * a webhook waits on the one loop in the set.\n *\n * Rules all of them keep:\n *   - They play once, when the medallion reaches the viewport, and then stop.\n *     `pulse` is the exception and says so — a listening indicator is a loading\n *     indicator, not decoration.\n *   - They are `aria-hidden` and `pointer-events-none`. Nothing here is content.\n *   - Under reduced motion they resolve instantly to their finished state, so\n *     the composition is identical and only the travel is gone.\n *   - They are monochrome and low contrast. If a backdrop competes with the\n *     glyph in front of it, it is wrong.\n */\nexport type MedallionBackdrop =\n  | 'none'\n  | 'rings'\n  | 'halo'\n  | 'grid'\n  | 'cells'\n  | 'scan'\n  | 'orbit'\n  | 'bars'\n  | 'rays'\n  | 'stack'\n  | 'ripple'\n  | 'beam'\n  | 'chime'\n  | 'comet'\n  | 'sparks'\n  | 'dashes'\n  | 'crosshair'\n  | 'wave'\n  | 'shards'\n  | 'trail'\n  | 'corners'\n  | 'drip'\n  | 'contour'\n  | 'sieve'\n  | 'spotlight'\n  | 'pulse';\n\nconst HAIRLINE = 'border-black/[0.07] dark:border-white/[0.1]';\nconst HAIRLINE_SOFT = 'border-black/[0.045] dark:border-white/[0.06]';\nconst HAIRLINE_FAINT = 'border-black/[0.028] dark:border-white/[0.04]';\n/**\n * EmptyPanel's own surface. Any backdrop whose parts overlap has to be filled\n * with it: an outlined-but-transparent shape turns into a lattice of crossing\n * hairlines the moment a second one lands on top of it.\n */\nconst SURFACE = 'bg-white dark:bg-neutral-950';\nconst INK = 'bg-black/[0.09] dark:bg-white/[0.13]';\nconst INK_SOFT = 'bg-black/[0.055] dark:bg-white/[0.08]';\nconst FADE_DOWN = '[mask-image:linear-gradient(to_bottom,black_15%,transparent_95%)]';\nconst FADE_EDGES = '[mask-image:radial-gradient(closest-side,black_55%,transparent_100%)]';\n\ntype Phase = 'hidden' | 'shown';\n\ninterface BackdropProps {\n  kind: MedallionBackdrop;\n}\n\n/**\n * One IntersectionObserver per medallion, not one per moving part: the backdrop\n * flips a single phase and every child animates off the same variant.\n */\nexport function MedallionBackdropLayer({ kind }: BackdropProps) {\n  const reduced = useReducedMotion();\n  const ref = React.useRef<HTMLDivElement>(null);\n  const seen = useInView(ref, { once: true, amount: 0.4 });\n  const phase: Phase = seen ? 'shown' : 'hidden';\n\n  return (\n    <div\n      ref={ref}\n      aria-hidden\n      className=\"pointer-events-none absolute inset-0 grid place-items-center\"\n    >\n      {kind !== 'none' && <Backdrop kind={kind} phase={phase} reduced={Boolean(reduced)} />}\n    </div>\n  );\n}\n\ninterface PartProps {\n  phase: Phase;\n  reduced: boolean;\n}\n\nfunction Backdrop({ kind, phase, reduced }: BackdropProps & PartProps) {\n  switch (kind) {\n    case 'rings':\n      return <Rings phase={phase} reduced={reduced} />;\n    case 'halo':\n      return <Halo phase={phase} reduced={reduced} />;\n    case 'grid':\n      return <DotGrid phase={phase} reduced={reduced} />;\n    case 'cells':\n      return <Cells phase={phase} reduced={reduced} />;\n    case 'scan':\n      return <Scan phase={phase} reduced={reduced} />;\n    case 'orbit':\n      return <Orbit phase={phase} reduced={reduced} />;\n    case 'bars':\n      return <Bars phase={phase} reduced={reduced} />;\n    case 'rays':\n      return <Rays phase={phase} reduced={reduced} />;\n    case 'stack':\n      return <Stack phase={phase} reduced={reduced} />;\n    case 'ripple':\n      return <Ripple phase={phase} reduced={reduced} />;\n    case 'beam':\n      return <Beam phase={phase} reduced={reduced} />;\n    case 'chime':\n      return <Chime phase={phase} reduced={reduced} />;\n    case 'comet':\n      return <Comet phase={phase} reduced={reduced} />;\n    case 'sparks':\n      return <Sparks phase={phase} reduced={reduced} />;\n    case 'dashes':\n      return <Dashes phase={phase} reduced={reduced} />;\n    case 'crosshair':\n      return <Crosshair phase={phase} reduced={reduced} />;\n    case 'wave':\n      return <Wave phase={phase} reduced={reduced} />;\n    case 'shards':\n      return <Shards phase={phase} reduced={reduced} />;\n    case 'trail':\n      return <Trail phase={phase} reduced={reduced} />;\n    case 'corners':\n      return <Corners phase={phase} reduced={reduced} />;\n    case 'drip':\n      return <Drip phase={phase} reduced={reduced} />;\n    case 'contour':\n      return <Contour phase={phase} reduced={reduced} />;\n    case 'sieve':\n      return <Sieve phase={phase} reduced={reduced} />;\n    case 'spotlight':\n      return <Spotlight phase={phase} reduced={reduced} />;\n    case 'pulse':\n      return <Pulse reduced={reduced} />;\n    default:\n      return null;\n  }\n}\n\n/** Two hairline circles opening outward. The quietest of the set. */\nfunction Rings({ phase, reduced }: PartProps) {\n  return (\n    <>\n      {[112, 164].map((diameter, index) => (\n        <motion.span\n          key={diameter}\n          initial=\"hidden\"\n          animate={phase}\n          variants={{\n            hidden: reduced ? { opacity: 0 } : { opacity: 0, scale: 0.55 },\n            shown: {\n              opacity: 1,\n              scale: 1,\n              transition: reduced\n                ? { duration: 0.2 }\n                : { ...SPRING_FLUID, delay: 0.06 + index * 0.07 },\n            },\n          }}\n          style={{ width: diameter, height: diameter }}\n          className={cn(\n            'absolute rounded-full border',\n            index === 0 ? HAIRLINE : HAIRLINE_SOFT,\n            FADE_DOWN,\n          )}\n        />\n      ))}\n    </>\n  );\n}\n\n/** A soft wash that swells behind the tile, with one circle to give it an edge. */\nfunction Halo({ phase, reduced }: PartProps) {\n  return (\n    <>\n      <motion.span\n        initial=\"hidden\"\n        animate={phase}\n        variants={{\n          hidden: reduced ? { opacity: 0 } : { opacity: 0, scale: 0.5 },\n          shown: {\n            opacity: 1,\n            scale: 1,\n            transition: reduced ? { duration: 0.2 } : { ...SPRING_FLUID, delay: 0.05 },\n          },\n        }}\n        className=\"absolute size-[150px] rounded-full bg-[radial-gradient(closest-side,rgba(0,0,0,0.055),transparent)] dark:bg-[radial-gradient(closest-side,rgba(255,255,255,0.08),transparent)]\"\n      />\n      <motion.span\n        initial=\"hidden\"\n        animate={phase}\n        variants={{\n          hidden: reduced ? { opacity: 0 } : { opacity: 0, scale: 0.7 },\n          shown: {\n            opacity: 1,\n            scale: 1,\n            transition: reduced ? { duration: 0.2 } : { ...SPRING_FLUID, delay: 0.14 },\n          },\n        }}\n        className={cn('absolute size-[96px] rounded-full border', HAIRLINE, FADE_DOWN)}\n      />\n    </>\n  );\n}\n\n/** A field of dots lighting up from the centre out. */\nfunction DotGrid({ phase, reduced }: PartProps) {\n  const cells = React.useMemo(() => {\n    const out: { x: number; y: number; distance: number }[] = [];\n    for (let row = 0; row < 5; row += 1) {\n      for (let column = 0; column < 5; column += 1) {\n        out.push({ x: column - 2, y: row - 2, distance: Math.abs(column - 2) + Math.abs(row - 2) });\n      }\n    }\n    return out;\n  }, []);\n\n  return (\n    <div className={cn('absolute size-[160px]', FADE_EDGES)}>\n      {cells.map((cell) => (\n        <motion.span\n          key={`${cell.x}:${cell.y}`}\n          initial=\"hidden\"\n          animate={phase}\n          variants={{\n            hidden: reduced ? { opacity: 0 } : { opacity: 0, scale: 0.6 },\n            shown: {\n              opacity: 1,\n              scale: 1,\n              transition: reduced\n                ? { duration: 0.2 }\n                : { ...SPRING_SNAPPY, delay: 0.04 + cell.distance * 0.045 },\n            },\n          }}\n          style={{\n            left: `${50 + cell.x * 16}%`,\n            top: `${50 + cell.y * 16}%`,\n          }}\n          className={cn('absolute size-[3px] -translate-x-1/2 -translate-y-1/2 rounded-full', INK)}\n        />\n      ))}\n    </div>\n  );\n}\n\n/** Nine tiles filling in like a catalogue loading its rows. */\nfunction Cells({ phase, reduced }: PartProps) {\n  return (\n    <div className={cn('absolute grid size-[156px] grid-cols-3 grid-rows-3 gap-2', FADE_EDGES)}>\n      {[0, 1, 2, 3, 4, 5, 6, 7, 8].map((cell) => (\n        <motion.span\n          key={cell}\n          initial=\"hidden\"\n          animate={phase}\n          variants={{\n            hidden: reduced ? { opacity: 0 } : { opacity: 0, scale: 0.7 },\n            shown: {\n              opacity: 1,\n              scale: 1,\n              transition: reduced\n                ? { duration: 0.2 }\n                : { ...SPRING_SNAPPY, delay: 0.03 * ((cell * 5) % 9) },\n            },\n          }}\n          className={cn('rounded-[9px] border', HAIRLINE)}\n        />\n      ))}\n    </div>\n  );\n}\n\n/** A line travelling down a framed square, once, the way a check runs. */\nfunction Scan({ phase, reduced }: PartProps) {\n  return (\n    <div\n      className={cn(\n        'absolute size-[150px] overflow-hidden rounded-[26px] border',\n        HAIRLINE,\n        FADE_DOWN,\n      )}\n    >\n      <motion.span\n        initial=\"hidden\"\n        animate={phase}\n        variants={{\n          hidden: { y: -8, opacity: 0 },\n          shown: {\n            y: 150,\n            opacity: [0, 1, 1, 0],\n            transition: reduced ? { duration: 0 } : { duration: 1.1, ease: EASE_OUT },\n          },\n        }}\n        className=\"absolute inset-x-0 h-px bg-black/20 dark:bg-white/25\"\n      />\n    </div>\n  );\n}\n\n/** One dot going round the tile and parking at the top. */\nfunction Orbit({ phase, reduced }: PartProps) {\n  return (\n    <>\n      <span className={cn('absolute size-[120px] rounded-full border', HAIRLINE_SOFT, FADE_DOWN)} />\n      <motion.span\n        initial=\"hidden\"\n        animate={phase}\n        variants={{\n          hidden: { rotate: -120, opacity: 0 },\n          shown: {\n            rotate: 240,\n            opacity: 1,\n            transition: reduced ? { duration: 0 } : { duration: 1.3, ease: EASE_OUT },\n          },\n        }}\n        className=\"absolute size-[120px]\"\n      >\n        <span className=\"absolute left-1/2 top-0 size-[7px] -translate-x-1/2 rounded-full bg-black/[0.16] dark:bg-white/25\" />\n      </motion.span>\n    </>\n  );\n}\n\n/** A histogram rising behind the glyph. */\nfunction Bars({ phase, reduced }: PartProps) {\n  const heights = [0.34, 0.58, 0.44, 0.82, 0.5, 0.68, 0.38];\n  return (\n    <div className={cn('absolute flex h-[120px] w-[168px] items-end gap-2', FADE_EDGES)}>\n      {heights.map((height, index) => (\n        <motion.span\n          key={index}\n          initial=\"hidden\"\n          animate={phase}\n          variants={{\n            hidden: { scaleY: 0 },\n            shown: {\n              scaleY: height,\n              transition: reduced ? { duration: 0 } : { ...SPRING_FLUID, delay: index * 0.05 },\n            },\n          }}\n          style={{ originY: 1 }}\n          className={cn('h-full flex-1 rounded-t-[4px]', INK_SOFT)}\n        />\n      ))}\n    </div>\n  );\n}\n\n/**\n * Ticks radiating out, like something being broadcast.\n *\n * The rotation lives on a plain wrapper: motion writes `transform` itself, so a\n * `rotate()` passed through `style` is thrown away the moment a variant runs.\n */\nfunction Rays({ phase, reduced }: PartProps) {\n  return (\n    <div className={cn('absolute size-[160px]', FADE_DOWN)}>\n      {Array.from({ length: 12 }).map((_, index) => (\n        <span\n          key={index}\n          style={{ transform: `rotate(${index * 30}deg)` }}\n          className=\"absolute left-1/2 top-1/2 h-[78px] w-px origin-top\"\n        >\n          <motion.span\n            initial=\"hidden\"\n            animate={phase}\n            variants={{\n              hidden: reduced ? { opacity: 0 } : { opacity: 0, scaleY: 0 },\n              shown: {\n                opacity: 1,\n                scaleY: 1,\n                transition: reduced\n                  ? { duration: 0.2 }\n                  : { ...SPRING_SNAPPY, delay: 0.04 + index * 0.035 },\n              },\n            }}\n            style={{ originY: 0 }}\n            className={cn('absolute bottom-0 block h-3.5 w-px', INK)}\n          />\n        </span>\n      ))}\n    </div>\n  );\n}\n\n/**\n * A cascade of cards behind the tile — the shape of a template picker.\n *\n * Two earlier versions failed the same way: anything rotated about its own\n * centre, or fanned from its bottom edge, throws corners out on every side and\n * reads as a rosette rather than a stack at 56px. Straight offsets, each card a\n * little smaller than the one in front and filled with the panel surface, are\n * unmistakable — you see two slivers of paper and nothing else.\n */\nfunction Stack({ phase, reduced }: PartProps) {\n  const cards = [\n    { y: -17, scale: 0.82, border: HAIRLINE_SOFT },\n    { y: -9, scale: 0.91, border: HAIRLINE },\n  ];\n  return (\n    <>\n      {cards.map((card, index) => (\n        <motion.span\n          key={card.y}\n          initial=\"hidden\"\n          animate={phase}\n          variants={{\n            hidden: reduced\n              ? { opacity: 0, y: card.y, scale: card.scale }\n              : { opacity: 0, y: 0, scale: 1 },\n            shown: {\n              opacity: 1,\n              y: card.y,\n              scale: card.scale,\n              transition: reduced\n                ? { duration: 0.2 }\n                : { ...SPRING_FLUID, delay: 0.12 + index * 0.07 },\n            },\n          }}\n          className={cn('absolute size-14 rounded-2xl border', SURFACE, card.border)}\n        />\n      ))}\n    </>\n  );\n}\n\n/** Three squares opening outward, one after another. A query going out. */\nfunction Ripple({ phase, reduced }: PartProps) {\n  return (\n    <>\n      {[0, 1, 2].map((index) => (\n        <motion.span\n          key={index}\n          initial=\"hidden\"\n          animate={phase}\n          variants={{\n            hidden: { opacity: 0, scale: 0.4 },\n            shown: {\n              opacity: [0, 0.9, 0],\n              scale: [0.4, 1, 1.25],\n              transition: reduced\n                ? { duration: 0 }\n                : { duration: 1.5, delay: index * 0.22, ease: EASE_OUT },\n            },\n          }}\n          className={cn('absolute size-[150px] rounded-full border', HAIRLINE)}\n        />\n      ))}\n      <span className={cn('absolute size-[92px] rounded-full border', HAIRLINE_SOFT)} />\n    </>\n  );\n}\n\n/** A column of light rising through the tile. */\nfunction Beam({ phase, reduced }: PartProps) {\n  return (\n    <>\n      <motion.span\n        initial=\"hidden\"\n        animate={phase}\n        variants={{\n          hidden: reduced ? { opacity: 0 } : { opacity: 0, scaleY: 0.3 },\n          shown: {\n            opacity: 1,\n            scaleY: 1,\n            transition: reduced ? { duration: 0.2 } : { ...SPRING_FLUID, delay: 0.05 },\n          },\n        }}\n        style={{ originY: 1 }}\n        className=\"absolute h-[156px] w-[58px] rounded-full bg-[linear-gradient(to_top,rgba(0,0,0,0.05),transparent_78%)] blur-[6px] dark:bg-[linear-gradient(to_top,rgba(255,255,255,0.08),transparent_78%)]\"\n      />\n      {[0, 1, 2].map((index) => (\n        <motion.span\n          key={index}\n          initial=\"hidden\"\n          animate={phase}\n          variants={{\n            hidden: { opacity: 0, y: 28 },\n            shown: {\n              opacity: [0, 1, 0],\n              y: -34 - index * 8,\n              transition: reduced\n                ? { duration: 0 }\n                : { duration: 1.2, delay: 0.1 + index * 0.16, ease: EASE_OUT },\n            },\n          }}\n          style={{ left: `calc(50% + ${(index - 1) * 18}px)` }}\n          className={cn('absolute size-[3px] rounded-full', INK)}\n        />\n      ))}\n    </>\n  );\n}\n\n/**\n * Two short arcs either side of the bell — resonance, not a progress ring.\n *\n * This was a 72%-complete circle, which at a glance is a spinner stuck at\n * three quarters. On a panel whose whole message is \"nothing is happening\",\n * that is precisely the wrong thing to draw.\n */\nfunction Chime({ phase, reduced }: PartProps) {\n  const arcs = [\n    'M92.8 37 A40 40 0 0 1 92.8 83',\n    'M27.2 83 A40 40 0 0 1 27.2 37',\n    'M102.6 30.2 A52 52 0 0 1 102.6 89.8',\n    'M17.4 89.8 A52 52 0 0 1 17.4 30.2',\n  ];\n  return (\n    <svg viewBox=\"0 0 120 120\" className=\"absolute size-[128px] overflow-visible\">\n      {arcs.map((d, index) => (\n        <motion.path\n          key={d}\n          d={d}\n          fill=\"none\"\n          strokeWidth=\"1.4\"\n          strokeLinecap=\"round\"\n          initial=\"hidden\"\n          animate={phase}\n          variants={{\n            hidden: { pathLength: 0, opacity: 0 },\n            shown: {\n              pathLength: 1,\n              opacity: index > 1 ? 0.45 : 1,\n              transition: reduced\n                ? { duration: 0 }\n                : {\n                    duration: 0.5,\n                    delay: 0.08 + Math.floor(index / 2) * 0.12,\n                    ease: EASE_OUT,\n                  },\n            },\n          }}\n          className=\"stroke-black/[0.16] dark:stroke-white/25\"\n        />\n      ))}\n    </svg>\n  );\n}\n\n/** A dot with a tail sweeping past, once. */\nfunction Comet({ phase, reduced }: PartProps) {\n  return (\n    <>\n      <span className={cn('absolute size-[136px] rounded-full border', HAIRLINE_SOFT, FADE_DOWN)} />\n      <motion.span\n        initial=\"hidden\"\n        animate={phase}\n        variants={{\n          hidden: { rotate: -40, opacity: 0 },\n          shown: {\n            rotate: 320,\n            opacity: [0, 1, 1, 0],\n            transition: reduced ? { duration: 0 } : { duration: 1.6, ease: EASE_OUT },\n          },\n        }}\n        className=\"absolute size-[136px]\"\n      >\n        <span\n          className=\"absolute left-1/2 top-0 h-[7px] w-[34px] -translate-x-full rounded-full\"\n          style={{\n            background:\n              'linear-gradient(to right, transparent, color-mix(in oklab, currentColor 18%, transparent))',\n          }}\n        />\n        <span\n          className={cn('absolute left-1/2 top-0 size-[6px] -translate-x-1/2 rounded-full', INK)}\n        />\n      </motion.span>\n    </>\n  );\n}\n\n/** Small dots thrown outward. Reserved for the states that are good news. */\nfunction Sparks({ phase, reduced }: PartProps) {\n  const angles = [18, 62, 106, 150, 194, 238, 282, 326];\n  return (\n    <>\n      {angles.map((angle, index) => (\n        <motion.span\n          key={angle}\n          initial=\"hidden\"\n          animate={phase}\n          variants={{\n            hidden: { opacity: 0, x: 0, y: 0, scale: 0.6 },\n            shown: {\n              opacity: [0, 1, 0],\n              x: Math.cos((angle * Math.PI) / 180) * 66,\n              y: Math.sin((angle * Math.PI) / 180) * 66,\n              scale: 1,\n              transition: reduced\n                ? { duration: 0 }\n                : { duration: 0.95, delay: 0.12 + index * 0.02, ease: EASE_OUT },\n            },\n          }}\n          className={cn('absolute size-[4px] rounded-full', INK)}\n        />\n      ))}\n      <span className={cn('absolute size-[104px] rounded-full border', HAIRLINE_SOFT, FADE_DOWN)} />\n    </>\n  );\n}\n\n/** A dashed dial turning a notch into place. */\nfunction Dashes({ phase, reduced }: PartProps) {\n  return (\n    <motion.span\n      initial=\"hidden\"\n      animate={phase}\n      variants={{\n        hidden: reduced ? { opacity: 0 } : { opacity: 0, rotate: -35, scale: 0.85 },\n        shown: {\n          opacity: 1,\n          rotate: 0,\n          scale: 1,\n          transition: reduced ? { duration: 0.2 } : { ...SPRING_FLUID, delay: 0.05 },\n        },\n      }}\n      className={cn('absolute size-[128px]', FADE_DOWN)}\n    >\n      <svg viewBox=\"0 0 100 100\" className=\"size-full\">\n        <circle\n          cx=\"50\"\n          cy=\"50\"\n          r=\"47\"\n          fill=\"none\"\n          strokeWidth=\"1.4\"\n          strokeDasharray=\"4 7\"\n          strokeLinecap=\"round\"\n          className=\"stroke-black/[0.16] dark:stroke-white/20\"\n        />\n      </svg>\n    </motion.span>\n  );\n}\n\n/** Four guides drawing out from the tile, the way a grid finds a slot. */\nfunction Crosshair({ phase, reduced }: PartProps) {\n  const arms = [\n    { rotate: 0, key: 'up' },\n    { rotate: 90, key: 'right' },\n    { rotate: 180, key: 'down' },\n    { rotate: 270, key: 'left' },\n  ];\n  return (\n    <>\n      {arms.map((arm, index) => (\n        <span\n          key={arm.key}\n          style={{ transform: `rotate(${arm.rotate}deg)` }}\n          className=\"absolute bottom-1/2 h-[92px] w-px origin-bottom\"\n        >\n          <motion.span\n            initial=\"hidden\"\n            animate={phase}\n            variants={{\n              hidden: { opacity: 0, scaleY: 0 },\n              shown: {\n                opacity: 1,\n                scaleY: 1,\n                transition: reduced\n                  ? { duration: 0.2 }\n                  : { ...SPRING_FLUID, delay: 0.05 + index * 0.06 },\n              },\n            }}\n            style={{ originY: 1 }}\n            className={cn(\n              'absolute inset-0 block w-px',\n              INK_SOFT,\n              '[mask-image:linear-gradient(to_top,black,transparent)]',\n            )}\n          />\n        </span>\n      ))}\n      <span className={cn('absolute size-[78px] rounded-2xl border', HAIRLINE_SOFT)} />\n    </>\n  );\n}\n\n/** A slow signal drawn left to right. */\nfunction Wave({ phase, reduced }: PartProps) {\n  return (\n    <svg viewBox=\"0 0 160 60\" className={cn('absolute h-[60px] w-[168px]', FADE_EDGES)}>\n      <motion.path\n        d=\"M0 30 Q 20 4 40 30 T 80 30 T 120 30 T 160 30\"\n        fill=\"none\"\n        strokeWidth=\"1.5\"\n        strokeLinecap=\"round\"\n        initial=\"hidden\"\n        animate={phase}\n        variants={{\n          hidden: { pathLength: 0, opacity: 0 },\n          shown: {\n            pathLength: 1,\n            opacity: 1,\n            transition: reduced ? { duration: 0 } : { duration: 1.25, ease: EASE_OUT },\n          },\n        }}\n        className=\"stroke-black/[0.14] dark:stroke-white/[0.18]\"\n      />\n    </svg>\n  );\n}\n\n/** Diagonals cutting past. For the states where something broke. */\nfunction Shards({ phase, reduced }: PartProps) {\n  const lines = [-56, -28, 0, 28, 56];\n  return (\n    <div className={cn('absolute size-[164px] overflow-hidden', FADE_EDGES)}>\n      {lines.map((offset, index) => (\n        <motion.span\n          key={offset}\n          initial=\"hidden\"\n          animate={phase}\n          variants={{\n            hidden: { opacity: 0, x: -26 },\n            shown: {\n              opacity: 1,\n              x: 0,\n              transition: reduced ? { duration: 0.2 } : { ...SPRING_FLUID, delay: 0.04 * index },\n            },\n          }}\n          style={{ left: `calc(50% + ${offset}px)` }}\n          className={cn('absolute -top-8 h-[220px] w-px rotate-[24deg]', INK_SOFT)}\n        />\n      ))}\n    </div>\n  );\n}\n\n/**\n * Ghost tiles left behind, so the glyph reads as having just arrived.\n *\n * Painted far to near and filled with the panel surface, so each ghost covers\n * the trailing edge of the one behind it; the fade is carried by the border\n * alone, because fading the whole element would make the fills transparent and\n * put every outline back on top of every other one.\n */\nfunction Trail({ phase, reduced }: PartProps) {\n  const steps: { step: number; border: string }[] = [\n    { step: 3, border: HAIRLINE_FAINT },\n    { step: 2, border: HAIRLINE_SOFT },\n    { step: 1, border: HAIRLINE },\n  ];\n  return (\n    <>\n      {steps.map(({ step, border }) => (\n        <motion.span\n          key={step}\n          initial=\"hidden\"\n          animate={phase}\n          variants={{\n            hidden: reduced ? { opacity: 0, x: -step * 24 } : { opacity: 0, x: 0 },\n            shown: {\n              opacity: 1,\n              x: -step * 24,\n              transition: reduced\n                ? { duration: 0.2 }\n                : { ...SPRING_FLUID, delay: 0.06 + (3 - step) * 0.06 },\n            },\n          }}\n          className={cn('absolute size-14 rounded-2xl border', SURFACE, border)}\n        />\n      ))}\n    </>\n  );\n}\n\n/** Four brackets closing into a frame. */\nfunction Corners({ phase, reduced }: PartProps) {\n  const corners = [\n    { className: 'left-0 top-0 border-l border-t rounded-tl-xl', x: -10, y: -10 },\n    { className: 'right-0 top-0 border-r border-t rounded-tr-xl', x: 10, y: -10 },\n    { className: 'left-0 bottom-0 border-b border-l rounded-bl-xl', x: -10, y: 10 },\n    { className: 'right-0 bottom-0 border-b border-r rounded-br-xl', x: 10, y: 10 },\n  ];\n  return (\n    <div className=\"absolute size-[132px]\">\n      {corners.map((corner, index) => (\n        <motion.span\n          key={corner.className}\n          initial=\"hidden\"\n          animate={phase}\n          variants={{\n            hidden: reduced ? { opacity: 0 } : { opacity: 0, x: corner.x, y: corner.y },\n            shown: {\n              opacity: 1,\n              x: 0,\n              y: 0,\n              transition: reduced\n                ? { duration: 0.2 }\n                : { ...SPRING_FLUID, delay: 0.05 + index * 0.05 },\n            },\n          }}\n          className={cn('absolute size-6', HAIRLINE, corner.className)}\n        />\n      ))}\n    </div>\n  );\n}\n\n/** Dots falling past and away. */\nfunction Drip({ phase, reduced }: PartProps) {\n  const drops = [-48, -22, 4, 30, 54];\n  return (\n    <div className={cn('absolute h-[160px] w-[150px] overflow-hidden', FADE_EDGES)}>\n      {/* The drops pass through and are gone; the basin is what the panel is\n          left looking at, so it is static. */}\n      <span\n        className={cn(\n          'absolute bottom-3 left-1/2 size-[112px] -translate-x-1/2 rounded-full border',\n          HAIRLINE,\n          '[mask-image:linear-gradient(to_top,black_30%,transparent_85%)]',\n        )}\n      />\n      {drops.map((offset, index) => (\n        <motion.span\n          key={offset}\n          initial=\"hidden\"\n          animate={phase}\n          variants={{\n            hidden: { opacity: 0, y: -12 },\n            shown: {\n              opacity: [0, 1, 0],\n              y: 150,\n              transition: reduced\n                ? { duration: 0 }\n                : { duration: 1.5, delay: index * 0.13, ease: EASE_IN_OUT },\n            },\n          }}\n          style={{ left: `calc(50% + ${offset}px)` }}\n          className={cn('absolute top-0 h-3 w-px rounded-full', INK)}\n        />\n      ))}\n    </div>\n  );\n}\n\n/** Nested rounded squares, stepping outward. */\nfunction Contour({ phase, reduced }: PartProps) {\n  return (\n    <>\n      {[88, 124, 160].map((size, index) => (\n        <motion.span\n          key={size}\n          initial=\"hidden\"\n          animate={phase}\n          variants={{\n            hidden: reduced ? { opacity: 0 } : { opacity: 0, scale: 0.8 },\n            shown: {\n              opacity: 1,\n              scale: 1,\n              transition: reduced\n                ? { duration: 0.2 }\n                : { ...SPRING_FLUID, delay: 0.05 + index * 0.07 },\n            },\n          }}\n          style={{ width: size, height: size, borderRadius: 18 + index * 8 }}\n          className={cn('absolute border', index === 0 ? HAIRLINE : HAIRLINE_SOFT, FADE_DOWN)}\n        />\n      ))}\n    </>\n  );\n}\n\n/**\n * Rows of dots narrowing on the way down — many in, few out.\n *\n * The version this replaces was four hairlines at different widths, which read\n * as stray rules crossing the tile rather than as anything being filtered. Dots\n * stay clear of the tile entirely and carry the idea on their own.\n */\nfunction Sieve({ phase, reduced }: PartProps) {\n  const rows = [9, 6, 3];\n  return (\n    <div className=\"absolute bottom-[calc(100%+2px)] flex flex-col items-center gap-2\">\n      {rows.map((count, row) => (\n        <div key={count} className=\"flex gap-2\">\n          {Array.from({ length: count }).map((_, index) => (\n            <motion.span\n              key={index}\n              initial=\"hidden\"\n              animate={phase}\n              variants={{\n                hidden: reduced ? { opacity: 0 } : { opacity: 0, y: -5, scale: 0.6 },\n                shown: {\n                  opacity: 1,\n                  y: 0,\n                  scale: 1,\n                  transition: reduced\n                    ? { duration: 0.2 }\n                    : {\n                        ...SPRING_SNAPPY,\n                        delay: 0.05 + row * 0.08 + Math.abs(index - (count - 1) / 2) * 0.02,\n                      },\n                },\n              }}\n              className={cn('size-[3px] rounded-full', row === rows.length - 1 ? INK : INK_SOFT)}\n            />\n          ))}\n        </div>\n      ))}\n    </div>\n  );\n}\n\n/** A cone of light from above, for the things being held back. */\nfunction Spotlight({ phase, reduced }: PartProps) {\n  return (\n    <motion.span\n      initial=\"hidden\"\n      animate={phase}\n      variants={{\n        hidden: reduced ? { opacity: 0 } : { opacity: 0, scaleY: 0.4 },\n        shown: {\n          opacity: 1,\n          scaleY: 1,\n          transition: reduced ? { duration: 0.2 } : { ...SPRING_FLUID, delay: 0.05 },\n        },\n      }}\n      style={{\n        originY: 0,\n        clipPath: 'polygon(38% 0%, 62% 0%, 100% 100%, 0% 100%)',\n      }}\n      className=\"absolute -top-[46px] h-[150px] w-[168px] bg-[linear-gradient(to_bottom,rgba(0,0,0,0.07),transparent_85%)] dark:bg-[linear-gradient(to_bottom,rgba(255,255,255,0.09),transparent_85%)]\"\n    />\n  );\n}\n\n/**\n * The one loop. A listening state is reporting that something live is happening,\n * which is the only case in this set where a repeat is information.\n */\nfunction Pulse({ reduced }: { reduced: boolean }) {\n  if (reduced) {\n    return (\n      <span className={cn('absolute size-[120px] rounded-full border', HAIRLINE, FADE_DOWN)} />\n    );\n  }\n  return (\n    <>\n      {[0, 1].map((index) => (\n        <motion.span\n          key={index}\n          initial={{ opacity: 0, scale: 0.42 }}\n          animate={{ opacity: [0, 0.75, 0], scale: [0.42, 1.15] }}\n          transition={{ duration: 2.6, repeat: Infinity, delay: index * 1.3, ease: EASE_OUT }}\n          className={cn('absolute size-[150px] rounded-full border', HAIRLINE)}\n        />\n      ))}\n      <span className={cn('absolute size-[92px] rounded-full border', HAIRLINE_SOFT, FADE_DOWN)} />\n    </>\n  );\n}\n\n/* ── Medallion ────────────────────────────────────────────────────────────── */\n\nexport interface EmptyMedallionProps {\n  /** An Iconly Bold glyph from this file. Sized by the medallion, not by you. */\n  icon: React.ReactNode;\n  tone?: EmptyTone;\n  size?: 'sm' | 'md' | 'lg';\n  /**\n   * What happens behind the tile. Each block in this category picks a different\n   * one; `none` is right whenever the panel already has a background of its own\n   * — ghost table rows, chart gridlines, a blurred preview — because a second\n   * ambient animation on top of those is noise.\n   */\n  backdrop?: MedallionBackdrop;\n  /** Sits on the tile's lower-right corner: a badge, a count, a second glyph. */\n  badge?: React.ReactNode;\n  className?: string;\n}\n\nconst TILE: Record<'sm' | 'md' | 'lg', string> = {\n  sm: 'size-10 rounded-xl [&_svg]:size-[18px]',\n  md: 'size-14 rounded-2xl [&_svg]:size-6',\n  lg: 'size-16 rounded-3xl [&_svg]:size-7',\n};\n\n/**\n * The glyph, raised off the page: a backdrop that plays once behind it, and a\n * tile that arrives blurred and resolves — which reads as focus rather than as\n * movement.\n */\nexport function EmptyMedallion({\n  icon,\n  tone = 'neutral',\n  size = 'md',\n  backdrop = 'rings',\n  badge,\n  className,\n}: EmptyMedallionProps) {\n  const reduced = useReducedMotion();\n\n  return (\n    <div className={cn('relative grid place-items-center', className)}>\n      <MedallionBackdropLayer kind={backdrop} />\n\n      <motion.div\n        initial={reduced ? { opacity: 0 } : { opacity: 0, scale: 0.84, filter: 'blur(6px)' }}\n        whileInView={reduced ? { opacity: 1 } : { opacity: 1, scale: 1, filter: 'blur(0px)' }}\n        viewport={VIEWPORT}\n        transition={reduced ? { duration: 0.15 } : SPRING_ENTRANCE}\n        className={cn(\n          /* Opaque in both themes. A translucent tile lets whatever the backdrop\n             is doing show straight through the glyph's own surface, which is how\n             the stack behind it ended up drawing lines across the folder. */\n          'relative grid place-items-center border border-black/[0.07] bg-white',\n          'shadow-[0_1px_2px_rgba(0,0,0,0.05),0_14px_30px_-18px_rgba(0,0,0,0.5)]',\n          'dark:border-white/[0.11] dark:bg-[#151515] dark:shadow-[0_1px_2px_rgba(0,0,0,0.4)]',\n          TILE[size],\n          TONE_GLYPH[tone],\n        )}\n      >\n        {icon}\n        {badge && (\n          <span className=\"absolute -bottom-1.5 -right-1.5 flex items-center justify-center rounded-full bg-white p-px dark:bg-neutral-950\">\n            {badge}\n          </span>\n        )}\n      </motion.div>\n    </div>\n  );\n}\n\n/* ── Text anatomy ─────────────────────────────────────────────────────────── */\n\nexport interface EmptyStateProps {\n  icon?: React.ReactNode;\n  tone?: EmptyTone;\n  medallionSize?: 'sm' | 'md' | 'lg';\n  /** The block's own backdrop. See EmptyMedallionProps. */\n  backdrop?: MedallionBackdrop;\n  badge?: React.ReactNode;\n  eyebrow?: string;\n  title: string;\n  description?: React.ReactNode;\n  /** Anything between the copy and the actions: chips, a field, a preview. */\n  children?: React.ReactNode;\n  actions?: React.ReactNode;\n  /** The quiet line under the actions — a shortcut, a doc link, a limit. */\n  footnote?: React.ReactNode;\n  align?: 'center' | 'start';\n  className?: string;\n}\n\n/**\n * Title, one line of copy, then the way out. The order never changes, because\n * the reader's question never changes: what is this, why is it empty, what now.\n */\nexport function EmptyState({\n  icon,\n  tone = 'neutral',\n  medallionSize = 'md',\n  backdrop = 'rings',\n  badge,\n  eyebrow,\n  title,\n  description,\n  children,\n  actions,\n  footnote,\n  align = 'center',\n  className,\n}: EmptyStateProps) {\n  const { stack, item } = useReveal();\n  const centred = align === 'center';\n\n  return (\n    <motion.div\n      variants={stack}\n      initial=\"hidden\"\n      whileInView=\"shown\"\n      viewport={VIEWPORT}\n      className={cn(\n        EMPTY_FONT,\n        'flex w-full flex-col',\n        centred ? 'items-center text-center' : 'items-start text-left',\n        className,\n      )}\n    >\n      {icon && (\n        <motion.div variants={item} className=\"mb-5\">\n          <EmptyMedallion\n            icon={icon}\n            tone={tone}\n            size={medallionSize}\n            backdrop={backdrop}\n            badge={badge}\n          />\n        </motion.div>\n      )}\n\n      {eyebrow && (\n        <motion.p\n          variants={item}\n          className=\"mb-2 font-mono text-[12px] font-medium uppercase leading-[1.4] tracking-[0.12em] text-neutral-600 dark:text-neutral-400\"\n        >\n          {eyebrow}\n        </motion.p>\n      )}\n\n      <motion.h3\n        variants={item}\n        className=\"text-balance text-[17px] font-semibold leading-[1.3] tracking-[-0.014em] text-neutral-900 dark:text-neutral-50\"\n      >\n        {title}\n      </motion.h3>\n\n      {description && (\n        <motion.p\n          variants={item}\n          className={cn(\n            /* 46ch keeps the measure inside the 60–75 character band once the\n               panel's own padding is taken off. */\n            'mt-2.5 max-w-[46ch] text-pretty text-[13.5px] leading-[1.62] tracking-[-0.003em]',\n            'text-neutral-600 dark:text-neutral-400',\n            centred && 'mx-auto',\n          )}\n        >\n          {description}\n        </motion.p>\n      )}\n\n      {children && (\n        <motion.div variants={item} className={cn('mt-5 w-full', centred && 'flex justify-center')}>\n          {children}\n        </motion.div>\n      )}\n\n      {actions && (\n        <motion.div\n          variants={item}\n          className={cn(\n            'mt-6 flex flex-wrap items-center gap-2',\n            centred ? 'justify-center' : 'justify-start',\n          )}\n        >\n          {actions}\n        </motion.div>\n      )}\n\n      {footnote && (\n        <motion.div\n          variants={item}\n          className=\"mt-5 text-pretty text-[13px] leading-[1.55] tracking-[-0.002em] text-neutral-600 dark:text-neutral-400\"\n        >\n          {footnote}\n        </motion.div>\n      )}\n    </motion.div>\n  );\n}\n\n/* ── Actions ──────────────────────────────────────────────────────────────── */\n\nconst MotionButton = motion.create(Button);\n\n/* The drag and animation handlers are omitted because motion redefines every\n   one of them with its own gesture signature, and a spread of React's DOM\n   handlers onto a motion component is a type error, not a merge. */\nexport interface EmptyActionProps extends Omit<\n  React.ComponentProps<typeof Button>,\n  | 'onDrag'\n  | 'onDragStart'\n  | 'onDragEnd'\n  | 'onDragEnter'\n  | 'onDragExit'\n  | 'onDragLeave'\n  | 'onDragOver'\n  | 'onDrop'\n  | 'onAnimationStart'\n  | 'onAnimationEnd'\n  | 'onAnimationIteration'\n> {\n  emphasis?: 'primary' | 'secondary' | 'quiet';\n  /** Leading glyph. Sized to 15px so it sits on the label's cap height. */\n  icon?: React.ReactNode;\n  /** Trailing glyph, for actions that lead somewhere. */\n  trailing?: React.ReactNode;\n}\n\n/**\n * Every emphasis states its hover *text* colour as well as its hover fill.\n *\n * These ride on shadcn's `ghost` variant, which carries\n * `hover:text-accent-foreground`. tailwind-merge keeps that class, because a\n * plain `text-*` and a `hover:text-*` are not the same utility — so a button\n * that only set `text-neutral-50` kept its white label at rest and repainted it\n * near-black the moment a pointer touched it. Black on black. Anything layered\n * over a variant has to close every state that variant opens.\n */\nconst EMPHASIS: Record<'primary' | 'secondary' | 'quiet', string> = {\n  primary: cn(\n    'bg-neutral-900 text-neutral-50 hover:bg-neutral-800 hover:text-white',\n    'dark:bg-neutral-100 dark:text-neutral-900 dark:hover:bg-white dark:hover:text-neutral-900',\n  ),\n  secondary: cn(\n    'border border-black/[0.09] bg-white text-neutral-800 hover:bg-neutral-50 hover:text-neutral-900',\n    'dark:border-white/[0.12] dark:bg-white/[0.04] dark:text-neutral-100 dark:hover:bg-white/[0.08] dark:hover:text-white',\n  ),\n  quiet: cn(\n    'bg-transparent text-neutral-500 hover:bg-black/[0.04] hover:text-neutral-900',\n    'dark:text-neutral-400 dark:hover:bg-white/[0.06] dark:hover:text-neutral-100',\n  ),\n};\n\n/**\n * Press physics on every action. `whileTap` fires on pointerdown, so the button\n * answers before the handler does — the difference between a button that works\n * and a button that feels like it works.\n */\nexport function EmptyAction({\n  emphasis = 'primary',\n  icon,\n  trailing,\n  className,\n  children,\n  ...props\n}: EmptyActionProps) {\n  const reduced = useReducedMotion();\n\n  return (\n    <MotionButton\n      variant=\"ghost\"\n      /* Press only. `whileHover` has no (hover: hover) gate, so on a phone the\n         scale fires on touch and the button is left sitting at 1.02 after the\n         finger lifts. The hover state it used to carry is a colour change now,\n         which Tailwind v4 already gates for us. */\n      whileTap={reduced ? undefined : { scale: 0.96 }}\n      transition={SPRING_TACTILE}\n      className={cn(\n        'h-9 gap-1.5 rounded-xl px-3.5 text-[14px] font-medium tracking-[-0.006em] shadow-none',\n        'focus-visible:ring-1 focus-visible:ring-neutral-950 focus-visible:ring-offset-0 dark:focus-visible:ring-neutral-300',\n        '[&_svg]:size-[15px]',\n        EMPHASIS[emphasis],\n        className,\n      )}\n      {...props}\n    >\n      {icon}\n      {children}\n      {trailing}\n    </MotionButton>\n  );\n}\n\n/* ── Surfaces & parts ─────────────────────────────────────────────────────── */\n\nexport interface EmptyPanelProps {\n  /** The panel's own title — the screen the reader is standing on. */\n  title?: React.ReactNode;\n  meta?: React.ReactNode;\n  toolbar?: React.ReactNode;\n  children: React.ReactNode;\n  className?: string;\n  bodyClassName?: string;\n}\n\n/** The product chrome around the message. An empty state with no surface under it reads as an error page. */\nexport function EmptyPanel({\n  title,\n  meta,\n  toolbar,\n  children,\n  className,\n  bodyClassName,\n}: EmptyPanelProps) {\n  return (\n    <section\n      className={cn(\n        EMPTY_FONT,\n        'w-full overflow-hidden rounded-2xl border border-black/[0.08] bg-white',\n        'shadow-[0_1px_2px_rgba(0,0,0,0.04),0_18px_40px_-28px_rgba(0,0,0,0.35)]',\n        'dark:border-white/[0.09] dark:bg-neutral-950 dark:shadow-none',\n        className,\n      )}\n    >\n      {(title || toolbar) && (\n        <header className=\"flex min-w-0 items-center gap-3 border-b border-black/[0.06] px-4 py-3 dark:border-white/[0.07]\">\n          <div className=\"min-w-0 flex-1\">\n            {title && (\n              <p className=\"truncate text-[14px] font-semibold leading-[1.35] tracking-[-0.008em] text-neutral-800 dark:text-neutral-100\">\n                {title}\n              </p>\n            )}\n            {/* Inter, not mono. This line is a caption — a count, a date, a\n                path — not code, and mono at caption size reads as a terminal\n                dropped into the corner of a product panel. Tabular figures keep\n                the numbers from shifting when they change. */}\n            {meta && (\n              <p className=\"mt-0.5 truncate text-[12.5px] leading-[1.4] tracking-[-0.002em] tabular-nums text-neutral-600 dark:text-neutral-400\">\n                {meta}\n              </p>\n            )}\n          </div>\n          {toolbar && <div className=\"flex shrink-0 items-center gap-1.5\">{toolbar}</div>}\n        </header>\n      )}\n      <div className={cn('px-5 py-10 sm:px-8 sm:py-14', bodyClassName)}>{children}</div>\n    </section>\n  );\n}\n\nexport interface GhostRowsProps {\n  rows?: number;\n  /** Where the fade runs. `up` puts solid rows at the bottom of the stack. */\n  fade?: 'down' | 'up';\n  className?: string;\n}\n\n/** The list that would have been here, drawn faint so the layout keeps its shape. */\nexport function GhostRows({ rows = 4, fade = 'down', className }: GhostRowsProps) {\n  const reduced = useReducedMotion();\n\n  return (\n    <div aria-hidden className={cn('flex w-full flex-col gap-2', className)}>\n      {Array.from({ length: rows }).map((_, index) => {\n        const depth = fade === 'down' ? index : rows - 1 - index;\n        return (\n          <motion.div\n            key={index}\n            initial={reduced ? { opacity: 0 } : { opacity: 0, y: 6 }}\n            whileInView={{ opacity: Math.max(0.08, 0.5 - depth * 0.11), y: 0 }}\n            viewport={VIEWPORT}\n            transition={reduced ? { duration: 0.2 } : { ...SPRING_FLUID, delay: 0.05 * index }}\n            className=\"flex items-center gap-3 rounded-xl border border-black/[0.06] px-3 py-2.5 dark:border-white/[0.08]\"\n          >\n            <span className=\"size-6 shrink-0 rounded-full bg-neutral-200 dark:bg-white/[0.12]\" />\n            <span className=\"h-2 flex-1 rounded-full bg-neutral-200 dark:bg-white/[0.12]\" />\n            <span className=\"h-2 w-10 rounded-full bg-neutral-200/70 dark:bg-white/[0.08]\" />\n          </motion.div>\n        );\n      })}\n    </div>\n  );\n}\n\nexport function Kbd({ children, className }: { children: React.ReactNode; className?: string }) {\n  return (\n    <kbd\n      className={cn(\n        'inline-flex h-5 min-w-[20px] items-center justify-center rounded-[6px] border border-black/[0.09] bg-white px-1.5',\n        'font-mono text-[12px] font-medium text-neutral-500',\n        'dark:border-white/[0.14] dark:bg-white/[0.06] dark:text-neutral-300',\n        className,\n      )}\n    >\n      {children}\n    </kbd>\n  );\n}\n\nexport function EmptyHint({\n  keys,\n  children,\n  className,\n}: {\n  keys?: string[];\n  children: React.ReactNode;\n  className?: string;\n}) {\n  return (\n    <span className={cn('inline-flex items-center gap-1.5 text-[13px]', className)}>\n      {keys?.map((key) => (\n        <Kbd key={key}>{key}</Kbd>\n      ))}\n      <span className=\"text-neutral-600 dark:text-neutral-400\">{children}</span>\n    </span>\n  );\n}\n\nexport interface EmptyMeterProps {\n  /** 0 to 1. Values above 1 are clamped, because a full bar is the message. */\n  value: number;\n  tone?: EmptyTone;\n  className?: string;\n}\n\nconst TONE_FILL: Record<EmptyTone, string> = {\n  neutral: 'bg-neutral-800 dark:bg-neutral-200',\n  positive: 'bg-emerald-500',\n  caution: 'bg-amber-500',\n  critical: 'bg-red-500',\n};\n\nexport function EmptyMeter({ value, tone = 'neutral', className }: EmptyMeterProps) {\n  const reduced = useReducedMotion();\n  const clamped = Math.min(1, Math.max(0, value));\n\n  return (\n    <div\n      role=\"progressbar\"\n      aria-valuenow={Math.round(clamped * 100)}\n      aria-valuemin={0}\n      aria-valuemax={100}\n      className={cn(\n        'h-1.5 w-full overflow-hidden rounded-full bg-black/[0.07] dark:bg-white/[0.1]',\n        className,\n      )}\n    >\n      <motion.div\n        initial={{ scaleX: reduced ? clamped : 0 }}\n        whileInView={{ scaleX: clamped }}\n        viewport={VIEWPORT}\n        transition={reduced ? { duration: 0 } : { ...SPRING_FLUID, delay: 0.18 }}\n        style={{ originX: 0 }}\n        className={cn('h-full rounded-full', TONE_FILL[tone])}\n      />\n    </div>\n  );\n}\n\nexport function StatusDot({\n  tone = 'neutral',\n  className,\n}: {\n  tone?: EmptyTone;\n  className?: string;\n}) {\n  return (\n    <span\n      aria-hidden\n      className={cn('inline-block size-1.5 shrink-0 rounded-full', TONE_FILL[tone], className)}\n    />\n  );\n}\n\n/** A dotted target the reader can drop something on, or click to start one. */\nexport function DropTarget({\n  active,\n  children,\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement> & { active?: boolean }) {\n  return (\n    <div\n      className={cn(\n        'rounded-2xl border border-dashed transition-colors duration-200',\n        active\n          ? 'border-neutral-400 bg-black/[0.03] dark:border-neutral-500 dark:bg-white/[0.05]'\n          : 'border-black/[0.14] dark:border-white/[0.16]',\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </div>\n  );\n}\n",
      "type": "registry:block",
      "target": "components/spectrumui/blocks/empty-states/empty-state-kit.tsx"
    }
  ],
  "type": "registry:block"
}
