From b0377f5dbaa0c70541c783689a7ed214602739e6 Mon Sep 17 00:00:00 2001 From: xKevIsDev Date: Thu, 2 Jul 2026 14:28:29 +0100 Subject: [PATCH 1/4] chore: add bolt-slides template --- bolt-slides/README.md | 34 + bolt-slides/index.html | 13 + bolt-slides/package-lock.json | 1779 +++++++++++++++++ bolt-slides/package.json | 22 + bolt-slides/src/App.tsx | 272 +++ bolt-slides/src/components/Accordion.tsx | 58 + bolt-slides/src/components/Bento.tsx | 41 + bolt-slides/src/components/BrowserFrame.tsx | 15 + bolt-slides/src/components/Charts.tsx | 65 + bolt-slides/src/components/CodeWindow.tsx | 46 + bolt-slides/src/components/Comparison.tsx | 34 + bolt-slides/src/components/CountUp.tsx | 42 + bolt-slides/src/components/Marquee.tsx | 12 + bolt-slides/src/components/Split.tsx | 25 + bolt-slides/src/components/SpotlightCard.tsx | 16 + bolt-slides/src/components/StatGrid.tsx | 30 + bolt-slides/src/components/Tabs.tsx | 35 + bolt-slides/src/components/TiltCard.tsx | 25 + bolt-slides/src/components/Timeline.tsx | 37 + .../src/components/VisualDashboard.tsx | 53 + bolt-slides/src/deck/Annotator.tsx | 153 ++ bolt-slides/src/deck/Build.tsx | 53 + bolt-slides/src/deck/Deck.tsx | 240 +++ bolt-slides/src/deck/DeckContext.ts | 16 + bolt-slides/src/deck/Reveal.tsx | 29 + bolt-slides/src/deck/Slide.tsx | 25 + bolt-slides/src/deck/icons.tsx | 12 + bolt-slides/src/deck/useInView.ts | 23 + bolt-slides/src/main.tsx | 11 + bolt-slides/src/styles/base.css | 375 ++++ bolt-slides/src/styles/tokens.css | 55 + bolt-slides/tsconfig.json | 21 + bolt-slides/tsconfig.node.json | 11 + bolt-slides/vite.config.ts | 6 + 34 files changed, 3684 insertions(+) create mode 100644 bolt-slides/README.md create mode 100644 bolt-slides/index.html create mode 100644 bolt-slides/package-lock.json create mode 100644 bolt-slides/package.json create mode 100644 bolt-slides/src/App.tsx create mode 100644 bolt-slides/src/components/Accordion.tsx create mode 100644 bolt-slides/src/components/Bento.tsx create mode 100644 bolt-slides/src/components/BrowserFrame.tsx create mode 100644 bolt-slides/src/components/Charts.tsx create mode 100644 bolt-slides/src/components/CodeWindow.tsx create mode 100644 bolt-slides/src/components/Comparison.tsx create mode 100644 bolt-slides/src/components/CountUp.tsx create mode 100644 bolt-slides/src/components/Marquee.tsx create mode 100644 bolt-slides/src/components/Split.tsx create mode 100644 bolt-slides/src/components/SpotlightCard.tsx create mode 100644 bolt-slides/src/components/StatGrid.tsx create mode 100644 bolt-slides/src/components/Tabs.tsx create mode 100644 bolt-slides/src/components/TiltCard.tsx create mode 100644 bolt-slides/src/components/Timeline.tsx create mode 100644 bolt-slides/src/components/VisualDashboard.tsx create mode 100644 bolt-slides/src/deck/Annotator.tsx create mode 100644 bolt-slides/src/deck/Build.tsx create mode 100644 bolt-slides/src/deck/Deck.tsx create mode 100644 bolt-slides/src/deck/DeckContext.ts create mode 100644 bolt-slides/src/deck/Reveal.tsx create mode 100644 bolt-slides/src/deck/Slide.tsx create mode 100644 bolt-slides/src/deck/icons.tsx create mode 100644 bolt-slides/src/deck/useInView.ts create mode 100644 bolt-slides/src/main.tsx create mode 100644 bolt-slides/src/styles/base.css create mode 100644 bolt-slides/src/styles/tokens.css create mode 100644 bolt-slides/tsconfig.json create mode 100644 bolt-slides/tsconfig.node.json create mode 100644 bolt-slides/vite.config.ts 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..f150ad15 --- /dev/null +++ b/bolt-slides/src/App.tsx @@ -0,0 +1,272 @@ +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..44175f04 --- /dev/null +++ b/bolt-slides/src/components/Accordion.tsx @@ -0,0 +1,58 @@ +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..6f455ccb --- /dev/null +++ b/bolt-slides/src/components/Bento.tsx @@ -0,0 +1,41 @@ +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..31834e13 --- /dev/null +++ b/bolt-slides/src/components/BrowserFrame.tsx @@ -0,0 +1,15 @@ +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..130be0e2 --- /dev/null +++ b/bolt-slides/src/components/Charts.tsx @@ -0,0 +1,65 @@ +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..251b5ca8 --- /dev/null +++ b/bolt-slides/src/components/CodeWindow.tsx @@ -0,0 +1,46 @@ +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..1800e2eb --- /dev/null +++ b/bolt-slides/src/components/Comparison.tsx @@ -0,0 +1,34 @@ +/* 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..e00be403 --- /dev/null +++ b/bolt-slides/src/components/CountUp.tsx @@ -0,0 +1,42 @@ +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..4336e4d3 --- /dev/null +++ b/bolt-slides/src/components/Marquee.tsx @@ -0,0 +1,12 @@ +/* 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..6181cefe --- /dev/null +++ b/bolt-slides/src/components/Split.tsx @@ -0,0 +1,25 @@ +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..46a6dc27 --- /dev/null +++ b/bolt-slides/src/components/SpotlightCard.tsx @@ -0,0 +1,16 @@ +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..4d100e7c --- /dev/null +++ b/bolt-slides/src/components/StatGrid.tsx @@ -0,0 +1,30 @@ +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..f55892d7 --- /dev/null +++ b/bolt-slides/src/components/Tabs.tsx @@ -0,0 +1,35 @@ +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..1b57ab7a --- /dev/null +++ b/bolt-slides/src/components/TiltCard.tsx @@ -0,0 +1,25 @@ +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..25a8d84b --- /dev/null +++ b/bolt-slides/src/components/Timeline.tsx @@ -0,0 +1,37 @@ +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..af2a75ed --- /dev/null +++ b/bolt-slides/src/components/VisualDashboard.tsx @@ -0,0 +1,53 @@ +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..fa646102 --- /dev/null +++ b/bolt-slides/src/deck/Annotator.tsx @@ -0,0 +1,153 @@ +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..50de6813 --- /dev/null +++ b/bolt-slides/src/deck/Build.tsx @@ -0,0 +1,53 @@ +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..90e1a7e1 --- /dev/null +++ b/bolt-slides/src/deck/Deck.tsx @@ -0,0 +1,240 @@ +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}
} +