itinera-showcase-react SPA: Vite + React 19 + React Router 7 + Tailwind 4 + GSAP. It is a static, content-driven showcase with zero runtime API calls to the Laravel backend — all content lives in src/lib/*.ts and is rendered client-side.vite@8, @vitejs/plugin-react, @tailwindcss/vite@4, typescript@6, vitest@4 (happy-dom), oxlint.src/main.tsx:1-13:import { initTheme } from './lib/theme-store'
initTheme() // 1. set html.light + colorScheme from localStorage BEFORE React mounts — prevents flash
createRoot(...).render(<StrictMode><App /></StrictMode>) // 2. double-invoke effects in devvite.config.ts registers react() and tailwindcss() plugins; test.environment = 'happy-dom', globals: true for Vitest.src/App.tsx:23-36createBrowserRouter), flat BrowserRouter + Routes:| Path | Element | Notes |
|---|---|---|
/ | Home | 9 sections: Hero, Orbit (§01), Bins (§02), Frontend flip-cards (§03), Bento vault (§04), Ops (§05), etc. |
/lifecycle | LifecyclePage | 12 sections pinned via GSAP scrub (see Phase 11) |
/docs | Docs | Static explorer |
/wiki, /wiki/:guideId | Wiki | useGuide fetches markdown under /wiki/ |
* | Home | catch-all |
GlobalPalette lives outside Routes but inside BrowserRouter, reads useLocation(). entries = GUIDES.map(...) only when pathname.startsWith("/wiki") — route-aware palette index.src/lib/theme-store.ts:1-47: Module variable theme: Theme + Set<() => void> listeners + useSyncExternalStore(subscribe, getTheme). apply() toggles document.documentElement.classList.toggle("light", ...) and style.colorScheme. initTheme() called before React; setTheme() persists to localStorage and notifies listeners. Every useTheme() instance syncs without a Provider.Home.tsx — dialogIdx, activeDefense, useState for flip is-flipped via pointerenter; LifecyclePage.tsx — imperative currentIdx, startOffset Map, stInstances[] (not React state). GSAP owns the scroll state, not React.src/lib/use-guide.ts:6,23: Module cache = new Map<string,string>(), fetch(/wiki/${guide.file}) with cancelled guard and cache.set. Mount checks cache.get synchronously for instant hit.src/lib/:home-content.ts — HARDENING[], TELEMETRY[], TERM_LINES[]lifecycle-content.ts — LifecycleStage[] (10 stages + hero/outro)wiki-data.ts / docs-data.ts / explorer-data.ts / kpi.ts — guides, explorer nodessecurity-data.ts — RBAC matrixlifecycle-content.ts → ChapterScene.tsx scene propfetch to api.php at runtime; fetch() appears only as textual content (LifecyclePage.tsx:56 chips ["fetch()",...]) except useGuide static markdown fetch.src/components/
layout/ Topbar, Shell
sections/ FeMotifs, SecurityMotifs, HardCircle, etc. — §03 flip backs, §04 vault
lifecycle/ ChapterScene.tsx — SVG scenes per stage
palette/ command-palette.tsx — cmdk + Radix Dialog
explorer/ Explorer tree for /docs
wiki/ Markdown render (react-markdown)
docs/ Docs shell
ui/ cta-circle, sheet, dialog, command — Radix + cva + tailwind-merge
src/pages/
Home.tsx 688 lines: orbit, bento, flip-cards, ops
LifecyclePage.tsx 817 lines: GSAP scrub engine
Wiki.tsx / Docs.tsx
src/lib/
gsap.ts register ScrollTrigger + MotionPathPlugin once
fuzzy.ts palette fuzzy search
utils.ts cn() = clsx + twMerge
barcode.ts bwip-js helper
src/hooks/
useIsReducedMotion, useScrollTo, useCountUpuseGuide:15-38:toggleTheme() → setTheme(next) → localStorage + apply() → listeners.forEach(l=>l()) → useSyncExternalStore re-render — no Context propagation, minimal re-renders (only useTheme subscribers).src/lib/*.ts is the CMS; adding a stage is a data entry in lifecycle-content.ts, not a migration. No backend coupling in prod showcase.useSyncExternalStore avoids a ThemeProvider wrapping the whole tree and works before React mounts (initTheme()).LifecyclePage keeps timelines in stInstances[] and tl locals, useRef for DOM, startOffset Map for nav — React state is not the source of truth for scrub position (see Phase 11).useGuide has module cache + cancelled flag for StrictMode double-mount — no memory leak, no duplicate network on remount.html.light class flips CSS vars; JS only toggles the class (Phase 10).