@@ -17,6 +17,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.2.0",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/node": "^20",
|
||||
@@ -35,12 +36,206 @@
|
||||
"packages": {
|
||||
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
||||
|
||||
"@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
|
||||
|
||||
"@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="],
|
||||
|
||||
"@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="],
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.29.7", "", { "dependencies": { "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ=="],
|
||||
|
||||
"@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw=="],
|
||||
|
||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="],
|
||||
|
||||
"@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg=="],
|
||||
|
||||
"@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg=="],
|
||||
|
||||
"@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.8", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "debug": "^4.4.3", "lodash.debounce": "^4.0.8", "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA=="],
|
||||
|
||||
"@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg=="],
|
||||
|
||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="],
|
||||
|
||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="],
|
||||
|
||||
"@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong=="],
|
||||
|
||||
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
|
||||
|
||||
"@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-wrap-function": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og=="],
|
||||
|
||||
"@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.29.7", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="],
|
||||
|
||||
"@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="],
|
||||
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w=="],
|
||||
|
||||
"@babel/plugin-bugfix-safari-class-field-initializer-scope": ["@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ=="],
|
||||
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ["@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ=="],
|
||||
|
||||
"@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": ["@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA=="],
|
||||
|
||||
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ["@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/plugin-transform-optional-chaining": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.13.0" } }, "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw=="],
|
||||
|
||||
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ["@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw=="],
|
||||
|
||||
"@babel/plugin-proposal-private-property-in-object": ["@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w=="],
|
||||
|
||||
"@babel/plugin-syntax-import-assertions": ["@babel/plugin-syntax-import-assertions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw=="],
|
||||
|
||||
"@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg=="],
|
||||
|
||||
"@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A=="],
|
||||
|
||||
"@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA=="],
|
||||
|
||||
"@babel/plugin-syntax-unicode-sets-regex": ["@babel/plugin-syntax-unicode-sets-regex@7.18.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg=="],
|
||||
|
||||
"@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ=="],
|
||||
|
||||
"@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-remap-async-to-generator": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA=="],
|
||||
|
||||
"@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-remap-async-to-generator": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w=="],
|
||||
|
||||
"@babel/plugin-transform-block-scoped-functions": ["@babel/plugin-transform-block-scoped-functions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA=="],
|
||||
|
||||
"@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ=="],
|
||||
|
||||
"@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA=="],
|
||||
|
||||
"@babel/plugin-transform-class-static-block": ["@babel/plugin-transform-class-static-block@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.12.0" } }, "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A=="],
|
||||
|
||||
"@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g=="],
|
||||
|
||||
"@babel/plugin-transform-computed-properties": ["@babel/plugin-transform-computed-properties@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/template": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA=="],
|
||||
|
||||
"@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg=="],
|
||||
|
||||
"@babel/plugin-transform-dotall-regex": ["@babel/plugin-transform-dotall-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ=="],
|
||||
|
||||
"@babel/plugin-transform-duplicate-keys": ["@babel/plugin-transform-duplicate-keys@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ=="],
|
||||
|
||||
"@babel/plugin-transform-duplicate-named-capturing-groups-regex": ["@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA=="],
|
||||
|
||||
"@babel/plugin-transform-dynamic-import": ["@babel/plugin-transform-dynamic-import@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg=="],
|
||||
|
||||
"@babel/plugin-transform-explicit-resource-management": ["@babel/plugin-transform-explicit-resource-management@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-transform-destructuring": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw=="],
|
||||
|
||||
"@babel/plugin-transform-exponentiation-operator": ["@babel/plugin-transform-exponentiation-operator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ=="],
|
||||
|
||||
"@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA=="],
|
||||
|
||||
"@babel/plugin-transform-for-of": ["@babel/plugin-transform-for-of@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ=="],
|
||||
|
||||
"@babel/plugin-transform-function-name": ["@babel/plugin-transform-function-name@7.29.7", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg=="],
|
||||
|
||||
"@babel/plugin-transform-json-strings": ["@babel/plugin-transform-json-strings@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg=="],
|
||||
|
||||
"@babel/plugin-transform-literals": ["@babel/plugin-transform-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw=="],
|
||||
|
||||
"@babel/plugin-transform-logical-assignment-operators": ["@babel/plugin-transform-logical-assignment-operators@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q=="],
|
||||
|
||||
"@babel/plugin-transform-member-expression-literals": ["@babel/plugin-transform-member-expression-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg=="],
|
||||
|
||||
"@babel/plugin-transform-modules-amd": ["@babel/plugin-transform-modules-amd@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew=="],
|
||||
|
||||
"@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ=="],
|
||||
|
||||
"@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ=="],
|
||||
|
||||
"@babel/plugin-transform-modules-umd": ["@babel/plugin-transform-modules-umd@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA=="],
|
||||
|
||||
"@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ=="],
|
||||
|
||||
"@babel/plugin-transform-new-target": ["@babel/plugin-transform-new-target@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A=="],
|
||||
|
||||
"@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg=="],
|
||||
|
||||
"@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw=="],
|
||||
|
||||
"@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.29.7", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-transform-destructuring": "^7.29.7", "@babel/plugin-transform-parameters": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A=="],
|
||||
|
||||
"@babel/plugin-transform-object-super": ["@babel/plugin-transform-object-super@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA=="],
|
||||
|
||||
"@babel/plugin-transform-optional-catch-binding": ["@babel/plugin-transform-optional-catch-binding@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng=="],
|
||||
|
||||
"@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ=="],
|
||||
|
||||
"@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g=="],
|
||||
|
||||
"@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug=="],
|
||||
|
||||
"@babel/plugin-transform-private-property-in-object": ["@babel/plugin-transform-private-property-in-object@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA=="],
|
||||
|
||||
"@babel/plugin-transform-property-literals": ["@babel/plugin-transform-property-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA=="],
|
||||
|
||||
"@babel/plugin-transform-react-constant-elements": ["@babel/plugin-transform-react-constant-elements@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-J0wGhKan+rIiE2OhfhRptySLrJ6SjQYM6b6N1FMlhyhCcw1Mig8vQjWchyB+bgHGDvaWo6Diu6CLRMra2uMtmg=="],
|
||||
|
||||
"@babel/plugin-transform-react-display-name": ["@babel/plugin-transform-react-display-name@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-module-imports": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-syntax-jsx": "^7.29.7", "@babel/types": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx-development": ["@babel/plugin-transform-react-jsx-development@7.29.7", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g=="],
|
||||
|
||||
"@babel/plugin-transform-react-pure-annotations": ["@babel/plugin-transform-react-pure-annotations@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA=="],
|
||||
|
||||
"@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-rNNFV0DBAJp988xW2DOntfDoYn1eR8GGF5AT5vYc+rjyfaQkM242c9tZUHHPe7KYaiJizXPWhQTzzdbXySyhBw=="],
|
||||
|
||||
"@babel/plugin-transform-regexp-modifiers": ["@babel/plugin-transform-regexp-modifiers@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ=="],
|
||||
|
||||
"@babel/plugin-transform-reserved-words": ["@babel/plugin-transform-reserved-words@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA=="],
|
||||
|
||||
"@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg=="],
|
||||
|
||||
"@babel/plugin-transform-spread": ["@babel/plugin-transform-spread@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-/u5K1QWada7tbYNqTjMh96718g9NTwh9tfPJMsSmVsQwGT447FskV+KcfeXkXq2GWki4EM/MuTdmBec+hOuVTQ=="],
|
||||
|
||||
"@babel/plugin-transform-sticky-regex": ["@babel/plugin-transform-sticky-regex@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA=="],
|
||||
|
||||
"@babel/plugin-transform-template-literals": ["@babel/plugin-transform-template-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA=="],
|
||||
|
||||
"@babel/plugin-transform-typeof-symbol": ["@babel/plugin-transform-typeof-symbol@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A=="],
|
||||
|
||||
"@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/plugin-syntax-typescript": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw=="],
|
||||
|
||||
"@babel/plugin-transform-unicode-escapes": ["@babel/plugin-transform-unicode-escapes@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA=="],
|
||||
|
||||
"@babel/plugin-transform-unicode-property-regex": ["@babel/plugin-transform-unicode-property-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw=="],
|
||||
|
||||
"@babel/plugin-transform-unicode-regex": ["@babel/plugin-transform-unicode-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA=="],
|
||||
|
||||
"@babel/plugin-transform-unicode-sets-regex": ["@babel/plugin-transform-unicode-sets-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg=="],
|
||||
|
||||
"@babel/preset-env": ["@babel/preset-env@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7", "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.29.7", "@babel/plugin-syntax-import-attributes": "^7.29.7", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.29.7", "@babel/plugin-transform-async-generator-functions": "^7.29.7", "@babel/plugin-transform-async-to-generator": "^7.29.7", "@babel/plugin-transform-block-scoped-functions": "^7.29.7", "@babel/plugin-transform-block-scoping": "^7.29.7", "@babel/plugin-transform-class-properties": "^7.29.7", "@babel/plugin-transform-class-static-block": "^7.29.7", "@babel/plugin-transform-classes": "^7.29.7", "@babel/plugin-transform-computed-properties": "^7.29.7", "@babel/plugin-transform-destructuring": "^7.29.7", "@babel/plugin-transform-dotall-regex": "^7.29.7", "@babel/plugin-transform-duplicate-keys": "^7.29.7", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7", "@babel/plugin-transform-dynamic-import": "^7.29.7", "@babel/plugin-transform-explicit-resource-management": "^7.29.7", "@babel/plugin-transform-exponentiation-operator": "^7.29.7", "@babel/plugin-transform-export-namespace-from": "^7.29.7", "@babel/plugin-transform-for-of": "^7.29.7", "@babel/plugin-transform-function-name": "^7.29.7", "@babel/plugin-transform-json-strings": "^7.29.7", "@babel/plugin-transform-literals": "^7.29.7", "@babel/plugin-transform-logical-assignment-operators": "^7.29.7", "@babel/plugin-transform-member-expression-literals": "^7.29.7", "@babel/plugin-transform-modules-amd": "^7.29.7", "@babel/plugin-transform-modules-commonjs": "^7.29.7", "@babel/plugin-transform-modules-systemjs": "^7.29.7", "@babel/plugin-transform-modules-umd": "^7.29.7", "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7", "@babel/plugin-transform-new-target": "^7.29.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7", "@babel/plugin-transform-numeric-separator": "^7.29.7", "@babel/plugin-transform-object-rest-spread": "^7.29.7", "@babel/plugin-transform-object-super": "^7.29.7", "@babel/plugin-transform-optional-catch-binding": "^7.29.7", "@babel/plugin-transform-optional-chaining": "^7.29.7", "@babel/plugin-transform-parameters": "^7.29.7", "@babel/plugin-transform-private-methods": "^7.29.7", "@babel/plugin-transform-private-property-in-object": "^7.29.7", "@babel/plugin-transform-property-literals": "^7.29.7", "@babel/plugin-transform-regenerator": "^7.29.7", "@babel/plugin-transform-regexp-modifiers": "^7.29.7", "@babel/plugin-transform-reserved-words": "^7.29.7", "@babel/plugin-transform-shorthand-properties": "^7.29.7", "@babel/plugin-transform-spread": "^7.29.7", "@babel/plugin-transform-sticky-regex": "^7.29.7", "@babel/plugin-transform-template-literals": "^7.29.7", "@babel/plugin-transform-typeof-symbol": "^7.29.7", "@babel/plugin-transform-unicode-escapes": "^7.29.7", "@babel/plugin-transform-unicode-property-regex": "^7.29.7", "@babel/plugin-transform-unicode-regex": "^7.29.7", "@babel/plugin-transform-unicode-sets-regex": "^7.29.7", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.15", "babel-plugin-polyfill-corejs3": "^0.14.0", "babel-plugin-polyfill-regenerator": "^0.6.6", "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA=="],
|
||||
|
||||
"@babel/preset-modules": ["@babel/preset-modules@0.1.6-no-external-plugins", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", "esutils": "^2.0.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA=="],
|
||||
|
||||
"@babel/preset-react": ["@babel/preset-react@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "@babel/plugin-transform-react-display-name": "^7.29.7", "@babel/plugin-transform-react-jsx": "^7.29.7", "@babel/plugin-transform-react-jsx-development": "^7.29.7", "@babel/plugin-transform-react-pure-annotations": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-C+PV1TFUPTmBQGoPBL8j2QmLpZ117YTCwxIZeJOM96GbYMFSc7/pOXU5lVykwnZxyTqQxRsvoRk6f2FktZgGHA=="],
|
||||
|
||||
"@babel/preset-typescript": ["@babel/preset-typescript@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "@babel/plugin-syntax-jsx": "^7.29.7", "@babel/plugin-transform-modules-commonjs": "^7.29.7", "@babel/plugin-transform-typescript": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ=="],
|
||||
|
||||
"@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="],
|
||||
|
||||
"@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="],
|
||||
|
||||
"@babel/traverse": ["@babel/traverse@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/types": "^7.29.7", "debug": "^4.3.1" } }, "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
||||
|
||||
"@biomejs/biome": ["@biomejs/biome@2.2.0", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.2.0", "@biomejs/cli-darwin-x64": "2.2.0", "@biomejs/cli-linux-arm64": "2.2.0", "@biomejs/cli-linux-arm64-musl": "2.2.0", "@biomejs/cli-linux-x64": "2.2.0", "@biomejs/cli-linux-x64-musl": "2.2.0", "@biomejs/cli-win32-arm64": "2.2.0", "@biomejs/cli-win32-x64": "2.2.0" }, "bin": { "biome": "bin/biome" } }, "sha512-3On3RSYLsX+n9KnoSgfoYlckYBoU6VRM22cw1gB4Y0OuUVSYd/O/2saOJMrA4HFfA1Ff0eacOvMN1yAAvHtzIw=="],
|
||||
@@ -151,6 +346,34 @@
|
||||
|
||||
"@react-three/fiber": ["@react-three/fiber@9.5.0", "", { "dependencies": { "@babel/runtime": "^7.17.8", "@types/webxr": "*", "base64-js": "^1.5.1", "buffer": "^6.0.3", "its-fine": "^2.0.0", "react-use-measure": "^2.1.7", "scheduler": "^0.27.0", "suspend-react": "^0.1.3", "use-sync-external-store": "^1.4.0", "zustand": "^5.0.3" }, "peerDependencies": { "expo": ">=43.0", "expo-asset": ">=8.4", "expo-file-system": ">=11.0", "expo-gl": ">=11.0", "react": ">=19 <19.3", "react-dom": ">=19 <19.3", "react-native": ">=0.78", "three": ">=0.156" }, "optionalPeers": ["expo", "expo-asset", "expo-file-system", "expo-gl", "react-dom", "react-native"] }, "sha512-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA=="],
|
||||
|
||||
"@svgr/babel-plugin-add-jsx-attribute": ["@svgr/babel-plugin-add-jsx-attribute@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g=="],
|
||||
|
||||
"@svgr/babel-plugin-remove-jsx-attribute": ["@svgr/babel-plugin-remove-jsx-attribute@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA=="],
|
||||
|
||||
"@svgr/babel-plugin-remove-jsx-empty-expression": ["@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA=="],
|
||||
|
||||
"@svgr/babel-plugin-replace-jsx-attribute-value": ["@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ=="],
|
||||
|
||||
"@svgr/babel-plugin-svg-dynamic-title": ["@svgr/babel-plugin-svg-dynamic-title@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og=="],
|
||||
|
||||
"@svgr/babel-plugin-svg-em-dimensions": ["@svgr/babel-plugin-svg-em-dimensions@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g=="],
|
||||
|
||||
"@svgr/babel-plugin-transform-react-native-svg": ["@svgr/babel-plugin-transform-react-native-svg@8.1.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q=="],
|
||||
|
||||
"@svgr/babel-plugin-transform-svg-component": ["@svgr/babel-plugin-transform-svg-component@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw=="],
|
||||
|
||||
"@svgr/babel-preset": ["@svgr/babel-preset@8.1.0", "", { "dependencies": { "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", "@svgr/babel-plugin-transform-svg-component": "8.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug=="],
|
||||
|
||||
"@svgr/core": ["@svgr/core@8.1.0", "", { "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", "camelcase": "^6.2.0", "cosmiconfig": "^8.1.3", "snake-case": "^3.0.4" } }, "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA=="],
|
||||
|
||||
"@svgr/hast-util-to-babel-ast": ["@svgr/hast-util-to-babel-ast@8.0.0", "", { "dependencies": { "@babel/types": "^7.21.3", "entities": "^4.4.0" } }, "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q=="],
|
||||
|
||||
"@svgr/plugin-jsx": ["@svgr/plugin-jsx@8.1.0", "", { "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", "@svgr/hast-util-to-babel-ast": "8.0.0", "svg-parser": "^2.0.4" }, "peerDependencies": { "@svgr/core": "*" } }, "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA=="],
|
||||
|
||||
"@svgr/plugin-svgo": ["@svgr/plugin-svgo@8.1.0", "", { "dependencies": { "cosmiconfig": "^8.1.3", "deepmerge": "^4.3.1", "svgo": "^3.0.2" }, "peerDependencies": { "@svgr/core": "*" } }, "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA=="],
|
||||
|
||||
"@svgr/webpack": ["@svgr/webpack@8.1.0", "", { "dependencies": { "@babel/core": "^7.21.3", "@babel/plugin-transform-react-constant-elements": "^7.21.3", "@babel/preset-env": "^7.20.2", "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.21.0", "@svgr/core": "8.1.0", "@svgr/plugin-jsx": "8.1.0", "@svgr/plugin-svgo": "8.1.0" } }, "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA=="],
|
||||
|
||||
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.2.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.0" } }, "sha512-Yv+fn/o2OmL5fh/Ir62VXItdShnUxfpkMA4Y7jdeC8O81WPB8Kf6TT6GSHvnqgSwDzlB5iT7kDpeXxLsUS0T6Q=="],
|
||||
@@ -211,6 +434,14 @@
|
||||
|
||||
"@webgpu/types": ["@webgpu/types@0.1.69", "", {}, "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.17", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w=="],
|
||||
|
||||
"babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.14.2", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8", "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g=="],
|
||||
|
||||
"babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.8", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg=="],
|
||||
|
||||
"babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="],
|
||||
|
||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||
@@ -225,8 +456,16 @@
|
||||
|
||||
"bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
|
||||
|
||||
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
||||
|
||||
"browserslist": ["browserslist@4.28.2", "", { "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" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
|
||||
|
||||
"buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
|
||||
|
||||
"camera-controls": ["camera-controls@3.1.2", "", { "peerDependencies": { "three": ">=0.126.1" } }, "sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001770", "", {}, "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw=="],
|
||||
@@ -235,28 +474,70 @@
|
||||
|
||||
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
|
||||
|
||||
"commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"core-js-compat": ["core-js-compat@3.49.0", "", { "dependencies": { "browserslist": "^4.28.1" } }, "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA=="],
|
||||
|
||||
"cosmiconfig": ["cosmiconfig@8.3.6", "", { "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0", "path-type": "^4.0.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA=="],
|
||||
|
||||
"cross-env": ["cross-env@7.0.3", "", { "dependencies": { "cross-spawn": "^7.0.1" }, "bin": { "cross-env": "src/bin/cross-env.js", "cross-env-shell": "src/bin/cross-env-shell.js" } }, "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
|
||||
|
||||
"css-tree": ["css-tree@2.3.1", "", { "dependencies": { "mdn-data": "2.0.30", "source-map-js": "^1.0.1" } }, "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw=="],
|
||||
|
||||
"css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
|
||||
|
||||
"csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="],
|
||||
|
||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
|
||||
|
||||
"deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="],
|
||||
|
||||
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||
|
||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
|
||||
"detect-gpu": ["detect-gpu@5.0.70", "", { "dependencies": { "webgl-constants": "^1.1.1" } }, "sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
|
||||
|
||||
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
|
||||
|
||||
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
|
||||
|
||||
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
|
||||
|
||||
"dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="],
|
||||
|
||||
"draco3d": ["draco3d@1.5.7", "", {}, "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.375", "", {}, "sha512-ZWP5eB4BVPW/ZYo9252hQZHZ5XavtsTgpbhcmMmRwymavC5AsLWQWBPaKMeNd2LW0KGby5HPXvj7+sr4ta5j/Q=="],
|
||||
|
||||
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="],
|
||||
|
||||
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
|
||||
|
||||
"fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="],
|
||||
@@ -267,22 +548,34 @@
|
||||
|
||||
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
|
||||
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
|
||||
|
||||
"glsl-noise": ["glsl-noise@0.0.0", "", {}, "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="],
|
||||
|
||||
"hls.js": ["hls.js@1.6.15", "", {}, "sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA=="],
|
||||
|
||||
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="],
|
||||
|
||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
|
||||
|
||||
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
|
||||
|
||||
"is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="],
|
||||
|
||||
"is-promise": ["is-promise@2.2.2", "", {}, "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
@@ -291,6 +584,16 @@
|
||||
|
||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
|
||||
"js-yaml": ["js-yaml@4.2.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw=="],
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||
|
||||
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
|
||||
|
||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="],
|
||||
@@ -317,10 +620,20 @@
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="],
|
||||
|
||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||
|
||||
"lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
|
||||
|
||||
"lower-case": ["lower-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg=="],
|
||||
|
||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"maath": ["maath@0.10.8", "", { "peerDependencies": { "@types/three": ">=0.134.0", "three": ">=0.134.0" } }, "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"mdn-data": ["mdn-data@2.0.30", "", {}, "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="],
|
||||
|
||||
"meshline": ["meshline@3.3.1", "", { "peerDependencies": { "three": ">=0.137" } }, "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ=="],
|
||||
|
||||
"meshoptimizer": ["meshoptimizer@1.0.1", "", {}, "sha512-Vix+QlA1YYT3FwmBBZ+49cE5y/b+pRrcXKqGpS5ouh33d3lSp2PoTpCw19E0cKDFWalembrHnIaZetf27a+W2g=="],
|
||||
@@ -337,18 +650,34 @@
|
||||
|
||||
"motion-utils": ["motion-utils@12.29.2", "", {}, "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
||||
"napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="],
|
||||
|
||||
"next": ["next@16.1.6", "", { "dependencies": { "@next/env": "16.1.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.6", "@next/swc-darwin-x64": "16.1.6", "@next/swc-linux-arm64-gnu": "16.1.6", "@next/swc-linux-arm64-musl": "16.1.6", "@next/swc-linux-x64-gnu": "16.1.6", "@next/swc-linux-x64-musl": "16.1.6", "@next/swc-win32-arm64-msvc": "16.1.6", "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw=="],
|
||||
|
||||
"no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="],
|
||||
|
||||
"node-abi": ["node-abi@3.87.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.47", "", {}, "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og=="],
|
||||
|
||||
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
|
||||
|
||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||
|
||||
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||
|
||||
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
@@ -371,13 +700,29 @@
|
||||
|
||||
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
|
||||
"regenerate": ["regenerate@1.4.2", "", {}, "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A=="],
|
||||
|
||||
"regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="],
|
||||
|
||||
"regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="],
|
||||
|
||||
"regjsgen": ["regjsgen@0.8.0", "", {}, "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q=="],
|
||||
|
||||
"regjsparser": ["regjsparser@0.13.2", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ=="],
|
||||
|
||||
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
|
||||
"resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="],
|
||||
|
||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
||||
|
||||
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||
|
||||
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
|
||||
|
||||
@@ -389,6 +734,8 @@
|
||||
|
||||
"simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="],
|
||||
|
||||
"snake-case": ["snake-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"stats-gl": ["stats-gl@2.4.2", "", { "dependencies": { "@types/three": "*", "three": "^0.170.0" } }, "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ=="],
|
||||
@@ -401,8 +748,14 @@
|
||||
|
||||
"styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
|
||||
"suspend-react": ["suspend-react@0.1.3", "", { "peerDependencies": { "react": ">=17.0" } }, "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ=="],
|
||||
|
||||
"svg-parser": ["svg-parser@2.0.4", "", {}, "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ=="],
|
||||
|
||||
"svgo": ["svgo@3.3.3", "", { "dependencies": { "commander": "^7.2.0", "css-select": "^5.1.0", "css-tree": "^2.3.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.0.0", "sax": "^1.5.0" }, "bin": "./bin/svgo" }, "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng=="],
|
||||
|
||||
"swr": ["swr@2.4.1", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.2.0", "", {}, "sha512-yYzTZ4++b7fNYxFfpnberEEKu43w44aqDMNM9MHMmcKuCH7lL8jJ4yJ7LGHv7rSwiqM0nkiobF9I6cLlpS2P7Q=="],
|
||||
@@ -435,6 +788,16 @@
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"unicode-canonical-property-names-ecmascript": ["unicode-canonical-property-names-ecmascript@2.0.1", "", {}, "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg=="],
|
||||
|
||||
"unicode-match-property-ecmascript": ["unicode-match-property-ecmascript@2.0.0", "", { "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" } }, "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q=="],
|
||||
|
||||
"unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="],
|
||||
|
||||
"unicode-property-aliases-ecmascript": ["unicode-property-aliases-ecmascript@2.2.0", "", {}, "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||
|
||||
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
|
||||
|
||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||
@@ -449,8 +812,44 @@
|
||||
|
||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||
|
||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"zustand": ["zustand@5.0.11", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg=="],
|
||||
|
||||
"@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
|
||||
"@babel/core/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
|
||||
|
||||
"@babel/generator/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
|
||||
|
||||
"@babel/helper-annotate-as-pure/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
|
||||
|
||||
"@babel/helper-module-imports/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
|
||||
|
||||
"@babel/helper-module-transforms/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
|
||||
"@babel/helper-optimise-call-expression/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
|
||||
|
||||
"@babel/helper-wrap-function/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
|
||||
|
||||
"@babel/helpers/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
|
||||
|
||||
"@babel/parser/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
|
||||
|
||||
"@babel/plugin-transform-modules-systemjs/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
|
||||
|
||||
"@babel/preset-modules/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
|
||||
|
||||
"@babel/template/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
|
||||
|
||||
"@babel/traverse/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
||||
@@ -465,12 +864,80 @@
|
||||
|
||||
"bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
||||
|
||||
"browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.10.38", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw=="],
|
||||
|
||||
"browserslist/caniuse-lite": ["caniuse-lite@1.0.30001799", "", {}, "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw=="],
|
||||
|
||||
"csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
|
||||
|
||||
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
|
||||
|
||||
"node-abi/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"stats-gl/three": ["three@0.170.0", "", {}, "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ=="],
|
||||
|
||||
"three-stdlib/fflate": ["fflate@0.6.10", "", {}, "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg=="],
|
||||
|
||||
"tunnel-rat/zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="],
|
||||
|
||||
"@babel/core/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||
|
||||
"@babel/core/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
|
||||
"@babel/generator/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||
|
||||
"@babel/generator/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
|
||||
"@babel/helper-annotate-as-pure/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||
|
||||
"@babel/helper-annotate-as-pure/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
|
||||
"@babel/helper-module-imports/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||
|
||||
"@babel/helper-module-imports/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
|
||||
"@babel/helper-optimise-call-expression/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||
|
||||
"@babel/helper-optimise-call-expression/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
|
||||
"@babel/helper-wrap-function/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||
|
||||
"@babel/helper-wrap-function/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
|
||||
"@babel/helpers/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||
|
||||
"@babel/helpers/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
|
||||
"@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||
|
||||
"@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
|
||||
"@babel/preset-modules/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||
|
||||
"@babel/preset-modules/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
|
||||
"@babel/template/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||
|
||||
"@babel/template/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
|
||||
"@babel/traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||
|
||||
"@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
|
||||
"csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,14 @@ const nextConfig: NextConfig = {
|
||||
output: "standalone",
|
||||
reactCompiler: true,
|
||||
poweredByHeader: false,
|
||||
turbopack: {
|
||||
rules: {
|
||||
"*.svg": {
|
||||
loaders: ["@svgr/webpack"],
|
||||
as: "*.js",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.2.0",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/node": "^20",
|
||||
|
||||
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 101 KiB |
@@ -15,8 +15,8 @@
|
||||
--color-accent-violet: var(--accent-violet);
|
||||
--font-heading: var(--font-heading);
|
||||
--font-body: var(--font-body);
|
||||
--font-cursive: var(--font-cursive);
|
||||
--font-mono: var(--font-mono);
|
||||
--breakpoint-desktop: 60rem;
|
||||
}
|
||||
|
||||
html {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Caveat, Courier_Prime, Outfit, Space_Grotesk } from "next/font/google";
|
||||
import { Courier_Prime, Outfit, Space_Grotesk } from "next/font/google";
|
||||
import type { ReactNode } from "react";
|
||||
import "./globals.css";
|
||||
import { MotionProvider } from "@/components/motion-provider";
|
||||
@@ -16,12 +16,6 @@ const outfit = Outfit({
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
const caveat = Caveat({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-cursive",
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
const courierPrime = Courier_Prime({
|
||||
weight: "400",
|
||||
subsets: ["latin"],
|
||||
@@ -41,7 +35,7 @@ export default function RootLayout({
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${spaceGrotesk.variable} ${outfit.variable} ${caveat.variable} ${courierPrime.variable} font-body antialiased`}
|
||||
className={`${spaceGrotesk.variable} ${outfit.variable} ${courierPrime.variable} font-body antialiased`}
|
||||
>
|
||||
<MotionProvider>{children}</MotionProvider>
|
||||
</body>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { About } from "@/app/sections/about";
|
||||
import { Landing } from "@/app/sections/landing";
|
||||
import { Photography } from "@/app/sections/photography";
|
||||
import { MagneticHoverShowcase } from "@/components/magnetic-hover-showcase";
|
||||
import { Projects } from "@/app/sections/projects";
|
||||
import { ScrollProgressIndicator } from "@/components/scroll-progress-indicator";
|
||||
|
||||
export default function Home() {
|
||||
@@ -14,46 +14,7 @@ export default function Home() {
|
||||
|
||||
<About />
|
||||
|
||||
<section id="projects" className="min-h-screen px-8 py-16 md:px-16 lg:px-24">
|
||||
<div className="mx-auto max-w-4xl space-y-16">
|
||||
<div className="space-y-8">
|
||||
<h2 className="font-bold font-heading text-5xl md:text-7xl">Projects</h2>
|
||||
<p className="font-body text-xl leading-relaxed md:text-2xl">
|
||||
Interactive showcase of the magnetic spring hover effect from Story 1.5.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<MagneticHoverShowcase />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="travel" className="min-h-[200vh] px-8 py-16 md:px-16 lg:px-24">
|
||||
<div className="mx-auto max-w-4xl space-y-16">
|
||||
<div className="space-y-8">
|
||||
<h2 className="font-bold font-heading text-5xl md:text-7xl">Travel</h2>
|
||||
<p className="font-body text-xl leading-relaxed md:text-2xl">
|
||||
This section is intentionally very tall to simulate the 3D passport experience that
|
||||
will be built in Epic 5. The scroll indicator correctly tracks position regardless
|
||||
of section height.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-8 md:grid-cols-2">
|
||||
{Array.from({ length: 8 }, (_, i) => (
|
||||
<div
|
||||
key={`travel-card-${i + 1}`}
|
||||
className="flex aspect-video items-center justify-center rounded-lg border border-foreground/10 bg-foreground/5"
|
||||
>
|
||||
<span className="font-heading text-2xl opacity-40">Travel Card {i + 1}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="py-32 text-center">
|
||||
<p className="font-cursive text-4xl text-accent-violet">The journey continues...</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<Projects />
|
||||
|
||||
<Photography />
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"use client";
|
||||
|
||||
import { m, type Variants } from "motion/react";
|
||||
import { useMagneticSpringHover } from "@/hooks/use-magnetic-spring-hover";
|
||||
|
||||
type Fact = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
type FactsRevealProps = {
|
||||
facts: readonly Fact[];
|
||||
};
|
||||
|
||||
const containerVariants: Variants = {
|
||||
hidden: {},
|
||||
visible: {
|
||||
transition: {
|
||||
staggerChildren: 0.08,
|
||||
delayChildren: 0.1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const itemVariants: Variants = {
|
||||
hidden: { opacity: 0, y: 16 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: { duration: 0.55, ease: [0.22, 0.61, 0.36, 1] },
|
||||
},
|
||||
};
|
||||
|
||||
const FactItem = ({ fact }: { fact: Fact }) => {
|
||||
const hover = useMagneticSpringHover<HTMLDivElement>({
|
||||
magnetStrength: 0.12,
|
||||
scaleAmount: 1.02,
|
||||
shadowElevation: 4,
|
||||
});
|
||||
|
||||
return (
|
||||
<m.div variants={itemVariants} data-scroll-animated="opacity:1;transform:translateY(0px)">
|
||||
<m.div
|
||||
ref={hover.ref}
|
||||
style={{ ...hover.style, boxShadow: "none" }}
|
||||
{...hover.handlers}
|
||||
className="cursor-default space-y-1"
|
||||
>
|
||||
<p className="font-body text-sm uppercase tracking-wider opacity-60">{fact.label}</p>
|
||||
<p className="font-body text-lg">{fact.value}</p>
|
||||
</m.div>
|
||||
</m.div>
|
||||
);
|
||||
};
|
||||
|
||||
export const FactsReveal = ({ facts }: FactsRevealProps) => {
|
||||
return (
|
||||
<m.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, amount: 0.25 }}
|
||||
className="grid gap-x-6 gap-y-18 md:grid-cols-2"
|
||||
>
|
||||
{facts.map((fact) => (
|
||||
<FactItem key={fact.label} fact={fact} />
|
||||
))}
|
||||
</m.div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
"use client";
|
||||
|
||||
import { useMotionValueEvent, useScroll } from "motion/react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { fetchFont, renderHandwriting } from "./render-handwriting";
|
||||
|
||||
type PathInfo = { el: SVGPathElement; length: number; cumStart: number };
|
||||
|
||||
type HandwrittenStatementProps = {
|
||||
text: string;
|
||||
fontUrl?: string;
|
||||
};
|
||||
|
||||
const MAX_WIDTH_PX = 420;
|
||||
const STROKE_WIDTH = 1.2;
|
||||
|
||||
export const HandwrittenStatement = ({
|
||||
text,
|
||||
fontUrl = "/fonts/shadows-into-light.json",
|
||||
}: HandwrittenStatementProps) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const pathsRef = useRef<PathInfo[]>([]);
|
||||
const totalLengthRef = useRef(0);
|
||||
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: containerRef,
|
||||
offset: ["start 80%", "start 30%"],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
let cancelled = false;
|
||||
let renderedSvg: SVGSVGElement | null = null;
|
||||
|
||||
(async () => {
|
||||
const font = await fetchFont(fontUrl);
|
||||
if (cancelled || !containerRef.current) return;
|
||||
|
||||
const svg = renderHandwriting(containerRef.current, font, text, {
|
||||
fontSize: 28,
|
||||
strokeWidth: STROKE_WIDTH,
|
||||
color: "currentColor",
|
||||
});
|
||||
renderedSvg = svg;
|
||||
|
||||
const bbox = svg.getBBox();
|
||||
const margin = STROKE_WIDTH / 2 + 1;
|
||||
const vbX = bbox.x - margin;
|
||||
const vbY = bbox.y - margin;
|
||||
const vbW = bbox.width + margin * 2;
|
||||
const vbH = bbox.height + margin * 2;
|
||||
|
||||
svg.setAttribute("width", String(Math.ceil(vbW)));
|
||||
svg.setAttribute("height", String(Math.ceil(vbH)));
|
||||
svg.setAttribute("viewBox", `${vbX} ${vbY} ${vbW} ${vbH}`);
|
||||
svg.setAttribute("preserveAspectRatio", "xMinYMin meet");
|
||||
svg.style.display = "block";
|
||||
svg.style.maxWidth = `${MAX_WIDTH_PX}px`;
|
||||
svg.style.height = "auto";
|
||||
|
||||
const paths = Array.from(svg.querySelectorAll("path")) as SVGPathElement[];
|
||||
let cum = 0;
|
||||
const info: PathInfo[] = [];
|
||||
for (const path of paths) {
|
||||
path.style.opacity = "1";
|
||||
path.setAttribute("data-scroll-animated", "stroke-dashoffset:0");
|
||||
const length = path.getTotalLength();
|
||||
info.push({ el: path, length, cumStart: cum });
|
||||
cum += length;
|
||||
}
|
||||
pathsRef.current = info;
|
||||
totalLengthRef.current = cum;
|
||||
})();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
renderedSvg?.remove();
|
||||
pathsRef.current = [];
|
||||
totalLengthRef.current = 0;
|
||||
};
|
||||
}, [text, fontUrl]);
|
||||
|
||||
useMotionValueEvent(scrollYProgress, "change", (progress) => {
|
||||
const total = totalLengthRef.current;
|
||||
if (total === 0) return;
|
||||
const inked = progress * total;
|
||||
for (const { el, length, cumStart } of pathsRef.current) {
|
||||
const drawn = Math.max(0, Math.min(length, inked - cumStart));
|
||||
el.style.strokeDashoffset = `${length + 1 - drawn}`;
|
||||
}
|
||||
});
|
||||
|
||||
return <div ref={containerRef} className="flex justify-center text-accent-cyan" />;
|
||||
};
|
||||
@@ -1,6 +1,9 @@
|
||||
import { about } from "@/content/about";
|
||||
import { BuilderTypewriter } from "./builder-typewriter";
|
||||
import { generateKeySequence } from "./builder-typewriter/sequence";
|
||||
import { FactsReveal } from "./facts-reveal";
|
||||
import { HandwrittenStatement } from "./handwritten-statement";
|
||||
import { Threshold } from "./threshold";
|
||||
|
||||
export const About = () => {
|
||||
const { prefix, suffixes } = about.builderPhrase;
|
||||
@@ -8,30 +11,19 @@ export const About = () => {
|
||||
|
||||
return (
|
||||
<section id="about" className="px-8 py-16 md:px-16 lg:px-24">
|
||||
<div className="mx-auto max-w-4xl space-y-16">
|
||||
<div className="space-y-6">
|
||||
<BuilderTypewriter prefix={prefix} suffixes={suffixes} sequence={sequence} />
|
||||
</div>
|
||||
<Threshold>
|
||||
<div className="mx-auto max-w-4xl space-y-16">
|
||||
<div className="space-y-6">
|
||||
<BuilderTypewriter prefix={prefix} suffixes={suffixes} sequence={sequence} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
{about.personalFacts.map((fact) => (
|
||||
<div key={fact.label} className="space-y-1">
|
||||
<p className="font-body text-sm uppercase tracking-wider opacity-60">
|
||||
{fact.label}
|
||||
</p>
|
||||
<p className="font-body text-lg">{fact.value}</p>
|
||||
</div>
|
||||
))}
|
||||
<FactsReveal facts={about.personalFacts} />
|
||||
|
||||
<div>
|
||||
<HandwrittenStatement text={about.transitionalStatement} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="font-cursive text-3xl text-accent-cyan md:text-4xl">
|
||||
{about.transitionalStatement}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Threshold>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Minimal renderer for vara-format stroke fonts: turns a string into an SVG of
|
||||
* cursive/handwritten stroke paths, each pre-set to be invisible (opacity 0,
|
||||
* stroke-dashoffset at full length). Lifted and trimmed from vara.js — the
|
||||
* animation/queue/multi-line systems aren't included; we drive the dashoffsets
|
||||
* ourselves from scroll progress in the consumer.
|
||||
*
|
||||
* Single line, no word-wrap, no letterSpacing, no minWidth. If the consumer
|
||||
* needs those, port them back in.
|
||||
*/
|
||||
|
||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||
|
||||
type FontPath = {
|
||||
d: string;
|
||||
mx: number;
|
||||
my: number;
|
||||
};
|
||||
|
||||
type FontCharacter = {
|
||||
paths: FontPath[];
|
||||
w: number;
|
||||
};
|
||||
|
||||
export type HandwritingFont = {
|
||||
/** Properties shared across the whole font. */
|
||||
p: {
|
||||
/** stroke-linecap (e.g. "round"). */
|
||||
slc: string;
|
||||
/** stroke-linejoin. */
|
||||
slj: string;
|
||||
/** Default stroke width (the consumer can override via options). */
|
||||
bsw: number;
|
||||
/** Line height (unused here — single-line only). */
|
||||
lh: number;
|
||||
/** Width of the space character, in font units. */
|
||||
space: number;
|
||||
/** Top-factor: used to vertically anchor the baseline. */
|
||||
tf: number;
|
||||
};
|
||||
/** Characters, keyed by charCode as a string. */
|
||||
c: Record<string, FontCharacter | undefined>;
|
||||
};
|
||||
|
||||
export type RenderHandwritingOptions = {
|
||||
fontSize: number;
|
||||
strokeWidth: number;
|
||||
color: string;
|
||||
};
|
||||
|
||||
const fontCache = new Map<string, Promise<HandwritingFont>>();
|
||||
|
||||
export const fetchFont = (url: string): Promise<HandwritingFont> => {
|
||||
const existing = fontCache.get(url);
|
||||
if (existing) return existing;
|
||||
const promise = fetch(url).then((res) => {
|
||||
if (!res.ok) throw new Error(`Failed to fetch font ${url}: ${res.status}`);
|
||||
return res.json() as Promise<HandwritingFont>;
|
||||
});
|
||||
fontCache.set(url, promise);
|
||||
return promise;
|
||||
};
|
||||
|
||||
const createNode = <K extends keyof SVGElementTagNameMap>(
|
||||
name: K,
|
||||
attrs: Record<string, string | number>,
|
||||
): SVGElementTagNameMap[K] => {
|
||||
const el = document.createElementNS(SVG_NS, name);
|
||||
for (const [key, value] of Object.entries(attrs)) {
|
||||
el.setAttributeNS(null, key, String(value));
|
||||
}
|
||||
return el;
|
||||
};
|
||||
|
||||
/** Replaces the element's transform with an absolute translate, matching vara's setPosition for absolute coordinates. */
|
||||
const setAbsoluteTranslate = (el: SVGGraphicsElement, x: number, y: number) => {
|
||||
const bbox = el.getBBox();
|
||||
el.setAttribute("transform", `translate(${x},${y - bbox.y})`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the given text using the font's stroke paths into a freshly-created
|
||||
* SVG appended to `container`. Returns the SVG element so the caller can
|
||||
* inspect bbox, harvest paths, etc.
|
||||
*/
|
||||
export const renderHandwriting = (
|
||||
container: HTMLElement,
|
||||
font: HandwritingFont,
|
||||
text: string,
|
||||
options: RenderHandwritingOptions,
|
||||
): SVGSVGElement => {
|
||||
const { fontSize, strokeWidth, color } = options;
|
||||
const { slc, slj, space: spaceWidth, tf } = font.p;
|
||||
|
||||
const svg = createNode("svg", { width: "100%" });
|
||||
container.appendChild(svg);
|
||||
|
||||
const questionMark = font.c["63"];
|
||||
const spacePaths: FontPath[] = [{ d: `M0,0 l${spaceWidth} 0`, mx: 0, my: 0 }];
|
||||
|
||||
const outerLayer = createNode("g", { class: "outer", transform: "translate(0,0)" });
|
||||
svg.appendChild(outerLayer);
|
||||
|
||||
const lineGroup = createNode("g", {});
|
||||
outerLayer.appendChild(lineGroup);
|
||||
|
||||
let cursorX = 0;
|
||||
let largestHeight = 0;
|
||||
|
||||
for (const char of text) {
|
||||
const isSpace = char === " ";
|
||||
const code = char.charCodeAt(0).toString();
|
||||
const charData = isSpace ? undefined : (font.c[code] ?? questionMark);
|
||||
const paths = isSpace ? spacePaths : (charData?.paths ?? []);
|
||||
const strokeColor = isSpace ? "transparent" : color;
|
||||
|
||||
const charGroup = createNode("g", {});
|
||||
lineGroup.appendChild(charGroup);
|
||||
|
||||
for (const pathDef of paths) {
|
||||
const path = createNode("path", {
|
||||
d: pathDef.d,
|
||||
"stroke-width": strokeWidth,
|
||||
stroke: strokeColor,
|
||||
fill: "none",
|
||||
"stroke-linecap": slc,
|
||||
"stroke-linejoin": slj,
|
||||
});
|
||||
charGroup.appendChild(path);
|
||||
path.setAttribute("transform", `translate(${pathDef.mx},${-pathDef.my})`);
|
||||
|
||||
const candidate = pathDef.my - path.getBBox().y;
|
||||
if (candidate > largestHeight) largestHeight = candidate;
|
||||
|
||||
path.style.opacity = "0";
|
||||
const length = path.getTotalLength();
|
||||
// The +1/+2 trick keeps a tiny pre-dash so round linecaps don't render a stray dot at the start.
|
||||
path.style.strokeDasharray = `${length} ${length + 2}`;
|
||||
path.style.strokeDashoffset = `${length + 1}`;
|
||||
}
|
||||
|
||||
const correction = charGroup.getBBox().x * fontSize;
|
||||
charGroup.setAttribute("transform", `translate(${cursorX - correction},0) scale(${fontSize})`);
|
||||
cursorX += charGroup.getBBox().width * fontSize;
|
||||
}
|
||||
|
||||
// Anchor the baseline. Matches vara's absolute setPosition (subtracts bbox.y to align top-of-bbox to the y target).
|
||||
setAbsoluteTranslate(lineGroup, 0, tf * fontSize - largestHeight);
|
||||
setAbsoluteTranslate(outerLayer, 0, 0);
|
||||
|
||||
return svg;
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
"use client";
|
||||
|
||||
import { m, useScroll, useTransform } from "motion/react";
|
||||
import { type ReactNode, useRef } from "react";
|
||||
|
||||
type ThresholdProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps the about content. As the section approaches the viewport, the content is
|
||||
* revealed from the centerline outward via clip-path, with two accent edges
|
||||
* marking the parting line. Uses clip-path (not opaque overlays) so it doesn't
|
||||
* cover the particles rendered behind the section.
|
||||
*/
|
||||
export const Threshold = ({ children }: ThresholdProps) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: ref,
|
||||
offset: ["start end", "start start"],
|
||||
});
|
||||
|
||||
const clipPath = useTransform(
|
||||
scrollYProgress,
|
||||
[0, 1],
|
||||
["inset(0 50% 0 50%)", "inset(0 0% 0 0%)"],
|
||||
);
|
||||
const leftEdge = useTransform(scrollYProgress, [0, 1], ["50%", "0%"]);
|
||||
const rightEdge = useTransform(scrollYProgress, [0, 1], ["50%", "100%"]);
|
||||
const edgeOpacity = useTransform(scrollYProgress, [0, 0.12, 0.88, 1], [0, 1, 1, 0]);
|
||||
|
||||
return (
|
||||
<div ref={ref} className="relative">
|
||||
<m.div style={{ clipPath }} data-scroll-animated="clip-path:inset(0 0% 0 0%)">
|
||||
{children}
|
||||
</m.div>
|
||||
<div aria-hidden className="pointer-events-none absolute inset-0">
|
||||
<m.div
|
||||
style={{ left: leftEdge, opacity: edgeOpacity }}
|
||||
data-scroll-animated="left:0%;opacity:0"
|
||||
className="absolute inset-y-0 w-px bg-accent-cyan shadow-[0_0_12px_oklch(0.75_0.14_195_/_0.6)]"
|
||||
/>
|
||||
<m.div
|
||||
style={{ left: rightEdge, opacity: edgeOpacity }}
|
||||
data-scroll-animated="left:100%;opacity:0"
|
||||
className="absolute inset-y-0 w-px bg-accent-cyan shadow-[0_0_12px_oklch(0.75_0.14_195_/_0.6)]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PhotographyClient } from "./photography-client";
|
||||
import { VoiceCta } from "./voice-cta";
|
||||
|
||||
export const Photography = () => {
|
||||
const apiUrl = process.env.RATE_MY_SHOTS_URL;
|
||||
@@ -12,6 +13,7 @@ export const Photography = () => {
|
||||
<p className="max-w-2xl font-body text-xl leading-relaxed md:text-2xl">
|
||||
Moments captured through the lens.
|
||||
</p>
|
||||
<VoiceCta url={apiUrl} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-auto mt-16 max-w-480">
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { m } from "motion/react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import useSWRInfinite from "swr/infinite";
|
||||
import ArrowUpRightIcon from "@/components/icons/arrow-up-right.svg";
|
||||
import { useMagneticSpringHover } from "@/hooks/use-magnetic-spring-hover";
|
||||
import { PhotoTile } from "./photo-tile";
|
||||
import type { PhotoItem } from "./types";
|
||||
@@ -188,9 +189,10 @@ export const PhotographyClient = ({ apiUrl }: PhotographyClientProps) => {
|
||||
href={`${apiUrl}/gallery`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-block rounded-lg border border-accent-cyan/40 px-6 py-3 font-body text-accent-cyan text-base transition-colors hover:border-accent-cyan/70 hover:bg-accent-cyan/10"
|
||||
className="inline-flex cursor-pointer items-center gap-3 rounded-full border border-foreground/15 bg-foreground/3 px-6 py-3 font-heading text-lg backdrop-blur-sm"
|
||||
>
|
||||
See the rest of the portfolio
|
||||
<span>See the rest of the portfolio</span>
|
||||
<ArrowUpRightIcon className="size-5 text-foreground/60" />
|
||||
</m.a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import { m } from "motion/react";
|
||||
import MegaphoneIcon from "@/components/icons/megaphone.svg";
|
||||
import { useMagneticSpringHover } from "@/hooks/use-magnetic-spring-hover";
|
||||
|
||||
type VoiceCtaProps = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
export const VoiceCta = ({ url }: VoiceCtaProps) => {
|
||||
const hover = useMagneticSpringHover<HTMLAnchorElement>({
|
||||
magnetStrength: 0.2,
|
||||
scaleAmount: 1.04,
|
||||
shadowElevation: 16,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
<span className="font-body text-foreground/45 text-sm">Wanna help?</span>
|
||||
<m.a
|
||||
ref={hover.ref}
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
style={hover.style}
|
||||
{...hover.handlers}
|
||||
className="inline-flex cursor-pointer items-center gap-3 rounded-full border border-foreground/15 bg-foreground/3 px-6 py-3 font-heading text-lg backdrop-blur-sm"
|
||||
>
|
||||
<MegaphoneIcon className="size-5" />
|
||||
<span>Make your voice heard</span>
|
||||
</m.a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
"use client";
|
||||
|
||||
import { type MotionValue, m, useTransform } from "motion/react";
|
||||
import { PHASES } from "./lifecycle";
|
||||
|
||||
type DescriptionRevealProps = {
|
||||
text: string;
|
||||
localProgress: MotionValue<number>;
|
||||
};
|
||||
|
||||
export const DescriptionReveal = ({ text, localProgress }: DescriptionRevealProps) => {
|
||||
const { enterStart, enterEnd, exitStart, exitEnd } = PHASES.description;
|
||||
const opacity = useTransform(
|
||||
localProgress,
|
||||
[enterStart, enterEnd, exitStart, exitEnd],
|
||||
[0, 1, 1, 0],
|
||||
);
|
||||
const y = useTransform(
|
||||
localProgress,
|
||||
[enterStart, enterEnd, exitStart, exitEnd],
|
||||
[28, 0, 0, -16],
|
||||
);
|
||||
const filter = useTransform(
|
||||
localProgress,
|
||||
[enterStart, enterEnd, exitStart, exitEnd],
|
||||
["blur(10px)", "blur(0px)", "blur(0px)", "blur(8px)"],
|
||||
);
|
||||
|
||||
return (
|
||||
<m.p
|
||||
style={{ opacity, y, filter }}
|
||||
className="max-w-xl font-body desktop:text-lg text-base text-foreground/70 leading-relaxed"
|
||||
>
|
||||
{text}
|
||||
</m.p>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
"use client";
|
||||
|
||||
import type { MotionValue } from "motion/react";
|
||||
import { HookWord } from "./hook-word";
|
||||
|
||||
type HookRevealProps = {
|
||||
text: string;
|
||||
localProgress: MotionValue<number>;
|
||||
};
|
||||
|
||||
type Token = { kind: "word"; text: string; wordIndex: number } | { kind: "space"; text: string };
|
||||
|
||||
const tokenize = (text: string): { tokens: Token[]; wordCount: number } => {
|
||||
const tokens: Token[] = [];
|
||||
let wordIndex = 0;
|
||||
for (const match of text.match(/\S+|\s+/g) ?? []) {
|
||||
if (/^\s/.test(match)) {
|
||||
tokens.push({ kind: "space", text: match });
|
||||
} else {
|
||||
tokens.push({ kind: "word", text: match, wordIndex });
|
||||
wordIndex += 1;
|
||||
}
|
||||
}
|
||||
return { tokens, wordCount: wordIndex };
|
||||
};
|
||||
|
||||
export const HookReveal = ({ text, localProgress }: HookRevealProps) => {
|
||||
const { tokens, wordCount } = tokenize(text);
|
||||
|
||||
return (
|
||||
<h3 className="font-heading font-semibold desktop:text-5xl text-4xl leading-[1.1] lg:text-6xl">
|
||||
{tokens.map((token, i) => {
|
||||
if (token.kind === "space") {
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: token positions are stable for a given hook string
|
||||
return <span key={i}>{token.text}</span>;
|
||||
}
|
||||
return (
|
||||
<HookWord
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: token positions are stable for a given hook string
|
||||
key={i}
|
||||
word={token.text}
|
||||
index={token.wordIndex}
|
||||
total={wordCount}
|
||||
localProgress={localProgress}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</h3>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
"use client";
|
||||
|
||||
import { easeIn, easeOut, type MotionValue, m, useTransform } from "motion/react";
|
||||
import { linear, PHASES, stagger } from "./lifecycle";
|
||||
|
||||
type HookWordProps = {
|
||||
word: string;
|
||||
index: number;
|
||||
total: number;
|
||||
localProgress: MotionValue<number>;
|
||||
};
|
||||
|
||||
export const HookWord = ({ word, index, total, localProgress }: HookWordProps) => {
|
||||
const phase = stagger(PHASES.hook, index, total, 0.55, true);
|
||||
const { enterStart, enterEnd, exitStart, exitEnd } = phase;
|
||||
|
||||
const exitX = index % 2 === 0 ? -48 : 48;
|
||||
const exitY = index % 2 === 0 ? -56 : 44;
|
||||
const exitRotate = index % 2 === 0 ? -10 : 10;
|
||||
|
||||
const opacity = useTransform(
|
||||
localProgress,
|
||||
[enterStart, enterEnd, exitStart, exitEnd],
|
||||
[0, 1, 1, 0],
|
||||
{ ease: [easeOut, linear, easeIn] },
|
||||
);
|
||||
const y = useTransform(
|
||||
localProgress,
|
||||
[enterStart, enterEnd, exitStart, exitEnd],
|
||||
[88, 0, 0, exitY],
|
||||
{ ease: [easeOut, linear, easeIn] },
|
||||
);
|
||||
const x = useTransform(
|
||||
localProgress,
|
||||
[enterStart, enterEnd, exitStart, exitEnd],
|
||||
[0, 0, 0, exitX],
|
||||
{ ease: [easeOut, linear, easeIn] },
|
||||
);
|
||||
const rotate = useTransform(
|
||||
localProgress,
|
||||
[enterStart, enterEnd, exitStart, exitEnd],
|
||||
[4, 0, 0, exitRotate],
|
||||
{ ease: [easeOut, linear, easeIn] },
|
||||
);
|
||||
|
||||
return (
|
||||
<m.span style={{ opacity, x, y, rotate }} className="inline-block">
|
||||
{word}
|
||||
</m.span>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import { giteaProfileUrl, githubProfileUrl, projects } from "@/content/projects";
|
||||
import { Outro } from "./outro";
|
||||
import { ProjectsStage } from "./projects-stage";
|
||||
|
||||
export const Projects = () => {
|
||||
return (
|
||||
<section id="projects" className="relative">
|
||||
<ProjectsStage projects={projects} />
|
||||
<Outro profileUrls={[githubProfileUrl, giteaProfileUrl]} />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
export type Phase = {
|
||||
enterStart: number;
|
||||
enterEnd: number;
|
||||
exitStart: number;
|
||||
exitEnd: number;
|
||||
};
|
||||
|
||||
export const PHASES = {
|
||||
placard: { enterStart: 0.0, enterEnd: 0.08, exitStart: 0.92, exitEnd: 1.0 },
|
||||
hook: { enterStart: 0.0, enterEnd: 0.16, exitStart: 0.84, exitEnd: 1.0 },
|
||||
description: { enterStart: 0.08, enterEnd: 0.24, exitStart: 0.78, exitEnd: 0.94 },
|
||||
visual: { enterStart: 0.02, enterEnd: 0.22, exitStart: 0.82, exitEnd: 1.0 },
|
||||
techKeys: { enterStart: 0.12, enterEnd: 0.34, exitStart: 0.66, exitEnd: 0.9 },
|
||||
links: { enterStart: 0.18, enterEnd: 0.36, exitStart: 0.64, exitEnd: 0.82 },
|
||||
} satisfies Record<string, Phase>;
|
||||
|
||||
export const VISUAL_BODY = { start: 0.22, end: 0.82 } as const;
|
||||
|
||||
const allPhases = Object.values(PHASES);
|
||||
/** Highest enterEnd across all elements — the moment everything has finished entering. */
|
||||
export const MAX_ENTER_END = Math.max(...allPhases.map((p) => p.enterEnd));
|
||||
/** Lowest exitStart across all elements — the moment the first element starts exiting. */
|
||||
export const MIN_EXIT_START = Math.min(...allPhases.map((p) => p.exitStart));
|
||||
|
||||
export const linear = (t: number) => t;
|
||||
|
||||
export const stagger = (
|
||||
phase: Phase,
|
||||
index: number,
|
||||
total: number,
|
||||
staggerFraction = 0.5,
|
||||
reverseExit = false,
|
||||
): Phase => {
|
||||
if (total <= 1) return phase;
|
||||
const enterWindow = phase.enterEnd - phase.enterStart;
|
||||
const exitWindow = phase.exitEnd - phase.exitStart;
|
||||
const enterPer = enterWindow * (1 - staggerFraction);
|
||||
const exitPer = exitWindow * (1 - staggerFraction);
|
||||
const enterStep = (enterWindow * staggerFraction) / (total - 1);
|
||||
const exitStep = (exitWindow * staggerFraction) / (total - 1);
|
||||
const exitIndex = reverseExit ? total - 1 - index : index;
|
||||
return {
|
||||
enterStart: phase.enterStart + index * enterStep,
|
||||
enterEnd: phase.enterStart + index * enterStep + enterPer,
|
||||
exitStart: phase.exitStart + exitIndex * exitStep,
|
||||
exitEnd: phase.exitStart + exitIndex * exitStep + exitPer,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
"use client";
|
||||
|
||||
import { easeIn, easeOut, type MotionValue, m, useTransform } from "motion/react";
|
||||
import type { ReactNode } from "react";
|
||||
import type { Project } from "@/content/projects";
|
||||
import { linear, PHASES, stagger } from "./lifecycle";
|
||||
import { LiveLink, SourceLink } from "./source-link";
|
||||
|
||||
type LinksRowProps = {
|
||||
project: Project;
|
||||
localProgress: MotionValue<number>;
|
||||
sideSign: -1 | 1;
|
||||
};
|
||||
|
||||
type LinkWrapperProps = {
|
||||
index: number;
|
||||
total: number;
|
||||
localProgress: MotionValue<number>;
|
||||
sideSign: -1 | 1;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const LinkWrapper = ({ index, total, localProgress, sideSign, children }: LinkWrapperProps) => {
|
||||
const phase = stagger(PHASES.links, index, total, 0.5, true);
|
||||
const { enterStart, enterEnd, exitStart, exitEnd } = phase;
|
||||
const offEdge = -40 * sideSign;
|
||||
|
||||
const opacity = useTransform(
|
||||
localProgress,
|
||||
[enterStart, enterEnd, exitStart, exitEnd],
|
||||
[0, 1, 1, 0],
|
||||
{ ease: [easeOut, linear, easeIn] },
|
||||
);
|
||||
const x = useTransform(
|
||||
localProgress,
|
||||
[enterStart, enterEnd, exitStart, exitEnd],
|
||||
[offEdge, 0, 0, offEdge],
|
||||
{ ease: [easeOut, linear, easeIn] },
|
||||
);
|
||||
|
||||
return (
|
||||
<m.div style={{ opacity, x }} className="inline-flex">
|
||||
{children}
|
||||
</m.div>
|
||||
);
|
||||
};
|
||||
|
||||
export const LinksRow = ({ project, localProgress, sideSign }: LinksRowProps) => {
|
||||
type Item = { key: string; node: ReactNode };
|
||||
const items: Item[] = [];
|
||||
if (project.liveUrl) {
|
||||
items.push({ key: "live", node: <LiveLink url={project.liveUrl} /> });
|
||||
}
|
||||
for (const source of project.sources ?? []) {
|
||||
items.push({ key: source.url, node: <SourceLink source={source} /> });
|
||||
}
|
||||
|
||||
if (items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-x-6 gap-y-3 pt-4">
|
||||
{items.map((item, i) => (
|
||||
<LinkWrapper
|
||||
key={item.key}
|
||||
index={i}
|
||||
total={items.length}
|
||||
localProgress={localProgress}
|
||||
sideSign={sideSign}
|
||||
>
|
||||
{item.node}
|
||||
</LinkWrapper>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import { m } from "motion/react";
|
||||
import ArrowUpRight from "@/components/icons/arrow-up-right.svg";
|
||||
import { useMagneticSpringHover } from "@/hooks/use-magnetic-spring-hover";
|
||||
import { resolveHost } from "@/lib/host-icons";
|
||||
|
||||
type ProfileLinkProps = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
const ProfileLink = ({ url }: ProfileLinkProps) => {
|
||||
const hover = useMagneticSpringHover<HTMLAnchorElement>({
|
||||
magnetStrength: 0.25,
|
||||
scaleAmount: 1.04,
|
||||
shadowElevation: 12,
|
||||
});
|
||||
const { Icon, name } = resolveHost(url);
|
||||
|
||||
return (
|
||||
<m.a
|
||||
ref={hover.ref}
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
style={hover.style}
|
||||
{...hover.handlers}
|
||||
className="inline-flex cursor-pointer items-center gap-3 rounded-full border border-foreground/15 bg-foreground/3 px-6 py-3 font-heading text-lg backdrop-blur-sm"
|
||||
>
|
||||
<Icon className="size-5" />
|
||||
<span>{name}</span>
|
||||
<ArrowUpRight className="size-4 text-foreground/60" />
|
||||
</m.a>
|
||||
);
|
||||
};
|
||||
|
||||
type OutroProps = {
|
||||
profileUrls: string[];
|
||||
};
|
||||
|
||||
export const Outro = ({ profileUrls }: OutroProps) => {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-4 py-32">
|
||||
<div className="flex flex-col items-center gap-1 text-center">
|
||||
<p className="text-foreground text-xl">{"But wait, there's more!"}</p>
|
||||
<p className="text-foreground/45 text-sm">
|
||||
{"It's just that this was getting a little long"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-wrap items-center justify-center gap-4">
|
||||
{profileUrls.map((url) => (
|
||||
<ProfileLink key={url} url={url} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
"use client";
|
||||
|
||||
import { type MotionValue, m, useTransform } from "motion/react";
|
||||
import { PHASES } from "./lifecycle";
|
||||
|
||||
type PlacardProps = {
|
||||
index: number;
|
||||
total: number;
|
||||
localProgress: MotionValue<number>;
|
||||
};
|
||||
|
||||
export const Placard = ({ index, total, localProgress }: PlacardProps) => {
|
||||
const { enterStart, enterEnd, exitStart, exitEnd } = PHASES.placard;
|
||||
const opacity = useTransform(
|
||||
localProgress,
|
||||
[enterStart, enterEnd, exitStart, exitEnd],
|
||||
[0, 1, 1, 0],
|
||||
);
|
||||
const y = useTransform(
|
||||
localProgress,
|
||||
[enterStart, enterEnd, exitStart, exitEnd],
|
||||
[-12, 0, 0, 12],
|
||||
);
|
||||
|
||||
return (
|
||||
<m.div style={{ opacity, y }} className="flex items-center gap-3">
|
||||
<span className="font-mono text-[oklch(var(--project-accent))] text-xs tracking-widest">
|
||||
{String(index + 1).padStart(2, "0")} / {String(total).padStart(2, "0")}
|
||||
</span>
|
||||
</m.div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
"use client";
|
||||
|
||||
import { type MotionValue, m, useMotionValueEvent, useTransform } from "motion/react";
|
||||
import { type CSSProperties, useState } from "react";
|
||||
import type { Project } from "@/content/projects";
|
||||
import { DescriptionReveal } from "./description-reveal";
|
||||
import { HookReveal } from "./hook-reveal";
|
||||
import { LinksRow } from "./links-row";
|
||||
import { Placard } from "./placard";
|
||||
import { TechKeyDeck } from "./tech-key-deck";
|
||||
import { VisualShowcase } from "./visual-showcase";
|
||||
|
||||
type ProjectLayerProps = {
|
||||
project: Project;
|
||||
index: number;
|
||||
total: number;
|
||||
inputRange: number[];
|
||||
outputRange: number[];
|
||||
sectionProgress: MotionValue<number>;
|
||||
};
|
||||
|
||||
export const ProjectLayer = ({
|
||||
project,
|
||||
index,
|
||||
total,
|
||||
inputRange,
|
||||
outputRange,
|
||||
sectionProgress,
|
||||
}: ProjectLayerProps) => {
|
||||
const localProgress = useTransform(sectionProgress, inputRange, outputRange, {
|
||||
clamp: true,
|
||||
});
|
||||
|
||||
const sectionStart = inputRange[0];
|
||||
const sectionEnd = inputRange[inputRange.length - 1];
|
||||
|
||||
const [active, setActive] = useState(false);
|
||||
useMotionValueEvent(sectionProgress, "change", (p) => {
|
||||
const next = p >= sectionStart - 0.005 && p <= sectionEnd + 0.005;
|
||||
setActive((prev) => (prev === next ? prev : next));
|
||||
});
|
||||
|
||||
const visualOnLeft = index % 2 === 1;
|
||||
const contentSideSign: -1 | 1 = visualOnLeft ? 1 : -1;
|
||||
const visualSideSign: -1 | 1 = visualOnLeft ? -1 : 1;
|
||||
|
||||
const layerStyle = {
|
||||
"--project-accent": project.accent,
|
||||
pointerEvents: active ? "auto" : "none",
|
||||
} as CSSProperties;
|
||||
|
||||
return (
|
||||
<m.div
|
||||
style={layerStyle}
|
||||
className="absolute inset-0 flex items-center desktop:px-16 px-8 lg:px-24"
|
||||
>
|
||||
<div className="mx-auto grid w-full max-w-7xl desktop:grid-cols-2 grid-cols-1 items-center desktop:gap-12 gap-10 lg:gap-20">
|
||||
<div className={visualOnLeft ? "desktop:order-2" : "desktop:order-1"}>
|
||||
<div className="space-y-6">
|
||||
<Placard index={index} total={total} localProgress={localProgress} />
|
||||
<HookReveal text={project.hook} localProgress={localProgress} />
|
||||
<DescriptionReveal text={project.description} localProgress={localProgress} />
|
||||
<TechKeyDeck techStack={project.techStack} localProgress={localProgress} />
|
||||
<LinksRow project={project} localProgress={localProgress} sideSign={contentSideSign} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={visualOnLeft ? "desktop:order-1" : "desktop:order-2"}>
|
||||
<VisualShowcase
|
||||
media={project.media}
|
||||
localProgress={localProgress}
|
||||
priority={index === 0}
|
||||
sideSign={visualSideSign}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</m.div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,113 @@
|
||||
"use client";
|
||||
|
||||
import { useScroll } from "motion/react";
|
||||
import { useMemo, useRef } from "react";
|
||||
import type { Project } from "@/content/projects";
|
||||
import { MAX_ENTER_END, MIN_EXIT_START } from "./lifecycle";
|
||||
import { ProjectLayer } from "./project-layer";
|
||||
|
||||
const BASE_VH = 160;
|
||||
const EXTRA_VH = 80;
|
||||
const OVERLAP_VH = 40;
|
||||
/**
|
||||
* Pre-/post-engagement zones, in vh. Equal to one viewport (sticky engages when
|
||||
* section.top reaches viewport.top, disengages when section.bottom reaches viewport.bottom).
|
||||
* Used to allocate scroll space for the first project's entrance and the last project's exit
|
||||
* so that their per-element choreography plays exactly across the section's approach/departure.
|
||||
*/
|
||||
const PRE_BUFFER_VH = 150;
|
||||
const POST_BUFFER_VH = 150;
|
||||
|
||||
type ProjectsStageProps = {
|
||||
projects: readonly Project[];
|
||||
};
|
||||
|
||||
type LayerRange = {
|
||||
inputRange: number[];
|
||||
outputRange: number[];
|
||||
};
|
||||
|
||||
type Layout = {
|
||||
layers: LayerRange[];
|
||||
sectionVh: number;
|
||||
};
|
||||
|
||||
const computeLayout = (projects: readonly Project[]): Layout => {
|
||||
const lastIndex = projects.length - 1;
|
||||
const weights = projects.map((p, i) => {
|
||||
let w = BASE_VH + (p.media.length - 1) * EXTRA_VH;
|
||||
if (i === 0) w += PRE_BUFFER_VH;
|
||||
if (i === lastIndex) w += POST_BUFFER_VH;
|
||||
return w;
|
||||
});
|
||||
|
||||
const rawRanges: { start: number; end: number }[] = [];
|
||||
let cursor = 0;
|
||||
for (let i = 0; i < weights.length; i++) {
|
||||
if (i > 0) cursor -= OVERLAP_VH;
|
||||
rawRanges.push({ start: cursor, end: cursor + weights[i] });
|
||||
cursor += weights[i];
|
||||
}
|
||||
|
||||
// Total widened scroll = pre + content + post = section + viewport
|
||||
const totalWidenedVh = cursor;
|
||||
const sectionVh = totalWidenedVh - 100;
|
||||
const stickyEngageProgress = PRE_BUFFER_VH / totalWidenedVh;
|
||||
const stickyDisengageProgress = (totalWidenedVh - POST_BUFFER_VH) / totalWidenedVh;
|
||||
|
||||
const layers: LayerRange[] = rawRanges.map((r, i) => {
|
||||
const startProgress = r.start / totalWidenedVh;
|
||||
const endProgress = r.end / totalWidenedVh;
|
||||
|
||||
if (i === 0) {
|
||||
return {
|
||||
inputRange: [startProgress, stickyEngageProgress, endProgress],
|
||||
outputRange: [0, MAX_ENTER_END, 1],
|
||||
};
|
||||
}
|
||||
if (i === lastIndex) {
|
||||
return {
|
||||
inputRange: [startProgress, stickyDisengageProgress, endProgress],
|
||||
outputRange: [0, MIN_EXIT_START, 1],
|
||||
};
|
||||
}
|
||||
return {
|
||||
inputRange: [startProgress, endProgress],
|
||||
outputRange: [0, 1],
|
||||
};
|
||||
});
|
||||
|
||||
return { layers, sectionVh };
|
||||
};
|
||||
|
||||
export const ProjectsStage = ({ projects }: ProjectsStageProps) => {
|
||||
const { layers, sectionVh } = useMemo(() => computeLayout(projects), [projects]);
|
||||
const sectionRef = useRef<HTMLDivElement>(null);
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: sectionRef,
|
||||
offset: ["start end", "end start"],
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={sectionRef}
|
||||
className="relative"
|
||||
style={{ height: `${sectionVh}vh` }}
|
||||
data-projects-stage
|
||||
>
|
||||
<div className="sticky top-0 h-screen overflow-hidden">
|
||||
{projects.map((project, i) => (
|
||||
<ProjectLayer
|
||||
key={project.name}
|
||||
project={project}
|
||||
index={i}
|
||||
total={projects.length}
|
||||
inputRange={layers[i].inputRange}
|
||||
outputRange={layers[i].outputRange}
|
||||
sectionProgress={scrollYProgress}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
"use client";
|
||||
|
||||
import ArrowUpRight from "@/components/icons/arrow-up-right.svg";
|
||||
import type { ProjectSource } from "@/content/projects";
|
||||
import { resolveHost } from "@/lib/host-icons";
|
||||
|
||||
type SourceLinkProps = {
|
||||
source: ProjectSource;
|
||||
};
|
||||
|
||||
export const SourceLink = ({ source }: SourceLinkProps) => {
|
||||
const { Icon, name } = resolveHost(source.url);
|
||||
const label = source.label ?? name;
|
||||
|
||||
return (
|
||||
<a
|
||||
href={source.url}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="group inline-flex items-center gap-1.5 font-body text-foreground/70 text-sm transition-colors duration-200 hover:text-[oklch(var(--project-accent))]"
|
||||
>
|
||||
<Icon className="size-4 shrink-0" />
|
||||
<span>{label}</span>
|
||||
<ArrowUpRight className="group-hover:-translate-y-0.5 ml-0.5 inline-block size-3.5 transition-transform duration-200 group-hover:translate-x-0.5" />
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
type LiveLinkProps = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
export const LiveLink = ({ url }: LiveLinkProps) => (
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="group inline-flex items-center gap-1.5 font-body font-medium text-[oklch(var(--project-accent))] text-sm transition-opacity hover:opacity-80"
|
||||
>
|
||||
<span className="size-1.5 rounded-full bg-[oklch(var(--project-accent))] shadow-[0_0_8px_oklch(var(--project-accent)/0.8)]" />
|
||||
<span>Live</span>
|
||||
<ArrowUpRight className="group-hover:-translate-y-0.5 inline-block size-3.5 transition-transform duration-200 group-hover:translate-x-0.5" />
|
||||
</a>
|
||||
);
|
||||
@@ -0,0 +1,63 @@
|
||||
"use client";
|
||||
|
||||
import { backOut, easeIn, easeOut, type MotionValue, m, useTransform } from "motion/react";
|
||||
import { linear, PHASES, stagger } from "./lifecycle";
|
||||
import { TechKey } from "./tech-key";
|
||||
|
||||
type TechKeyDeckProps = {
|
||||
techStack: readonly string[];
|
||||
localProgress: MotionValue<number>;
|
||||
};
|
||||
|
||||
type TechKeyWrapperProps = {
|
||||
techKey: string;
|
||||
index: number;
|
||||
total: number;
|
||||
localProgress: MotionValue<number>;
|
||||
};
|
||||
|
||||
const TechKeyWrapper = ({ techKey, index, total, localProgress }: TechKeyWrapperProps) => {
|
||||
const phase = stagger(PHASES.techKeys, index, total, 0.6, true);
|
||||
const { enterStart, enterEnd, exitStart, exitEnd } = phase;
|
||||
|
||||
const opacity = useTransform(
|
||||
localProgress,
|
||||
[enterStart, enterEnd, exitStart, exitEnd],
|
||||
[0, 1, 1, 0],
|
||||
{ ease: [easeOut, linear, easeIn] },
|
||||
);
|
||||
const y = useTransform(
|
||||
localProgress,
|
||||
[enterStart, enterEnd, exitStart, exitEnd],
|
||||
[-140, 0, 0, 140],
|
||||
{ ease: [backOut, linear, easeIn] },
|
||||
);
|
||||
const rotate = useTransform(
|
||||
localProgress,
|
||||
[enterStart, enterEnd, exitStart, exitEnd],
|
||||
[index % 2 === 0 ? -18 : 18, 0, 0, index % 2 === 0 ? -32 : 32],
|
||||
{ ease: [backOut, linear, easeIn] },
|
||||
);
|
||||
|
||||
return (
|
||||
<m.div style={{ opacity, y, rotate }} className="inline-block">
|
||||
<TechKey techKey={techKey} />
|
||||
</m.div>
|
||||
);
|
||||
};
|
||||
|
||||
export const TechKeyDeck = ({ techStack, localProgress }: TechKeyDeckProps) => {
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-2 pt-2">
|
||||
{techStack.map((tech, i) => (
|
||||
<TechKeyWrapper
|
||||
key={tech}
|
||||
techKey={tech}
|
||||
index={i}
|
||||
total={techStack.length}
|
||||
localProgress={localProgress}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
"use client";
|
||||
|
||||
import { m } from "motion/react";
|
||||
import { springSnappy } from "@/lib/motion";
|
||||
import { getTechEntry } from "@/lib/tech-registry";
|
||||
|
||||
type TechKeyProps = {
|
||||
techKey: string;
|
||||
};
|
||||
|
||||
export const TechKey = ({ techKey }: TechKeyProps) => {
|
||||
const { label, url } = getTechEntry(techKey);
|
||||
const Wrapper = url ? m.a : m.div;
|
||||
|
||||
return (
|
||||
<Wrapper
|
||||
href={url}
|
||||
target={url ? "_blank" : undefined}
|
||||
rel={url ? "noreferrer noopener" : undefined}
|
||||
whileHover="hovered"
|
||||
whileTap="pressed"
|
||||
transition={springSnappy}
|
||||
className="group relative inline-block select-none pb-1.5"
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className="absolute inset-0 top-1.5 rounded-md bg-foreground/10 ring-1 ring-foreground/15 ring-inset transition-colors duration-200 group-hover:bg-[oklch(var(--project-accent)/0.18)] group-hover:ring-[oklch(var(--project-accent)/0.45)] group-active:bg-[oklch(var(--project-accent)/0.28)] group-active:ring-[oklch(var(--project-accent)/0.6)]"
|
||||
/>
|
||||
<m.span
|
||||
initial={{ y: 0 }}
|
||||
variants={{ hovered: { y: 3 }, pressed: { y: 5 } }}
|
||||
className="relative block rounded-md border border-foreground/25 bg-background px-3 py-1.5 font-mono text-foreground/80 text-xs leading-none tracking-wide shadow-[inset_0_1px_0_0_oklch(0.94_0.02_90/0.05)] transition-colors duration-200 group-hover:border-[oklch(var(--project-accent)/0.55)] group-hover:text-[oklch(var(--project-accent))] group-active:border-[oklch(var(--project-accent)/0.8)] group-active:text-[oklch(var(--project-accent))]"
|
||||
>
|
||||
{label}
|
||||
</m.span>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,217 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
AnimatePresence,
|
||||
easeIn,
|
||||
easeOut,
|
||||
type MotionValue,
|
||||
m,
|
||||
useMotionValueEvent,
|
||||
useTransform,
|
||||
} from "motion/react";
|
||||
import Image from "next/image";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { ProjectMedia } from "@/content/projects";
|
||||
import { linear, PHASES, VISUAL_BODY } from "./lifecycle";
|
||||
|
||||
type ScaledIframeProps = {
|
||||
src: string;
|
||||
alt: string;
|
||||
simulatedWidth: number;
|
||||
aspectRatio?: number;
|
||||
};
|
||||
|
||||
const ScaledIframe = ({ src, alt, simulatedWidth, aspectRatio = 16 / 10 }: ScaledIframeProps) => {
|
||||
const wrapperRef = useRef<HTMLAnchorElement>(null);
|
||||
const [scale, setScale] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const el = wrapperRef.current;
|
||||
if (!el) return;
|
||||
const update = () => setScale(el.offsetWidth / simulatedWidth);
|
||||
update();
|
||||
const observer = new ResizeObserver(update);
|
||||
observer.observe(el);
|
||||
return () => observer.disconnect();
|
||||
}, [simulatedWidth]);
|
||||
|
||||
const simulatedHeight = simulatedWidth / aspectRatio;
|
||||
|
||||
return (
|
||||
<a
|
||||
ref={wrapperRef}
|
||||
href={src}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
aria-label={alt}
|
||||
className="absolute inset-0 block overflow-hidden"
|
||||
>
|
||||
<iframe
|
||||
src={src}
|
||||
title={alt}
|
||||
loading="lazy"
|
||||
tabIndex={-1}
|
||||
className="pointer-events-none origin-top-left border-0 bg-background"
|
||||
style={{
|
||||
width: `${simulatedWidth}px`,
|
||||
height: `${simulatedHeight}px`,
|
||||
transform: `scale(${scale})`,
|
||||
}}
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
type VisualShowcaseProps = {
|
||||
media: readonly ProjectMedia[];
|
||||
localProgress: MotionValue<number>;
|
||||
priority?: boolean;
|
||||
sideSign: -1 | 1;
|
||||
};
|
||||
|
||||
const renderMedia = (item: ProjectMedia, priority: boolean) => {
|
||||
switch (item.type) {
|
||||
case "image":
|
||||
return (
|
||||
<Image
|
||||
src={item.src}
|
||||
alt={item.alt}
|
||||
fill
|
||||
sizes="(min-width: 768px) 50vw, 100vw"
|
||||
priority={priority}
|
||||
className="object-cover"
|
||||
/>
|
||||
);
|
||||
case "video":
|
||||
return (
|
||||
<video
|
||||
src={item.src}
|
||||
poster={item.poster}
|
||||
aria-label={item.alt}
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
className="size-full object-cover"
|
||||
/>
|
||||
);
|
||||
case "iframe":
|
||||
if (item.simulatedWidth) {
|
||||
return (
|
||||
<ScaledIframe
|
||||
src={item.src}
|
||||
alt={item.alt}
|
||||
simulatedWidth={item.simulatedWidth}
|
||||
aspectRatio={item.aspectRatio}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<a
|
||||
href={item.src}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
aria-label={item.alt}
|
||||
className="block size-full"
|
||||
>
|
||||
<iframe
|
||||
src={item.src}
|
||||
title={item.alt}
|
||||
loading="lazy"
|
||||
tabIndex={-1}
|
||||
className="pointer-events-none size-full border-0 bg-background"
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const VisualShowcase = ({
|
||||
media,
|
||||
localProgress,
|
||||
priority = false,
|
||||
sideSign,
|
||||
}: VisualShowcaseProps) => {
|
||||
const bodyProgress = useTransform(localProgress, [VISUAL_BODY.start, VISUAL_BODY.end], [0, 1], {
|
||||
clamp: true,
|
||||
});
|
||||
|
||||
const [index, setIndex] = useState(0);
|
||||
|
||||
useMotionValueEvent(bodyProgress, "change", (value) => {
|
||||
if (media.length <= 1) return;
|
||||
const next = Math.max(0, Math.min(Math.floor(value * media.length), media.length - 1));
|
||||
setIndex((prev) => (prev === next ? prev : next));
|
||||
});
|
||||
|
||||
const { enterStart, enterEnd, exitStart, exitEnd } = PHASES.visual;
|
||||
|
||||
const opacity = useTransform(
|
||||
localProgress,
|
||||
[enterStart, enterEnd, exitStart, exitEnd],
|
||||
[0, 1, 1, 0],
|
||||
{ ease: [easeOut, linear, easeIn] },
|
||||
);
|
||||
const scale = useTransform(
|
||||
localProgress,
|
||||
[enterStart, enterEnd, exitStart, exitEnd],
|
||||
[0.88, 1, 1, 0.84],
|
||||
{ ease: [easeOut, linear, easeIn] },
|
||||
);
|
||||
const rotate = useTransform(
|
||||
localProgress,
|
||||
[enterStart, enterEnd, exitStart, exitEnd],
|
||||
[sideSign * -3, 0, 0, sideSign * 3],
|
||||
{ ease: [easeOut, linear, easeIn] },
|
||||
);
|
||||
const filter = useTransform(localProgress, [exitStart, exitEnd], ["blur(0px)", "blur(10px)"], {
|
||||
clamp: true,
|
||||
});
|
||||
const enterClip = useTransform(localProgress, [enterStart, enterEnd], [0, 110], { clamp: true });
|
||||
const clipPath = useTransform(enterClip, (v) => `circle(${v}% at 50% 50%)`);
|
||||
|
||||
const item = media[index];
|
||||
if (!item) return null;
|
||||
|
||||
return (
|
||||
<m.div style={{ opacity, scale, rotate, filter }} className="relative w-full">
|
||||
<m.div
|
||||
style={{ clipPath }}
|
||||
className="relative aspect-16/10 w-full overflow-hidden rounded-lg bg-foreground/4 ring-1 ring-foreground/10 ring-inset"
|
||||
>
|
||||
<AnimatePresence mode="sync">
|
||||
<m.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, scale: 1.03 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.98 }}
|
||||
transition={{ duration: 0.55, ease: [0.22, 0.61, 0.36, 1] }}
|
||||
className="absolute inset-0"
|
||||
>
|
||||
{renderMedia(item, priority)}
|
||||
</m.div>
|
||||
</AnimatePresence>
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-0 rounded-lg ring-1 ring-[oklch(var(--project-accent)/0.25)] ring-inset"
|
||||
/>
|
||||
</m.div>
|
||||
|
||||
{media.length > 1 ? (
|
||||
<div className="mt-4 flex items-center gap-1.5">
|
||||
{media.map((_, i) => (
|
||||
<span
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: media list is static and positional
|
||||
key={i}
|
||||
className={
|
||||
i === index
|
||||
? "h-0.5 w-8 bg-[oklch(var(--project-accent))] transition-all duration-500"
|
||||
: "h-0.5 w-4 bg-foreground/20 transition-all duration-500"
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</m.div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M7 7h10v10" />
|
||||
<path d="M7 17 17 7" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 240 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M5 6h11v6a5 5 0 0 1-5 5H10a5 5 0 0 1-5-5V6Z" />
|
||||
<path d="M16 8h2a2.5 2.5 0 0 1 0 5h-2" />
|
||||
<path d="M8 3v2M11 3v2M14 3v2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 327 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 788 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="m3 11 18-5v12L3 14v-3z" />
|
||||
<path d="M11.6 16.8a3 3 0 1 1-5.8-1.6" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 270 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="m9 8-5 4 5 4" />
|
||||
<path d="m15 8 5 4-5 4" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 245 B |
@@ -24,24 +24,28 @@ export const about = {
|
||||
},
|
||||
personalFacts: [
|
||||
{
|
||||
label: "Location",
|
||||
value: "Viana do Castelo, Portugal",
|
||||
label: "Based in",
|
||||
value: "Viana do Castelo, Portugal. Looking to relocate.",
|
||||
},
|
||||
{
|
||||
label: "Homelab",
|
||||
value: "15+ services on hardware I control",
|
||||
label: "Internationally known",
|
||||
value: "Volunteered in rural Bulgaria, until COVID interrupted me",
|
||||
},
|
||||
{
|
||||
label: "Side project",
|
||||
value: "Cataloguing my father's coin collection in 3D",
|
||||
},
|
||||
{
|
||||
label: "Non-profit",
|
||||
label: "NGO",
|
||||
value: "President of a youth organization",
|
||||
},
|
||||
{
|
||||
label: "Soapbox",
|
||||
value: "Cities would be better with fewer cars and more transit",
|
||||
},
|
||||
{
|
||||
label: "Languages",
|
||||
value: "Portuguese (native), English (fluent), Dutch (WIP)",
|
||||
value: "🇵🇹 Portuguese (native), 🇬🇧 English (fluent), 🇳🇱 Dutch (learning)",
|
||||
},
|
||||
{
|
||||
label: "Off the clock",
|
||||
value: "Board games, hiking, photography",
|
||||
},
|
||||
],
|
||||
transitionalStatement: "Easier to show than tell.",
|
||||
|
||||
@@ -16,7 +16,7 @@ export const footer = {
|
||||
},
|
||||
{
|
||||
platform: "Gitea",
|
||||
url: "https://gitea.bate-estacas.xyz/luis",
|
||||
url: "https://gitea.luisdralves.dev/luis",
|
||||
},
|
||||
{
|
||||
platform: "LinkedIn",
|
||||
|
||||
@@ -1,64 +1,107 @@
|
||||
export type ProjectMedia =
|
||||
| { type: "image"; src: string; alt: string }
|
||||
| { type: "video"; src: string; poster?: string; alt: string }
|
||||
| {
|
||||
type: "iframe";
|
||||
src: string;
|
||||
alt: string;
|
||||
aspectRatio?: number;
|
||||
/**
|
||||
* When set, render the iframe at this CSS pixel width (with height derived from
|
||||
* aspectRatio, defaulting to 16/10 to match the showcase frame) and scale it down via
|
||||
* a CSS transform so the embedded page sees a "desktop-sized" viewport.
|
||||
*/
|
||||
simulatedWidth?: number;
|
||||
};
|
||||
|
||||
export type ProjectSource = {
|
||||
url: string;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
export type Project = {
|
||||
name: string;
|
||||
hook: string;
|
||||
description: string;
|
||||
techStack: string[];
|
||||
liveUrl?: string;
|
||||
giteaUrl: string;
|
||||
images: {
|
||||
src: string;
|
||||
alt: string;
|
||||
}[];
|
||||
sources?: ProjectSource[];
|
||||
media: ProjectMedia[];
|
||||
/** OKLCH "L C H" triplet (space-separated, no parentheses). Bound as --project-accent. */
|
||||
accent: string;
|
||||
};
|
||||
|
||||
export const giteaProfileUrl = "https://gitea.luisdralves.dev/luis" as const;
|
||||
export const githubProfileUrl = "https://github.com/luisdralves" as const;
|
||||
|
||||
export const projects = [
|
||||
{
|
||||
name: "clocks",
|
||||
hook: "Time doesn't have to be a circle.",
|
||||
description:
|
||||
"A collection of alternative clocks that challenge how we visualize time. Varying degrees of practicality, but consistent subversion. The screenshots don't do them justice, see them in action.",
|
||||
techStack: ["TypeScript", "Vite", "GLSL"],
|
||||
liveUrl: "https://clocks.bate-estacas.xyz",
|
||||
giteaUrl: "https://gitea.bate-estacas.xyz/luis/clocks",
|
||||
images: [
|
||||
{ src: "/images/projects/clocks/wall.png", alt: "Wall o'Clocks" },
|
||||
{ src: "/images/projects/clocks/spiral.png", alt: "Spiral Clock" },
|
||||
{ src: "/images/projects/clocks/lemniscate.png", alt: "Lemniscate Clock" },
|
||||
"A collection of alternative clocks that challenge how we visualize time. Some are practical, some aren't, but they all toy with convention.",
|
||||
techStack: ["TypeScript", "Vite", "WebGL", "GLSL"],
|
||||
liveUrl: "https://clocks.luisdralves.dev",
|
||||
sources: [{ url: "https://gitea.luisdralves.dev/luis/clocks" }],
|
||||
media: [
|
||||
{
|
||||
type: "iframe",
|
||||
src: "https://clocks.luisdralves.dev/wall-o-clocks/",
|
||||
alt: "Wall o'Clocks",
|
||||
},
|
||||
{ type: "iframe", src: "https://clocks.luisdralves.dev/spiral/", alt: "Spiral Clock" },
|
||||
{
|
||||
type: "iframe",
|
||||
src: "https://clocks.luisdralves.dev/precalculated-grid/",
|
||||
alt: "Precalculated Grid",
|
||||
},
|
||||
],
|
||||
accent: "0.75 0.14 340",
|
||||
},
|
||||
{
|
||||
name: "drosterizer",
|
||||
hook: "Now everyone can have stylish profile pics like me",
|
||||
hook: "Now everyone can have a cool profile pic like me",
|
||||
description:
|
||||
'Add recursion to your photos! Simply draw a "hole" around an object or region and watch your photo fold in on itself, infinitely. All processing happens locally via WebGL shaders, nothing leaves your browser.',
|
||||
techStack: ["React", "TypeScript", "WebGL", "GLSL"],
|
||||
liveUrl: "https://drosterizer.bate-estacas.xyz",
|
||||
giteaUrl: "https://gitea.bate-estacas.xyz/luis/drosterizer",
|
||||
images: [
|
||||
{ src: "/images/projects/drosterizer/mask.png", alt: "Drawing a mask on a photo" },
|
||||
{ src: "/images/projects/drosterizer/effect.png", alt: "The resulting effect" },
|
||||
{ src: "/images/projects/drosterizer/params.png", alt: "Playing around with the parameters" },
|
||||
liveUrl: "https://drosterizer.luisdralves.dev",
|
||||
sources: [{ url: "https://gitea.luisdralves.dev/luis/drosterizer" }],
|
||||
media: [
|
||||
{
|
||||
type: "image",
|
||||
src: "/images/projects/drosterizer/mask.png",
|
||||
alt: "Drawing a mask on a photo",
|
||||
},
|
||||
{
|
||||
type: "image",
|
||||
src: "/images/projects/drosterizer/effect.png",
|
||||
alt: "The resulting effect",
|
||||
},
|
||||
],
|
||||
accent: "0.58 0.19 300",
|
||||
},
|
||||
{
|
||||
name: "todo3",
|
||||
hook: "TUI aesthetics and real-time",
|
||||
name: "coins-catalogue",
|
||||
hook: "A physical collection, rendered in physics.",
|
||||
description:
|
||||
"Just another todo app. This one mimics a terminal interface and is collaborative in real-time. Everything over WebSockets, even auth.",
|
||||
techStack: ["React", "Elysia", "Bun", "SQLite", "WebSockets"],
|
||||
liveUrl: "https://todo3.bate-estacas.xyz",
|
||||
giteaUrl: "https://gitea.bate-estacas.xyz/luis/todo3",
|
||||
images: [{ src: "/images/projects/todo3/lists.png", alt: "Todo lists with items" }],
|
||||
},
|
||||
{
|
||||
name: "sysmon-web",
|
||||
hook: "System monitor on the web",
|
||||
description:
|
||||
"A system monitor that reads metrics with Rust and renders them on HTML canvas. CPU, memory, disk, network, all updating in real-time with a persistent window so that the charts are populated on first load. This one uses simple polling instead of WebSockets or SSEs though.",
|
||||
techStack: ["TypeScript", "Rust", "React", "Canvas"],
|
||||
liveUrl: "https://sysmon.bate-estacas.xyz",
|
||||
giteaUrl: "https://gitea.bate-estacas.xyz/luis/sysmon-web",
|
||||
images: [{ src: "/images/projects/sysmon/dashboard.png", alt: "sysmon-web dashboard" }],
|
||||
"Cataloguing my father's coin collection in 3D, built from Numista data and assets. Browse by country, inspect coins in a showcase, or watch a set tumble into a pile.",
|
||||
techStack: ["React", "TypeScript", "Three.js", "R3F"],
|
||||
liveUrl: "https://coins.luisdralves.dev",
|
||||
sources: [{ url: "https://gitea.luisdralves.dev/luis/coins" }],
|
||||
media: [
|
||||
{
|
||||
type: "iframe",
|
||||
src: "https://coins.luisdralves.dev/",
|
||||
alt: "Index by country, with coins falling in the background",
|
||||
simulatedWidth: 1920,
|
||||
},
|
||||
{
|
||||
type: "iframe",
|
||||
src: "https://coins.luisdralves.dev/pile?countries=royaume-uni",
|
||||
alt: "A pile of coins on a surface",
|
||||
},
|
||||
],
|
||||
accent: "0.78 0.13 90",
|
||||
},
|
||||
{
|
||||
name: "places-ive-been-in",
|
||||
@@ -66,17 +109,66 @@ export const projects = [
|
||||
description:
|
||||
"I've been known to indulge in retrospection and melancholy from time to time. I like to keep track of where I've been and look back.",
|
||||
techStack: ["React", "TypeScript", "Mapbox", "Vite"],
|
||||
liveUrl: "https://places-ive-been-in.bate-estacas.xyz",
|
||||
giteaUrl: "https://gitea.bate-estacas.xyz/luis/places-ive-been-in",
|
||||
images: [
|
||||
liveUrl: "https://places-ive-been-in.luisdralves.dev",
|
||||
sources: [{ url: "https://gitea.luisdralves.dev/luis/places-ive-been-in" }],
|
||||
media: [
|
||||
{
|
||||
type: "image",
|
||||
src: "/images/projects/places-ive-been-in/overview.png",
|
||||
alt: "Locations without clustering",
|
||||
},
|
||||
{
|
||||
type: "image",
|
||||
src: "/images/projects/places-ive-been-in/carousel.png",
|
||||
alt: "The carousel on a location",
|
||||
},
|
||||
],
|
||||
accent: "0.70 0.16 246",
|
||||
},
|
||||
{
|
||||
name: "todo3",
|
||||
hook: "TUI aesthetics and real-time",
|
||||
description:
|
||||
"Just another todo app. This one mimics a terminal interface and is collaborative in real-time. Everything over WebSockets, even auth.",
|
||||
techStack: ["React", "Elysia", "Bun", "SQLite", "WebSockets"],
|
||||
liveUrl: "https://todo3.luisdralves.dev",
|
||||
sources: [{ url: "https://gitea.luisdralves.dev/luis/todo3" }],
|
||||
media: [
|
||||
{ type: "image", src: "/images/projects/todo3/lists.png", alt: "Todo lists with items" },
|
||||
],
|
||||
accent: "0.78 0.18 145",
|
||||
},
|
||||
{
|
||||
name: "sysmon-web",
|
||||
hook: "System monitor on the web",
|
||||
description:
|
||||
"A system monitor that reads metrics with Rust and renders them on HTML canvas. CPU, memory, disk, network, all updating in real-time with a persistent window so that the charts are populated on first load. This one uses simple polling instead of WebSockets or SSEs though.",
|
||||
techStack: ["TypeScript", "Rust", "React", "Canvas"],
|
||||
liveUrl: "https://sysmon.luisdralves.dev",
|
||||
sources: [{ url: "https://gitea.luisdralves.dev/luis/sysmon-web" }],
|
||||
media: [
|
||||
{
|
||||
type: "iframe",
|
||||
src: "https://sysmon.luisdralves.dev",
|
||||
alt: "sysmon-web dashboard",
|
||||
simulatedWidth: 1920,
|
||||
},
|
||||
],
|
||||
accent: "0.72 0.10 219",
|
||||
},
|
||||
{
|
||||
name: "homelab",
|
||||
hook: "It outgrew its own case.",
|
||||
description:
|
||||
"15+ services on hardware I own, behind a reverse proxy. Email, photos, code, media, dashboards, personal projects. A repurposed gaming desktop that ran out of room in two different ways: no free space left on its disks and no free bays left inside the case. Shown here mid-upgrade. Don't worry about the cables, they don't look like this now :)",
|
||||
techStack: ["Linux", "Docker", "Caddy"],
|
||||
media: [
|
||||
{
|
||||
type: "image",
|
||||
src: "/images/projects/homelab/dissection.jpg",
|
||||
alt: "The server with its side panel off, an external drive cage wired in beside it",
|
||||
},
|
||||
],
|
||||
accent: "0.65 0.18 18",
|
||||
},
|
||||
] as const satisfies Project[];
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
export type TravelEntry = {
|
||||
location: string;
|
||||
country: string;
|
||||
date: string; // YYYY-MM-DD
|
||||
photos: {
|
||||
src: string;
|
||||
alt: string;
|
||||
width: number; // 0-1, fraction of page width
|
||||
}[];
|
||||
journalEntry: string;
|
||||
coordinates: {
|
||||
lat: number;
|
||||
lng: number;
|
||||
};
|
||||
};
|
||||
|
||||
export const travel = [
|
||||
{
|
||||
location: "Kyoto",
|
||||
country: "Japan",
|
||||
date: "2024-03-15",
|
||||
photos: [
|
||||
{ src: "/images/travel/kyoto-1.jpg", alt: "Fushimi Inari shrine gates", width: 1 },
|
||||
{ src: "/images/travel/kyoto-2.jpg", alt: "Bamboo grove at Arashiyama", width: 0.5 },
|
||||
],
|
||||
journalEntry:
|
||||
"Wandered through thousands of vermillion torii gates at dawn. The silence was almost sacred.",
|
||||
coordinates: {
|
||||
lat: 35.0116,
|
||||
lng: 135.7681,
|
||||
},
|
||||
},
|
||||
{
|
||||
location: "Lisbon",
|
||||
country: "Portugal",
|
||||
date: "2023-09-10",
|
||||
photos: [
|
||||
{ src: "/images/travel/lisbon-1.jpg", alt: "Tram 28 climbing a hill", width: 0.5 },
|
||||
{ src: "/images/travel/lisbon-2.jpg", alt: "Sunset over the Tagus river", width: 0.5 },
|
||||
],
|
||||
journalEntry:
|
||||
"Got lost in Alfama's labyrinth of streets. Found the best pastéis de nata in a bakery with no name.",
|
||||
coordinates: {
|
||||
lat: 38.7223,
|
||||
lng: -9.1393,
|
||||
},
|
||||
},
|
||||
{
|
||||
location: "Reykjavik",
|
||||
country: "Iceland",
|
||||
date: "2023-01-20",
|
||||
photos: [
|
||||
{ src: "/images/travel/iceland-1.jpg", alt: "Northern lights over a frozen lake", width: 1 },
|
||||
],
|
||||
journalEntry: "Stayed up until 3am chasing the aurora. Worth every sleepless moment.",
|
||||
coordinates: {
|
||||
lat: 64.1466,
|
||||
lng: -21.9426,
|
||||
},
|
||||
},
|
||||
] as const satisfies TravelEntry[];
|
||||
@@ -2,14 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const SECTION_IDS = [
|
||||
"landing",
|
||||
"about",
|
||||
"projects",
|
||||
"travel",
|
||||
"photography",
|
||||
"footer",
|
||||
] as const;
|
||||
export const SECTION_IDS = ["landing", "about", "projects", "photography", "footer"] as const;
|
||||
|
||||
export type SectionId = (typeof SECTION_IDS)[number];
|
||||
|
||||
@@ -17,7 +10,6 @@ export const SECTION_LABELS: Record<SectionId, string> = {
|
||||
landing: "Landing",
|
||||
about: "About",
|
||||
projects: "Projects",
|
||||
travel: "Travel",
|
||||
photography: "Photography",
|
||||
footer: "Footer",
|
||||
};
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { FC, SVGProps } from "react";
|
||||
import GiteaIcon from "@/components/icons/gitea.svg";
|
||||
import GitHubIcon from "@/components/icons/github.svg";
|
||||
import SourceIcon from "@/components/icons/source.svg";
|
||||
|
||||
type IconProps = SVGProps<SVGSVGElement>;
|
||||
|
||||
const hostRules: { match: (hostname: string) => boolean; name: string; Icon: FC<IconProps> }[] = [
|
||||
{
|
||||
match: (h) => h === "github.com" || h.endsWith(".github.com"),
|
||||
name: "GitHub",
|
||||
Icon: GitHubIcon,
|
||||
},
|
||||
{ match: (h) => h.includes("gitea"), name: "Gitea", Icon: GiteaIcon },
|
||||
];
|
||||
|
||||
export type HostInfo = {
|
||||
name: string;
|
||||
Icon: FC<IconProps>;
|
||||
};
|
||||
|
||||
export const resolveHost = (url: string): HostInfo => {
|
||||
let hostname: string;
|
||||
try {
|
||||
hostname = new URL(url).hostname.toLowerCase();
|
||||
} catch {
|
||||
return { name: "Source", Icon: SourceIcon };
|
||||
}
|
||||
const rule = hostRules.find((r) => r.match(hostname));
|
||||
return rule ? { name: rule.name, Icon: rule.Icon } : { name: "Source", Icon: SourceIcon };
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
export type TechEntry = {
|
||||
label: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export const techRegistry: Record<string, TechEntry> = {
|
||||
TypeScript: { label: "TypeScript", url: "https://www.typescriptlang.org/" },
|
||||
React: { label: "React", url: "https://react.dev/" },
|
||||
Vite: { label: "Vite", url: "https://vitejs.dev/" },
|
||||
Bun: { label: "Bun", url: "https://bun.sh/" },
|
||||
Elysia: { label: "Elysia", url: "https://elysiajs.com/" },
|
||||
Rust: { label: "Rust", url: "https://www.rust-lang.org/" },
|
||||
GLSL: {
|
||||
label: "GLSL",
|
||||
url: "https://www.khronos.org/opengl/wiki/Core_Language_(GLSL)",
|
||||
},
|
||||
WebGL: { label: "WebGL", url: "https://www.khronos.org/webgl/" },
|
||||
WebSockets: {
|
||||
label: "WebSockets",
|
||||
url: "https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API",
|
||||
},
|
||||
Canvas: {
|
||||
label: "Canvas",
|
||||
url: "https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API",
|
||||
},
|
||||
SQLite: { label: "SQLite", url: "https://www.sqlite.org/" },
|
||||
Mapbox: { label: "Mapbox", url: "https://www.mapbox.com/" },
|
||||
"Three.js": { label: "Three.js", url: "https://threejs.org/" },
|
||||
R3F: { label: "R3F", url: "https://r3f.docs.pmnd.rs/" },
|
||||
Docker: { label: "Docker", url: "https://www.docker.com/" },
|
||||
Caddy: { label: "Caddy", url: "https://caddyserver.com/" },
|
||||
Linux: { label: "Linux", url: "https://www.kernel.org/" },
|
||||
};
|
||||
|
||||
export const getTechEntry = (key: string): { label: string; url?: string } =>
|
||||
techRegistry[key] ?? { label: key };
|
||||
@@ -0,0 +1,6 @@
|
||||
declare module "*.svg" {
|
||||
import type { FC, SVGProps } from "react";
|
||||
|
||||
const content: FC<SVGProps<SVGSVGElement>>;
|
||||
export default content;
|
||||
}
|
||||