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 +
+
+ +
+
+
+
+ Revenue +
+ +
+
+ +
+
+
+
+ + {/* 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) => ( +
+ + + {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) => ( +
+
+ +
+
{d.label}
+
+ ))} +
+ ); +} + +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) => ( + + ))} +
+
+ + + {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%
+
+
+
Churn
+
+ +
+
▼ 0.6%
+
+
+ + + + + + + + + + +
+ {bars.map((h, i) => ( +
+
+ +
+
{days[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) => ( + + ))} + + {COLORS.map((c) => ( + + ))} + + + +
+ )} + + ); +} 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 ( +
+ +
+ {children} +
+
+
+ ); +} + +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 && ( + + )} + + + + {isPresenter && ( +
+
+ + Presenter · {slide + 1} / {total} + + {fmt(elapsed)} +
+ {nextSlide && ( +
+ {nextSlide} +
+ )} +