{
  "motion/collapse/README.md": "# Collapse and disclosure chevrons\n\nOpening or closing a group animates its height through grid rows (1fr to 0fr, never `height`) with a fade, and its arrow turns with it. The inner padding and negative margin keep focus rings of edge items from being clipped.\n\nOpen [`demo.html`](demo.html) in a browser (it links `../../tokens/tokens.css`) and use \"Toggle dark\" to check both themes.\n\n## Files\n\n| File | What it is |\n|---|---|\n| `collapse.css` | The `.sw-collapse` grid-rows animation, verbatim, plus the chevron rotation rules the app writes as Tailwind classes (`.sw-chevron*` are the CSS equivalents). |\n| `demo.html` | A standalone page with the live animation in light and dark, replayable. |\n| `react/` | Copies of the real app files (see `react/README.md` for the imports to replace). |\n| `previews/` | Animated WebP and GIF previews, light and dark (regenerated by `scripts/make-previews.mjs`). |\n\n## Where the app uses it\n\n- `components/ui/Collapse.tsx` (used by `components/shell/SidebarSection.tsx` \"More\" and `components/shell/Disclosure.tsx`).\n- Chevrons: `SidebarSection.tsx` (180 deg), `components/ui/CollapsibleSection.tsx` and `components/card-designer/InspectorSection.tsx` (90 deg), `features/hub/landing/FaqSection.tsx` (plus to 45 deg), `features/docs/docs.css` (`sw-doc-nav-chevron`).\n\n## Why\n\nOpening a group keeps its neighbours in view and shows what moved; the turning arrow reports the state before the content finishes moving. Closing is faster than opening.\n\n## Timing, easing and distance\n\n| Part | Value |\n|---|---|\n| Open | grid rows 0fr to 1fr and opacity, `--sw-dur-base` 180 ms, `--sw-ease-standard` |\n| Close | to 0fr and opacity 0, `--sw-dur-fast` 120 ms, `--sw-ease-exit`, `forwards` |\n| Chevrons | `transform` 180 ms (sidebar, sections), 200 ms (FAQ plus), 160 ms (docs nav) |\n\n## Trigger\n\nThe `open` prop or `aria-expanded toggles `data-state=\"open|closed\"`; `usePresence` keeps the region mounted for the 120 ms exit and makes it inert, so collapsed content is never reachable by Tab.\n\n## Reduced motion\n\nThe collapse animation is inside `no-preference`, so the region opens and closes at once; chevrons jump to their final angle.\n\n## Dark theme\n\nNone: colour-neutral.\n\n## Do and don't\n\n- Do animate grid rows (1fr to 0fr), not `height`.\n- Do keep `aria-controls` pointing at a wrapper that always exists.\n- Do make closed content unreachable (unmount, or `inert` while closing).\n- Do not stagger children of an opening group.\n\n## Copy-paste usage\n\n```html\n<button aria-expanded=\"true\" aria-controls=\"more\">More <svg class=\"sw-chevron sw-chevron-flip\">…</svg></button>\n<div id=\"more\" class=\"sw-collapse\" data-state=\"open\"><div class=\"sw-collapse-inner\">…items…</div></div>\n```\n\nTo close set `data-state=\"closed\"` (and `inert`); hide or remove it on `animationend`.\n\nEvery other animation in the product, and the rules behind them: [`../../docs/motion.md`](../../docs/motion.md). Index of the kit: [`../README.md`](../README.md).\n",
  "motion/collapse/react/README.md": "# React copies\n\nUnmodified copies of the app files for this family (SpaceWorks 0.12.0; paths are relative to `frontend/src/`). They are reference code: they import app modules, Lingui (`t`) and Tailwind utility classes, so swap those for your own, or use the framework-free CSS one folder up, which is all the motion needs.\n\n| File | App path | Imports to provide or replace |\n|---|---|---|\n| `Collapse.tsx` | `components/ui/Collapse.tsx` | `../../lib/motion` |\n| `CollapsibleSection.tsx` | `components/ui/CollapsibleSection.tsx` | `@lingui/core/macro`, `./Collapse` |\n| `SidebarSection.tsx` | `components/shell/SidebarSection.tsx` | `@lingui/core/macro`, `lucide-react`, `../ui/Collapse`, `../../lib/safeStorage`, `./SidebarItem`, `./types` |\n| `Disclosure.tsx` | `components/shell/Disclosure.tsx` | `../../lib/motion`, `./types` |\n| `FaqSection.tsx` | `features/hub/landing/FaqSection.tsx` | `@lingui/core/macro`, `lucide-react`, `../hubLinks` |\n| `usePresence.ts` | `lib/motion/usePresence.ts` | `./motion` |\n| `motion.ts` | `lib/motion/motion.ts` | none |\n| `motion.css` | `styles/motion.css` | none |\n",
  "motion/delight/README.md": "# Delight: logo flaps, Patch, card tilt\n\nThe optional playful layer, shown only when interface effects (\"delight\") are on and motion is allowed, never on kiosk, scanner, checkout or payment paths: the logo's four flaps springing on hover or click, the 404 pet Patch (walk, nap, paw prints, a still moment that trots), a small companion walk, a glyph tilt and the member card that tilts with the pointer and flips.\n\nOpen [`demo.html`](demo.html) in a browser (it links `../../tokens/tokens.css`) and use \"Toggle dark\" to check both themes.\n\n## Files\n\n| File | What it is |\n|---|---|\n| `delight.css` | Logo flap spring and pop (`logoUnfold.css`), Patch (`patch.css`), companion walk and glyph tilt (`delight.css`) verbatim, plus the holographic card transition. |\n| `demo.html` | A standalone page with the live animation in light and dark, replayable. |\n| `react/` | Copies of the real app files (see `react/README.md` for the imports to replace). |\n| `previews/` | Animated WebP and GIF previews, light and dark (regenerated by `scripts/make-previews.mjs`). |\n\n## Where the app uses it\n\n- Logo flaps: `components/delight/LogoDelight.tsx`, `logoUnfold.css` (the real logo's `data-logo` paths; the demo uses a four-square stand-in).\n- Patch: `NotFoundPatch.tsx` (the 404 pet, walks with `requestAnimationFrame`), `PatchMoment.tsx` (a still frame that trots when scrolled into view), `patch.css`.\n- Companion walk, glyph tilt: `components/delight/Companion.tsx`, `delight.css`.\n- Card tilt and flip: `features/members/wallet/HolographicCard.tsx`.\n\n## Why\n\nSmall human moments that make a workshop tool feel friendly, kept out of staff chrome and every flow where speed or trust matters. They carry no information; removing them loses nothing.\n\n## Timing, easing and distance\n\n| Animation | Value |\n|---|---|\n| Flap hover | 460 ms `cubic-bezier(0.34,1.56,0.64,1)`; rotate 7, 7, 5, 5 deg |\n| Flap pop (click) | 560 ms, same curve, overshoot 13, 13, 9, 9 deg at 40 % |\n| Patch trot | 960 ms `steps(8)` x 2, then rests on frame 0 |\n| Patch \"z\" | 2400 ms ease-in-out, loops (decorative) |\n| Paw print | 1600 ms ease-out fade |\n| Companion walk | 4 s ease-in-out, infinite, 16 px |\n| Glyph tilt | 120 ms, -3 deg |\n| Card tilt / flip | 650 ms `cubic-bezier(0.32,0.72,0,1)`, rotate up to 9 deg, perspective 1200 px |\n\n## Trigger\n\nHover, focus-visible, click or pointer move, only with `data-sw-delight` on `<html>` and (for the pointer ones) a hover-capable device; the card tilts on pointer move (not touch) and device orientation (coarse pointer, permission asked).\n\n## Reduced motion\n\nNot mounted or `animation: none`: the still mark, the still Patch frame and a flat card with an instant flip.\n\n## Dark theme\n\nPatch uses the `-dark` sprite sheets (never recoloured or mirrored by hand); the card and flaps read from tokens.\n\n## Do and don't\n\n- Do gate it on interface effects and motion allowed.\n- Do keep Patch and the flaps rotation-only (never recolour, stretch or skew).\n- Do not loop outside the two decorative exceptions (the \"z\" and the companion walk).\n- Do not use it in staff chrome, kiosk, scanner, checkout or payment flows.\n\n## Copy-paste usage\n\n```html\n<link rel=\"stylesheet\" href=\"delight.css\">\n<html data-sw-delight> … <a data-logo href=\"#\"><svg>…<path class=\"sw-logo-flap sw-logo-flap-tl\"/>…</svg></a>\n```\n\nSee `demo.html` for the flaps, the \"z\", the companion walk and the tilting card.\n\nEvery other animation in the product, and the rules behind them: [`../../docs/motion.md`](../../docs/motion.md). Index of the kit: [`../README.md`](../README.md).\n",
  "motion/delight/react/README.md": "# React copies\n\nUnmodified copies of the app files for this family (SpaceWorks 0.12.0; paths are relative to `frontend/src/`). They are reference code: they import app modules, Lingui (`t`) and Tailwind utility classes, so swap those for your own, or use the framework-free CSS one folder up, which is all the motion needs.\n\n| File | App path | Imports to provide or replace |\n|---|---|---|\n| `LogoDelight.tsx` | `components/delight/LogoDelight.tsx` | `./DelightProvider` |\n| `logoUnfold.css` | `components/delight/logoUnfold.css` | none |\n| `NotFoundPatch.tsx` | `components/delight/NotFoundPatch.tsx` | `lucide-react`, `../../lib/theme`, `./DelightProvider` |\n| `PatchMoment.tsx` | `components/delight/PatchMoment.tsx` | `../../lib/theme`, `./NotFoundPatch`, `./DelightProvider` |\n| `patch.css` | `components/delight/patch.css` | none |\n| `Companion.tsx` | `components/delight/Companion.tsx` | `@lingui/core/macro`, `./Artwork`, `./DelightProvider` |\n| `HolographicCard.tsx` | `features/members/wallet/HolographicCard.tsx` | `@lingui/core/macro`, `../../../components/delight/DelightProvider`, `./api`, `./CardFaces` |\n",
  "motion/feedback/README.md": "# Feedback: stamp, result, confetti\n\nHow the product confirms something worked. A valid card check lands with one stamp; a result panel arrives with a small scale-in; a pass tears in from above; the workshop logbook opens like a cover; a completed return fires one confetti burst (only with interface effects on). Errors do not bounce or shake: they appear.\n\nOpen [`demo.html`](demo.html) in a browser (it links `../../tokens/tokens.css`) and use \"Toggle dark\" to check both themes.\n\n## Files\n\n| File | What it is |\n|---|---|\n| `feedback.css` | `sw-verify-stamp` (card verify), `sw-confetti-*` (return burst), `sw-result-arrive`, `sw-pass-tear`, `sw-logbook-open`, copied unchanged. |\n| `demo.html` | A standalone page with the live animation in light and dark, replayable. |\n| `react/` | Copies of the real app files (see `react/README.md` for the imports to replace). |\n| `previews/` | Animated WebP and GIF previews, light and dark (regenerated by `scripts/make-previews.mjs`). |\n\n## Where the app uses it\n\n- Stamp: `features/cards/CardVerifyPage.tsx`, `cardVerify.css` (valid card check).\n- Confetti: `components/delight/ReturnConfetti.tsx` inside `SuccessCard.tsx` after a return is confirmed.\n- Result, pass, logbook: `components/delight/delight.css` on `ResultPanel` / `PassState.tsx` / `WorkshopLogbook.tsx`.\n- The primary success feedback everywhere else is a status toast (see toast).\n\n## Why\n\nA confirmed result should feel confirmed, once, without delaying anyone. The stamp echoes a physical handover; the confetti is a rare celebration of a completed return. None of it carries information the text does not already carry.\n\n## Timing, easing and distance\n\n| Animation | Duration | Easing |\n|---|---|---|\n| `sw-verify-stamp` | 420 ms, once | `cubic-bezier(0.32,0.72,0,1)`; scale 1.35 to 0.96 to 1, rotate -8 deg to 1 deg to 0 |\n| `sw-confetti-burst` | 1100 ms, per piece delay 0, 30, 60, 90 ms | `cubic-bezier(0.2,0.7,0.3,1)`; 16 pieces in a fixed fan |\n| `sw-result-arrive` | 360 ms | `ease-out`; scale .96 to 1 |\n| `sw-pass-tear` | 360 ms | `ease-out`; translateY -8 px to 0 |\n| `sw-logbook-open` | 360 ms | `ease-out`; rotateY -8 deg to 0 (perspective 600 px) |\n\n## Trigger\n\nMount of the result. Confetti is gated: interface effects on, motion allowed, and never on kiosk, scanner, checkout or payment paths. The stamp is also gated by effects on.\n\n## Reduced motion\n\nEverything is off: the stamp is inside `no-preference`, the delight classes set `animation: none`, confetti is `display: none`. The text and colour of the result carry the meaning.\n\n## Dark theme\n\nConfetti colours are brand tokens (`--color-accent`, `--color-emphasis`, `--color-success`, `--color-warn`, `--color-info`); on a citron or yellow panel two of them swap for the panel's ink so pieces never vanish. The stamp follows the result colours.\n\n## Do and don't\n\n- Do keep it once and under half a second (the burst is the one longer piece).\n- Do make it decorative and `aria-hidden`.\n- Do not use confetti in staff, kiosk, scanner, checkout or payment flows.\n- Do not animate validation errors (they appear, they do not bounce).\n- Do not add sound.\n\n## Copy-paste usage\n\n```html\n<link rel=\"stylesheet\" href=\"../../tokens/tokens.css\"><link rel=\"stylesheet\" href=\"feedback.css\">\n<span class=\"sw-verify-stamp\">Verified</span>\n<div class=\"sw-delight-result\">…result…</div>\n```\n\nConfetti: `demo.html` builds the 16 pieces (`.sw-confetti-piece` with `--dx`, `--dy`, `--spin`, `--piece`) exactly as `ReturnConfetti.tsx` does.\n\nEvery other animation in the product, and the rules behind them: [`../../docs/motion.md`](../../docs/motion.md). Index of the kit: [`../README.md`](../README.md).\n",
  "motion/feedback/react/README.md": "# React copies\n\nUnmodified copies of the app files for this family (SpaceWorks 0.12.0; paths are relative to `frontend/src/`). They are reference code: they import app modules, Lingui (`t`) and Tailwind utility classes, so swap those for your own, or use the framework-free CSS one folder up, which is all the motion needs.\n\n| File | App path | Imports to provide or replace |\n|---|---|---|\n| `ReturnConfetti.tsx` | `components/delight/ReturnConfetti.tsx` | `./DelightProvider` |\n| `confetti.css` | `components/delight/confetti.css` | none |\n| `delight.css` | `components/delight/delight.css` | none |\n| `cardVerify.css` | `features/cards/cardVerify.css` | none |\n| `SuccessCard.tsx` | `components/delight/SuccessCard.tsx` | `@lingui/core/macro`, `lucide-react`, `./DelightProvider`, `./doorSounds`, `./Artwork` |\n| `PassState.tsx` | `components/delight/PassState.tsx` | `../../lib/format`, `lucide-react`, `./DelightProvider` |\n| `WorkshopLogbook.tsx` | `components/delight/WorkshopLogbook.tsx` | `@lingui/core/macro`, `./DelightProvider` |\n",
  "motion/hover-press/README.md": "# Hover and press\n\nThe rule for pointer feedback on product chrome: hover and active change colour (120 ms) and nothing else; pressing a button sinks it to 0.98. Cards, rows, tabs and links follow the colour-only rule. The Space Works mark's box-open is the one deliberate exception (see box-open).\n\nOpen [`demo.html`](demo.html) in a browser (it links `../../tokens/tokens.css`) and use \"Toggle dark\" to check both themes.\n\n## Files\n\n| File | What it is |\n|---|---|\n| `hover-press.css` | The button transition and press rule verbatim from `styles/motion.css`, plus the colour-only hover classes the app writes per component (`.sw-hover`, `.sw-card-hover`, `.sw-row-hover`, `.sw-link-hover`) and a small example skin. |\n| `demo.html` | A standalone page with the live animation in light and dark, replayable. |\n| `react/` | Copies of the real app files (see `react/README.md` for the imports to replace). |\n| `previews/` | Animated WebP and GIF previews, light and dark (regenerated by `scripts/make-previews.mjs`). |\n\n## Where the app uses it\n\n- Buttons and tabs: every `desk-button*`, `desk-tab` and `desk-nav-item` (`styles/motion.css`), sidebar items (`components/shell/SidebarItem.tsx` with `motion-safe:active:scale`).\n- Cards, tabs, rows and links: `features/landing/SpaceCard.tsx`, `features/hub/hub.css`, `features/hub/featuresPage/features.css`, `features/docs/docs.css` (nav links, pager, TOC), `features/discover/spaces/spaces.css`, `components/layouts/SettingsNav.tsx`, `features/staff/SettingsSectionNav.tsx`.\n- Hover opacity: `features/staff/eventProgramme/AnalyticsColumns.tsx` (bars to 80 %).\n\n## Why\n\nThe control answers the pointer without moving the page: colour tells you what is under the cursor, the 0.98 sink tells you the press registered. Because nothing translates, scales or lifts on hover, dense staff screens never jitter and layouts never shift.\n\n## Timing, easing and distance\n\n| Part | Value |\n|---|---|\n| Colour, background, border | `--sw-dur-fast` 120 ms, `--sw-ease-standard` |\n| Press | `transform: scale(0.98)` on `:active` (not disabled), same 120 ms |\n| Focus ring | untouched, never animated |\n\n## Trigger\n\nHover, `:active`. Press only runs under `prefers-reduced-motion: no-preference`.\n\n## Reduced motion\n\nColour still changes (the duration is 0): the state is shown, only the easing is removed. The press sink is not applied.\n\n## Dark theme\n\nHover fills are the dark-family tokens (`--color-hover`, `--color-accent-hover`); the rule is the same.\n\n## Do and don't\n\n- Do change colour only on hover.\n- Do keep focus rings out of the transition.\n- Do not translate, scale, lift or add a shadow on hover.\n- Do not animate on touch beyond the colour of the pressed state.\n\n## Copy-paste usage\n\n```html\n<link rel=\"stylesheet\" href=\"../../tokens/tokens.css\"><link rel=\"stylesheet\" href=\"hover-press.css\">\n<button class=\"desk-button-primary\">Save</button>\n<a class=\"sw-link-hover sw-hover\" href=\"#\">Read more</a>\n```\n\nEvery other animation in the product, and the rules behind them: [`../../docs/motion.md`](../../docs/motion.md). Index of the kit: [`../README.md`](../README.md).\n",
  "motion/hover-press/react/README.md": "# React copies\n\nUnmodified copies of the app files for this family (SpaceWorks 0.12.0; paths are relative to `frontend/src/`). They are reference code: they import app modules, Lingui (`t`) and Tailwind utility classes, so swap those for your own, or use the framework-free CSS one folder up, which is all the motion needs.\n\n| File | App path | Imports to provide or replace |\n|---|---|---|\n| `motion.css` | `styles/motion.css` | none |\n| `SidebarItem.tsx` | `components/shell/SidebarItem.tsx` | `@lingui/core/macro`, `../../lib/motion`, `./types` |\n| `SpaceCard.tsx` | `features/landing/SpaceCard.tsx` | `@lingui/core/macro`, `../../components/BrandFacets`, `../../components/MakerspaceBrand`, `../../components/MakerspaceMapLink`, `../../components/ui`, `../../components/ui/tones`, `../../types/inventory`, `../../components/LogoLoader` |\n",
  "motion/kiosk-display/README.md": "# Kiosk and display screens\n\nThe rules for screens that stay on all day: the space display (a rotating slide show on a Raspberry Pi), the kiosk and the stats TV. Slides change with one opacity fade; the distance tokens are zeroed so every shared animation (route, dialog, drawer, menu, toast) becomes a fade. Nothing loops and nothing keeps repainting while idle.\n\nOpen [`demo.html`](demo.html) in a browser (it links `../../tokens/tokens.css`) and use \"Toggle dark\" to check both themes.\n\n## Files\n\n| File | What it is |\n|---|---|\n| `kiosk-display.css` | `sw-display-fade` (400 ms slide fade) and the `.sw-kiosk, .sw-tv` token override, verbatim. |\n| `demo.html` | A standalone page with the live animation in light and dark, replayable. |\n| `react/` | Copies of the real app files (see `react/README.md` for the imports to replace). |\n| `previews/` | Animated WebP and GIF previews, light and dark (regenerated by `scripts/make-previews.mjs`). |\n\n## Where the app uses it\n\n- Display slide fade: `features/spaceDisplay/display.css` (`.sw-display-fade` on each slide in the space display).\n- Kiosk and TV: `.sw-kiosk` (`components/layouts/KioskLayout.tsx`) and `.sw-tv` (`features/stats/PublicStatsTvPage.tsx`) zero `--sw-enter-y`, `--sw-pop-scale`, `--sw-pop-offset`, `--sw-slide-x` in `styles/motionTokens.css`.\n\n## Why\n\nA small Raspberry Pi browser, a wall TV or a shared kiosk must stay idle between changes and must never move content a bystander is reading. A fade shows a change without motion across the screen; zeroing the tokens gives every component that behaviour without per-component code.\n\n## Timing, easing and distance\n\n| Part | Value |\n|---|---|\n| Slide fade | 400 ms `ease-out, opacity 0 to 1, `both` |\n| Tokens inside `.sw-kiosk` / `.sw-tv` | `--sw-enter-y: 0px`, `--sw-pop-scale: 1`, `--sw-pop-offset: 0px`, `--sw-slide-x: 0%` |\n\n## Trigger\n\nA slide mounts or changes (key the element so the animation replays). The token override applies to everything inside the kiosk or TV wrapper.\n\n## Reduced motion\n\nThe display fade is `animation: none`; tokens go to 0 as everywhere else.\n\n## Dark theme\n\nColour-neutral; the displays use the same tokens and the black-family dark theme.\n\n## Do and don't\n\n- Do use fades only on shared screens.\n- Do key each slide so the fade replays once per change.\n- Do not slide, scale or loop on kiosk, TV or display screens.\n- Do not keep anything animating while idle (no CSS loops, no timers that repaint).\n\n## Copy-paste usage\n\n```html\n<link rel=\"stylesheet\" href=\"../../tokens/tokens.css\"><link rel=\"stylesheet\" href=\"../overlays/overlays.css\"><link rel=\"stylesheet\" href=\"kiosk-display.css\">\n<div class=\"sw-kiosk\"> <!-- every shared animation inside is a fade -->\n  <section class=\"sw-display-fade\" key=\"slide-3\">…</section>\n</div>\n```\n\nEvery other animation in the product, and the rules behind them: [`../../docs/motion.md`](../../docs/motion.md). Index of the kit: [`../README.md`](../README.md).\n",
  "motion/kiosk-display/react/README.md": "# React copies\n\nUnmodified copies of the app files for this family (SpaceWorks 0.12.0; paths are relative to `frontend/src/`). They are reference code: they import app modules, Lingui (`t`) and Tailwind utility classes, so swap those for your own, or use the framework-free CSS one folder up, which is all the motion needs.\n\n| File | App path | Imports to provide or replace |\n|---|---|---|\n| `display.css` | `features/spaceDisplay/display.css` | none |\n| `KioskLayout.tsx` | `components/layouts/KioskLayout.tsx` | `@lingui/core/macro`, `../brand/AnimatedLogoMark`, `../ui`, `./KioskPin`, `./LayoutFrame`, `./types` |\n| `PublicStatsTvPage.tsx` | `features/stats/PublicStatsTvPage.tsx` | `../../lib/format`, `@lingui/core/macro`, `../../components/PageMetadata`, `../../components/brand/AnimatedLogoMark`, `../../components/layouts/LayoutFrame`, `../../lib/useTenantBranding`, `../../lib/tenant`, `../inventory/PublicInventoryParts`, `../inventory/usePublicInventory`, `../inventory/publicErrorState`, `./api`, `./StatsMetrics`, `./StatsParts` |\n",
  "motion/overlays/README.md": "# Overlays: dialog, drawer, sheet, menu, scrim\n\nOne set of enter and exit animations for every layer that appears over the page: the scrim (fade), the dialog (pop), the phone bottom sheet (rise), side drawers (slide from their edge) and anchored menus and popovers (drop 4 px). Layers carry `data-state=\"open|closed\"`; a presence hook keeps a layer mounted for its exit and makes it inert while it closes.\n\nOpen [`demo.html`](demo.html) in a browser (it links `../../tokens/tokens.css`) and use \"Toggle dark\" to check both themes.\n\n## Files\n\n| File | What it is |\n|---|---|\n| `overlays.css` | The `sw-fx-fade`, `sw-fx-pop`, `sw-fx-drawer`, `sw-fx-menu` rules and every keyframe, verbatim from the app. |\n| `demo.html` | A standalone page with the live animation in light and dark, replayable. |\n| `overlays.js` | Shared demo controller: waits for the actual exit animation, then opens the latest requested layer. Also used by the React demo. |\n| `react/OverlaysDemo.tsx` | A reusable React demo with the same frame, token skins and exclusive layer controller. |\n| `react/` | Copies of the real app files (see `react/README.md` for the imports to replace). |\n| `previews/` | Animated WebP and GIF previews, light and dark (regenerated by `scripts/make-previews.mjs`). |\n\n## Where the app uses it\n\n- Fade (scrim): behind every dialog and drawer (`components/ui/Modal.tsx`, `DetailDrawer.tsx`, the docs drawer scrim in `features/docs/docs.css`).\n- Pop (dialog): `components/ui/Modal.tsx`, `components/layouts/FilterPopover.tsx`, `features/presence/PresenceCheckInSheet.tsx`. On phones (under 640 px) the same class becomes the bottom sheet (`sw-sheet-in/out`).\n- Drawer: `components/ui/DetailDrawer.tsx` (right edge), `components/shell/SidebarDrawer.tsx` and `features/docs/DocsLayout.tsx` (left edge, `data-edge=\"left\"`).\n- Menu: `components/ui/OverflowMenu.tsx`, the user menu, popovers; `data-above` flips the 4 px offset when the menu opens upward.\n- Hook: `lib/motion/usePresence.ts` keeps the layer mounted until its exit finishes. Tooltips do not animate (they are native or instant).\n\n## Why\n\nA layer that appears from just in front (dialog), from the edge it docks to (drawer, sheet) or from its trigger (menu) tells the person where it came from and where it goes when closed. Exits are shorter than entrances so closing never makes anyone wait. Focus lands on something that visibly appeared.\n\n## Timing, easing and distance\n\n| Layer | In | Out | Distance |\n|---|---|---|---|\n| Fade (scrim) | `--sw-dur-base` 180 ms, standard | `--sw-dur-fast` 120 ms, exit | none |\n| Pop (dialog) | 180 ms, enter | 120 ms, exit | scale `--sw-pop-scale` 0.98 |\n| Sheet (phones) | 180 ms, enter | 120 ms, exit | translateY 2 x `--sw-enter-y` (16 px) |\n| Drawer | `--sw-dur-slow` 240 ms, enter | 180 ms, exit | translateX `--sw-slide-x` (100 %), mirrored for `data-edge=\"left\"` |\n| Menu | 120 ms, enter | 120 ms, exit | translateY `--sw-pop-offset` 4 px |\n\n## Trigger\n\nSet `data-state=\"open\"` when the layer shows. To close, set `data-state=\"closed\"`, make the layer inert, and remove it on `animationend` (or after the exit time plus 80 ms). Without animation support the layer unmounts at once.\n\nThe complete demo uses one active layer and a pending request. Opening a dialog, sheet, either drawer or menu first closes the current layer; its exit finishes before the requested layer appears. Rapid requests replace the pending request. Escape and Close clear it. Reduced motion, including a preference change during an exit, finishes immediately.\n\n`sw-ov-frame` owns the containing block, paint boundary and stacking context. Its backdrop covers only that frame and never changes page scroll or layout. Panels use the panel, ink, line, focus and scrim tokens in both themes. The sheet has rounded top corners, a drag handle and safe-area footer padding; its body scrolls while the header and actions remain visible. Drawers fill the frame height. The menu is positioned from the Actions button and clamped inside the frame, including after resizing.\n\nFocus enters each layer without scrolling the page. Tab cycles inside a dialog, sheet or drawer; menu items support arrow keys, Home, End, Enter and Space. Escape, a scrim click or Close restores the opener. The handle is decorative; this template does not implement drag-to-dismiss.\n\n## Reduced motion\n\nAll of it sits inside `@media (prefers-reduced-motion: no-preference)`; the app also zeroes every duration under reduced motion (`styles/motion.css`) and the JS hooks skip the exit wait, so layers unmount at once. The layer appears and disappears instantly; the information (it opened, it closed) is carried by focus and content.\n\n## Dark theme\n\nColour-neutral. The scrim and panel colours come from tokens (`--color-scrim`, `--color-panel`), so dark uses the black family and `--color-line` borders instead of shadows.\n\n## Do and don't\n\n- Do give every layer a closed state and an exit.\n- Do make a closing layer inert so it cannot take focus.\n- Do keep exits shorter than entrances.\n- Do not unmount a layer instantly when animations are available.\n- Do not slide or scale on kiosk and TV screens (the tokens zero the distances; only the fade remains).\n- Do not animate layout properties.\n\n## Copy-paste usage\n\n```html\n<link rel=\"stylesheet\" href=\"../../tokens/tokens.css\"><link rel=\"stylesheet\" href=\"overlays.css\">\n<div class=\"sw-fx-fade\" data-state=\"open\"> <!-- scrim -->\n  <div class=\"sw-fx-pop\" data-state=\"open\" role=\"dialog\" aria-modal=\"true\">…</div>\n</div>\n<aside class=\"sw-fx-drawer\" data-edge=\"left\" data-state=\"open\">…</aside>\n<ul class=\"sw-fx-menu\" data-state=\"open\" role=\"menu\">…</ul>\n```\n\nClose by setting `data-state=\"closed\"` and `inert`, then remove the node on `animationend` (see `demo.html`).\n\nFor a complete styled example, copy `demo.html`, `overlays.css`, `overlays.js` and the shared `../demo.css`, keeping the tokens link. Initialise the controller on the wrapper containing its controls, status and frame:\n\n```js\n// Load overlays.js as a classic script (or import it once for its side effect).\nconst cleanup = SwOverlays.initOverlays(document.querySelector('[data-overlays-demo]'));\n// Call cleanup() when the host view unmounts.\n```\n\nFor React, render `<OverlaysDemo />` from `react/OverlaysDemo.tsx` and load the tokens and `../demo.css` once. Each instance uses unique accessible IDs. The adjacent app reference components retain the app's portal and Tailwind integration; the kit adapter deliberately contains its layers in its own frame.\n\nEvery other animation in the product, and the rules behind them: [`../../docs/motion.md`](../../docs/motion.md). Index of the kit: [`../README.md`](../README.md).\n",
  "motion/overlays/react/README.md": "# React copies\n\n`OverlaysDemo.tsx` is the kit's standalone React adapter. It shares `../overlays.js` and `../overlays.css` with the HTML demo: one active layer, exit before replacement, a pending request that follows the latest click, frame-scoped scrims, token skins, focus handling and instant reduced-motion end states. Load `../../demo.css` and the design tokens once in your host application. The component cleans up listeners and pending animation waits when unmounted.\n\nThe files in the table below remain app reference copies; their imports and viewport portals are specific to the app. Use the kit adapter when embedding a demo in a documentation frame.\n\nUnmodified copies of the app files for this family (SpaceWorks 0.12.0; paths are relative to `frontend/src/`). They are reference code: they import app modules, Lingui (`t`) and Tailwind utility classes, so swap those for your own, or use the framework-free CSS one folder up, which is all the motion needs.\n\n| File | App path | Imports to provide or replace |\n|---|---|---|\n| `usePresence.ts` | `lib/motion/usePresence.ts` | `./motion` |\n| `motion.ts` | `lib/motion/motion.ts` | none |\n| `Modal.tsx` | `components/ui/Modal.tsx` | `@lingui/core/macro`, `./dialogFocus`, `../../lib/motion`, `./keyboardSafe`, `./OverlayPortal`, `lucide-react` |\n| `DetailDrawer.tsx` | `components/ui/DetailDrawer.tsx` | `@lingui/core/macro`, `./dialogFocus`, `../../lib/motion`, `./keyboardSafe`, `./OverlayPortal` |\n| `OverflowMenu.tsx` | `components/ui/OverflowMenu.tsx` | `@lingui/core/macro`, `../../lib/motion`, `./ConfirmDialog`, `./RouteLink`, `./icon`, `./types`, `./useRovingFocus` |\n| `OverlayPortal.tsx` | `components/ui/OverlayPortal.tsx` | none |\n| `motionOverlay.css` | `styles/motionOverlay.css` | none |\n",
  "motion/page-enter/README.md": "# Page enter\n\nHow content arrives when a page or panel first appears: a list or dashboard that assembles in order (stagger), the sign-in brand panel (facets and showcase cards), and the landing page's areas panel and story proof. Each runs once and never on hover.\n\nOpen [`demo.html`](demo.html) in a browser (it links `../../tokens/tokens.css`) and use \"Toggle dark\" to check both themes.\n\n## Files\n\n| File | What it is |\n|---|---|\n| `page-enter.css` | Stagger rise, facet and showcase entrance, areas panel wipe and proof rise, verbatim from the app. The areas panel animation is applied to `.sw-hub-area-panel`. |\n| `demo.html` | A standalone page with the live animation in light and dark, replayable. |\n| `react/` | Copies of the real app files (see `react/README.md` for the imports to replace). |\n| `previews/` | Animated WebP and GIF previews, light and dark (regenerated by `scripts/make-previews.mjs`). |\n\n## Where the app uses it\n\n- Stagger: `components/layouts/StaggerGroup.tsx` (class `sw-stagger` from `layouts.css`) on dashboards and first-load lists, first eight children only.\n- Facets and showcase: `components/BrandFacets.tsx` and `styles/fields.css` on the sign-in and centred-card layouts (`layouts/AuthLayout.tsx`).\n- Areas panel and story proof: `features/hub/landing/AreasSection.tsx`, `StorySections.tsx`, `features/hub/hub.css` on the public landing page.\n- Handover wizard steps reuse the shared fade and rise tokens (`features/staff/handover/HandoverWizard.tsx`).\n\n## Why\n\nFirst load of a list reads as assembled in order, so the eye knows where to start; the sign-in panel gives the brand a single quiet entrance; switching an area swaps panel and proof so the change is noticed. It is page-level motion, once, so it never competes with input.\n\n## Timing, easing and distance\n\n| Animation | Duration | Easing | Distance |\n|---|---|---|---|\n| `sw-stagger-rise` | 200 ms, 30 ms between children (first eight) | `ease-out` | 6 px rise |\n| `sw-facet-in` | 640 ms, delay 120 ms (second facet 200 ms) | `cubic-bezier(0.32,0.72,0,1)` | translate 8 px, 6 px |\n| `sw-rise` (showcase cards) | 480 ms, delays 0, 120, 240 ms | `cubic-bezier(0.32,0.72,0,1)` | translate 12 px |\n| `sw-hub-step-in` (areas panel) | 360 ms | `cubic-bezier(0.16,1,0.3,1)` (enter) | clip-path wipe, top to bottom |\n| `sw-hub-proof-in` | 360 ms | enter | 6 px rise |\n\n## Trigger\n\nMount or first load. The stagger class is dropped after 600 ms (and only applied when interface effects are on and the path is not kiosk, scanner, checkout or payment), so refetches, pagination and tab changes never replay it. The areas panel replays when the selection changes (keyed remount).\n\n## Reduced motion\n\nEverything is inside `no-preference`, so the final layout shows at once.\n\n## Dark theme\n\nColour-neutral. The sign-in brand panel is an island-dark in light and part of the black family in dark; its artwork has dark variants.\n\n## Do and don't\n\n- Do run once, at page level.\n- Do stagger at most eight children.\n- Do not replay on refetch, pagination or tab change.\n- Do not run on hover or on kiosk, scanner, checkout or payment screens.\n- Do not use the individual `translate` property substitutes if the element already uses `transform` for a tilt (the showcase uses `translate` for exactly that reason).\n\n## Copy-paste usage\n\n```html\n<link rel=\"stylesheet\" href=\"page-enter.css\">\n<div class=\"sw-stagger\"> <!-- first 8 children rise in 30 ms apart -->\n  <article>One</article><article>Two</article><article>Three</article>\n</div>\n```\n\nRemove the `sw-stagger` class after about 600 ms so it never replays. For the areas panel, give the panel the class `sw-hub-area-panel` and remount it (change its `key`) when the selection changes.\n\nEvery other animation in the product, and the rules behind them: [`../../docs/motion.md`](../../docs/motion.md). Index of the kit: [`../README.md`](../README.md).\n",
  "motion/page-enter/react/README.md": "# React copies\n\nUnmodified copies of the app files for this family (SpaceWorks 0.12.0; paths are relative to `frontend/src/`). They are reference code: they import app modules, Lingui (`t`) and Tailwind utility classes, so swap those for your own, or use the framework-free CSS one folder up, which is all the motion needs.\n\n| File | App path | Imports to provide or replace |\n|---|---|---|\n| `StaggerGroup.tsx` | `components/layouts/StaggerGroup.tsx` | `../delight/DelightProvider` |\n| `layouts.css` | `components/layouts/layouts.css` | none |\n| `BrandFacets.tsx` | `components/BrandFacets.tsx` | `./spaceWorksLogoPaths` |\n| `fields.css` | `styles/fields.css` | none |\n| `hub.css` | `features/hub/hub.css` | none |\n",
  "motion/route-transition/README.md": "# Route transition\n\nA page change reads as one move: only the main content region fades out (90 ms) and the new page fades in with a short rise, while the sidebar and top bar stay still. It uses the View Transitions API on the one named region and falls back to a CSS fade where the API is missing.\n\nOpen [`demo.html`](demo.html) in a browser (it links `../../tokens/tokens.css`) and use \"Toggle dark\" to check both themes.\n\n## Files\n\n| File | What it is |\n|---|---|\n| `route-transition.css` | The View Transition rules for the named main region, the CSS fallback (`.sw-route-enter`) and the direction distances, verbatim from the app. |\n| `demo.html` | A standalone page with the live animation in light and dark, replayable. |\n| `react/` | Copies of the real app files (see `react/README.md` for the imports to replace). |\n| `previews/` | Animated WebP and GIF previews, light and dark (regenerated by `scripts/make-previews.mjs`). |\n\n## Where the app uses it\n\n- Every in-app link and navigation: `components/layouts/`, `components/shell/` (sidebar, bottom tabs, top bar), staff, member, public and docs pages. Only the element marked `data-sw-main` animates.\n- Files: `lib/motion/TransitionLink.tsx`, `useTransitionNavigate.ts`, `routeTransition.ts`, `RouteTransitionGate.tsx`, `ScrollManager.tsx`, `styles/motionRoute.css`.\n\n## Why\n\nThe visitor sees that the page changed without losing their place: the chrome is stable, the content is new. The old page leaves before the new one arrives, so the two are never visible together. Direction sets the settle distance (forward rises 8 px, a lateral move 6 px, back sinks 6 px, a fade-only change 0).\n\n## Timing, easing and distance\n\n| What | Value |\n|---|---|\n| Old page out | 90 ms, `--sw-ease-exit`, opacity only |\n| New page in | `--sw-dur-slow` (240 ms), `--sw-ease-enter`, after 70 ms, opacity and translateY |\n| Distance | `--sw-vt-y`: 8 px forward, 6 px lateral, -6 px back, 0 fade; kiosk and TV use 0 |\n| Safety | the transition settles after 160 ms if the page is slow, and never blocks navigation |\n\n## Trigger\n\nA plain left-click on a `TransitionLink` or a push navigation through `useTransitionNavigate` sets `data-sw-vt=\"route\"` and `data-sw-nav=\"forward|lateral|back|fade\"` on `<html>` and calls `document.startViewTransition`. Back and forward buttons and unwrapped links replay `.sw-route-enter` instead.\n\n## Reduced motion\n\nNo View Transition is started and `.sw-route-enter` is inside `no-preference`, so the new page simply appears. Scroll is restored instantly.\n\n## Dark theme\n\nColour-neutral: the transition animates opacity and transform of whatever the page already paints, so nothing differs between themes.\n\n## Do and don't\n\n- Do mark one region `data-sw-main` and keep the sidebar and top bar outside it.\n- Do keep the whole move under 240 ms plus the 70 ms offset.\n- Do not animate the sidebar, the top bar or the browser chrome.\n- Do not block navigation on the animation or wait for it before fetching.\n- Do not slide or scale on kiosk and TV screens (fade only).\n\n## Copy-paste usage\n\n```js\nconst reduced = matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\nfunction go(update) {\n  if (!document.startViewTransition || reduced) return update();\n  document.documentElement.dataset.swNav = \"forward\";   // forward | lateral | back | fade\n  document.documentElement.dataset.swVt = \"route\";\n  const t = document.startViewTransition(update);\n  t.finished.finally(() => { delete document.documentElement.dataset.swVt; delete document.documentElement.dataset.swNav; });\n}\n```\n\nMark the region `<main data-sw-main>`. Without View Transitions, add the class `sw-route-enter` (and `data-sw-enter=\"forward\"`) to it after swapping the content.\n\nEvery other animation in the product, and the rules behind them: [`../../docs/motion.md`](../../docs/motion.md). Index of the kit: [`../README.md`](../README.md).\n",
  "motion/route-transition/react/README.md": "# React copies\n\nUnmodified copies of the app files for this family (SpaceWorks 0.12.0; paths are relative to `frontend/src/`). They are reference code: they import app modules, Lingui (`t`) and Tailwind utility classes, so swap those for your own, or use the framework-free CSS one folder up, which is all the motion needs.\n\n| File | App path | Imports to provide or replace |\n|---|---|---|\n| `TransitionLink.tsx` | `lib/motion/TransitionLink.tsx` | `./routeTransition` |\n| `useTransitionNavigate.ts` | `lib/motion/useTransitionNavigate.ts` | `./routeTransition` |\n| `routeTransition.ts` | `lib/motion/routeTransition.ts` | `./motion` |\n| `RouteTransitionGate.tsx` | `lib/motion/RouteTransitionGate.tsx` | `./routeTransition` |\n| `ScrollManager.tsx` | `lib/motion/ScrollManager.tsx` | none |\n| `motion.ts` | `lib/motion/motion.ts` | none |\n| `index.ts` | `lib/motion/index.ts` | `./motion`, `./usePresence`, `./routeTransition`, `./RouteTransitionGate`, `./ScrollManager`, `./TransitionLink`, `./useTransitionNavigate`, `./useSlideIndicator` |\n| `motionRoute.css` | `styles/motionRoute.css` | none |\n",
  "motion/scroll-reveal/README.md": "# Scroll reveal and pinned hero\n\nThe landing page's scroll story: the pinned hero sequence (copy lifts away, the real console rises, zooms through its surface and the overview settles in, all in one sticky scene), the handover steps beside a sticky record, and reveal-on-scroll blocks that arrive once. The default rules are the static page, which is what reduced motion, phones and browsers without JavaScript get.\n\nOpen [`demo.html`](demo.html) in a browser (it links `../../tokens/tokens.css`) and use \"Toggle dark\" to check both themes.\n\n## Files\n\n| File | What it is |\n|---|---|\n| `scroll-reveal.css` | The whole landing scroll stylesheet, verbatim (static page by default; pinned and reveal states only when the hooks mark the page). |\n| `scroll-reveal.js` | Framework-free port of the hooks: `initReveal` (IntersectionObserver, once) and `initSequence` (smoothstep scroll mapping with the real ranges). The React originals are in `react/`. |\n| `demo.html` | A standalone page with the live animation in light and dark, replayable. |\n| `react/` | Copies of the real app files (see `react/README.md` for the imports to replace). |\n| `previews/` | Animated WebP and GIF previews, light and dark (regenerated by `scripts/make-previews.mjs`). |\n\n## Where the app uses it\n\n- Pinned hero: `features/hub/landing/HeroSection.tsx`, `useScrollSequence.ts` (768 px and wider, when the copy, console and overview fit).\n- Handover steps beside a sticky record: `HandoverScrolly.tsx` (1024 px wide and 640 px tall or more).\n- Reveal on scroll: `data-reveal` blocks in `StorySections.tsx`, `AreasSection.tsx`, `FaqSection.tsx` and the rest of the landing page, via `useInView.ts‬ (`useRevealOnScroll`).\n- Styles: `features/hub/landing/landing-scroll.css`.\n\n## Why\n\nScrolling walks the visitor from the promise to the real console to the overview in one scene, so the product is understood by looking at it, not by reading about it. The handover record changes as each step reaches the middle, showing the evidence trail forming. Reveals pace a long page without hiding content.\n\n## Timing, easing and distance\n\n| Part | Value |\n|---|---|\n| Pinned scene | 300 svh tall, scroll-driven transform and opacity (no timing), `will-change` only while moving |\n| Ranges (fraction of the sequence) | copy lift 0 to 0.25, console rise 0 to 0.38 (smoothstep), zoom 0.55 to 0.84, figure fade 0.68 to 0.84, overview reveal 0.86 to 1; copy lifts 72 px, overview rises 32 px and scales 0.94 to 1 |\n| Scrolly step | border and text colour 240 ms ease-out |\n| Reveal | 640 ms `cubic-bezier(0.2,0.7,0.2,1)`, opacity and a 24 px rise, once, observed at the lower 12 % of the screen |\n\n## Trigger\n\nScroll position (one passive listener, one paint per animation frame, geometry measured on resize); reveal on the first time an element's top crosses the lower eighth of the screen.\n\n## Reduced motion\n\nStatic page, top to bottom in DOM order: no pinning, no reveal hiding, step transitions off. Content is never hidden from reduced-motion users, from phones or from no-JavaScript visitors. Print forces everything visible.\n\n## Dark theme\n\nColours are tokens (`--color-bg`, `--color-surface`, `--color-panel`, `--color-line`, `--color-accent-ink`), so the story switches with `html.dark`; the console artwork has dark variants.\n\n## Do and don't\n\n- Do reveal once; never replay on scroll back up.\n- Do animate only transform and opacity, and set `will-change` only while moving.\n- Do keep the static page as the default and add motion only when it fits.\n- Do not reveal on hover.\n- Do not hide content from reduced-motion users.\n- Do not read layout on every scroll event (measure on resize).\n\n## Copy-paste usage\n\n```html\n<link rel=\"stylesheet\" href=\"scroll-reveal.css\"><script src=\"scroll-reveal.js\"></script>\n<main id=\"page\"><section data-reveal>…</section><section data-reveal>…</section></main>\n<script>SwScroll.initReveal(document.getElementById(\"page\"));</script>\n```\n\nFor the pinned hero, build the `data-seq` structure shown in `demo.html` and call `SwScroll.initSequence(root)`.\n\nEvery other animation in the product, and the rules behind them: [`../../docs/motion.md`](../../docs/motion.md). Index of the kit: [`../README.md`](../README.md).\n",
  "motion/scroll-reveal/react/README.md": "# React copies\n\nUnmodified copies of the app files for this family (SpaceWorks 0.12.0; paths are relative to `frontend/src/`). They are reference code: they import app modules, Lingui (`t`) and Tailwind utility classes, so swap those for your own, or use the framework-free CSS one folder up, which is all the motion needs.\n\n| File | App path | Imports to provide or replace |\n|---|---|---|\n| `useScrollSequence.ts` | `features/hub/landing/useScrollSequence.ts` | none |\n| `useInView.ts` | `features/hub/landing/useInView.ts` | none |\n| `HeroSection.tsx` | `features/hub/landing/HeroSection.tsx` | `@lingui/core/macro`, `lucide-react`, `../ExternalLink`, `../hubLinks`, `./DeskPreview`, `./SkipAhead`, `./StorySections`, `./useScrollSequence` |\n| `HandoverScrolly.tsx` | `features/hub/landing/HandoverScrolly.tsx` | `@lingui/core/macro`, `./HandoverStory`, `./useInView` |\n| `landing-scroll.css` | `features/hub/landing/landing-scroll.css` | none |\n",
  "motion/search/README.md": "# Search field\n\nOne search input for the whole product: focus eases the border to the focus colour and nudges the magnifier, a running query swaps the magnifier for the box-open spinner, the clear button fades in with the text, Escape clears, and suggestion lists rise in with a short stagger.\n\nOpen [`demo.html`](demo.html) in a browser (it links `../../tokens/tokens.css`) and use \"Toggle dark\" to check both themes.\n\n## Files\n\n| File | What it is |\n|---|---|\n| `search.css` | The field's motion CSS, the app's real values, plus the base field styles the app gets from `.desk-input`. Tokens only. |\n| `search.js` | About 40 lines of vanilla JS: clear button, Escape-to-clear, shortcut-chip state and `SwSearch.setLoading`. |\n| `demo.html` | A standalone page: field with suggestions, loading simulation, grow-on-focus slot, light and dark. |\n| `react/` | Copies of the real app files, `SearchField.tsx` and `SearchField.css`. |\n| `previews/` | Animated WebP and GIF previews (focus, typing, loading, results, clear), light and dark. Regenerated by `scripts/make-previews.mjs`. |\n\n## Where the app uses it\n\n`components/ui/SearchField.tsx` is the only search input; the guard test `SearchField.guard.test.ts` fails if a raw `<input type=\"search\">` appears anywhere else. It is used by:\n\n- `components/ui/FilterBar.tsx`, which serves `/spaces` (`features/discover/spaces/SpacesToolbar.tsx`), the landing hero (`features/landing/LandingHero.tsx`) and the organization directory.\n- The command palettes: `features/docs/DocsSearchPalette.tsx` (borderless, `bare`) and `features/staff/CommandPalette.tsx`.\n- Help (`features/help/HelpIndex.tsx`), features (`features/hub/FeaturesPage.tsx`), the hub docs search, the settings hub and its section nav, the member directory, makerspace discovery, public inventory, the hackathon galleries and judging list, event and booking discovery, the guest-pass desk, the report hub, platform search and limits.\n- Every staff picker and filter: `features/staff/AsyncSearchPicker.tsx` (shows the loading swap and the animated dropdown), the member, organization, space, product and machine pickers, adapter logs, roster filters, the card designer's font, icon and \"preview as\" pickers.\n\n## Why\n\nSearch is the most repeated gesture in the console and on the public pages. One component means one feel: the field answers focus at once, shows when a query is running without moving anything, and resetting is one tap or one key. It never steals focus and never changes layout (the optional grow uses a reserved slot).\n\n## Timing, easing and distance\n\n| Part | Value |\n|---|---|\n| Border to focus colour | `--sw-dur-fast` 120 ms, `--sw-ease-standard` |\n| Magnifier colour to `accent-ink`, 2 px nudge | `--sw-dur-base` 180 ms, `--sw-ease-enter` |\n| Shortcut chip (for example Ctrl K) | opacity, 120 ms; out on focus or text, back on blur |\n| Loading swap | magnifier opacity 120 ms; the 16 px mono `BoxSpinner` fades in 120 ms |\n| Clear button | opacity 0 to 1 and scale 0.9 to 1, 120 ms, `--sw-ease-enter` |\n| Dropdown | opacity and `translateY(4px)` to 0, `--sw-dur-base`, `--sw-ease-enter`; first six rows stagger 20 ms; \"No results\" fades in |\n| Optional grow | width 72 % to 100 % of its slot, `--sw-dur-slow` 240 ms |\n\nCommand palettes keep their existing overlay pop (`overlays/`); only the field and its list use this family.\n\n## Trigger\n\n`:focus-within` on `.sw-search` (focus, shortcut chip, border, magnifier); the `data-loading` attribute on `.sw-search-icon` (a query in flight); the clear button is rendered only while the field has text; Escape clears a non-empty field and keeps focus (off for fields where Escape closes a popup); `.sw-search-pop` and `.sw-search-item` (with `--sw-i`) are put on a suggestion list when it mounts.\n\n## Reduced motion\n\nColour changes only: no nudge, no scale, no rise, no stagger, no width change. The spinner swap stays because it is the loader rule (the box spinner still shows its open still).\n\n## Dark theme\n\nColour-neutral: every colour is a token. The focus border is Citron in dark and teal in light, the magnifier turns to `accent-ink`. On a filled surface (the teal public-inventory and `/spaces` bars) the magnifier and clear button keep the on-fill colour (`fixedIcon`).\n\n## Do and don't\n\n- Do give the field a visible or `aria-label` name; the component adds the clear button's own label.\n- Do keep the slot's width fixed when using the grow, so neighbours never shift.\n- Do keep focus in the field when clearing.\n- Do not use hover-only states: the clear button is always visible when there is text.\n- Do not animate layout properties (the field never changes height; the list is absolutely positioned).\n- Do not swap the spinner for text; the box-open loader is the only loading mark.\n\n## Copy-paste usage\n\n```html\n<link rel=\"stylesheet\" href=\"tokens.css\"><link rel=\"stylesheet\" href=\"box-open.css\"><link rel=\"stylesheet\" href=\"search.css\">\n<div class=\"sw-search\" id=\"s\">\n  <span class=\"sw-search-icon\"><svg class=\"sw-search-glyph\">…magnifier…</svg></span>\n  <input class=\"sw-search-input\" type=\"search\" aria-label=\"Search tools\" placeholder=\"Search tools\">\n  <kbd class=\"sw-search-kbd\" aria-hidden=\"true\">Ctrl K</kbd>\n</div>\n<script src=\"search.js\"></script>\n<script>SwSearch.init(document.getElementById('s'), { spinner: '<span class=\"sw-box loop mono\">…box markup…</span>' });\nSwSearch.setLoading(document.getElementById('s'), true);</script>\n```\n\nIn React: `<SearchField aria-label=\"Search tools\" value={q} onChange={e => setQ(e.target.value)} loading={isFetching} shortcut=\"Ctrl K\" />`; give a suggestion list the `sw-search-pop` class and each row `className=\"sw-search-item\" style={searchItemStyle(index)}`.\n\nEvery other animation in the product, and the rules behind them: [`../../docs/motion.md`](../../docs/motion.md). Index of the kit: [`../README.md`](../README.md).\n",
  "motion/search/react/README.md": "# React copies\n\nUnmodified copies of the app files for this family (Design System v2.0, taken from the SpaceWorks 0.12.0 app; paths are relative to `frontend/src/`). They are reference code: they import app modules, Lingui (`t`), `lucide-react` and Tailwind utility classes, so swap those for your own, or use the framework-free CSS one folder up, which is all the motion needs.\n\n| File | App path | Imports to provide or replace |\n|---|---|---|\n| `SearchField.tsx` | `components/ui/SearchField.tsx` | `@lingui/core/macro`, `lucide-react`, `../BoxSpinner` (the box-open spinner), `./SearchField.css` |\n| `SearchField.css` | `components/ui/SearchField.css` | none (tokens from `tokens/tokens.css`) |\n",
  "motion/sidebar/README.md": "# Sidebar and tab indicator\n\nThe current section is shown by one box that slides to the selected item, instead of each item repainting its own highlight. The same hook moves the tab underline. A resize skips the transition so a collapsing section never makes the indicator lag.\n\nOpen [`demo.html`](demo.html) in a browser (it links `../../tokens/tokens.css`) and use \"Toggle dark\" to check both themes.\n\n## Files\n\n| File | What it is |\n|---|---|\n| `sidebar.css` | The `.sw-indicator` box, the rules that drop the item's own highlight once the indicator is placed, and the tab underline, verbatim from the app. |\n| `demo.html` | A standalone page with the live animation in light and dark, replayable. |\n| `react/` | Copies of the real app files (see `react/README.md` for the imports to replace). |\n| `previews/` | Animated WebP and GIF previews, light and dark (regenerated by `scripts/make-previews.mjs`). |\n\n## Where the app uses it\n\n- Staff and member sidebar: `components/shell/Sidebar.tsx`, `SidebarItem.tsx`, `SidebarSection.tsx` (the \"More\" group uses the collapse family).\n- Tabs with an underline: `.desk-tab-active` and `.sw-tab-indicator` in staff panels.\n- Hook: `lib/motion/useSlideIndicator.ts`.\n\n## Why\n\nMoving one box shows where you came from and where you are now; repainting a list item does not. It is also the cheapest possible animation: one transform.\n\n## Timing, easing and distance\n\n| Property | Value |\n|---|---|\n| `transform` | `--sw-dur-base` 180 ms, `--sw-ease-standard` |\n| Size | set directly (width and height are never transitioned) |\n| Resize | `transition: none` for that update |\n\n## Trigger\n\nA change of the current item or tab (the hook key). The container gets `data-slide` once the indicator is placed; the indicator hides when nothing matches.\n\n## Reduced motion\n\nThe token duration is 0, so the box jumps to the new item; the current item is still marked by `aria-current`.\n\n## Dark theme\n\nColours come from tokens (`--color-accent-soft` fill, `--color-accent-ink` underline).\n\n## Do and don't\n\n- Do transition only `transform` and set the size directly.\n- Do keep `aria-current=\"page\"` on the real item.\n- Do not animate the sidebar on route changes (see route-transition).\n- Do not repaint each item's highlight separately.\n\n## Copy-paste usage\n\n```html\n<nav id=\"nav\" style=\"position:relative\">\n  <div class=\"sw-indicator\" id=\"bar\"></div>\n  <a class=\"sw-slide-item\" aria-current=\"page\">Requests</a><a class=\"sw-slide-item\">Inventory</a>\n</nav>\n```\n\nCall the placement function from `demo.html` (a plain-JS port of `useSlideIndicator`) whenever the current item changes and on resize.\n\nEvery other animation in the product, and the rules behind them: [`../../docs/motion.md`](../../docs/motion.md). Index of the kit: [`../README.md`](../README.md).\n",
  "motion/sidebar/react/README.md": "# React copies\n\nUnmodified copies of the app files for this family (SpaceWorks 0.12.0; paths are relative to `frontend/src/`). They are reference code: they import app modules, Lingui (`t`) and Tailwind utility classes, so swap those for your own, or use the framework-free CSS one folder up, which is all the motion needs.\n\n| File | App path | Imports to provide or replace |\n|---|---|---|\n| `useSlideIndicator.ts` | `lib/motion/useSlideIndicator.ts` | none |\n| `motion.ts` | `lib/motion/motion.ts` | none |\n| `Sidebar.tsx` | `components/shell/Sidebar.tsx` | `@lingui/core/macro`, `lucide-react`, `../../lib/motion`, `../brand/AnimatedLogoMark`, `../ui`, `./SidebarSection`, `./WorkspaceSwitcher`, `./UserMenu`, `./navKeys`, `./types` |\n| `SidebarItem.tsx` | `components/shell/SidebarItem.tsx` | `@lingui/core/macro`, `../../lib/motion`, `./types` |\n| `SidebarSection.tsx` | `components/shell/SidebarSection.tsx` | `@lingui/core/macro`, `lucide-react`, `../ui/Collapse`, `../../lib/safeStorage`, `./SidebarItem`, `./types` |\n| `Disclosure.tsx` | `components/shell/Disclosure.tsx` | `../../lib/motion`, `./types` |\n| `motion.css` | `styles/motion.css` | none |\n",
  "motion/skeleton/README.md": "# Skeleton and progress\n\nLoading placeholders and progress. A skeleton reserves the shape of what is coming and puts the Space Works box spinner in it; it does not shimmer. A progress bar grows to its new value instead of jumping.\n\nOpen [`demo.html`](demo.html) in a browser (it links `../../tokens/tokens.css`) and use \"Toggle dark\" to check both themes.\n\n## Files\n\n| File | What it is |\n|---|---|\n| `skeleton.css` | The still placeholder shape and the progress-width transition. The spinner itself is the box-open loader (`../box-open/`). |\n| `demo.html` | A standalone page with the live animation in light and dark, replayable. |\n| `react/` | Copies of the real app files (see `react/README.md` for the imports to replace). |\n| `previews/` | Animated WebP and GIF previews, light and dark (regenerated by `scripts/make-previews.mjs`). |\n\n## Where the app uses it\n\n- `components/ui/Skeleton.tsx` and `SkeletonRows` in `components/ui/DataTable.tsx‬ loading rows, list and card placeholders across staff and member screens.\n- Progress width: `features/cards/cardJobs.tsx` (card job progress). `components/ui/ProgressBar.tsx` sets the width without a transition.\n\n## Why\n\nThe page keeps its layout while data loads, so nothing jumps when it arrives, and the one brand-owned loading indicator tells the person something is coming. A shimmer would keep repainting an idle screen; progress that eases to its value reads as continuous.\n\n## Timing, easing and distance\n\n| Part | Value |\n|---|---|\n| Skeleton | no animation of its own; the box spinner inside loops 4400 ms |\n| Progress fill | `width` 150 ms, `cubic-bezier(0.4,0,0.2,1)` (Tailwind default) |\n\n## Trigger\n\nA skeleton is mounted while a query loads. The progress fill transitions whenever its width changes.\n\n## Reduced motion\n\nThe spinner shows the still, open logo; the progress width jumps to its value (the transition is inside `no-preference`).\n\n## Dark theme\n\nThe surface and fill are tokens; the box spinner uses the reversed artwork in dark (see box-open).\n\n## Do and don't\n\n- Do size a skeleton to the content it replaces.\n- Do put one box spinner in it, not a spinner on every row at once.\n- Do not add shimmer sweeps or pulsing greys.\n- Do not use a progress bar for unknown time (use the box loader).\n\n## Copy-paste usage\n\n```html\n<link rel=\"stylesheet\" href=\"../box-open/box-open.css\"><link rel=\"stylesheet\" href=\"skeleton.css\">\n<div class=\"sw-skeleton\" style=\"height:56px\" aria-hidden=\"true\">\n  <div class=\"sw-box loop\" style=\"width:22px;height:20px\"><!-- ../box-open/box-open-markup.html --></div>\n</div>\n<div class=\"sw-progress\" role=\"progressbar\" aria-valuenow=\"40\"><div class=\"sw-progress-fill\" style=\"width:40%\"></div></div>\n```\n\nEvery other animation in the product, and the rules behind them: [`../../docs/motion.md`](../../docs/motion.md). Index of the kit: [`../README.md`](../README.md).\n",
  "motion/skeleton/react/README.md": "# React copies\n\nUnmodified copies of the app files for this family (SpaceWorks 0.12.0; paths are relative to `frontend/src/`). They are reference code: they import app modules, Lingui (`t`) and Tailwind utility classes, so swap those for your own, or use the framework-free CSS one folder up, which is all the motion needs.\n\n| File | App path | Imports to provide or replace |\n|---|---|---|\n| `Skeleton.tsx` | `components/ui/Skeleton.tsx` | `../BoxSpinner` |\n| `ProgressBar.tsx` | `components/ui/ProgressBar.tsx` | none |\n| `cardJobs.tsx` | `features/cards/cardJobs.tsx` | `../../lib/format`, `@lingui/core/macro`, `../../lib/api` |\n",
  "motion/theme-reveal/README.md": "# Theme reveal\n\nThe Space Works light/dark switch animation. When someone presses the theme toggle, the new theme grows from the\ntoggle as a circle and covers the page in 720 ms, so the change reads as chosen, not as a flash. Where the browser\ncannot do that it cross-fades; under reduced motion it switches instantly.\n\n![Light to dark, then dark to light, as the circle grows from the toggle](previews/theme-reveal.webp)\n\nOpen [`demo.html`](demo.html) in a browser for the real thing (a page, a round toggle and a Light / Dark / System\npicker). It uses the design tokens from [`../../tokens/tokens.css`](../../tokens/tokens.css).\n\n## Files\n\n| File | What it is |\n|---|---|\n| `theme-reveal.css` | The View Transition rules, the registered `--sw-reveal` property for the soft edge, and the cross-fade fallback. |\n| `theme-reveal.js` | Framework-free classic script. Exposes `SwThemeReveal.switchTheme(next, originEl)` and `SwThemeReveal.applyTheme(next)`. |\n| `demo.html` | Standalone sample page with a toggle and a segmented picker, including the no-flash pre-paint script. |\n| `react/` | The SpaceWorks app's own files: `themeTransition.ts` and `themeTransition.css` (the reveal), `theme.ts` (preference, system, storage), `themeReveal.ts` (wrapper), `ThemeToggle.tsx` and `ThemeChoice.tsx`. See the import note below. |\n| `previews/` | The animated preview (`theme-reveal.webp`, `.gif`), generated by `scripts/make-previews.mjs`. |\n\n## What it does\n\n1. `switchTheme(\"dark\", button)` finds the centre of `button`.\n2. With View Transitions, the browser snapshots the old page, `html.dark` is toggled inside the transition callback,\n   and the **new** snapshot is revealed through a circle that grows from the button's centre to the farthest page\n   corner, **720 ms**, `cubic-bezier(0.45, 0, 0.2, 1)`. The edge is soft (a 160 px feather) where a registered custom\n   property can animate inside a mask; elsewhere it is a hard `clip-path` circle. The final frame is held\n   (`fill: \"forwards\"`) so the old theme never flashes back.\n3. The preference is stored in `localStorage` under `makerspace.theme` (`light`, `dark` or `system`), the\n   `<meta name=\"theme-color\">` becomes `#FFF4E8` (light) or `#000000` (dark), and `html.dark` is the single source of\n   truth. Storage failing (a private window) never stops the theme from changing for the session.\n\n## Timing and tokens\n\n| Part | Value |\n|---|---|\n| Reveal | 720 ms, `cubic-bezier(0.45, 0, 0.2, 1)`, grows from the toggle's centre |\n| Soft edge | 160 px feather |\n| Cross-fade fallback | 420 ms (class `sw-theme-fade` held for 460 ms) on `background-color`, `border-color`, `color`, `fill`, `stroke`, `box-shadow` |\n| Colours | `tokens/tokens.css`: `html.dark` swaps every `--color-*` triplet; nothing else in the page changes |\n| Browser chrome | `#FFF4E8` light, `#000000` dark |\n\nThe durations are deliberately longer than the 120 to 240 ms UI motion tokens: this is a page-level moment and never\nblocks input (see `docs/motion.md`).\n\n## Reduced motion and support\n\n- `prefers-reduced-motion: reduce`: the theme changes instantly, with no reveal and no fade.\n- No `document.startViewTransition` (Firefox before 144, older browsers): every colour cross-fades for 420 ms.\n- No `CSS.registerProperty` or mask support: hard-edged circle via `clip-path` instead of the soft edge.\n- If the animation API throws, the theme is still applied. The reveal is decoration, never a dependency.\n\n## Usage without a framework\n\n```html\n<link rel=\"stylesheet\" href=\"tokens/tokens.css\">\n<link rel=\"stylesheet\" href=\"theme-reveal.css\">\n<script src=\"boot.js\"></script>   <!-- no-flash pre-paint script from docs/theming.md, synchronous, in <head> -->\n\n<button id=\"theme\" type=\"button\" aria-label=\"Switch theme\">...</button>\n\n<script src=\"theme-reveal.js\"></script>\n<script>\n  document.getElementById(\"theme\").onclick = function (e) {\n    var dark = document.documentElement.classList.contains(\"dark\");\n    SwThemeReveal.switchTheme(dark ? \"light\" : \"dark\", e.currentTarget);\n  };\n</script>\n```\n\n`switchTheme(next, originEl, options)`: `next` is `\"light\"`, `\"dark\"` or `\"system\"`; `originEl` is the element the circle\ngrows from (omit it and the circle starts at the top right). Options: `storageKey` (default `makerspace.theme`),\n`onApply(isDark)`, called inside the transition when the theme is painted, for apps that mirror the theme somewhere\nelse (the design site sets `data-theme` there), and `apply(next)`, which replaces the default painting entirely (the\nTheming demo on the design site re-themes one container instead of `<html>`).\n\n**Pair it with the pre-paint script.** The reveal only animates a change after the page is up. On first load the\nsynchronous `boot.js` in `<head>` (see [`docs/theming.md`](../../docs/theming.md), section 3) must already have put\n`dark` on `<html>`, or a returning dark-mode visitor sees a light flash before anything can animate.\n\n## Usage in React\n\nCopy `react/` into the app (the SpaceWorks app keeps them at `frontend/src/lib/` and `frontend/src/components/`):\n\n```tsx\nimport { useSyncExternalStore } from \"react\";\nimport { currentTheme, subscribeTheme } from \"./lib/theme\";\nimport { transitionTheme } from \"./lib/themeTransition\"; // imports ./themeTransition.css itself\n\nfunction ThemeToggle() {\n  const theme = useSyncExternalStore(subscribeTheme, currentTheme);\n  return <button type=\"button\" onClick={e => transitionTheme(theme === \"dark\" ? \"light\" : \"dark\", e.currentTarget)}>\n    {theme === \"dark\" ? \"Light\" : \"Dark\"}\n  </button>;\n}\n```\n\nCall `initializeTheme()` from `theme.ts` once before `createRoot(...).render`.\n\n### Import note for the `react/` copies\n\nThese are verbatim copies from the SpaceWorks app, so some imports point at app code that is not in this folder:\n\n- `theme.ts` imports `./safeStorage` (`readStorage`, `writeStorage`: `localStorage` wrapped in `try/catch`). The plain\n  `try/catch` versions in `docs/theming.md` section 4 are equivalent.\n- `themeReveal.ts` is a thin wrapper kept for existing callers; new code can call `transitionTheme` directly.\n- `ThemeToggle.tsx` and `ThemeChoice.tsx` use the app's `@lingui` translations, `useDelight`/`useReducedMotion`\n  (`components/delight/DelightProvider`), `icons`, `ui/IconButton` and `ui/SegmentedControl`. Replace those with your\n  own button and picker; the only theme-specific call is `transitionTheme`/`revealTheme`.\n- `themeTransition.ts` also clears `data-sw-vt`, an app attribute a route transition uses. Drop that line if you\n  have no route transitions.\n\n## Source of truth\n\nThe SpaceWorks app (`frontend/src/lib/themeTransition.ts`, `themeTransition.css`, `lib/theme.ts`). `theme-reveal.js`\nand `theme-reveal.css` are its framework-free mirror; change them together. `scripts/check-previews.mjs` fails when\n`theme-reveal.css`, `theme-reveal.js` or `demo.html` change without the preview being regenerated\n(`node scripts/make-previews.mjs theme-reveal`).\n",
  "motion/toast/README.md": "# Toast\n\nA status toast arrives from just below its slot (12 px, fading in) and leaves by dropping 8 px while fading. It never steals focus; the message is announced through a polite live region (errors through an alert region).\n\nOpen [`demo.html`](demo.html) in a browser (it links `../../tokens/tokens.css`) and use \"Toggle dark\" to check both themes.\n\n## Files\n\n| File | What it is |\n|---|---|\n| `toast.css` | The `sw-fx-toast` rule and its two keyframes, copied unchanged from `styles/motionOverlay.css`. |\n| `demo.html` | A standalone page with the live animation in light and dark, replayable. |\n| `react/` | Copies of the real app files (see `react/README.md` for the imports to replace). |\n| `previews/` | Animated WebP and GIF previews, light and dark (regenerated by `scripts/make-previews.mjs`). |\n\n## Where the app uses it\n\n- `components/ui/ToastHost.tsx` (the one host mounted by the app shell): every save, undo, error and result message in staff, member and public flows.\n- `lib/motion/usePresence.ts` (`useExitingItems`) keeps a dismissed toast on screen, inert, while its exit plays.\n\n## Why\n\nStatus appears without taking focus or moving anything else, and its short exit confirms the dismissal. The tone (error colour) is carried by colour and text, never by shaking or bouncing.\n\n## Timing, easing and distance\n\n| Phase | Value |\n|---|---|\n| In | `--sw-dur-base` 180 ms, `--sw-ease-enter`, translateY 1.5 x `--sw-enter-y` (12 px) and opacity |\n| Out | `--sw-dur-fast` 120 ms, `--sw-ease-exit`, translateY `--sw-enter-y` (8 px) and opacity |\n\n## Trigger\n\nA toast is added to the store (`data-state=\"open\"`) or dismissed (`data-state=\"closed\"` plus `inert`, removed on animation end).\n\n## Reduced motion\n\nInside `no-preference`: the toast appears and disappears instantly; the live region still announces it.\n\n## Dark theme\n\nColour-neutral; the panel, border and error text colours are tokens.\n\n## Do and don't\n\n- Do announce through `role=\"status\"` (polite) or `role=\"alert\"` for errors.\n- Do keep the toast out of the focus order while it leaves (`inert`).\n- Do not shake, bounce or auto-focus a toast.\n- Do not add sound.\n\n## Copy-paste usage\n\n```html\n<link rel=\"stylesheet\" href=\"../../tokens/tokens.css\"><link rel=\"stylesheet\" href=\"toast.css\">\n<div class=\"sw-fx-toast\" data-state=\"open\" role=\"status\">Saved.</div>\n```\n\nTo dismiss: set `data-state=\"closed\"` and `inert`, then remove the element on `animationend`.\n\nEvery other animation in the product, and the rules behind them: [`../../docs/motion.md`](../../docs/motion.md). Index of the kit: [`../README.md`](../README.md).\n",
  "motion/toast/react/README.md": "# React copies\n\nUnmodified copies of the app files for this family (SpaceWorks 0.12.0; paths are relative to `frontend/src/`). They are reference code: they import app modules, Lingui (`t`) and Tailwind utility classes, so swap those for your own, or use the framework-free CSS one folder up, which is all the motion needs.\n\n| File | App path | Imports to provide or replace |\n|---|---|---|\n| `ToastHost.tsx` | `components/ui/ToastHost.tsx` | `@lingui/core/macro`, `../../lib/motion`, `./toastStore`, `../BoxSpinner` |\n| `usePresence.ts` | `lib/motion/usePresence.ts` | `./motion` |\n| `motion.ts` | `lib/motion/motion.ts` | none |\n",
  "motion/README.md": "# Space Works motion kit\n\nPart of Design System v2.0. Values are copied from the app as shipped in SpaceWorks 0.12.0.\n\nEvery animation in the product as a reusable, framework-free template: one folder per family, each with the CSS\n(copied unchanged from the app, using the tokens in [`../tokens/tokens.css`](../tokens/tokens.css)), `react/` copies\nof the real app files, a standalone `demo.html` (light and dark, honours reduced motion), animated `previews/`\n(WebP and GIF) and a README that says what it is, where the app uses it, why, the timing and easing tokens, the\ntrigger, reduced-motion and dark behaviour, do and don't, and copy-paste usage.\n\nThe full catalogue with every trigger and reduced-motion note is [`../docs/motion.md`](../docs/motion.md). The\nrules behind it: transform and opacity only, enter decelerates and exit accelerates, nothing on an interaction path\nover 240 ms, hover is colour only, reduced motion is a floor, never loop outside a loader, check both themes.\n\n## Folders\n\n| Folder | What it covers |\n|---|---|\n| [`box-open/`](box-open/README.md) | The box loader (loop), the inline spinner, the in-button mono spinner, the logo hover and focus opening, the cold-start splash opening |\n| [`theme-reveal/`](theme-reveal/README.md) | The light/dark switch: a circle growing from the toggle |\n| [`search/`](search/README.md) | The shared animated search field |\n| [`route-transition/`](route-transition/README.md) | Page change: old out, new in, only the main region |\n| [`page-enter/`](page-enter/README.md) | List stagger, sign-in facets and showcase, areas panel and proof |\n| [`scroll-reveal/`](scroll-reveal/README.md) | Landing story: pinned hero, handover steps beside a sticky record, reveal on scroll |\n| [`overlays/`](overlays/README.md) | Scrim fade, dialog pop, phone sheet, side drawers, menus and popovers |\n| [`toast/`](toast/README.md) | Status toast in and out |\n| [`sidebar/`](sidebar/README.md) | Sliding section and tab indicator |\n| [`collapse/`](collapse/README.md) | Height collapse via grid rows, disclosure chevrons |\n| [`skeleton/`](skeleton/README.md) | Still loading placeholders and the progress width |\n| [`hover-press/`](hover-press/README.md) | Colour-only hover, press sink, card, row and link hover |\n| [`feedback/`](feedback/README.md) | Verify stamp, result arrive, pass tear, logbook, return confetti |\n| [`delight/`](delight/README.md) | Logo flap spring and pop, Patch, companion walk, glyph tilt, card tilt and flip |\n| [`kiosk-display/`](kiosk-display/README.md) | Display slide fade and the kiosk and TV token override |\n\n## Every animation, where it lives\n\n| Animation | Folder | Where | Why |\n|---|---|---|---|\n| Mark opening on hover and focus | `box-open/` | Every Space Works logo placement (`AnimatedLogoMark`) | The brand signature; shows the link is live without moving layout |\n| Cold-start splash opening | `box-open/` | `index.html`, `lib/splash.ts` | First paint hands over to the header logo |\n| Cold-start splash flight and fade | `box-open/` | `lib/splash.ts` | The mark flies to the header logo (520 ms), then the splash fades (200 ms) |\n| Box loader (loop) | `box-open/` | `LogoLoader` route fallbacks and panels | One brand loading indicator |\n| Box spinner and mono | `box-open/` | `BoxSpinner` (buttons, rows, fields) | Inline and in-button busy state |\n| Theme switch reveal | `theme-reveal/` | `themeReveal.ts`, `themeTransition.ts` | The new theme grows from the toggle |\n| Animated search field | `search/` | Shared search field | See its README |\n| Route transition (View Transition) | `route-transition/` | `lib/motion/*`, `motionRoute.css` | A page change reads as one move; chrome stays still |\n| Route fallback fade (`sw-route-enter`) | `route-transition/` | Back and forward, unwrapped links | Same move without the API |\n| Scroll restoration | `route-transition/` | `ScrollManager.tsx` | Back returns to where you were |\n| Root cross-fade | `route-transition/` | `delight.css` | Non-route View Transitions fade the page (180 ms) |\n| Stagger rise | `page-enter/` | `StaggerGroup`, `layouts.css` | First load reads as assembled in order |\n| Facet entrance | `page-enter/` | `BrandFacets`, `fields.css` | Sign-in panel entrance, once |\n| Showcase rise | `page-enter/` | `fields.css` (`sw-auth-showcase`) | Sign-in cards arrive in turn |\n| Areas panel wipe | `page-enter/` | `AreasSection`, `hub.css` | A switched area is noticed |\n| Story proof rise | `page-enter/` | `StorySections`, `hub.css` | A new proof row lands |\n| Handover wizard step change | `page-enter/` | `HandoverWizard.tsx` | Shared fade and rise tokens |\n| Pinned hero sequence | `scroll-reveal/` | `HeroSection`, `useScrollSequence` | Promise to console to overview in one scene |\n| Handover steps beside sticky record | `scroll-reveal/` | `HandoverScrolly` | The evidence trail forms as steps pass |\n| Reveal on scroll | `scroll-reveal/` | `data-reveal` blocks | Sections arrive once |\n| Smooth scroll to target | `scroll-reveal/` | `HandoverScrolly`, `JoinButton`, kiosk recovery, print request | Bring the result into view |\n| Scrim fade | `overlays/` | Dialogs, drawers, docs drawer scrim | The layer arrives without a jump |\n| Dialog pop | `overlays/` | `Modal`, `FilterPopover`, `PresenceCheckInSheet` | Arrives from just in front |\n| Bottom sheet | `overlays/` | Dialogs under 640 px | Rises from the edge it docks to |\n| Drawer (right and left) | `overlays/` | `DetailDrawer`, `SidebarDrawer`, `DocsLayout` | Slides from its own edge |\n| Menu and popover | `overlays/` | `OverflowMenu`, user menu, popovers | Drops 4 px from the trigger |\n| Presence (exit keeps mounted) | `overlays/` | `usePresence.ts` | Exits can play and the layer is inert while closing |\n| Status toast | `toast/` | `ToastHost` | Status without stealing focus |\n| Sliding indicator (sidebar) | `sidebar/` | `Sidebar`, `useSlideIndicator` | One box shows the current section |\n| Tab underline slide | `sidebar/` | Staff panel tabs | Same hook on tabs |\n| Section collapse | `collapse/` | `Collapse`, `Disclosure`, sidebar \"More\" | A group opens without losing neighbours |\n| Disclosure chevrons | `collapse/` | Sidebar, collapsible and inspector sections, FAQ, docs nav | The arrow reports the state |\n| Skeleton placeholder | `skeleton/` | `Skeleton`, DataTable loading rows | Reserves the shape; box spinner inside |\n| Progress width | `skeleton/` | `cardJobs.tsx` | Progress eases to its value |\n| Press and hover colour | `hover-press/` | Buttons, tabs, nav items (`motion.css`) | The control answers; press sinks to 0.98 |\n| Hover colour on cards, rows, links, tabs | `hover-press/` | Space cards, hub, docs, directory, settings nav | Same colour-only rule |\n| Hover opacity and drag dim | `hover-press/` | Analytics bars, crew rows | Marks the hovered or dragged item |\n| Card verify stamp | `feedback/` | `CardVerifyPage` | A valid check lands with one stamp |\n| Return confetti | `feedback/` | `ReturnConfetti` | Celebrates a completed return (delight on only) |\n| Result arrive, pass tear, logbook open | `feedback/` | `delight.css` | Small human moments (delight on only) |\n| Logo flap spring and pop | `delight/` | `logoUnfold.css` | Playful flaps (delight on only) |\n| Patch: pet walk, naps, paw prints | `delight/` | `NotFoundPatch` | The 404 pet |\n| Patch moment trot | `delight/` | `PatchMoment` | Still moment that trots once |\n| Patch \"z\" | `delight/` | `patch.css` | Sleeping decoration (loops, decorative) |\n| Companion walk | `delight/` | `Companion` | Decorative walk (loops, decorative) |\n| Glyph tilt | `delight/` | `delight.css` | -3 deg under the pointer |\n| Card tilt and flip | `delight/` | `HolographicCard` | The printed card follows the pointer |\n| Display slide fade | `kiosk-display/` | `spaceDisplay/display.css` | One fade per slide; a Pi stays idle |\n| Kiosk and TV token override | `kiosk-display/` | `.sw-kiosk`, `.sw-tv` | Fades only on shared screens |\n\n## Previews and checks\n\nAnimated WebP and GIF previews (light and dark, each under 400 KB) are in every folder's `previews/`. They are\nrendered in headless Edge from the same CSS by `node scripts/make-previews.mjs motion-kit`, with every CSS\nanimation and transition paused and seeked to an exact time per frame. `node scripts/check-previews.mjs` fails when\nany family's CSS, demo page or preview is stale or missing.\n\n## Using a family\n\n1. Link `tokens/tokens.css` (it defines the motion tokens and both themes: dark is the class `dark` on `<html>`).\n2. Link the family stylesheet and copy the markup from its `demo.html`.\n3. Check it in light and dark, and with `prefers-reduced-motion: reduce`.\n"
}
