diff --git a/.gitignore b/.gitignore
index 7f63c8e..5455db3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
*.swp
*.swo
*~
+/node_modules
\ No newline at end of file
diff --git a/web/.gitignore b/web/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/web/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/web/.npmrc b/web/.npmrc
new file mode 100644
index 0000000..214c29d
--- /dev/null
+++ b/web/.npmrc
@@ -0,0 +1 @@
+registry=https://registry.npmjs.org/
diff --git a/web/README.md b/web/README.md
new file mode 100644
index 0000000..7dbf7eb
--- /dev/null
+++ b/web/README.md
@@ -0,0 +1,73 @@
+# React + TypeScript + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
+
+## React Compiler
+
+The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
+
+```js
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ // Other configs...
+
+ // Remove tseslint.configs.recommended and replace with this
+ tseslint.configs.recommendedTypeChecked,
+ // Alternatively, use this for stricter rules
+ tseslint.configs.strictTypeChecked,
+ // Optionally, add this for stylistic rules
+ tseslint.configs.stylisticTypeChecked,
+
+ // Other configs...
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ // other options...
+ },
+ },
+])
+```
+
+You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
+
+```js
+// eslint.config.js
+import reactX from 'eslint-plugin-react-x'
+import reactDom from 'eslint-plugin-react-dom'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ // Other configs...
+ // Enable lint rules for React
+ reactX.configs['recommended-typescript'],
+ // Enable lint rules for React DOM
+ reactDom.configs.recommended,
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ // other options...
+ },
+ },
+])
+```
diff --git a/web/eslint.config.js b/web/eslint.config.js
new file mode 100644
index 0000000..5e6b472
--- /dev/null
+++ b/web/eslint.config.js
@@ -0,0 +1,23 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+import { defineConfig, globalIgnores } from 'eslint/config'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ js.configs.recommended,
+ tseslint.configs.recommended,
+ reactHooks.configs.flat.recommended,
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ },
+])
diff --git a/web/index.html b/web/index.html
new file mode 100644
index 0000000..ec41311
--- /dev/null
+++ b/web/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Claude Design Skill · 视频演示
+
+
+
+
+
+
diff --git a/web/package-lock.json b/web/package-lock.json
new file mode 100644
index 0000000..34a5124
--- /dev/null
+++ b/web/package-lock.json
@@ -0,0 +1,3013 @@
+{
+ "name": "my-app",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "my-app",
+ "version": "0.0.0",
+ "dependencies": {
+ "react": "^19.2.5",
+ "react-dom": "^19.2.5"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.39.4",
+ "@types/node": "^24.12.2",
+ "@types/react": "^19.2.14",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^6.0.1",
+ "eslint": "^9.39.4",
+ "eslint-plugin-react-hooks": "^7.1.1",
+ "eslint-plugin-react-refresh": "^0.5.2",
+ "globals": "^17.5.0",
+ "typescript": "~6.0.2",
+ "typescript-eslint": "^8.58.2",
+ "vite": "^8.0.9"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@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.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@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.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "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.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
+ "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
+ "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
+ "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.1",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
+ "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
+ "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz",
+ "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.5"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz",
+ "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.14.0",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.1",
+ "minimatch": "^3.1.5",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.39.4",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz",
+ "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
+ "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/types": "^0.15.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.8",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
+ "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.2",
+ "@humanfs/types": "^0.15.0",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/types": {
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
+ "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "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/@napi-rs/wasm-runtime": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
+ "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tybys/wasm-util": "^0.10.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.126.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.126.0.tgz",
+ "integrity": "sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.0.0-rc.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.16.tgz",
+ "integrity": "sha512-rhY3k7Bsae9qQfOtph2Pm2jZEA+s8Gmjoz4hhmx70K9iMQ/ddeae+xhRQcM5IuVx5ry1+bGfkvMn7D6MJggVSA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.0.0-rc.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.16.tgz",
+ "integrity": "sha512-rNz0yK078yrNn3DrdgN+PKiMOW8HfQ92jQiXxwX8yW899ayV00MLVdaCNeVBhG/TbH3ouYVObo8/yrkiectkcQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.0.0-rc.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.16.tgz",
+ "integrity": "sha512-r/OmdR00HmD4i79Z//xO06uEPOq5hRXdhw7nzkxQxwSavs3PSHa1ijntdpOiZ2mzOQ3fVVu8C1M19FoNM+dMUQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.0.0-rc.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.16.tgz",
+ "integrity": "sha512-KcRE5w8h0OnjUatG8pldyD14/CQ5Phs1oxfR+3pKDjboHRo9+MkqQaiIZlZRpsxC15paeXme/I127tUa9TXJ6g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.0.0-rc.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.16.tgz",
+ "integrity": "sha512-bT0guA1bpxEJ/ZhTRniQf7rNF8ybvXOuWbNIeLABaV5NGjx4EtOWBTSRGWFU9ZWVkPOZ+HNFP8RMcBokBiZ0Kg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.0.0-rc.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.16.tgz",
+ "integrity": "sha512-+tHktCHWV8BDQSjemUqm/Jl/TPk3QObCTIjmdDy/nlupcujZghmKK2962LYrqFpWu+ai01AN/REOH3NEpqvYQg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.0.0-rc.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.16.tgz",
+ "integrity": "sha512-3fPzdREH806oRLxpTWW1Gt4tQHs0TitZFOECB2xzCFLPKnSOy90gwA7P29cksYilFO6XVRY1kzga0cL2nRjKPg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.0.0-rc.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.16.tgz",
+ "integrity": "sha512-EKwI1tSrLs7YVw+JPJT/G2dJQ1jl9qlTTTEG0V2Ok/RdOenRfBw2PQdLPyjhIu58ocdBfP7vIRN/pvMsPxs/AQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.0.0-rc.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.16.tgz",
+ "integrity": "sha512-Uknladnb3Sxqu6SEcqBldQyJUpk8NleooZEc0MbRBJ4inEhRYWZX0NJu12vNf2mqAq7gsofAxHrGghiUYjhaLQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.0.0-rc.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.16.tgz",
+ "integrity": "sha512-FIb8+uG49sZBtLTn+zt1AJ20TqVcqWeSIyoVt0or7uAWesgKaHbiBh6OpA/k9v0LTt+PTrb1Lao133kP4uVxkg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.0.0-rc.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.16.tgz",
+ "integrity": "sha512-RuERhF9/EgWxZEXYWCOaViUWHIboceK4/ivdtQ3R0T44NjLkIIlGIAVAuCddFxsZ7vnRHtNQUrt2vR2n2slB2w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.0.0-rc.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.16.tgz",
+ "integrity": "sha512-mXcXnvd9GpazCxeUCCnZ2+YF7nut+ZOEbE4GtaiPtyY6AkhZWbK70y1KK3j+RDhjVq5+U8FySkKRb/+w0EeUwA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi": {
+ "version": "1.0.0-rc.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.16.tgz",
+ "integrity": "sha512-3Q2KQxnC8IJOLqXmUMoYwyIPZU9hzRbnHaoV3Euz+VVnjZKcY8ktnNP8T9R4/GGQtb27C/UYKABxesKWb8lsvQ==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "1.9.2",
+ "@emnapi/runtime": "1.9.2",
+ "@napi-rs/wasm-runtime": "^1.1.4"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.0.0-rc.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.16.tgz",
+ "integrity": "sha512-tj7XRemQcOcFwv7qhpUxMTBbI5mWMlE4c1Omhg5+h8GuLXzyj8HviYgR+bB2DMDgRqUE+jiDleqSCRjx4aYk/Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.0.0-rc.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.16.tgz",
+ "integrity": "sha512-PH5DRZT+F4f2PTXRXR8uJxnBq2po/xFtddyabTJVJs/ZYVHqXPEgNIr35IHTEa6bpa0Q8Awg+ymkTaGnKITw4g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.7",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz",
+ "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
+ "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "24.12.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz",
+ "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.59.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.0.tgz",
+ "integrity": "sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.59.0",
+ "@typescript-eslint/type-utils": "8.59.0",
+ "@typescript-eslint/utils": "8.59.0",
+ "@typescript-eslint/visitor-keys": "8.59.0",
+ "ignore": "^7.0.5",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.5.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.59.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.59.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.0.tgz",
+ "integrity": "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.59.0",
+ "@typescript-eslint/types": "8.59.0",
+ "@typescript-eslint/typescript-estree": "8.59.0",
+ "@typescript-eslint/visitor-keys": "8.59.0",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.59.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.0.tgz",
+ "integrity": "sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.59.0",
+ "@typescript-eslint/types": "^8.59.0",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.59.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.0.tgz",
+ "integrity": "sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.59.0",
+ "@typescript-eslint/visitor-keys": "8.59.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.59.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.0.tgz",
+ "integrity": "sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.59.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.0.tgz",
+ "integrity": "sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.59.0",
+ "@typescript-eslint/typescript-estree": "8.59.0",
+ "@typescript-eslint/utils": "8.59.0",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.5.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.59.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.0.tgz",
+ "integrity": "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.59.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.0.tgz",
+ "integrity": "sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.59.0",
+ "@typescript-eslint/tsconfig-utils": "8.59.0",
+ "@typescript-eslint/types": "8.59.0",
+ "@typescript-eslint/visitor-keys": "8.59.0",
+ "debug": "^4.4.3",
+ "minimatch": "^10.2.2",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.5.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
+ "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.59.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.0.tgz",
+ "integrity": "sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.59.0",
+ "@typescript-eslint/types": "8.59.0",
+ "@typescript-eslint/typescript-estree": "8.59.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.59.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.0.tgz",
+ "integrity": "sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.59.0",
+ "eslint-visitor-keys": "^5.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz",
+ "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rolldown/pluginutils": "1.0.0-rc.7"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
+ "babel-plugin-react-compiler": "^1.0.0",
+ "vite": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@rolldown/plugin-babel": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
+ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.20",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.20.tgz",
+ "integrity": "sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
+ "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
+ "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
+ "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.12",
+ "caniuse-lite": "^1.0.30001782",
+ "electron-to-chromium": "^1.5.328",
+ "node-releases": "^2.0.36",
+ "update-browserslist-db": "^1.2.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001788",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz",
+ "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==",
+ "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/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "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/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.340",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.340.tgz",
+ "integrity": "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "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/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.39.4",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz",
+ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.2",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
+ "@eslint/eslintrc": "^3.3.5",
+ "@eslint/js": "9.39.4",
+ "@eslint/plugin-kit": "^0.4.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.14.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.5",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz",
+ "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.24.4",
+ "@babel/parser": "^7.24.4",
+ "hermes-parser": "^0.25.1",
+ "zod": "^3.25.0 || ^4.0.0",
+ "zod-validation-error": "^3.5.0 || ^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz",
+ "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": "^9 || ^10"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "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/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "17.5.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz",
+ "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/hermes-estree": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
+ "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/hermes-parser": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
+ "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hermes-estree": "0.25.1"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "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==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "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/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "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.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "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/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.37",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz",
+ "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.10",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
+ "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
+ "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.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.5",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
+ "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.5",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz",
+ "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.5"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.0.0-rc.16",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.16.tgz",
+ "integrity": "sha512-rzi5WqKzEZw3SooTt7cgm4eqIoujPIyGcJNGFL7iPEuajQw7vxMHUkXylu4/vhCkJGXsgRmxqMKXUpT6FEgl0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.126.0",
+ "@rolldown/pluginutils": "1.0.0-rc.16"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.0.0-rc.16",
+ "@rolldown/binding-darwin-arm64": "1.0.0-rc.16",
+ "@rolldown/binding-darwin-x64": "1.0.0-rc.16",
+ "@rolldown/binding-freebsd-x64": "1.0.0-rc.16",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.16",
+ "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.16",
+ "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.16",
+ "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.16",
+ "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.16",
+ "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.16",
+ "@rolldown/binding-linux-x64-musl": "1.0.0-rc.16",
+ "@rolldown/binding-openharmony-arm64": "1.0.0-rc.16",
+ "@rolldown/binding-wasm32-wasi": "1.0.0-rc.16",
+ "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.16",
+ "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.16"
+ }
+ },
+ "node_modules/rolldown/node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.16.tgz",
+ "integrity": "sha512-45+YtqxLYKDWQouLKCrpIZhke+nXxhsw+qAHVzHDVwttyBlHNBVs2K25rDXrZzhpTp9w1FlAlvweV1H++fdZoA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "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/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.16",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
+ "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+ "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "0BSD",
+ "optional": true
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
+ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.59.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.0.tgz",
+ "integrity": "sha512-BU3ONW9X+v90EcCH9ZS6LMackcVtxRLlI3XrYyqZIwVSHIk7Qf7bFw1z0M9Q0IUxhTMZCf8piY9hTYaNEIASrw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.59.0",
+ "@typescript-eslint/parser": "8.59.0",
+ "@typescript-eslint/typescript-estree": "8.59.0",
+ "@typescript-eslint/utils": "8.59.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "8.0.9",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.9.tgz",
+ "integrity": "sha512-t7g7GVRpMXjNpa67HaVWI/8BWtdVIQPCL2WoozXXA7LBGEFK4AkkKkHx2hAQf5x1GZSlcmEDPkVLSGahxnEEZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.32.0",
+ "picomatch": "^4.0.4",
+ "postcss": "^8.5.10",
+ "rolldown": "1.0.0-rc.16",
+ "tinyglobby": "^0.2.16"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.1.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-validation-error": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
+ "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.0 || ^4.0.0"
+ }
+ }
+ }
+}
diff --git a/web/package.json b/web/package.json
new file mode 100644
index 0000000..dd4aa46
--- /dev/null
+++ b/web/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "my-app",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^19.2.5",
+ "react-dom": "^19.2.5"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.39.4",
+ "@types/node": "^24.12.2",
+ "@types/react": "^19.2.14",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^6.0.1",
+ "eslint": "^9.39.4",
+ "eslint-plugin-react-hooks": "^7.1.1",
+ "eslint-plugin-react-refresh": "^0.5.2",
+ "globals": "^17.5.0",
+ "typescript": "~6.0.2",
+ "typescript-eslint": "^8.58.2",
+ "vite": "^8.0.9"
+ }
+}
diff --git a/web/public/favicon.svg b/web/public/favicon.svg
new file mode 100644
index 0000000..6893eb1
--- /dev/null
+++ b/web/public/favicon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web/public/video.mp4 b/web/public/video.mp4
new file mode 100644
index 0000000..6a33bfc
Binary files /dev/null and b/web/public/video.mp4 differ
diff --git a/web/src/App.tsx b/web/src/App.tsx
new file mode 100644
index 0000000..002ae94
--- /dev/null
+++ b/web/src/App.tsx
@@ -0,0 +1,32 @@
+import { Stage } from './stage/Stage';
+import { ChapterHost } from './stage/ChapterHost';
+import { ProgressBar } from './stage/ProgressBar';
+import { useHotKeys } from './stage/useHotKeys';
+import { stepStore, useStep } from './store/useStep';
+import { chapters } from './chapters';
+
+function App() {
+ useHotKeys();
+ const { chapterIndex } = useStep();
+ const theme = chapters[chapterIndex]?.theme ?? 'light';
+
+ return (
+ {
+ // 任何带 data-no-step 的祖先都不触发推进
+ const target = e.target as HTMLElement;
+ if (target.closest('[data-no-step]')) return;
+ stepStore.next();
+ }}
+ >
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/web/src/chapters/01-opening/Opening.css b/web/src/chapters/01-opening/Opening.css
new file mode 100644
index 0000000..914f7a7
--- /dev/null
+++ b/web/src/chapters/01-opening/Opening.css
@@ -0,0 +1,705 @@
+/* =========================================================
+ Chapter 01 · Opening 「一则崩盘」
+ - ink 主题(深墨底)
+ - 4 幕全部由 SceneFade 包裹独立铺满 stage,互不重叠
+ - 没有任何"网页 chrome"(无顶部章节标记 / 底部 footer)
+ - 真实数据:LegalZoom -20% / CRCL -20% / CrowdStrike -7% / Figma -7%
+ ========================================================= */
+
+.opening {
+ position: absolute;
+ inset: 0;
+ font-family: var(--f-sans);
+ color: var(--fg);
+ background: var(--bg);
+ overflow: hidden;
+}
+
+/* 每幕都是覆盖 stage 的全屏容器 */
+.opening__sceneA,
+.opening__sceneB,
+.opening__sceneC,
+.opening__sceneD {
+ position: absolute;
+ inset: 0;
+ padding: 110px 120px;
+}
+
+.opening__sceneC {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+}
+
+.opening__sceneD {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+
+/* =========================================================
+ SCENE A · Crash
+ ========================================================= */
+
+/* LIVE 报价条(仅 Scene A 出现,作为"市场环境") */
+.opening__live {
+ position: absolute;
+ top: 90px;
+ right: 120px;
+ display: flex;
+ align-items: center;
+ gap: 18px;
+ padding: 10px 20px;
+ border: 1px solid var(--line-mid);
+ background: oklch(0.965 0.018 78 / 0.025);
+ backdrop-filter: blur(6px);
+ font-family: var(--f-mono);
+ font-size: 16px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ z-index: 6;
+ animation: opLiveIn 700ms cubic-bezier(.2,.8,.2,1) both;
+}
+@keyframes opLiveIn {
+ from { opacity: 0; transform: translateY(-12px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+.opening__live-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: var(--crimson);
+ animation: opPulse 1.6s ease-in-out infinite;
+}
+@keyframes opPulse {
+ 0%, 100% { opacity: 0.4; transform: scale(0.85); }
+ 50% { opacity: 1; transform: scale(1.15); }
+}
+.opening__live-label {
+ color: var(--fg-soft);
+ font-weight: 500;
+}
+.opening__live-clock {
+ color: var(--paper);
+ font-size: 18px;
+ letter-spacing: 0.08em;
+}
+.opening__live-sep {
+ color: var(--fg-faint);
+}
+.opening__live-quote {
+ display: flex;
+ align-items: baseline;
+ gap: 10px;
+}
+.opening__live-quote-tag {
+ color: var(--fg-mute);
+ font-size: 13px;
+ padding: 2px 6px;
+ border: 1px solid var(--line-mid);
+}
+.opening__live-quote-val {
+ color: var(--paper);
+ font-size: 18px;
+}
+.opening__live-quote-d {
+ color: var(--crimson);
+ font-size: 14px;
+}
+
+/* "FIGMA" 巨字背景水印 */
+.opening__crash-mega {
+ position: absolute;
+ left: 100px;
+ bottom: 120px;
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 360px;
+ line-height: 0.85;
+ color: oklch(0.965 0.018 78 / 0.05);
+ letter-spacing: -0.04em;
+ user-select: none;
+ pointer-events: none;
+ white-space: nowrap;
+ z-index: 1;
+}
+
+/* 引子小字 */
+.opening__crash-intro {
+ position: absolute;
+ top: 230px;
+ left: 120px;
+ display: flex;
+ align-items: center;
+ gap: 18px;
+ font-family: var(--f-mono);
+ font-size: 16px;
+ letter-spacing: 0.18em;
+ color: var(--fg-mute);
+ z-index: 4;
+}
+.opening__crash-intro-tag {
+ padding: 5px 12px;
+ border: 1px solid var(--line-mid);
+ color: var(--fg-soft);
+ text-transform: uppercase;
+}
+.opening__crash-intro-text {
+ font-family: var(--f-sans);
+ font-size: 22px;
+ letter-spacing: 0;
+ color: var(--fg-soft);
+}
+
+/* 折线图 */
+.opening__chart {
+ position: absolute;
+ inset: 320px 80px 280px;
+ width: calc(100% - 160px);
+ height: calc(100% - 600px);
+ z-index: 2;
+}
+.opening__chart-grid line {
+ stroke: var(--line);
+}
+.opening__chart-history {
+ stroke: var(--fg-faint);
+ opacity: 0.65;
+ stroke-dasharray: 1400;
+ stroke-dashoffset: 1400;
+ animation: opPathDraw 1500ms cubic-bezier(.2,.8,.2,1) forwards,
+ opHistoryHum 6s ease-in-out 1500ms infinite alternate;
+}
+@keyframes opHistoryHum {
+ 0% { transform: translateY(0); }
+ 50% { transform: translateY(-3px); }
+ 100% { transform: translateY(2px); }
+}
+.opening__chart-crash {
+ stroke: var(--crimson);
+ stroke-linecap: round;
+ stroke-dasharray: 800;
+ stroke-dashoffset: 800;
+ animation: opPathDraw 720ms cubic-bezier(.4,0,1,1) 80ms forwards;
+ filter: drop-shadow(0 0 24px oklch(0.560 0.200 22 / 0.5));
+}
+@keyframes opPathDraw {
+ to { stroke-dashoffset: 0; }
+}
+.opening__chart-fill {
+ opacity: 0;
+ animation: opFadeIn 600ms cubic-bezier(.2,.8,.2,1) 700ms forwards;
+}
+.opening__chart-end {
+ opacity: 0;
+ animation: opFadeIn 500ms cubic-bezier(.2,.8,.2,1) 900ms forwards;
+}
+.opening__chart-end circle {
+ fill: var(--crimson);
+ filter: drop-shadow(0 0 10px oklch(0.560 0.200 22 / 0.7));
+ animation: opEndPulse 1.8s ease-in-out infinite;
+ transform-origin: 1760px 510px;
+}
+@keyframes opEndPulse {
+ 0%, 100% { transform: scale(1); }
+ 50% { transform: scale(1.4); }
+}
+.opening__chart-end line {
+ stroke: var(--fg-faint);
+}
+.opening__chart-end text {
+ font-family: var(--f-mono);
+ font-size: 13px;
+ letter-spacing: 0.15em;
+ fill: var(--fg-mute);
+ text-transform: uppercase;
+}
+@keyframes opFadeIn {
+ to { opacity: 1; }
+}
+
+/* 中文 headline */
+.opening__crash-headline {
+ position: absolute;
+ bottom: 140px;
+ left: 120px;
+ right: 600px;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: baseline;
+ gap: 22px;
+ z-index: 4;
+}
+.opening__crash-line {
+ font-size: 42px;
+ color: var(--fg-soft);
+ font-weight: 300;
+ letter-spacing: 0.02em;
+}
+.opening__crash-emph {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 130px;
+ line-height: 0.95;
+ color: var(--paper);
+ letter-spacing: -0.02em;
+ font-weight: 400;
+}
+
+/* 跌幅大数字 */
+.opening__crash-stat {
+ position: absolute;
+ right: 120px;
+ top: 230px;
+ text-align: right;
+ z-index: 4;
+}
+.opening__crash-stat-num {
+ font-family: var(--f-serif);
+ font-size: 240px;
+ line-height: 0.9;
+ color: var(--crimson);
+ letter-spacing: -0.04em;
+ font-feature-settings: "tnum";
+ display: flex;
+ align-items: baseline;
+ justify-content: flex-end;
+ filter: drop-shadow(0 0 40px oklch(0.560 0.200 22 / 0.35));
+}
+.opening__crash-stat-sign {
+ margin-right: 6px;
+}
+.opening__crash-stat-pct {
+ font-size: 130px;
+ margin-left: 4px;
+}
+.opening__crash-stat-label {
+ margin-top: 18px;
+ font-family: var(--f-mono);
+ font-size: 16px;
+ letter-spacing: 0.22em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+}
+
+/* =========================================================
+ SCENE B · Victims
+ ========================================================= */
+.opening__vics-eyebrow {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ gap: 18px;
+ margin-bottom: 56px;
+}
+.opening__vics-eyebrow-inner {
+ display: flex;
+ align-items: baseline;
+ gap: 18px;
+ font-size: 38px;
+ color: var(--fg-soft);
+ font-weight: 300;
+}
+.opening__vics-em {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 92px;
+ color: var(--accent);
+ font-weight: 400;
+ line-height: 1;
+ position: relative;
+}
+.opening__vics-em::after {
+ content: "";
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 8px;
+ height: 8px;
+ background: oklch(0.700 0.170 42 / 0.25);
+ z-index: -1;
+ animation: opUnderlineGrow 700ms cubic-bezier(.2,.8,.2,1) 320ms both;
+ transform-origin: 0 50%;
+}
+@keyframes opUnderlineGrow {
+ from { transform: scaleX(0); }
+ to { transform: scaleX(1); }
+}
+.opening__vics-by {
+ display: flex;
+ align-items: baseline;
+ gap: 14px;
+ font-family: var(--f-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.22em;
+}
+.opening__vics-by-tag {
+ font-size: 14px;
+ color: var(--fg-faint);
+}
+.opening__vics-by-name {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 60px;
+ color: var(--accent);
+ text-transform: none;
+ letter-spacing: -0.01em;
+}
+
+.opening__vics-table {
+ display: flex;
+ flex-direction: column;
+ border-top: 1px solid var(--line-mid);
+}
+.opening__vics-thead {
+ display: grid;
+ grid-template-columns: 1.4fr 1.2fr 0.5fr 0.7fr 0.5fr;
+ gap: 32px;
+ padding: 14px 0;
+ font-family: var(--f-mono);
+ font-size: 13px;
+ letter-spacing: 0.22em;
+ text-transform: uppercase;
+ color: var(--fg-faint);
+ border-bottom: 1px solid var(--line);
+}
+
+.opening__vic {
+ display: grid;
+ grid-template-columns: 1.4fr 1.2fr 0.5fr 0.7fr 0.5fr;
+ gap: 32px;
+ align-items: baseline;
+ padding: 30px 0;
+ border-bottom: 1px solid var(--line);
+ position: relative;
+}
+.opening__vic-product {
+ font-family: var(--f-mono);
+ font-size: 22px;
+ font-weight: 500;
+ color: var(--accent);
+ letter-spacing: -0.01em;
+ display: flex;
+ align-items: baseline;
+ gap: 12px;
+}
+.opening__vic-product::before {
+ content: "▸";
+ color: var(--accent);
+ font-size: 16px;
+}
+.opening__vic-company {
+ font-family: var(--f-serif);
+ font-size: 60px;
+ letter-spacing: -0.01em;
+ color: var(--fg);
+ line-height: 1;
+}
+.opening__vic-ticker {
+ font-family: var(--f-mono);
+ font-size: 16px;
+ color: var(--fg-mute);
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ border: 1px solid var(--line-mid);
+ padding: 4px 10px;
+ display: inline-block;
+ width: fit-content;
+}
+.opening__vic-field {
+ font-size: 18px;
+ color: var(--fg-mute);
+}
+.opening__vic-drop {
+ font-family: var(--f-mono);
+ font-size: 44px;
+ font-weight: 500;
+ color: var(--fg-mute);
+ text-align: right;
+ font-feature-settings: "tnum";
+ letter-spacing: -0.01em;
+}
+.opening__vic.is-current {
+ background: linear-gradient(
+ to right,
+ transparent 0%,
+ oklch(0.700 0.170 42 / 0.05) 30%,
+ oklch(0.700 0.170 42 / 0.10) 70%,
+ transparent 100%
+ );
+}
+.opening__vic.is-current .opening__vic-company {
+ color: var(--paper);
+}
+.opening__vic.is-current .opening__vic-drop {
+ color: var(--crimson);
+}
+.opening__vic-flag {
+ position: absolute;
+ right: 0;
+ top: -4px;
+ font-family: var(--f-mono);
+ font-size: 11px;
+ letter-spacing: 0.3em;
+ text-transform: uppercase;
+ color: var(--accent);
+ padding: 3px 8px;
+ border: 1px solid var(--accent);
+ background: var(--bg);
+ animation: opFlagPulse 1.6s ease-in-out infinite;
+}
+@keyframes opFlagPulse {
+ 0%, 100% { opacity: 0.5; }
+ 50% { opacity: 1; }
+}
+
+/* =========================================================
+ SCENE C · Claude Design 揭幕
+ ========================================================= */
+.opening__reveal-meta {
+ display: flex;
+ align-items: center;
+ gap: 22px;
+ font-family: var(--f-mono);
+ font-size: 16px;
+ letter-spacing: 0.3em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ margin-bottom: 56px;
+}
+.opening__reveal-meta .dot {
+ width: 4px;
+ height: 4px;
+ background: var(--fg-mute);
+ border-radius: 50%;
+}
+
+.opening__reveal-title {
+ display: flex;
+ align-items: baseline;
+ gap: 32px;
+ margin: 0;
+ font-size: 220px;
+ line-height: 0.9;
+ font-family: var(--f-serif);
+ font-weight: 400;
+ letter-spacing: -0.03em;
+}
+.opening__reveal-word {
+ display: inline-block;
+}
+.opening__reveal-word--em {
+ font-style: italic;
+ color: var(--accent);
+}
+
+.opening__reveal-sub {
+ margin-top: 50px;
+ display: flex;
+ align-items: center;
+ gap: 22px;
+ font-size: 52px;
+ font-weight: 300;
+ color: var(--fg-soft);
+}
+.opening__reveal-tilde {
+ color: var(--accent);
+ font-family: var(--f-serif);
+ font-style: italic;
+}
+
+.opening__reveal-tags {
+ margin-top: 56px;
+ display: flex;
+ gap: 14px;
+}
+.opening__reveal-tags span {
+ padding: 10px 20px;
+ border: 1px solid var(--line-mid);
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ background: oklch(0.965 0.018 78 / 0.025);
+}
+
+/* =========================================================
+ SCENE D · Skill pivot
+ ========================================================= */
+.opening__pivot-eyebrow {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ font-family: var(--f-mono);
+ font-size: 18px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--accent);
+ margin-bottom: 36px;
+}
+.opening__pivot-eyebrow-bar {
+ width: 56px;
+ height: 1px;
+ background: var(--accent);
+}
+
+.opening__pivot-title {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 124px;
+ line-height: 1.05;
+ letter-spacing: -0.025em;
+ color: var(--paper);
+ max-width: 1500px;
+ margin: 0 0 64px 0;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0 18px;
+}
+.opening__pivot-em {
+ color: var(--accent);
+ font-style: italic;
+ position: relative;
+ white-space: nowrap;
+}
+.opening__pivot-em::after {
+ content: "";
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 6px;
+ height: 8px;
+ background: oklch(0.700 0.170 42 / 0.25);
+ z-index: -1;
+ animation: opUnderlineGrow 800ms cubic-bezier(.2,.8,.2,1) 1100ms both;
+ transform-origin: 0 50%;
+}
+
+.opening__pivot-row {
+ display: grid;
+ grid-template-columns: 1.1fr 1fr;
+ gap: 80px;
+ align-items: center;
+}
+
+.opening__skill-card {
+ position: relative;
+ border: 1px solid var(--line-mid);
+ background: oklch(0.965 0.018 78 / 0.025);
+ padding: 36px 44px;
+ backdrop-filter: blur(4px);
+}
+.opening__skill-card-deco::before,
+.opening__skill-card-deco::after {
+ content: "";
+ position: absolute;
+ width: 18px;
+ height: 18px;
+}
+.opening__skill-card-deco::before {
+ top: -1px;
+ left: -1px;
+ border-top: 2px solid var(--accent);
+ border-left: 2px solid var(--accent);
+}
+.opening__skill-card-deco::after {
+ bottom: -1px;
+ right: -1px;
+ border-bottom: 2px solid var(--accent);
+ border-right: 2px solid var(--accent);
+}
+
+.opening__skill-card-head {
+ display: flex;
+ align-items: baseline;
+ gap: 18px;
+ padding-bottom: 22px;
+ border-bottom: 1px solid var(--line);
+ margin-bottom: 24px;
+}
+.opening__skill-card-tag {
+ font-family: var(--f-mono);
+ font-size: 13px;
+ letter-spacing: 0.28em;
+ text-transform: uppercase;
+ color: var(--accent);
+ padding: 4px 10px;
+ border: 1px solid var(--accent);
+}
+.opening__skill-card-name {
+ font-family: var(--f-mono);
+ font-size: 32px;
+ font-weight: 500;
+ color: var(--paper);
+ letter-spacing: -0.01em;
+}
+.opening__skill-card-body {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+.opening__skill-card-row {
+ display: grid;
+ grid-template-columns: 130px 1fr;
+ gap: 24px;
+ align-items: baseline;
+ font-size: 22px;
+}
+.opening__skill-card-k {
+ font-family: var(--f-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.18em;
+ font-size: 13px;
+ color: var(--fg-mute);
+}
+.opening__skill-card-v {
+ color: var(--paper);
+}
+.opening__skill-card-foot {
+ margin-top: 24px;
+ padding-top: 18px;
+ border-top: 1px solid var(--line);
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ font-family: var(--f-mono);
+ font-size: 13px;
+ text-transform: uppercase;
+ letter-spacing: 0.22em;
+ color: var(--fg-mute);
+}
+.opening__skill-card-pulse {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: oklch(0.78 0.18 145);
+ animation: opPulse 1.6s ease-in-out infinite;
+}
+
+.opening__pivot-aside {
+ font-family: var(--f-serif);
+ font-size: 56px;
+ line-height: 1.2;
+ color: var(--fg-soft);
+ position: relative;
+}
+.opening__pivot-aside-q {
+ position: absolute;
+ left: -56px;
+ top: -36px;
+ font-size: 200px;
+ color: var(--accent);
+ opacity: 0.4;
+ font-style: italic;
+ line-height: 1;
+}
+.opening__pivot-aside em {
+ font-style: italic;
+ color: var(--paper);
+ display: block;
+ margin-top: 12px;
+}
diff --git a/web/src/chapters/01-opening/index.tsx b/web/src/chapters/01-opening/index.tsx
new file mode 100644
index 0000000..349cfe6
--- /dev/null
+++ b/web/src/chapters/01-opening/index.tsx
@@ -0,0 +1,383 @@
+import type { ChapterContext, ChapterDef } from '../types';
+import { Reveal } from '../../shared/Reveal';
+import { NumberTicker } from '../../shared/NumberTicker';
+import { LiveClock, FlickerNumber } from '../../shared/LiveTicker';
+import { SceneFade } from '../../shared/SceneFade';
+import './Opening.css';
+
+/**
+ * Chapter 01 · Opening 「一则崩盘」
+ *
+ * 4 幕 × 14 个 step:
+ * Scene A · Crash (0..3) Figma 股价崩盘
+ * Scene B · Victims (4..8) Anthropic 历次带崩列表
+ * Scene C · Reveal (9..11) Claude Design 揭幕
+ * Scene D · Skill (12..13) 我把它做成了 Skill
+ *
+ * 真实数据:LegalZoom -20% / CRCL -20% / CrowdStrike -7% / Figma -7%
+ *
+ * 设计原则:
+ * - 没有任何"网页 chrome"(没有顶部章节标记 / 底部 footer / 永久 hint)
+ * - 每幕通过 SceneFade 优雅交叉淡入淡出,不会有重叠 bug
+ * - 每个 step 推进只增加 1 个新视觉元素
+ */
+
+const VICTIMS = [
+ { product: 'Claude Cowork', company: 'LegalZoom', ticker: 'LZ', drop: -20.0, field: '法律服务' },
+ { product: 'Claude Code Security', company: 'Circle Internet', ticker: 'CRCL', drop: -20.0, field: '云安全' },
+ { product: 'Claude Mythos', company: 'CrowdStrike', ticker: 'CRWD', drop: -7.0, field: '终端安全' },
+ { product: 'Claude Design', company: 'Figma', ticker: 'FIG', drop: -7.0, field: '设计协作', current: true },
+];
+
+function Opening({ localStep }: ChapterContext) {
+ const at = (n: number) => localStep >= n;
+ const between = (a: number, b: number) => localStep >= a && localStep <= b;
+
+ const sceneCrash = localStep <= 3;
+ const sceneVictims = between(4, 8);
+ const sceneReveal = between(9, 11);
+ const sceneSkill = at(12);
+
+ return (
+
+ {/* ============== SCENE A · Crash (0..3) ============== */}
+
+
+ {/* LIVE 报价条 —— 仅在崩盘场景中作为"市场环境"出现 */}
+
+
+ NASDAQ · LIVE
+
+ |
+
+ FIG
+
+ −7.0%
+
+
+
+ {/* "FIGMA" 巨字背景 —— step 2 才显形 */}
+ {at(2) && (
+
+ FIGMA
+
+ )}
+
+ {/* 引子小字 —— step 1 */}
+ {at(1) && (
+
+ 2026 · 04 · 17 · NASDAQ CLOSE
+ 就在前两天 ——
+
+ )}
+
+ {/* 折线图 —— step 1 开始绘制历史 / step 2 崩盘 */}
+
+
+ {/* 中文 headline —— step 2 */}
+ {at(2) && (
+
+ Figma
+ 的股价崩了。
+
+ )}
+
+ {/* −7.0% 大数字 —— step 3 */}
+ {at(3) && (
+
+
+ −
+
+ %
+
+ 单日跌幅 · 收盘
+
+ )}
+
+
+
+ {/* ============== SCENE B · Victims (4..8) ============== */}
+
+
+
+
+ 这已经是
+ 第 N 次
+ 了 ——
+
+
+ CRASHED · BY
+ ANTHROPIC
+
+
+
+
+
+ ANTHROPIC PRODUCT
+ AFFECTED COMPANY
+ TICKER
+ SECTOR
+ DAY DROP
+
+
+ {VICTIMS.map((v, i) => {
+ const showAt = 5 + i;
+ if (!at(showAt)) return null;
+ return (
+
+
+
+ );
+ })}
+
+
+
+
+ {/* ============== SCENE C · Claude Design 揭幕 (9..11) ============== */}
+
+
+
+ ANTHROPIC
+
+ NEW PRODUCT
+
+ 2026 · 04 · 17
+
+
+
+
+ Claude
+
+ {at(10) && (
+
+ Design
+
+ )}
+
+
+ {at(10) && (
+
+ ——
+ 设计师的 Claude Code
+
+ )}
+
+ {at(11) && (
+
+
+ Powered by Opus 4.7
+
+
+ Pro · Max · Team · Enterprise
+
+
+ 左侧聊天 · 右侧画布
+
+
+ )}
+
+
+
+ {/* ============== SCENE D · Skill (12..13) ============== */}
+
+
+
+
+ 于是 ——
+
+
+
+
+ 我把它做成了一个
+
+
+ 人人都能用的 Skill。
+
+
+
+ {at(13) && (
+
+
+
+
+
+ "
+ 人人都能成为
+ 顶级网站设计师。
+
+
+ )}
+
+
+
+ );
+}
+
+/* ────────────── 子组件 ────────────── */
+
+function CrashChart({ phase }: { phase: number }) {
+ const showHistory = phase >= 1;
+ const showCrash = phase >= 2;
+ const showFill = phase >= 2;
+ const showEnd = phase >= 2;
+
+ return (
+
+
+
+
+
+
+
+
+
+ {[0, 1, 2, 3].map((i) => (
+
+ ))}
+
+
+ {showHistory && (
+
+ )}
+
+ {showCrash && (
+
+ )}
+
+ {showFill && (
+
+ )}
+
+ {showEnd && (
+
+
+
+ 前日收盘 51.95
+ 今日收盘 48.32
+
+ )}
+
+ );
+}
+
+interface VictimRowProps {
+ v: typeof VICTIMS[number];
+ animate: boolean;
+}
+
+function VictimRow({ v, animate }: VictimRowProps) {
+ return (
+
+ {v.product}
+ {v.company}
+ ${v.ticker}
+ {v.field}
+
+ {animate ? (
+
+ ) : (
+
+ −{Math.abs(v.drop).toFixed(1)}%
+
+ )}
+
+ {v.current && CURRENT }
+
+ );
+}
+
+function SkillCard() {
+ return (
+
+
+
+ SKILL · v1
+ web-design-engineer
+
+
+
+ 支持环境
+ Cursor · Claude Code · Codex
+
+
+ 体量
+ ≈ 400 行 · 含模板库 ≈ 520 行
+
+
+ 来源
+ Claude Design 系统提示词 · 提炼 + 改良
+
+
+ 效果
+ 85 → 95 分
+
+
+
+
+ READY · open-source
+
+
+ );
+}
+
+const def: ChapterDef = {
+ id: 'opening',
+ title: '开场 · 一则崩盘',
+ eyebrow: '01',
+ steps: 14,
+ theme: 'ink',
+ Component: Opening,
+};
+
+export default def;
diff --git a/web/src/chapters/02-video/Video.css b/web/src/chapters/02-video/Video.css
new file mode 100644
index 0000000..f78015a
--- /dev/null
+++ b/web/src/chapters/02-video/Video.css
@@ -0,0 +1,183 @@
+/* =========================================================
+ Chapter 02 · Anthropic 官方宣传片
+ light 主题 · 米色纸感底 · 中央虚拟显示器外框
+ ========================================================= */
+
+.vid {
+ position: absolute;
+ inset: 0;
+ background: var(--bg);
+ color: var(--fg);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 60px 100px 80px;
+ font-family: var(--f-sans);
+ overflow: hidden;
+}
+
+/* TV 外壳 */
+.vid__tv-wrap {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.vid__tv {
+ position: relative;
+ width: 1280px;
+ background: linear-gradient(
+ 180deg,
+ oklch(0.245 0.015 60) 0%,
+ oklch(0.190 0.014 60) 100%
+ );
+ border-radius: 18px;
+ padding: 18px 22px 22px;
+ box-shadow:
+ 0 60px 80px -40px oklch(0.180 0.014 60 / 0.35),
+ 0 24px 36px -20px oklch(0.180 0.014 60 / 0.20),
+ inset 0 1px 0 oklch(0.965 0.018 78 / 0.08),
+ inset 0 -1px 0 oklch(0.180 0.014 60 / 0.6);
+}
+
+/* 角落螺丝 */
+.vid__screw {
+ position: absolute;
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: radial-gradient(
+ circle at 35% 35%,
+ oklch(0.700 0.012 60) 0%,
+ oklch(0.380 0.012 60) 70%,
+ oklch(0.180 0.012 60) 100%
+ );
+ box-shadow: inset 0 0 1px oklch(0.180 0.012 60);
+}
+.vid__screw--tl { top: 12px; left: 12px; }
+.vid__screw--tr { top: 12px; right: 12px; }
+.vid__screw--bl { bottom: 14px; left: 12px; }
+.vid__screw--br { bottom: 14px; right: 12px; }
+
+/* 顶部状态条 */
+.vid__topstrip {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 0 18px 12px;
+ font-family: var(--f-mono);
+ font-size: 11px;
+ letter-spacing: 0.28em;
+ color: oklch(0.620 0.020 60);
+ text-transform: uppercase;
+}
+.vid__led {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ background: oklch(0.78 0.18 145);
+ box-shadow: 0 0 8px oklch(0.78 0.18 145 / 0.7);
+ animation: vidLedPulse 1.4s ease-in-out infinite;
+}
+@keyframes vidLedPulse {
+ 0%, 100% { opacity: 0.5; }
+ 50% { opacity: 1; }
+}
+.vid__topstrip-spacer {
+ flex: 1;
+}
+
+/* 屏幕(视频容器) */
+.vid__screen {
+ position: relative;
+ aspect-ratio: 16 / 9;
+ background: #000;
+ border-radius: 6px;
+ overflow: hidden;
+ box-shadow:
+ inset 0 0 0 1px oklch(0.180 0.014 60),
+ inset 0 0 24px oklch(0.180 0.014 60 / 0.6);
+}
+
+.vid__video {
+ display: block;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ background: #000;
+}
+
+/* 微弱扫描线 */
+.vid__scanlines {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ z-index: 2;
+ background-image: repeating-linear-gradient(
+ 180deg,
+ oklch(0.965 0.018 78 / 0) 0px,
+ oklch(0.965 0.018 78 / 0) 2px,
+ oklch(0.965 0.018 78 / 0.025) 3px,
+ oklch(0.965 0.018 78 / 0) 4px
+ );
+ mix-blend-mode: overlay;
+ opacity: 0.6;
+}
+
+/* 底部品牌条 */
+.vid__brandstrip {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 12px;
+ padding: 14px 18px 4px;
+ font-family: var(--f-mono);
+ color: oklch(0.620 0.020 60);
+ font-size: 14px;
+ text-transform: uppercase;
+ letter-spacing: 0.18em;
+}
+.vid__brand-mark {
+ width: 14px;
+ height: 14px;
+ background: var(--accent);
+ clip-path: polygon(50% 0, 100% 100%, 0 100%);
+}
+.vid__brand-name {
+ font-weight: 600;
+ color: oklch(0.880 0.020 78);
+ letter-spacing: 0.22em;
+}
+
+/* 底座 */
+.vid__stand {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-top: -2px;
+}
+.vid__stand-neck {
+ width: 200px;
+ height: 24px;
+ background: linear-gradient(
+ 180deg,
+ oklch(0.220 0.015 60) 0%,
+ oklch(0.180 0.014 60) 100%
+ );
+ clip-path: polygon(8% 0, 92% 0, 100% 100%, 0 100%);
+ box-shadow: 0 4px 8px -2px oklch(0.180 0.014 60 / 0.3);
+}
+.vid__stand-base {
+ width: 340px;
+ height: 8px;
+ background: linear-gradient(
+ 180deg,
+ oklch(0.230 0.015 60) 0%,
+ oklch(0.170 0.014 60) 100%
+ );
+ border-radius: 4px;
+ box-shadow:
+ 0 12px 24px -8px oklch(0.180 0.014 60 / 0.5),
+ 0 4px 6px -1px oklch(0.180 0.014 60 / 0.3);
+}
diff --git a/web/src/chapters/02-video/index.tsx b/web/src/chapters/02-video/index.tsx
new file mode 100644
index 0000000..2e1f401
--- /dev/null
+++ b/web/src/chapters/02-video/index.tsx
@@ -0,0 +1,90 @@
+import { useEffect, useRef } from 'react';
+import type { ChapterContext, ChapterDef } from '../types';
+import { Reveal } from '../../shared/Reveal';
+import './Video.css';
+
+/**
+ * Chapter 02 · Anthropic 官方宣传片嵌入
+ *
+ * 浅色纸感背景 + 虚拟电视外框 + 16:9 视频。视频源:/video.mp4
+ *
+ * 仅保留电视外框 —— 不放任何文案 / eyebrow / caption。
+ * - 进入时静音自动播放(绕过浏览器策略),用户可通过 controls 解除静音 / 暂停
+ * - TV + 底座 data-no-step,操作 controls 不会推进
+ * - 点击 TV 之外的留白才会推进到 Ch03
+ */
+function VideoChapter(_: ChapterContext) {
+ const videoRef = useRef(null);
+
+ useEffect(() => {
+ const v = videoRef.current;
+ if (!v) return;
+ v.currentTime = 0;
+ const play = v.play();
+ if (play && typeof play.catch === 'function') {
+ play.catch(() => {/* 用户必须自行点击播放 */});
+ }
+ return () => {
+ v.pause();
+ };
+ }, []);
+
+ return (
+
+
+
+ {/* 角注 4 颗螺丝 */}
+
+
+
+
+
+ {/* 顶部状态条 */}
+
+
+ ON · CH · 02
+
+ SIGNAL · STABLE
+
+
+ {/* 屏幕 */}
+
+
+ {/* 底部品牌条 */}
+
+
+ ANTHROPIC
+
+
+
+ {/* 底座 */}
+
+
+
+
+
+
+ );
+}
+
+const def: ChapterDef = {
+ id: 'video',
+ title: '官方宣传片',
+ eyebrow: '02',
+ steps: 1,
+ theme: 'light',
+ Component: VideoChapter,
+};
+
+export default def;
diff --git a/web/src/chapters/03-core-point/CorePoint.css b/web/src/chapters/03-core-point/CorePoint.css
new file mode 100644
index 0000000..334df2c
--- /dev/null
+++ b/web/src/chapters/03-core-point/CorePoint.css
@@ -0,0 +1,480 @@
+/* =========================================================
+ Chapter 03 · 核心观点
+ ink 主题 · hero → 50/50 split → 提示词倾泻 → leaked → pivot
+ ========================================================= */
+
+.cp {
+ position: absolute;
+ inset: 0;
+ background: var(--bg);
+ color: var(--fg);
+ font-family: var(--f-sans);
+ overflow: hidden;
+}
+
+/* 背景网格氛围 */
+.cp__grid {
+ position: absolute;
+ inset: 0;
+ background-image:
+ linear-gradient(to right, var(--line) 0, var(--line) 1px, transparent 1px),
+ linear-gradient(to bottom, var(--line) 0, var(--line) 1px, transparent 1px);
+ background-size: 80px 80px;
+ mask-image: radial-gradient(circle at 50% 50%, #000 0%, #000 60%, transparent 95%);
+ pointer-events: none;
+ opacity: 0.5;
+}
+
+/* 角落坐标十字 */
+.cp__cornerTL,
+.cp__cornerBR {
+ position: absolute;
+ width: 28px;
+ height: 28px;
+ pointer-events: none;
+}
+.cp__cornerTL { top: 56px; left: 64px; }
+.cp__cornerBR { bottom: 56px; right: 64px; }
+.cp__cornerTL span,
+.cp__cornerBR span {
+ position: absolute;
+ background: var(--fg-faint);
+}
+.cp__cornerTL span:nth-child(1),
+.cp__cornerBR span:nth-child(1) { top: 0; left: 50%; width: 1px; height: 100%; transform: translateX(-50%); }
+.cp__cornerTL span:nth-child(2),
+.cp__cornerBR span:nth-child(2) { left: 0; top: 50%; width: 100%; height: 1px; transform: translateY(-50%); }
+
+/* ===================== Scene HERO ===================== */
+
+.cp__hero {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 120px 140px;
+ text-align: center;
+}
+
+.cp__hero-eyebrow {
+ display: flex;
+ align-items: center;
+ gap: 22px;
+ font-family: var(--f-mono);
+ font-size: 18px;
+ letter-spacing: 0.4em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ margin-bottom: 64px;
+}
+.cp__hero-eyebrow-bar {
+ width: 64px;
+ height: 1px;
+ background: var(--line-mid);
+}
+
+.cp__hero-title {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 168px;
+ line-height: 1.02;
+ letter-spacing: -0.02em;
+ color: var(--fg);
+ margin: 0;
+ max-width: 1500px;
+}
+.cp__hero-em {
+ font-style: italic;
+ color: var(--accent);
+ position: relative;
+}
+.cp__hero-em::after {
+ content: '';
+ position: absolute;
+ left: 4%;
+ right: 4%;
+ bottom: 8px;
+ height: 4px;
+ background: var(--accent);
+ opacity: 0.25;
+ transform-origin: left;
+ animation: cpUnderline 1100ms 320ms cubic-bezier(.2,.8,.2,1) backwards;
+}
+@keyframes cpUnderline {
+ from { transform: scaleX(0); opacity: 0; }
+ to { transform: scaleX(1); opacity: 0.25; }
+}
+
+/* ===================== Scene SPLIT ===================== */
+
+.cp__split {
+ position: absolute;
+ inset: 0;
+ padding: 110px 100px 100px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.cp__split-eyebrow {
+ display: flex;
+ align-items: center;
+ gap: 18px;
+ font-family: var(--f-mono);
+ font-size: 18px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ margin-bottom: 28px;
+}
+.cp__split-eyebrow-arrow {
+ color: var(--accent);
+ font-family: var(--f-sans);
+ letter-spacing: 0;
+}
+
+/* 分隔线(中央) */
+.cp__split-divider {
+ position: absolute;
+ top: 220px;
+ bottom: 200px;
+ left: 50%;
+ transform: translateX(-50%);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ pointer-events: none;
+}
+.cp__split-divider-line {
+ width: 1px;
+ flex: 1;
+ background: linear-gradient(
+ to bottom,
+ transparent,
+ var(--line-mid) 20%,
+ var(--line-mid) 80%,
+ transparent
+ );
+}
+.cp__split-divider-knob {
+ width: 36px;
+ height: 36px;
+ border: 1px solid var(--line-strong);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-family: var(--f-serif);
+ font-size: 28px;
+ color: var(--accent);
+ background: var(--bg);
+ margin: 14px 0;
+}
+
+/* 两栏 */
+.cp__columns {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 120px;
+ width: 100%;
+ max-width: 1640px;
+ margin-top: 24px;
+ flex: 1;
+}
+
+.cp__col {
+ display: flex;
+ flex-direction: column;
+ padding: 8px 8px 0;
+}
+.cp__col--left { padding-right: 80px; }
+.cp__col--right { padding-left: 80px; }
+
+.cp__col-pct {
+ display: flex;
+ align-items: baseline;
+ gap: 6px;
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 200px;
+ line-height: 1;
+ color: var(--fg);
+ letter-spacing: -0.04em;
+ margin-bottom: 12px;
+}
+.cp__col--right .cp__col-pct {
+ color: var(--accent);
+}
+.cp__col-pct-sign {
+ font-family: var(--f-serif);
+ font-size: 100px;
+ color: var(--fg-mute);
+}
+.cp__col--right .cp__col-pct-sign {
+ color: var(--accent);
+ opacity: 0.7;
+}
+
+.cp__col-kicker {
+ font-family: var(--f-mono);
+ font-size: 16px;
+ letter-spacing: 0.34em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ margin-bottom: 14px;
+}
+
+.cp__col-title {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 86px;
+ line-height: 1;
+ margin: 0 0 22px;
+ color: var(--fg);
+}
+
+.cp__col-desc {
+ font-family: var(--f-sans);
+ font-size: 26px;
+ line-height: 1.5;
+ color: var(--fg-soft);
+ margin: 0 0 28px;
+ max-width: 540px;
+}
+
+/* 左:进度计 */
+.cp__col-meter {
+ position: relative;
+ height: 32px;
+ margin-bottom: 28px;
+ max-width: 540px;
+}
+.cp__col-meter-bar {
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(
+ to right,
+ var(--accent),
+ var(--accent-deep)
+ );
+ height: 4px;
+ top: 14px;
+ border-radius: 1px;
+ transition: width 1200ms cubic-bezier(.2,.8,.2,1) 600ms;
+}
+.cp__col-meter-ticks {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+.cp__col-meter-ticks span {
+ width: 1px;
+ height: 12px;
+ background: var(--line-mid);
+}
+
+.cp__col-tags {
+ display: flex;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+.cp__col-tags span {
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ border: 1px solid var(--line-mid);
+ padding: 6px 12px;
+ border-radius: var(--r-pill);
+}
+
+/* 右:文档预览 */
+.cp__doc {
+ margin-top: 4px;
+ max-width: 620px;
+ background: oklch(0.330 0.014 60);
+ border: 1px solid var(--line-mid);
+ border-radius: 4px;
+ overflow: hidden;
+ box-shadow:
+ 0 32px 60px -32px oklch(0 0 0 / 0.4),
+ inset 0 1px 0 oklch(0.965 0.018 78 / 0.04);
+}
+.cp__doc-bar {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 10px 14px;
+ background: oklch(0.380 0.014 60);
+ border-bottom: 1px solid var(--line-mid);
+ font-family: var(--f-mono);
+ font-size: 13px;
+ letter-spacing: 0.06em;
+ color: var(--fg-mute);
+}
+.cp__doc-bar-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: oklch(0.420 0.020 60);
+}
+.cp__doc-bar-name {
+ margin-left: 8px;
+}
+
+.cp__doc-body {
+ padding: 14px 18px 22px;
+ min-height: 280px;
+ font-family: var(--f-mono);
+ font-size: 15px;
+ line-height: 1.65;
+ color: var(--fg-soft);
+ position: relative;
+}
+.cp__doc-line {
+ display: flex;
+ gap: 16px;
+ white-space: pre;
+ align-items: baseline;
+}
+.cp__doc-line-no {
+ width: 22px;
+ flex: none;
+ color: var(--fg-faint);
+ text-align: right;
+}
+.cp__doc-line-text {
+ color: var(--fg-soft);
+}
+.cp__doc-cursor {
+ display: inline-block;
+ margin-top: 6px;
+ margin-left: 38px;
+ color: var(--accent);
+ animation: cpCursor 1.1s steps(1) infinite;
+}
+@keyframes cpCursor {
+ 0%, 50% { opacity: 1; }
+ 51%, 100% { opacity: 0; }
+}
+
+/* leaked 徽章 */
+.cp__leaked {
+ position: absolute;
+ bottom: 130px;
+ left: 50%;
+ transform: translateX(-50%);
+ display: flex;
+ align-items: center;
+ gap: 28px;
+ padding: 20px 32px;
+ background: oklch(0.340 0.014 60);
+ border: 1px solid var(--line-strong);
+ border-radius: 4px;
+ box-shadow: 0 24px 48px -20px oklch(0 0 0 / 0.45);
+ max-width: 920px;
+}
+.cp__leaked-stamp {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-family: var(--f-mono);
+ font-size: 22px;
+ letter-spacing: 0.32em;
+ color: var(--crimson);
+ font-weight: 600;
+ border: 2px solid var(--crimson);
+ padding: 10px 16px 8px;
+ border-radius: 2px;
+ transform: rotate(-3deg);
+}
+.cp__leaked-stamp-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: var(--crimson);
+ box-shadow: 0 0 8px var(--crimson);
+ animation: cpStampPulse 1.2s ease-in-out infinite;
+}
+@keyframes cpStampPulse {
+ 0%, 100% { opacity: 0.5; }
+ 50% { opacity: 1; }
+}
+.cp__leaked-meta {
+ display: flex;
+ align-items: center;
+ gap: 28px;
+}
+.cp__leaked-meta-time {
+ display: flex;
+ align-items: baseline;
+ gap: 6px;
+ font-family: var(--f-serif);
+ font-size: 72px;
+ line-height: 1;
+ color: var(--fg);
+}
+.cp__leaked-meta-lt {
+ font-family: var(--f-serif);
+ font-size: 56px;
+ color: var(--fg-mute);
+ margin-right: 4px;
+}
+.cp__leaked-meta-unit {
+ font-family: var(--f-mono);
+ font-size: 18px;
+ letter-spacing: 0.28em;
+ color: var(--fg-mute);
+ margin-left: 6px;
+}
+.cp__leaked-meta-text {
+ font-family: var(--f-sans);
+ font-size: 22px;
+ line-height: 1.45;
+ color: var(--fg-soft);
+}
+
+/* pivot 转场指引 */
+.cp__pivot {
+ position: absolute;
+ bottom: 56px;
+ left: 50%;
+ transform: translateX(-50%);
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ font-family: var(--f-mono);
+ font-size: 18px;
+ letter-spacing: 0.28em;
+ text-transform: uppercase;
+ color: var(--accent);
+}
+.cp__pivot-arrow {
+ width: 56px;
+ height: 1px;
+ background: var(--accent);
+ position: relative;
+}
+.cp__pivot-arrow::after {
+ content: '';
+ position: absolute;
+ right: -2px;
+ top: 50%;
+ width: 8px;
+ height: 8px;
+ border-right: 1px solid var(--accent);
+ border-bottom: 1px solid var(--accent);
+ transform: translateY(-50%) rotate(-45deg);
+}
+.cp__pivot-text {
+ animation: cpPivotBlink 2.4s ease-in-out infinite;
+}
+@keyframes cpPivotBlink {
+ 0%, 100% { opacity: 0.65; }
+ 50% { opacity: 1; }
+}
diff --git a/web/src/chapters/03-core-point/index.tsx b/web/src/chapters/03-core-point/index.tsx
new file mode 100644
index 0000000..e057234
--- /dev/null
+++ b/web/src/chapters/03-core-point/index.tsx
@@ -0,0 +1,204 @@
+import type { ChapterContext, ChapterDef } from '../types';
+import { Reveal } from '../../shared/Reveal';
+import { SceneFade } from '../../shared/SceneFade';
+import { NumberTicker } from '../../shared/NumberTicker';
+import './CorePoint.css';
+
+/**
+ * Chapter 03 · 核心观点
+ *
+ * 口播主旨:
+ * 「Claude Design 之所以强,一半靠 Opus 4.7,另一半靠精心设计的提示词。
+ * 上线不到 24 小时,完整系统提示词被扒出来。下面,我们逐条拆解。」
+ *
+ * 节奏(6 步 / step 0..5):
+ * 0 环境(深墨底 + 网格氛围,一行小标 eyebrow)
+ * 1 hero 问句:"Claude Design · 为什么这么强?"
+ * 2 答案展开:50/50 分屏,左 OPUS 4.7 / 右 SYSTEM PROMPT
+ * 3 右侧"提示词从天而降"——真实片段逐条落入文档预览
+ * 4 leaked 事件徽章:"< 24 HOURS · LEAKED"
+ * 5 转场指引:"下面,我们逐条拆解 ↓"
+ */
+
+const PROMPT_LINES: string[] = [
+ 'You are an expert designer working with the user as a manager.',
+ 'You produce design artifacts on behalf of the user using HTML.',
+ 'HTML is your tool, but your medium and output format vary.',
+ 'You must embody an expert in that domain:',
+ ' animator, UX designer, slide designer, prototyper, etc.',
+ 'Avoid web design tropes and conventions',
+ ' unless you are making a web page.',
+ '## Your workflow',
+ '1. Understand user needs. Ask clarifying questions ...',
+ '2. Explore provided resources. Read the design system ...',
+];
+
+function CorePoint({ localStep }: ChapterContext) {
+ const at = (n: number) => localStep >= n;
+
+ // 场景:1+ 进入分析;2 之前是单独 hero 居中
+ const sceneHero = localStep <= 1;
+ const sceneSplit = localStep >= 2;
+
+ return (
+
+ {/* 装饰性背景网格 + 角落坐标 */}
+
+
+
+
+
+
+
+
+ {/* ───────── Scene HERO(step 0..1)───────── */}
+
+
+
+
+ 03 · 核心观点
+
+
+
+ {at(1) && (
+
+ Claude Design
+ 为什么这么强?
+
+ )}
+
+
+
+ {/* ───────── Scene SPLIT(step 2..5)───────── */}
+
+
+ {/* 顶部留下问句的小回响(不再是大字) */}
+
+ 为什么这么强?
+ →
+ 答案是
+
+
+ {/* 中央分隔线 */}
+
+
+ +
+
+
+
+
+ {/* —— 左:OPUS 4.7 —— */}
+
+
+
+ %
+
+ MODEL
+ Opus 4.7
+
+ Anthropic 当前旗舰模型 ——
+ 决策力、品味、长链推理与代码能力的综合上限
+
+
+
+
+
+ reasoning
+ taste
+ code
+
+
+
+ {/* —— 右:SYSTEM PROMPT —— */}
+
+
+
+ %
+
+ SYSTEM PROMPT
+ 提示词工程
+
+ ~420 行专家级 system prompt ——
+ 对模型的"角色 / 流程 / 边界 / 品味"做了极强约束
+
+
+ {/* 文档预览 */}
+
+
+
+
+
+ claude-design.system.md
+
+
+ {at(3) && PROMPT_LINES.map((line, i) => (
+
+ {String(i + 1).padStart(2, '0')}
+ {line}
+
+ ))}
+ {at(3) && (
+
+ ▍
+
+ )}
+
+
+
+
+
+ {/* leaked 徽章 */}
+ {at(4) && (
+
+
+
+ LEAKED
+
+
+
+ <
+
+ HOURS
+
+
+ 上线不到 24 小时,完整系统提示词被扒出,
+ 在安全 / 提示词圈广为流传
+
+
+
+ )}
+
+ {/* 转场指引 */}
+ {at(5) && (
+
+
+ 下面,我们逐条拆解这套提示词
+
+ )}
+
+
+
+ );
+}
+
+const def: ChapterDef = {
+ id: 'core-point',
+ title: '核心观点',
+ eyebrow: '03',
+ steps: 6,
+ theme: 'ink',
+ Component: CorePoint,
+};
+
+export default def;
diff --git a/web/src/chapters/04-role/Role.css b/web/src/chapters/04-role/Role.css
new file mode 100644
index 0000000..698d6cc
--- /dev/null
+++ b/web/src/chapters/04-role/Role.css
@@ -0,0 +1,641 @@
+/* =========================================================
+ Chapter 04 · 角色定位
+ light 主题 · 引文 → 高亮 → benefit → 名片翻 → 全卡 + 收尾
+ ========================================================= */
+
+.role {
+ position: absolute;
+ inset: 0;
+ background: var(--bg);
+ color: var(--fg);
+ font-family: var(--f-sans);
+ overflow: hidden;
+}
+
+/* 顶部 eyebrow 贯穿全章节 */
+.role__eyebrow {
+ position: absolute;
+ top: 56px;
+ left: 96px;
+ display: flex;
+ align-items: center;
+ gap: 18px;
+ font-family: var(--f-mono);
+ font-size: 16px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ z-index: 4;
+}
+.role__eyebrow-num {
+ font-family: var(--f-serif);
+ font-size: 28px;
+ letter-spacing: 0;
+ color: var(--accent);
+ font-style: italic;
+}
+.role__eyebrow-bar {
+ width: 36px;
+ height: 1px;
+ background: var(--line-mid);
+}
+.role__eyebrow-bar--long { width: 96px; }
+.role__eyebrow-text { color: var(--fg); letter-spacing: 0.18em; }
+.role__eyebrow-mute { color: var(--fg-faint); font-size: 13px; }
+
+/* ===================== Scene QUOTE ===================== */
+
+.role__quote-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 140px 140px 120px;
+ text-align: center;
+}
+
+/* ===== prompt 源标签(mono 小字) ===== */
+.role__src {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ margin-bottom: 28px;
+ padding: 6px 12px;
+ border: 1px solid var(--line-mid);
+ border-radius: 2px;
+ background: var(--bg-2);
+}
+.role__src-bracket {
+ color: var(--accent);
+ font-weight: 600;
+}
+.role__src-label {
+ color: var(--fg);
+ font-weight: 600;
+ letter-spacing: 0.22em;
+}
+.role__src-sep {
+ color: var(--fg-faint);
+}
+.role__src-line {
+ color: var(--accent);
+ font-weight: 600;
+}
+.role__src-mute {
+ color: var(--fg-mute);
+}
+
+.role__quote-en {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-weight: 400;
+ font-size: 96px;
+ line-height: 1.18;
+ color: var(--fg);
+ margin: 0 0 48px;
+ max-width: 1500px;
+ letter-spacing: -0.01em;
+}
+
+.role__quote-marks {
+ font-family: var(--f-serif);
+ font-style: italic;
+ color: var(--fg-faint);
+ font-size: 110px;
+ line-height: 0;
+ vertical-align: -8px;
+ margin: 0 4px;
+}
+
+.role__quote-keyword {
+ position: relative;
+ font-style: italic;
+ color: var(--fg);
+ display: inline-block;
+ padding: 0 4px;
+ transition: color 360ms var(--ease-enter);
+}
+.role__quote-keyword.is-marked {
+ color: var(--accent);
+}
+.role__quote-keyword .role__quote-mark {
+ position: absolute;
+ left: -10%;
+ right: -10%;
+ bottom: 18px;
+ height: 3px;
+ background: var(--accent);
+ transform: scaleX(0);
+ transform-origin: left;
+ opacity: 0;
+}
+.role__quote-keyword.is-marked .role__quote-mark {
+ animation: roleUnderline 720ms cubic-bezier(.2,.8,.2,1) forwards;
+}
+.role__quote-keyword--alt.is-marked .role__quote-mark {
+ background: var(--accent-deep);
+ animation-delay: 220ms;
+}
+@keyframes roleUnderline {
+ from { transform: scaleX(0); opacity: 0; }
+ to { transform: scaleX(1); opacity: 0.7; }
+}
+
+.role__quote-cn {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 38px;
+ line-height: 1.6;
+ color: var(--fg-soft);
+ margin: 0 0 36px;
+ letter-spacing: 0.04em;
+}
+.role__quote-cn-key {
+ position: relative;
+ padding: 0 6px;
+ transition: color 360ms;
+}
+.role__quote-cn-key.is-marked {
+ color: var(--accent);
+ background: linear-gradient(180deg, transparent 60%, oklch(0.860 0.060 60 / 0.6) 60%);
+}
+.role__quote-cn-key--alt.is-marked {
+ color: var(--accent-deep);
+ background: linear-gradient(180deg, transparent 60%, oklch(0.860 0.060 60 / 0.6) 60%);
+}
+
+.role__quote-note {
+ font-family: var(--f-mono);
+ font-size: 18px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ margin-bottom: 56px;
+}
+.role__quote-note u {
+ color: var(--crimson);
+ text-decoration-thickness: 2px;
+ text-underline-offset: 4px;
+}
+
+.role__benefits {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 48px;
+ width: 100%;
+ max-width: 1280px;
+ margin-top: 8px;
+}
+
+.role__benefit {
+ position: relative;
+ padding: 28px 32px 32px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-radius: 6px;
+ text-align: left;
+ box-shadow: 0 24px 40px -28px oklch(0.180 0.014 60 / 0.4);
+}
+
+.role__benefit-num {
+ position: absolute;
+ top: -22px;
+ left: 28px;
+ width: 44px;
+ height: 44px;
+ background: var(--accent);
+ color: var(--paper);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-family: var(--f-serif);
+ font-size: 28px;
+ font-style: italic;
+}
+
+.role__benefit-title {
+ font-family: var(--f-serif);
+ font-size: 44px;
+ line-height: 1.1;
+ color: var(--fg);
+ margin: 8px 0 14px;
+}
+.role__benefit-desc {
+ font-family: var(--f-sans);
+ font-size: 22px;
+ line-height: 1.55;
+ color: var(--fg-soft);
+}
+
+/* ===================== Scene FLIP ===================== */
+
+.role__flip-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 140px 140px 80px;
+ gap: 32px;
+}
+
+/* ===== 原文引用块(prompt 风) ===== */
+.role__excerpt {
+ width: 100%;
+ max-width: 1280px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-left: 3px solid var(--accent);
+ border-radius: 2px;
+ padding: 22px 32px 26px;
+ text-align: left;
+ box-shadow: 0 18px 32px -22px oklch(0.180 0.014 60 / 0.30);
+}
+.role__excerpt-head {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-family: var(--f-mono);
+ font-size: 13px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ margin-bottom: 14px;
+ padding-bottom: 12px;
+ border-bottom: 1px dashed var(--line-mid);
+}
+.role__excerpt-body {
+ display: flex;
+ align-items: flex-start;
+ gap: 18px;
+}
+.role__excerpt-gt {
+ font-family: var(--f-mono);
+ font-size: 38px;
+ line-height: 1.2;
+ color: var(--accent);
+ flex: none;
+}
+.role__excerpt-text {
+ font-family: var(--f-mono);
+ font-size: 26px;
+ line-height: 1.55;
+ color: var(--fg);
+ flex: 1;
+}
+.role__excerpt-em {
+ font-style: normal;
+ color: var(--accent);
+ background: linear-gradient(180deg, transparent 70%, oklch(0.860 0.060 60 / 0.5) 70%);
+ padding: 0 2px;
+}
+.role__excerpt-list {
+ font-style: italic;
+ color: var(--accent-deep);
+}
+
+.role__flip-eyebrow {
+ display: flex;
+ align-items: baseline;
+ gap: 16px;
+ font-family: var(--f-serif);
+ font-size: 32px;
+ color: var(--fg-soft);
+ margin: 4px 0 0;
+}
+.role__flip-eyebrow-arrow {
+ color: var(--fg-mute);
+ font-family: var(--f-sans);
+ font-size: 28px;
+}
+.role__flip-eyebrow-em {
+ font-style: italic;
+ color: var(--accent);
+ font-size: 42px;
+ position: relative;
+}
+.role__flip-eyebrow-em::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 4px;
+ height: 3px;
+ background: var(--accent);
+ opacity: 0.3;
+ transform-origin: left;
+ animation: roleUnderline 800ms 220ms cubic-bezier(.2,.8,.2,1) backwards;
+}
+
+/* 翻牌窗:3 张卡片层叠,translateY 控制可见状态 */
+.role__flip-window {
+ position: relative;
+ width: 720px;
+ height: 380px;
+ perspective: 1600px;
+}
+
+.role__flip-slot {
+ position: absolute;
+ inset: 0;
+ transition:
+ transform 760ms cubic-bezier(.2,.8,.2,1),
+ opacity 600ms cubic-bezier(.2,.8,.2,1),
+ filter 600ms;
+ transform-origin: 50% 50% -200px;
+ will-change: transform, opacity;
+}
+.role__flip-slot[data-state="prev"] {
+ transform: translateY(-30%) rotateX(50deg);
+ opacity: 0;
+ pointer-events: none;
+ filter: blur(4px);
+}
+.role__flip-slot[data-state="current"] {
+ transform: translateY(0) rotateX(0deg);
+ opacity: 1;
+ filter: blur(0);
+}
+.role__flip-slot[data-state="next"] {
+ transform: translateY(30%) rotateX(-50deg);
+ opacity: 0;
+ pointer-events: none;
+ filter: blur(4px);
+}
+
+/* 进度刻度(步进) */
+.role__flip-ticks {
+ display: flex;
+ gap: 64px;
+ margin-top: 8px;
+}
+.role__flip-tick {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+ opacity: 0.32;
+ transition: opacity 380ms, transform 380ms;
+}
+.role__flip-tick.is-active {
+ opacity: 1;
+ transform: translateY(-2px);
+}
+.role__flip-tick-num {
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.32em;
+ color: var(--fg-mute);
+}
+.role__flip-tick.is-active .role__flip-tick-num {
+ color: var(--accent);
+}
+.role__flip-tick-name {
+ font-family: var(--f-serif);
+ font-size: 22px;
+ color: var(--fg);
+}
+.role__flip-tick.is-active .role__flip-tick-name {
+ color: var(--accent);
+ font-style: italic;
+}
+
+/* ===================== Card 视觉 ===================== */
+
+.role__card {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ background: var(--paper);
+ border: 1px solid var(--line-mid);
+ box-shadow:
+ 0 30px 50px -30px oklch(0.180 0.014 60 / 0.35),
+ 0 12px 18px -10px oklch(0.180 0.014 60 / 0.18),
+ inset 0 1px 0 oklch(0.965 0.018 78 / 1);
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+.role__card::before {
+ /* 纸感纹理 */
+ content: '';
+ position: absolute;
+ inset: 0;
+ background-image:
+ radial-gradient(circle at 20% 20%, oklch(0.180 0.014 60 / 0.04) 0, transparent 50%),
+ radial-gradient(circle at 80% 80%, oklch(0.180 0.014 60 / 0.04) 0, transparent 50%);
+ pointer-events: none;
+}
+.role__card--lg { padding: 32px 44px; }
+.role__card--sm { padding: 22px 26px; }
+
+.role__card-strip {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ font-family: var(--f-mono);
+ font-size: 13px;
+ letter-spacing: 0.28em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ padding-bottom: 18px;
+ border-bottom: 1px dashed var(--line-mid);
+}
+.role__card--sm .role__card-strip {
+ font-size: 11px;
+ padding-bottom: 12px;
+}
+.role__card-strip-dot {
+ width: 4px;
+ height: 4px;
+ border-radius: 50%;
+ background: var(--fg-faint);
+}
+
+.role__card-body {
+ flex: 1;
+ display: grid;
+ grid-template-columns: auto 1fr;
+ gap: 28px;
+ align-items: center;
+ padding: 22px 0;
+}
+
+.role__card-meta {
+ width: 110px;
+ height: 110px;
+ border-radius: 6px;
+ background: oklch(0.928 0.024 78);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--accent);
+}
+.role__card--sm .role__card-meta {
+ width: 64px;
+ height: 64px;
+}
+.role__icon {
+ width: 60px;
+ height: 60px;
+}
+.role__card--sm .role__icon {
+ width: 36px;
+ height: 36px;
+}
+
+.role__card-text {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+.role__card-en {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 36px;
+ color: var(--fg-mute);
+ line-height: 1;
+}
+.role__card--sm .role__card-en {
+ font-size: 22px;
+}
+.role__card-cn {
+ font-family: var(--f-serif);
+ font-size: 64px;
+ line-height: 1;
+ color: var(--fg);
+ letter-spacing: 0.02em;
+}
+.role__card--sm .role__card-cn {
+ font-size: 36px;
+}
+
+.role__card-foot {
+ display: flex;
+ align-items: baseline;
+ gap: 12px;
+ padding-top: 18px;
+ border-top: 1px solid var(--line-mid);
+}
+.role__card--sm .role__card-foot {
+ padding-top: 12px;
+}
+.role__card-ctx {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 28px;
+ color: var(--accent);
+}
+.role__card--sm .role__card-ctx {
+ font-size: 18px;
+}
+.role__card-ctx-en {
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.22em;
+ text-transform: uppercase;
+ color: var(--fg-faint);
+}
+.role__card--sm .role__card-ctx-en {
+ font-size: 11px;
+}
+
+/* 角注 */
+.role__card-corner {
+ position: absolute;
+ width: 12px;
+ height: 12px;
+ border: 1px solid var(--fg-faint);
+}
+.role__card-corner--tl { top: 8px; left: 8px; border-right: 0; border-bottom: 0; }
+.role__card-corner--tr { top: 8px; right: 8px; border-left: 0; border-bottom: 0; }
+.role__card-corner--bl { bottom: 8px; left: 8px; border-right: 0; border-top: 0; }
+.role__card-corner--br { bottom: 8px; right: 8px; border-left: 0; border-top: 0; }
+
+/* ===================== Scene ALL ===================== */
+
+.role__all-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 140px 100px 120px;
+}
+
+.role__all-row {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 36px;
+ width: 100%;
+ max-width: 1480px;
+ margin-bottom: 64px;
+}
+
+.role__all-slot {
+ height: 280px;
+ animation: roleAllRise 720ms cubic-bezier(.2,.8,.2,1) backwards;
+}
+.role__all-slot:nth-child(1) { transform: rotate(-1.5deg); }
+.role__all-slot:nth-child(2) { transform: rotate(0.5deg); }
+.role__all-slot:nth-child(3) { transform: rotate(-0.8deg); }
+@keyframes roleAllRise {
+ from { opacity: 0; transform: translateY(28px) rotate(-3deg); }
+ to { opacity: 1; }
+}
+
+.role__all-takeaway {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 88px;
+ line-height: 1.15;
+ text-align: center;
+ margin: 0 0 28px;
+ color: var(--fg);
+ max-width: 1400px;
+}
+.role__all-takeaway em {
+ font-style: italic;
+ color: var(--fg-soft);
+}
+.role__all-em {
+ color: var(--accent);
+ position: relative;
+}
+.role__all-em::after {
+ content: '';
+ position: absolute;
+ left: -4%;
+ right: -4%;
+ bottom: 6px;
+ height: 8px;
+ background: var(--accent);
+ opacity: 0.22;
+}
+
+.role__all-foot {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ font-family: var(--f-mono);
+ font-size: 16px;
+ letter-spacing: 0.22em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+}
+.role__all-foot-dot {
+ width: 4px;
+ height: 4px;
+ border-radius: 50%;
+ background: var(--fg-faint);
+}
diff --git a/web/src/chapters/04-role/index.tsx b/web/src/chapters/04-role/index.tsx
new file mode 100644
index 0000000..9ec882d
--- /dev/null
+++ b/web/src/chapters/04-role/index.tsx
@@ -0,0 +1,297 @@
+import type { ChapterContext, ChapterDef } from '../types';
+import { Reveal } from '../../shared/Reveal';
+import { SceneFade } from '../../shared/SceneFade';
+import './Role.css';
+
+/**
+ * Chapter 04 · 第一部分:角色定位
+ *
+ * 口播主旨:
+ * "你是一个专家设计师,用户是你的产品经理。"
+ * - 不说 "AI 助手",说 designer / manager 关系 → 决策果断 + 关键征询
+ * - 而且角色还会变:动画时 = Motion Designer;原型时 = UX Designer;幻灯时 = Deck Designer
+ * - 好的角色定位 = 动态的
+ *
+ * 节奏(8 步 / step 0..7):
+ * 0 环境(eyebrow)
+ * 1 hero 引文(英文 italic + 中文)
+ * 2 关键词高亮(designer / manager 圈出)
+ * 3 双 benefit 卡片(果断 / 征询)
+ * 4 名片切到 Motion Designer
+ * 5 → UX Designer
+ * 6 → Deck Designer
+ * 7 收尾:三张名片并列 + 结论"好的角色定位 · 是动态的"
+ */
+
+interface Role {
+ id: string;
+ en: string;
+ cn: string;
+ ctx: string;
+ ctxEn: string;
+ icon: 'motion' | 'ux' | 'deck';
+}
+
+const ROLES: Role[] = [
+ { id: 'motion', en: 'Motion Designer', cn: '动效设计师', ctx: '做动画时', ctxEn: 'when animating', icon: 'motion' },
+ { id: 'ux', en: 'UX Designer', cn: 'UX 设计师', ctx: '做原型时', ctxEn: 'when prototyping', icon: 'ux' },
+ { id: 'deck', en: 'Deck Designer', cn: 'Deck 设计师', ctx: '做幻灯片时', ctxEn: 'when decking', icon: 'deck' },
+];
+
+function RoleIcon({ kind }: { kind: Role['icon'] }) {
+ switch (kind) {
+ case 'motion':
+ return (
+
+
+
+
+
+
+ );
+ case 'ux':
+ return (
+
+
+
+
+
+
+ );
+ case 'deck':
+ return (
+
+
+
+
+
+ );
+ }
+}
+
+function RoleCard({ role, size = 'lg' }: { role: Role; size?: 'lg' | 'sm' }) {
+ return (
+
+
+ ROLE / {role.id.toUpperCase()}
+
+ WHEN ACTIVE
+
+
+
+
+
+
+
+
{role.en}
+
{role.cn}
+
+
+
+
+ {role.ctx}
+ {role.ctxEn}
+
+
+
+
+
+
+
+ );
+}
+
+function Role({ localStep }: ChapterContext) {
+ const at = (n: number) => localStep >= n;
+
+ // 三幕:引文 / 单卡片切换 / 收尾全卡
+ const sceneQuote = localStep <= 3;
+ const sceneFlip = localStep >= 4 && localStep <= 6;
+ const sceneAll = localStep >= 7;
+
+ const roleIndex = Math.min(2, Math.max(0, localStep - 4));
+
+ return (
+
+ {/* ═════════ Scene QUOTE(step 0..3)═════════ */}
+
+
+ {at(1) && (
+
+ [
+ SYSTEM PROMPT
+ ·
+ L01
+ /
+ 原文
+ ]
+
+ )}
+
+ {at(1) && (
+
+ "
+ You are an expert{' '}
+
+ designer
+
+
+ , working with the user as a{' '}
+
+ manager
+
+
+ .
+ "
+
+ )}
+
+ {at(1) && (
+
+ 你是一个专家
+ 设计师
+ —— 而用户,是你的
+ 产品经理
+ 。
+
+ )}
+
+ {at(2) && (
+
+ 注意 —— 这里 没有 说 "你是一个 AI 助手"
+
+ )}
+
+ {at(3) && (
+
+
+ A
+ 决策更果断
+
+ 设计师本就该有判断力 —— AI 不再事事征询,
+ 能直接拍板的,自己拍。
+
+
+
+
+ B
+ 关键节点 · 请示你
+
+ 因为你是 PM —— 在方向 / 取舍 / 命名这种
+ 关键节点上,最终还是你说了算。
+
+
+
+ )}
+
+
+
+ {/* ═════════ Scene FLIP(step 4..6)═════════ */}
+
+
+ {/* 原文参考块 —— 来自系统提示词 L04 */}
+
+
+ [
+ SYSTEM PROMPT
+ ·
+ L04
+ /
+ 紧接下一句
+ ]
+
+
+ >
+
+ HTML is your tool, but your{' '}
+ medium and output format vary .
+ You must embody an expert in that domain:{' '}
+
+ animator, UX designer, slide designer, prototyper, etc.
+
+
+
+
+
+
+ ↓
+ 翻成大白话 ——
+ 角色,还会变
+
+
+ {/* 名片"翻牌"窗 */}
+
+ {ROLES.map((r, i) => {
+ const state = i === roleIndex
+ ? 'current'
+ : i < roleIndex ? 'prev' : 'next';
+ return (
+
+
+
+ );
+ })}
+
+
+ {/* 步进刻度 */}
+
+ {ROLES.map((r, i) => (
+
+ 0{i + 1}
+ {r.cn}
+
+ ))}
+
+
+
+
+ {/* ═════════ Scene ALL(step 7)═════════ */}
+
+
+
+ {ROLES.map((r, i) => (
+
+
+
+ ))}
+
+
+
+ 好的角色定位 ——
+ 不是固定的,而是 动态 的。
+
+
+
+ 不会用做网页的脑子做 PPT
+
+ 不会给动画加页脚
+
+ 不会给幻灯片加导航栏
+
+
+
+
+ );
+}
+
+const def: ChapterDef = {
+ id: 'role',
+ title: '第一部分 · 角色定位',
+ eyebrow: '04',
+ steps: 8,
+ theme: 'light',
+ Component: Role,
+};
+
+export default def;
diff --git a/web/src/chapters/05-workflow/Workflow.css b/web/src/chapters/05-workflow/Workflow.css
new file mode 100644
index 0000000..96fe28b
--- /dev/null
+++ b/web/src/chapters/05-workflow/Workflow.css
@@ -0,0 +1,633 @@
+/* =========================================================
+ Chapter 05 · 第二部分 · 工作流
+ ink 主题 · 流水线 → 问 vs 干 → 极简总结
+ ========================================================= */
+
+.wf {
+ position: absolute;
+ inset: 0;
+ background: var(--bg);
+ color: var(--fg);
+ font-family: var(--f-sans);
+ overflow: hidden;
+}
+
+/* —— 顶部 eyebrow —— */
+.wf__eyebrow {
+ position: absolute;
+ top: 56px;
+ left: 96px;
+ display: flex;
+ align-items: center;
+ gap: 18px;
+ font-family: var(--f-mono);
+ font-size: 16px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ z-index: 4;
+}
+.wf__eyebrow-num {
+ font-family: var(--f-serif);
+ font-size: 28px;
+ letter-spacing: 0;
+ color: var(--accent);
+ font-style: italic;
+}
+.wf__eyebrow-bar {
+ width: 36px;
+ height: 1px;
+ background: var(--line-mid);
+}
+.wf__eyebrow-bar--long { width: 96px; }
+.wf__eyebrow-text { color: var(--fg); letter-spacing: 0.18em; }
+.wf__eyebrow-mute { color: var(--fg-faint); font-size: 13px; }
+
+/* —— 通用 prompt 源标签子部件 —— */
+.wf__src-bracket { color: var(--accent); font-weight: 600; }
+.wf__src-label { color: var(--fg); font-weight: 600; letter-spacing: 0.22em; }
+.wf__src-sep { color: var(--fg-faint); }
+.wf__src-line { color: var(--accent); font-weight: 600; }
+.wf__src-mute { color: var(--fg-mute); }
+
+/* ===================== Scene PIPELINE ===================== */
+
+.wf__pipe-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 140px 100px 100px;
+ gap: 80px;
+}
+
+/* 原文引用块(与 ch04 同款) */
+.wf__excerpt {
+ width: 100%;
+ max-width: 1280px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-left: 3px solid var(--accent);
+ border-radius: 2px;
+ padding: 22px 32px 26px;
+ text-align: left;
+ box-shadow: 0 18px 32px -22px oklch(0 0 0 / 0.5);
+}
+.wf__excerpt-head {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-family: var(--f-mono);
+ font-size: 13px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ margin-bottom: 14px;
+ padding-bottom: 12px;
+ border-bottom: 1px dashed var(--line-mid);
+}
+.wf__excerpt-body {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+.wf__excerpt-title {
+ font-family: var(--f-mono);
+ font-size: 22px;
+ color: var(--accent);
+ margin-bottom: 6px;
+}
+.wf__excerpt-list {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 10px 36px;
+ font-family: var(--f-mono);
+ font-size: 19px;
+ line-height: 1.45;
+ color: var(--fg-soft);
+}
+.wf__excerpt-list b {
+ color: var(--accent);
+ font-weight: 600;
+ margin-right: 6px;
+}
+.wf__excerpt-list code {
+ background: oklch(0.380 0.014 60);
+ padding: 0 6px;
+ border-radius: 2px;
+ color: var(--accent);
+}
+.wf__excerpt-list em {
+ font-style: italic;
+ color: var(--accent);
+ background: linear-gradient(180deg, transparent 70%, oklch(0.700 0.170 42 / 0.25) 70%);
+ padding: 0 2px;
+}
+
+/* —— 流水线 —— */
+.wf__pipeline {
+ position: relative;
+ width: 100%;
+ max-width: 1480px;
+ padding: 0 60px;
+}
+
+.wf__line {
+ position: absolute;
+ top: 50%;
+ left: 60px;
+ right: 60px;
+ height: 2px;
+ background: var(--line-mid);
+ border-radius: 1px;
+ transform: translateY(-50%);
+ overflow: hidden;
+}
+.wf__line-fill {
+ height: 100%;
+ background: linear-gradient(to right, var(--accent), var(--accent-deep));
+ transition: width 1100ms cubic-bezier(.2,.8,.2,1);
+ box-shadow: 0 0 12px var(--accent);
+}
+
+.wf__stations {
+ position: relative;
+ display: grid;
+ grid-template-columns: repeat(6, 1fr);
+ gap: 24px;
+}
+
+.wf__station {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 22px;
+ position: relative;
+ transition: filter 540ms;
+ filter: grayscale(0.6) brightness(0.6);
+}
+.wf__station.is-lit {
+ filter: grayscale(0) brightness(1);
+}
+
+.wf__station-cn {
+ font-family: var(--f-serif);
+ font-size: 30px;
+ color: var(--fg);
+ height: 36px;
+}
+.wf__station-en {
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.28em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+}
+.wf__station.is-lit .wf__station-en {
+ color: var(--accent);
+}
+
+.wf__station-node {
+ position: relative;
+ width: 56px;
+ height: 56px;
+ border-radius: 50%;
+ background: var(--bg);
+ border: 2px solid var(--line-mid);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-family: var(--f-serif);
+ font-size: 28px;
+ font-style: italic;
+ color: var(--fg-mute);
+ z-index: 1;
+ transition: border-color 540ms, background 540ms, color 540ms, transform 540ms;
+}
+.wf__station.is-lit .wf__station-node {
+ background: var(--accent);
+ border-color: var(--accent);
+ color: var(--paper);
+ transform: scale(1.08);
+ box-shadow: 0 0 0 6px oklch(0.700 0.170 42 / 0.15);
+}
+.wf__station-no {
+ z-index: 2;
+}
+.wf__station-pulse {
+ position: absolute;
+ inset: -6px;
+ border-radius: 50%;
+ border: 1px solid var(--accent);
+ opacity: 0;
+ pointer-events: none;
+}
+.wf__station.is-lit .wf__station-pulse {
+ animation: wfPulse 1.6s ease-out infinite;
+}
+@keyframes wfPulse {
+ 0% { transform: scale(0.9); opacity: 0.7; }
+ 100% { transform: scale(1.6); opacity: 0; }
+}
+
+/* ===================== Scene DECIDE ===================== */
+
+.wf__decide-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 140px 100px 80px;
+}
+
+.wf__decide-head {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 14px;
+ margin-bottom: 56px;
+ text-align: center;
+}
+.wf__decide-num {
+ font-family: var(--f-mono);
+ font-size: 16px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--accent);
+}
+.wf__decide-title {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 76px;
+ line-height: 1.15;
+ color: var(--fg);
+ margin: 0;
+}
+.wf__decide-title em {
+ font-style: italic;
+ color: var(--accent);
+}
+.wf__decide-do {
+ position: relative;
+}
+.wf__decide-do::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 4px;
+ height: 4px;
+ background: var(--accent);
+ opacity: 0.3;
+ transform-origin: left;
+ animation: wfUnderline 800ms 320ms cubic-bezier(.2,.8,.2,1) backwards;
+}
+@keyframes wfUnderline {
+ from { transform: scaleX(0); opacity: 0; }
+ to { transform: scaleX(1); opacity: 0.3; }
+}
+.wf__decide-rule {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ padding: 6px 14px;
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.24em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ border: 1px solid var(--line-mid);
+ border-radius: 2px;
+ background: var(--bg-2);
+}
+.wf__decide-rule-text {
+ color: var(--fg);
+ letter-spacing: 0.16em;
+}
+
+/* —— 决策对比(两栏 + 中央 vs) —— */
+.wf__decide-grid {
+ position: relative;
+ display: grid;
+ grid-template-columns: 1fr 80px 1fr;
+ gap: 24px;
+ width: 100%;
+ max-width: 1500px;
+ align-items: stretch;
+}
+
+.wf__chat {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ padding: 28px 28px 20px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-radius: 4px;
+ position: relative;
+}
+.wf__chat--ask { border-top: 3px solid var(--crimson); }
+.wf__chat--do { border-top: 3px solid var(--accent); }
+
+.wf__chat-tag {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-family: var(--f-mono);
+ font-size: 13px;
+ letter-spacing: 0.24em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ margin-bottom: 4px;
+}
+.wf__chat-tag-dot {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ background: var(--crimson);
+ box-shadow: 0 0 6px var(--crimson);
+ animation: wfPulse 1.6s ease-out infinite;
+}
+.wf__chat-tag-dot--do {
+ background: var(--accent);
+ box-shadow: 0 0 6px var(--accent);
+}
+
+.wf__bubble {
+ padding: 16px 20px;
+ border-radius: 12px;
+ position: relative;
+ font-family: var(--f-sans);
+ font-size: 22px;
+ line-height: 1.45;
+}
+.wf__bubble p { margin: 0; }
+.wf__bubble-meta {
+ display: block;
+ font-family: var(--f-mono);
+ font-size: 11px;
+ letter-spacing: 0.28em;
+ text-transform: uppercase;
+ color: var(--fg-faint);
+ margin-bottom: 6px;
+}
+.wf__bubble--user {
+ background: oklch(0.380 0.014 60);
+ border: 1px solid var(--line-mid);
+ border-bottom-left-radius: 2px;
+ align-self: flex-start;
+ max-width: 84%;
+}
+.wf__bubble--ai {
+ background: oklch(0.355 0.018 60);
+ border: 1px solid oklch(0.700 0.170 42 / 0.4);
+ border-bottom-right-radius: 2px;
+ align-self: flex-end;
+ max-width: 92%;
+ box-shadow: 0 8px 16px -8px oklch(0.700 0.170 42 / 0.25);
+}
+
+.wf__qmarks {
+ position: absolute;
+ top: -22px;
+ right: 20px;
+ display: flex;
+ gap: 8px;
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 36px;
+ color: var(--crimson);
+ pointer-events: none;
+}
+.wf__qmarks span {
+ animation: wfQ 2.4s ease-in-out infinite;
+ opacity: 0;
+}
+@keyframes wfQ {
+ 0%, 100% { opacity: 0; transform: translateY(8px); }
+ 20%, 70% { opacity: 0.9; transform: translateY(0); }
+}
+
+.wf__action {
+ margin-top: 14px;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+.wf__action-bar {
+ height: 6px;
+ background: linear-gradient(to right, var(--accent) 0%, var(--accent-deep) 100%);
+ border-radius: 1px;
+ transform-origin: left;
+ animation: wfBar 1.2s cubic-bezier(.2,.8,.2,1) infinite;
+}
+.wf__action-bar:nth-child(1) { width: 70%; animation-delay: 0ms; }
+.wf__action-bar:nth-child(2) { width: 90%; animation-delay: 200ms; }
+.wf__action-bar:nth-child(3) { width: 50%; animation-delay: 400ms; }
+@keyframes wfBar {
+ 0% { transform: scaleX(0); opacity: 0.4; }
+ 60% { transform: scaleX(1); opacity: 1; }
+ 100% { transform: scaleX(1); opacity: 0.4; }
+}
+
+.wf__chat-verdict {
+ margin-top: auto;
+ padding: 12px 14px;
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.22em;
+ text-transform: uppercase;
+ text-align: center;
+ border: 1px dashed var(--line-mid);
+ border-radius: 2px;
+}
+.wf__chat-verdict--ask { color: var(--crimson); border-color: var(--crimson); }
+.wf__chat-verdict--do { color: var(--accent); border-color: var(--accent); }
+
+.wf__decide-vs {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ pointer-events: none;
+}
+.wf__decide-vs-line {
+ width: 1px;
+ flex: 1;
+ background: var(--line-mid);
+}
+.wf__decide-vs-knob {
+ width: 44px;
+ height: 44px;
+ border: 1px solid var(--line-strong);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 22px;
+ color: var(--fg-mute);
+ background: var(--bg);
+}
+
+/* ===================== Scene SUMMARY ===================== */
+
+.wf__sum-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 140px 100px 80px;
+ gap: 28px;
+}
+
+.wf__sum-num {
+ font-family: var(--f-mono);
+ font-size: 16px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--accent);
+}
+
+.wf__sum-hero {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 88px;
+ line-height: 1.15;
+ text-align: center;
+ margin: 0;
+ color: var(--fg);
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+.wf__sum-hero-en em {
+ font-style: italic;
+ color: var(--accent);
+}
+.wf__sum-hero-cn {
+ font-size: 56px;
+ color: var(--fg-soft);
+}
+.wf__sum-hero-cn em {
+ font-style: italic;
+ color: var(--accent);
+}
+
+.wf__sum-source {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ padding: 6px 14px;
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ border: 1px solid var(--line-mid);
+ border-radius: 2px;
+ background: var(--bg-2);
+}
+.wf__sum-source-quote {
+ font-family: var(--f-mono);
+ letter-spacing: 0.04em;
+ text-transform: none;
+ color: var(--fg);
+ margin-left: 6px;
+}
+
+.wf__sum-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 36px;
+ width: 100%;
+ max-width: 1400px;
+ margin-top: 18px;
+}
+
+.wf__sum-card {
+ position: relative;
+ padding: 22px 26px 24px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-radius: 4px;
+ overflow: hidden;
+}
+.wf__sum-card--bad {
+ border-top: 3px solid var(--crimson);
+}
+.wf__sum-card--good {
+ border-top: 3px solid var(--accent);
+ box-shadow: 0 16px 28px -18px oklch(0.700 0.170 42 / 0.35);
+}
+.wf__sum-card-tag {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.22em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ margin-bottom: 14px;
+ padding-bottom: 12px;
+ border-bottom: 1px dashed var(--line-mid);
+}
+.wf__sum-x {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 20px;
+ color: var(--crimson);
+}
+.wf__sum-check {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 20px;
+ color: var(--accent);
+}
+.wf__sum-card-body {
+ font-family: var(--f-sans);
+ font-size: 22px;
+ line-height: 1.5;
+ color: var(--fg-soft);
+}
+.wf__sum-card-body p { margin: 0 0 8px; }
+.wf__sum-card-body p:last-child { margin: 0; }
+.wf__sum-card-body b {
+ font-family: var(--f-mono);
+ font-size: 16px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--accent);
+ margin-right: 10px;
+}
+.wf__sum-card-body s {
+ text-decoration-color: var(--crimson);
+ text-decoration-thickness: 2px;
+ color: var(--fg-mute);
+}
+
+.wf__sum-strike {
+ position: absolute;
+ left: -10%;
+ right: -10%;
+ top: 50%;
+ height: 2px;
+ background: var(--crimson);
+ transform: translateY(-50%) rotate(-3deg);
+ opacity: 0;
+ animation: wfStrike 700ms 480ms cubic-bezier(.2,.8,.2,1) forwards;
+ transform-origin: left;
+}
+@keyframes wfStrike {
+ from { opacity: 0; transform: translateY(-50%) rotate(-3deg) scaleX(0); }
+ to { opacity: 0.7; transform: translateY(-50%) rotate(-3deg) scaleX(1); }
+}
diff --git a/web/src/chapters/05-workflow/index.tsx b/web/src/chapters/05-workflow/index.tsx
new file mode 100644
index 0000000..07ff6eb
--- /dev/null
+++ b/web/src/chapters/05-workflow/index.tsx
@@ -0,0 +1,266 @@
+import type { ChapterContext, ChapterDef } from '../types';
+import { Reveal } from '../../shared/Reveal';
+import { SceneFade } from '../../shared/SceneFade';
+import './Workflow.css';
+
+/**
+ * Chapter 05 · 第二部分:工作流
+ *
+ * 口播主旨:
+ * - 六步流程:理解需求 → 探索资源 → 制定计划 → 搭建结构 → 完成验证 → 极简总结
+ * - 细节①:什么时候问 / 什么时候直接干 —— 信息够就干,不够才问
+ * 用户: "做个 PPT" → AI 先问几个问题
+ * 用户: "做个 PPT, 工程全员 All Hands, 10 min" → AI 直接动手
+ * - 细节②:极简总结 —— "Summarize EXTREMELY BRIEFLY"
+ *
+ * 节奏(7 步 / step 0..6):
+ * 0 环境(eyebrow)
+ * 1 原文 prompt block + 六站流水线(空)
+ * 2 点亮第 1-3 站(line 推进)
+ * 3 点亮第 4-6 站
+ * 4 pivot:细节① "何时问 vs 何时干"
+ * 5 双栏对话气泡对比
+ * 6 细节② "Summarize EXTREMELY BRIEFLY" 原文 + 反例 / 正例对比
+ */
+
+interface Station {
+ no: string;
+ en: string;
+ cn: string;
+}
+
+const STATIONS: Station[] = [
+ { no: '1', en: 'Understand', cn: '理解需求' },
+ { no: '2', en: 'Explore', cn: '探索资源' },
+ { no: '3', en: 'Plan', cn: '制定计划' },
+ { no: '4', en: 'Build', cn: '搭建结构' },
+ { no: '5', en: 'Verify', cn: '完成验证' },
+ { no: '6', en: 'Brief', cn: '极简总结' },
+];
+
+function Workflow({ localStep }: ChapterContext) {
+ const at = (n: number) => localStep >= n;
+
+ // —— 三幕 ——
+ const scenePipe = localStep <= 3;
+ const sceneDecide = localStep === 4 || localStep === 5;
+ const sceneSummary = localStep >= 6;
+
+ // 流水线点亮进度 0..6
+ const litCount = (() => {
+ if (localStep < 1) return 0;
+ if (localStep === 1) return 0;
+ if (localStep === 2) return 3;
+ return 6;
+ })();
+ const linePct = (litCount / 6) * 100;
+
+ return (
+
+ {/* ════════════ Scene PIPELINE(step 0..3)════════════ */}
+
+
+ {at(1) && (
+
+
+ [
+ SYSTEM PROMPT
+ ·
+ L17-23
+ /
+ 原文
+ ]
+
+
+
## Your workflow
+
+ 1. Understand user needs ...
+ 2. Explore provided resources ...
+ 3. Plan and/or make a todo list.
+ 4. Build folder structure ...
+ 5. Finish: call done ...
+ 6. Summarize EXTREMELY BRIEFLY — caveats and next steps only.
+
+
+
+ )}
+
+ {at(1) && (
+
+ {/* 底部基线 */}
+
+
+ {/* 6 站 */}
+
+ {STATIONS.map((s, i) => {
+ const lit = i < litCount;
+ return (
+
+
{s.cn}
+
+ {s.no}
+
+
+
{s.en}
+
+ );
+ })}
+
+
+ )}
+
+
+
+ {/* ════════════ Scene DECIDE(step 4..5)════════════ */}
+
+
+
+ 细节①
+
+ 什么时候问 ?什么时候直接干 ?
+
+
+ [
+ RULE
+ ]
+ 信息充足就干 · 信息不足才问
+
+
+
+
+ {/* —— 左:模糊请求 → 反复问 —— */}
+
+
+
+ AMBIGUOUS · 模糊请求
+
+
+
+
+
+
CLAUDE
+
受众?时长?正式度?品牌?数据有吗?...
+
+ ?
+ ?
+ ?
+ ?
+
+
+
+
+ → ASK QUESTIONS
+
+
+
+ {/* —— 中央分隔 —— */}
+
+
+ vs
+
+
+
+ {/* —— 右:详细请求 → 直接动手 —— */}
+ {at(5) && (
+
+
+
+ ENOUGH INFO · 信息够
+
+
+
+
USER
+
帮我做个 PPT,工程全员 All Hands,10 分钟
+
+
+
+
CLAUDE
+
好的,开始 ——
+
+
+
+
+
+
+
+
+ → NO QUESTIONS · GO BUILD
+
+
+ )}
+
+
+
+
+ {/* ════════════ Scene SUMMARY(step 6)════════════ */}
+
+
+
+ 细节②
+
+
+
+ Summarize EXTREMELY BRIEFLY
+ 只说 注意事项 与 下一步 。
+
+
+
+ [
+ SYSTEM PROMPT
+ ·
+ L23
+ ]
+
+ “Summarize EXTREMELY BRIEFLY — caveats and next steps only.”
+
+
+
+
+ {/* 反例 */}
+
+
+ × 复述自己干了什么
+
+
+ 我先创建了 Header.tsx ,然后又新增了 Hero.tsx ,
+ 接着把样式拆分到 theme.ts ,又给按钮加了 hover ...
+
+
+
+
+ {/* 正例 */}
+
+
+ ✓ 注意事项 + 下一步
+
+
+
caveats — 暂未做响应式 / 文案为占位
+
next — 加 hover 状态 / 替换真实文案
+
+
+
+
+
+
+ );
+}
+
+const def: ChapterDef = {
+ id: 'workflow',
+ title: '第二部分 · 工作流',
+ eyebrow: '05',
+ steps: 7,
+ theme: 'ink',
+ Component: Workflow,
+};
+
+export default def;
diff --git a/web/src/chapters/06-anti-ai/AntiAi.css b/web/src/chapters/06-anti-ai/AntiAi.css
new file mode 100644
index 0000000..6dd32ee
--- /dev/null
+++ b/web/src/chapters/06-anti-ai/AntiAi.css
@@ -0,0 +1,564 @@
+/* =========================================================
+ Chapter 06 · 第三部分 · 去 AI 味
+ light 主题 · hero → 反面教材 grid + 红斜线 → 字体黑名单 / 替代
+ ========================================================= */
+
+.aa {
+ position: absolute;
+ inset: 0;
+ background: var(--bg);
+ color: var(--fg);
+ font-family: var(--f-sans);
+ overflow: hidden;
+}
+
+/* —— 顶部 eyebrow —— */
+.aa__eyebrow {
+ position: absolute;
+ top: 56px;
+ left: 96px;
+ display: flex;
+ align-items: center;
+ gap: 18px;
+ font-family: var(--f-mono);
+ font-size: 16px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ z-index: 4;
+}
+.aa__eyebrow-num {
+ font-family: var(--f-serif);
+ font-size: 28px;
+ letter-spacing: 0;
+ color: var(--accent);
+ font-style: italic;
+}
+.aa__eyebrow-bar {
+ width: 36px;
+ height: 1px;
+ background: var(--line-mid);
+}
+.aa__eyebrow-bar--long { width: 96px; }
+.aa__eyebrow-text { color: var(--fg); letter-spacing: 0.18em; }
+.aa__eyebrow-mute { color: var(--fg-faint); font-size: 13px; }
+
+/* —— 通用 prompt 源标签子部件 —— */
+.aa__src-bracket { color: var(--accent); font-weight: 600; }
+.aa__src-label { color: var(--fg); font-weight: 600; letter-spacing: 0.22em; }
+.aa__src-sep { color: var(--fg-faint); }
+.aa__src-line { color: var(--accent); font-weight: 600; }
+.aa__src-mute { color: var(--fg-mute); }
+
+/* ===================== Scene HERO ===================== */
+
+.aa__hero {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 140px 140px 120px;
+ text-align: center;
+ gap: 32px;
+}
+
+.aa__hero-title {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 168px;
+ line-height: 1.05;
+ margin: 0;
+}
+.aa__hero-em {
+ font-style: italic;
+ color: var(--accent);
+ position: relative;
+}
+.aa__hero-em::after {
+ content: '';
+ position: absolute;
+ left: 4%;
+ right: 4%;
+ bottom: 8px;
+ height: 6px;
+ background: var(--accent);
+ opacity: 0.22;
+ transform-origin: left;
+ animation: aaUnderline 1100ms 360ms cubic-bezier(.2,.8,.2,1) backwards;
+}
+@keyframes aaUnderline {
+ from { transform: scaleX(0); opacity: 0; }
+ to { transform: scaleX(1); opacity: 0.22; }
+}
+
+.aa__hero-sub {
+ font-family: var(--f-serif);
+ font-size: 36px;
+ line-height: 1.4;
+ color: var(--fg-soft);
+ margin: 0;
+ max-width: 1100px;
+}
+.aa__hero-sub em {
+ font-style: italic;
+ color: var(--accent);
+}
+
+.aa__excerpt {
+ width: 100%;
+ max-width: 1100px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-left: 3px solid var(--accent);
+ border-radius: 2px;
+ padding: 18px 28px 22px;
+ text-align: left;
+ margin-top: 12px;
+}
+.aa__excerpt-head {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-family: var(--f-mono);
+ font-size: 13px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ margin-bottom: 10px;
+}
+.aa__excerpt-body {
+ display: flex;
+ align-items: flex-start;
+ gap: 14px;
+}
+.aa__excerpt-gt {
+ font-family: var(--f-mono);
+ font-size: 28px;
+ color: var(--accent);
+}
+.aa__excerpt-text {
+ font-family: var(--f-mono);
+ font-size: 22px;
+ line-height: 1.5;
+ color: var(--fg);
+}
+.aa__excerpt-text em {
+ font-style: normal;
+ color: var(--accent);
+ background: linear-gradient(180deg, transparent 70%, oklch(0.860 0.060 60 / 0.6) 70%);
+ padding: 0 2px;
+}
+
+/* ===================== Scene GRID ===================== */
+
+.aa__grid-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 140px 100px 90px;
+ gap: 32px;
+}
+
+.aa__grid-cap {
+ font-family: var(--f-serif);
+ font-size: 32px;
+ font-style: italic;
+ color: var(--fg-soft);
+ text-align: center;
+}
+
+.aa__grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 28px;
+ width: 100%;
+ max-width: 1480px;
+}
+
+.aa__grid-slot {
+ animation: aaGridIn 720ms cubic-bezier(.2,.8,.2,1) backwards;
+}
+@keyframes aaGridIn {
+ from { opacity: 0; transform: translateY(28px) scale(0.94); }
+ to { opacity: 1; transform: none; }
+}
+
+.aa__bad {
+ display: flex;
+ flex-direction: column;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-radius: 4px;
+ overflow: hidden;
+ transition: filter 600ms, opacity 600ms;
+}
+.aa__bad.is-slashed {
+ filter: grayscale(0.55) brightness(0.92);
+ opacity: 0.78;
+}
+
+.aa__bad-canvas {
+ position: relative;
+ height: 220px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background:
+ repeating-linear-gradient(45deg,
+ oklch(0.180 0.014 60 / 0.025) 0 6px,
+ transparent 6px 12px);
+ overflow: hidden;
+}
+
+/* —— 反面教材 1:紫粉蓝渐变 —— */
+.aa__bad-gradient {
+ position: absolute;
+ inset: 14px;
+ border-radius: 12px;
+ background: linear-gradient(135deg, #a78bfa 0%, #f0abfc 35%, #67e8f9 70%, #fda4af 100%);
+ box-shadow: 0 12px 36px -10px oklch(0.6 0.18 320 / 0.4);
+}
+
+/* —— 反面教材 2:Emoji 滥用 —— */
+.aa__bad-emoji {
+ display: flex;
+ gap: 12px;
+ font-size: 44px;
+ line-height: 1;
+ filter: drop-shadow(0 2px 4px oklch(0 0 0 / 0.15));
+}
+
+/* —— 反面教材 3:左侧色条卡 —— */
+.aa__bad-leftbar {
+ position: relative;
+ width: 220px;
+ height: 130px;
+ background: white;
+ border-radius: 14px;
+ display: flex;
+ align-items: stretch;
+ overflow: hidden;
+ box-shadow: 0 16px 28px -10px oklch(0.180 0.014 60 / 0.18);
+}
+.aa__bad-leftbar-stripe {
+ width: 8px;
+ background: linear-gradient(to bottom, #6366f1, #a855f7);
+}
+.aa__bad-leftbar-body {
+ flex: 1;
+ padding: 18px 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+.aa__bad-leftbar-t {
+ width: 60%;
+ height: 14px;
+ background: oklch(0.4 0.04 280);
+ border-radius: 4px;
+}
+.aa__bad-leftbar-l {
+ width: 90%;
+ height: 8px;
+ background: oklch(0.85 0.02 280);
+ border-radius: 4px;
+}
+.aa__bad-leftbar-l--short { width: 60%; }
+
+/* —— 反面教材 4:烂大街字体 —— */
+.aa__bad-font {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ align-items: center;
+ font-size: 22px;
+ color: var(--fg);
+}
+.aa__bad-font span:first-child { font-weight: 600; font-size: 26px; }
+
+/* —— 反面教材 5:data slop —— */
+.aa__bad-data {
+ display: grid;
+ grid-template-columns: repeat(2, auto);
+ gap: 18px 32px;
+ font-family: var(--f-mono);
+ font-size: 22px;
+ color: var(--fg);
+}
+.aa__bad-data > div {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+.aa__bad-data span {
+ color: oklch(0.6 0.18 320);
+ font-size: 20px;
+}
+.aa__bad-data b {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-weight: 400;
+ font-size: 28px;
+ color: var(--fg);
+}
+
+/* —— 反面教材 6:复杂 SVG —— */
+.aa__bad-svg {
+ width: 130px;
+ height: 130px;
+ filter: drop-shadow(0 6px 12px oklch(0.6 0.18 320 / 0.35));
+}
+
+/* —— 红斜杠 —— */
+.aa__bad-slash {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0;
+ transition: opacity 360ms;
+}
+.aa__bad.is-slashed .aa__bad-slash {
+ opacity: 1;
+}
+.aa__bad-slash-line {
+ display: block;
+ width: 130%;
+ height: 5px;
+ background: var(--crimson);
+ transform: rotate(-18deg) scaleX(0);
+ transform-origin: left;
+ border-radius: 1px;
+ box-shadow:
+ 0 0 12px oklch(0.560 0.200 22 / 0.6),
+ 0 0 24px oklch(0.560 0.200 22 / 0.25);
+ animation-fill-mode: forwards;
+}
+.aa__bad.is-slashed .aa__bad-slash-line {
+ animation: aaSlash 520ms cubic-bezier(.6,-0.05,.2,1.2) forwards;
+}
+@keyframes aaSlash {
+ from { transform: rotate(-18deg) scaleX(0); }
+ to { transform: rotate(-18deg) scaleX(1); }
+}
+
+/* 错开每张卡的斜线触发节奏 */
+.aa__grid-slot:nth-child(1) .aa__bad-slash-line { animation-delay: 80ms; }
+.aa__grid-slot:nth-child(2) .aa__bad-slash-line { animation-delay: 180ms; }
+.aa__grid-slot:nth-child(3) .aa__bad-slash-line { animation-delay: 280ms; }
+.aa__grid-slot:nth-child(4) .aa__bad-slash-line { animation-delay: 380ms; }
+.aa__grid-slot:nth-child(5) .aa__bad-slash-line { animation-delay: 480ms; }
+.aa__grid-slot:nth-child(6) .aa__bad-slash-line { animation-delay: 580ms; }
+
+.aa__bad-foot {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ padding: 14px 18px 16px;
+ border-top: 1px solid var(--line-mid);
+ background: var(--bg);
+}
+.aa__bad-en {
+ font-family: var(--f-mono);
+ font-size: 13px;
+ letter-spacing: 0.28em;
+ text-transform: uppercase;
+ color: var(--fg-faint);
+}
+.aa__bad-cn {
+ font-family: var(--f-serif);
+ font-size: 26px;
+ color: var(--fg);
+}
+
+.aa__grid-verdict {
+ font-family: var(--f-serif);
+ font-size: 32px;
+ color: var(--fg-soft);
+ text-align: center;
+ display: flex;
+ align-items: center;
+ gap: 16px;
+}
+.aa__grid-verdict em {
+ font-style: italic;
+ color: var(--accent);
+}
+.aa__grid-verdict-mark {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 36px;
+ color: var(--crimson);
+}
+
+/* ===================== Scene FONTS ===================== */
+
+.aa__fonts-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 130px 100px 90px;
+ gap: 32px;
+}
+
+.aa__fonts-head {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 10px;
+ text-align: center;
+}
+.aa__fonts-num {
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--accent);
+}
+.aa__fonts-title {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 64px;
+ line-height: 1.15;
+ color: var(--fg);
+ margin: 0;
+}
+.aa__fonts-title em {
+ font-style: italic;
+ color: var(--accent);
+}
+
+.aa__fonts-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 36px;
+ width: 100%;
+ max-width: 1480px;
+}
+
+.aa__fonts-col {
+ display: flex;
+ flex-direction: column;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-radius: 4px;
+ padding: 22px 28px 26px;
+}
+.aa__fonts-col--ban {
+ border-top: 3px solid var(--crimson);
+}
+.aa__fonts-col--alt {
+ border-top: 3px solid var(--accent);
+ box-shadow: 0 16px 28px -18px oklch(0.700 0.170 42 / 0.35);
+}
+
+.aa__fonts-col-tag {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.24em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ padding-bottom: 14px;
+ margin-bottom: 14px;
+ border-bottom: 1px dashed var(--line-mid);
+}
+.aa__fonts-col-mark {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 22px;
+ color: var(--crimson);
+}
+.aa__fonts-col-mark--ok {
+ color: var(--accent);
+}
+
+.aa__fonts-list {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+}
+
+.aa__fonts-row {
+ position: relative;
+ display: grid;
+ grid-template-columns: 1fr 1.2fr auto;
+ gap: 16px;
+ align-items: center;
+ padding: 8px 4px;
+ animation: aaFontIn 540ms cubic-bezier(.2,.8,.2,1) backwards;
+}
+@keyframes aaFontIn {
+ from { opacity: 0; transform: translateX(-12px); }
+ to { opacity: 1; transform: none; }
+}
+.aa__fonts-row-name {
+ font-size: 28px;
+ color: var(--fg);
+}
+.aa__fonts-row-sample {
+ font-size: 24px;
+ color: var(--fg-soft);
+}
+.aa__fonts-row-tag {
+ font-family: var(--f-mono);
+ font-size: 12px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--fg-faint);
+}
+
+.aa__fonts-row-strike {
+ position: absolute;
+ left: -4px;
+ right: -4px;
+ top: 50%;
+ height: 2px;
+ background: var(--crimson);
+ transform-origin: left;
+ transform: translateY(-50%) scaleX(0);
+ animation: aaRowStrike 520ms cubic-bezier(.6,-0.05,.2,1.2) forwards;
+ opacity: 0.85;
+}
+.aa__fonts-row--ban:nth-child(1) .aa__fonts-row-strike { animation-delay: 480ms; }
+.aa__fonts-row--ban:nth-child(2) .aa__fonts-row-strike { animation-delay: 580ms; }
+.aa__fonts-row--ban:nth-child(3) .aa__fonts-row-strike { animation-delay: 680ms; }
+.aa__fonts-row--ban:nth-child(4) .aa__fonts-row-strike { animation-delay: 780ms; }
+.aa__fonts-row--ban:nth-child(5) .aa__fonts-row-strike { animation-delay: 880ms; }
+@keyframes aaRowStrike {
+ to { transform: translateY(-50%) scaleX(1); }
+}
+
+.aa__fonts-foot {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.24em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+}
+.aa__fonts-foot span:last-child {
+ color: var(--accent);
+ font-weight: 600;
+}
+.aa__fonts-foot-dot {
+ width: 4px;
+ height: 4px;
+ border-radius: 50%;
+ background: var(--fg-faint);
+ flex: none;
+}
diff --git a/web/src/chapters/06-anti-ai/index.tsx b/web/src/chapters/06-anti-ai/index.tsx
new file mode 100644
index 0000000..ec84287
--- /dev/null
+++ b/web/src/chapters/06-anti-ai/index.tsx
@@ -0,0 +1,290 @@
+import type { ChapterContext, ChapterDef } from '../types';
+import { Reveal } from '../../shared/Reveal';
+import { SceneFade } from '../../shared/SceneFade';
+import './AntiAi.css';
+
+/**
+ * Chapter 06 · 第三部分:去 AI 味
+ *
+ * 口播主旨:
+ * - "AI 味" 来自一组反复出现的视觉套路:渐变 / Emoji / 左侧色条卡 / 烂大街字体 / data slop / 复杂硬画 SVG
+ * - 单个看都行,反复出现就成了"味道"
+ * - 字体清单:重点不是该用什么,是别用什么 (Inter / Roboto / Arial / Fraunces / system-ui)
+ * - 替代方案给了小众但品质高的字体 (Plus Jakarta Sans / Space Grotesk / Sora / Newsreader)
+ *
+ * 节奏(6 步 / step 0..5):
+ * 0 环境(eyebrow)
+ * 1 hero "什么是 AI 味?" + 原文 prompt block
+ * 2 6 张反面教材依次出现(stagger)
+ * 3 全部被红色斜杠"叉掉"
+ * 4 字体小专题:黑名单(实际字体渲染)
+ * 5 推荐替代(实际字体渲染)+ takeaway
+ */
+
+interface AntiPattern {
+ id: string;
+ cn: string;
+ en: string;
+}
+
+const PATTERNS: AntiPattern[] = [
+ { id: 'gradient', cn: '紫粉蓝渐变背景', en: 'PASTEL GRADIENT' },
+ { id: 'emoji', cn: 'Emoji 当图标', en: 'EMOJI ICONS' },
+ { id: 'leftbar', cn: '左侧彩色色条卡', en: 'LEFT COLOR BAR' },
+ { id: 'font', cn: '烂大街字体', en: 'STOCK FONTS' },
+ { id: 'data', cn: '堆砌假数据', en: 'DATA SLOP' },
+ { id: 'svg', cn: '复杂硬画 SVG', en: 'OVER-DRAWN SVG' },
+];
+
+const BANNED_FONTS = [
+ { name: 'Inter', family: 'Inter, sans-serif', sample: 'AaBbCc 123' },
+ { name: 'Roboto', family: 'Roboto, sans-serif', sample: 'AaBbCc 123' },
+ { name: 'Arial', family: 'Arial, sans-serif', sample: 'AaBbCc 123' },
+ { name: 'Fraunces', family: '"Fraunces", serif', sample: 'AaBbCc 123' },
+ { name: 'system-ui', family: 'system-ui, sans-serif', sample: 'AaBbCc 123' },
+];
+
+const BETTER_FONTS = [
+ { name: 'Plus Jakarta Sans', family: '"Plus Jakarta Sans", sans-serif', sample: 'AaBbCc 123', tag: 'sans · workhorse' },
+ { name: 'Space Grotesk', family: '"Space Grotesk", sans-serif', sample: 'AaBbCc 123', tag: 'sans · 工程感' },
+ { name: 'Sora', family: '"Sora", sans-serif', sample: 'AaBbCc 123', tag: 'sans · 现代克制' },
+ { name: 'Newsreader', family: '"Newsreader", serif', sample: 'AaBbCc 123', tag: 'serif · 编辑感' },
+];
+
+/** 单个反面教材小卡片 —— 视觉示意 + 名称 + (后期) 红斜线 */
+function BadCard({ p, slashed }: { p: AntiPattern; slashed: boolean }) {
+ return (
+
+
+ {p.id === 'gradient' && (
+
+ )}
+ {p.id === 'emoji' && (
+
+ 🚀 🎯 💡 ⭐ 🔥
+
+ )}
+ {p.id === 'leftbar' && (
+
+ )}
+ {p.id === 'font' && (
+
+ Welcome to Our Platform
+ Get Started in Seconds
+
+ )}
+ {p.id === 'data' && (
+
+
★ 4.9
+
↗ +42%
+
◷ 12k
+
⚡ 99.9%
+
+ )}
+ {p.id === 'svg' && (
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ {/* 红色斜杠覆盖层 */}
+
+
+
+
+
+
+ {p.en}
+ {p.cn}
+
+
+ );
+}
+
+function AntiAi({ localStep }: ChapterContext) {
+ const at = (n: number) => localStep >= n;
+
+ const sceneHero = localStep <= 1;
+ const sceneGrid = localStep === 2 || localStep === 3;
+ const sceneFonts = localStep >= 4;
+
+ const slashed = localStep >= 3;
+ const showAlt = localStep >= 5;
+
+ return (
+
+ {/* ════════ Scene HERO(step 0..1)════════ */}
+
+
+ {at(1) && (
+ <>
+
+ 什么是 AI 味 ?
+
+
+
+ 单看每一条都不算错 ——
+ 反复堆在一起,就成了一种味道。
+
+
+
+
+ [
+ SYSTEM PROMPT
+ ·
+ L04
+ ]
+
+
+ >
+
+ Avoid web design tropes and conventions unless you are
+ making a web page.
+
+
+
+ >
+ )}
+
+
+
+ {/* ════════ Scene GRID(step 2..3)════════ */}
+
+
+
+ 举几个 AI 反复用的"老套路" —— 一看就知道
+
+
+
+ {PATTERNS.map((p, i) => (
+
+
+
+ ))}
+
+
+ {at(3) && (
+
+ ×
+ Claude Design 把这些雷区一条条列出来 ,逼着 AI 走新路。
+
+ )}
+
+
+
+ {/* ════════ Scene FONTS(step 4..5)════════ */}
+
+
+
+ 小专题
+
+ 字体推荐 —— 重点是 别用什么
+
+
+
+
+ {/* 黑名单 */}
+
+
+ ×
+ BLACKLIST · 别用
+
+
+ {BANNED_FONTS.map((f, i) => (
+
+
+ {f.name}
+
+
+ {f.sample}
+
+
+
+ ))}
+
+
+
+ {/* 推荐替代 */}
+ {showAlt && (
+
+
+ ✓
+ ALTERNATIVES · 替代方案
+
+
+ {BETTER_FONTS.map((f, i) => (
+
+
+ {f.name}
+
+
+ {f.sample}
+
+
{f.tag}
+
+ ))}
+
+
+ )}
+
+
+ {showAlt && (
+
+ 小众
+
+ 免费
+
+ 品质高
+
+ 立刻去掉 AI 味
+
+ )}
+
+
+
+ );
+}
+
+const def: ChapterDef = {
+ id: 'anti-ai',
+ title: '第三部分 · 去 AI 味',
+ eyebrow: '06',
+ steps: 6,
+ theme: 'light',
+ Component: AntiAi,
+};
+
+export default def;
diff --git a/web/src/chapters/07-oklch/Oklch.css b/web/src/chapters/07-oklch/Oklch.css
new file mode 100644
index 0000000..96970c9
--- /dev/null
+++ b/web/src/chapters/07-oklch/Oklch.css
@@ -0,0 +1,539 @@
+/* =========================================================
+ Chapter 07 · 第四部分 · oklch 配色
+ light 主题 · 顺序对齐口播稿:
+ 原文 prompt → 三层策略 → 大字提问 → 双色相条对比 → 收尾
+ ========================================================= */
+
+.ok {
+ position: absolute;
+ inset: 0;
+ background: var(--bg);
+ color: var(--fg);
+ font-family: var(--f-sans);
+ overflow: hidden;
+}
+
+/* —— 通用 prompt 源标签 —— */
+.ok__src-bracket { color: var(--accent); font-weight: 600; }
+.ok__src-label { color: var(--fg); font-weight: 600; letter-spacing: 0.22em; }
+.ok__src-sep { color: var(--fg-faint); }
+.ok__src-line { color: var(--accent); font-weight: 600; }
+.ok__src-mute { color: var(--fg-mute); }
+
+/* ===================== Scene SOURCE (step 0) ===================== */
+
+.ok__src-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 140px 140px 120px;
+ gap: 36px;
+ text-align: center;
+}
+
+.ok__src-eyebrow {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.22em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+}
+
+.ok__src-block {
+ width: 100%;
+ max-width: 1400px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-left: 3px solid var(--accent);
+ border-radius: 2px;
+ padding: 36px 44px 40px;
+ text-align: left;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.ok__src-line-row {
+ display: grid;
+ grid-template-columns: 56px 1fr;
+ align-items: baseline;
+ gap: 24px;
+ font-family: var(--f-mono);
+ font-size: 32px;
+ line-height: 1.45;
+ color: var(--fg);
+ opacity: 0;
+ transform: translateY(10px);
+ animation: okSrcLineIn 720ms cubic-bezier(.2,.8,.2,1) forwards;
+}
+.ok__src-line-row--1 { animation-delay: 380ms; }
+.ok__src-line-row--2 { animation-delay: 980ms; }
+.ok__src-line-row--3 { animation-delay: 1580ms; }
+@keyframes okSrcLineIn {
+ to { opacity: 1; transform: none; }
+}
+
+.ok__src-num {
+ font-family: var(--f-mono);
+ font-size: 18px;
+ letter-spacing: 0.08em;
+ color: var(--fg-faint);
+ text-align: right;
+}
+
+.ok__src-text em.ok__src-h {
+ font-style: normal;
+ color: var(--accent);
+ background: linear-gradient(180deg, transparent 65%, oklch(0.860 0.060 60 / 0.7) 65%);
+ padding: 0 4px;
+ font-weight: 600;
+}
+.ok__src-h--1 { animation: okSrcHi 700ms 700ms cubic-bezier(.2,.8,.2,1) backwards; }
+.ok__src-h--2 { animation: okSrcHi 700ms 1300ms cubic-bezier(.2,.8,.2,1) backwards; }
+.ok__src-h--3 { animation: okSrcHi 700ms 1900ms cubic-bezier(.2,.8,.2,1) backwards; }
+@keyframes okSrcHi {
+ from {
+ background: linear-gradient(180deg, transparent 65%, oklch(0.860 0.060 60 / 0) 65%);
+ color: var(--fg);
+ }
+ to {
+ background: linear-gradient(180deg, transparent 65%, oklch(0.860 0.060 60 / 0.7) 65%);
+ color: var(--accent);
+ }
+}
+
+.ok__src-foot {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 28px;
+ color: var(--fg-mute);
+}
+.ok__src-foot em {
+ font-style: italic;
+ color: var(--accent);
+}
+
+/* ===================== Scene RULES (step 1) ===================== */
+
+.ok__rules-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 130px 140px 100px;
+ gap: 40px;
+}
+
+.ok__rules-head {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 72px;
+ line-height: 1.1;
+ margin: 0;
+ text-align: center;
+}
+.ok__rules-head em {
+ font-style: italic;
+ color: var(--accent);
+}
+
+.ok__rules {
+ width: 100%;
+ max-width: 1400px;
+ display: flex;
+ flex-direction: column;
+ gap: 18px;
+}
+
+.ok__rule {
+ display: grid;
+ grid-template-columns: 90px 1fr auto;
+ align-items: center;
+ gap: 28px;
+ padding: 22px 32px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-left: 4px solid var(--line-mid);
+ border-radius: 3px;
+ transition: border-color 360ms;
+}
+.ok__rule--good { border-left-color: var(--accent); }
+.ok__rule--ok { border-left-color: var(--accent); }
+.ok__rule--bad { border-left-color: var(--crimson); opacity: 0.94; }
+
+.ok__rule-num {
+ font-family: var(--f-mono);
+ font-size: 56px;
+ letter-spacing: -0.02em;
+ color: var(--fg-faint);
+ font-weight: 500;
+}
+
+.ok__rule-body {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+.ok__rule-title {
+ font-family: var(--f-serif);
+ font-size: 36px;
+ color: var(--fg);
+ line-height: 1.2;
+}
+.ok__rule-title em {
+ font-style: italic;
+ color: var(--accent);
+}
+.ok__rule-desc {
+ font-family: var(--f-sans);
+ font-size: 19px;
+ line-height: 1.5;
+ color: var(--fg-mute);
+}
+.ok__rule-swatches {
+ display: flex;
+ gap: 6px;
+ margin-top: 4px;
+}
+.ok__rule-swatches span {
+ width: 32px;
+ height: 32px;
+ border-radius: 2px;
+ box-shadow: inset 0 0 0 1px oklch(0 0 0 / 0.08);
+ animation: okSwatchIn 520ms cubic-bezier(.2,.8,.2,1) backwards;
+}
+.ok__rule-swatches span:nth-child(2) { animation-delay: 60ms; }
+.ok__rule-swatches span:nth-child(3) { animation-delay: 120ms; }
+.ok__rule-swatches span:nth-child(4) { animation-delay: 180ms; }
+.ok__rule-swatches span:nth-child(5) { animation-delay: 240ms; }
+.ok__rule-swatches span:nth-child(6) { animation-delay: 300ms; }
+@keyframes okSwatchIn {
+ from { opacity: 0; transform: translateY(14px) scale(0.92); }
+ to { opacity: 1; transform: none; }
+}
+
+.ok__rule-mark {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 56px;
+ line-height: 1;
+ width: 56px;
+ text-align: center;
+}
+.ok__rule-mark--good { color: var(--accent); }
+.ok__rule-mark--bad { color: var(--crimson); }
+
+/* ===================== Scene PIVOT (step 2) ===================== */
+
+.ok__pivot {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ gap: 24px;
+ padding: 140px;
+}
+
+.ok__pivot-q {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 168px;
+ line-height: 1.05;
+ margin: 0;
+ color: var(--fg);
+}
+.ok__pivot-em {
+ font-family: var(--f-mono);
+ font-style: italic;
+ color: var(--accent);
+ font-size: 0.78em;
+ letter-spacing: -0.01em;
+ margin: 0 0.06em;
+ position: relative;
+}
+.ok__pivot-em::after {
+ content: '';
+ position: absolute;
+ left: 6%;
+ right: 6%;
+ bottom: 6px;
+ height: 8px;
+ background: var(--accent);
+ opacity: 0.22;
+ transform-origin: left;
+ animation: okPivotUnderline 1100ms 380ms cubic-bezier(.2,.8,.2,1) backwards;
+}
+@keyframes okPivotUnderline {
+ from { transform: scaleX(0); opacity: 0; }
+ to { transform: scaleX(1); opacity: 0.22; }
+}
+
+.ok__pivot-sub {
+ font-family: var(--f-serif);
+ font-size: 36px;
+ line-height: 1.4;
+ color: var(--fg-soft);
+ margin: 12px 0 0;
+}
+.ok__pivot-strike {
+ position: relative;
+ color: var(--fg-mute);
+ font-family: var(--f-mono);
+ font-style: italic;
+ padding: 0 6px;
+}
+.ok__pivot-strike::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 55%;
+ height: 3px;
+ background: var(--crimson);
+ transform-origin: left;
+ transform: translateY(-50%) scaleX(0);
+ animation: okPivotStrike 520ms 980ms cubic-bezier(.6,-0.05,.2,1.2) forwards;
+ opacity: 0.75;
+}
+@keyframes okPivotStrike {
+ to { transform: translateY(-50%) scaleX(1); }
+}
+
+.ok__pivot-issue {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 96px;
+ line-height: 1;
+ margin: 0;
+ color: var(--fg);
+ letter-spacing: -0.01em;
+}
+.ok__pivot-issue em {
+ font-style: italic;
+ color: var(--crimson);
+}
+
+/* ===================== Scene COMPARE (step 3) ===================== */
+
+.ok__cmp-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 130px 100px 100px;
+ gap: 36px;
+}
+
+.ok__cmp-cap {
+ font-family: var(--f-serif);
+ font-size: 30px;
+ font-style: italic;
+ color: var(--fg-soft);
+ text-align: center;
+}
+.ok__cmp-cap em {
+ font-style: italic;
+ color: var(--accent);
+}
+
+.ok__cmp-grid {
+ display: flex;
+ flex-direction: column;
+ gap: 64px;
+ width: 100%;
+ max-width: 1480px;
+}
+
+.ok__cmp-row {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+ padding-bottom: 88px;
+}
+
+.ok__cmp-row-head {
+ display: flex;
+ align-items: baseline;
+ gap: 18px;
+}
+.ok__cmp-row-tag {
+ font-family: var(--f-mono);
+ font-size: 18px;
+ letter-spacing: 0.24em;
+ text-transform: uppercase;
+ padding: 4px 10px;
+ border-radius: 2px;
+}
+.ok__cmp-row-tag--bad {
+ color: var(--crimson);
+ background: oklch(0.560 0.200 22 / 0.10);
+ border: 1px solid oklch(0.560 0.200 22 / 0.45);
+}
+.ok__cmp-row-tag--good {
+ color: var(--accent-deep);
+ background: oklch(0.700 0.170 42 / 0.12);
+ border: 1px solid oklch(0.700 0.170 42 / 0.45);
+}
+.ok__cmp-row-formula {
+ font-family: var(--f-mono);
+ font-size: 18px;
+ color: var(--fg-mute);
+ letter-spacing: 0.04em;
+}
+
+.ok__cmp-strip {
+ display: grid;
+ grid-template-columns: repeat(12, 1fr);
+ gap: 8px;
+ height: 110px;
+}
+
+.ok__cmp-swatch {
+ position: relative;
+ border-radius: 2px;
+ box-shadow: inset 0 0 0 1px oklch(0 0 0 / 0.06);
+ animation: okSwatchIn 620ms cubic-bezier(.2,.8,.2,1) backwards;
+}
+/* HSL 行的"黄色"格子单独脉冲一下 */
+.ok__cmp-swatch.is-spot {
+ animation: okSwatchIn 620ms cubic-bezier(.2,.8,.2,1) backwards,
+ okSpotPulse 1600ms 1400ms ease-in-out infinite;
+ box-shadow:
+ inset 0 0 0 1px oklch(0 0 0 / 0.06),
+ 0 0 0 0 oklch(0.560 0.200 22 / 0.5);
+}
+@keyframes okSpotPulse {
+ 0%, 100% {
+ box-shadow:
+ inset 0 0 0 1px oklch(0 0 0 / 0.06),
+ 0 0 0 0 oklch(0.560 0.200 22 / 0.45);
+ transform: translateY(0);
+ }
+ 50% {
+ box-shadow:
+ inset 0 0 0 1px oklch(0 0 0 / 0.06),
+ 0 0 0 8px oklch(0.560 0.200 22 / 0);
+ transform: translateY(-4px);
+ }
+}
+.ok__cmp-swatch-tick {
+ position: absolute;
+ bottom: -22px;
+ left: 50%;
+ transform: translateX(-50%);
+ font-family: var(--f-mono);
+ font-size: 11px;
+ letter-spacing: 0.04em;
+ color: var(--fg-faint);
+}
+
+/* —— 感知亮度曲线 —— */
+.ok__cmp-curve {
+ position: absolute;
+ bottom: 28px;
+ left: 0;
+ right: 0;
+ width: 100%;
+ height: 60px;
+ pointer-events: none;
+ animation: okCurveIn 720ms cubic-bezier(.2,.8,.2,1) backwards;
+}
+.ok__cmp-curve--bad { animation-delay: 1100ms; }
+.ok__cmp-curve--good { animation-delay: 1280ms; }
+@keyframes okCurveIn {
+ from { opacity: 0; transform: translateY(8px); }
+ to { opacity: 1; transform: none; }
+}
+
+.ok__cmp-callout {
+ position: absolute;
+ bottom: -30px;
+ left: 0;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-family: var(--f-serif);
+ font-size: 22px;
+ font-style: italic;
+ opacity: 0;
+ animation: okCalloutIn 620ms cubic-bezier(.2,.8,.2,1) forwards;
+}
+.ok__cmp-callout--bad { color: var(--crimson); animation-delay: 1700ms; }
+.ok__cmp-callout--good { color: var(--accent-deep); animation-delay: 1900ms; }
+@keyframes okCalloutIn {
+ from { opacity: 0; transform: translateX(-8px); }
+ to { opacity: 1; transform: none; }
+}
+.ok__cmp-callout-arrow {
+ font-family: var(--f-mono);
+ font-style: normal;
+}
+
+/* ===================== Scene CLOSE (step 4) ===================== */
+
+.ok__close {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ gap: 20px;
+ padding: 140px;
+}
+
+.ok__close-eyebrow {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 36px;
+ color: var(--fg-mute);
+}
+
+.ok__close-line {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 200px;
+ line-height: 1;
+ margin: 0;
+ color: var(--fg);
+}
+.ok__close-line em {
+ font-style: italic;
+ color: var(--accent);
+}
+
+.ok__close-arrow {
+ font-family: var(--f-mono);
+ font-size: 96px;
+ color: var(--accent);
+ line-height: 1;
+ display: inline-block;
+ animation: okCloseArrow 1200ms 1100ms cubic-bezier(.6,-0.05,.2,1.2) backwards;
+}
+@keyframes okCloseArrow {
+ from { transform: translateY(40px); opacity: 0; }
+ to { transform: none; opacity: 1; }
+}
+
+.ok__close-caption {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 30px;
+ color: var(--fg-mute);
+ margin-top: 16px;
+}
diff --git a/web/src/chapters/07-oklch/index.tsx b/web/src/chapters/07-oklch/index.tsx
new file mode 100644
index 0000000..64de199
--- /dev/null
+++ b/web/src/chapters/07-oklch/index.tsx
@@ -0,0 +1,306 @@
+import type { ChapterContext, ChapterDef } from '../types';
+import { Reveal } from '../../shared/Reveal';
+import { SceneFade } from '../../shared/SceneFade';
+import './Oklch.css';
+
+/**
+ * Chapter 07 · 第四部分:oklch 配色
+ *
+ * 口播原顺序(严格对齐):
+ * 1. "他的配色策略分成了:优先用品牌色;不够就用 oklch 派生衍生色;绝对不要凭空编新色。"
+ * 2. "为什么是 oklch 呢?"
+ * 3. "传统的 HSL色彩空间有个大问题 — 感知不均匀。"
+ * "同样的亮度值,黄色看着比蓝色亮一大截。"
+ * 4. "oklch 是感知均匀的色彩空间。保持亮度和色度不变,只转色相角,出来的颜色自然就和谐。"
+ * 5. "这个细节看着小,但网页端高级感一下就上来了。"
+ *
+ * 节奏(5 步 / step 0..4):
+ * 0 原文 prompt(L41-43)三段被逐句"点亮" —— 引出策略原文
+ * 1 三层策略卡:① 品牌色 ② oklch 派生 ③ 禁止凭空造色
+ * 2 pivot 大字 "为什么是 oklch ?" + 副:"HSL 有个大问题 —— 感知不均匀"
+ * 3 HSL vs OKLCH 双色相条对比 + 感知亮度曲线 + 黄色 spotlight
+ * 4 收尾:"网页端 高级感 ↑"
+ */
+
+const HUES = [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330];
+
+/** HSL 感知亮度(粗略 sRGB BT.709 相对亮度) */
+function hslPerceived(h: number): number {
+ const s = 0.7;
+ const l = 0.6;
+ const c = (1 - Math.abs(2 * l - 1)) * s;
+ const hh = h / 60;
+ const x = c * (1 - Math.abs((hh % 2) - 1));
+ let r = 0, g = 0, b = 0;
+ if (hh < 1) { r = c; g = x; }
+ else if (hh < 2) { r = x; g = c; }
+ else if (hh < 3) { g = c; b = x; }
+ else if (hh < 4) { g = x; b = c; }
+ else if (hh < 5) { r = x; b = c; }
+ else { r = c; b = x; }
+ const m = l - c / 2;
+ r += m; g += m; b += m;
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
+}
+
+function Oklch({ localStep }: ChapterContext) {
+ const at = (n: number) => localStep >= n;
+
+ const sceneSrc = localStep <= 0;
+ const sceneRules = localStep === 1;
+ const scenePivot = localStep === 2;
+ const sceneCompare = localStep === 3;
+ const sceneClose = localStep >= 4;
+
+ return (
+
+ {/* ════════ Scene SOURCE(step 0)—— 原文先出 ════════ */}
+
+
+
+ [
+ SYSTEM PROMPT
+ ·
+ L41-43
+ /
+ Color Strategy 原文
+ ]
+
+
+
+
+ L41
+
+ Color usage: try to use colors from{' '}
+ brand / design system .
+
+
+
+ L42
+
+ If too restrictive, use{' '}
+ oklch {' '}
+ to define harmonious colors that match.
+
+
+
+ L43
+
+ Avoid inventing {' '}
+ new colors from scratch.
+
+
+
+
+
+ 三段 —— 一套分层 的配色策略
+
+
+
+
+ {/* ════════ Scene RULES(step 1)—— 三层策略 ════════ */}
+
+
+
+ 配色策略 —— 三层防线
+
+
+
+ {/* 第 1 层 */}
+
+ 01
+
+
优先品牌色
+
已有 design system → 直接复用,别"再创造"
+
+
+
+
+
+
+ ✓
+
+
+ {/* 第 2 层 */}
+
+ 02
+
+
不够用?oklch 派生
+
L / C 不变,h 旋转 —— 自动得到和谐衍生色
+
+ {[42, 90, 150, 200, 260, 320].map((h) => (
+
+ ))}
+
+
+ ✓
+
+
+ {/* 第 3 层 */}
+
+ 03
+
+
禁止凭空造色
+
"我觉得这个紫色挺好看" —— 这就是 AI 味的源头
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+ {/* ════════ Scene PIVOT(step 2)—— 大字提问 ════════ */}
+
+
+
+ 为什么是 oklch ?
+
+
+
+ HSL
+ 有个大问题 ——
+
+
+
+ 感知不均匀
+
+
+
+
+ {/* ════════ Scene COMPARE(step 3)—— 双色相条 + 曲线 ════════ */}
+
+
+
+ 同一组亮度 / 饱和度 · 12 个色相 —— 看人眼对哪种更舒服
+
+
+
+ {/* HSL 行 */}
+
+
+ HSL · 老
+ hsl(h, 70%, 60%)
+
+
+ {HUES.map((h, i) => (
+
+ {h}°
+
+ ))}
+
+
+ {
+ const x = (i / (HUES.length - 1)) * 1200;
+ const y = 78 - hslPerceived(h) * 70;
+ return `L${x.toFixed(1)} ${y.toFixed(1)}`;
+ }).join(' ') +
+ ' L1200 80 Z'
+ }
+ fill="var(--crimson)"
+ fillOpacity="0.12"
+ stroke="var(--crimson)"
+ strokeWidth="2"
+ />
+
+
+ ↑
+ 同 60% 亮度 —— 黄色看着像被打了灯
+
+
+
+ {/* OKLCH 行 */}
+
+
+ OKLCH · 新
+ oklch(0.70 0.15 h)
+
+
+ {HUES.map((h, i) => (
+
+ {h}°
+
+ ))}
+
+
+
+
+
+ →
+ L / C 不变,只转 h —— 亮度自动一致
+
+
+
+
+
+
+ {/* ════════ Scene CLOSE(step 4)════════ */}
+
+
+
+ 细节看着小 ——
+
+
+
+ 网页端高级感
+
+
+
+ ↑
+
+
+
+ 一下就上来了
+
+
+
+
+ );
+}
+
+const def: ChapterDef = {
+ id: 'oklch',
+ title: '第四部分 · oklch 配色',
+ eyebrow: '07',
+ steps: 5,
+ theme: 'light',
+ Component: Oklch,
+};
+
+export default def;
diff --git a/web/src/chapters/08-restraint/Restraint.css b/web/src/chapters/08-restraint/Restraint.css
new file mode 100644
index 0000000..8454544
--- /dev/null
+++ b/web/src/chapters/08-restraint/Restraint.css
@@ -0,0 +1,726 @@
+/* =========================================================
+ Chapter 08 · 第五部分 · 内容克制
+ light 主题 · 顺序对齐口播稿:
+ 乔布斯 1000No/1Yes 开场 → 落地页 → 塞满 → 砍 → 立原则 → 收尾
+ ========================================================= */
+
+.re {
+ position: absolute;
+ inset: 0;
+ background: var(--bg);
+ color: var(--fg);
+ font-family: var(--f-sans);
+ overflow: hidden;
+}
+
+/* —— 通用 prompt 源标签 —— */
+.re__src-bracket { color: var(--accent); font-weight: 600; }
+.re__src-label { color: var(--fg); font-weight: 600; letter-spacing: 0.22em; }
+.re__src-sep { color: var(--fg-faint); }
+.re__src-line { color: var(--accent); font-weight: 600; }
+
+/* ===================== Scene JOBS (step 0) ===================== */
+
+.re__jobs {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ gap: 24px;
+ padding: 120px;
+}
+
+.re__jobs-by {
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--fg-faint);
+ margin-bottom: 16px;
+}
+
+.re__jobs-row {
+ display: flex;
+ align-items: center;
+ gap: 64px;
+}
+
+.re__jobs-num {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 6px;
+}
+.re__jobs-num-figure {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 320px;
+ line-height: 0.9;
+ letter-spacing: -0.04em;
+}
+.re__jobs-num-label {
+ font-family: var(--f-mono);
+ font-size: 22px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+}
+.re__jobs-num--no .re__jobs-num-figure {
+ color: var(--fg-mute);
+ font-style: italic;
+}
+.re__jobs-num--yes .re__jobs-num-figure {
+ color: var(--accent);
+ font-style: italic;
+ position: relative;
+}
+.re__jobs-num--yes .re__jobs-num-figure::after {
+ content: '';
+ position: absolute;
+ left: -10%;
+ right: -10%;
+ bottom: 28px;
+ height: 14px;
+ background: var(--accent);
+ opacity: 0.18;
+ transform-origin: left;
+ animation: reJobsUnderline 1100ms 1100ms cubic-bezier(.2,.8,.2,1) backwards;
+}
+@keyframes reJobsUnderline {
+ from { transform: scaleX(0); opacity: 0; }
+ to { transform: scaleX(1); opacity: 0.18; }
+}
+
+.re__jobs-arrow {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 64px;
+ color: var(--fg-faint);
+}
+
+.re__jobs-quote {
+ margin-top: 24px;
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 36px;
+ color: var(--fg-soft);
+ letter-spacing: 0.01em;
+}
+.re__jobs-quote em {
+ color: var(--accent);
+ font-style: italic;
+ font-size: 1.1em;
+}
+
+.re__jobs-src {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-top: 4px;
+ font-family: var(--f-mono);
+ font-size: 13px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+}
+
+/* ===================== Scene PAGE (step 1..3) ===================== */
+
+.re__page-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 120px 120px 90px;
+ gap: 24px;
+}
+
+.re__page-cap {
+ display: flex;
+ align-items: baseline;
+ gap: 14px;
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.24em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+}
+.re__page-cap-tag {
+ color: var(--accent);
+ font-weight: 600;
+}
+.re__page-cap-sep {
+ color: var(--fg-faint);
+}
+.re__page-cap-text {
+ font-family: var(--f-serif);
+ font-style: italic;
+ letter-spacing: 0.02em;
+ text-transform: none;
+ font-size: 22px;
+ color: var(--fg-soft);
+}
+
+/* —— 浏览器外框 —— */
+.re__browser {
+ width: 100%;
+ max-width: 1500px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-radius: 4px;
+ overflow: hidden;
+ box-shadow: 0 24px 48px -28px oklch(0 0 0 / 0.18);
+}
+.re__browser-bar {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 10px 16px;
+ background: oklch(0.910 0.020 78);
+ border-bottom: 1px solid var(--line-mid);
+}
+.re__browser-dot {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ background: oklch(0.820 0.030 78);
+}
+.re__browser-dot:nth-child(1) { background: oklch(0.730 0.180 28); }
+.re__browser-dot:nth-child(2) { background: oklch(0.830 0.150 80); }
+.re__browser-dot:nth-child(3) { background: oklch(0.770 0.140 145); }
+.re__browser-url {
+ margin-left: 14px;
+ font-family: var(--f-mono);
+ font-size: 13px;
+ color: var(--fg-mute);
+ letter-spacing: 0.04em;
+}
+.re__browser-body {
+ background: var(--bg);
+ padding: 18px 22px;
+ height: 600px;
+ overflow: hidden;
+}
+
+/* —— 6 段 section —— */
+.re__page {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ grid-auto-rows: 1fr;
+ gap: 14px;
+ width: 100%;
+ height: 100%;
+}
+
+.re__sec {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ background: var(--bg);
+ border: 1px solid var(--line-mid);
+ border-radius: 3px;
+ padding: 14px 18px;
+ gap: 10px;
+ overflow: hidden;
+ animation: reSecIn 720ms cubic-bezier(.2,.8,.2,1) backwards;
+ transition:
+ opacity 600ms cubic-bezier(.4,0,1,1),
+ filter 600ms cubic-bezier(.4,0,1,1),
+ transform 600ms cubic-bezier(.4,0,1,1);
+}
+@keyframes reSecIn {
+ from { opacity: 0; transform: translateY(20px); }
+ to { opacity: 1; transform: none; }
+}
+
+.re__sec.is-pruned {
+ opacity: 0.32;
+ filter: grayscale(0.8);
+ transform: scale(0.985);
+}
+
+.re__sec-head {
+ display: flex;
+ align-items: baseline;
+ gap: 10px;
+ font-family: var(--f-mono);
+ font-size: 12px;
+ letter-spacing: 0.22em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+}
+.re__sec-num { color: var(--accent); font-weight: 600; }
+.re__sec-label { color: var(--fg); font-weight: 600; }
+.re__sec-dot { width: 4px; height: 4px; border-radius: 50%; background: var(--fg-faint); }
+.re__sec-cn {
+ font-family: var(--f-serif);
+ font-style: italic;
+ letter-spacing: 0.02em;
+ text-transform: none;
+ font-size: 16px;
+ color: var(--fg-soft);
+}
+
+.re__sec-body {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ align-items: stretch;
+ justify-content: center;
+}
+.re__sec-bar {
+ height: 8px;
+ background: var(--line-mid);
+ border-radius: 2px;
+}
+.re__sec-bar--w70 { width: 70%; }
+.re__sec-bar--w50 { width: 50%; }
+
+/* —— Filler 内容(视觉上"塞满")—— */
+.re__filler {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ animation: reFillerIn 520ms cubic-bezier(.2,.8,.2,1) backwards;
+}
+@keyframes reFillerIn {
+ from { opacity: 0; transform: translateY(8px); }
+ to { opacity: 1; transform: none; }
+}
+
+.re__filler--hero {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 6px;
+ padding: 4px 0;
+}
+.re__filler-h {
+ font-family: var(--f-serif);
+ font-size: 24px;
+ font-weight: 500;
+ line-height: 1.1;
+ color: var(--fg);
+}
+.re__filler-sub {
+ font-family: var(--f-sans);
+ font-size: 13px;
+ line-height: 1.4;
+ color: var(--fg-mute);
+}
+.re__filler-cta {
+ margin-top: 4px;
+ padding: 4px 10px;
+ background: var(--accent);
+ color: var(--paper);
+ font-family: var(--f-mono);
+ font-size: 11px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ border-radius: 2px;
+}
+
+.re__filler--feat {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ grid-template-rows: repeat(2, 1fr);
+ gap: 6px;
+ width: 100%;
+ height: 100%;
+}
+.re__filler-card {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ padding: 6px 8px;
+ background: var(--bg-2);
+ border: 1px solid var(--line);
+ border-radius: 2px;
+}
+.re__filler-card-icon {
+ font-size: 14px;
+ color: var(--accent);
+}
+.re__filler-card-t {
+ height: 6px;
+ width: 60%;
+ background: var(--line-mid);
+ border-radius: 1px;
+}
+.re__filler-card-l {
+ height: 4px;
+ width: 90%;
+ background: var(--line);
+ border-radius: 1px;
+}
+
+.re__filler--social {
+ flex-direction: row;
+ gap: 8px;
+}
+.re__filler-quote {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ padding: 6px 8px;
+ background: var(--bg-2);
+ border: 1px solid var(--line);
+ border-radius: 2px;
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 11px;
+ line-height: 1.35;
+ color: var(--fg-soft);
+}
+.re__filler-quote-mark {
+ font-family: var(--f-serif);
+ font-size: 18px;
+ line-height: 1;
+ color: var(--accent);
+}
+.re__filler-quote-by {
+ font-family: var(--f-mono);
+ font-size: 9px;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: var(--fg-faint);
+}
+
+.re__filler--data {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ align-items: center;
+ gap: 4px;
+ width: 100%;
+ font-family: var(--f-mono);
+ font-size: 10px;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ text-align: center;
+}
+.re__filler--data span {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 2px;
+}
+.re__filler--data b {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-weight: 400;
+ font-size: 22px;
+ color: var(--fg);
+ letter-spacing: 0;
+}
+
+.re__filler--faq {
+ flex-direction: column;
+ gap: 4px;
+ font-family: var(--f-sans);
+ font-size: 12px;
+ color: var(--fg-soft);
+}
+.re__filler--faq span {
+ padding: 4px 8px;
+ background: var(--bg-2);
+ border: 1px solid var(--line);
+ border-radius: 2px;
+}
+
+.re__filler--contact {
+ flex-direction: column;
+ gap: 4px;
+}
+.re__filler-input {
+ display: block;
+ width: 100%;
+ height: 14px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-radius: 2px;
+}
+.re__filler-btn {
+ align-self: flex-start;
+ margin-top: 2px;
+ padding: 4px 10px;
+ background: var(--accent);
+ color: var(--paper);
+ font-family: var(--f-mono);
+ font-size: 10px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ border-radius: 2px;
+}
+
+/* —— 红 × 修剪覆盖层 —— */
+.re__sec-prune {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0;
+ transition: opacity 360ms;
+}
+.re__sec.is-pruned .re__sec-prune {
+ opacity: 1;
+}
+.re__sec-prune-mark {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 96px;
+ line-height: 1;
+ color: var(--crimson);
+ transform: scale(0);
+ animation: rePruneMarkIn 520ms cubic-bezier(.6,-0.05,.2,1.2) forwards;
+}
+.re__sec-prune-line {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 50%;
+ height: 3px;
+ background: var(--crimson);
+ transform: translateY(-50%) scaleX(0);
+ transform-origin: left;
+ animation: rePruneLine 520ms cubic-bezier(.6,-0.05,.2,1.2) forwards;
+ opacity: 0.55;
+}
+@keyframes rePruneMarkIn {
+ from { transform: scale(0) rotate(-12deg); opacity: 0; }
+ to { transform: scale(1) rotate(-12deg); opacity: 0.92; }
+}
+@keyframes rePruneLine {
+ to { transform: translateY(-50%) scaleX(1); }
+}
+
+/* 错开 6 段被砍的节奏 */
+.re__sec:nth-child(1).is-pruned .re__sec-prune-mark,
+.re__sec:nth-child(1).is-pruned .re__sec-prune-line { animation-delay: 80ms; }
+.re__sec:nth-child(2).is-pruned .re__sec-prune-mark,
+.re__sec:nth-child(2).is-pruned .re__sec-prune-line { animation-delay: 200ms; }
+.re__sec:nth-child(3).is-pruned .re__sec-prune-mark,
+.re__sec:nth-child(3).is-pruned .re__sec-prune-line { animation-delay: 320ms; }
+.re__sec:nth-child(4).is-pruned .re__sec-prune-mark,
+.re__sec:nth-child(4).is-pruned .re__sec-prune-line { animation-delay: 440ms; }
+.re__sec:nth-child(5).is-pruned .re__sec-prune-mark,
+.re__sec:nth-child(5).is-pruned .re__sec-prune-line { animation-delay: 560ms; }
+.re__sec:nth-child(6).is-pruned .re__sec-prune-mark,
+.re__sec:nth-child(6).is-pruned .re__sec-prune-line { animation-delay: 680ms; }
+
+.re__page-verdict {
+ font-family: var(--f-serif);
+ font-size: 32px;
+ font-style: italic;
+ color: var(--fg-soft);
+ text-align: center;
+ display: flex;
+ align-items: center;
+ gap: 14px;
+}
+.re__page-verdict em {
+ font-style: italic;
+ color: var(--accent);
+}
+.re__page-verdict-mark {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 36px;
+ color: var(--crimson);
+}
+
+/* ===================== Scene PRINCIPLE (step 4) ===================== */
+
+.re__princ {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 130px 140px 100px;
+ gap: 32px;
+ text-align: center;
+}
+
+.re__princ-head {
+ font-family: var(--f-mono);
+ font-size: 16px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--accent);
+ margin: 0;
+}
+
+.re__princ-line {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 88px;
+ line-height: 1.15;
+ margin: 0;
+ color: var(--fg);
+ max-width: 1500px;
+}
+.re__princ-line em {
+ font-style: italic;
+ color: var(--accent);
+ position: relative;
+}
+.re__princ-line em::after {
+ content: '';
+ position: absolute;
+ left: 4%;
+ right: 4%;
+ bottom: 8px;
+ height: 8px;
+ background: var(--accent);
+ opacity: 0.22;
+ transform-origin: left;
+ animation: rePrincUnderline 1100ms 800ms cubic-bezier(.2,.8,.2,1) backwards;
+}
+@keyframes rePrincUnderline {
+ from { transform: scaleX(0); opacity: 0; }
+ to { transform: scaleX(1); opacity: 0.22; }
+}
+
+.re__princ-rules {
+ display: flex;
+ flex-direction: column;
+ gap: 18px;
+ margin-top: 16px;
+ align-items: center;
+}
+.re__princ-rule {
+ display: flex;
+ align-items: baseline;
+ gap: 18px;
+ padding: 14px 32px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-radius: 999px;
+ font-family: var(--f-serif);
+ font-size: 32px;
+ color: var(--fg);
+ box-shadow: 0 12px 24px -16px oklch(0 0 0 / 0.18);
+}
+.re__princ-q {
+ color: var(--fg-soft);
+ font-style: italic;
+}
+.re__princ-arrow {
+ font-family: var(--f-mono);
+ font-style: normal;
+ font-size: 24px;
+ color: var(--fg-faint);
+}
+.re__princ-a {
+ color: var(--fg);
+ font-style: italic;
+ font-weight: 500;
+}
+.re__princ-a em {
+ font-style: italic;
+ color: var(--accent);
+}
+
+.re__princ-excerpt {
+ width: 100%;
+ max-width: 1100px;
+ margin-top: 12px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-left: 3px solid var(--accent);
+ border-radius: 2px;
+ padding: 18px 28px 22px;
+ text-align: left;
+}
+.re__princ-excerpt-head {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-family: var(--f-mono);
+ font-size: 13px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ margin-bottom: 10px;
+}
+.re__princ-excerpt-body {
+ display: flex;
+ align-items: flex-start;
+ gap: 14px;
+}
+.re__princ-excerpt-gt {
+ font-family: var(--f-mono);
+ font-size: 28px;
+ color: var(--accent);
+}
+.re__princ-excerpt-text {
+ font-family: var(--f-mono);
+ font-size: 20px;
+ line-height: 1.55;
+ color: var(--fg);
+}
+.re__princ-excerpt-text em {
+ font-style: normal;
+ color: var(--accent);
+ background: linear-gradient(180deg, transparent 70%, oklch(0.860 0.060 60 / 0.6) 70%);
+ padding: 0 2px;
+}
+
+/* ===================== Scene CLOSE (step 5) ===================== */
+
+.re__close {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 20px;
+ padding: 140px;
+ text-align: center;
+}
+
+.re__close-line {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 110px;
+ line-height: 1.15;
+ margin: 0;
+ color: var(--fg);
+ max-width: 1500px;
+}
+.re__close-line--alt {
+ color: var(--fg);
+}
+.re__close-line em {
+ font-style: italic;
+ color: var(--accent);
+}
+
+.re__close-foot {
+ margin-top: 48px;
+ display: flex;
+ align-items: center;
+ gap: 24px;
+ font-family: var(--f-mono);
+ font-size: 22px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+}
+.re__close-foot-eq {
+ color: var(--accent);
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 36px;
+ letter-spacing: 0;
+}
diff --git a/web/src/chapters/08-restraint/index.tsx b/web/src/chapters/08-restraint/index.tsx
new file mode 100644
index 0000000..aa66ac8
--- /dev/null
+++ b/web/src/chapters/08-restraint/index.tsx
@@ -0,0 +1,303 @@
+import type { ChapterContext, ChapterDef } from '../types';
+import { Reveal } from '../../shared/Reveal';
+import { SceneFade } from '../../shared/SceneFade';
+import './Restraint.css';
+
+/**
+ * Chapter 08 · 第五部分:内容克制
+ *
+ * 口播原顺序(严格对齐):
+ * 1. "提示词里引了乔布斯一句经典的话:'一千个 No 换一个 Yes'。"
+ * 2. "AI 做设计有个毛病——恨不得把空间塞满。"
+ * "Hero、特性、评价、数据、FAQ、联系方式…一股脑全上了,但每块都很平庸。"
+ * 3. "Claude Design 的态度很明确:每个元素得证明自己为什么该在那。"
+ * 4. "想加东西?先问用户。页面看着空?那是排版的问题,用留白来解决,别靠塞东西。"
+ * 5. "一个大胆的留白,比十个凑数的板块有表现力得多。"
+ *
+ * 节奏(6 步 / step 0..5):
+ * 0 hero "1000 No · 1 Yes" 大字开场 + Steve Jobs + 原文 prompt(L75)
+ * 1 转场到 AI 落地页:6 段 section 线框依次出现
+ * 2 filler 内容塞满("恨不得把空间塞满")
+ * 3 红 × 一段段砍掉("每个元素得证明自己")
+ * 4 留白后中央立原则: "想加?先问。空?用留白" + earn-its-place 提示词
+ * 5 收尾: "一个大胆的留白 > 十个凑数的板块"
+ */
+
+interface Section {
+ id: string;
+ label: string;
+ cn: string;
+}
+
+const SECTIONS: Section[] = [
+ { id: 'hero', label: 'HERO', cn: '主视觉' },
+ { id: 'feat', label: 'FEATURES', cn: '6 大特性' },
+ { id: 'social', label: 'TESTIMONIALS', cn: '客户评价' },
+ { id: 'data', label: 'DATA', cn: '数据展示' },
+ { id: 'faq', label: 'FAQ', cn: '常见问题' },
+ { id: 'contact', label: 'CONTACT', cn: '联系方式' },
+];
+
+function SectionBlock({
+ s,
+ index,
+ filled,
+ pruned,
+}: {
+ s: Section;
+ index: number;
+ filled: boolean;
+ pruned: boolean;
+}) {
+ return (
+
+
+ {String(index + 1).padStart(2, '0')}
+ {s.label}
+
+ {s.cn}
+
+
+ {!filled && (
+ <>
+
+
+ >
+ )}
+ {filled && s.id === 'hero' && (
+
+ Build the Future. Today.
+ The all-in-one platform for the modern team — fast, simple, powerful.
+ Get Started →
+
+ )}
+ {filled && s.id === 'feat' && (
+
+ {[0, 1, 2, 3, 4, 5].map((i) => (
+
+ ★
+
+
+
+ ))}
+
+ )}
+ {filled && s.id === 'social' && (
+
+ {[0, 1, 2].map((i) => (
+
+ "
+ Best product I've ever used. 10/10.
+ — User #{i + 1}
+
+ ))}
+
+ )}
+ {filled && s.id === 'data' && (
+
+ 10k+ users
+ 99.9% uptime
+ 4.9★ rating
+ +42% growth
+
+ )}
+ {filled && s.id === 'faq' && (
+
+ + How does it work?
+ + Is there a free trial?
+ + Can I cancel anytime?
+
+ )}
+ {filled && s.id === 'contact' && (
+
+
+
+ Send Message
+
+ )}
+
+
+
+ ×
+
+
+
+ );
+}
+
+function Restraint({ localStep }: ChapterContext) {
+ const at = (n: number) => localStep >= n;
+ void at;
+
+ const sceneJobs = localStep <= 0;
+ const scenePage = localStep >= 1 && localStep <= 3;
+ const scenePrinc = localStep === 4;
+ const sceneClose = localStep >= 5;
+
+ const filled = localStep >= 2;
+ const pruned = localStep >= 3;
+
+ return (
+
+ {/* ════════ Scene JOBS(step 0)—— 乔布斯名言开场 ════════ */}
+
+
+
+ —— STEVE JOBS · 提示词原文引用
+
+
+
+
+ 1000
+ No
+
+
+
+ 换
+
+
+
+ 1
+ Yes
+
+
+
+
+ "
+ One thousand no's for every yes.
+ "
+
+
+
+ [
+ SYSTEM PROMPT
+ ·
+ L77
+ ]
+
+
+
+
+ {/* ════════ Scene PAGE(step 1..3)—— AI 把 6 段塞满 → 砍 ════════ */}
+
+
+
+ A TYPICAL "AI" LANDING PAGE
+ /
+
+ {!filled && 'AI 一上来就把 6 段全摆好了 ——'}
+ {filled && !pruned && '然后把每一格都塞满 ——'}
+ {pruned && '一一拷问:你为什么在这?'}
+
+
+
+
+
+
+
+
+ claude-design.demo / fake-landing-page
+
+
+
+ {SECTIONS.map((s, i) => (
+
+ ))}
+
+
+
+
+ {pruned && (
+
+ ×
+ 每一块都"还行",加在一起 —— 还是平庸
+
+ )}
+
+
+
+ {/* ════════ Scene PRINCIPLE(step 4)—— 立原则 ════════ */}
+
+
+
+ Claude Design 的态度 ——
+
+
+
+ 每个元素,得证明自己 为什么该在那。
+
+
+
+
+ 想加东西?
+ →
+ 先问用户
+
+
+ 页面看着空?
+ →
+ 用留白 解决,不是塞内容
+
+
+
+
+
+ [
+ SYSTEM PROMPT
+ ·
+ L75
+ ]
+
+
+ >
+
+ Never pad a design with{' '}
+ placeholder text, dummy sections {' '}
+ just to fill space. Every element should earn its place.
+
+
+
+
+
+
+ {/* ════════ Scene CLOSE(step 5)—— 大胆留白 ════════ */}
+
+
+
+ 一个大胆的留白 ,
+
+
+
+ 比十个凑数的板块更有表现力 。
+
+
+
+ 留白
+ =
+ 设计
+
+
+
+
+ );
+}
+
+const def: ChapterDef = {
+ id: 'restraint',
+ title: '第五部分 · 内容克制',
+ eyebrow: '08',
+ steps: 6,
+ theme: 'light',
+ Component: Restraint,
+};
+
+export default def;
diff --git a/web/src/chapters/09-verification/Verification.css b/web/src/chapters/09-verification/Verification.css
new file mode 100644
index 0000000..5120227
--- /dev/null
+++ b/web/src/chapters/09-verification/Verification.css
@@ -0,0 +1,636 @@
+/* =========================================================
+ Chapter 09 · 第六部分 · 验证闭环
+ ink 主题 · prompt 原文 → 主 Agent 自循环 → fork → 检查 → 收尾
+ ========================================================= */
+
+.vf {
+ position: absolute;
+ inset: 0;
+ background: var(--bg);
+ color: var(--fg);
+ font-family: var(--f-sans);
+ overflow: hidden;
+}
+
+.vf__src-bracket { color: var(--accent); font-weight: 600; }
+.vf__src-label { color: var(--fg); font-weight: 600; letter-spacing: 0.22em; }
+.vf__src-sep { color: var(--fg-faint); }
+.vf__src-line { color: var(--accent); font-weight: 600; }
+
+/* ===================== Scene INTRO (step 0) ===================== */
+
+.vf__intro {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 140px;
+ gap: 32px;
+ text-align: center;
+}
+
+.vf__intro-tag {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.22em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+}
+
+.vf__intro-code {
+ display: flex;
+ align-items: flex-start;
+ gap: 28px;
+ padding: 32px 44px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-left: 3px solid var(--accent);
+ border-radius: 2px;
+ font-family: var(--f-mono);
+ font-size: 30px;
+ line-height: 1.55;
+ text-align: left;
+ max-width: 1400px;
+ color: var(--fg-soft);
+ box-shadow: 0 24px 48px -28px oklch(0 0 0 / 0.45);
+}
+.vf__intro-code-num {
+ font-size: 36px;
+ color: var(--accent);
+ font-weight: 500;
+ line-height: 1.4;
+}
+.vf__intro-code-text em {
+ font-style: normal;
+ color: var(--fg);
+}
+.vf__intro-fn {
+ font-style: normal;
+ color: var(--accent) !important;
+ background: oklch(0.700 0.170 42 / 0.14);
+ padding: 0 8px;
+ border-radius: 2px;
+ font-weight: 500;
+}
+
+.vf__intro-title {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 96px;
+ line-height: 1.1;
+ margin: 0;
+ color: var(--fg);
+ max-width: 1500px;
+}
+.vf__intro-title em {
+ font-style: italic;
+ color: var(--accent);
+}
+
+/* ===================== Scene AGENT (step 1) ===================== */
+
+.vf__agent-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 130px 140px 100px;
+ gap: 40px;
+}
+
+.vf__agent-cap {
+ font-family: var(--f-serif);
+ font-size: 32px;
+ font-style: italic;
+ color: var(--fg-soft);
+}
+.vf__agent-cap em {
+ color: var(--accent);
+}
+
+.vf__board {
+ position: relative;
+ width: 100%;
+ max-width: 1400px;
+ height: 460px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.vf__board--fork {
+ justify-content: space-between;
+ padding: 0 60px;
+}
+
+/* —— Agent 节点 —— */
+.vf__node {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 6px;
+ padding: 28px 40px 24px;
+ background: var(--bg-2);
+ border: 2px solid var(--accent);
+ border-radius: 4px;
+ min-width: 320px;
+ box-shadow: 0 24px 48px -22px oklch(0 0 0 / 0.5);
+ animation: vfNodeIn 720ms cubic-bezier(.2,.8,.2,1) backwards;
+}
+@keyframes vfNodeIn {
+ from { opacity: 0; transform: translateY(20px) scale(0.92); }
+ to { opacity: 1; transform: none; }
+}
+.vf__node--small {
+ min-width: 280px;
+ padding: 20px 30px 18px;
+ border-color: var(--line-strong);
+ box-shadow: 0 18px 32px -22px oklch(0 0 0 / 0.4);
+}
+.vf__node--verifier {
+ border: 2px dashed var(--accent);
+ animation: vfNodeIn 720ms 600ms cubic-bezier(.6,-0.05,.2,1.2) backwards;
+ box-shadow: 0 24px 48px -22px oklch(0.700 0.170 42 / 0.4);
+}
+.vf__node-tag {
+ font-family: var(--f-mono);
+ font-size: 12px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--accent);
+ font-weight: 600;
+}
+.vf__node-tag--alt {
+ color: var(--accent);
+}
+.vf__node-title {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 36px;
+ line-height: 1.05;
+ color: var(--fg);
+ margin: 4px 0;
+}
+.vf__node-meta {
+ font-family: var(--f-mono);
+ font-size: 13px;
+ color: var(--fg-mute);
+ letter-spacing: 0.04em;
+}
+.vf__node-fresh {
+ color: oklch(0.770 0.140 145);
+ font-weight: 600;
+ letter-spacing: 0.06em;
+}
+
+/* —— 自循环 SVG —— */
+.vf__loop {
+ position: absolute;
+ inset: -60px;
+ width: calc(100% + 120px);
+ height: calc(100% + 120px);
+ pointer-events: none;
+}
+.vf__loop-ring {
+ transform-origin: 100px 100px;
+ animation: vfLoopSpin 5s linear infinite;
+}
+@keyframes vfLoopSpin {
+ from { transform: rotate(0); }
+ to { transform: rotate(360deg); }
+}
+
+/* —— 自言自语气泡 —— */
+.vf__bubble {
+ position: absolute;
+ top: -68px;
+ left: 50%;
+ transform: translateX(-50%);
+ padding: 10px 18px;
+ background: var(--bg);
+ border: 1px solid var(--line-mid);
+ border-radius: 999px;
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 22px;
+ color: var(--fg-soft);
+ white-space: nowrap;
+ animation: vfBubbleIn 620ms 800ms cubic-bezier(.2,.8,.2,1) backwards;
+}
+.vf__bubble::after {
+ content: '';
+ position: absolute;
+ bottom: -7px;
+ left: 50%;
+ transform: translateX(-50%) rotate(45deg);
+ width: 10px;
+ height: 10px;
+ background: var(--bg);
+ border-right: 1px solid var(--line-mid);
+ border-bottom: 1px solid var(--line-mid);
+}
+@keyframes vfBubbleIn {
+ from { opacity: 0; transform: translate(-50%, -8px); }
+ to { opacity: 1; transform: translate(-50%, 0); }
+}
+
+.vf__agent-verdict {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ font-family: var(--f-serif);
+ font-size: 30px;
+ font-style: italic;
+ color: var(--fg-soft);
+}
+.vf__agent-verdict em {
+ font-style: italic;
+ color: var(--crimson);
+}
+.vf__agent-verdict-x {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 36px;
+ color: var(--crimson);
+}
+
+/* ===================== Scene FORK (step 2) ===================== */
+
+.vf__fork-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 130px 140px 100px;
+ gap: 32px;
+}
+
+.vf__fork-cap {
+ display: flex;
+ align-items: baseline;
+ gap: 18px;
+ font-family: var(--f-serif);
+ font-size: 30px;
+ font-style: italic;
+ color: var(--fg-soft);
+}
+.vf__fork-cap em {
+ color: var(--accent);
+}
+.vf__fork-cap-fn {
+ font-family: var(--f-mono);
+ font-style: normal;
+ font-size: 26px;
+ color: var(--accent);
+ background: oklch(0.700 0.170 42 / 0.14);
+ padding: 4px 10px;
+ border-radius: 2px;
+}
+.vf__fork-cap-arrow {
+ font-family: var(--f-mono);
+ font-style: normal;
+ color: var(--fg-faint);
+}
+
+.vf__fork-link {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ width: 600px;
+ height: 220px;
+ transform: translate(-50%, -50%);
+ pointer-events: none;
+}
+.vf__fork-link-branch {
+ stroke-dashoffset: 200;
+ animation: vfForkDraw 1100ms 380ms cubic-bezier(.2,.8,.2,1) forwards;
+}
+@keyframes vfForkDraw {
+ to { stroke-dashoffset: 0; }
+}
+
+/* —— 子 Agent 模拟 iframe —— */
+.vf__node-iframe {
+ margin-top: 10px;
+ width: 240px;
+ background: var(--bg);
+ border: 1px solid var(--line-mid);
+ border-radius: 2px;
+ overflow: hidden;
+}
+.vf__node-iframe-bar {
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ padding: 6px 10px;
+ background: oklch(0.355 0.014 60);
+ border-bottom: 1px solid var(--line-mid);
+}
+.vf__node-iframe-dot {
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: var(--fg-faint);
+}
+.vf__node-iframe-url {
+ margin-left: 8px;
+ font-family: var(--f-mono);
+ font-size: 10px;
+ color: var(--fg-mute);
+ letter-spacing: 0.04em;
+}
+.vf__node-iframe-body {
+ display: block;
+ position: relative;
+ height: 80px;
+ background:
+ repeating-linear-gradient(
+ 45deg,
+ oklch(0.965 0.018 78 / 0.04) 0 6px,
+ transparent 6px 12px);
+ overflow: hidden;
+}
+.vf__node-iframe-flash {
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(
+ 90deg,
+ transparent 0%,
+ oklch(0.700 0.170 42 / 0.45) 50%,
+ transparent 100%);
+ transform: translateX(-100%);
+ animation: vfFlash 1800ms 1100ms ease-in-out infinite;
+}
+@keyframes vfFlash {
+ 0% { transform: translateX(-100%); }
+ 60% { transform: translateX(100%); }
+ 100% { transform: translateX(100%); }
+}
+
+.vf__fork-verdict {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 26px;
+ color: var(--fg-mute);
+}
+.vf__fork-verdict em {
+ font-style: italic;
+ color: var(--accent);
+}
+
+/* ===================== Scene CHECK (step 3) ===================== */
+
+.vf__check-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 130px 140px 100px;
+ gap: 28px;
+}
+
+.vf__check-cap {
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--accent);
+}
+
+.vf__check-panel {
+ width: 100%;
+ max-width: 1280px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-radius: 4px;
+ overflow: hidden;
+ box-shadow: 0 32px 60px -32px oklch(0 0 0 / 0.5);
+}
+.vf__check-panel-bar {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 14px 22px;
+ background: oklch(0.355 0.014 60);
+ border-bottom: 1px solid var(--line-mid);
+}
+.vf__check-panel-dot {
+ width: 9px;
+ height: 9px;
+ border-radius: 50%;
+ background: oklch(0.500 0.020 60);
+}
+.vf__check-panel-name {
+ margin-left: 10px;
+ font-family: var(--f-mono);
+ font-size: 14px;
+ color: var(--fg-mute);
+ letter-spacing: 0.06em;
+}
+.vf__check-panel-status {
+ margin-left: auto;
+ font-family: var(--f-mono);
+ font-size: 12px;
+ letter-spacing: 0.32em;
+ color: var(--accent);
+ animation: vfStatusBlink 1.4s ease-in-out infinite;
+}
+@keyframes vfStatusBlink {
+ 0%, 100% { opacity: 0.4; }
+ 50% { opacity: 1; }
+}
+
+.vf__check-list {
+ display: flex;
+ flex-direction: column;
+}
+
+.vf__check-row {
+ display: grid;
+ grid-template-columns: 56px 240px auto 1fr 60px;
+ align-items: center;
+ gap: 18px;
+ padding: 18px 28px;
+ border-bottom: 1px dashed var(--line);
+ font-family: var(--f-mono);
+ font-size: 22px;
+ color: var(--fg);
+ opacity: 0;
+ animation: vfCheckIn 520ms cubic-bezier(.2,.8,.2,1) forwards;
+}
+.vf__check-row:last-child { border-bottom: none; }
+@keyframes vfCheckIn {
+ from { opacity: 0; transform: translateX(-12px); }
+ to { opacity: 1; transform: none; }
+}
+
+.vf__check-num {
+ color: var(--fg-faint);
+ letter-spacing: 0.08em;
+}
+.vf__check-label {
+ color: var(--fg);
+ letter-spacing: 0.18em;
+ font-weight: 600;
+}
+.vf__check-cn {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 20px;
+ color: var(--fg-mute);
+ letter-spacing: 0;
+}
+.vf__check-bar {
+ position: relative;
+ height: 4px;
+ background: var(--line);
+ border-radius: 2px;
+ overflow: hidden;
+}
+.vf__check-bar::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 0;
+ height: 100%;
+ width: 0;
+ background: var(--accent);
+ animation: vfCheckBar 700ms cubic-bezier(.2,.8,.2,1) forwards;
+}
+.vf__check-row:nth-child(1) .vf__check-bar::after { animation-delay: 320ms; }
+.vf__check-row:nth-child(2) .vf__check-bar::after { animation-delay: 540ms; }
+.vf__check-row:nth-child(3) .vf__check-bar::after { animation-delay: 760ms; }
+.vf__check-row:nth-child(4) .vf__check-bar::after { animation-delay: 980ms; }
+@keyframes vfCheckBar {
+ to { width: 100%; }
+}
+
+.vf__check-mark {
+ position: relative;
+ width: 30px;
+ height: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.vf__check-mark-spin {
+ position: absolute;
+ inset: 0;
+ border: 2px solid var(--line-mid);
+ border-top-color: var(--accent);
+ border-radius: 50%;
+ animation: vfSpin 700ms linear infinite;
+}
+.vf__check-mark-tick {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 28px;
+ color: oklch(0.770 0.140 145);
+ opacity: 0;
+ animation: vfTickIn 320ms cubic-bezier(.6,-0.05,.2,1.2) forwards;
+}
+@keyframes vfSpin {
+ to { transform: rotate(360deg); }
+}
+@keyframes vfTickIn {
+ from { opacity: 0; transform: scale(0.4); }
+ to { opacity: 1; transform: scale(1); }
+}
+
+/* 错峰:spinner 跑完才显示对勾 */
+.vf__check-row:nth-child(1) .vf__check-mark-spin { animation: vfSpin 700ms linear 1; }
+.vf__check-row:nth-child(2) .vf__check-mark-spin { animation: vfSpin 700ms 220ms linear 1; }
+.vf__check-row:nth-child(3) .vf__check-mark-spin { animation: vfSpin 700ms 440ms linear 1; }
+.vf__check-row:nth-child(4) .vf__check-mark-spin { animation: vfSpin 700ms 660ms linear 1; }
+.vf__check-row:nth-child(1) .vf__check-mark-spin { animation-fill-mode: forwards; }
+.vf__check-row .vf__check-mark-spin {
+ animation-fill-mode: forwards;
+}
+.vf__check-row:nth-child(1) .vf__check-mark-tick { animation-delay: 1020ms; }
+.vf__check-row:nth-child(2) .vf__check-mark-tick { animation-delay: 1240ms; }
+.vf__check-row:nth-child(3) .vf__check-mark-tick { animation-delay: 1460ms; }
+.vf__check-row:nth-child(4) .vf__check-mark-tick { animation-delay: 1680ms; }
+
+/* spinner 在对勾出现时淡出 */
+.vf__check-row:nth-child(1) .vf__check-mark-spin { animation: vfSpin 700ms linear 1, vfSpinOut 240ms 1020ms forwards; }
+.vf__check-row:nth-child(2) .vf__check-mark-spin { animation: vfSpin 700ms 220ms linear 1, vfSpinOut 240ms 1240ms forwards; }
+.vf__check-row:nth-child(3) .vf__check-mark-spin { animation: vfSpin 700ms 440ms linear 1, vfSpinOut 240ms 1460ms forwards; }
+.vf__check-row:nth-child(4) .vf__check-mark-spin { animation: vfSpin 700ms 660ms linear 1, vfSpinOut 240ms 1680ms forwards; }
+@keyframes vfSpinOut {
+ to { opacity: 0; transform: scale(0.6); }
+}
+
+.vf__check-foot {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ padding: 16px 28px;
+ background: oklch(0.355 0.014 60);
+ border-top: 1px solid var(--line-mid);
+ font-family: var(--f-mono);
+ font-size: 14px;
+ color: var(--fg-mute);
+ letter-spacing: 0.04em;
+}
+.vf__check-foot-tag {
+ padding: 4px 10px;
+ border-radius: 2px;
+ letter-spacing: 0.32em;
+ font-weight: 600;
+}
+.vf__check-foot-tag--pass {
+ color: oklch(0.770 0.140 145);
+ background: oklch(0.770 0.140 145 / 0.14);
+ border: 1px solid oklch(0.770 0.140 145 / 0.45);
+}
+
+/* ===================== Scene CLOSE (step 4) ===================== */
+
+.vf__close {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ gap: 12px;
+ padding: 140px;
+}
+.vf__close-l1,
+.vf__close-l2 {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 128px;
+ line-height: 1.05;
+ margin: 0;
+ color: var(--fg);
+}
+.vf__close-l1 em,
+.vf__close-l2 em {
+ font-style: italic;
+ color: var(--accent);
+}
+.vf__close-cap {
+ margin-top: 32px;
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 30px;
+ color: var(--fg-mute);
+}
diff --git a/web/src/chapters/09-verification/index.tsx b/web/src/chapters/09-verification/index.tsx
new file mode 100644
index 0000000..c8bb31e
--- /dev/null
+++ b/web/src/chapters/09-verification/index.tsx
@@ -0,0 +1,277 @@
+import type { ChapterContext, ChapterDef } from '../types';
+import { Reveal } from '../../shared/Reveal';
+import { SceneFade } from '../../shared/SceneFade';
+import './Verification.css';
+
+/**
+ * Chapter 09 · 第六部分:验证闭环
+ *
+ * 口播原顺序(严格对齐):
+ * 1. "第六个点,验证。"
+ * 2. "在开发完成后,它会 Fork 出一个独立的子 Agent,然后对当前完成的网页做全面检查。"
+ * 3. "同一个 Agent 检查自己的输出的时候,天然会倾向于觉得没问题。"
+ * 4. "换一个全新的上下文,这种'自我感觉良好'就容易被打破。"
+ *
+ * 原文(L22):
+ * "Finish: call `done` to surface the file to the user and check it loads cleanly.
+ * If errors, fix and `done` again. If clean, call `fork_verifier_agent`."
+ *
+ * 节奏(5 步 / step 0..4):
+ * 0 hero · 提示词原文 (L22) + 大字 "验证 —— 不信任自己的输出"
+ * 1 主 Agent 节点出现,自带 self-loop("我做的没问题吧?")→ 标 "确认偏误"
+ * 2 fork → 子 Agent 节点弹出,标 "fresh context"
+ * 3 子 Agent 跑 4 项检查:SCREENSHOT / CONSOLE / LAYOUT / JS PROBE 错峰打勾
+ * 4 收尾大字:"换个新脑子 / 才能跳出 自我感觉良好"
+ */
+
+interface Check {
+ id: string;
+ label: string;
+ cn: string;
+}
+
+const CHECKS: Check[] = [
+ { id: 'shot', label: 'SCREENSHOT', cn: '截图比对' },
+ { id: 'cons', label: 'CONSOLE LOGS', cn: '控制台错误' },
+ { id: 'lay', label: 'LAYOUT', cn: '布局偏移' },
+ { id: 'js', label: 'JS PROBE', cn: 'DOM 探测' },
+];
+
+function Verification({ localStep }: ChapterContext) {
+ const at = (n: number) => localStep >= n;
+
+ const sceneIntro = localStep <= 0;
+ const sceneAgent = localStep === 1;
+ const sceneFork = localStep === 2;
+ const sceneCheck = localStep === 3;
+ const sceneClose = localStep >= 4;
+
+ return (
+
+ {/* ════════ Scene INTRO(step 0)════════ */}
+
+
+
+ [
+ SYSTEM PROMPT
+ ·
+ L22
+ ]
+
+
+
+ 5.
+
+ Finish: call done .
+ If errors, fix and done again.
+ If clean, call fork_verifier_agent() .
+
+
+
+
+ 验证 —— 不信任自己 的输出
+
+
+
+
+ {/* ════════ Scene AGENT(step 1)—— 主 Agent + 自循环 ════════ */}
+
+
+
+ 一个 Agent —— 检查自己 会发生什么?
+
+
+
+ {/* 主 Agent 节点 */}
+
+
MAIN AGENT
+
opus 4.7
+
role · designer · L01
+
+ {/* 自循环箭头(SVG) */}
+
+
+
+
+
+
+
+
+
+ {/* 自言自语气泡 */}
+
+ "我做的应该没问题吧?"
+
+
+
+
+
+ ×
+ 天然会倾向于觉得没问题 —— 这就是确认偏误
+
+
+
+
+ {/* ════════ Scene FORK(step 2)—— fork 出子 Agent ════════ */}
+
+
+
+ fork_verifier_agent()
+ →
+ 另开一个 新脑子
+
+
+
+ {/* 主 Agent(左侧) */}
+
+
MAIN AGENT
+
opus 4.7
+
已完成 · 等待审稿
+
+
+ {/* fork 连线 */}
+
+
+
+
+
+
+ {/* 主线 */}
+
+ {/* 分叉线(弹性曲线) */}
+
+ {/* 节点圆点 */}
+
+
+
+ {/* 子 Agent(右上) */}
+
+
VERIFIER AGENT
+
subagent · 0x9c
+
+ ● fresh context
+
+
+
+
+
+
+ about:blank
+
+
+
+
+
+
+
+
+
+ 独立 iframe · 独立上下文 · 跑全面检查后再回报
+
+
+
+
+ {/* ════════ Scene CHECK(step 3)—— 子 Agent 跑检查清单 ════════ */}
+
+
+
+ 子 Agent · 全面检查
+
+
+
+
+
+
+
+ verifier · subagent · 0x9c
+ RUNNING…
+
+
+ {CHECKS.map((c, i) => (
+
+ 0{i + 1}
+ {c.label}
+ / {c.cn}
+
+
+
+ ✓
+
+
+ ))}
+
+
+ PASS
+ silent on pass · 不打断主 Agent
+
+
+
+
+
+ {/* ════════ Scene CLOSE(step 4)════════ */}
+
+
+
+ 换个新脑子
+
+
+ 才能跳出 自我感觉良好
+
+
+ AI 也需要审稿人
+
+
+
+
+ );
+}
+
+const def: ChapterDef = {
+ id: 'verification',
+ title: '第六部分 · 验证闭环',
+ eyebrow: '09',
+ steps: 5,
+ theme: 'ink',
+ Component: Verification,
+};
+
+export default def;
diff --git a/web/src/chapters/10-to-skill/ToSkill.css b/web/src/chapters/10-to-skill/ToSkill.css
new file mode 100644
index 0000000..0697d9b
--- /dev/null
+++ b/web/src/chapters/10-to-skill/ToSkill.css
@@ -0,0 +1,631 @@
+/* =========================================================
+ Chapter 10 · 过渡到 Skill
+ light 主题 · 回顾 → 但 → 三号被封 → 好消息 → Skill 卡 → 收尾
+ ========================================================= */
+
+.ts {
+ position: absolute;
+ inset: 0;
+ background: var(--bg);
+ color: var(--fg);
+ font-family: var(--f-sans);
+ overflow: hidden;
+}
+
+.ts__src-bracket { color: var(--accent); font-weight: 600; }
+.ts__src-label { color: var(--fg); font-weight: 600; letter-spacing: 0.22em; }
+
+/* ===================== Scene RECAP (step 0) ===================== */
+
+.ts__recap {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 130px 140px 100px;
+ gap: 56px;
+ text-align: center;
+}
+
+.ts__recap-title {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 112px;
+ line-height: 1.1;
+ margin: 0;
+ color: var(--fg);
+}
+.ts__recap-title em {
+ font-style: italic;
+ color: var(--accent);
+}
+
+.ts__recap-list {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 14px 28px;
+ width: 100%;
+ max-width: 1100px;
+}
+
+.ts__recap-item {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ padding: 14px 22px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-left: 3px solid var(--accent);
+ border-radius: 2px;
+ font-family: var(--f-serif);
+ font-size: 26px;
+ color: var(--fg);
+ opacity: 0;
+ animation: tsRecapIn 620ms cubic-bezier(.2,.8,.2,1) forwards;
+}
+@keyframes tsRecapIn {
+ from { opacity: 0; transform: translateY(12px); }
+ to { opacity: 1; transform: none; }
+}
+.ts__recap-num {
+ font-family: var(--f-mono);
+ font-size: 18px;
+ letter-spacing: 0.12em;
+ color: var(--fg-faint);
+}
+.ts__recap-name {
+ flex: 1;
+ font-style: italic;
+ text-align: left;
+}
+.ts__recap-tick {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 28px;
+ color: var(--accent);
+}
+
+/* ===================== Scene PROBLEM (step 1) ===================== */
+
+.ts__prob {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ gap: 24px;
+ padding: 140px;
+}
+
+.ts__prob-but {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 200px;
+ line-height: 0.95;
+ margin: 0;
+ color: var(--fg);
+ font-style: italic;
+}
+
+.ts__prob-line {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 88px;
+ line-height: 1.15;
+ margin: 0;
+ color: var(--fg);
+}
+.ts__prob-line em {
+ font-style: italic;
+ color: var(--crimson);
+ position: relative;
+}
+.ts__prob-line em::after {
+ content: '';
+ position: absolute;
+ left: 4%;
+ right: 4%;
+ bottom: 8px;
+ height: 8px;
+ background: var(--crimson);
+ opacity: 0.18;
+ transform-origin: left;
+ animation: tsProbUnderline 1100ms 1100ms cubic-bezier(.2,.8,.2,1) backwards;
+}
+@keyframes tsProbUnderline {
+ from { transform: scaleX(0); opacity: 0; }
+ to { transform: scaleX(1); opacity: 0.18; }
+}
+
+.ts__prob-meta {
+ margin-top: 32px;
+ display: flex;
+ align-items: center;
+ gap: 18px;
+ font-family: var(--f-mono);
+ font-size: 18px;
+ letter-spacing: 0.24em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+}
+.ts__prob-meta-dot {
+ width: 4px;
+ height: 4px;
+ border-radius: 50%;
+ background: var(--fg-faint);
+}
+
+/* ===================== Scene BANNED (step 2) ===================== */
+
+.ts__banned-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 120px 100px 90px;
+ gap: 40px;
+}
+
+.ts__banned-cap {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 32px;
+ color: var(--fg-soft);
+ text-align: center;
+}
+.ts__banned-cap em {
+ color: var(--crimson);
+ font-style: italic;
+ font-weight: 500;
+}
+
+.ts__banned-row {
+ display: flex;
+ gap: 36px;
+ align-items: flex-start;
+ justify-content: center;
+}
+
+.ts__card {
+ position: relative;
+ width: 380px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-radius: 4px;
+ overflow: hidden;
+ box-shadow: 0 24px 48px -28px oklch(0 0 0 / 0.25);
+ opacity: 0;
+ transform: translateY(0);
+ animation: tsCardFall 1100ms cubic-bezier(.6,-0.05,.2,1.2) forwards;
+}
+.ts__card--n1 { transform-origin: 70% 100%; }
+.ts__card--n2 { transform-origin: 50% 100%; margin-top: 32px; }
+.ts__card--n3 { transform-origin: 30% 100%; margin-top: 64px; }
+@keyframes tsCardFall {
+ 0% { opacity: 0; transform: translateY(-50px) rotate(-2deg); }
+ 20% { opacity: 1; transform: translateY(0) rotate(0); }
+ 60% { opacity: 1; transform: translateY(0) rotate(0); }
+ 100% { opacity: 0.7; transform: translateY(28px) rotate(var(--fall-rot, 4deg)); }
+}
+.ts__card--n1 { --fall-rot: -6deg; }
+.ts__card--n2 { --fall-rot: 3deg; }
+.ts__card--n3 { --fall-rot: -2deg; }
+
+.ts__card-bar {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 8px 14px;
+ background: oklch(0.910 0.020 78);
+ border-bottom: 1px solid var(--line-mid);
+}
+.ts__card-bar-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: oklch(0.820 0.030 78);
+}
+.ts__card-bar-dot:nth-child(1) { background: oklch(0.730 0.180 28); }
+.ts__card-bar-dot:nth-child(2) { background: oklch(0.830 0.150 80); }
+.ts__card-bar-dot:nth-child(3) { background: oklch(0.770 0.140 145); }
+.ts__card-bar-name {
+ margin-left: 10px;
+ font-family: var(--f-mono);
+ font-size: 12px;
+ color: var(--fg-mute);
+ letter-spacing: 0.04em;
+}
+
+.ts__card-body {
+ display: flex;
+ align-items: center;
+ gap: 18px;
+ padding: 22px 24px 26px;
+}
+.ts__card-avatar {
+ width: 60px;
+ height: 60px;
+ border-radius: 999px;
+ background: oklch(0.700 0.170 42 / 0.18);
+ color: var(--accent);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 32px;
+}
+.ts__card-info {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ min-width: 0;
+}
+.ts__card-name {
+ font-family: var(--f-serif);
+ font-size: 24px;
+ color: var(--fg);
+}
+.ts__card-mail {
+ font-family: var(--f-mono);
+ font-size: 13px;
+ color: var(--fg-mute);
+ letter-spacing: 0.04em;
+}
+.ts__card-plan {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-family: var(--f-mono);
+ font-size: 12px;
+ color: var(--fg-faint);
+ letter-spacing: 0.06em;
+ margin-top: 4px;
+}
+.ts__card-plan-tag {
+ padding: 2px 8px;
+ background: var(--accent);
+ color: var(--paper);
+ letter-spacing: 0.18em;
+ border-radius: 2px;
+ font-weight: 600;
+}
+
+/* —— BANNED 印章 —— */
+.ts__stamp {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 4px;
+ padding: 16px 32px 12px;
+ border: 4px solid var(--crimson);
+ border-radius: 4px;
+ color: var(--crimson);
+ font-family: var(--f-mono);
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ background: oklch(0.560 0.200 22 / 0.06);
+ transform: translate(-50%, -50%) rotate(-12deg) scale(0);
+ opacity: 0;
+ animation: tsStampIn 480ms cubic-bezier(.6,-0.05,.2,1.2) forwards;
+}
+.ts__card--n1 .ts__stamp { animation-delay: 600ms; }
+.ts__card--n2 .ts__stamp { animation-delay: 920ms; }
+.ts__card--n3 .ts__stamp { animation-delay: 1240ms; }
+@keyframes tsStampIn {
+ from { opacity: 0; transform: translate(-50%, -50%) rotate(-12deg) scale(1.6); }
+ to { opacity: 1; transform: translate(-50%, -50%) rotate(-12deg) scale(1); }
+}
+.ts__stamp-text {
+ font-family: var(--f-mono);
+ font-size: 36px;
+ font-weight: 700;
+ letter-spacing: 0.16em;
+}
+.ts__stamp-sub {
+ font-family: var(--f-mono);
+ font-size: 11px;
+ letter-spacing: 0.18em;
+ opacity: 0.85;
+}
+
+.ts__banned-foot {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 28px;
+ color: var(--fg-mute);
+}
+.ts__banned-foot em {
+ color: var(--crimson);
+ font-style: italic;
+ font-weight: 500;
+}
+.ts__banned-foot-x {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 32px;
+ color: var(--crimson);
+}
+
+/* ===================== Scene PIVOT (step 3) ===================== */
+
+.ts__pivot {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ gap: 16px;
+ padding: 140px;
+}
+
+.ts__pivot-eyebrow {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 36px;
+ color: var(--fg-mute);
+}
+
+.ts__pivot-good {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 168px;
+ line-height: 1;
+ margin: 0;
+ color: var(--fg);
+}
+.ts__pivot-good em {
+ font-style: italic;
+ color: var(--accent);
+ position: relative;
+}
+.ts__pivot-good em::after {
+ content: '';
+ position: absolute;
+ left: 4%;
+ right: 4%;
+ bottom: 12px;
+ height: 12px;
+ background: var(--accent);
+ opacity: 0.22;
+ transform-origin: left;
+ animation: tsPivotUnderline 1100ms 600ms cubic-bezier(.2,.8,.2,1) backwards;
+}
+@keyframes tsPivotUnderline {
+ from { transform: scaleX(0); opacity: 0; }
+ to { transform: scaleX(1); opacity: 0.22; }
+}
+
+.ts__pivot-line {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 80px;
+ line-height: 1.15;
+ margin: 0;
+ color: var(--fg);
+}
+.ts__pivot-line em {
+ font-style: italic;
+ color: var(--accent);
+}
+
+.ts__pivot-cap {
+ margin-top: 16px;
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 28px;
+ color: var(--fg-mute);
+}
+.ts__pivot-cap em {
+ color: var(--accent);
+}
+
+/* ===================== Scene SKILL (step 4) ===================== */
+
+.ts__skill-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 110px 140px 90px;
+ gap: 28px;
+ text-align: center;
+}
+
+.ts__skill-eyebrow {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+}
+
+.ts__skill-card {
+ width: 100%;
+ max-width: 1280px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-radius: 4px;
+ overflow: hidden;
+ box-shadow: 0 32px 60px -28px oklch(0 0 0 / 0.25);
+ text-align: left;
+}
+.ts__skill-bar {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 10px 18px;
+ background: oklch(0.910 0.020 78);
+ border-bottom: 1px solid var(--line-mid);
+}
+.ts__skill-bar-dot {
+ width: 9px;
+ height: 9px;
+ border-radius: 50%;
+ background: oklch(0.820 0.030 78);
+}
+.ts__skill-bar-dot:nth-child(1) { background: oklch(0.730 0.180 28); }
+.ts__skill-bar-dot:nth-child(2) { background: oklch(0.830 0.150 80); }
+.ts__skill-bar-dot:nth-child(3) { background: oklch(0.770 0.140 145); }
+.ts__skill-bar-path {
+ margin-left: 14px;
+ font-family: var(--f-mono);
+ font-size: 14px;
+ color: var(--fg-mute);
+ letter-spacing: 0.04em;
+}
+
+.ts__skill-body {
+ padding: 40px 56px 44px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+.ts__skill-tag {
+ font-family: var(--f-mono);
+ font-size: 13px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--accent);
+ font-weight: 600;
+}
+.ts__skill-name {
+ font-family: var(--f-mono);
+ font-weight: 500;
+ font-size: 88px;
+ line-height: 1;
+ margin: 4px 0 8px;
+ color: var(--fg);
+ letter-spacing: -0.02em;
+}
+.ts__skill-desc {
+ font-family: var(--f-serif);
+ font-size: 32px;
+ line-height: 1.4;
+ color: var(--fg-soft);
+ margin: 0;
+}
+.ts__skill-desc em {
+ font-style: italic;
+ color: var(--accent);
+}
+.ts__skill-meta {
+ margin-top: 14px;
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.24em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+}
+.ts__skill-meta-dot {
+ width: 4px;
+ height: 4px;
+ border-radius: 50%;
+ background: var(--fg-faint);
+}
+
+.ts__tools-cap {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 24px;
+ color: var(--fg-mute);
+}
+
+.ts__tools-row {
+ display: flex;
+ gap: 24px;
+ align-items: stretch;
+}
+
+.ts__tool {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 6px;
+ padding: 18px 36px 22px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-top: 3px solid var(--accent);
+ border-radius: 3px;
+ min-width: 200px;
+ opacity: 0;
+ animation: tsToolIn 720ms cubic-bezier(.2,.8,.2,1) forwards;
+}
+@keyframes tsToolIn {
+ from { opacity: 0; transform: translateY(16px); }
+ to { opacity: 1; transform: none; }
+}
+.ts__tool-glyph {
+ font-family: var(--f-mono);
+ font-size: 12px;
+ letter-spacing: 0.32em;
+ color: var(--accent);
+}
+.ts__tool-name {
+ font-family: var(--f-serif);
+ font-size: 32px;
+ color: var(--fg);
+}
+.ts__tool-mono {
+ font-family: var(--f-mono);
+ font-size: 12px;
+ color: var(--fg-mute);
+ letter-spacing: 0.06em;
+}
+
+/* ===================== Scene CLOSE (step 5) ===================== */
+
+.ts__close {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ gap: 16px;
+ padding: 140px;
+}
+.ts__close-l1 {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 110px;
+ line-height: 1;
+ margin: 0;
+ color: var(--fg-soft);
+ font-style: italic;
+}
+.ts__close-l2 {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 168px;
+ line-height: 1;
+ margin: 0;
+ color: var(--fg);
+}
+.ts__close-l2 em {
+ font-style: italic;
+ color: var(--accent);
+}
diff --git a/web/src/chapters/10-to-skill/index.tsx b/web/src/chapters/10-to-skill/index.tsx
new file mode 100644
index 0000000..742642d
--- /dev/null
+++ b/web/src/chapters/10-to-skill/index.tsx
@@ -0,0 +1,248 @@
+import type { ChapterContext, ChapterDef } from '../types';
+import { Reveal } from '../../shared/Reveal';
+import { SceneFade } from '../../shared/SceneFade';
+import './ToSkill.css';
+
+/**
+ * Chapter 10 · 过渡到 Skill
+ *
+ * 口播原顺序(严格对齐):
+ * 1. "以上就是 Claude Design 提示词里最核心的东西。"
+ * 2. "但有个现实问题 — Anthropic 的产品在国内用起来都非常的难。"
+ * "我自己被封了三个号,彻底放弃官方渠道了。"
+ * "而且没有 API,没法接到自己的工作流里。"
+ * 3. "不过好消息是:它的提示词已经泄出来了。
+ * Claude Design 厉害的另一半,主要就这套提示词里。"
+ * 4. "所以我做了个 Skill,叫 web-design-engineer,
+ * 把这套提示词的精华提炼了出来。"
+ * 5. "Claude Code、Cursor、Codex 都能直接用,人人都能成为顶级网页设计师。"
+ *
+ * 节奏(6 步 / step 0..5):
+ * 0 回顾:"以上 —— 提示词原文最核心的东西" + 五块小钩
+ * 1 转折大字 "但..." + "Anthropic 在国内 —— 难"
+ * 2 三张账号卡片依次倒下 + BANNED 红章 + "没 API"小注
+ * 3 Pivot:"好消息 —— 提示词 已经泄出来了"
+ * 4 Skill 卡:web-design-engineer 终端式呈现 + 三个适用工具
+ * 5 收尾大字 "人人都能成为 顶级网页设计师"
+ */
+
+const RECAP_POINTS = [
+ '角色定位',
+ '工作流',
+ '去 AI 味',
+ 'oklch 配色',
+ '内容克制',
+ '验证闭环',
+];
+
+const TOOLS = [
+ { id: 'cc', name: 'Claude Code', mono: 'claude.code' },
+ { id: 'cu', name: 'Cursor', mono: 'cursor.sh' },
+ { id: 'cx', name: 'Codex', mono: 'codex.cli' },
+];
+
+function ToSkill({ localStep }: ChapterContext) {
+ const at = (n: number) => localStep >= n;
+ void at;
+
+ const sceneRecap = localStep <= 0;
+ const sceneProb = localStep === 1;
+ const sceneBanned = localStep === 2;
+ const scenePivot = localStep === 3;
+ const sceneSkill = localStep === 4;
+ const sceneClose = localStep >= 5;
+
+ return (
+
+ {/* ════════ Scene RECAP(step 0)════════ */}
+
+
+
+ 以上 —— 提示词里
+ 最核心 的东西
+
+
+
+ {RECAP_POINTS.map((p, i) => (
+
+ 0{i + 1}
+ {p}
+ ✓
+
+ ))}
+
+
+
+
+ {/* ════════ Scene PROBLEM(step 1)—— "但..." ════════ */}
+
+
+
+ 但 ——
+
+
+
+ Anthropic 的产品 ——
+ 在国内 真的难用
+
+
+
+ 无官方支付
+
+ 无 API
+
+ 账号易被封
+
+
+
+
+ {/* ════════ Scene BANNED(step 2)—— 三张账号倒下 ════════ */}
+
+
+
+ "我自己被封了 三个号 ,彻底放弃官方渠道了。"
+
+
+
+ {[1, 2, 3].map((n, i) => (
+
+
+
+
+
+ claude.ai / account
+
+
+
{['F', 'G', 'H'][i]}
+
+
花园老师 #{n}
+
flower-{i + 1}@anthropic.user
+
+ Pro
+ activated · 2026.0{i + 1}
+
+
+
+ {/* BANNED 印章 */}
+
+ BANNED
+ violation · #{n}
+
+
+ ))}
+
+
+
+ ×
+ 而且 —— 没有 API ,接不进自己的工作流
+
+
+
+
+ {/* ════════ Scene PIVOT(step 3)—— "好消息" ════════ */}
+
+
+
+ 不过 ——
+
+
+
+ 好消息 是:
+
+
+
+ 提示词 —— 已经泄出来了
+
+
+
+ "Claude Design 厉害的另一半,主要就这套提示词 里。"
+
+
+
+
+ {/* ════════ Scene SKILL(step 4)—— web-design-engineer ════════ */}
+
+
+
+ [
+ SKILL · OPEN SOURCE
+ ]
+
+
+
+
+
+
+
+ .claude / skills / web-design-engineer / SKILL.md
+
+
+
SKILL.md
+
web-design-engineer
+
+ 把 Claude Design 提示词的精华,
+ 提炼成一个可复用 的 Skill
+
+
+ ≈ 400 行
+
+ 开源
+
+ 免费
+
+
+
+
+
+ 适用于 ——
+
+
+
+ {TOOLS.map((t, i) => (
+
+
[ {t.id} ]
+
{t.name}
+
{t.mono}
+
+ ))}
+
+
+
+
+ {/* ════════ Scene CLOSE(step 5)════════ */}
+
+
+
+ 人人都能成为
+
+
+ 顶级网页设计师
+
+
+
+
+ );
+}
+
+const def: ChapterDef = {
+ id: 'to-skill',
+ title: '过渡 · Skill 是怎么来的',
+ eyebrow: '10',
+ steps: 6,
+ theme: 'light',
+ Component: ToSkill,
+};
+
+export default def;
diff --git a/web/src/chapters/11-skill-changes/SkillChanges.css b/web/src/chapters/11-skill-changes/SkillChanges.css
new file mode 100644
index 0000000..2b97bd0
--- /dev/null
+++ b/web/src/chapters/11-skill-changes/SkillChanges.css
@@ -0,0 +1,1141 @@
+/* =========================================================
+ Chapter 11 · Skill 的关键改动(重做)
+ light 主题 · 文件树 → 假滚动 → 剥离 → 4 改动 + 原文 → 收尾
+ ========================================================= */
+
+.sk {
+ position: absolute;
+ inset: 0;
+ background: var(--bg);
+ color: var(--fg);
+ font-family: var(--f-sans);
+ overflow: hidden;
+}
+
+.sk__src-bracket { color: var(--accent); font-weight: 600; }
+.sk__src-label { color: var(--fg); font-weight: 600; letter-spacing: 0.22em; }
+.sk__src-sep { color: var(--fg-faint); }
+.sk__src-line { color: var(--accent); font-weight: 600; }
+
+/* ========================================================================
+ Scene TREE (step 0) —— Skill 文件目录全貌
+ ======================================================================== */
+
+.sk__tree-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 110px 140px 90px;
+ gap: 22px;
+ text-align: center;
+}
+
+.sk__tree-eyebrow {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+}
+
+.sk__tree-title {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 96px;
+ line-height: 1.1;
+ margin: 0;
+ color: var(--fg);
+}
+
+.sk__tree-card {
+ width: 100%;
+ max-width: 1200px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-radius: 4px;
+ overflow: hidden;
+ box-shadow: 0 32px 60px -28px oklch(0 0 0 / 0.22);
+ text-align: left;
+}
+.sk__tree-bar {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 10px 18px;
+ background: oklch(0.910 0.020 78);
+ border-bottom: 1px solid var(--line-mid);
+}
+.sk__tree-bar-dot {
+ width: 9px;
+ height: 9px;
+ border-radius: 50%;
+ background: oklch(0.820 0.030 78);
+}
+.sk__tree-bar-dot:nth-child(1) { background: oklch(0.730 0.180 28); }
+.sk__tree-bar-dot:nth-child(2) { background: oklch(0.830 0.150 80); }
+.sk__tree-bar-dot:nth-child(3) { background: oklch(0.770 0.140 145); }
+.sk__tree-bar-path {
+ margin-left: 14px;
+ font-family: var(--f-mono);
+ font-size: 14px;
+ color: var(--fg-mute);
+ letter-spacing: 0.04em;
+}
+
+.sk__tree-body {
+ padding: 28px 38px 30px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.sk__tree-row {
+ display: grid;
+ grid-template-columns: auto auto 1fr auto auto;
+ align-items: baseline;
+ gap: 12px;
+ padding: 8px 14px;
+ border-radius: 2px;
+ font-family: var(--f-mono);
+ font-size: 22px;
+ color: var(--fg);
+ opacity: 0;
+ transform: translateX(-8px);
+ animation: skTreeRowIn 540ms cubic-bezier(.2,.8,.2,1) forwards;
+}
+@keyframes skTreeRowIn {
+ to { opacity: 1; transform: none; }
+}
+.sk__tree-row--main {
+ background: oklch(0.700 0.170 42 / 0.08);
+ border-left: 3px solid var(--accent);
+ padding-left: 12px;
+}
+.sk__tree-row--child {
+ margin-left: 56px;
+ background: oklch(0.700 0.170 42 / 0.05);
+ border-left: 2px solid oklch(0.700 0.170 42 / 0.45);
+ padding-left: 12px;
+}
+.sk__tree-row--sub {
+ margin-top: 4px;
+}
+
+.sk__tree-glyph {
+ color: var(--accent);
+ font-size: 16px;
+ width: 16px;
+}
+.sk__tree-pipe {
+ color: var(--fg-faint);
+ font-size: 16px;
+ letter-spacing: 0;
+}
+.sk__tree-pipe--child {
+ color: oklch(0.700 0.170 42 / 0.55);
+}
+.sk__tree-name {
+ color: var(--fg);
+ font-weight: 500;
+}
+.sk__tree-name--md {
+ color: var(--accent);
+ font-weight: 500;
+}
+.sk__tree-slash {
+ color: var(--fg-faint);
+}
+.sk__tree-tag {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 18px;
+ color: var(--fg-mute);
+ letter-spacing: 0;
+}
+.sk__tree-meta {
+ font-family: var(--f-mono);
+ font-size: 14px;
+ color: var(--fg-faint);
+ letter-spacing: 0.06em;
+ text-align: right;
+}
+.sk__tree-meta--em {
+ color: var(--accent);
+ font-weight: 600;
+}
+
+.sk__tree-foot {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 26px;
+ color: var(--fg-soft);
+}
+.sk__tree-foot em {
+ color: var(--accent);
+ font-style: italic;
+}
+
+/* ========================================================================
+ Scene SCROLL (step 1) —— SKILL.md 假滚动展示
+ ======================================================================== */
+
+.sk__scroll-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 100px 140px 80px;
+ gap: 18px;
+ text-align: center;
+}
+
+.sk__scroll-eyebrow {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+}
+
+.sk__scroll-cap {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 30px;
+ color: var(--fg-soft);
+}
+.sk__scroll-cap em {
+ color: var(--accent);
+ font-style: italic;
+}
+
+.sk__scroll-card {
+ width: 100%;
+ max-width: 1280px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-radius: 4px;
+ overflow: hidden;
+ box-shadow: 0 32px 60px -28px oklch(0 0 0 / 0.25);
+ text-align: left;
+}
+
+.sk__scroll-bar {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 10px 18px;
+ background: oklch(0.910 0.020 78);
+ border-bottom: 1px solid var(--line-mid);
+}
+.sk__scroll-bar-dot {
+ width: 9px;
+ height: 9px;
+ border-radius: 50%;
+ background: oklch(0.820 0.030 78);
+}
+.sk__scroll-bar-dot:nth-child(1) { background: oklch(0.730 0.180 28); }
+.sk__scroll-bar-dot:nth-child(2) { background: oklch(0.830 0.150 80); }
+.sk__scroll-bar-dot:nth-child(3) { background: oklch(0.770 0.140 145); }
+.sk__scroll-bar-path {
+ margin-left: 14px;
+ font-family: var(--f-mono);
+ font-size: 14px;
+ color: var(--accent);
+ font-weight: 600;
+ letter-spacing: 0.04em;
+}
+.sk__scroll-bar-meta {
+ margin-left: auto;
+ font-family: var(--f-mono);
+ font-size: 12px;
+ color: var(--fg-faint);
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+.sk__scroll-frame {
+ position: relative;
+ height: 480px;
+ background: var(--bg);
+ overflow: hidden;
+}
+.sk__scroll-frame::before,
+.sk__scroll-frame::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ right: 0;
+ height: 80px;
+ pointer-events: none;
+ z-index: 2;
+}
+.sk__scroll-frame::before {
+ top: 0;
+ background: linear-gradient(180deg, var(--bg) 0%, oklch(0.965 0.018 78 / 0) 100%);
+}
+.sk__scroll-frame::after {
+ bottom: 0;
+ background: linear-gradient(0deg, var(--bg) 0%, oklch(0.965 0.018 78 / 0) 100%);
+}
+
+.sk__scroll-stream {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ display: flex;
+ flex-direction: column;
+ animation: skScrollLoop 28s linear infinite;
+ will-change: transform;
+}
+@keyframes skScrollLoop {
+ from { transform: translateY(0); }
+ to { transform: translateY(-50%); }
+}
+
+.sk__scroll-block {
+ display: flex;
+ flex-direction: column;
+}
+
+.sk__scroll-line {
+ display: grid;
+ grid-template-columns: 56px 1fr;
+ align-items: baseline;
+ gap: 18px;
+ padding: 4px 24px;
+ font-family: var(--f-mono);
+ font-size: 18px;
+ line-height: 1.55;
+ color: var(--fg-soft);
+ min-height: 32px;
+}
+.sk__scroll-line.is-mute {
+ color: var(--fg-faint);
+}
+.sk__scroll-line.is-hi {
+ background: linear-gradient(
+ 90deg,
+ oklch(0.700 0.170 42 / 0.10) 0%,
+ oklch(0.700 0.170 42 / 0.04) 100%);
+ color: var(--fg);
+}
+.sk__scroll-line.is-hi .sk__scroll-text {
+ color: var(--accent);
+ font-weight: 600;
+}
+
+.sk__scroll-num {
+ font-family: var(--f-mono);
+ font-size: 13px;
+ color: var(--fg-faint);
+ text-align: right;
+ letter-spacing: 0.04em;
+}
+.sk__scroll-text {
+ white-space: pre-wrap;
+ word-break: break-word;
+}
+
+/* 像编辑器一样的小光标(永远在中间附近闪一下) */
+.sk__scroll-cursor {
+ position: absolute;
+ top: 50%;
+ left: 80px;
+ width: 2px;
+ height: 22px;
+ background: var(--accent);
+ transform: translateY(-50%);
+ animation: skCursorBlink 1.1s steps(1, end) infinite;
+ z-index: 3;
+ opacity: 0.55;
+}
+@keyframes skCursorBlink {
+ 0%, 50% { opacity: 0.55; }
+ 51%, 100% { opacity: 0; }
+}
+
+.sk__scroll-foot {
+ display: flex;
+ align-items: baseline;
+ gap: 10px;
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 26px;
+ color: var(--fg-mute);
+}
+.sk__scroll-foot em {
+ color: var(--accent);
+ font-style: italic;
+ font-weight: 500;
+}
+
+/* ========================================================================
+ Scene STRIP (step 2) —— 第一刀:剥离
+ ======================================================================== */
+
+.sk__strip-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 110px 140px 90px;
+ gap: 24px;
+ text-align: center;
+}
+
+.sk__strip {
+ display: grid;
+ grid-template-columns: 1fr 80px 1fr;
+ gap: 24px;
+ width: 100%;
+ max-width: 1500px;
+}
+
+.sk__strip-col {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+ padding: 26px 32px 30px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-radius: 4px;
+ text-align: left;
+}
+.sk__strip-col--keep { border-left: 3px solid var(--accent); }
+.sk__strip-col--drop {
+ border-left: 3px solid var(--crimson);
+ opacity: 0.6;
+ filter: grayscale(0.5);
+}
+
+.sk__strip-head {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.24em;
+ text-transform: uppercase;
+ color: var(--fg);
+ font-weight: 600;
+ border-bottom: 1px dashed var(--line);
+ padding-bottom: 12px;
+ margin-bottom: 4px;
+}
+.sk__strip-mark {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 26px;
+ line-height: 1;
+}
+.sk__strip-mark--keep { color: var(--accent); }
+.sk__strip-mark--drop { color: var(--crimson); }
+
+.sk__strip-list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.sk__strip-row {
+ display: flex;
+ align-items: baseline;
+ gap: 14px;
+ font-family: var(--f-serif);
+ font-size: 24px;
+ color: var(--fg);
+ opacity: 0;
+ animation: skDiffRowIn 520ms cubic-bezier(.2,.8,.2,1) forwards;
+}
+@keyframes skDiffRowIn {
+ from { opacity: 0; transform: translateX(-12px); }
+ to { opacity: 1; transform: none; }
+}
+.sk__strip-row code {
+ font-family: var(--f-mono);
+ font-size: 19px;
+ background: oklch(0.910 0.020 78);
+ border: 1px solid var(--line);
+ padding: 1px 8px;
+ border-radius: 2px;
+ color: var(--fg-soft);
+}
+.sk__strip-row-glyph {
+ font-family: var(--f-mono);
+ font-size: 22px;
+ width: 18px;
+ text-align: center;
+ color: var(--accent);
+}
+.sk__strip-row--drop .sk__strip-row-glyph { color: var(--crimson); }
+.sk__strip-row--drop {
+ text-decoration: line-through;
+ text-decoration-thickness: 1px;
+ text-decoration-color: var(--crimson);
+ color: var(--fg-mute);
+}
+
+.sk__strip-sep {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 14px;
+ color: var(--fg-faint);
+}
+.sk__strip-sep-line {
+ flex: 1;
+ width: 1px;
+ background: var(--line-mid);
+}
+.sk__strip-sep-knob {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 36px;
+ color: var(--accent);
+}
+
+/* ========================================================================
+ Scene CHANGE 通用 (step 3..6)
+ ======================================================================== */
+
+.sk__chg-scene {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 80px 100px 60px;
+ gap: 18px;
+ text-align: center;
+}
+
+.sk__chg-num {
+ font-family: var(--f-mono);
+ font-size: 14px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--accent);
+ font-weight: 600;
+}
+
+.sk__chg-title {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 64px;
+ line-height: 1.1;
+ margin: 0;
+ color: var(--fg);
+}
+.sk__chg-title em {
+ font-style: italic;
+ color: var(--accent);
+}
+
+/* 左右分屏 */
+.sk__split {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 32px;
+ width: 100%;
+ max-width: 1640px;
+ align-items: stretch;
+ margin-top: 6px;
+}
+.sk__split-left,
+.sk__split-right {
+ display: flex;
+}
+
+/* ──────── 改动 01 · 流程对比 ──────── */
+
+.sk__chg-flow {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 24px;
+ width: 100%;
+ align-items: stretch;
+}
+.sk__chg-flow-col {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+ padding: 18px 22px 22px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-radius: 4px;
+}
+.sk__chg-flow-col--bad { border-left: 3px solid var(--crimson); opacity: 0.85; }
+.sk__chg-flow-col--good { border-left: 3px solid var(--accent); }
+
+.sk__chg-flow-tag {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-family: var(--f-mono);
+ font-size: 12px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ margin-bottom: 4px;
+}
+.sk__chg-flow-mark {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 22px;
+ line-height: 1;
+}
+.sk__chg-flow-mark--bad { color: var(--crimson); }
+.sk__chg-flow-mark--good { color: var(--accent); }
+
+.sk__chg-flow-step {
+ width: 100%;
+ padding: 10px 14px;
+ background: var(--bg);
+ border: 1px solid var(--line-mid);
+ border-radius: 3px;
+ font-family: var(--f-serif);
+ font-size: 20px;
+ color: var(--fg);
+ text-align: center;
+}
+.sk__chg-flow-step--code {
+ font-family: var(--f-mono);
+ font-size: 17px;
+ letter-spacing: 0.04em;
+}
+.sk__chg-flow-step--out {
+ background: var(--accent);
+ color: var(--paper);
+ border-color: var(--accent);
+}
+.sk__chg-flow-step--system {
+ padding: 0;
+ border: 1px solid var(--accent);
+ background: oklch(0.700 0.170 42 / 0.06);
+ overflow: hidden;
+}
+
+.sk__chg-flow-arrow {
+ font-family: var(--f-mono);
+ font-size: 18px;
+ color: var(--fg-faint);
+}
+
+.sk__chg-system {
+ display: flex;
+ flex-direction: column;
+}
+.sk__chg-system-row {
+ display: grid;
+ grid-template-columns: 80px 1fr;
+ gap: 10px;
+ padding: 7px 14px;
+ border-bottom: 1px dashed var(--line);
+ font-family: var(--f-mono);
+ font-size: 14px;
+ color: var(--fg);
+ text-align: left;
+}
+.sk__chg-system-row:last-child { border-bottom: none; }
+.sk__chg-system-row span {
+ color: var(--fg-mute);
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ font-size: 10px;
+ align-self: center;
+}
+.sk__chg-system-row b {
+ font-weight: 500;
+ color: var(--accent);
+ letter-spacing: 0.02em;
+}
+
+.sk__chg-flow-tip {
+ margin-top: 6px;
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 17px;
+ color: var(--fg-soft);
+}
+.sk__chg-flow-col--bad .sk__chg-flow-tip em { color: var(--crimson); font-style: italic; }
+.sk__chg-flow-col--good .sk__chg-flow-tip em { color: var(--accent); font-style: italic; }
+
+/* ──────── 改动 02 · v0 vs v1 ──────── */
+
+.sk__v {
+ display: grid;
+ grid-template-columns: 1fr 50px 1fr;
+ gap: 16px;
+ width: 100%;
+ align-items: stretch;
+}
+.sk__v-card {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ padding: 20px 22px 22px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-radius: 4px;
+}
+.sk__v-card--v0 { border-left: 3px solid var(--accent); }
+.sk__v-card--v1 { border-left: 3px solid var(--crimson); opacity: 0.92; }
+
+.sk__v-tag {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-family: var(--f-mono);
+ font-size: 12px;
+ letter-spacing: 0.24em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+ border-bottom: 1px dashed var(--line);
+ padding-bottom: 10px;
+}
+.sk__v-tag em {
+ font-family: var(--f-serif);
+ font-style: italic;
+ letter-spacing: 0.02em;
+ text-transform: none;
+ color: var(--fg);
+ font-size: 16px;
+}
+.sk__v-mark {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 20px;
+ line-height: 1;
+}
+.sk__v-mark--good { color: var(--accent); }
+.sk__v-mark--bad { color: var(--crimson); }
+
+.sk__v-mock {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ padding: 16px;
+ background: var(--bg);
+ border: 1px dashed var(--line-mid);
+ border-radius: 2px;
+ min-height: 160px;
+ align-items: flex-start;
+ justify-content: center;
+}
+.sk__v-mock--full {
+ border: 1px solid var(--line-mid);
+ border-radius: 3px;
+ align-items: center;
+ text-align: center;
+ background: linear-gradient(180deg, oklch(0.910 0.020 78) 0%, var(--bg) 60%);
+}
+.sk__v-mock-bar {
+ display: block;
+ height: 12px;
+ background: var(--line-mid);
+ border-radius: 2px;
+}
+.sk__v-mock-bar--w70 { width: 70%; }
+.sk__v-mock-bar--w50 { width: 50%; }
+.sk__v-mock-bar--w40 { width: 40%; }
+.sk__v-mock-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 6px;
+ width: 100%;
+}
+.sk__v-mock-grid > span {
+ height: 44px;
+ background: var(--line-mid);
+ border-radius: 2px;
+}
+.sk__v-mock-grid--full > span {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--bg-2);
+ border: 1px solid var(--line);
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 22px;
+ color: var(--accent);
+}
+.sk__v-mock-h {
+ font-family: var(--f-serif);
+ font-size: 22px;
+ font-weight: 500;
+ color: var(--fg);
+}
+.sk__v-mock-sub {
+ font-family: var(--f-sans);
+ font-size: 12px;
+ color: var(--fg-mute);
+ margin-bottom: 4px;
+}
+.sk__v-mock-cta {
+ margin-top: 4px;
+ padding: 5px 14px;
+ background: var(--accent);
+ color: var(--paper);
+ font-family: var(--f-mono);
+ font-size: 11px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ border-radius: 2px;
+}
+
+.sk__v-foot {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 18px;
+ color: var(--fg-mute);
+}
+.sk__v-card--v0 .sk__v-foot em { color: var(--accent); font-style: italic; }
+.sk__v-card--v1 .sk__v-foot em { color: var(--crimson); font-style: italic; }
+
+.sk__v-vs {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 36px;
+ color: var(--fg-faint);
+}
+
+.sk__v-strike {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ overflow: hidden;
+}
+.sk__v-strike-line {
+ position: absolute;
+ top: 50%;
+ left: -10%;
+ right: -10%;
+ height: 3px;
+ background: var(--crimson);
+ transform: translateY(-50%) rotate(-8deg) scaleX(0);
+ transform-origin: left;
+ animation: skVStrike 620ms 1500ms cubic-bezier(.6,-0.05,.2,1.2) forwards;
+ opacity: 0.55;
+}
+@keyframes skVStrike {
+ to { transform: translateY(-50%) rotate(-8deg) scaleX(1); }
+}
+
+/* ──────── 改动 03 / 04 共用 col ──────── */
+
+.sk__cd-col {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+ padding: 22px 26px 26px;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-left: 3px solid var(--accent);
+ border-radius: 4px;
+ width: 100%;
+ text-align: left;
+}
+
+.sk__cd-tag {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 24px;
+ color: var(--fg);
+ border-bottom: 1px dashed var(--line);
+ padding-bottom: 12px;
+}
+.sk__cd-mark {
+ font-family: var(--f-mono);
+ font-style: normal;
+ font-size: 12px;
+ letter-spacing: 0.24em;
+ text-transform: uppercase;
+ color: var(--accent);
+ background: oklch(0.700 0.170 42 / 0.12);
+ border: 1px solid oklch(0.700 0.170 42 / 0.45);
+ padding: 4px 10px;
+ border-radius: 2px;
+ font-weight: 600;
+}
+
+.sk__cd-anti {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+.sk__cd-anti-row {
+ display: flex;
+ align-items: baseline;
+ gap: 12px;
+ padding: 8px 14px;
+ background: var(--bg);
+ border: 1px solid var(--line);
+ border-radius: 2px;
+ font-family: var(--f-sans);
+ font-size: 19px;
+ color: var(--fg-soft);
+ opacity: 0;
+ animation: skDiffRowIn 520ms cubic-bezier(.2,.8,.2,1) forwards;
+}
+.sk__cd-anti-x {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 20px;
+ color: var(--crimson);
+}
+
+.sk__pairs {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+.sk__pair {
+ display: grid;
+ grid-template-columns: 110px 110px 24px 1fr;
+ align-items: center;
+ gap: 10px;
+ padding: 8px 12px;
+ background: var(--bg);
+ border: 1px solid var(--line);
+ border-radius: 2px;
+ font-family: var(--f-sans);
+ font-size: 16px;
+ color: var(--fg);
+ opacity: 0;
+ animation: skDiffRowIn 520ms cubic-bezier(.2,.8,.2,1) forwards;
+}
+.sk__pair-tag {
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 18px;
+ color: var(--fg);
+}
+.sk__pair-color {
+ font-family: var(--f-mono);
+ font-size: 13px;
+ color: var(--accent);
+ letter-spacing: 0.04em;
+}
+.sk__pair-x {
+ font-family: var(--f-mono);
+ color: var(--fg-faint);
+ text-align: center;
+}
+.sk__pair-font {
+ font-family: var(--f-mono);
+ font-size: 13px;
+ color: var(--fg-soft);
+ letter-spacing: 0.02em;
+}
+
+/* ========================================================================
+ 原文摘录面板(Excerpt) —— 关键行高亮 / 上下文模糊虚化
+ ======================================================================== */
+
+.sk__ex {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ background: var(--bg-2);
+ border: 1px solid var(--line-mid);
+ border-radius: 4px;
+ overflow: hidden;
+ box-shadow: 0 24px 48px -28px oklch(0 0 0 / 0.22);
+}
+
+.sk__ex-bar {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 8px 16px;
+ background: oklch(0.910 0.020 78);
+ border-bottom: 1px solid var(--line-mid);
+}
+.sk__ex-bar-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: oklch(0.820 0.030 78);
+}
+.sk__ex-bar-dot:nth-child(1) { background: oklch(0.730 0.180 28); }
+.sk__ex-bar-dot:nth-child(2) { background: oklch(0.830 0.150 80); }
+.sk__ex-bar-dot:nth-child(3) { background: oklch(0.770 0.140 145); }
+.sk__ex-bar-path {
+ margin-left: 12px;
+ font-family: var(--f-mono);
+ font-size: 13px;
+ color: var(--accent);
+ font-weight: 600;
+ letter-spacing: 0.04em;
+}
+.sk__ex-bar-range {
+ margin-left: auto;
+ font-family: var(--f-mono);
+ font-size: 11px;
+ color: var(--fg-mute);
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+}
+
+.sk__ex-body {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ padding: 14px 0;
+ background:
+ linear-gradient(180deg,
+ oklch(0.940 0.018 78) 0%,
+ oklch(0.940 0.018 78) 100%);
+ /* 上下文虚化基底:让非高亮行整体偏淡 */
+}
+
+.sk__ex-line {
+ display: grid;
+ grid-template-columns: 48px 1fr;
+ gap: 14px;
+ align-items: baseline;
+ padding: 2px 22px 2px calc(22px + var(--ex-indent, 0px));
+ font-family: var(--f-mono);
+ font-size: 15px;
+ line-height: 1.65;
+ color: var(--fg-mute);
+ opacity: 0.32;
+ filter: blur(0.7px);
+ transition: opacity 360ms, filter 360ms;
+ min-height: 22px;
+}
+
+.sk__ex-line.is-hi {
+ opacity: 1;
+ filter: none;
+ background: oklch(0.700 0.170 42 / 0.10);
+ color: var(--fg);
+ position: relative;
+ animation: skExHiPulse 1100ms cubic-bezier(.2,.8,.2,1) backwards;
+}
+.sk__ex-line.is-hi::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ width: 3px;
+ background: var(--accent);
+}
+@keyframes skExHiPulse {
+ 0% { background: oklch(0.700 0.170 42 / 0); transform: translateX(-4px); }
+ 35% { background: oklch(0.700 0.170 42 / 0.30); transform: translateX(0); }
+ 100% { background: oklch(0.700 0.170 42 / 0.10); transform: translateX(0); }
+}
+
+.sk__ex-line.is-hi b {
+ color: var(--accent);
+ font-weight: 600;
+}
+.sk__ex-line.is-hi em {
+ font-style: italic;
+ color: var(--accent);
+ background: oklch(0.700 0.170 42 / 0.18);
+ padding: 0 4px;
+ border-radius: 2px;
+}
+
+.sk__ex-num {
+ font-family: var(--f-mono);
+ font-size: 12px;
+ color: var(--fg-faint);
+ text-align: right;
+ letter-spacing: 0.04em;
+ user-select: none;
+}
+.sk__ex-line.is-hi .sk__ex-num {
+ color: var(--accent);
+ font-weight: 600;
+}
+.sk__ex-text {
+ white-space: pre-wrap;
+ word-break: break-word;
+}
+
+.sk__ex-caption {
+ padding: 10px 22px 14px;
+ background: oklch(0.910 0.020 78);
+ border-top: 1px solid var(--line-mid);
+ font-family: var(--f-serif);
+ font-style: italic;
+ font-size: 18px;
+ color: var(--fg-soft);
+ text-align: center;
+}
+.sk__ex-caption em {
+ color: var(--accent);
+ font-style: italic;
+}
+
+/* ========================================================================
+ Scene CLOSE (step 7)
+ ======================================================================== */
+
+.sk__close {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ gap: 18px;
+ padding: 140px;
+}
+.sk__close-l1 {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 128px;
+ line-height: 1;
+ margin: 0;
+ color: var(--fg);
+}
+.sk__close-l2 {
+ font-family: var(--f-serif);
+ font-weight: 400;
+ font-size: 64px;
+ line-height: 1.2;
+ margin: 0;
+ color: var(--fg-soft);
+}
+.sk__close-l1 em,
+.sk__close-l2 em {
+ font-style: italic;
+ color: var(--accent);
+}
diff --git a/web/src/chapters/11-skill-changes/index.tsx b/web/src/chapters/11-skill-changes/index.tsx
new file mode 100644
index 0000000..8c5a547
--- /dev/null
+++ b/web/src/chapters/11-skill-changes/index.tsx
@@ -0,0 +1,663 @@
+import type { ReactNode } from 'react';
+import type { ChapterContext, ChapterDef } from '../types';
+import { Reveal } from '../../shared/Reveal';
+import { SceneFade } from '../../shared/SceneFade';
+import './SkillChanges.css';
+
+/**
+ * Chapter 11 · Skill 的关键改动(重做版)
+ *
+ * 口播原顺序(严格对齐 article/口播稿.md):
+ * "这个 Skill 大概 400 行,首先我把 Claude Design 中特有工具和环境的描述剥离了出去,
+ * 只保留了其中能通用的、最有价值的部分,然后在这个基础上我还优化了几个地方:"
+ * ① 写代码之前必须先用自然语言把设计系统说清楚 — 配色、字体、间距,全列出来。
+ * ② 要求它尽早拿出一个带假设和占位符的最小版本(v0)。
+ * ③ 在 Claude Design 的基础上,补充了更多的去除 AI 味的条目。
+ * ④ 加了几套经过验证的配色 × 字体的配对参考表 —— 给 AI 一个靠谱的起点。
+ *
+ * 节奏(8 步 / step 0..7):
+ * 0 Skill 全貌 —— 文件目录树(SKILL.md ≈ 400 行 / references ≈ 520 行)
+ * 1 SKILL.md 真原文滚动展示(虚假的编辑器持续 scroll)
+ * 2 第一刀:剥离 —— 工具/环境 vs 通用精华
+ * 3 + 改动 01 · 先宣告设计系统 + SKILL.md L79-91 原文摘录(关键高亮 / 上下文虚化)
+ * 4 + 改动 02 · v0 半成品先出 + SKILL.md L93-101 原文摘录
+ * 5 + 改动 03 · 反 AI 味扩展 + SKILL.md L200-219 原文摘录
+ * 6 + 改动 04 · 配色 × 字体配对 + advanced-patterns.md L505-516 原文摘录
+ * 7 收尾:"给 AI 一个靠谱的起点 —— 比让它自由发挥稳定"
+ */
+
+/* ──────────────────────────────────────────────────────────────────
+ * 复用:原文摘录代码面板(关键行高亮、上下文虚化模糊)
+ * ────────────────────────────────────────────────────────────────── */
+
+interface CodeLine {
+ /** 真实行号(用于左侧 gutter) */
+ n: number;
+ /** 行内容 */
+ text: ReactNode;
+ /** 是否高亮(不模糊、不弱化) */
+ hi?: boolean;
+ /** 行视觉缩进(每级 16px) */
+ indent?: number;
+}
+
+interface ExcerptProps {
+ filePath: string;
+ range: string;
+ caption?: ReactNode;
+ lines: CodeLine[];
+ /** delay before highlight pulse */
+ pulseDelay?: number;
+}
+
+function Excerpt({ filePath, range, caption, lines, pulseDelay = 600 }: ExcerptProps) {
+ return (
+
+
+
+
+
+ {filePath}
+ {range}
+
+
+ {lines.map((l, i) => (
+
+ {l.n}
+ {l.text}
+
+ ))}
+
+ {caption &&
{caption}
}
+
+ );
+}
+
+/* ──────────────────────────────────────────────────────────────────
+ * SKILL.md 假滚动用的内容片段(接近真实,用于动态滚动展示)
+ * ────────────────────────────────────────────────────────────────── */
+
+const SCROLL_LINES: { t: ReactNode; hi?: boolean; mute?: boolean }[] = [
+ { t: '---', mute: true },
+ { t: 'name: web-design-engineer', hi: true },
+ { t: 'description: |' },
+ { t: ' Build high-quality visual Web artifacts using HTML / CSS / JS / React —' },
+ { t: ' web pages, dashboards, prototypes, slide decks, animated demos, …' },
+ { t: ' Use this skill whenever the request involves a visual deliverable.' },
+ { t: '---', mute: true },
+ { t: '' },
+ { t: '# Web Design Engineer', hi: true },
+ { t: '' },
+ { t: 'Core philosophy: the bar is "stunning," not "functional".' },
+ { t: 'Every pixel is intentional. Every interaction is deliberate.' },
+ { t: '' },
+ { t: '## Workflow', hi: true },
+ { t: '' },
+ { t: '### Step 1 · Understand the Requirements' },
+ { t: '### Step 2 · Gather Design Context' },
+ { t: '### Step 3 · Declare the Design System Before Writing Code', hi: true },
+ { t: '### Step 4 · Show a v0 Draft Early', hi: true },
+ { t: '### Step 5 · Full Build' },
+ { t: '### Step 6 · Verification' },
+ { t: '' },
+ { t: '## Technical Specifications' },
+ { t: '' },
+ { t: '#### Three Non-negotiable Hard Rules', hi: true },
+ { t: '1. Never use `const styles = {...}`' },
+ { t: '2. Separate