diff --git a/bolt-slides/.bolt/skills/slides/SKILL.md b/bolt-slides/.bolt/skills/slides/SKILL.md
new file mode 100644
index 00000000..5acdd496
--- /dev/null
+++ b/bolt-slides/.bolt/skills/slides/SKILL.md
@@ -0,0 +1,220 @@
+---
+name: slides
+description: >-
+ Build a premium PRESENTED slide deck — classic one-slide-at-a-time slides you
+ advance with a clicker — as a Vite + React app. It keeps the Slidev navigation UI
+ (floating glass dock + thumbnail rail + click-builds + presenter) but slides are
+ RESPONSIVE React (reflow to any screen, no fixed canvas) and fully interactive.
+ Use this for a deck you'll PRESENT (talk over, projector, screen-share) when you
+ want web interactivity and responsiveness without Slidev's constraints. The repo
+ IS the running app (Vite + React) — you install it in place, theme the tokens, and
+ AUTHOR an original deck from the user's real topic/brand.
+---
+
+# Slides — a premium, responsive, React presentation engine for Bolt
+
+Classic **paged** slides (advance one at a time, present over them) — but rebuilt in
+**Vite + React** so each slide is a **responsive web layout** instead of a fixed
+1080×607 canvas that clips. It keeps the Slidev UI you liked (the floating dock +
+thumbnail rail + click-builds + presenter) and adds real web interactivity.
+
+Two halves — keep them separate:
+
+- **The engine + UI are pre-built** — this repo is a complete, runnable app: the paged
+ engine (`src/deck/`), the dock/rail chrome, the section components (`src/components/`),
+ the shared CSS. **Leave the engine as-is. Never regenerate it.** This is the part you
+ liked; it must look/behave identically.
+- **The content is authored fresh, every time** — the slides (topic, structure, copy,
+ visuals, theme) are designed from scratch for *this* request.
+
+> **Theme surface.** All color, type, radius, depth, and motion live in the `:root`
+> token vocabulary of `src/styles/tokens.css`. Theme a brand once, there.
+
+## ⛔ Two hard rules
+
+1. **Don't touch the engine** — leave `src/deck/` (the engine + chrome: `Deck`, `Slide`,
+ `Build`, `Reveal`, `DeckContext`, `icons`, `Annotator`, `useInView`) untouched. Don't
+ rewrite or paraphrase the engine or the chrome; author *around* it.
+2. **Author the deck from the user's REAL input — do not reskin the starter.**
+ `src/App.tsx` is a throwaway that only proves it runs. **Delete its slides and
+ write a new deck.** Never reuse its order, copy, placeholders, or fake names.
+
+---
+
+## Step 0 — Ground the deck in the user's real input
+
+Use the user's real topic, brand, document, facts. Never fabricate a placeholder
+company, logo, or quote for a real subject. If a URL/brand is given, the theme comes
+from that brand — fetch the page for real colors/font/logo, or use the brand's known
+palette, or **STOP and ask**. Report which colors/fonts you used and where from.
+
+---
+
+## Step 1 — Run it in place (the repo is the app)
+
+The repo root is already a complete Vite + React app — no scaffolding or copying. Its
+layout:
+
+```
+package.json vite.config.ts tsconfig*.json index.html src/main.tsx
+src/App.tsx ← THROWAWAY. delete its slides; author the real deck.
+src/styles/ tokens.css (edit :root ONLY) · base.css ← don't edit base.css
+src/deck/ Deck Slide Build Reveal DeckContext useInView icons Annotator ← engine + UI. LOCKED.
+src/components/ CountUp TiltCard Marquee Bento Split StatGrid VisualDashboard Accordion
+ Comparison Tabs Timeline CodeWindow BrowserFrame SpotlightCard Charts
+```
+
+`npm install && npm run dev` runs the deck at `/`. Verify the dock / thumbnail rail /
+click-builds work, then delete the starter slides and author the real deck in
+`App.tsx`.
+
+---
+
+## Step 2 — Theme it (edit only the `:root` block)
+
+All color, type, radius, depth, motion live in `src/styles/tokens.css` `:root`.
+**Change values, never variable names.** Nine ready-made theme families to pull from
+(dark product, editorial luxury, Swiss, dark technical, warm minimal,
+fintech, aurora glass, cinematic, paper editorial). One accent, used sparingly.
+Dark vs light: set `html { color-scheme }` in base.css and pick `--bg`/`--fg`
+accordingly. Set fonts in `--font-head`/`--font-body` and the `@import` at the top
+of `base.css`. Derive from the brand when given.
+
+---
+
+## Step 3 — Author slides (each child of `` is one slide)
+
+Compose slides in `App.tsx`. The building blocks:
+
+- **``** — one slide. `center` for cover/statement/quote/CTA; `full` for
+ edge-to-edge; `nav="Label"`; `notes="…"` (editable in the presenter overlay).
+- **``** — text + edge-to-edge media (`flip` swaps). media = ` `, a color
+ panel, a ``, or ` `.
+- **``** — asymmetric tile grid; tiles take `c`/`r` spans + `variant`.
+- **``** — responsive proof cards; pass a `` as a stat `value`.
+- **``** — us-vs-them feature matrix; one column highlighted in the accent.
+- **``** — tabbed content with a sliding accent pill.
+- **``** — expand/collapse panels (FAQ, feature detail).
+- **``** — vertical roadmap that draws its connector + milestones in.
+- **``** — macOS code window with line numbers + line highlight.
+- **``** — browser chrome around a screenshot / full-bleed app mock
+ (fill it edge-to-edge — a real-looking app screen, not a floating card).
+- **``** — card with a cursor-follow accent glow (Linear/Vercel hover).
+- **Charts** — `` / `` / ``, all draw-in on view.
+- **Atoms** (CSS classes): `.display .headline .lead .subhead .kicker .figure
+ .accent-text .rule`. All fluid (`clamp()`).
+
+**Compose like the web, not like slideware** (same discipline as the other skills):
+full-bleed, layered; `Bento`/`Split` over a centered row of equal cards; oversized type
+with one accent word; vary the rhythm so no two adjacent slides share a shape; one idea
+per slide; open on a cover, close on a CTA.
+
+> **Centering rule:** left-aligned/asymmetric layouts need a **side visual** (a `Split`,
+> an image, a `BrowserFrame`). A **text-only** section anchored left reads as off-center
+> — center those: a centered heading over a centered content block (e.g.
+> `marginInline:'auto'`). `Comparison`, `Tabs`, `Timeline`, `Accordion`, `StatGrid` look
+> best centered unless paired with a side visual.
+
+### Interactivity: click-builds (the signature)
+Reveal content in beats with **``** — it stays hidden until you advance
+to step `n` on that slide, then animates in. Advancing (→ / space / Next) reveals the
+next build, then moves to the next slide. Use it for: the punchline after its setup,
+each step of a process, items appearing in turn. Use **``** for an on-enter
+entrance (no click needed) on headlines/grids.
+
+**Auto-play mode.** The same ``s can instead reveal **automatically, staggered,
+on slide load** — no clicking. Toggle it live with the dock's auto button or the `A`
+key, or start the deck in auto mode with **``** (tune the gap with
+``, seconds). Ideal for a self-running deck or mobile viewing;
+click-mode stays the default.
+
+```tsx
+
+ Three things changed.
+ First, the data got bigger.
+ Then, the tools got faster.
+ Now, anyone can ship.
+
+```
+
+---
+
+## Step 4 — Responsive, not fixed (no clipping)
+
+Each slide is a **full-viewport responsive layout**, not a fixed canvas — it reflows
+to the screen, so nothing scales-and-clips:
+
+- **Fluid sizing.** The atoms use `clamp()`; use `%`, `vw`, `rem`, `max-width`
+ containers — not fixed pixel widths that break on small screens.
+- **One idea per slide**, sized to fill ~one screen with deliberate negative space.
+- **Check a narrow viewport** — `Bento`/`Split` stack (built in); make sure headlines
+ don't overflow and nothing needs scrolling. (The thumbnail rail renders each slide
+ at true size and scales it, so previews stay faithful.)
+- **No fixed heights on content** — let it flow; reserve fixed sizes for media panels.
+
+---
+
+## Step 5 — Visuals & imagery
+
+Visuals must fit the topic (same rules as the other skills): data/SaaS → a `.vframe`
+mock (`VisualDashboard` is an *example* — build topic-fit ones with real data);
+brand/product/editorial/real-world → **generate images** into `public/`, one
+consistent style, used as `Split` media or full-bleed slide backgrounds under a
+gradient scrim (no text in images). A `Split` or full-bleed image beats a floating card.
+
+---
+
+## Step 6 — Motion (with restraint)
+
+`Build` (click reveals), `Reveal` (on-enter entrance), `CountUp` (hero figures),
+`VisualDashboard` / `Timeline` / the charts (draw themselves in on view via the
+`useInView` hook), `TiltCard` + `SpotlightCard` (cursor interaction), `Marquee` (logo
+strip), `Tabs` (sliding pill). The ambient background (drifting spotlights + grain +
+vignette) and the slide-change transition are automatic. **One or two motion ideas per
+slide**, never a circus. All honors `prefers-reduced-motion`.
+
+---
+
+## Extend the system — invent new slides, components & visuals
+
+The kit is a **floor, not a ceiling.** The bundled components cover a lot (comparison,
+tabs, timeline, charts, code/browser frames, accordion, bento, split…) — but author
+new ones for the topic when nothing fits: a `` table, a Gantt, a device/phone
+mock, a chat or kanban mock, a map. Only the token *names* and `src/deck/` (engine +
+chrome) are off-limits to rewrite; **adding** components/visuals is encouraged. Every
+new piece must: use `var(--…)` tokens only (no raw hex), compose like a web section, be
+responsive (work on mobile), animate with `Reveal`/`Build`/`useInView` + honor
+reduced-motion, use tabular figures, and add **no new dependencies** (plain React +
+CSS + SVG).
+
+---
+
+## Step 7 — Structure & writing
+
+Pick an arc that fits the deck type (pitch, launch, brand, teaching, report) —
+structure follows content. Open on a cover, close on a CTA. ~8–16 slides sized to the
+material. Headlines short, declarative, specific (sentence case); body 1–3 tight
+sentences; 1–3 word kickers; one idea per slide. Use the user's real numbers; never
+invent numbers for a real brand. Zero lorem, zero placeholder names. Add `notes` for
+talking points where useful.
+
+---
+
+## Definition of done (self-check)
+
+- [ ] The engine + chrome in `src/deck/` are **left untouched**; the
+ dock + thumbnail rail appear, arrow keys advance AND step back through builds,
+ auto-play / fullscreen / overview work, annotation (D) has full tools and
+ persists per slide, presenter (P) opens a synced new tab, `H` hides the UI, and
+ the URL hash tracks the slide.
+- [ ] The deck is **authored, not reskinned** — topic, structure, copy, names are the
+ user's, with no starter leftovers (no "Title"/"Northwind").
+- [ ] If a brand/URL was given, `--primary`, fonts, and logo come from that brand.
+- [ ] Only the `:root` block was edited for the theme; editing `--primary` recolors
+ everything incl. the dock.
+- [ ] Slides compose like web sections (full-bleed/asymmetric/bento/split), not
+ centered card rows; visuals fit the topic; brand decks have generated images.
+- [ ] **Responsive:** looks right narrow + wide — sections stack, nothing clips or
+ needs scrolling. Builds reveal in the intended order.
+- [ ] Motion is restrained; reduced-motion respected.
+- [ ] `npm install && npm run dev` runs with no console errors; `npm run build` passes.
diff --git a/bolt-slides/.gitignore b/bolt-slides/.gitignore
new file mode 100644
index 00000000..da431f1c
--- /dev/null
+++ b/bolt-slides/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+dist/
+.vite/
+.DS_Store
+.env
diff --git a/bolt-slides/README.md b/bolt-slides/README.md
new file mode 100644
index 00000000..5cd949f4
--- /dev/null
+++ b/bolt-slides/README.md
@@ -0,0 +1,34 @@
+# Bolt slides skill
+
+A Bolt skill that builds a premium, **responsive React presentation deck** — classic
+paged slides you present one at a time, with a Slidev-style floating dock + thumbnail
+rail, click-builds, auto-play, annotation, and presenter mode — but each slide is a
+responsive web layout (no fixed canvas, no clipping) built from a rich component
+library.
+
+## The skill
+
+This repo **is** the running app. The authoring guide lives at
+[`.bolt/skills/slides.md`](./.bolt/skills/slides.md); the app itself sits at the repo
+root — a complete Vite + React deck: the paged engine + chrome (`src/deck/`), ~14
+components (`src/components/`: Bento, Split, StatGrid, Comparison, Tabs, Accordion,
+Timeline, CodeWindow, BrowserFrame, SpotlightCard, charts, CountUp, TiltCard,
+Marquee, …), and the token-driven theme (`src/styles/`). The engine is left as-is;
+only the `:root` token block and the slides in `src/App.tsx` are authored per deck.
+
+## Add it in Bolt
+
+1. In Bolt's **Add skill from GitHub**, paste this repo's URL —
+ `https://github.com/inkko44/bolt-slides-skill`.
+2. The `slides` skill auto-discovers at `.bolt/skills/slides.md`.
+3. Tell Bolt to use the `slides` skill and build a deck about your topic/brand.
+
+## Run it locally
+
+```bash
+npm install
+npm run dev
+```
+
+`npm run dev` opens the deck at `/`. Re-theme everything by editing one `:root` block
+in `src/styles/tokens.css`.
diff --git a/bolt-slides/index.html b/bolt-slides/index.html
new file mode 100644
index 00000000..56383bc9
--- /dev/null
+++ b/bolt-slides/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ Replace — your deck title
+
+
+
+
+
+
+
diff --git a/bolt-slides/package-lock.json b/bolt-slides/package-lock.json
new file mode 100644
index 00000000..4b5d7aae
--- /dev/null
+++ b/bolt-slides/package-lock.json
@@ -0,0 +1,1779 @@
+{
+ "name": "slides",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "slides",
+ "dependencies": {
+ "framer-motion": "^11.3.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
+ "@vitejs/plugin-react": "^4.3.1",
+ "typescript": "^5.5.3",
+ "vite": "^5.4.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
+ "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
+ "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
+ "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.7"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
+ "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
+ "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
+ "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
+ "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
+ "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz",
+ "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz",
+ "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz",
+ "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz",
+ "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz",
+ "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz",
+ "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz",
+ "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz",
+ "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz",
+ "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz",
+ "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz",
+ "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz",
+ "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz",
+ "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz",
+ "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz",
+ "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz",
+ "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz",
+ "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz",
+ "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz",
+ "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz",
+ "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz",
+ "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz",
+ "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz",
+ "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz",
+ "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.31",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz",
+ "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.40",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz",
+ "integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.4",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz",
+ "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.10.38",
+ "caniuse-lite": "^1.0.30001799",
+ "electron-to-chromium": "^1.5.376",
+ "node-releases": "^2.0.48",
+ "update-browserslist-db": "^1.2.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001800",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz",
+ "integrity": "sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.382",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.382.tgz",
+ "integrity": "sha512-8ETaWbV6SZOrno+G93Ffd9ENsMtetqdnqj4nlfxFW90Sm5GgnuV28Kf62hqQVD6VUgzm7qFQKsTsAPmeUiU3Ug==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/framer-motion": {
+ "version": "11.18.2",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz",
+ "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^11.18.1",
+ "motion-utils": "^11.18.1",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/motion-dom": {
+ "version": "11.18.1",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz",
+ "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^11.18.1"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "11.18.1",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz",
+ "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==",
+ "license": "MIT"
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.15",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
+ "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.50",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz",
+ "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/postcss": {
+ "version": "8.5.16",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz",
+ "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.12",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
+ "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.9"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.62.2",
+ "@rollup/rollup-android-arm64": "4.62.2",
+ "@rollup/rollup-darwin-arm64": "4.62.2",
+ "@rollup/rollup-darwin-x64": "4.62.2",
+ "@rollup/rollup-freebsd-arm64": "4.62.2",
+ "@rollup/rollup-freebsd-x64": "4.62.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.62.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.62.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.62.2",
+ "@rollup/rollup-linux-arm64-musl": "4.62.2",
+ "@rollup/rollup-linux-loong64-gnu": "4.62.2",
+ "@rollup/rollup-linux-loong64-musl": "4.62.2",
+ "@rollup/rollup-linux-ppc64-gnu": "4.62.2",
+ "@rollup/rollup-linux-ppc64-musl": "4.62.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.62.2",
+ "@rollup/rollup-linux-riscv64-musl": "4.62.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.62.2",
+ "@rollup/rollup-linux-x64-gnu": "4.62.2",
+ "@rollup/rollup-linux-x64-musl": "4.62.2",
+ "@rollup/rollup-openbsd-x64": "4.62.2",
+ "@rollup/rollup-openharmony-arm64": "4.62.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.62.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.62.2",
+ "@rollup/rollup-win32-x64-gnu": "4.62.2",
+ "@rollup/rollup-win32-x64-msvc": "4.62.2",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ }
+ }
+}
diff --git a/bolt-slides/package.json b/bolt-slides/package.json
new file mode 100644
index 00000000..40aae25c
--- /dev/null
+++ b/bolt-slides/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "slides",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "framer-motion": "^11.3.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
+ "@vitejs/plugin-react": "^4.3.1",
+ "typescript": "^5.5.3",
+ "vite": "^5.4.0"
+ }
+}
diff --git a/bolt-slides/src/App.tsx b/bolt-slides/src/App.tsx
new file mode 100644
index 00000000..e7769f2f
--- /dev/null
+++ b/bolt-slides/src/App.tsx
@@ -0,0 +1,744 @@
+import Deck from './deck/Deck';
+import Slide from './deck/Slide';
+import Build from './deck/Build';
+import Reveal from './deck/Reveal';
+import Bento from './components/Bento';
+import Split from './components/Split';
+import CountUp from './components/CountUp';
+import TiltCard from './components/TiltCard';
+import Marquee from './components/Marquee';
+import VisualDashboard from './components/VisualDashboard';
+import StatGrid from './components/StatGrid';
+import Accordion from './components/Accordion';
+import Comparison from './components/Comparison';
+import Tabs from './components/Tabs';
+import Timeline from './components/Timeline';
+import CodeWindow from './components/CodeWindow';
+import BrowserFrame from './components/BrowserFrame';
+import SpotlightCard from './components/SpotlightCard';
+import { BarChart, LineChart, DonutChart } from './components/Charts';
+
+/* ══════════════════════════════════════════════════════════════════════
+ ⚠️ THROWAWAY DEMO showing every component. DELETE these slides and AUTHOR
+ THE USER'S DECK. Each child of is one slide. Add speaker notes with
+ notes="…" on any slide (shown in presenter mode — press P). (A = auto-play.)
+ ══════════════════════════════════════════════════════════════════════ */
+const panel = (extra = 0.22): React.CSSProperties => ({
+ position: 'absolute',
+ inset: 0,
+ background: `radial-gradient(120% 100% at 30% 20%, color-mix(in srgb, var(--primary) ${
+ extra * 100
+ }%, transparent), transparent 60%), var(--surface-2)`,
+});
+const card: React.CSSProperties = {
+ padding: 22,
+ borderRadius: 'var(--radius)',
+ background: 'var(--surface)',
+ border: '1px solid var(--hair)',
+};
+
+export default function App() {
+ return (
+
+ {/* Cover */}
+
+
+
+ Helix · Component demo
+
+
+ Helix
+
+
+ A responsive React deck engine. Delete this and build the real one.
+
+
+
+
+ {/* Statement + click-build */}
+
+
+ Dashboards are everywhere.{' '}
+ Insight isn't.
+
+
+
+ Helix turns raw events into answers — automatically.
+
+
+
+
+ {/* Split feature */}
+
+ Everything, as it happens.
+ >
+ }
+ body="Live metrics with sub-second latency — no pipelines to babysit."
+ media={
+ <>
+
+
+
+
+
+
+ >
+ }
+ />
+
+ {/* Bento */}
+ ,
+ body: 'events / min at peak.',
+ c: 5,
+ r: 2,
+ variant: 'glow',
+ },
+ {
+ k: 'Uptime',
+ fig: ,
+ c: 4,
+ },
+ { k: 'Regions', fig: , c: 3, variant: 'accent' },
+ {
+ k: 'Connectors',
+ title: '120+ native',
+ body: 'Snowflake, Kafka, dbt…',
+ c: 4,
+ },
+ { k: 'Compliance', title: 'SOC 2 · HIPAA', c: 3 },
+ ]}
+ />
+
+ {/* StatGrid — traction */}
+ ,
+ label: 'ARR',
+ caption: 'up 3× year over year',
+ },
+ {
+ value: ,
+ label: 'Net retention',
+ caption: 'best in class',
+ },
+ {
+ value: ,
+ label: 'Enterprise logos',
+ caption: 'across six industries',
+ },
+ ]}
+ />
+
+ {/* Comparison */}
+
+
+
+ Why teams switch
+
+
+ The honest comparison.
+
+
+
+
+
+
+
+
+
+ {/* Tabs */}
+
+
+
+ One platform
+
+
+ Built for every team.
+
+
+
+
+ Trace any request end-to-end, alert on anomalies, ship with
+ confidence.
+
+ ),
+ },
+ {
+ label: 'Data',
+ content: (
+
+
+
+ ),
+ },
+ {
+ label: 'Ops',
+ content: (
+
+ One source of truth for uptime, cost, and capacity — no
+ spreadsheets.
+
+ ),
+ },
+ ]}
+ />
+
+
+
+ {/* Split + code */}
+
+ Drop-in simple.
+ >
+ }
+ body="Add it to your app in three lines. No SDK to learn, no schema to define."
+ media={
+ <>
+
+
+
+
+ >
+ }
+ />
+
+ {/* Browser frame */}
+
+
+
+ See it live
+
+
+ Your data, one screen.
+
+
+
+
+
+
+
+
+ Helix
+
+ {['Overview', 'Events', 'Funnels', 'Cohorts', 'Settings'].map(
+ (n, i) => (
+
+ {n}
+
+ )
+ )}
+
+
+
+
+ Overview
+
+ Last 30 days
+
+
+ {[
+ ['Revenue', '$1.24M', '▲ 18.2%'],
+ ['Active users', '48,210', '▲ 9.4%'],
+ ['Churn', '1.9%', '▼ 0.6%'],
+ ].map(([l, v, d]) => (
+
+
+ {l}
+
+
+ {v}
+
+
+ {d}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+ {/* Charts */}
+
+
+
+ The numbers
+
+
+ Growth you can see.
+
+
+
+
+
+
+ Weekly active
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Timeline */}
+
+
+
+ Where we're going
+
+
+ The roadmap.
+
+
+
+
+
+
+
+ {/* Spotlight principles */}
+
+
+
+ What we believe
+
+
+ Three principles.
+
+
+
+
+ {[
+ {
+ k: '01',
+ t: 'Fast by default',
+ d: 'Speed is a feature. Everything is realtime.',
+ },
+ {
+ k: '02',
+ t: 'Yours to own',
+ d: 'Your data, your infra, no lock-in.',
+ },
+ {
+ k: '03',
+ t: 'Honest pricing',
+ d: 'No per-seat tax. Scale without surprises.',
+ },
+ ].map((p) => (
+
+
+ {p.k}
+
+
+ {p.t}
+
+
+ {p.d}
+
+
+ ))}
+
+
+
+
+ {/* Accordion — FAQ */}
+
+
+
+ Common questions
+
+
+ Frequently asked.
+
+
+
+
+
+
+
+ {/* Logos */}
+
+
+
+ Trusted by teams everywhere
+
+
+
+
+
+ {/* Quote */}
+
+
+
+ “We replaced four tools with Helix and never looked back.”
+
+
+ — Dana Kim, VP Engineering
+
+
+
+
+ {/* CTA */}
+
+
+
+ Let's talk.
+
+
+ helix.io · hello@helix.io
+
+
+
+
+ );
+}
diff --git a/bolt-slides/src/components/Accordion.tsx b/bolt-slides/src/components/Accordion.tsx
new file mode 100644
index 00000000..9e4897cd
--- /dev/null
+++ b/bolt-slides/src/components/Accordion.tsx
@@ -0,0 +1,75 @@
+import { useState, type ReactNode } from 'react';
+import { AnimatePresence, motion } from 'framer-motion';
+
+/* An expand/collapse accordion. Data-driven like Bento/StatGrid.
+
+ - single (default true): only one panel open at a time.
+ - defaultOpen: index open on mount (null = all closed). */
+export type AccordionItem = { title: string; body: ReactNode };
+
+const Chevron = () => (
+
+
+
+);
+
+export default function Accordion({
+ items,
+ single = true,
+ defaultOpen = 0,
+}: {
+ items: AccordionItem[];
+ single?: boolean;
+ defaultOpen?: number | null;
+}) {
+ const [open, setOpen] = useState(
+ defaultOpen == null ? [] : [defaultOpen]
+ );
+ const isOpen = (i: number) => open.includes(i);
+ const toggle = (i: number) =>
+ setOpen((cur) => {
+ const has = cur.includes(i);
+ if (single) return has ? [] : [i];
+ return has ? cur.filter((x) => x !== i) : [...cur, i];
+ });
+
+ return (
+
+ {items.map((it, i) => (
+
+
toggle(i)}
+ aria-expanded={isOpen(i)}
+ >
+ {String(i + 1).padStart(2, '0')}
+ {it.title}
+
+
+
+
+
+ {isOpen(i) && (
+
+ {it.body}
+
+ )}
+
+
+ ))}
+
+ );
+}
diff --git a/bolt-slides/src/components/Bento.tsx b/bolt-slides/src/components/Bento.tsx
new file mode 100644
index 00000000..b9984966
--- /dev/null
+++ b/bolt-slides/src/components/Bento.tsx
@@ -0,0 +1,73 @@
+import type { CSSProperties, ReactNode } from 'react';
+import Reveal from '../deck/Reveal';
+
+/* A full-viewport slide laid out as an asymmetric bento grid. Spans via c
+ (columns of 12) and r (rows). Collapses to one column on narrow screens.
+ nav/notes are read by the engine (rail label / presenter notes). */
+export type BentoTile = {
+ k?: string;
+ fig?: ReactNode;
+ title?: string;
+ body?: string;
+ c?: number;
+ r?: number;
+ variant?: 'accent' | 'glow';
+};
+
+export default function Bento({
+ kicker,
+ title,
+ tiles,
+}: {
+ kicker?: string;
+ title?: string;
+ tiles: BentoTile[];
+ nav?: string;
+ notes?: string;
+}) {
+ return (
+
+
+
+ {kicker && (
+
+ {kicker}
+
+ )}
+ {title && (
+
+ {title}
+
+ )}
+
+
+ {tiles.map((t, i) => (
+
+ {t.k &&
{t.k}
}
+
+ {t.fig != null && (
+
+ {t.fig}
+
+ )}
+ {t.title &&
{t.title} }
+ {t.body &&
{t.body}
}
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/bolt-slides/src/components/BrowserFrame.tsx b/bolt-slides/src/components/BrowserFrame.tsx
new file mode 100644
index 00000000..ccb9ba44
--- /dev/null
+++ b/bolt-slides/src/components/BrowserFrame.tsx
@@ -0,0 +1,25 @@
+import type { ReactNode } from 'react';
+
+/* Wrap a screenshot or UI mock in browser chrome. children fill the body —
+ an , a , anything. */
+export default function BrowserFrame({
+ url = 'app.acme.com',
+ children,
+}: {
+ url?: string;
+ children: ReactNode;
+}) {
+ return (
+
+
+
+
+
+
+
+ {url}
+
+
{children}
+
+ );
+}
diff --git a/bolt-slides/src/components/Charts.tsx b/bolt-slides/src/components/Charts.tsx
new file mode 100644
index 00000000..9cc1fa99
--- /dev/null
+++ b/bolt-slides/src/components/Charts.tsx
@@ -0,0 +1,157 @@
+import { motion } from 'framer-motion';
+import type { CSSProperties } from 'react';
+import { useInView } from '../deck/useInView';
+
+/* A small hand-built chart kit — bar, line/area, and donut. Each draws itself in
+ when scrolled into view. All token-driven (one accent), no chart library.
+
+
+ */
+
+export function BarChart({
+ data,
+ height = 200,
+}: {
+ data: { label: string; value: number }[];
+ height?: number;
+}) {
+ const { ref, inView } = useInView(0.3);
+ const max = Math.max(...data.map((d) => d.value)) || 1;
+ return (
+
+ {data.map((d, i) => (
+
+ ))}
+
+ );
+}
+
+export function LineChart({
+ points,
+ height = 200,
+}: {
+ points: number[];
+ height?: number;
+}) {
+ const { ref, inView } = useInView(0.3);
+ const w = 300,
+ h = 120;
+ const max = Math.max(...points),
+ min = Math.min(...points);
+ const span = max - min || 1;
+ const coords = points.map((p, i) => [
+ (i / (points.length - 1)) * w,
+ h - ((p - min) / span) * (h - 10) - 5,
+ ]);
+ const line = coords
+ .map(
+ (c, i) => `${i === 0 ? 'M' : 'L'}${c[0].toFixed(1)},${c[1].toFixed(1)}`
+ )
+ .join(' ');
+ const area = `${line} L${w},${h} L0,${h} Z`;
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export function DonutChart({
+ value,
+ label,
+ size = 168,
+}: {
+ value: number;
+ label?: string;
+ size?: number;
+}) {
+ const { ref, inView } = useInView(0.4);
+ const r = 54;
+ const circ = 2 * Math.PI * r;
+ return (
+
+
+
+
+
+ {value}%
+
+
+ {label &&
{label}
}
+
+ );
+}
diff --git a/bolt-slides/src/components/CodeWindow.tsx b/bolt-slides/src/components/CodeWindow.tsx
new file mode 100644
index 00000000..fa8a20be
--- /dev/null
+++ b/bolt-slides/src/components/CodeWindow.tsx
@@ -0,0 +1,80 @@
+import { Fragment, type ReactNode } from 'react';
+
+/* A macOS-style code window with line numbers, restrained syntax tinting, and
+ optional line highlighting. Highlighting is intentionally minimal (keywords,
+ strings, comments) to stay on-brand — one accent, no rainbow.
+ */
+const KW =
+ /\b(const|let|var|function|return|if|else|for|while|import|from|export|default|class|new|await|async|type|interface|extends|implements|public|private|true|false|null|undefined)\b/g;
+
+function hl(line: string): ReactNode {
+ // split off a trailing line comment
+ const ci = line.indexOf('//');
+ const code = ci >= 0 ? line.slice(0, ci) : line;
+ const comment = ci >= 0 ? line.slice(ci) : '';
+ // strings first (odd indices), then keywords inside the rest
+ const segs = code.split(
+ /("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)/g
+ );
+ return (
+ <>
+ {segs.map((seg, i) =>
+ i % 2 === 1 ? (
+
+ {seg}
+
+ ) : (
+
+ {seg.split(KW).map((s, j) =>
+ j % 2 === 1 ? (
+
+ {s}
+
+ ) : (
+ s
+ )
+ )}
+
+ )
+ )}
+ {comment && {comment} }
+ >
+ );
+}
+
+export default function CodeWindow({
+ title = 'app.tsx',
+ code,
+ highlight = [],
+}: {
+ title?: string;
+ code: string;
+ highlight?: number[];
+}) {
+ const lines = code.replace(/\n$/, '').split('\n');
+ return (
+
+
+
+
+
+
+
+ {title}
+
+
+
+ {lines.map((ln, i) => (
+
+ {i + 1}
+ {ln.length ? hl(ln) : ' '}
+
+ ))}
+
+
+
+ );
+}
diff --git a/bolt-slides/src/components/Comparison.tsx b/bolt-slides/src/components/Comparison.tsx
new file mode 100644
index 00000000..28c420a2
--- /dev/null
+++ b/bolt-slides/src/components/Comparison.tsx
@@ -0,0 +1,82 @@
+/* A "us vs. them" feature matrix. One value column is highlighted (your column).
+
+ values are booleans (→ ✓/✗) or strings; `highlight` indexes the value columns. */
+import type { CSSProperties } from 'react';
+
+export type CompRow = { label: string; values: (boolean | string)[] };
+
+const Check = () => (
+
+
+
+);
+const Cross = () => (
+
+
+
+);
+
+export default function Comparison({
+ cols,
+ rows,
+ highlight = 0,
+}: {
+ cols: string[];
+ rows: CompRow[];
+ highlight?: number;
+}) {
+ const vcols = cols.length - 1;
+ return (
+
+
+ {cols.map((c, i) => (
+
+ {c}
+
+ ))}
+
+ {rows.map((r, ri) => (
+
+
{r.label}
+ {r.values.map((v, vi) => (
+
+ {typeof v === 'boolean' ? (
+ v ? (
+
+ ) : (
+
+ )
+ ) : (
+ {v}
+ )}
+
+ ))}
+
+ ))}
+
+ );
+}
diff --git a/bolt-slides/src/components/CountUp.tsx b/bolt-slides/src/components/CountUp.tsx
new file mode 100644
index 00000000..8362a4df
--- /dev/null
+++ b/bolt-slides/src/components/CountUp.tsx
@@ -0,0 +1,77 @@
+import { useEffect, useRef, useState } from 'react';
+import { useDeck } from '../deck/DeckContext';
+
+/* A number that animates 0 → to when its slide becomes active. Inherits color,
+ so inside a .figure / .stat-value it picks up the accent gradient. Renders
+ the final value instantly in thumbnails. */
+export default function CountUp({
+ to,
+ from = 0,
+ duration = 1500,
+ decimals = 0,
+ prefix = '',
+ suffix = '',
+}: {
+ to: number;
+ from?: number;
+ duration?: number;
+ decimals?: number;
+ prefix?: string;
+ suffix?: string;
+}) {
+ const { isStatic } = useDeck();
+ const ref = useRef(null);
+ const [val, setVal] = useState(isStatic ? to : from);
+ const started = useRef(false);
+
+ useEffect(() => {
+ if (isStatic) {
+ setVal(to);
+ return;
+ }
+ const el = ref.current;
+ if (!el) return;
+ let raf = 0;
+ const run = () => {
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
+ setVal(to);
+ return;
+ }
+ const t0 = performance.now();
+ const tick = (now: number) => {
+ const p = Math.min(1, (now - t0) / duration);
+ setVal(from + (to - from) * (1 - Math.pow(1 - p, 3)));
+ if (p < 1) raf = requestAnimationFrame(tick);
+ };
+ raf = requestAnimationFrame(tick);
+ };
+ const io = new IntersectionObserver(
+ (entries) =>
+ entries.forEach((e) => {
+ if (e.isIntersecting && !started.current) {
+ started.current = true;
+ run();
+ }
+ }),
+ { threshold: 0.4 }
+ );
+ io.observe(el);
+ return () => {
+ io.disconnect();
+ cancelAnimationFrame(raf);
+ };
+ }, [isStatic, to, from, duration]);
+
+ const text =
+ prefix +
+ val.toLocaleString('en-US', {
+ minimumFractionDigits: decimals,
+ maximumFractionDigits: decimals,
+ }) +
+ suffix;
+ return (
+
+ {text}
+
+ );
+}
diff --git a/bolt-slides/src/components/Marquee.tsx b/bolt-slides/src/components/Marquee.tsx
new file mode 100644
index 00000000..8a1141c2
--- /dev/null
+++ b/bolt-slides/src/components/Marquee.tsx
@@ -0,0 +1,23 @@
+/* A continuously-scrolling strip — logo wall, value props, tech stack. */
+export default function Marquee({
+ items,
+ duration = 26,
+}: {
+ items: string[];
+ duration?: number;
+}) {
+ return (
+
+
+ {[...items, ...items].map((it, i) => (
+
+ {it}
+
+ ))}
+
+
+ );
+}
diff --git a/bolt-slides/src/components/Split.tsx b/bolt-slides/src/components/Split.tsx
new file mode 100644
index 00000000..a5d08afd
--- /dev/null
+++ b/bolt-slides/src/components/Split.tsx
@@ -0,0 +1,48 @@
+import type { ReactNode } from 'react';
+import Reveal from '../deck/Reveal';
+
+/* A full-bleed feature slide: text one side, edge-to-edge media the other.
+ `flip` swaps sides. Stacks vertically on narrow screens. */
+export default function Split({
+ kicker,
+ title,
+ body,
+ media,
+ flip,
+}: {
+ kicker?: string;
+ title: ReactNode;
+ body?: ReactNode;
+ media: ReactNode;
+ flip?: boolean;
+ nav?: string;
+ notes?: string;
+}) {
+ return (
+
+
+
+
+ {kicker && (
+
+ {kicker}
+
+ )}
+
+ {title}
+
+ {body && (
+
+ {body}
+
+ )}
+
+
+
{media}
+
+
+ );
+}
diff --git a/bolt-slides/src/components/SpotlightCard.tsx b/bolt-slides/src/components/SpotlightCard.tsx
new file mode 100644
index 00000000..129627c1
--- /dev/null
+++ b/bolt-slides/src/components/SpotlightCard.tsx
@@ -0,0 +1,20 @@
+import { useRef, type ReactNode } from 'react';
+
+/* A card with a soft accent glow that follows the cursor (the Linear/Vercel
+ hover). Wrap content; it provides the card shell + padding.
+ …
… …
*/
+export default function SpotlightCard({ children }: { children: ReactNode }) {
+ const ref = useRef(null);
+ function move(e: React.MouseEvent) {
+ const el = ref.current;
+ if (!el) return;
+ const r = el.getBoundingClientRect();
+ el.style.setProperty('--mx', `${e.clientX - r.left}px`);
+ el.style.setProperty('--my', `${e.clientY - r.top}px`);
+ }
+ return (
+
+ {children}
+
+ );
+}
diff --git a/bolt-slides/src/components/StatGrid.tsx b/bolt-slides/src/components/StatGrid.tsx
new file mode 100644
index 00000000..a6cc3d3b
--- /dev/null
+++ b/bolt-slides/src/components/StatGrid.tsx
@@ -0,0 +1,56 @@
+import type { ReactNode } from 'react';
+import Reveal from '../deck/Reveal';
+
+/* A full-viewport proof slide: responsive auto-fit stat cards. Pass a
+ as a stat `value` so figures animate in. */
+export type Stat = { value?: ReactNode; label: string; caption?: string };
+
+export default function StatGrid({
+ kicker,
+ title,
+ stats,
+}: {
+ kicker?: string;
+ title?: string;
+ stats: Stat[];
+ nav?: string;
+ notes?: string;
+}) {
+ return (
+
+
+
+ {kicker && (
+
+ {kicker}
+
+ )}
+ {title && (
+
+ {title}
+
+ )}
+
+
+ {stats.map((s, i) => (
+
+
{s.value}
+
{s.label}
+ {s.caption &&
{s.caption}
}
+
+ ))}
+
+
+
+ );
+}
diff --git a/bolt-slides/src/components/Tabs.tsx b/bolt-slides/src/components/Tabs.tsx
new file mode 100644
index 00000000..309da5fa
--- /dev/null
+++ b/bolt-slides/src/components/Tabs.tsx
@@ -0,0 +1,53 @@
+import { useState, type ReactNode } from 'react';
+import { AnimatePresence, motion } from 'framer-motion';
+
+/* Tabbed content with a sliding accent pill (framer layoutId) and a cross-fade
+ between panels. }, …]} /> */
+export type Tab = { label: string; content: ReactNode };
+
+export default function Tabs({
+ tabs,
+ defaultTab = 0,
+}: {
+ tabs: Tab[];
+ defaultTab?: number;
+}) {
+ const [active, setActive] = useState(defaultTab);
+ return (
+
+
+ {tabs.map((t, i) => (
+ setActive(i)}
+ role="tab"
+ aria-selected={i === active}
+ >
+ {i === active && (
+
+ )}
+ {t.label}
+
+ ))}
+
+
+
+
+ {tabs[active].content}
+
+
+
+
+ );
+}
diff --git a/bolt-slides/src/components/TiltCard.tsx b/bolt-slides/src/components/TiltCard.tsx
new file mode 100644
index 00000000..29681e72
--- /dev/null
+++ b/bolt-slides/src/components/TiltCard.tsx
@@ -0,0 +1,33 @@
+import { useRef, useState, type CSSProperties, type ReactNode } from 'react';
+
+/* Mouse-tracked 3D tilt + glare. Wrap one hero visual per slide. */
+export default function TiltCard({ children }: { children: ReactNode }) {
+ const ref = useRef(null);
+ const [style, setStyle] = useState({});
+
+ function move(e: React.MouseEvent) {
+ const r = ref.current!.getBoundingClientRect();
+ const px = (e.clientX - r.left) / r.width - 0.5;
+ const py = (e.clientY - r.top) / r.height - 0.5;
+ setStyle({
+ transform: `perspective(1200px) rotateX(${(-py * 6).toFixed(
+ 2
+ )}deg) rotateY(${(px * 9).toFixed(2)}deg)`,
+ ['--gx' as string]: `${((px + 0.5) * 100).toFixed(1)}%`,
+ ['--gy' as string]: `${((py + 0.5) * 100).toFixed(1)}%`,
+ } as CSSProperties);
+ }
+
+ return (
+ setStyle({})}
+ >
+ {children}
+
+
+ );
+}
diff --git a/bolt-slides/src/components/Timeline.tsx b/bolt-slides/src/components/Timeline.tsx
new file mode 100644
index 00000000..05b825e6
--- /dev/null
+++ b/bolt-slides/src/components/Timeline.tsx
@@ -0,0 +1,46 @@
+import { motion } from 'framer-motion';
+import type { ReactNode } from 'react';
+import { useInView } from '../deck/useInView';
+
+/* A vertical timeline / roadmap. The connector line draws in and milestones
+ reveal in sequence when scrolled into view.
+ */
+export type TimelineItem = { time: string; title: string; body?: ReactNode };
+
+export default function Timeline({ items }: { items: TimelineItem[] }) {
+ const { ref, inView } = useInView(0.2);
+ return (
+
+
+
+
+
+ {items.map((it, i) => (
+
+
+
+
{it.time}
+
{it.title}
+ {it.body &&
{it.body}
}
+
+
+ ))}
+
+
+ );
+}
diff --git a/bolt-slides/src/components/VisualDashboard.tsx b/bolt-slides/src/components/VisualDashboard.tsx
new file mode 100644
index 00000000..8295ca7f
--- /dev/null
+++ b/bolt-slides/src/components/VisualDashboard.tsx
@@ -0,0 +1,117 @@
+import { useEffect, useRef, useState } from 'react';
+import CountUp from './CountUp';
+import { useDeck } from '../deck/DeckContext';
+
+/* Example analytics visual (a self-drawing dashboard mock). ONLY use it when
+ the deck is genuinely about data — otherwise build a topic-fit visual in the
+ same .vframe shell. Draws in when its slide becomes active. */
+const bars = [44, 62, 55, 78, 96, 70, 58];
+const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
+
+export default function VisualDashboard() {
+ const { isStatic } = useDeck();
+ const ref = useRef(null);
+ const [shown, setShown] = useState(isStatic);
+
+ useEffect(() => {
+ if (isStatic) {
+ setShown(true);
+ return;
+ }
+ const el = ref.current;
+ if (!el) return;
+ const io = new IntersectionObserver(
+ (entries) =>
+ entries.forEach((e) => {
+ if (e.isIntersecting) setShown(true);
+ }),
+ { threshold: 0.3 }
+ );
+ io.observe(el);
+ return () => io.disconnect();
+ }, [isStatic]);
+
+ return (
+
+
+
+
+ Overview · Last 30 days
+
+
+
+ Live
+
+
+
+
+
Revenue
+
+
+
+
▲ 18.2%
+
+
+
Active users
+
+
+
+
▲ 9.4%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {bars.map((h, i) => (
+
+ ))}
+
+
+ );
+}
diff --git a/bolt-slides/src/deck/Annotator.tsx b/bolt-slides/src/deck/Annotator.tsx
new file mode 100644
index 00000000..d17fac14
--- /dev/null
+++ b/bolt-slides/src/deck/Annotator.tsx
@@ -0,0 +1,364 @@
+import { useEffect, useRef, useState } from 'react';
+
+/* State-of-the-art annotation: a full-screen canvas + a floating tool bar.
+ Tools: pen, highlighter, line, arrow, rectangle, ellipse, eraser; a color
+ palette; three sizes; undo + clear. Strokes are stored as data (per slide,
+ in `store`) so they PERSIST on the slide they were made on, and undo /
+ stroke-erase work cleanly. The bar + drawing are only interactive when
+ `active`; otherwise the canvas just displays the slide's saved annotations. */
+
+type Tool =
+ | 'pen'
+ | 'highlighter'
+ | 'line'
+ | 'arrow'
+ | 'rect'
+ | 'ellipse'
+ | 'eraser';
+type Pt = { x: number; y: number };
+export type Stroke = { tool: Tool; color: string; size: number; points: Pt[] };
+
+const TOOLS: { id: Tool; label: string; path: string }[] = [
+ { id: 'pen', label: 'Pen', path: 'M4 20h4L18 10a2 2 0 0 0-3-3L5 17z' },
+ {
+ id: 'highlighter',
+ label: 'Highlighter',
+ path: 'M4 20h5l8-8-4-4-9 9zM13 7l4 4',
+ },
+ { id: 'line', label: 'Line', path: 'M5 19L19 5' },
+ { id: 'arrow', label: 'Arrow', path: 'M6 18L18 6M18 6h-6M18 6v6' },
+ { id: 'rect', label: 'Rectangle', path: 'M4 6h16v12H4z' },
+ {
+ id: 'ellipse',
+ label: 'Ellipse',
+ path: 'M12 6c4.5 0 8 2.7 8 6s-3.5 6-8 6-8-2.7-8-6 3.5-6 8-6z',
+ },
+ {
+ id: 'eraser',
+ label: 'Eraser',
+ path: 'M8 18l-4-4a2 2 0 0 1 0-3l7-7a2 2 0 0 1 3 0l4 4a2 2 0 0 1 0 3l-7 7zM7 17h11',
+ },
+];
+const COLORS = ['var(--primary)', '#ffffff', '#ef4444', '#f5b73a', '#4aa8ff'];
+const SIZES = [3, 6, 11];
+
+const IconUndo = () => (
+
+
+
+
+);
+const IconTrash = () => (
+
+
+
+);
+
+function distToSeg(
+ px: number,
+ py: number,
+ ax: number,
+ ay: number,
+ bx: number,
+ by: number
+) {
+ const dx = bx - ax,
+ dy = by - ay;
+ const len = dx * dx + dy * dy || 1;
+ let t = ((px - ax) * dx + (py - ay) * dy) / len;
+ t = Math.max(0, Math.min(1, t));
+ return Math.hypot(px - (ax + t * dx), py - (ay + t * dy));
+}
+function outline(s: Stroke): Pt[] {
+ if (s.points.length < 2) return s.points;
+ const a = s.points[0],
+ b = s.points[s.points.length - 1];
+ if (s.tool === 'rect')
+ return [a, { x: b.x, y: a.y }, b, { x: a.x, y: b.y }, a];
+ if (s.tool === 'ellipse') {
+ const cx = (a.x + b.x) / 2,
+ cy = (a.y + b.y) / 2,
+ rx = Math.abs(b.x - a.x) / 2,
+ ry = Math.abs(b.y - a.y) / 2;
+ return Array.from({ length: 25 }, (_, i) => ({
+ x: cx + rx * Math.cos((i / 24) * 2 * Math.PI),
+ y: cy + ry * Math.sin((i / 24) * 2 * Math.PI),
+ }));
+ }
+ return s.points;
+}
+function hits(s: Stroke, x: number, y: number, r: number) {
+ const pts = outline(s);
+ if (pts.length === 1)
+ return Math.hypot(pts[0].x - x, pts[0].y - y) < r + s.size;
+ for (let i = 0; i < pts.length - 1; i++)
+ if (
+ distToSeg(x, y, pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y) <
+ r + s.size / 2
+ )
+ return true;
+ return false;
+}
+
+export default function Annotator({
+ slide,
+ store,
+ active,
+}: {
+ slide: number;
+ store: Record;
+ active: boolean;
+}) {
+ const canvasRef = useRef(null);
+ if (!store[slide]) store[slide] = [];
+ const strokes = useRef(store[slide]);
+ const draft = useRef(null);
+ const [tool, setTool] = useState('pen');
+ const [color, setColor] = useState(COLORS[0]);
+ const [size, setSize] = useState(SIZES[1]);
+ const toolRef = useRef(tool),
+ colorRef = useRef(color),
+ sizeRef = useRef(size);
+ toolRef.current = tool;
+ colorRef.current = color;
+ sizeRef.current = size;
+
+ const resolve = (c: string) =>
+ c.startsWith('var(')
+ ? getComputedStyle(document.documentElement)
+ .getPropertyValue('--primary')
+ .trim() || '#4fe5b0'
+ : c;
+
+ function paint(ctx: CanvasRenderingContext2D, s: Stroke) {
+ if (!s.points.length) return;
+ ctx.save();
+ ctx.strokeStyle = resolve(s.color);
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+ ctx.lineWidth = s.size;
+ if (s.tool === 'highlighter') {
+ ctx.globalAlpha = 0.32;
+ ctx.lineWidth = s.size * 3.2;
+ }
+ const p = s.points,
+ a = p[0],
+ b = p[p.length - 1];
+ ctx.beginPath();
+ if (s.tool === 'pen' || s.tool === 'highlighter') {
+ ctx.moveTo(a.x, a.y);
+ for (let i = 1; i < p.length; i++) ctx.lineTo(p[i].x, p[i].y);
+ ctx.stroke();
+ } else if (s.tool === 'line' || s.tool === 'arrow') {
+ ctx.moveTo(a.x, a.y);
+ ctx.lineTo(b.x, b.y);
+ ctx.stroke();
+ if (s.tool === 'arrow') {
+ const ang = Math.atan2(b.y - a.y, b.x - a.x),
+ h = 8 + s.size * 1.8;
+ ctx.beginPath();
+ ctx.moveTo(b.x, b.y);
+ ctx.lineTo(
+ b.x - h * Math.cos(ang - 0.4),
+ b.y - h * Math.sin(ang - 0.4)
+ );
+ ctx.moveTo(b.x, b.y);
+ ctx.lineTo(
+ b.x - h * Math.cos(ang + 0.4),
+ b.y - h * Math.sin(ang + 0.4)
+ );
+ ctx.stroke();
+ }
+ } else if (s.tool === 'rect') {
+ ctx.strokeRect(
+ Math.min(a.x, b.x),
+ Math.min(a.y, b.y),
+ Math.abs(b.x - a.x),
+ Math.abs(b.y - a.y)
+ );
+ } else if (s.tool === 'ellipse') {
+ ctx.ellipse(
+ (a.x + b.x) / 2,
+ (a.y + b.y) / 2,
+ Math.abs(b.x - a.x) / 2,
+ Math.abs(b.y - a.y) / 2,
+ 0,
+ 0,
+ Math.PI * 2
+ );
+ ctx.stroke();
+ }
+ ctx.restore();
+ }
+ function redraw() {
+ const cv = canvasRef.current;
+ if (!cv) return;
+ const ctx = cv.getContext('2d');
+ if (!ctx) return;
+ const dpr = window.devicePixelRatio || 1;
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+ ctx.clearRect(0, 0, cv.width / dpr, cv.height / dpr);
+ for (const s of strokes.current) paint(ctx, s);
+ if (draft.current) paint(ctx, draft.current);
+ }
+ function commit() {
+ store[slide] = strokes.current;
+ redraw();
+ }
+ function erase(x: number, y: number) {
+ const before = strokes.current.length;
+ strokes.current = strokes.current.filter((s) => !hits(s, x, y, 12));
+ if (strokes.current.length !== before) commit();
+ }
+
+ useEffect(() => {
+ const cv = canvasRef.current;
+ if (!cv) return;
+ const fit = () => {
+ const dpr = window.devicePixelRatio || 1;
+ cv.width = window.innerWidth * dpr;
+ cv.height = window.innerHeight * dpr;
+ cv.style.width = window.innerWidth + 'px';
+ cv.style.height = window.innerHeight + 'px';
+ redraw();
+ };
+ fit();
+ const onUp = () => {
+ if (draft.current) {
+ strokes.current.push(draft.current);
+ draft.current = null;
+ commit();
+ }
+ };
+ window.addEventListener('resize', fit);
+ window.addEventListener('pointerup', onUp);
+ return () => {
+ window.removeEventListener('resize', fit);
+ window.removeEventListener('pointerup', onUp);
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ function down(e: React.PointerEvent) {
+ const x = e.clientX,
+ y = e.clientY;
+ if (toolRef.current === 'eraser') {
+ erase(x, y);
+ return;
+ }
+ draft.current = {
+ tool: toolRef.current,
+ color: colorRef.current,
+ size: sizeRef.current,
+ points: [{ x, y }],
+ };
+ redraw();
+ }
+ function move(e: React.PointerEvent) {
+ const x = e.clientX,
+ y = e.clientY;
+ if (toolRef.current === 'eraser') {
+ if (e.buttons) erase(x, y);
+ return;
+ }
+ const d = draft.current;
+ if (!d) return;
+ if (d.tool === 'pen' || d.tool === 'highlighter') d.points.push({ x, y });
+ else d.points = [d.points[0], { x, y }];
+ redraw();
+ }
+
+ return (
+ <>
+
+ {active && (
+
+ {TOOLS.map((t) => (
+
setTool(t.id)}
+ >
+
+
+
+
+ ))}
+
+ {COLORS.map((c) => (
+
setColor(c)}
+ style={{ background: c }}
+ />
+ ))}
+
+ {SIZES.map((s) => (
+ setSize(s)}
+ >
+
+
+ ))}
+
+ {
+ strokes.current = strokes.current.slice(0, -1);
+ commit();
+ }}
+ >
+
+
+ {
+ strokes.current = [];
+ commit();
+ }}
+ >
+
+
+
+ )}
+ >
+ );
+}
diff --git a/bolt-slides/src/deck/Build.tsx b/bolt-slides/src/deck/Build.tsx
new file mode 100644
index 00000000..f93fdc87
--- /dev/null
+++ b/bolt-slides/src/deck/Build.tsx
@@ -0,0 +1,72 @@
+import { motion } from 'framer-motion';
+import { useEffect } from 'react';
+import type { CSSProperties, ReactNode } from 'react';
+import { useDeck } from './DeckContext';
+
+/* Click-build (the Slidev "v-click"): this element stays hidden until the
+ presenter advances to step `at` (1-based) on the current slide, then it
+ animates in. Reveal content in beats: … . The deck
+ advances builds before moving to the next slide.
+
+ In AUTO mode (Deck autoplay / the dock auto button / "A"), every build
+ instead animates in automatically on slide load, staggered by `at`. */
+export default function Build({
+ at = 1,
+ y = 16,
+ children,
+ className,
+ style,
+}: {
+ at?: number;
+ y?: number;
+ children: ReactNode;
+ className?: string;
+ style?: CSSProperties;
+}) {
+ const { clicks, isStatic, auto, stagger = 0.16, registerMax } = useDeck();
+ useEffect(() => {
+ registerMax?.(at);
+ }, [at, registerMax]);
+
+ if (isStatic)
+ return (
+
+ {children}
+
+ );
+
+ if (auto) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ const shown = clicks >= at;
+ return (
+
+ {children}
+
+ );
+}
diff --git a/bolt-slides/src/deck/Deck.tsx b/bolt-slides/src/deck/Deck.tsx
new file mode 100644
index 00000000..216e0c65
--- /dev/null
+++ b/bolt-slides/src/deck/Deck.tsx
@@ -0,0 +1,482 @@
+import {
+ Children,
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from 'react';
+import type { ReactElement, ReactNode } from 'react';
+import { MotionConfig } from 'framer-motion';
+import { DeckCtx } from './DeckContext';
+import Annotator, { type Stroke } from './Annotator';
+import {
+ IconGrid,
+ IconLeft,
+ IconRight,
+ IconPencil,
+ IconExpand,
+ IconShrink,
+ IconPresent,
+ IconClose,
+ IconAuto,
+} from './icons';
+
+/* ── The paged presentation engine + the Slidev-style chrome (dock + rail).
+ Wrap your //… in . Each top-level child is one slide.
+ → / ↓ / Space next (reveals the next , then the next slide)
+ ← / ↑ previous O overview F fullscreen
+ Home / End first / last D draw P presenter (new tab)
+ A auto-play H hide/show the UI
+ starts in auto mode; tunes the gap.
+ Copy verbatim; theme only via the :root tokens. ───────────────────────── */
+
+const fmt = (s: number) =>
+ `${String(Math.floor(s / 60)).padStart(2, '0')}:${String(s % 60).padStart(
+ 2,
+ '0'
+ )}`;
+
+function Thumb({ children }: { children: ReactNode }) {
+ const frameRef = useRef(null);
+ const [d, setD] = useState({ vw: 1280, vh: 720, scale: 0.15 });
+ useEffect(() => {
+ const el = frameRef.current;
+ if (!el) return;
+ const update = () =>
+ setD({
+ vw: window.innerWidth,
+ vh: window.innerHeight,
+ scale: el.clientWidth / window.innerWidth,
+ });
+ update();
+ const ro = new ResizeObserver(update);
+ ro.observe(el);
+ window.addEventListener('resize', update);
+ return () => {
+ ro.disconnect();
+ window.removeEventListener('resize', update);
+ };
+ }, []);
+ return (
+
+ );
+}
+
+export default function Deck({
+ children,
+ autoplay = false,
+ stagger = 0.16,
+}: {
+ children: ReactNode;
+ autoplay?: boolean;
+ stagger?: number;
+}) {
+ const slides = useMemo(
+ () => Children.toArray(children) as ReactElement[],
+ [children]
+ );
+ const total = slides.length;
+ const isPresenter = useMemo(
+ () => new URLSearchParams(window.location.search).has('presenter'),
+ []
+ );
+
+ const [slide, setSlide] = useState(() => {
+ const h = parseInt(window.location.hash.slice(1), 10);
+ return h >= 1 && h <= total ? h - 1 : 0;
+ });
+ const [clicks, setClicks] = useState(0);
+ const [curMax, setCurMax] = useState(0);
+ const [auto, setAuto] = useState(autoplay);
+ const [railOpen, setRailOpen] = useState(false);
+ const [drawing, setDrawing] = useState(false);
+ const [elapsed, setElapsed] = useState(0);
+ const [fs, setFs] = useState(false);
+ const [uiHidden, setUiHidden] = useState(false);
+ const [nearDock, setNearDock] = useState(false);
+ const [cursorIdle, setCursorIdle] = useState(false);
+ const [noteOverrides, setNoteOverrides] = useState>(
+ () => {
+ try {
+ return JSON.parse(localStorage.getItem('deck:notes') || '{}');
+ } catch {
+ return {};
+ }
+ }
+ );
+
+ // per-slide build maxima (so going back restores the right click state) and
+ // per-slide annotations (so drawings persist on the slide they were made).
+ const maxMap = useRef>({});
+ const annStore = useRef>({});
+ const slideRef = useRef(slide);
+ slideRef.current = slide;
+
+ const registerMax = useCallback((at: number) => {
+ const m = maxMap.current;
+ m[slideRef.current] = Math.max(m[slideRef.current] || 0, at);
+ setCurMax((c) => Math.max(c, at));
+ }, []);
+
+ const go = useCallback(
+ (i: number) => {
+ const n = Math.max(0, Math.min(total - 1, i));
+ setSlide(n);
+ setClicks(0);
+ setCurMax(maxMap.current[n] || 0);
+ },
+ [total]
+ );
+ const next = useCallback(() => {
+ if (!auto && clicks < curMax) {
+ setClicks(clicks + 1);
+ return;
+ }
+ if (slide < total - 1) {
+ const n = slide + 1;
+ setSlide(n);
+ setClicks(0);
+ setCurMax(maxMap.current[n] || 0);
+ }
+ }, [auto, clicks, curMax, slide, total]);
+ const prev = useCallback(() => {
+ if (!auto && clicks > 0) {
+ setClicks(clicks - 1);
+ return;
+ }
+ if (slide > 0) {
+ const n = slide - 1;
+ const m = auto ? 0 : maxMap.current[n] || 0;
+ setSlide(n);
+ setClicks(m);
+ setCurMax(m);
+ }
+ }, [auto, clicks, slide]);
+
+ const toggleFs = useCallback(() => {
+ if (document.fullscreenElement) document.exitFullscreen();
+ else document.documentElement.requestFullscreen?.();
+ }, []);
+ const toggleAuto = useCallback(() => setAuto((v) => !v), []);
+ const setNote = useCallback((text: string) => {
+ setNoteOverrides((prev) => {
+ const nextO = { ...prev, [slideRef.current]: text };
+ try {
+ localStorage.setItem('deck:notes', JSON.stringify(nextO));
+ } catch {
+ /* ignore */
+ }
+ return nextO;
+ });
+ }, []);
+ const openPresenter = useCallback(() => {
+ if (isPresenter) return;
+ const url =
+ window.location.pathname + '?presenter=1' + window.location.hash;
+ window.open(url, 'deck-presenter');
+ }, [isPresenter]);
+
+ // keyboard
+ useEffect(() => {
+ const onKey = (e: KeyboardEvent) => {
+ if (e.metaKey || e.ctrlKey || e.altKey) return;
+ const t = e.target as HTMLElement | null;
+ if (
+ t &&
+ (t.tagName === 'TEXTAREA' ||
+ t.tagName === 'INPUT' ||
+ t.isContentEditable)
+ )
+ return;
+ switch (e.key) {
+ case 'ArrowRight':
+ case 'ArrowDown':
+ case ' ':
+ case 'PageDown':
+ e.preventDefault();
+ next();
+ break;
+ case 'ArrowLeft':
+ case 'ArrowUp':
+ case 'PageUp':
+ e.preventDefault();
+ prev();
+ break;
+ case 'Home':
+ e.preventDefault();
+ go(0);
+ break;
+ case 'End':
+ e.preventDefault();
+ go(total - 1);
+ break;
+ case 'o':
+ case 'O':
+ setRailOpen((v) => !v);
+ break;
+ case 'f':
+ case 'F':
+ toggleFs();
+ break;
+ case 'd':
+ case 'D':
+ setDrawing((v) => !v);
+ break;
+ case 'p':
+ case 'P':
+ openPresenter();
+ break;
+ case 'a':
+ case 'A':
+ toggleAuto();
+ break;
+ case 'h':
+ case 'H':
+ setUiHidden((v) => !v);
+ break;
+ case 'Escape':
+ setRailOpen(false);
+ setDrawing(false);
+ setUiHidden(false);
+ break;
+ }
+ };
+ window.addEventListener('keydown', onKey);
+ return () => window.removeEventListener('keydown', onKey);
+ }, [next, prev, go, total, toggleFs, toggleAuto, openPresenter]);
+
+ // URL hash sync (initial slide comes from the hash via useState above)
+ useEffect(() => {
+ const want = String(slide + 1);
+ if (window.location.hash.slice(1) !== want)
+ history.replaceState(null, '', '#' + want);
+ }, [slide]);
+ useEffect(() => {
+ const onHash = () => {
+ const h = parseInt(window.location.hash.slice(1), 10);
+ if (h >= 1 && h <= total && h - 1 !== slide) go(h - 1);
+ };
+ window.addEventListener('hashchange', onHash);
+ return () => window.removeEventListener('hashchange', onHash);
+ }, [slide, total, go]);
+
+ // cross-tab sync (audience ⇄ presenter), via BroadcastChannel
+ const chan = useRef(null);
+ const applyingRemote = useRef(false);
+ useEffect(() => {
+ const c = new BroadcastChannel('deck-sync');
+ chan.current = c;
+ c.onmessage = (e) => {
+ if (e.data?.type === 'state') {
+ applyingRemote.current = true;
+ setSlide(e.data.slide);
+ setClicks(e.data.clicks);
+ setAuto(e.data.auto);
+ }
+ };
+ return () => c.close();
+ }, []);
+ useEffect(() => {
+ if (applyingRemote.current) {
+ applyingRemote.current = false;
+ return;
+ }
+ chan.current?.postMessage({ type: 'state', slide, clicks, auto });
+ }, [slide, clicks, auto]);
+
+ // fullscreen flag, presenter timer, idle auto-hide
+ useEffect(() => {
+ const h = () => setFs(!!document.fullscreenElement);
+ document.addEventListener('fullscreenchange', h);
+ return () => document.removeEventListener('fullscreenchange', h);
+ }, []);
+ useEffect(() => {
+ if (!isPresenter) return;
+ setElapsed(0);
+ const t = setInterval(() => setElapsed((e) => e + 1), 1000);
+ return () => clearInterval(t);
+ }, [isPresenter]);
+ // mouse-driven only: keyboard nav keeps the UI hidden; the dock returns when
+ // the pointer nears the bottom (where it lives); the cursor hides on idle.
+ useEffect(() => {
+ let t = 0;
+ const onMove = (e: MouseEvent) => {
+ setCursorIdle(false);
+ setNearDock(e.clientY > window.innerHeight - 150);
+ clearTimeout(t);
+ t = window.setTimeout(() => setCursorIdle(true), 2600);
+ };
+ window.addEventListener('mousemove', onMove);
+ return () => {
+ clearTimeout(t);
+ window.removeEventListener('mousemove', onMove);
+ };
+ }, []);
+
+ const liveCtx = useMemo(
+ () => ({ clicks, isStatic: false, auto, stagger, registerMax }),
+ [clicks, auto, stagger, registerMax]
+ );
+ const hasPrev = auto ? slide > 0 : slide > 0 || clicks > 0;
+ const hasNext = auto
+ ? slide < total - 1
+ : slide < total - 1 || clicks < curMax;
+ const notes = (slides[slide]?.props as { notes?: string } | undefined)?.notes;
+ const noteText = noteOverrides[slide] ?? notes ?? '';
+ const nextSlide = slides[slide + 1];
+ const hideUI = uiHidden || (fs && !nearDock);
+ const cursorHidden = fs && cursorIdle && !drawing;
+ const showAnnotator = drawing || (annStore.current[slide]?.length ?? 0) > 0;
+
+ return (
+
+
+
+
+ {slides[slide]}
+
+
+
+ {showAnnotator && (
+
+ )}
+
+
+
+ Slides
+ setRailOpen(false)}
+ >
+
+
+
+
+ {railOpen &&
+ slides.map((s, i) => (
+ {
+ go(i);
+ setRailOpen(false);
+ }}
+ >
+ {i + 1}
+ {s}
+
+ ))}
+
+
+
+ {isPresenter && (
+
+
+
+ Presenter · {slide + 1} / {total}
+
+ {fmt(elapsed)}
+
+ {nextSlide && (
+
+ {nextSlide}
+
+ )}
+
+ )}
+
+
+
+
setRailOpen((v) => !v)}
+ >
+
+
+
+
+
+
+
+ {slide + 1}
+ / {total}
+
+
+
+
+
+
+
+
+
setDrawing((v) => !v)}
+ >
+
+
+
+ {fs ? : }
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/bolt-slides/src/deck/DeckContext.ts b/bolt-slides/src/deck/DeckContext.ts
new file mode 100644
index 00000000..6cde9ae6
--- /dev/null
+++ b/bolt-slides/src/deck/DeckContext.ts
@@ -0,0 +1,19 @@
+import { createContext, useContext } from 'react';
+
+/* Shared state for click-builds. The live slide gets {clicks, isStatic:false,
+ registerMax}; thumbnails/previews get {isStatic:true} so every build shows
+ at once with no animation. Default is static so components render fine even
+ outside a . */
+export type DeckCtxValue = {
+ clicks: number;
+ isStatic: boolean;
+ auto?: boolean; // auto-play: reveal all builds in a stagger on slide load
+ stagger?: number; // seconds between builds in auto mode
+ registerMax?: (at: number) => void;
+};
+
+export const DeckCtx = createContext({
+ clicks: 9999,
+ isStatic: true,
+});
+export const useDeck = () => useContext(DeckCtx);
diff --git a/bolt-slides/src/deck/Reveal.tsx b/bolt-slides/src/deck/Reveal.tsx
new file mode 100644
index 00000000..6b40350a
--- /dev/null
+++ b/bolt-slides/src/deck/Reveal.tsx
@@ -0,0 +1,38 @@
+import { motion } from 'framer-motion';
+import type { CSSProperties, ReactNode } from 'react';
+import { useDeck } from './DeckContext';
+
+/* Entrance animation: content rises + fades in when its slide becomes active
+ (on mount). Wrap a headline, a grid, a card. Static in thumbnails. */
+export default function Reveal({
+ children,
+ y = 26,
+ delay = 0,
+ className,
+ style,
+}: {
+ children: ReactNode;
+ y?: number;
+ delay?: number;
+ className?: string;
+ style?: CSSProperties;
+}) {
+ const { isStatic } = useDeck();
+ if (isStatic)
+ return (
+
+ {children}
+
+ );
+ return (
+
+ {children}
+
+ );
+}
diff --git a/bolt-slides/src/deck/Slide.tsx b/bolt-slides/src/deck/Slide.tsx
new file mode 100644
index 00000000..f5b2b685
--- /dev/null
+++ b/bolt-slides/src/deck/Slide.tsx
@@ -0,0 +1,34 @@
+import type { CSSProperties, ReactNode } from 'react';
+
+/* One full-viewport slide. Content REFLOWS responsively (no fixed canvas).
+ - center: centered + text-align center (cover, statement, quote, CTA)
+ - full: no padding, edge-to-edge (for split / full-bleed media)
+ - nav: label shown in the thumbnail rail
+ - notes: speaker notes shown in the presenter overlay
+ Non-full slides wrap content in a max-width .container. */
+export default function Slide({
+ children,
+ center,
+ full,
+ className = '',
+ style,
+}: {
+ children: ReactNode;
+ center?: boolean;
+ full?: boolean;
+ nav?: string;
+ notes?: string;
+ className?: string;
+ style?: CSSProperties;
+}) {
+ return (
+
+ {full ? children :
{children}
}
+
+ );
+}
diff --git a/bolt-slides/src/deck/icons.tsx b/bolt-slides/src/deck/icons.tsx
new file mode 100644
index 00000000..7a77b17e
--- /dev/null
+++ b/bolt-slides/src/deck/icons.tsx
@@ -0,0 +1,61 @@
+/* Minimal inline icons for the dock (no icon-font dependency). */
+const base = {
+ width: 24,
+ height: 24,
+ viewBox: '0 0 24 24',
+ fill: 'none',
+ stroke: 'currentColor',
+ strokeWidth: 1.8,
+ strokeLinecap: 'round' as const,
+ strokeLinejoin: 'round' as const,
+};
+
+export const IconGrid = () => (
+
+
+
+
+
+
+);
+export const IconLeft = () => (
+
+
+
+);
+export const IconRight = () => (
+
+
+
+);
+export const IconPencil = () => (
+
+
+
+);
+export const IconExpand = () => (
+
+
+
+);
+export const IconShrink = () => (
+
+
+
+);
+export const IconPresent = () => (
+
+
+
+
+);
+export const IconClose = () => (
+
+
+
+);
+export const IconAuto = () => (
+
+
+
+);
diff --git a/bolt-slides/src/deck/useInView.ts b/bolt-slides/src/deck/useInView.ts
new file mode 100644
index 00000000..a31a00af
--- /dev/null
+++ b/bolt-slides/src/deck/useInView.ts
@@ -0,0 +1,29 @@
+import { useEffect, useRef, useState } from 'react';
+import { useDeck } from './DeckContext';
+
+/* Returns a ref + `inView` that flips true when the element scrolls into view —
+ the trigger for draw-in animations. In thumbnails (isStatic) it's true
+ immediately so previews render their final state. */
+export function useInView(threshold = 0.3) {
+ const { isStatic } = useDeck();
+ const ref = useRef(null);
+ const [inView, setInView] = useState(isStatic);
+ useEffect(() => {
+ if (isStatic) {
+ setInView(true);
+ return;
+ }
+ const el = ref.current;
+ if (!el) return;
+ const io = new IntersectionObserver(
+ (entries) =>
+ entries.forEach((e) => {
+ if (e.isIntersecting) setInView(true);
+ }),
+ { threshold }
+ );
+ io.observe(el);
+ return () => io.disconnect();
+ }, [isStatic, threshold]);
+ return { ref, inView };
+}
diff --git a/bolt-slides/src/main.tsx b/bolt-slides/src/main.tsx
new file mode 100644
index 00000000..342c2b6f
--- /dev/null
+++ b/bolt-slides/src/main.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+import './styles/tokens.css';
+import './styles/base.css';
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+
+);
diff --git a/bolt-slides/src/styles/base.css b/bolt-slides/src/styles/base.css
new file mode 100644
index 00000000..58fc3c62
--- /dev/null
+++ b/bolt-slides/src/styles/base.css
@@ -0,0 +1,1514 @@
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
+
+/* ── Reset & page ───────────────────────────────────────────────── */
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+html,
+body,
+#root {
+ height: 100%;
+ margin: 0;
+}
+html {
+ color-scheme: dark;
+} /* set "light" for light decks */
+body {
+ background: var(--bg);
+ color: var(--fg);
+ font-family: var(--font-body);
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ text-rendering: optimizeLegibility;
+ font-feature-settings: 'kern' 1, 'liga' 1, 'ss01' 1;
+ letter-spacing: -0.011em;
+ overflow: hidden;
+}
+
+/* ── The deck: a fixed-viewport stage that PAGES (one slide at a time) ── */
+.deck {
+ position: relative;
+ width: 100vw;
+ height: 100vh;
+ height: 100svh;
+ overflow: hidden;
+}
+.slide-stage {
+ position: absolute;
+ inset: 0;
+ animation: slideIn 0.5s var(--ease) both;
+}
+@keyframes slideIn {
+ from {
+ opacity: 0;
+ transform: scale(1.018);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+/* ── A slide = one full-viewport screen. Content is RESPONSIVE: it
+ reflows to the viewport (no fixed canvas, no scaling/clipping). ──── */
+.slide {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ padding: var(--gutter-y) var(--gutter);
+ overflow: hidden;
+}
+.slide.center {
+ align-items: center;
+ text-align: center;
+}
+.slide.full {
+ padding: 0;
+}
+.slide > * {
+ position: relative;
+ z-index: 1;
+}
+.container {
+ width: 100%;
+ max-width: 1180px;
+ margin-inline: auto;
+}
+
+/* Atmosphere — drifting spotlights + film grain + vignette, per slide */
+.slide {
+ box-shadow: inset 0 0 220px 50px var(--vignette);
+}
+.slide::before {
+ content: '';
+ position: absolute;
+ inset: -15%;
+ pointer-events: none;
+ z-index: 0;
+ background: radial-gradient(
+ 44% 50% at 24% 16%,
+ var(--bg-grad-1),
+ transparent 60%
+ ),
+ radial-gradient(50% 56% at 82% 88%, var(--bg-grad-2), transparent 62%);
+ animation: drift 26s var(--ease) infinite alternate;
+}
+@keyframes drift {
+ 0% {
+ transform: translate3d(-2%, -1%, 0) scale(1.06);
+ }
+ 100% {
+ transform: translate3d(3%, 2%, 0) scale(1.14);
+ }
+}
+.slide::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ z-index: 2;
+ opacity: var(--grain);
+ mix-blend-mode: overlay;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
+}
+
+/* ── Fluid type atoms ───────────────────────────────────────────── */
+h1,
+h2,
+h3 {
+ font-family: var(--font-head);
+ font-weight: 600;
+ letter-spacing: -0.028em;
+ line-height: 1.04;
+ margin: 0;
+ color: var(--fg);
+}
+p {
+ margin: 0;
+ line-height: 1.5;
+}
+strong {
+ font-weight: 600;
+ color: var(--fg);
+}
+.kicker {
+ font-size: clamp(12px, 1.1vw, 15px);
+ font-weight: 500;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: var(--fg-faint);
+}
+.display {
+ font-weight: 600;
+ letter-spacing: -0.04em;
+ line-height: 0.98;
+ font-size: clamp(44px, 8vw, 116px);
+}
+.headline {
+ font-weight: 600;
+ letter-spacing: -0.03em;
+ line-height: 1.04;
+ font-size: clamp(32px, 5.2vw, 62px);
+ max-width: 18ch;
+}
+.lead {
+ font-size: clamp(17px, 2.1vw, 25px);
+ color: var(--fg-muted);
+ max-width: 42ch;
+ line-height: 1.5;
+}
+.subhead {
+ font-size: clamp(18px, 2.4vw, 30px);
+ color: var(--fg-muted);
+ max-width: 26ch;
+ line-height: 1.35;
+}
+.foot {
+ font-size: clamp(13px, 1.2vw, 16px);
+ color: var(--fg-faint);
+}
+.center .lead,
+.center .subhead {
+ margin-inline: auto;
+}
+.accent-text {
+ background: var(--accent);
+ -webkit-background-clip: text;
+ background-clip: text;
+ color: transparent;
+}
+.figure {
+ font-weight: 600;
+ letter-spacing: -0.05em;
+ line-height: 0.9;
+ font-size: clamp(88px, 16vw, 220px);
+ font-feature-settings: 'tnum' 1;
+ background: var(--accent);
+ -webkit-background-clip: text;
+ background-clip: text;
+ color: transparent;
+}
+.rule {
+ width: 96px;
+ height: 1px;
+ background: linear-gradient(90deg, var(--primary), transparent);
+}
+
+/* ── Bento — responsive asymmetric tile grid ────────────────────── */
+.bento {
+ display: grid;
+ grid-template-columns: repeat(12, 1fr);
+ grid-auto-rows: minmax(clamp(130px, 20vh, 230px), auto);
+ gap: 16px;
+ width: 100%;
+}
+.btile {
+ position: relative;
+ overflow: hidden;
+ grid-column: span var(--c, 4);
+ grid-row: span var(--r, 1);
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ gap: 14px;
+ padding: clamp(20px, 2vw, 28px);
+ border-radius: var(--radius-lg);
+ background: linear-gradient(180deg, var(--surface-2), var(--surface));
+ border: 1px solid var(--hair);
+ transition: border-color var(--dur) var(--ease),
+ transform var(--dur) var(--ease);
+}
+.btile:hover {
+ border-color: color-mix(in srgb, var(--primary) 32%, transparent);
+ transform: translateY(-3px);
+}
+.btile.accent {
+ background: var(--accent);
+ border-color: transparent;
+}
+.btile.accent .btile-fig,
+.btile.accent h3,
+.btile.accent p {
+ color: var(--accent-ink);
+ -webkit-text-fill-color: var(--accent-ink);
+}
+.btile.accent .btile-k {
+ color: color-mix(in srgb, var(--accent-ink) 72%, transparent);
+}
+.btile.glow {
+ box-shadow: var(--glow);
+ border-color: color-mix(in srgb, var(--primary) 40%, transparent);
+}
+.btile-k {
+ font-size: clamp(11px, 1vw, 13px);
+ font-weight: 600;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: var(--fg-faint);
+}
+.btile-fig {
+ font-size: clamp(40px, 4.4vw, 64px);
+ font-weight: 600;
+ letter-spacing: -0.045em;
+ line-height: 0.92;
+ font-feature-settings: 'tnum' 1;
+ color: var(--fg);
+}
+.btile-fig.grad {
+ background: var(--accent);
+ -webkit-background-clip: text;
+ background-clip: text;
+ color: transparent;
+}
+.btile h3 {
+ font-size: clamp(18px, 2vw, 23px);
+}
+.btile p {
+ font-size: clamp(14px, 1.4vw, 16px);
+ color: var(--fg-muted);
+ line-height: 1.4;
+}
+
+/* ── Split — full-bleed feature section ─────────────────────────── */
+.split {
+ display: grid;
+ grid-template-columns: 1.05fr 1fr;
+ width: 100%;
+ height: 100%;
+}
+.split-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ gap: clamp(14px, 2vh, 22px);
+ padding: var(--gutter-y) clamp(28px, 5vw, 72px) var(--gutter-y) var(--gutter);
+}
+.split.flip .split-body {
+ order: 2;
+ padding: var(--gutter-y) var(--gutter) var(--gutter-y) clamp(28px, 5vw, 72px);
+}
+.split-media {
+ position: relative;
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.split-media img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+}
+
+/* ── Stat grid + card ───────────────────────────────────────────── */
+.stat-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+ gap: clamp(14px, 1.6vw, 22px);
+ width: 100%;
+}
+.stat-card {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ padding: clamp(24px, 2.4vw, 36px) clamp(22px, 2vw, 30px);
+ border-radius: var(--radius);
+ background: var(--surface);
+ border: 1px solid var(--hair);
+ text-align: left;
+ transition: transform var(--dur) var(--ease),
+ border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
+}
+.stat-card:hover {
+ transform: translateY(-6px);
+ border-color: color-mix(in srgb, var(--primary) 35%, transparent);
+ box-shadow: var(--glow);
+}
+.stat-value {
+ font-size: clamp(40px, 4.6vw, 58px);
+ font-weight: 600;
+ letter-spacing: -0.04em;
+ line-height: 1;
+ font-feature-settings: 'tnum' 1;
+ background: var(--accent);
+ -webkit-background-clip: text;
+ background-clip: text;
+ color: transparent;
+}
+.stat-label {
+ font-size: clamp(16px, 1.6vw, 19px);
+ font-weight: 600;
+ color: var(--fg);
+ margin-top: 8px;
+}
+.stat-caption {
+ font-size: clamp(13px, 1.3vw, 15px);
+ color: var(--fg-faint);
+ line-height: 1.4;
+}
+
+/* ── Accordion ──────────────────────────────────────────────────── */
+.accordion {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ width: 100%;
+}
+.acc-item {
+ border: 1px solid var(--hair);
+ border-radius: var(--radius);
+ background: var(--surface);
+ overflow: hidden;
+ transition: border-color var(--dur) var(--ease),
+ background var(--dur) var(--ease);
+}
+.acc-item.open {
+ border-color: color-mix(in srgb, var(--primary) 38%, transparent);
+ background: var(--surface-2);
+}
+.acc-head {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ width: 100%;
+ padding: clamp(16px, 1.8vw, 22px) clamp(18px, 2vw, 26px);
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ text-align: left;
+ font-family: var(--font-body);
+ color: var(--fg);
+}
+.acc-index {
+ font-family: var(--font-mono);
+ font-size: 13px;
+ font-weight: 600;
+ letter-spacing: 0.04em;
+ background: var(--accent);
+ -webkit-background-clip: text;
+ background-clip: text;
+ color: transparent;
+ flex-shrink: 0;
+}
+.acc-head h3 {
+ flex: 1;
+ font-size: clamp(17px, 1.9vw, 21px);
+ font-weight: 600;
+ letter-spacing: -0.02em;
+ color: var(--fg);
+ margin: 0;
+}
+.acc-chevron {
+ display: grid;
+ place-items: center;
+ color: var(--fg-faint);
+ transition: transform var(--dur) var(--ease), color var(--dur) var(--ease);
+ flex-shrink: 0;
+}
+.acc-item.open .acc-chevron {
+ transform: rotate(180deg);
+ color: var(--primary);
+}
+.acc-chevron svg {
+ width: 18px;
+ height: 18px;
+}
+.acc-body {
+ overflow: hidden;
+}
+.acc-body-inner {
+ padding: 0 clamp(18px, 2vw, 26px) clamp(18px, 2vw, 24px)
+ clamp(44px, 5vw, 58px);
+ font-size: clamp(14px, 1.5vw, 17px);
+ color: var(--fg-muted);
+ line-height: 1.5;
+ max-width: 62ch;
+}
+
+/* ═══ State-of-the-art sections ═════════════════════════════════════ */
+
+/* Comparison (us vs them) */
+.cmp {
+ width: 100%;
+ border: 1px solid var(--hair);
+ border-radius: var(--radius);
+ overflow: hidden;
+ background: var(--surface);
+}
+.cmp-row {
+ display: grid;
+ grid-template-columns: 1.6fr repeat(var(--vcols, 2), 1fr);
+ align-items: center;
+}
+.cmp-row + .cmp-row {
+ border-top: 1px solid var(--hair-2);
+}
+.cmp-cell {
+ padding: clamp(12px, 1.5vw, 18px) clamp(14px, 1.6vw, 22px);
+ font-size: clamp(14px, 1.5vw, 17px);
+ color: var(--fg-muted);
+ display: flex;
+ align-items: center;
+}
+.cmp-cell:not(.cmp-label):not(.cmp-h) {
+ justify-content: center;
+}
+.cmp-head {
+ background: var(--surface-2);
+}
+.cmp-h {
+ font-weight: 600;
+ color: var(--fg);
+}
+.cmp-label {
+ color: var(--fg);
+ font-weight: 500;
+}
+.cmp-cell.hl {
+ background: color-mix(in srgb, var(--primary) 8%, transparent);
+}
+.cmp-h.hl {
+ color: transparent;
+ background: var(--accent);
+ -webkit-background-clip: text;
+ background-clip: text;
+}
+.cmp-ic {
+ width: 22px;
+ height: 22px;
+}
+.cmp-ic.ok {
+ color: var(--primary);
+}
+.cmp-ic.no {
+ color: var(--fg-faint);
+ opacity: 0.55;
+}
+.cmp-val {
+ font-feature-settings: 'tnum' 1;
+}
+
+/* Tabs (sliding accent pill) */
+.tabs {
+ width: 100%;
+}
+.tabs-bar {
+ display: inline-flex;
+ gap: 4px;
+ padding: 5px;
+ border-radius: 999px;
+ background: var(--surface);
+ border: 1px solid var(--hair);
+}
+.tab {
+ position: relative;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ padding: 9px 18px;
+ border-radius: 999px;
+ font-family: var(--font-body);
+ font-size: 15px;
+ font-weight: 600;
+ color: var(--fg-muted);
+ transition: color var(--dur) var(--ease);
+}
+.tab.on {
+ color: var(--accent-ink);
+}
+.tab-pill {
+ position: absolute;
+ inset: 0;
+ border-radius: 999px;
+ background: var(--accent);
+ z-index: 0;
+}
+.tab-label {
+ position: relative;
+ z-index: 1;
+}
+.tabs-panel {
+ margin-top: 22px;
+}
+
+/* Timeline */
+.tl {
+ position: relative;
+}
+.tl-line {
+ position: absolute;
+ left: 11px;
+ top: 8px;
+ bottom: 8px;
+ width: 2px;
+ background: var(--hair);
+ border-radius: 2px;
+ overflow: hidden;
+}
+.tl-line-fill {
+ position: absolute;
+ inset: 0;
+ background: var(--accent);
+ transform-origin: top;
+}
+.tl-items {
+ display: flex;
+ flex-direction: column;
+ gap: clamp(20px, 3vh, 34px);
+}
+.tl-item {
+ position: relative;
+ display: flex;
+ gap: 22px;
+ padding-left: 6px;
+}
+.tl-dot {
+ position: relative;
+ z-index: 1;
+ flex-shrink: 0;
+ width: 12px;
+ height: 12px;
+ margin-top: 5px;
+ border-radius: 50%;
+ background: var(--primary);
+ box-shadow: 0 0 0 4px var(--bg), var(--glow);
+}
+.tl-time {
+ font-size: 12.5px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ background: var(--accent);
+ -webkit-background-clip: text;
+ background-clip: text;
+ color: transparent;
+ margin-bottom: 4px;
+}
+.tl-content h3 {
+ font-size: clamp(18px, 2vw, 23px);
+ font-weight: 600;
+ letter-spacing: -0.02em;
+ margin: 0 0 5px;
+}
+.tl-content p {
+ font-size: clamp(14px, 1.5vw, 16px);
+ color: var(--fg-muted);
+ line-height: 1.5;
+ max-width: 52ch;
+ margin: 0;
+}
+
+/* Code window */
+.cw {
+ width: 100%;
+ border: 1px solid var(--hair);
+ border-radius: var(--radius);
+ overflow: hidden;
+ background: var(--bg);
+ box-shadow: var(--shadow);
+}
+.cw-bar {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 11px 16px;
+ background: var(--surface-2);
+ border-bottom: 1px solid var(--hair-2);
+}
+.cw-dots {
+ display: flex;
+ gap: 7px;
+}
+.cw-dots i {
+ width: 11px;
+ height: 11px;
+ border-radius: 50%;
+ background: var(--hair);
+}
+.cw-title {
+ font-family: var(--font-mono);
+ font-size: 12.5px;
+ color: var(--fg-faint);
+}
+.cw-body {
+ margin: 0;
+ padding: 16px 0;
+ overflow-x: auto;
+}
+.cw-body code {
+ font-family: var(--font-mono);
+ font-size: clamp(12px, 1.3vw, 14px);
+ line-height: 1.65;
+}
+.cw-line {
+ display: flex;
+ padding: 0 16px;
+ white-space: pre;
+}
+.cw-line.hl {
+ background: color-mix(in srgb, var(--primary) 10%, transparent);
+ box-shadow: inset 2px 0 0 var(--primary);
+}
+.cw-no {
+ width: 30px;
+ flex-shrink: 0;
+ color: var(--fg-faint);
+ opacity: 0.5;
+ user-select: none;
+ text-align: right;
+ padding-right: 18px;
+}
+.cw-code {
+ color: var(--fg);
+}
+.cw-kw {
+ color: var(--primary);
+}
+.cw-str {
+ color: color-mix(in srgb, var(--primary) 50%, var(--fg-muted));
+}
+.cw-com {
+ color: var(--fg-faint);
+ font-style: italic;
+}
+
+/* Browser frame */
+.bf {
+ width: 100%;
+ border: 1px solid var(--hair);
+ border-radius: var(--radius-lg);
+ overflow: hidden;
+ background: var(--surface);
+ box-shadow: var(--shadow);
+}
+.bf-bar {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ padding: 11px 16px;
+ background: var(--surface-2);
+ border-bottom: 1px solid var(--hair-2);
+}
+.bf-dots {
+ display: flex;
+ gap: 7px;
+}
+.bf-dots i {
+ width: 11px;
+ height: 11px;
+ border-radius: 50%;
+ background: var(--hair);
+}
+.bf-url {
+ flex: 1;
+ font-family: var(--font-mono);
+ font-size: 12.5px;
+ color: var(--fg-faint);
+ background: var(--bg);
+ border: 1px solid var(--hair-2);
+ border-radius: 999px;
+ padding: 6px 14px;
+ text-align: center;
+}
+.bf-body {
+ background: var(--bg);
+}
+.bf-body img {
+ display: block;
+ width: 100%;
+}
+
+/* Charts */
+.ch-bars {
+ display: flex;
+ align-items: flex-end;
+ gap: clamp(8px, 1.4vw, 16px);
+ width: 100%;
+}
+.ch-col {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 9px;
+ height: 100%;
+}
+.ch-bar-track {
+ width: 100%;
+ flex: 1;
+ display: flex;
+ align-items: flex-end;
+}
+.ch-bar {
+ width: 100%;
+ border-radius: 8px 8px 4px 4px;
+ background: var(--accent);
+ display: block;
+}
+.ch-x {
+ font-size: 11.5px;
+ color: var(--fg-faint);
+}
+.ch-line {
+ width: 100%;
+ display: block;
+}
+.ch-donut {
+ width: var(--ch-size, 168px);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 12px;
+}
+.ch-donut svg {
+ width: 100%;
+ height: auto;
+ display: block;
+}
+.ch-donut-val {
+ fill: var(--fg);
+ font-family: var(--font-head);
+ font-size: 30px;
+ font-weight: 600;
+}
+.ch-donut-label {
+ font-size: 14px;
+ color: var(--fg-muted);
+ font-weight: 500;
+}
+
+/* Spotlight card (cursor-follow glow) */
+.spot {
+ position: relative;
+ border: 1px solid var(--hair);
+ border-radius: var(--radius-lg);
+ background: var(--surface);
+ padding: clamp(24px, 2.6vw, 36px);
+ overflow: hidden;
+ transition: border-color var(--dur) var(--ease);
+}
+.spot::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ opacity: 0;
+ transition: opacity 0.3s var(--ease);
+ background: radial-gradient(
+ 240px circle at var(--mx, 50%) var(--my, 50%),
+ color-mix(in srgb, var(--primary) 20%, transparent),
+ transparent 72%
+ );
+}
+.spot:hover {
+ border-color: color-mix(in srgb, var(--primary) 35%, transparent);
+}
+.spot:hover::before {
+ opacity: 1;
+}
+.spot > * {
+ position: relative;
+ z-index: 1;
+}
+
+/* ── Hover helpers ──────────────────────────────────────────────── */
+.lift {
+ transition: transform var(--dur) var(--ease),
+ box-shadow var(--dur) var(--ease);
+}
+.lift:hover {
+ transform: translateY(-6px);
+ box-shadow: var(--shadow), var(--glow);
+}
+
+/* ── Responsive collapse ────────────────────────────────────────── */
+@media (max-width: 900px) {
+ .bento {
+ grid-template-columns: repeat(2, 1fr);
+ grid-auto-rows: auto;
+ }
+ .btile {
+ grid-column: 1 / -1;
+ grid-row: auto;
+ min-height: clamp(140px, 24vh, 200px);
+ }
+ .split {
+ grid-template-columns: 1fr;
+ grid-template-rows: 1fr 1fr;
+ }
+ .split.flip .split-body {
+ order: 0;
+ }
+ .split-media {
+ order: 2;
+ }
+}
+
+/* ═══════════════════════════════════════════════════════════════════
+ NAVIGATION CHROME — the Slidev-style floating glass dock + thumbnail
+ rail, ported to React. This is the UI to keep; restyle only via tokens.
+ ═══════════════════════════════════════════════════════════════════ */
+.noir-dock {
+ position: fixed;
+ bottom: 22px;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 90;
+ opacity: 0.35;
+ transition: opacity 0.35s ease;
+ font-family: var(--font-body);
+}
+.noir-dock:hover {
+ opacity: 1;
+}
+.noir-bar {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding: 7px 9px;
+ border-radius: 999px;
+ background: rgba(28, 28, 30, 0.66);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ backdrop-filter: blur(22px) saturate(180%);
+ -webkit-backdrop-filter: blur(22px) saturate(180%);
+ box-shadow: 0 18px 50px -20px rgba(0, 0, 0, 0.8);
+}
+.noir-icon-btn {
+ width: 38px;
+ height: 38px;
+ display: grid;
+ place-items: center;
+ border: none;
+ border-radius: 999px;
+ background: transparent;
+ color: #e8e8ea;
+ font-size: 19px;
+ cursor: pointer;
+ transition: background 0.2s ease, color 0.2s ease, opacity 0.2s ease;
+}
+.noir-icon-btn:hover {
+ background: rgba(255, 255, 255, 0.12);
+}
+.noir-icon-btn:disabled {
+ opacity: 0.28;
+ cursor: default;
+}
+.noir-icon-btn:disabled:hover {
+ background: transparent;
+}
+.noir-icon-btn.on {
+ color: var(--accent-ink, #04140e);
+ background: var(--accent, linear-gradient(115deg, #c8f56e, #34dcd8));
+}
+.noir-icon-btn.sm {
+ width: 30px;
+ height: 30px;
+ font-size: 16px;
+}
+.noir-icon-btn svg {
+ width: 1em;
+ height: 1em;
+}
+.noir-sep {
+ width: 1px;
+ height: 22px;
+ background: rgba(255, 255, 255, 0.12);
+ margin: 0 4px;
+}
+.noir-counter {
+ display: flex;
+ align-items: baseline;
+ gap: 5px;
+ padding: 0 12px;
+ font-feature-settings: 'tnum' 1;
+ user-select: none;
+}
+.noir-counter-now {
+ font-size: 16px;
+ font-weight: 600;
+ color: #f5f5f7;
+}
+.noir-counter-tot {
+ font-size: 13px;
+ color: #8e8e93;
+}
+
+/* Left thumbnail rail */
+.noir-rail {
+ position: fixed;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ width: 248px;
+ z-index: 95;
+ padding: 18px 14px 26px;
+ overflow-y: auto;
+ background: rgba(18, 18, 20, 0.82);
+ border-right: 1px solid rgba(255, 255, 255, 0.08);
+ backdrop-filter: blur(26px) saturate(180%);
+ -webkit-backdrop-filter: blur(26px) saturate(180%);
+ font-family: var(--font-body);
+ transform: translateX(-100%);
+ transition: transform 0.32s cubic-bezier(0.32, 0.72, 0, 1);
+}
+.noir-rail.open {
+ transform: translateX(0);
+}
+.noir-rail-head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 4px 6px 14px;
+}
+.noir-rail-title {
+ font-size: 12px;
+ font-weight: 600;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: #8e8e93;
+}
+.noir-rail-list {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+.noir-thumb {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 0;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ text-align: left;
+}
+.noir-thumb-no {
+ width: 18px;
+ font-size: 12px;
+ font-feature-settings: 'tnum' 1;
+ color: #6e6e73;
+ flex-shrink: 0;
+}
+.noir-thumb-frame {
+ flex: 1;
+ border-radius: 9px;
+ overflow: hidden;
+ border: 1.5px solid transparent;
+ transition: border-color 0.25s ease;
+ line-height: 0;
+ aspect-ratio: 16 / 9;
+ position: relative;
+ background: var(--bg);
+}
+.noir-thumb:hover .noir-thumb-frame {
+ border-color: rgba(255, 255, 255, 0.22);
+}
+.noir-thumb.active .noir-thumb-no {
+ color: var(--primary);
+}
+.noir-thumb.active .noir-thumb-frame {
+ border-color: var(--primary);
+}
+/* the live slide rendered at true viewport size, then scaled into the frame
+ (so responsive vw/vh units stay faithful). width/height/transform set inline. */
+.noir-thumb-scale {
+ position: absolute;
+ top: 0;
+ left: 0;
+ transform-origin: top left;
+ pointer-events: none;
+}
+.noir-rail::-webkit-scrollbar {
+ width: 0;
+}
+.noir-rail {
+ scrollbar-width: none;
+}
+
+/* Presenter overlay (notes + next + timer) */
+.noir-presenter {
+ position: fixed;
+ right: 18px;
+ bottom: 78px;
+ z-index: 92;
+ width: clamp(260px, 26vw, 360px);
+ background: rgba(18, 18, 20, 0.9);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ border-radius: 16px;
+ backdrop-filter: blur(22px) saturate(180%);
+ -webkit-backdrop-filter: blur(22px) saturate(180%);
+ box-shadow: 0 18px 50px -20px rgba(0, 0, 0, 0.8);
+ padding: 16px;
+ font-family: var(--font-body);
+ color: #e8e8ea;
+}
+.noir-presenter-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 12px;
+}
+.noir-presenter-label {
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: #8e8e93;
+}
+.noir-presenter-timer {
+ font-size: 13px;
+ font-feature-settings: 'tnum' 1;
+ color: var(--primary);
+}
+.noir-presenter-next {
+ border-radius: 9px;
+ overflow: hidden;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ aspect-ratio: 16/9;
+ position: relative;
+ background: var(--bg);
+ margin-bottom: 12px;
+}
+.noir-presenter-notes {
+ width: 100%;
+ min-height: 84px;
+ max-height: 32vh;
+ font-size: 13.5px;
+ line-height: 1.5;
+ color: #c7c7cc;
+ background: transparent;
+ border: none;
+ resize: none;
+ outline: none;
+ font-family: var(--font-body);
+}
+.noir-presenter-notes:focus {
+ color: #f0f0f2;
+}
+.noir-presenter-notes::placeholder {
+ color: #6e6e73;
+}
+.noir-presenter-hint {
+ margin-top: 8px;
+ font-size: 11px;
+ color: #6e6e73;
+}
+
+/* Annotation overlay + tool bar */
+.ann-canvas {
+ position: fixed;
+ inset: 0;
+ z-index: 88;
+ touch-action: none;
+}
+.ann-bar {
+ position: fixed;
+ bottom: 78px;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 91;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding: 7px 9px;
+ border-radius: 999px;
+ flex-wrap: wrap;
+ max-width: 94vw;
+ justify-content: center;
+ background: rgba(28, 28, 30, 0.72);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ backdrop-filter: blur(22px) saturate(180%);
+ -webkit-backdrop-filter: blur(22px) saturate(180%);
+ box-shadow: 0 18px 50px -20px rgba(0, 0, 0, 0.8);
+ font-family: var(--font-body);
+}
+.ann-btn {
+ width: 34px;
+ height: 34px;
+ display: grid;
+ place-items: center;
+ border: none;
+ border-radius: 999px;
+ background: transparent;
+ color: #e8e8ea;
+ cursor: pointer;
+ transition: background 0.2s ease, color 0.2s ease;
+}
+.ann-btn svg {
+ width: 19px;
+ height: 19px;
+}
+.ann-btn:hover {
+ background: rgba(255, 255, 255, 0.12);
+}
+.ann-btn.on {
+ color: var(--accent-ink);
+ background: var(--accent);
+}
+.ann-sep {
+ width: 1px;
+ height: 22px;
+ background: rgba(255, 255, 255, 0.12);
+ margin: 0 4px;
+}
+.ann-color {
+ width: 22px;
+ height: 22px;
+ border-radius: 50%;
+ border: 2px solid transparent;
+ cursor: pointer;
+ padding: 0;
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18);
+}
+.ann-color.on {
+ border-color: #fff;
+ transform: scale(1.08);
+}
+.ann-size {
+ width: 30px;
+ height: 34px;
+ display: grid;
+ place-items: center;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ border-radius: 10px;
+}
+.ann-size span {
+ display: block;
+ border-radius: 50%;
+ background: #e8e8ea;
+}
+.ann-size.on {
+ background: rgba(255, 255, 255, 0.12);
+}
+.ann-size.on span {
+ background: var(--primary);
+}
+
+/* Tooltips for chrome controls (instant, styled) */
+[data-tip] {
+ position: relative;
+}
+[data-tip]::after {
+ content: attr(data-tip);
+ position: absolute;
+ bottom: calc(100% + 9px);
+ left: 50%;
+ transform: translateX(-50%) translateY(4px);
+ padding: 5px 9px;
+ border-radius: 7px;
+ white-space: nowrap;
+ font-size: 11.5px;
+ font-weight: 500;
+ font-family: var(--font-body);
+ background: rgba(20, 20, 22, 0.96);
+ color: #f5f5f7;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ box-shadow: 0 8px 24px -8px rgba(0, 0, 0, 0.7);
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 0.15s ease, transform 0.15s ease;
+ z-index: 100;
+}
+[data-tip]:hover::after {
+ opacity: 1;
+ transform: translateX(-50%) translateY(0);
+}
+
+/* Hide / dim the UI (H key, or auto on idle in fullscreen) */
+.noir-dock.hidden {
+ opacity: 0 !important;
+ pointer-events: none;
+ transform: translateX(-50%) translateY(18px);
+}
+.deck.nocursor,
+.deck.nocursor * {
+ cursor: none !important;
+}
+
+/* ── TiltCard / Marquee / visual frame (shared with the other skills) ─ */
+.tilt {
+ position: relative;
+ display: inline-block;
+ transition: transform 0.5s var(--ease);
+ transform-style: preserve-3d;
+ will-change: transform;
+}
+.tilt-glare {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ border-radius: var(--radius-lg);
+ background: radial-gradient(
+ 60% 60% at var(--gx, 50%) var(--gy, 50%),
+ rgba(255, 255, 255, 0.16),
+ transparent 60%
+ );
+ opacity: 0;
+ transition: opacity 0.4s var(--ease);
+}
+.tilt:hover .tilt-glare {
+ opacity: 1;
+}
+.marquee {
+ width: 100%;
+ overflow: hidden;
+ -webkit-mask-image: linear-gradient(
+ 90deg,
+ transparent,
+ #000 8%,
+ #000 92%,
+ transparent
+ );
+ mask-image: linear-gradient(
+ 90deg,
+ transparent,
+ #000 8%,
+ #000 92%,
+ transparent
+ );
+}
+.marquee-track {
+ display: flex;
+ gap: 56px;
+ width: max-content;
+ animation: marquee linear infinite;
+}
+.marquee-item {
+ font-size: clamp(20px, 2.4vw, 28px);
+ font-weight: 600;
+ letter-spacing: -0.02em;
+ white-space: nowrap;
+ color: var(--fg-muted);
+ opacity: 0.78;
+}
+@keyframes marquee {
+ from {
+ transform: translateX(0);
+ }
+ to {
+ transform: translateX(-50%);
+ }
+}
+.vframe {
+ padding: 24px;
+ border-radius: var(--radius-lg);
+ background: linear-gradient(180deg, var(--surface-2), var(--surface));
+ border: 1px solid var(--hair);
+ box-shadow: var(--shadow);
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ font-family: var(--font-body);
+}
+.vtitle {
+ font-size: 18px;
+ font-weight: 600;
+ letter-spacing: -0.02em;
+ color: var(--fg);
+}
+.vmeta {
+ font-size: 12.5px;
+ color: var(--fg-faint);
+}
+.ddot {
+ width: 9px;
+ height: 9px;
+ border-radius: 3px;
+ background: var(--accent);
+}
+.dlive {
+ margin-left: auto;
+ display: flex;
+ align-items: center;
+ gap: 7px;
+ font-size: 12px;
+ color: var(--primary);
+ padding: 5px 11px;
+ border-radius: 999px;
+ background: color-mix(in srgb, var(--primary) 10%, transparent);
+ border: 1px solid color-mix(in srgb, var(--primary) 28%, transparent);
+}
+.dlive span {
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: var(--primary);
+ animation: pulse 1.6s ease-in-out infinite;
+}
+@keyframes pulse {
+ 0%,
+ 100% {
+ opacity: 0.35;
+ }
+ 50% {
+ opacity: 1;
+ }
+}
+.dkpis {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 12px;
+}
+.dkpi {
+ background: var(--surface);
+ border: 1px solid var(--hair-2);
+ border-radius: var(--radius-sm);
+ padding: 14px 15px;
+}
+.dkpi-l {
+ font-size: 12.5px;
+ color: var(--fg-faint);
+}
+.dkpi-v {
+ font-size: 25px;
+ font-weight: 600;
+ letter-spacing: -0.03em;
+ color: var(--fg);
+ margin: 4px 0 5px;
+ font-feature-settings: 'tnum' 1;
+}
+.dkpi-d {
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--primary);
+}
+.dbars {
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ gap: 10px;
+ align-items: end;
+}
+.dbar {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 7px;
+}
+.dbar-track {
+ width: 100%;
+ height: 44px;
+ border-radius: 7px;
+ background: var(--hair-2);
+ display: flex;
+ align-items: flex-end;
+ overflow: hidden;
+}
+.dbar-track span {
+ width: 100%;
+ border-radius: 7px;
+ background: var(--accent);
+ display: block;
+ transform: scaleY(0);
+ transform-origin: bottom;
+}
+.dbar-k {
+ font-size: 10.5px;
+ color: var(--fg-faint);
+}
+.dline {
+ stroke-dasharray: 1;
+ stroke-dashoffset: 1;
+}
+.dfill-area {
+ opacity: 0;
+ transition: opacity 0.8s var(--ease) 0.5s;
+}
+.vframe.shown .dline {
+ animation: dash 1.5s var(--ease) 0.15s forwards;
+}
+.vframe.shown .dfill-area {
+ opacity: 1;
+}
+.vframe.shown .dbar-track span {
+ animation: bargrow 0.8s var(--ease-spring) both;
+}
+@keyframes dash {
+ to {
+ stroke-dashoffset: 0;
+ }
+}
+@keyframes bargrow {
+ from {
+ transform: scaleY(0);
+ }
+ to {
+ transform: scaleY(1);
+ }
+}
+
+/* ── Responsive controls — declutter the dock on touch / small screens ─
+ On touch there's no hover, so keep the dock fully visible; on phones,
+ shrink it and drop the presenter-only tools (draw + presenter overlay). */
+@media (hover: none) {
+ .noir-dock {
+ opacity: 1;
+ }
+}
+@media (max-width: 640px) {
+ .noir-dock {
+ opacity: 1;
+ bottom: 14px;
+ }
+ .noir-bar {
+ gap: 2px;
+ padding: 6px 7px;
+ }
+ .noir-icon-btn {
+ width: 34px;
+ height: 34px;
+ font-size: 17px;
+ }
+ .noir-icon-btn.sm {
+ width: 28px;
+ height: 28px;
+ }
+ .noir-counter {
+ padding: 0 8px;
+ }
+ .noir-counter-now {
+ font-size: 15px;
+ }
+ .noir-sep {
+ margin: 0 2px;
+ }
+ .noir-optional {
+ display: none;
+ }
+ .noir-rail {
+ width: min(248px, 84vw);
+ }
+}
+
+/* ── Reduced motion ─────────────────────────────────────────────── */
+@media (prefers-reduced-motion: reduce) {
+ .slide-stage,
+ .slide::before,
+ .lift,
+ .marquee-track,
+ .btile,
+ .acc-item,
+ .acc-chevron {
+ animation: none !important;
+ transition: none !important;
+ }
+ .tilt {
+ transform: none !important;
+ }
+ .dline {
+ stroke-dashoffset: 0;
+ }
+ .dfill-area {
+ opacity: 1;
+ }
+ .dbar-track span {
+ transform: scaleY(1);
+ }
+}
diff --git a/bolt-slides/src/styles/tokens.css b/bolt-slides/src/styles/tokens.css
new file mode 100644
index 00000000..a46a5639
--- /dev/null
+++ b/bolt-slides/src/styles/tokens.css
@@ -0,0 +1,63 @@
+/* ===================================================================
+ THEME TOKENS — edit ONLY this :root block to re-theme the whole deck.
+ The SAME token vocabulary as the `slides` (Slidev) and `webdeck` skills,
+ so a brand looks identical across all of them. The values below are a
+ "dark product" default — REPLACE them to match the brief or the brand.
+ =================================================================== */
+:root {
+ /* Surfaces */
+ --bg: #05070a;
+ --bg-grad-1: rgba(79, 229, 176, 0.12);
+ --bg-grad-2: rgba(41, 192, 240, 0.08);
+ --surface: rgba(255, 255, 255, 0.045);
+ --surface-2: rgba(255, 255, 255, 0.07);
+
+ /* Ink */
+ --fg: #f4f7fa;
+ --fg-muted: #9aa4b2;
+ --fg-faint: #626d7d;
+
+ /* Hairlines */
+ --hair: rgba(255, 255, 255, 0.1);
+ --hair-2: rgba(255, 255, 255, 0.06);
+
+ /* Accent — ONE accent. May be a 2–4 stop gradient within ~80° of hue. */
+ --primary: #4fe5b0;
+ --accent: linear-gradient(
+ 115deg,
+ #c8f56e 0%,
+ #5aeeb0 42%,
+ #34dcd8 74%,
+ #29c0f0 100%
+ );
+ --accent-ink: #04140e;
+
+ /* Radius */
+ --radius: 22px;
+ --radius-sm: 12px;
+ --radius-lg: 26px;
+
+ /* Depth */
+ --shadow: 0 50px 120px -50px rgba(0, 0, 0, 0.9);
+ --glow: 0 30px 80px -40px rgba(79, 229, 176, 0.5);
+
+ /* Type */
+ --font-head: 'Inter', -apple-system, BlinkMacSystemFont, 'SF Pro Display',
+ sans-serif;
+ --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'SF Pro Text',
+ sans-serif;
+ --font-mono: 'JetBrains Mono', ui-monospace, monospace;
+
+ /* Motion */
+ --dur: 0.4s;
+ --ease: cubic-bezier(0.32, 0.72, 0, 1);
+ --ease-spring: cubic-bezier(0.16, 1, 0.3, 1);
+
+ /* Atmosphere */
+ --grain: 0.035;
+ --vignette: rgba(0, 0, 0, 0.22);
+
+ /* Responsive gutter — fluid side / vertical padding */
+ --gutter: clamp(28px, 6vw, 120px);
+ --gutter-y: clamp(40px, 8vh, 96px);
+}
diff --git a/bolt-slides/tsconfig.json b/bolt-slides/tsconfig.json
new file mode 100644
index 00000000..17f43b17
--- /dev/null
+++ b/bolt-slides/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/bolt-slides/tsconfig.node.json b/bolt-slides/tsconfig.node.json
new file mode 100644
index 00000000..97ede7ee
--- /dev/null
+++ b/bolt-slides/tsconfig.node.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true,
+ "strict": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/bolt-slides/vite.config.ts b/bolt-slides/vite.config.ts
new file mode 100644
index 00000000..0466183a
--- /dev/null
+++ b/bolt-slides/vite.config.ts
@@ -0,0 +1,6 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ plugins: [react()],
+});