diff --git a/jsrepo-build-config.json b/jsrepo-build-config.json deleted file mode 100644 index 57e1c62d..00000000 --- a/jsrepo-build-config.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "https://unpkg.com/jsrepo@1.30.1/schemas/registry-config.json", - "meta": { - "authors": ["David Haz"], - "description": "An open source collection of animated, interactive & fully customizable React components for building stunning, memorable user interfaces.", - "bugs": "https://github.com/DavidHDev/react-bits/issues", - "homepage": "https://reactbits.dev", - "repository": "https://github.com/DavidHDev/react-bits", - "tags": [ - "react", - "javascript", - "components", - "web", - "reactjs", - "css-animations", - "component-library", - "ui-components", - "3d", - "ui-library", - "tailwind", - "tailwindcss", - "components", - "components-library" - ] - }, - "dirs": [], - "doNotListBlocks": [], - "doNotListCategories": [], - "listBlocks": [], - "listCategories": [], - "excludeDeps": ["react"], - "includeBlocks": [], - "includeCategories": [], - "excludeBlocks": [], - "excludeCategories": [], - "preview": true -} diff --git a/jsrepo.config.ts b/jsrepo.config.ts new file mode 100644 index 00000000..15689980 --- /dev/null +++ b/jsrepo.config.ts @@ -0,0 +1,137 @@ +import { defineConfig, RegistryItem } from 'jsrepo'; +import { distributed } from 'jsrepo/outputs'; +import { output as shadcn } from '@jsrepo/shadcn/output'; +import fs from 'node:fs'; +import path from 'node:path'; +import { z } from 'zod'; + +export default defineConfig({ + registry: [ + ({ cwd }) => ({ + name: '@react-bits', + authors: ['David Haz'], + description: + 'An open source collection of animated, interactive & fully customizable React components for building stunning, memorable user interfaces.', + bugs: 'https://github.com/DavidHDev/react-bits/issues', + homepage: 'https://reactbits.dev', + repository: 'https://github.com/DavidHDev/react-bits', + tags: [ + 'react', + 'javascript', + 'components', + 'web', + 'reactjs', + 'css-animations', + 'component-library', + 'ui-components', + '3d', + 'ui-library', + 'tailwind', + 'tailwindcss', + 'components', + 'components-library' + ], + excludeDeps: ['react'], + items: [ + ...getItems({ dir: path.join(cwd, 'src/ts-default'), cwd }), + ...getItems({ dir: path.join(cwd, 'src/ts-tailwind'), cwd, suffix: 'tw' }) + ], + outputs: [shadcn({ dir: 'public/r' }), distributed({ dir: 'public/jsrepo' })] + }) + ] +}); + +const metaSchema = z.object({ + description: z.string().optional() +}); + +function getItems({ dir, cwd, suffix }: { dir: string; cwd: string; suffix?: string }): RegistryItem[] { + const types = fs.readdirSync(dir); + + return types.flatMap(typeDir => { + const items = fs.readdirSync(path.join(dir, typeDir)); + + return items.map(item => { + const files = fs.readdirSync(path.join(dir, typeDir, item)); + let description: string | undefined; + if (files.includes('meta.json')) { + const meta = metaSchema.parse(JSON.parse(fs.readFileSync(path.join(dir, typeDir, item, 'meta.json'), 'utf-8'))); + description = meta.description; + } + return { + name: `${pascalToKebab(item)}${suffix ? `-${suffix}` : ''}`, + title: item, + description, + type: 'registry:block', + files: files + .filter(f => f !== 'meta.json') + .map(file => { + return { + path: path.relative(cwd, path.join(dir, typeDir, item, file)), + // prevent warnings + dependencyResolution: endsWithOneOf(file, ['.glb', '.png']) ? 'manual' : 'auto' + }; + }) + } satisfies RegistryItem; + }); + }); +} + +/** Converts a `PascalCase` string to a `kebab-case` string + * + * @param str + * @returns + * + * ## Usage + * ```ts + * camelToSnake('HelloWorld'); // hello-world + * ``` + */ +export function pascalToKebab(str: string): string { + let newStr = ''; + + for (let i = 0; i < str.length; i++) { + // is uppercase letter (ignoring the first) + if (i > 0 && isLetter(str[i]) && str[i].toUpperCase() === str[i]) { + let l = i; + + while (l < str.length && isLetter(str[l]) && str[l].toUpperCase() === str[l]) { + l++; + } + + newStr += `${str.slice(i, l - 1).toLocaleLowerCase()}-${str[l - 1].toLocaleLowerCase()}`; + + i = l - 1; + + continue; + } + + newStr += str[i].toLocaleLowerCase(); + } + + return newStr; +} + +const LETTER_REGEX = new RegExp(/[a-zA-Z]/); + +/** Checks if the provided character is a letter in the alphabet. + * + * @param char + * @returns + * + * ## Usage + * ```ts + * isLetter('a'); + * ``` + */ +function isLetter(char: string): boolean { + if (char.length > 1) { + throw new Error(`You probably only meant to pass a character to this function. Instead you gave ${char}`); + } + + return LETTER_REGEX.test(char); +} + +function endsWithOneOf(str: string, endings: string[]): boolean { + return endings.some(ending => str.endsWith(ending)); +} diff --git a/package-lock.json b/package-lock.json index 6a195599..37d2546c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ "three": "^0.167.1" }, "devDependencies": { + "@jsrepo/shadcn": "^0.0.1-beta.8", "@types/matter-js": "^0.19.8", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", @@ -57,13 +58,14 @@ "eslint-plugin-react": "^7.34.3", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.7", - "jsrepo": "^1.30.1", + "jsrepo": "^3.0.0-beta.8", "postcss-safe-parser": "^7.0.1", "prettier": "^3.6.2", "shadcn": "^3.1.0", "tw-animate-css": "^1.4.0", "typescript": "^5.7.3", - "vite": "^5.3.4" + "vite": "^5.3.4", + "zod": "^4.1.12" } }, "node_modules/@antfu/ni": { @@ -88,43 +90,6 @@ "nup": "bin/nup.mjs" } }, - "node_modules/@anthropic-ai/sdk": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.39.0.tgz", - "integrity": "sha512-eMyDIPRZbt1CCLErRCi3exlAvNkBtRe+kW5vvJyef93PmNr/clstYgHhtvmkxN82nlKgzyGPCyGxrm0JQ1ZIdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - } - }, - "node_modules/@anthropic-ai/sdk/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/@ark-ui/react": { "version": "5.25.0", "resolved": "https://registry.npmjs.org/@ark-ui/react/-/react-5.25.0.tgz", @@ -668,36 +633,6 @@ "node": ">=6.9.0" } }, - "node_modules/@biomejs/js-api": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@biomejs/js-api/-/js-api-0.7.1.tgz", - "integrity": "sha512-VFdgFFZWcyCQxZcAasyv8Enpexn4CblMdWmr6izLYHTLcbd+z9x/LuKU71qnmClABfnYqZjiY7c8DKTVri3Ajw==", - "dev": true, - "license": "MIT OR Apache-2.0", - "peerDependencies": { - "@biomejs/wasm-bundler": "^1.9.2", - "@biomejs/wasm-nodejs": "^1.9.2", - "@biomejs/wasm-web": "^1.9.2" - }, - "peerDependenciesMeta": { - "@biomejs/wasm-bundler": { - "optional": true - }, - "@biomejs/wasm-nodejs": { - "optional": true - }, - "@biomejs/wasm-web": { - "optional": true - } - } - }, - "node_modules/@biomejs/wasm-nodejs": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/wasm-nodejs/-/wasm-nodejs-1.9.4.tgz", - "integrity": "sha512-ZqNlhKcZW6MW1LxWIOfh9YVrBykvzyFad3bOh6JJFraDnNa3NXboRDiaI8dmrbb0ZHXCU1Tsq6WQsKV2Vpp5dw==", - "dev": true, - "license": "MIT OR Apache-2.0" - }, "node_modules/@bundled-es-modules/cookie": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz", @@ -749,29 +684,6 @@ "react-dom": ">=18" } }, - "node_modules/@clack/core": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.4.2.tgz", - "integrity": "sha512-NYQfcEy8MWIxrT5Fj8nIVchfRFA26yYKJcvBS7WlUIlw2OmQOY9DhGGXMovyI5J5PpxrCPGkgUi207EBrjpBvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "picocolors": "^1.0.0", - "sisteransi": "^1.0.5" - } - }, - "node_modules/@clack/prompts": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.10.1.tgz", - "integrity": "sha512-Q0T02vx8ZM9XSv9/Yde0jTmmBQufZhPJfYAg2XrrrxWWaZgq1rr8nU8Hv710BQ1dhoP8rtY7YUdpGej2Qza/cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@clack/core": "0.4.2", - "picocolors": "^1.0.0", - "sisteransi": "^1.0.5" - } - }, "node_modules/@dimforge/rapier3d-compat": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.15.0.tgz", @@ -1840,6 +1752,16 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsrepo/shadcn": { + "version": "0.0.1-beta.8", + "resolved": "https://registry.npmjs.org/@jsrepo/shadcn/-/shadcn-0.0.1-beta.8.tgz", + "integrity": "sha512-SFaEk8etDbp8KMGXhtZpHwiAGKKM+UDCRxzFHual28LAkbK+duumWE5ljWRiWmViHp63K3anrJf00kIO1u60ng==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "jsrepo": "3.0.0-beta.8" + } + }, "node_modules/@mediapipe/tasks-vision": { "version": "0.10.17", "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz", @@ -1870,6 +1792,26 @@ "node": ">=18" } }, + "node_modules/@modelcontextprotocol/sdk/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + }, "node_modules/@monogrid/gainmap-js": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.1.0.tgz", @@ -1992,372 +1934,6 @@ "node": ">= 8" } }, - "node_modules/@octokit/app": { - "version": "15.1.6", - "resolved": "https://registry.npmjs.org/@octokit/app/-/app-15.1.6.tgz", - "integrity": "sha512-WELCamoCJo9SN0lf3SWZccf68CF0sBNPQuLYmZ/n87p5qvBJDe9aBtr5dHkh7T9nxWZ608pizwsUbypSzZAiUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/auth-app": "^7.2.1", - "@octokit/auth-unauthenticated": "^6.1.3", - "@octokit/core": "^6.1.5", - "@octokit/oauth-app": "^7.1.6", - "@octokit/plugin-paginate-rest": "^12.0.0", - "@octokit/types": "^14.0.0", - "@octokit/webhooks": "^13.6.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-app": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-7.2.2.tgz", - "integrity": "sha512-p6hJtEyQDCJEPN9ijjhEC/kpFHMHN4Gca9r+8S0S8EJi7NaWftaEmexjxxpT1DFBeJpN4u/5RE22ArnyypupJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-app": "^8.1.4", - "@octokit/auth-oauth-user": "^5.1.4", - "@octokit/request": "^9.2.3", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "toad-cache": "^3.7.0", - "universal-github-app-jwt": "^2.2.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-oauth-app": { - "version": "8.1.4", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.4.tgz", - "integrity": "sha512-71iBa5SflSXcclk/OL3lJzdt4iFs56OJdpBGEBl1wULp7C58uiswZLV6TdRaiAzHP1LT8ezpbHlKuxADb+4NkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-device": "^7.1.5", - "@octokit/auth-oauth-user": "^5.1.4", - "@octokit/request": "^9.2.3", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-oauth-device": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.5.tgz", - "integrity": "sha512-lR00+k7+N6xeECj0JuXeULQ2TSBB/zjTAmNF2+vyGPDEFx1dgk1hTDmL13MjbSmzusuAmuJD8Pu39rjp9jH6yw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/oauth-methods": "^5.1.5", - "@octokit/request": "^9.2.3", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-oauth-user": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.6.tgz", - "integrity": "sha512-/R8vgeoulp7rJs+wfJ2LtXEVC7pjQTIqDab7wPKwVG6+2v/lUnCOub6vaHmysQBbb45FknM3tbHW8TOVqYHxCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-device": "^7.1.5", - "@octokit/oauth-methods": "^5.1.5", - "@octokit/request": "^9.2.3", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-token": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz", - "integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-unauthenticated": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-6.1.3.tgz", - "integrity": "sha512-d5gWJla3WdSl1yjbfMpET+hUSFCE15qM0KVSB0H1shyuJihf/RL1KqWoZMIaonHvlNojkL9XtLFp8QeLe+1iwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/core": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.6.tgz", - "integrity": "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^5.0.0", - "@octokit/graphql": "^8.2.2", - "@octokit/request": "^9.2.3", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "before-after-hook": "^3.0.2", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/endpoint": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", - "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/graphql": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.2.tgz", - "integrity": "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/request": "^9.2.3", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/oauth-app": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-7.1.6.tgz", - "integrity": "sha512-OMcMzY2WFARg80oJNFwWbY51TBUfLH4JGTy119cqiDawSFXSIBujxmpXiKbGWQlvfn0CxE6f7/+c6+Kr5hI2YA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-app": "^8.1.3", - "@octokit/auth-oauth-user": "^5.1.3", - "@octokit/auth-unauthenticated": "^6.1.2", - "@octokit/core": "^6.1.4", - "@octokit/oauth-authorization-url": "^7.1.1", - "@octokit/oauth-methods": "^5.1.4", - "@types/aws-lambda": "^8.10.83", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/oauth-authorization-url": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz", - "integrity": "sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/oauth-methods": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-5.1.5.tgz", - "integrity": "sha512-Ev7K8bkYrYLhoOSZGVAGsLEscZQyq7XQONCBBAl2JdMg7IT3PQn/y8P0KjloPoYpI5UylqYrLeUcScaYWXwDvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/oauth-authorization-url": "^7.0.0", - "@octokit/request": "^9.2.3", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/openapi-types": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", - "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@octokit/openapi-webhooks-types": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-11.0.0.tgz", - "integrity": "sha512-ZBzCFj98v3SuRM7oBas6BHZMJRadlnDoeFfvm1olVxZnYeU6Vh97FhPxyS5aLh5pN51GYv2I51l/hVUAVkGBlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-graphql": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-5.2.4.tgz", - "integrity": "sha512-pLZES1jWaOynXKHOqdnwZ5ULeVR6tVVCMm+AUbp0htdcyXDU95WbkYdU4R2ej1wKj5Tu94Mee2Ne0PjPO9cCyA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-12.0.0.tgz", - "integrity": "sha512-MPd6WK1VtZ52lFrgZ0R2FlaoiWllzgqFHaSZxvp72NmoDeZ0m8GeJdg4oB6ctqMTYyrnDYp592Xma21mrgiyDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-14.0.0.tgz", - "integrity": "sha512-iQt6ovem4b7zZYZQtdv+PwgbL5VPq37th1m2x2TdkgimIDJpsi2A6Q/OI/23i/hR6z5mL0EgisNR4dcbmckSZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-retry": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.2.1.tgz", - "integrity": "sha512-wUc3gv0D6vNHpGxSaR3FlqJpTXGWgqmk607N9L3LvPL4QjaxDgX/1nY2mGpT37Khn+nlIXdljczkRnNdTTV3/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "bottleneck": "^2.15.3" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-throttling": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-10.0.0.tgz", - "integrity": "sha512-Kuq5/qs0DVYTHZuBAzCZStCzo2nKvVRo/TDNhCcpC2TKiOGz/DisXMCvjt3/b5kr6SCI1Y8eeeJTHBxxpFvZEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0", - "bottleneck": "^2.15.3" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": "^6.1.3" - } - }, - "node_modules/@octokit/request": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.4.tgz", - "integrity": "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^10.1.4", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "fast-content-type-parse": "^2.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/request-error": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", - "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/types": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", - "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^25.1.0" - } - }, - "node_modules/@octokit/webhooks": { - "version": "13.9.1", - "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-13.9.1.tgz", - "integrity": "sha512-Nss2b4Jyn4wB3EAqAPJypGuCJFalz/ZujKBQQ5934To7Xw9xjf4hkr/EAByxQY7hp7MKd790bWGz7XYSTsHmaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/openapi-webhooks-types": "11.0.0", - "@octokit/request-error": "^6.1.7", - "@octokit/webhooks-methods": "^5.1.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/webhooks-methods": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-5.1.1.tgz", - "integrity": "sha512-NGlEHZDseJTCj8TMMFehzwa9g7On4KJMPVHDSrHxCQumL6uSQR8wIkP/qesv52fXqV1BPf4pTxwtS31ldAt9Xg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, "node_modules/@open-draft/deferred-promise": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", @@ -2383,10 +1959,27 @@ "dev": true, "license": "MIT" }, + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.96.0.tgz", + "integrity": "sha512-CofbPOiW1PG+hi8bgElJPK0ioHfw8nt4Vw9d+Q9JuMhygS6LbQyu1W6tIFZ1OPFofeFRdWus3vD29FBx+tvFOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@oxc-parser/binding-darwin-arm64": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.64.0.tgz", - "integrity": "sha512-FfmLZWrt5rsG+wzruv0xfYci1fE/GQ/HnUCmB+j3keU4SfDxkxSIGUTphxdcE8S4ISoLelgeVZiE8QDGRhmSoQ==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.96.0.tgz", + "integrity": "sha512-+HZ2L1a/1BsUXYik8XqQwT2Tl5Z3jRQ/RRQiPV9UsB2skKyd91NLDlQlMpdhjLGs9Qe7Y42unFjRg2iHjIiwnw==", "cpu": [ "arm64" ], @@ -2397,13 +1990,13 @@ "darwin" ], "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@oxc-parser/binding-darwin-x64": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.64.0.tgz", - "integrity": "sha512-FFbtYNdlRw6d/KcfSxqOAJAI4evijC+i+PHQkpB8JJGr+mPzQEPKwVa8vh2Qe/lcspaQs6IrR2GRpJ+5UvciRw==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.96.0.tgz", + "integrity": "sha512-GC8wH1W0XaCLyTeGsmyaMdnItiYQkqfTcn9Ygc55AWI+m11lCjQeoKDIsDCm/QwrKLCN07u3WWWsuPs5ubfXpA==", "cpu": [ "x64" ], @@ -2414,13 +2007,30 @@ "darwin" ], "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.96.0.tgz", + "integrity": "sha512-8SeXi2FmlN15uPY5oM03cua5RXBDYmY34Uewongv6RUiAaU/kWxLvzuijpyNC+yQ1r4fC2LbWJhAsKpX5qkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.64.0.tgz", - "integrity": "sha512-u113yYpeTW0rQBp6Lld2PvdEMzVQmTq8n2T4WDb7UNGQFCMzoURCKgahkIZCStph4+zHAFU5uKwG5waQaswCyw==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.96.0.tgz", + "integrity": "sha512-UEs+Zf6T2/FwQlLgv7gfZsKmY19sl3hK57r2BQVc2eCmCmF/deeqDcWyFjzkNLgdDDucY60PoNhNGClDm605uQ==", "cpu": [ "arm" ], @@ -2431,13 +2041,30 @@ "linux" ], "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.96.0.tgz", + "integrity": "sha512-1kuWvjR2+ORJMoyxt9LSbLcDhXZnL25XOuv9VmH6NmSPvLgewzuubSlm++W03x+U7SzWFilBsdwIHtD/0mjERw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@oxc-parser/binding-linux-arm64-gnu": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.64.0.tgz", - "integrity": "sha512-cqWgdJcXJ2u2Rcjd/+4mY10DPISZtKosgyL7eMZwZdCNJD8q2ohS57pk6IbCmopF55QAh9/Py8rajblKbFCJBg==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.96.0.tgz", + "integrity": "sha512-PHH4ETR1t0fymxuhpQNj3Z9t/78/zZa2Lj3Z3I0ZOd+/Ex+gtdhGoB5xYyy7lcYGAPMfZ+Gmr+dTCr1GYNZ3BA==", "cpu": [ "arm64" ], @@ -2448,13 +2075,13 @@ "linux" ], "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@oxc-parser/binding-linux-arm64-musl": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.64.0.tgz", - "integrity": "sha512-b7Ma+CDlkK+UIU/Zr8Ydo+q3A9ouWUhV8PzWcnfOxiOwK+JEaoz5N02ixAPK8qvO+IKqzP00HzxPD8tUto8GcA==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.96.0.tgz", + "integrity": "sha512-fjDPbZjkqaDSTBe0FM8nZ9zBw4B/NF/I0gH7CfvNDwIj9smISaNFypYeomkvubORpnbX9ORhvhYwg3TxQ60OGA==", "cpu": [ "arm64" ], @@ -2465,13 +2092,47 @@ "linux" ], "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.96.0.tgz", + "integrity": "sha512-59KAHd/6/LmjkdSAuJn0piKmwSavMasWNUKuYLX/UnqI5KkGIp14+LBwwaBG6KzOtIq1NrRCnmlL4XSEaNkzTg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.96.0.tgz", + "integrity": "sha512-VtupojtgahY8XmLwpVpM3C1WQEgMD1JxpB8lzUtdSLwosWaaz1EAl+VXWNuxTTZusNuLBtmR+F0qql22ISi/9g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@oxc-parser/binding-linux-x64-gnu": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.64.0.tgz", - "integrity": "sha512-7o/qfZNZ0kt1o5vtqUz6nQkV6tuCGor4+gOmqtrb2TtnAo3qxYwPXZVjd9LKv39Z+Nfpqz/2cnR+GIqUNqv34A==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.96.0.tgz", + "integrity": "sha512-8XSY9aUYY+5I4I1mhSEWmYqdUrJi3J5cCAInvEVHyTnDAPkhb+tnLGVZD696TpW+lFOLrTFF2V5GMWJVafqIUA==", "cpu": [ "x64" ], @@ -2482,13 +2143,13 @@ "linux" ], "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@oxc-parser/binding-linux-x64-musl": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.64.0.tgz", - "integrity": "sha512-nuL0rqoWgvO11pP7g5FYdTDsjX93mt8ZFtUaOL4HMVkvRAx3XiKltJBYXXWiI2kySbHRC/XHJftAKWEgGhcXgg==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.96.0.tgz", + "integrity": "sha512-IIVNtqhA0uxKkD8Y6aZinKO/sOD5O62VlduE54FnUU2rzZEszrZQLL8nMGVZhTdPaKW5M1aeLmjcdnOs6er1Jg==", "cpu": [ "x64" ], @@ -2499,13 +2160,13 @@ "linux" ], "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@oxc-parser/binding-wasm32-wasi": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.64.0.tgz", - "integrity": "sha512-iZ5LeOPDo0gCISzcq1JKo3HGqXwuQDTgHVPBUs+UFdCL9WJ9DmNkXXQPLVYEyyI/YFXg15y7Rv2L+FEvpvYa+w==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.96.0.tgz", + "integrity": "sha512-TJ/sNPbVD4u6kUwm7sDKa5iRDEB8vd7ZIMjYqFrrAo9US1RGYOSvt6Ie9sDRekUL9fZhNsykvSrpmIj6dg/C2w==", "cpu": [ "wasm32" ], @@ -2513,16 +2174,29 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.9" + "@napi-rs/wasm-runtime": "^1.0.7" }, "engines": { "node": ">=14.0.0" } }, + "node_modules/@oxc-parser/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", + "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@tybys/wasm-util": "^0.10.1" + } + }, "node_modules/@oxc-parser/binding-win32-arm64-msvc": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.64.0.tgz", - "integrity": "sha512-9kWLwYOT9sCVrFL3Egpt4+viAYtYOwmstGoy/CPikC0fxEpB760qln8u+MfZpbrH0Df2XgEdAUTqiwnRwcp+uA==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.96.0.tgz", + "integrity": "sha512-zCOhRB7MYVIHLj+2QYoTuLyaipiD8JG/ggUjfsMUaupRPpvwQNhsxINLIcTcb0AS+OsT7/OREhydjO74STqQzQ==", "cpu": [ "arm64" ], @@ -2533,13 +2207,13 @@ "win32" ], "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@oxc-parser/binding-win32-x64-msvc": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.64.0.tgz", - "integrity": "sha512-EHQglaBx4LpNw9BMA65aM36isTpuAdWxGbAUH7w55GYIGjVG7hIsMx/MuOrJXsmOBVmRokoYNYLN7X5aTd5TmQ==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.96.0.tgz", + "integrity": "sha512-J6zfx9TE0oS+TrqBUjMVMOi/d/j3HMj69Pip263pETOEPm788N0HXKPsc2X2jUfSTHzD9vmdjq0QFymbf2LhWg==", "cpu": [ "x64" ], @@ -2550,24 +2224,37 @@ "win32" ], "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@oxc-project/types": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.64.0.tgz", - "integrity": "sha512-B0dxuEZFV6M4tXjPFwDSaED5/J55YUhODBaF09xNFNRrEyzQLKZuhKXAw1xYK8bO4K8Jn1d21TZfei3kAIE8dA==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.96.0.tgz", + "integrity": "sha512-r/xkmoXA0xEpU6UGtn18CNVjXH6erU3KCpCDbpLmbVxBFor1U9MqN5Z2uMmCHJuXjJzlnDR+hWY+yPoLo8oHDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@pandacss/is-valid-prop": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@pandacss/is-valid-prop/-/is-valid-prop-0.54.0.tgz", + "integrity": "sha512-UhRgg1k9VKRCBAHl+XUK3lvN0k9bYifzYGZOqajDid4L1DyU813A1L0ZwN4iV9WX5TX3PfUugqtgG9LnIeFGBQ==" + }, + "node_modules/@quansync/fs": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-0.1.5.tgz", + "integrity": "sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==", "dev": true, "license": "MIT", + "dependencies": { + "quansync": "^0.2.11" + }, "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/sxzz" } }, - "node_modules/@pandacss/is-valid-prop": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@pandacss/is-valid-prop/-/is-valid-prop-0.54.0.tgz", - "integrity": "sha512-UhRgg1k9VKRCBAHl+XUK3lvN0k9bYifzYGZOqajDid4L1DyU813A1L0ZwN4iV9WX5TX3PfUugqtgG9LnIeFGBQ==" - }, "node_modules/@react-three/drei": { "version": "10.7.6", "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.6.tgz", @@ -3021,16 +2708,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sveltejs/acorn-typescript": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", - "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^8.9.0" - } - }, "node_modules/@swc/helpers": { "version": "0.5.17", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", @@ -3384,13 +3061,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@types/aws-lambda": { - "version": "8.10.152", - "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.152.tgz", - "integrity": "sha512-soT/c2gYBnT5ygwiHPmd9a1bftj462NWVk2tKCc1PYHSIacB2UwbTS2zYG4jzag1mRDuzg/OjtxQjQ2NKRB6Rw==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3475,23 +3145,13 @@ "version": "18.19.127", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.127.tgz", "integrity": "sha512-gSjxjrnKXML/yo0BO099uPixMqfpJU0TKYjpfLU7TrtA2WWDki412Np/RSTPRil1saKBhvVVKzVx/p/6p94nVA==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "undici-types": "~5.26.4" } }, - "node_modules/@types/node-fetch": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", - "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.4" - } - }, "node_modules/@types/offscreencanvas": { "version": "2019.7.3", "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", @@ -3646,8 +3306,9 @@ "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.22.tgz", "integrity": "sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/parser": "^7.28.4", "@vue/shared": "3.5.22", @@ -3660,8 +3321,9 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "devOptional": true, "license": "BSD-2-Clause", + "optional": true, + "peer": true, "engines": { "node": ">=0.12" }, @@ -3673,15 +3335,17 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "devOptional": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@vue/compiler-dom": { "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.22.tgz", "integrity": "sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@vue/compiler-core": "3.5.22", "@vue/shared": "3.5.22" @@ -3691,8 +3355,9 @@ "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.22.tgz", "integrity": "sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/parser": "^7.28.4", "@vue/compiler-core": "3.5.22", @@ -3709,15 +3374,17 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "devOptional": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@vue/compiler-ssr": { "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.22.tgz", "integrity": "sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.22", "@vue/shared": "3.5.22" @@ -3727,8 +3394,9 @@ "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.22.tgz", "integrity": "sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@vue/shared": "3.5.22" } @@ -3737,8 +3405,9 @@ "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.22.tgz", "integrity": "sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@vue/reactivity": "3.5.22", "@vue/shared": "3.5.22" @@ -3748,8 +3417,9 @@ "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.22.tgz", "integrity": "sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@vue/reactivity": "3.5.22", "@vue/runtime-core": "3.5.22", @@ -3761,8 +3431,9 @@ "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.22.tgz", "integrity": "sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@vue/compiler-ssr": "3.5.22", "@vue/shared": "3.5.22" @@ -3775,8 +3446,9 @@ "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.22.tgz", "integrity": "sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==", - "devOptional": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@webgpu/types": { "version": "0.1.65", @@ -4627,19 +4299,6 @@ "integrity": "sha512-4nU9lfFlLLW/4T+/HaP+HdHYFeWvacxSVcccv0JSf+ZTC110IldV48kZELP+wFg9xDL/jCPPjlRtO1K64EIwgA==", "license": "MIT" }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -4710,19 +4369,6 @@ "node": ">= 14" } }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -4740,80 +4386,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -4860,16 +4432,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", @@ -5031,23 +4593,6 @@ "node": ">= 0.4" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/atomically": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.0.3.tgz", - "integrity": "sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==", - "dev": true, - "dependencies": { - "stubborn-fs": "^1.2.5", - "when-exit": "^2.1.1" - } - }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -5064,16 +4609,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -5126,13 +4661,6 @@ "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/before-after-hook": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", - "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/bidi-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", @@ -5163,62 +4691,6 @@ "node": ">=18" } }, - "node_modules/bottleneck": { - "version": "2.19.5", - "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", - "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/boxen": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", - "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^8.0.0", - "chalk": "^5.3.0", - "cli-boxes": "^3.0.0", - "string-width": "^7.2.0", - "type-fest": "^4.21.0", - "widest-line": "^5.0.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/boxen/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -5370,19 +4842,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", - "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/camera-controls": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-3.1.0.tgz", @@ -5498,19 +4957,6 @@ "url": "https://polar.sh/cva" } }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", @@ -5641,19 +5087,6 @@ "dev": true, "license": "MIT" }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/comma-separated-tokens": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", @@ -5665,13 +5098,13 @@ } }, "node_modules/commander": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", - "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/complex.js": { @@ -5695,89 +5128,28 @@ "license": "MIT" }, "node_modules/concurrently": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", - "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "4.1.2", - "rxjs": "7.8.2", - "shell-quote": "1.8.3", - "supports-color": "8.1.1", - "tree-kill": "1.2.2", - "yargs": "17.7.2" - }, - "bin": { - "conc": "dist/bin/concurrently.js", - "concurrently": "dist/bin/concurrently.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" - } - }, - "node_modules/conf": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/conf/-/conf-13.1.0.tgz", - "integrity": "sha512-Bi6v586cy1CoTFViVO4lGTtx780lfF96fUmS1lSX6wpZf6330NvHUu6fReVuDP1de8Mg0nkZb01c8tAQdz1o3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "atomically": "^2.0.3", - "debounce-fn": "^6.0.0", - "dot-prop": "^9.0.0", - "env-paths": "^3.0.0", - "json-schema-typed": "^8.0.1", - "semver": "^7.6.3", - "uint8array-extras": "^1.4.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/conf/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/conf/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/conf/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, "bin": { - "semver": "bin/semver.js" + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" }, "engines": { - "node": ">=10" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, "node_modules/content-disposition": { @@ -5891,16 +5263,6 @@ "node": ">= 8" } }, - "node_modules/css-dependency": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/css-dependency/-/css-dependency-0.0.3.tgz", - "integrity": "sha512-jLQuve6jhpjkH3+k2Y8jK3j27Hm3rnIsRW/8oOf9oxFOBI5iu6sndwSv6lj5dNfO9JVP6cNb8Xs+VXhndgtLfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.1.0" - } - }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -5971,22 +5333,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/debounce-fn": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-6.0.0.tgz", - "integrity": "sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -6078,15 +5424,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } + "license": "MIT" }, "node_modules/depd": { "version": "2.0.0", @@ -6116,16 +5459,6 @@ "node": ">=8" } }, - "node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -6149,35 +5482,6 @@ "csstype": "^3.0.2" } }, - "node_modules/dot-prop": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", - "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^4.18.2" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dot-prop/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/dotenv": { "version": "17.2.2", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", @@ -6274,32 +5578,6 @@ "node": ">=10.13.0" } }, - "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/env-paths": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", - "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/error-ex": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", @@ -6733,13 +6011,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/esm-env": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "dev": true, - "license": "MIT" - }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -6785,16 +6056,6 @@ "node": ">=0.10" } }, - "node_modules/esrap": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz", - "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -6818,16 +6079,6 @@ "node": ">=4.0" } }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -6848,16 +6099,6 @@ "node": ">= 0.6" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/eventsource": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", @@ -7006,23 +6247,6 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "license": "0BSD" }, - "node_modules/fast-content-type-parse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", - "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -7080,23 +6304,6 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "license": "MIT" }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -7299,30 +6506,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data-encoder": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", - "dev": true, - "license": "MIT" - }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", @@ -7331,20 +6514,6 @@ "node": ">=0.4.x" } }, - "node_modules/formdata-node": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", - "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" - }, - "engines": { - "node": ">= 12.20" - } - }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -7628,19 +6797,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-tsconfig": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", - "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, "node_modules/gl-matrix": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", @@ -7971,16 +7127,6 @@ "node": ">=18.18.0" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -8467,16 +7613,6 @@ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", "license": "MIT" }, - "node_modules/is-reference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.6" - } - }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -8720,9 +7856,9 @@ "license": "MIT" }, "node_modules/jiti": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.0.tgz", - "integrity": "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -8779,13 +7915,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-schema-typed": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.1.tgz", - "integrity": "sha512-XQmWYj2Sm4kn4WeTYvmpKEbyPsL7nBsb647c7pMe6l02/yx2+Jfc4dT6UZkEXnIUb5LhD55r2HPsJ1milQ4rDg==", - "dev": true, - "license": "BSD-2-Clause" - }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -8820,96 +7949,30 @@ } }, "node_modules/jsrepo": { - "version": "1.47.1", - "resolved": "https://registry.npmjs.org/jsrepo/-/jsrepo-1.47.1.tgz", - "integrity": "sha512-m126Q8u8cQiZfZC8o4ntSwlNGU+pOAQjphYXL0dguV8zs9f4PlYgwUGnLh73j4Qvyc1usvBLHLMTfk94GMw+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@anthropic-ai/sdk": "^0.39.0", - "@biomejs/js-api": "^0.7.1", - "@biomejs/wasm-nodejs": "^1.9.4", - "@clack/prompts": "^0.10.1", - "boxen": "^8.0.1", - "chalk": "^5.4.1", - "commander": "^13.1.0", - "conf": "^13.1.0", - "css-dependency": "^0.0.3", - "diff": "^7.0.0", - "escape-string-regexp": "^5.0.0", - "estree-walker": "^3.0.3", - "execa": "^9.5.2", - "get-tsconfig": "^4.10.0", - "ignore": "^7.0.3", - "is-unicode-supported": "^2.1.0", - "node-fetch": "^3.3.2", - "octokit": "^4.1.2", - "ollama": "^0.5.14", - "openai": "^4.93.0", - "oxc-parser": "^0.64.0", - "package-manager-detector": "^1.1.0", - "parse5": "^7.2.1", - "pathe": "^2.0.3", - "prettier": "^3.5.3", - "prettier-plugin-svelte": "^3.3.3", - "semver": "^7.7.1", - "sisteransi": "^1.0.5", - "svelte": "^5.25.9", - "valibot": "1.0.0", - "validate-npm-package-name": "^6.0.0", - "vue": "^3.5.13" - }, - "bin": { - "jsrepo": "dist/index.js" - } - }, - "node_modules/jsrepo/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jsrepo/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "version": "3.0.0-beta.8", + "resolved": "https://registry.npmjs.org/jsrepo/-/jsrepo-3.0.0-beta.8.tgz", + "integrity": "sha512-BpfwbxK1tAZepmpkiqczD5pqbwyxrWKFg36UbaCNxXqsJBPWKwB211KZLADhpWWnmj+8G2f7IcYEnlnRMEdGmw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "commander": "^14.0.2", + "oxc-parser": "^0.96.0", + "unconfig": "^7.3.3" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jsrepo/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/jsrepo/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", "bin": { - "semver": "bin/semver.js" + "jsrepo": "dist/bin.js" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "svelte": "^5.41.4", + "vue": "^3.5.22" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + }, + "vue": { + "optional": true + } } }, "node_modules/jsx-ast-utils": { @@ -9231,13 +8294,6 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, - "node_modules/locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "dev": true, - "license": "MIT" - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -9489,29 +8545,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -9940,31 +8973,8 @@ "engines": { "node": ">= 0.4" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/octokit": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/octokit/-/octokit-4.1.4.tgz", - "integrity": "sha512-cRvxRte6FU3vAHRC9+PMSY3D+mRAs2Rd9emMoqp70UGRvJRM3sbAoim2IXRZNNsf8wVfn4sGxVBHRAP+JBVX/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/app": "^15.1.6", - "@octokit/core": "^6.1.5", - "@octokit/oauth-app": "^7.1.6", - "@octokit/plugin-paginate-graphql": "^5.2.4", - "@octokit/plugin-paginate-rest": "^12.0.0", - "@octokit/plugin-rest-endpoint-methods": "^14.0.0", - "@octokit/plugin-retry": "^7.2.1", - "@octokit/plugin-throttling": "^10.0.0", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "@octokit/webhooks": "^13.8.3" - }, - "engines": { - "node": ">= 18" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/ogl": { @@ -9973,16 +8983,6 @@ "integrity": "sha512-kUpC154AFfxi16pmZUK4jk3J+8zxwTWGPo03EoYA8QPbzikHoaC82n6pNTbd+oEaJonaE8aPWBlX7ad9zrqLsA==", "license": "Unlicense" }, - "node_modules/ollama": { - "version": "0.5.18", - "resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.18.tgz", - "integrity": "sha512-lTFqTf9bo7Cd3hpF6CviBe/DEhewjoZYd9N/uCe7O20qYTvGqrNOFOBDj3lbZgFWHUgDv5EeyusYxsZSLS8nvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-fetch": "^3.6.20" - } - }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -10022,58 +9022,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/openai": { - "version": "4.104.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", - "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - }, - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.23.8" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/openai/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -10171,31 +9119,36 @@ } }, "node_modules/oxc-parser": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.64.0.tgz", - "integrity": "sha512-T5/h7Iv3kwUwTaOwOLz2yTwz2LsUfdu5IXTmZuMEDYL2Bp/dxGdxQZHaz8lc4bUBU9Swnb+caioKk4FLBT7prg==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.96.0.tgz", + "integrity": "sha512-ucs6niJ5mZlYP3oTl4AK2eD2m7WLoSaljswnSFVYWrXzme5PtM97S7Ve1Tjx+/TKjanmEZuSt1f1qYi6SZvntw==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "^0.64.0" + "@oxc-project/types": "^0.96.0" }, "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/sponsors/Boshen" }, "optionalDependencies": { - "@oxc-parser/binding-darwin-arm64": "0.64.0", - "@oxc-parser/binding-darwin-x64": "0.64.0", - "@oxc-parser/binding-linux-arm-gnueabihf": "0.64.0", - "@oxc-parser/binding-linux-arm64-gnu": "0.64.0", - "@oxc-parser/binding-linux-arm64-musl": "0.64.0", - "@oxc-parser/binding-linux-x64-gnu": "0.64.0", - "@oxc-parser/binding-linux-x64-musl": "0.64.0", - "@oxc-parser/binding-wasm32-wasi": "0.64.0", - "@oxc-parser/binding-win32-arm64-msvc": "0.64.0", - "@oxc-parser/binding-win32-x64-msvc": "0.64.0" + "@oxc-parser/binding-android-arm64": "0.96.0", + "@oxc-parser/binding-darwin-arm64": "0.96.0", + "@oxc-parser/binding-darwin-x64": "0.96.0", + "@oxc-parser/binding-freebsd-x64": "0.96.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.96.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.96.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.96.0", + "@oxc-parser/binding-linux-arm64-musl": "0.96.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.96.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.96.0", + "@oxc-parser/binding-linux-x64-gnu": "0.96.0", + "@oxc-parser/binding-linux-x64-musl": "0.96.0", + "@oxc-parser/binding-wasm32-wasi": "0.96.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.96.0", + "@oxc-parser/binding-win32-x64-msvc": "0.96.0" } }, "node_modules/p-limit": { @@ -10298,19 +9251,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -10379,13 +9319,6 @@ "node": ">=8" } }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, "node_modules/perfect-freehand": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/perfect-freehand/-/perfect-freehand-1.2.2.tgz", @@ -10527,17 +9460,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/prettier-plugin-svelte": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.4.0.tgz", - "integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "prettier": "^3.0.0", - "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" - } - }, "node_modules/pretty-ms": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", @@ -10676,6 +9598,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -11074,16 +10013,6 @@ "node": ">=4" } }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -11511,16 +10440,6 @@ "shadcn": "dist/index.js" } }, - "node_modules/shadcn/node_modules/commander": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.1.tgz", - "integrity": "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, "node_modules/shadcn/node_modules/cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", @@ -11568,6 +10487,26 @@ "node": ">=6" } }, + "node_modules/shadcn/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/shadcn/node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -12015,12 +10954,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stubborn-fs": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-1.2.5.tgz", - "integrity": "sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==", - "dev": true - }, "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", @@ -12064,32 +10997,6 @@ "react": ">=17.0" } }, - "node_modules/svelte": { - "version": "5.39.6", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.39.6.tgz", - "integrity": "sha512-bOJXmuwLNaoqPCTWO8mPu/fwxI5peGE5Efe7oo6Cakpz/G60vsnVF6mxbGODaxMUFUKEnjm6XOwHEqOht6cbvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "@jridgewell/sourcemap-codec": "^1.5.0", - "@sveltejs/acorn-typescript": "^1.0.5", - "@types/estree": "^1.0.5", - "acorn": "^8.12.1", - "aria-query": "^5.3.1", - "axobject-query": "^4.1.0", - "clsx": "^2.1.1", - "esm-env": "^1.2.1", - "esrap": "^2.1.0", - "is-reference": "^3.0.3", - "locate-character": "^3.0.0", - "magic-string": "^0.30.11", - "zimmerframe": "^1.1.2" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/tailwind-merge": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", @@ -12242,16 +11149,6 @@ "node": ">=8.0" } }, - "node_modules/toad-cache": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", - "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -12275,13 +11172,6 @@ "node": ">=16" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, - "license": "MIT" - }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -12572,19 +11462,6 @@ "node": ">=14.17" } }, - "node_modules/uint8array-extras": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", - "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -12604,12 +11481,44 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/unconfig": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-7.4.0.tgz", + "integrity": "sha512-KM0SrvIvwQXJnbiSzur1Y+5jHSLVPhS31H5qzgjDQxGqS3PWrH6X7TxYX/JTuTlItarHkZ9ePK9t01Q6wu1c4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@quansync/fs": "^0.1.5", + "defu": "^6.1.4", + "jiti": "^2.6.1", + "quansync": "^0.2.11", + "unconfig-core": "7.4.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/unconfig-core": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/unconfig-core/-/unconfig-core-7.4.0.tgz", + "integrity": "sha512-3ew7rvES5x2LCZ/QRKV3nQQpq7eFYuszQuvZrhTHxDPKc34QFjRXI17XGiZI+WQTVIXKYeBti4v3LS39NWmhmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@quansync/fs": "^0.1.5", + "quansync": "^0.2.11" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "devOptional": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/unicorn-magic": { "version": "0.3.0", @@ -12624,20 +11533,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/universal-github-app-jwt": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.2.tgz", - "integrity": "sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/universal-user-agent": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", - "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", - "dev": true, - "license": "ISC" - }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -12733,31 +11628,6 @@ "node": ">= 4" } }, - "node_modules/valibot": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.0.0.tgz", - "integrity": "sha512-1Hc0ihzWxBar6NGeZv7fPLY0QuxFMyxwYR2sF1Blu7Wq7EnremwY2W02tit2ij2VJT8HcSkHAQqmFfl77f73Yw==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "typescript": ">=5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/validate-npm-package-name": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.2.tgz", - "integrity": "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -12831,8 +11701,9 @@ "version": "3.5.22", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz", "integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.22", "@vue/compiler-sfc": "3.5.22", @@ -12849,16 +11720,6 @@ } } }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/webgl-constants": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz", @@ -12870,38 +11731,6 @@ "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==", "license": "MIT" }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-fetch": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", - "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/when-exit": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.4.tgz", - "integrity": "sha512-4rnvd3A1t16PWzrBUcSDZqcAmsUIy4minDXT/CZ8F2mVDgd65i4Aalimgz1aQkRGU0iH5eT5+6Rx2TK8o443Pg==", - "dev": true, - "license": "MIT" - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -13006,22 +11835,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/widest-line": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", - "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -13032,53 +11845,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -13211,33 +11977,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/zimmerframe": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", - "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", - "dev": true, - "license": "MIT" - }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", + "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", - "dev": true, - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } - }, "node_modules/zustand": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz", diff --git a/package.json b/package.json index 1fa8a4a3..9a399897 100644 --- a/package.json +++ b/package.json @@ -5,16 +5,10 @@ "type": "module", "scripts": { "dev": "vite", - "build": "npm run jsrepo:build && npm run shadcn:build && npm run llms:text && vite build", + "build": "npm run registry:build && npm run llms:text && vite build", "new:component": "node scripts/generateComponent.js", - "shadcn:build": "npm run shadcn:generate && shadcn build", - "shadcn:generate": "node ./scripts/generateShadcnRegistry.js", "llms:text": "node ./scripts/generateLlmsText.js", - "jsrepo:build": "concurrently -p \"[{name}]\" -n \"default,tailwind,ts-default,ts-tailwind,readmes\" \"npm run jsrepo:default\" \"npm run jsrepo:tailwind\" \"npm run jsrepo:ts-default\" \"npm run jsrepo:ts-tailwind\" \"node ./scripts/generateJsrepoReadmes.js\"", - "jsrepo:default": "jsrepo build --dirs ./src/content --output-dir ./public/default", - "jsrepo:tailwind": "jsrepo build --dirs ./src/tailwind --output-dir ./public/tailwind", - "jsrepo:ts-default": "jsrepo build --dirs ./src/ts-default --output-dir ./public/ts/default", - "jsrepo:ts-tailwind": "jsrepo build --dirs ./src/ts-tailwind --output-dir ./public/ts/tailwind", + "registry:build": "jsrepo build", "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", "format": "prettier --write ." }, @@ -58,6 +52,7 @@ "three": "^0.167.1" }, "devDependencies": { + "@jsrepo/shadcn": "^0.0.1-beta.8", "@types/matter-js": "^0.19.8", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", @@ -68,12 +63,13 @@ "eslint-plugin-react": "^7.34.3", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.7", - "jsrepo": "^1.30.1", + "jsrepo": "^3.0.0-beta.8", "postcss-safe-parser": "^7.0.1", "prettier": "^3.6.2", "shadcn": "^3.1.0", "tw-animate-css": "^1.4.0", "typescript": "^5.7.3", - "vite": "^5.3.4" + "vite": "^5.3.4", + "zod": "^4.1.12" } } diff --git a/public/jsrepo/animated-content-tw.json b/public/jsrepo/animated-content-tw.json new file mode 100644 index 00000000..30746618 --- /dev/null +++ b/public/jsrepo/animated-content-tw.json @@ -0,0 +1 @@ +{"name":"animated-content-tw","title":"AnimatedContent","description":"Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.","type":"registry:block","add":"when-added","files":[{"content":"import React, { useRef, useEffect, ReactNode } from 'react';\nimport { gsap } from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ngsap.registerPlugin(ScrollTrigger);\n\ninterface AnimatedContentProps {\n children: ReactNode;\n distance?: number;\n direction?: 'vertical' | 'horizontal';\n reverse?: boolean;\n duration?: number;\n ease?: string | ((progress: number) => number);\n initialOpacity?: number;\n animateOpacity?: boolean;\n scale?: number;\n threshold?: number;\n delay?: number;\n onComplete?: () => void;\n}\n\nconst AnimatedContent: React.FC = ({\n children,\n distance = 100,\n direction = 'vertical',\n reverse = false,\n duration = 0.8,\n ease = 'power3.out',\n initialOpacity = 0,\n animateOpacity = true,\n scale = 1,\n threshold = 0.1,\n delay = 0,\n onComplete\n}) => {\n const ref = useRef(null);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const axis = direction === 'horizontal' ? 'x' : 'y';\n const offset = reverse ? -distance : distance;\n const startPct = (1 - threshold) * 100;\n\n gsap.set(el, {\n [axis]: offset,\n scale,\n opacity: animateOpacity ? initialOpacity : 1\n });\n\n gsap.to(el, {\n [axis]: 0,\n scale: 1,\n opacity: 1,\n duration,\n ease,\n delay,\n onComplete,\n scrollTrigger: {\n trigger: el,\n start: `top ${startPct}%`,\n toggleActions: 'play none none none',\n once: true\n }\n });\n\n return () => {\n ScrollTrigger.getAll().forEach(t => t.kill());\n gsap.killTweensOf(el);\n };\n }, [\n distance,\n direction,\n reverse,\n duration,\n ease,\n initialOpacity,\n animateOpacity,\n scale,\n threshold,\n delay,\n onComplete\n ]);\n\n return
{children}
;\n};\n\nexport default AnimatedContent;\n","path":"AnimatedContent.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}]} \ No newline at end of file diff --git a/public/jsrepo/animated-content.json b/public/jsrepo/animated-content.json new file mode 100644 index 00000000..7b88731e --- /dev/null +++ b/public/jsrepo/animated-content.json @@ -0,0 +1 @@ +{"name":"animated-content","title":"AnimatedContent","description":"Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.","type":"registry:block","add":"when-added","files":[{"content":"import React, { useRef, useEffect, ReactNode } from 'react';\nimport { gsap } from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ngsap.registerPlugin(ScrollTrigger);\n\ninterface AnimatedContentProps {\n children: ReactNode;\n distance?: number;\n direction?: 'vertical' | 'horizontal';\n reverse?: boolean;\n duration?: number;\n ease?: string | ((progress: number) => number);\n initialOpacity?: number;\n animateOpacity?: boolean;\n scale?: number;\n threshold?: number;\n delay?: number;\n onComplete?: () => void;\n}\n\nconst AnimatedContent: React.FC = ({\n children,\n distance = 100,\n direction = 'vertical',\n reverse = false,\n duration = 0.8,\n ease = 'power3.out',\n initialOpacity = 0,\n animateOpacity = true,\n scale = 1,\n threshold = 0.1,\n delay = 0,\n onComplete\n}) => {\n const ref = useRef(null);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const axis = direction === 'horizontal' ? 'x' : 'y';\n const offset = reverse ? -distance : distance;\n const startPct = (1 - threshold) * 100;\n\n gsap.set(el, {\n [axis]: offset,\n scale,\n opacity: animateOpacity ? initialOpacity : 1\n });\n\n gsap.to(el, {\n [axis]: 0,\n scale: 1,\n opacity: 1,\n duration,\n ease,\n delay,\n onComplete,\n scrollTrigger: {\n trigger: el,\n start: `top ${startPct}%`,\n toggleActions: 'play none none none',\n once: true\n }\n });\n\n return () => {\n ScrollTrigger.getAll().forEach(t => t.kill());\n gsap.killTweensOf(el);\n };\n }, [\n distance,\n direction,\n reverse,\n duration,\n ease,\n initialOpacity,\n animateOpacity,\n scale,\n threshold,\n delay,\n onComplete\n ]);\n\n return
{children}
;\n};\n\nexport default AnimatedContent;\n","path":"AnimatedContent.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}]} \ No newline at end of file diff --git a/public/jsrepo/animated-list-tw.json b/public/jsrepo/animated-list-tw.json new file mode 100644 index 00000000..40edcb58 --- /dev/null +++ b/public/jsrepo/animated-list-tw.json @@ -0,0 +1 @@ +{"name":"animated-list-tw","title":"AnimatedList","description":"List items enter with staggered motion variants for polished reveals.","type":"registry:block","add":"when-added","files":[{"content":"import React, { useRef, useState, useEffect, ReactNode, MouseEventHandler, UIEvent } from 'react';\nimport { motion, useInView } from 'motion/react';\n\ninterface AnimatedItemProps {\n children: ReactNode;\n delay?: number;\n index: number;\n onMouseEnter?: MouseEventHandler;\n onClick?: MouseEventHandler;\n}\n\nconst AnimatedItem: React.FC = ({ children, delay = 0, index, onMouseEnter, onClick }) => {\n const ref = useRef(null);\n const inView = useInView(ref, { amount: 0.5, once: false });\n return (\n \n {children}\n \n );\n};\n\ninterface AnimatedListProps {\n items?: string[];\n onItemSelect?: (item: string, index: number) => void;\n showGradients?: boolean;\n enableArrowNavigation?: boolean;\n className?: string;\n itemClassName?: string;\n displayScrollbar?: boolean;\n initialSelectedIndex?: number;\n}\n\nconst AnimatedList: React.FC = ({\n items = [\n 'Item 1',\n 'Item 2',\n 'Item 3',\n 'Item 4',\n 'Item 5',\n 'Item 6',\n 'Item 7',\n 'Item 8',\n 'Item 9',\n 'Item 10',\n 'Item 11',\n 'Item 12',\n 'Item 13',\n 'Item 14',\n 'Item 15'\n ],\n onItemSelect,\n showGradients = true,\n enableArrowNavigation = true,\n className = '',\n itemClassName = '',\n displayScrollbar = true,\n initialSelectedIndex = -1\n}) => {\n const listRef = useRef(null);\n const [selectedIndex, setSelectedIndex] = useState(initialSelectedIndex);\n const [keyboardNav, setKeyboardNav] = useState(false);\n const [topGradientOpacity, setTopGradientOpacity] = useState(0);\n const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1);\n\n const handleScroll = (e: UIEvent) => {\n const { scrollTop, scrollHeight, clientHeight } = e.target as HTMLDivElement;\n setTopGradientOpacity(Math.min(scrollTop / 50, 1));\n const bottomDistance = scrollHeight - (scrollTop + clientHeight);\n setBottomGradientOpacity(scrollHeight <= clientHeight ? 0 : Math.min(bottomDistance / 50, 1));\n };\n\n useEffect(() => {\n if (!enableArrowNavigation) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'ArrowDown' || (e.key === 'Tab' && !e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.min(prev + 1, items.length - 1));\n } else if (e.key === 'ArrowUp' || (e.key === 'Tab' && e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.max(prev - 1, 0));\n } else if (e.key === 'Enter') {\n if (selectedIndex >= 0 && selectedIndex < items.length) {\n e.preventDefault();\n if (onItemSelect) {\n onItemSelect(items[selectedIndex], selectedIndex);\n }\n }\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [items, selectedIndex, onItemSelect, enableArrowNavigation]);\n\n useEffect(() => {\n if (!keyboardNav || selectedIndex < 0 || !listRef.current) return;\n const container = listRef.current;\n const selectedItem = container.querySelector(`[data-index=\"${selectedIndex}\"]`) as HTMLElement | null;\n if (selectedItem) {\n const extraMargin = 50;\n const containerScrollTop = container.scrollTop;\n const containerHeight = container.clientHeight;\n const itemTop = selectedItem.offsetTop;\n const itemBottom = itemTop + selectedItem.offsetHeight;\n if (itemTop < containerScrollTop + extraMargin) {\n container.scrollTo({ top: itemTop - extraMargin, behavior: 'smooth' });\n } else if (itemBottom > containerScrollTop + containerHeight - extraMargin) {\n container.scrollTo({\n top: itemBottom - containerHeight + extraMargin,\n behavior: 'smooth'\n });\n }\n }\n setKeyboardNav(false);\n }, [selectedIndex, keyboardNav]);\n\n return (\n
\n \n {items.map((item, index) => (\n setSelectedIndex(index)}\n onClick={() => {\n setSelectedIndex(index);\n if (onItemSelect) {\n onItemSelect(item, index);\n }\n }}\n >\n
\n

{item}

\n
\n \n ))}\n
\n {showGradients && (\n <>\n \n \n \n )}\n \n );\n};\n\nexport default AnimatedList;\n","path":"AnimatedList.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"motion","version":"^12.23.12"}]} \ No newline at end of file diff --git a/public/jsrepo/animated-list.json b/public/jsrepo/animated-list.json new file mode 100644 index 00000000..5632ecfb --- /dev/null +++ b/public/jsrepo/animated-list.json @@ -0,0 +1 @@ +{"name":"animated-list","title":"AnimatedList","description":"List items enter with staggered motion variants for polished reveals.","type":"registry:block","add":"when-added","files":[{"content":".scroll-list-container {\n position: relative;\n width: 500px;\n}\n\n.scroll-list {\n max-height: 400px;\n overflow-y: auto;\n padding: 16px;\n}\n\n.scroll-list::-webkit-scrollbar {\n width: 8px;\n}\n\n.scroll-list::-webkit-scrollbar-track {\n background: #060606;\n}\n\n.scroll-list::-webkit-scrollbar-thumb {\n background: #222;\n border-radius: 4px;\n}\n\n.no-scrollbar::-webkit-scrollbar {\n display: none;\n}\n\n.no-scrollbar {\n -ms-overflow-style: none;\n scrollbar-width: none;\n}\n\n.item {\n padding: 16px;\n background-color: #111;\n border-radius: 8px;\n margin-bottom: 1rem;\n}\n\n.item.selected {\n background-color: #222;\n}\n\n.item-text {\n color: white;\n margin: 0;\n}\n\n.top-gradient {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 50px;\n background: linear-gradient(to bottom, #060010, transparent);\n pointer-events: none;\n transition: opacity 0.3s ease;\n}\n\n.bottom-gradient {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 100px;\n background: linear-gradient(to top, #060010, transparent);\n pointer-events: none;\n transition: opacity 0.3s ease;\n}\n","path":"AnimatedList.css","_imports_":[]},{"content":"import React, { useRef, useState, useEffect, ReactNode, MouseEventHandler, UIEvent } from 'react';\nimport { motion, useInView } from 'motion/react';\nimport './AnimatedList.css';\n\ninterface AnimatedItemProps {\n children: ReactNode;\n delay?: number;\n index: number;\n onMouseEnter?: MouseEventHandler;\n onClick?: MouseEventHandler;\n}\n\nconst AnimatedItem: React.FC = ({ children, delay = 0, index, onMouseEnter, onClick }) => {\n const ref = useRef(null);\n const inView = useInView(ref, { amount: 0.5, once: false });\n return (\n \n {children}\n \n );\n};\n\ninterface AnimatedListProps {\n items?: string[];\n onItemSelect?: (item: string, index: number) => void;\n showGradients?: boolean;\n enableArrowNavigation?: boolean;\n className?: string;\n itemClassName?: string;\n displayScrollbar?: boolean;\n initialSelectedIndex?: number;\n}\n\nconst AnimatedList: React.FC = ({\n items = [\n 'Item 1',\n 'Item 2',\n 'Item 3',\n 'Item 4',\n 'Item 5',\n 'Item 6',\n 'Item 7',\n 'Item 8',\n 'Item 9',\n 'Item 10',\n 'Item 11',\n 'Item 12',\n 'Item 13',\n 'Item 14',\n 'Item 15'\n ],\n onItemSelect,\n showGradients = true,\n enableArrowNavigation = true,\n className = '',\n itemClassName = '',\n displayScrollbar = true,\n initialSelectedIndex = -1\n}) => {\n const listRef = useRef(null);\n const [selectedIndex, setSelectedIndex] = useState(initialSelectedIndex);\n const [keyboardNav, setKeyboardNav] = useState(false);\n const [topGradientOpacity, setTopGradientOpacity] = useState(0);\n const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1);\n\n const handleScroll = (e: UIEvent) => {\n const target = e.target as HTMLDivElement;\n const { scrollTop, scrollHeight, clientHeight } = target;\n setTopGradientOpacity(Math.min(scrollTop / 50, 1));\n const bottomDistance = scrollHeight - (scrollTop + clientHeight);\n setBottomGradientOpacity(scrollHeight <= clientHeight ? 0 : Math.min(bottomDistance / 50, 1));\n };\n\n useEffect(() => {\n if (!enableArrowNavigation) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'ArrowDown' || (e.key === 'Tab' && !e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.min(prev + 1, items.length - 1));\n } else if (e.key === 'ArrowUp' || (e.key === 'Tab' && e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.max(prev - 1, 0));\n } else if (e.key === 'Enter') {\n if (selectedIndex >= 0 && selectedIndex < items.length) {\n e.preventDefault();\n if (onItemSelect) {\n onItemSelect(items[selectedIndex], selectedIndex);\n }\n }\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [items, selectedIndex, onItemSelect, enableArrowNavigation]);\n\n useEffect(() => {\n if (!keyboardNav || selectedIndex < 0 || !listRef.current) return;\n const container = listRef.current;\n const selectedItem = container.querySelector(`[data-index=\"${selectedIndex}\"]`) as HTMLElement | null;\n if (selectedItem) {\n const extraMargin = 50;\n const containerScrollTop = container.scrollTop;\n const containerHeight = container.clientHeight;\n const itemTop = selectedItem.offsetTop;\n const itemBottom = itemTop + selectedItem.offsetHeight;\n if (itemTop < containerScrollTop + extraMargin) {\n container.scrollTo({ top: itemTop - extraMargin, behavior: 'smooth' });\n } else if (itemBottom > containerScrollTop + containerHeight - extraMargin) {\n container.scrollTo({\n top: itemBottom - containerHeight + extraMargin,\n behavior: 'smooth'\n });\n }\n }\n setKeyboardNav(false);\n }, [selectedIndex, keyboardNav]);\n\n return (\n
\n
\n {items.map((item, index) => (\n setSelectedIndex(index)}\n onClick={() => {\n setSelectedIndex(index);\n if (onItemSelect) {\n onItemSelect(item, index);\n }\n }}\n >\n
\n

{item}

\n
\n \n ))}\n
\n {showGradients && (\n <>\n
\n
\n \n )}\n
\n );\n};\n\nexport default AnimatedList;\n","path":"AnimatedList.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"motion","version":"^12.23.12"}]} \ No newline at end of file diff --git a/public/jsrepo/ascii-text-tw.json b/public/jsrepo/ascii-text-tw.json new file mode 100644 index 00000000..f68b355d --- /dev/null +++ b/public/jsrepo/ascii-text-tw.json @@ -0,0 +1 @@ +{"name":"ascii-text-tw","title":"ASCIIText","description":"Renders text with an animated ASCII background for a retro feel.","type":"registry:block","add":"when-added","files":[{"content":"// Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE\n\nimport { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float mouse;\nuniform float uEnableWaves;\n\nvoid main() {\n vUv = uv;\n float time = uTime * 5.;\n\n float waveFactor = uEnableWaves;\n\n vec3 transformed = position;\n\n transformed.x += sin(time + position.y) * 0.5 * waveFactor;\n transformed.y += cos(time + position.z) * 0.15 * waveFactor;\n transformed.z += sin(time + position.x) * waveFactor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n}\n`;\n\nconst fragmentShader = `\nvarying vec2 vUv;\nuniform float mouse;\nuniform float uTime;\nuniform sampler2D uTexture;\n\nvoid main() {\n float time = uTime;\n vec2 pos = vUv;\n \n float move = sin(time + mouse) * 0.01;\n float r = texture2D(uTexture, pos + cos(time * 2. - time + pos.x) * .01).r;\n float g = texture2D(uTexture, pos + tan(time * .5 + pos.x - time) * .01).g;\n float b = texture2D(uTexture, pos - cos(time * 2. + time + pos.y) * .01).b;\n float a = texture2D(uTexture, pos).a;\n gl_FragColor = vec4(r, g, b, a);\n}\n`;\n\nfunction map(n: number, start: number, stop: number, start2: number, stop2: number) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n}\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\ninterface AsciiFilterOptions {\n fontSize?: number;\n fontFamily?: string;\n charset?: string;\n invert?: boolean;\n}\n\nclass AsciiFilter {\n renderer: THREE.WebGLRenderer;\n domElement: HTMLDivElement;\n pre: HTMLPreElement;\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n deg: number;\n invert: boolean;\n fontSize: number;\n fontFamily: string;\n charset: string;\n width: number = 0;\n height: number = 0;\n center: { x: number; y: number } = { x: 0, y: 0 };\n mouse: { x: number; y: number } = { x: 0, y: 0 };\n cols: number = 0;\n rows: number = 0;\n\n constructor(renderer: THREE.WebGLRenderer, { fontSize, fontFamily, charset, invert }: AsciiFilterOptions = {}) {\n this.renderer = renderer;\n this.domElement = document.createElement('div');\n this.domElement.style.position = 'absolute';\n this.domElement.style.top = '0';\n this.domElement.style.left = '0';\n this.domElement.style.width = '100%';\n this.domElement.style.height = '100%';\n\n this.pre = document.createElement('pre');\n this.domElement.appendChild(this.pre);\n\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.domElement.appendChild(this.canvas);\n\n this.deg = 0;\n this.invert = invert ?? true;\n this.fontSize = fontSize ?? 12;\n this.fontFamily = fontFamily ?? \"'Courier New', monospace\";\n this.charset = charset ?? ' .\\'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';\n\n if (this.context) {\n this.context.imageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n }\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width: number, height: number) {\n this.width = width;\n this.height = height;\n this.renderer.setSize(width, height);\n this.reset();\n\n this.center = { x: width / 2, y: height / 2 };\n this.mouse = { x: this.center.x, y: this.center.y };\n }\n\n reset() {\n if (this.context) {\n this.context.font = `${this.fontSize}px ${this.fontFamily}`;\n const charWidth = this.context.measureText('A').width;\n\n this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));\n this.rows = Math.floor(this.height / this.fontSize);\n\n this.canvas.width = this.cols;\n this.canvas.height = this.rows;\n this.pre.style.fontFamily = this.fontFamily;\n this.pre.style.fontSize = `${this.fontSize}px`;\n this.pre.style.margin = '0';\n this.pre.style.padding = '0';\n this.pre.style.lineHeight = '1em';\n this.pre.style.position = 'absolute';\n this.pre.style.left = '50%';\n this.pre.style.top = '50%';\n this.pre.style.transform = 'translate(-50%, -50%)';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n }\n\n render(scene: THREE.Scene, camera: THREE.Camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\n if (this.context) {\n this.context.clearRect(0, 0, w, h);\n if (this.context && w && h) {\n this.context.drawImage(this.renderer.domElement, 0, 0, w, h);\n }\n\n this.asciify(this.context, w, h);\n this.hue();\n }\n }\n\n onMouseMove(e: MouseEvent) {\n this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };\n }\n\n get dx() {\n return this.mouse.x - this.center.x;\n }\n\n get dy() {\n return this.mouse.y - this.center.y;\n }\n\n hue() {\n const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;\n this.deg += (deg - this.deg) * 0.075;\n this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;\n }\n\n asciify(ctx: CanvasRenderingContext2D, w: number, h: number) {\n if (w && h) {\n const imgData = ctx.getImageData(0, 0, w, h).data;\n let str = '';\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = x * 4 + y * 4 * w;\n const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];\n\n if (a === 0) {\n str += ' ';\n continue;\n }\n\n let gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;\n let idx = Math.floor((1 - gray) * (this.charset.length - 1));\n if (this.invert) idx = this.charset.length - idx - 1;\n str += this.charset[idx];\n }\n str += '\\n';\n }\n this.pre.innerHTML = str;\n }\n }\n\n dispose() {\n document.removeEventListener('mousemove', this.onMouseMove);\n }\n}\n\ninterface CanvasTxtOptions {\n fontSize?: number;\n fontFamily?: string;\n color?: string;\n}\n\nclass CanvasTxt {\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n txt: string;\n fontSize: number;\n fontFamily: string;\n color: string;\n font: string;\n\n constructor(txt: string, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' }: CanvasTxtOptions = {}) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.txt = txt;\n this.fontSize = fontSize;\n this.fontFamily = fontFamily;\n this.color = color;\n\n this.font = `600 ${this.fontSize}px ${this.fontFamily}`;\n }\n\n resize() {\n if (this.context) {\n this.context.font = this.font;\n const metrics = this.context.measureText(this.txt);\n\n const textWidth = Math.ceil(metrics.width) + 20;\n const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;\n\n this.canvas.width = textWidth;\n this.canvas.height = textHeight;\n }\n }\n\n render() {\n if (this.context) {\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.context.fillStyle = this.color;\n this.context.font = this.font;\n\n const metrics = this.context.measureText(this.txt);\n const yPos = 10 + metrics.actualBoundingBoxAscent;\n\n this.context.fillText(this.txt, 10, yPos);\n }\n }\n\n get width() {\n return this.canvas.width;\n }\n\n get height() {\n return this.canvas.height;\n }\n\n get texture() {\n return this.canvas;\n }\n}\n\ninterface CanvAsciiOptions {\n text: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n enableWaves: boolean;\n}\n\nclass CanvAscii {\n textString: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n container: HTMLElement;\n width: number;\n height: number;\n enableWaves: boolean;\n camera: THREE.PerspectiveCamera;\n scene: THREE.Scene;\n mouse: { x: number; y: number };\n textCanvas!: CanvasTxt;\n texture!: THREE.CanvasTexture;\n geometry!: THREE.PlaneGeometry;\n material!: THREE.ShaderMaterial;\n mesh!: THREE.Mesh;\n renderer!: THREE.WebGLRenderer;\n filter!: AsciiFilter;\n center!: { x: number; y: number };\n animationFrameId: number = 0;\n\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves }: CanvAsciiOptions,\n containerElem: HTMLElement,\n width: number,\n height: number\n ) {\n this.textString = text;\n this.asciiFontSize = asciiFontSize;\n this.textFontSize = textFontSize;\n this.textColor = textColor;\n this.planeBaseHeight = planeBaseHeight;\n this.container = containerElem;\n this.width = width;\n this.height = height;\n this.enableWaves = enableWaves;\n\n this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);\n this.camera.position.z = 30;\n\n this.scene = new THREE.Scene();\n this.mouse = { x: 0, y: 0 };\n\n this.onMouseMove = this.onMouseMove.bind(this);\n\n this.setMesh();\n this.setRenderer();\n }\n\n setMesh() {\n this.textCanvas = new CanvasTxt(this.textString, {\n fontSize: this.textFontSize,\n fontFamily: 'IBM Plex Mono',\n color: this.textColor\n });\n this.textCanvas.resize();\n this.textCanvas.render();\n\n this.texture = new THREE.CanvasTexture(this.textCanvas.texture);\n this.texture.minFilter = THREE.NearestFilter;\n\n const textAspect = this.textCanvas.width / this.textCanvas.height;\n const baseH = this.planeBaseHeight;\n const planeW = baseH * textAspect;\n const planeH = baseH;\n\n this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);\n this.material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n transparent: true,\n uniforms: {\n uTime: { value: 0 },\n mouse: { value: 1.0 },\n uTexture: { value: this.texture },\n uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }\n }\n });\n\n this.mesh = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.mesh);\n }\n\n setRenderer() {\n this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });\n this.renderer.setPixelRatio(1);\n this.renderer.setClearColor(0x000000, 0);\n\n this.filter = new AsciiFilter(this.renderer, {\n fontFamily: 'IBM Plex Mono',\n fontSize: this.asciiFontSize,\n invert: true\n });\n\n this.container.appendChild(this.filter.domElement);\n this.setSize(this.width, this.height);\n\n this.container.addEventListener('mousemove', this.onMouseMove);\n this.container.addEventListener('touchmove', this.onMouseMove);\n }\n\n setSize(w: number, h: number) {\n this.width = w;\n this.height = h;\n\n this.camera.aspect = w / h;\n this.camera.updateProjectionMatrix();\n\n this.filter.setSize(w, h);\n\n this.center = { x: w / 2, y: h / 2 };\n }\n\n load() {\n this.animate();\n }\n\n onMouseMove(evt: MouseEvent | TouchEvent) {\n const e = (evt as TouchEvent).touches ? (evt as TouchEvent).touches[0] : (evt as MouseEvent);\n const bounds = this.container.getBoundingClientRect();\n const x = e.clientX - bounds.left;\n const y = e.clientY - bounds.top;\n this.mouse = { x, y };\n }\n\n animate() {\n const animateFrame = () => {\n this.animationFrameId = requestAnimationFrame(animateFrame);\n this.render();\n };\n animateFrame();\n }\n\n render() {\n const time = new Date().getTime() * 0.001;\n\n this.textCanvas.render();\n this.texture.needsUpdate = true;\n\n (this.mesh.material as THREE.ShaderMaterial).uniforms.uTime.value = Math.sin(time);\n\n this.updateRotation();\n this.filter.render(this.scene, this.camera);\n }\n\n updateRotation() {\n const x = map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = map(this.mouse.x, 0, this.width, -0.5, 0.5);\n\n this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;\n this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;\n }\n\n clear() {\n this.scene.traverse(object => {\n const obj = object as unknown as THREE.Mesh;\n if (!obj.isMesh) return;\n [obj.material].flat().forEach(material => {\n material.dispose();\n Object.keys(material).forEach(key => {\n const matProp = material[key as keyof typeof material];\n if (matProp && typeof matProp === 'object' && 'dispose' in matProp && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n });\n obj.geometry.dispose();\n });\n this.scene.clear();\n }\n\n dispose() {\n cancelAnimationFrame(this.animationFrameId);\n this.filter.dispose();\n this.container.removeChild(this.filter.domElement);\n this.container.removeEventListener('mousemove', this.onMouseMove);\n this.container.removeEventListener('touchmove', this.onMouseMove);\n this.clear();\n this.renderer.dispose();\n }\n}\n\ninterface ASCIITextProps {\n text?: string;\n asciiFontSize?: number;\n textFontSize?: number;\n textColor?: string;\n planeBaseHeight?: number;\n enableWaves?: boolean;\n}\n\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}: ASCIITextProps) {\n const containerRef = useRef(null);\n const asciiRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const { width, height } = containerRef.current.getBoundingClientRect();\n\n if (width === 0 || height === 0) {\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {\n const { width: w, height: h } = entry.boundingClientRect;\n\n asciiRef.current = new CanvAscii(\n {\n text,\n asciiFontSize,\n textFontSize,\n textColor,\n planeBaseHeight,\n enableWaves\n },\n containerRef.current!,\n w,\n h\n );\n asciiRef.current.load();\n\n observer.disconnect();\n }\n },\n { threshold: 0.1 }\n );\n\n observer.observe(containerRef.current);\n\n return () => {\n observer.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n }\n };\n }\n\n asciiRef.current = new CanvAscii(\n {\n text,\n asciiFontSize,\n textFontSize,\n textColor,\n planeBaseHeight,\n enableWaves\n },\n containerRef.current,\n width,\n height\n );\n asciiRef.current.load();\n\n const ro = new ResizeObserver(entries => {\n if (!entries[0] || !asciiRef.current) return;\n const { width: w, height: h } = entries[0].contentRect;\n if (w > 0 && h > 0) {\n asciiRef.current.setSize(w, h);\n }\n });\n ro.observe(containerRef.current);\n\n return () => {\n ro.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n }\n };\n }, [text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves]);\n\n return (\n \n \n \n );\n}\n","path":"ASCIIText.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"three","version":"^0.167.1"}]} \ No newline at end of file diff --git a/public/jsrepo/ascii-text.json b/public/jsrepo/ascii-text.json new file mode 100644 index 00000000..5552f776 --- /dev/null +++ b/public/jsrepo/ascii-text.json @@ -0,0 +1 @@ +{"name":"ascii-text","title":"ASCIIText","description":"Renders text with an animated ASCII background for a retro feel.","type":"registry:block","add":"when-added","files":[{"content":"// Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE\n\nimport { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float mouse;\nuniform float uEnableWaves;\n\nvoid main() {\n vUv = uv;\n float time = uTime * 5.;\n\n float waveFactor = uEnableWaves;\n\n vec3 transformed = position;\n\n transformed.x += sin(time + position.y) * 0.5 * waveFactor;\n transformed.y += cos(time + position.z) * 0.15 * waveFactor;\n transformed.z += sin(time + position.x) * waveFactor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n}\n`;\n\nconst fragmentShader = `\nvarying vec2 vUv;\nuniform float mouse;\nuniform float uTime;\nuniform sampler2D uTexture;\n\nvoid main() {\n float time = uTime;\n vec2 pos = vUv;\n \n float move = sin(time + mouse) * 0.01;\n float r = texture2D(uTexture, pos + cos(time * 2. - time + pos.x) * .01).r;\n float g = texture2D(uTexture, pos + tan(time * .5 + pos.x - time) * .01).g;\n float b = texture2D(uTexture, pos - cos(time * 2. + time + pos.y) * .01).b;\n float a = texture2D(uTexture, pos).a;\n gl_FragColor = vec4(r, g, b, a);\n}\n`;\n\nfunction map(n: number, start: number, stop: number, start2: number, stop2: number) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n}\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\ninterface AsciiFilterOptions {\n fontSize?: number;\n fontFamily?: string;\n charset?: string;\n invert?: boolean;\n}\n\nclass AsciiFilter {\n renderer!: THREE.WebGLRenderer;\n domElement: HTMLDivElement;\n pre: HTMLPreElement;\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n deg: number;\n invert: boolean;\n fontSize: number;\n fontFamily: string;\n charset: string;\n width: number = 0;\n height: number = 0;\n center: { x: number; y: number } = { x: 0, y: 0 };\n mouse: { x: number; y: number } = { x: 0, y: 0 };\n cols: number = 0;\n rows: number = 0;\n\n constructor(renderer: THREE.WebGLRenderer, { fontSize, fontFamily, charset, invert }: AsciiFilterOptions = {}) {\n this.renderer = renderer;\n this.domElement = document.createElement('div');\n this.domElement.style.position = 'absolute';\n this.domElement.style.top = '0';\n this.domElement.style.left = '0';\n this.domElement.style.width = '100%';\n this.domElement.style.height = '100%';\n\n this.pre = document.createElement('pre');\n this.domElement.appendChild(this.pre);\n\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.domElement.appendChild(this.canvas);\n\n this.deg = 0;\n this.invert = invert ?? true;\n this.fontSize = fontSize ?? 12;\n this.fontFamily = fontFamily ?? \"'Courier New', monospace\";\n this.charset = charset ?? ' .\\'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';\n\n if (this.context) {\n this.context.imageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n }\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width: number, height: number) {\n this.width = width;\n this.height = height;\n this.renderer.setSize(width, height);\n this.reset();\n\n this.center = { x: width / 2, y: height / 2 };\n this.mouse = { x: this.center.x, y: this.center.y };\n }\n\n reset() {\n if (this.context) {\n this.context.font = `${this.fontSize}px ${this.fontFamily}`;\n const charWidth = this.context.measureText('A').width;\n\n this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));\n this.rows = Math.floor(this.height / this.fontSize);\n\n this.canvas.width = this.cols;\n this.canvas.height = this.rows;\n this.pre.style.fontFamily = this.fontFamily;\n this.pre.style.fontSize = `${this.fontSize}px`;\n this.pre.style.margin = '0';\n this.pre.style.padding = '0';\n this.pre.style.lineHeight = '1em';\n this.pre.style.position = 'absolute';\n this.pre.style.left = '50%';\n this.pre.style.top = '50%';\n this.pre.style.transform = 'translate(-50%, -50%)';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n }\n\n render(scene: THREE.Scene, camera: THREE.Camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\n if (this.context) {\n this.context.clearRect(0, 0, w, h);\n this.context.drawImage(this.renderer.domElement, 0, 0, w, h);\n this.asciify(this.context, w, h);\n this.hue();\n }\n }\n\n onMouseMove(e: MouseEvent) {\n this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };\n }\n\n get dx() {\n return this.mouse.x - this.center.x;\n }\n\n get dy() {\n return this.mouse.y - this.center.y;\n }\n\n hue() {\n const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;\n this.deg += (deg - this.deg) * 0.075;\n this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;\n }\n\n asciify(ctx: CanvasRenderingContext2D, w: number, h: number) {\n const imgData = ctx.getImageData(0, 0, w, h).data;\n let str = '';\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = x * 4 + y * 4 * w;\n const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];\n\n if (a === 0) {\n str += ' ';\n continue;\n }\n\n let gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;\n let idx = Math.floor((1 - gray) * (this.charset.length - 1));\n if (this.invert) idx = this.charset.length - idx - 1;\n str += this.charset[idx];\n }\n str += '\\n';\n }\n this.pre.innerHTML = str;\n }\n\n dispose() {\n document.removeEventListener('mousemove', this.onMouseMove);\n }\n}\n\ninterface CanvasTxtOptions {\n fontSize?: number;\n fontFamily?: string;\n color?: string;\n}\n\nclass CanvasTxt {\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n txt: string;\n fontSize: number;\n fontFamily: string;\n color: string;\n font: string;\n\n constructor(txt: string, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' }: CanvasTxtOptions = {}) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.txt = txt;\n this.fontSize = fontSize;\n this.fontFamily = fontFamily;\n this.color = color;\n\n this.font = `600 ${this.fontSize}px ${this.fontFamily}`;\n }\n\n resize() {\n if (this.context) {\n this.context.font = this.font;\n const metrics = this.context.measureText(this.txt);\n\n const textWidth = Math.ceil(metrics.width) + 20;\n const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;\n\n this.canvas.width = textWidth;\n this.canvas.height = textHeight;\n }\n }\n\n render() {\n if (this.context) {\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.context.fillStyle = this.color;\n this.context.font = this.font;\n\n const metrics = this.context.measureText(this.txt);\n const yPos = 10 + metrics.actualBoundingBoxAscent;\n\n this.context.fillText(this.txt, 10, yPos);\n }\n }\n\n get width() {\n return this.canvas.width;\n }\n\n get height() {\n return this.canvas.height;\n }\n\n get texture() {\n return this.canvas;\n }\n}\n\ninterface CanvAsciiOptions {\n text: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n enableWaves: boolean;\n}\n\nclass CanvAscii {\n textString: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n container: HTMLElement;\n width: number;\n height: number;\n enableWaves: boolean;\n camera: THREE.PerspectiveCamera;\n scene: THREE.Scene;\n mouse: { x: number; y: number };\n textCanvas!: CanvasTxt;\n texture!: THREE.CanvasTexture;\n geometry: THREE.PlaneGeometry | undefined;\n material: THREE.ShaderMaterial | undefined;\n mesh!: THREE.Mesh;\n renderer!: THREE.WebGLRenderer;\n filter!: AsciiFilter;\n center: { x: number; y: number } = { x: 0, y: 0 };\n animationFrameId: number = 0;\n\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves }: CanvAsciiOptions,\n containerElem: HTMLElement,\n width: number,\n height: number\n ) {\n this.textString = text;\n this.asciiFontSize = asciiFontSize;\n this.textFontSize = textFontSize;\n this.textColor = textColor;\n this.planeBaseHeight = planeBaseHeight;\n this.container = containerElem;\n this.width = width;\n this.height = height;\n this.enableWaves = enableWaves;\n\n this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);\n this.camera.position.z = 30;\n\n this.scene = new THREE.Scene();\n this.mouse = { x: 0, y: 0 };\n\n this.onMouseMove = this.onMouseMove.bind(this);\n\n this.setMesh();\n this.setRenderer();\n }\n\n setMesh() {\n this.textCanvas = new CanvasTxt(this.textString, {\n fontSize: this.textFontSize,\n fontFamily: 'IBM Plex Mono',\n color: this.textColor\n });\n this.textCanvas.resize();\n this.textCanvas.render();\n\n this.texture = new THREE.CanvasTexture(this.textCanvas.texture);\n this.texture.minFilter = THREE.NearestFilter;\n\n const textAspect = this.textCanvas.width / this.textCanvas.height;\n const baseH = this.planeBaseHeight;\n const planeW = baseH * textAspect;\n const planeH = baseH;\n\n this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);\n this.material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n transparent: true,\n uniforms: {\n uTime: { value: 0 },\n mouse: { value: 1.0 },\n uTexture: { value: this.texture },\n uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }\n }\n });\n\n this.mesh = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.mesh);\n }\n\n setRenderer() {\n this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });\n this.renderer.setPixelRatio(1);\n this.renderer.setClearColor(0x000000, 0);\n\n this.filter = new AsciiFilter(this.renderer, {\n fontFamily: 'IBM Plex Mono',\n fontSize: this.asciiFontSize,\n invert: true\n });\n\n this.container.appendChild(this.filter.domElement);\n this.setSize(this.width, this.height);\n\n this.container.addEventListener('mousemove', this.onMouseMove);\n this.container.addEventListener('touchmove', this.onMouseMove);\n }\n\n setSize(w: number, h: number) {\n this.width = w;\n this.height = h;\n\n this.camera.aspect = w / h;\n this.camera.updateProjectionMatrix();\n\n this.filter.setSize(w, h);\n\n this.center = { x: w / 2, y: h / 2 };\n }\n\n load() {\n this.animate();\n }\n\n onMouseMove(evt: MouseEvent | TouchEvent) {\n const e = (evt as TouchEvent).touches ? (evt as TouchEvent).touches[0] : (evt as MouseEvent);\n const bounds = this.container.getBoundingClientRect();\n const x = e.clientX - bounds.left;\n const y = e.clientY - bounds.top;\n this.mouse = { x, y };\n }\n\n animate() {\n const animateFrame = () => {\n this.animationFrameId = requestAnimationFrame(animateFrame);\n this.render();\n };\n animateFrame();\n }\n\n render() {\n const time = new Date().getTime() * 0.001;\n\n this.textCanvas.render();\n this.texture.needsUpdate = true;\n\n (this.mesh.material as THREE.ShaderMaterial).uniforms.uTime.value = Math.sin(time);\n\n this.updateRotation();\n this.filter.render(this.scene, this.camera);\n }\n\n updateRotation() {\n const x = map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = map(this.mouse.x, 0, this.width, -0.5, 0.5);\n\n this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;\n this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;\n }\n\n clear() {\n this.scene.traverse(object => {\n const obj = object as unknown as THREE.Mesh;\n if (!obj.isMesh) return;\n [obj.material].flat().forEach(material => {\n material.dispose();\n Object.keys(material).forEach(key => {\n const matProp = material[key as keyof typeof material];\n if (matProp && typeof matProp === 'object' && 'dispose' in matProp && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n });\n obj.geometry.dispose();\n });\n this.scene.clear();\n }\n\n dispose() {\n cancelAnimationFrame(this.animationFrameId);\n this.filter.dispose();\n this.container.removeChild(this.filter.domElement);\n this.container.removeEventListener('mousemove', this.onMouseMove);\n this.container.removeEventListener('touchmove', this.onMouseMove);\n this.clear();\n this.renderer.dispose();\n }\n}\n\ninterface ASCIITextProps {\n text?: string;\n asciiFontSize?: number;\n textFontSize?: number;\n textColor?: string;\n planeBaseHeight?: number;\n enableWaves?: boolean;\n}\n\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}: ASCIITextProps) {\n const containerRef = useRef(null);\n const asciiRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const { width, height } = containerRef.current.getBoundingClientRect();\n\n if (width === 0 || height === 0) {\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {\n const { width: w, height: h } = entry.boundingClientRect;\n\n asciiRef.current = new CanvAscii(\n {\n text,\n asciiFontSize,\n textFontSize,\n textColor,\n planeBaseHeight,\n enableWaves\n },\n containerRef.current!,\n w,\n h\n );\n asciiRef.current.load();\n\n observer.disconnect();\n }\n },\n { threshold: 0.1 }\n );\n\n observer.observe(containerRef.current);\n\n return () => {\n observer.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n }\n };\n }\n\n asciiRef.current = new CanvAscii(\n {\n text,\n asciiFontSize,\n textFontSize,\n textColor,\n planeBaseHeight,\n enableWaves\n },\n containerRef.current,\n width,\n height\n );\n asciiRef.current.load();\n\n const ro = new ResizeObserver(entries => {\n if (!entries[0] || !asciiRef.current) return;\n const { width: w, height: h } = entries[0].contentRect;\n if (w > 0 && h > 0) {\n asciiRef.current.setSize(w, h);\n }\n });\n ro.observe(containerRef.current);\n\n return () => {\n ro.disconnect();\n asciiRef.current?.dispose();\n };\n }, [text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves]);\n\n return (\n \n \n \n );\n}\n","path":"ASCIIText.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"three","version":"^0.167.1"}]} \ No newline at end of file diff --git a/public/jsrepo/aurora-tw.json b/public/jsrepo/aurora-tw.json new file mode 100644 index 00000000..4e413561 --- /dev/null +++ b/public/jsrepo/aurora-tw.json @@ -0,0 +1 @@ +{"name":"aurora-tw","title":"Aurora","description":"Flowing aurora gradient background.","type":"registry:block","add":"when-added","files":[{"content":"import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\n\nconst VERT = `#version 300 es\nin vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst FRAG = `#version 300 es\nprecision highp float;\n\nuniform float uTime;\nuniform float uAmplitude;\nuniform vec3 uColorStops[3];\nuniform vec2 uResolution;\nuniform float uBlend;\n\nout vec4 fragColor;\n\nvec3 permute(vec3 x) {\n return mod(((x * 34.0) + 1.0) * x, 289.0);\n}\n\nfloat snoise(vec2 v){\n const vec4 C = vec4(\n 0.211324865405187, 0.366025403784439,\n -0.577350269189626, 0.024390243902439\n );\n vec2 i = floor(v + dot(v, C.yy));\n vec2 x0 = v - i + dot(i, C.xx);\n vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n vec4 x12 = x0.xyxy + C.xxzz;\n x12.xy -= i1;\n i = mod(i, 289.0);\n\n vec3 p = permute(\n permute(i.y + vec3(0.0, i1.y, 1.0))\n + i.x + vec3(0.0, i1.x, 1.0)\n );\n\n vec3 m = max(\n 0.5 - vec3(\n dot(x0, x0),\n dot(x12.xy, x12.xy),\n dot(x12.zw, x12.zw)\n ), \n 0.0\n );\n m = m * m;\n m = m * m;\n\n vec3 x = 2.0 * fract(p * C.www) - 1.0;\n vec3 h = abs(x) - 0.5;\n vec3 ox = floor(x + 0.5);\n vec3 a0 = x - ox;\n m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);\n\n vec3 g;\n g.x = a0.x * x0.x + h.x * x0.y;\n g.yz = a0.yz * x12.xz + h.yz * x12.yw;\n return 130.0 * dot(m, g);\n}\n\nstruct ColorStop {\n vec3 color;\n float position;\n};\n\n#define COLOR_RAMP(colors, factor, finalColor) { \\\n int index = 0; \\\n for (int i = 0; i < 2; i++) { \\\n ColorStop currentColor = colors[i]; \\\n bool isInBetween = currentColor.position <= factor; \\\n index = int(mix(float(index), float(i), float(isInBetween))); \\\n } \\\n ColorStop currentColor = colors[index]; \\\n ColorStop nextColor = colors[index + 1]; \\\n float range = nextColor.position - currentColor.position; \\\n float lerpFactor = (factor - currentColor.position) / range; \\\n finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \\\n}\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / uResolution;\n \n ColorStop colors[3];\n colors[0] = ColorStop(uColorStops[0], 0.0);\n colors[1] = ColorStop(uColorStops[1], 0.5);\n colors[2] = ColorStop(uColorStops[2], 1.0);\n \n vec3 rampColor;\n COLOR_RAMP(colors, uv.x, rampColor);\n \n float height = snoise(vec2(uv.x * 2.0 + uTime * 0.1, uTime * 0.25)) * 0.5 * uAmplitude;\n height = exp(height);\n height = (uv.y * 2.0 - height + 0.2);\n float intensity = 0.6 * height;\n \n float midPoint = 0.20;\n float auroraAlpha = smoothstep(midPoint - uBlend * 0.5, midPoint + uBlend * 0.5, intensity);\n \n vec3 auroraColor = intensity * rampColor;\n \n fragColor = vec4(auroraColor * auroraAlpha, auroraAlpha);\n}\n`;\n\ninterface AuroraProps {\n colorStops?: string[];\n amplitude?: number;\n blend?: number;\n time?: number;\n speed?: number;\n}\n\nexport default function Aurora(props: AuroraProps) {\n const { colorStops = ['#5227FF', '#7cff67', '#5227FF'], amplitude = 1.0, blend = 0.5 } = props;\n const propsRef = useRef(props);\n propsRef.current = props;\n\n const ctnDom = useRef(null);\n\n useEffect(() => {\n const ctn = ctnDom.current;\n if (!ctn) return;\n\n const renderer = new Renderer({\n alpha: true,\n premultipliedAlpha: true,\n antialias: true\n });\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n gl.canvas.style.backgroundColor = 'transparent';\n\n let program: Program | undefined;\n\n function resize() {\n if (!ctn) return;\n const width = ctn.offsetWidth;\n const height = ctn.offsetHeight;\n renderer.setSize(width, height);\n if (program) {\n program.uniforms.uResolution.value = [width, height];\n }\n }\n window.addEventListener('resize', resize);\n\n const geometry = new Triangle(gl);\n if (geometry.attributes.uv) {\n delete geometry.attributes.uv;\n }\n\n const colorStopsArray = colorStops.map(hex => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n\n program = new Program(gl, {\n vertex: VERT,\n fragment: FRAG,\n uniforms: {\n uTime: { value: 0 },\n uAmplitude: { value: amplitude },\n uColorStops: { value: colorStopsArray },\n uResolution: { value: [ctn.offsetWidth, ctn.offsetHeight] },\n uBlend: { value: blend }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n ctn.appendChild(gl.canvas);\n\n let animateId = 0;\n const update = (t: number) => {\n animateId = requestAnimationFrame(update);\n const { time = t * 0.01, speed = 1.0 } = propsRef.current;\n if (program) {\n program.uniforms.uTime.value = time * speed * 0.1;\n program.uniforms.uAmplitude.value = propsRef.current.amplitude ?? 1.0;\n program.uniforms.uBlend.value = propsRef.current.blend ?? blend;\n const stops = propsRef.current.colorStops ?? colorStops;\n program.uniforms.uColorStops.value = stops.map((hex: string) => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n renderer.render({ scene: mesh });\n }\n };\n animateId = requestAnimationFrame(update);\n\n resize();\n\n return () => {\n cancelAnimationFrame(animateId);\n window.removeEventListener('resize', resize);\n if (ctn && gl.canvas.parentNode === ctn) {\n ctn.removeChild(gl.canvas);\n }\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n }, [amplitude]);\n\n return
;\n}\n","path":"Aurora.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"ogl","version":"^1.0.11"}]} \ No newline at end of file diff --git a/public/jsrepo/aurora.json b/public/jsrepo/aurora.json new file mode 100644 index 00000000..13303970 --- /dev/null +++ b/public/jsrepo/aurora.json @@ -0,0 +1 @@ +{"name":"aurora","title":"Aurora","description":"Flowing aurora gradient background.","type":"registry:block","add":"when-added","files":[{"content":".aurora-container {\n width: 100%;\n height: 100%;\n}\n","path":"Aurora.css","_imports_":[]},{"content":"import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\n\nimport './Aurora.css';\n\nconst VERT = `#version 300 es\nin vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst FRAG = `#version 300 es\nprecision highp float;\n\nuniform float uTime;\nuniform float uAmplitude;\nuniform vec3 uColorStops[3];\nuniform vec2 uResolution;\nuniform float uBlend;\n\nout vec4 fragColor;\n\nvec3 permute(vec3 x) {\n return mod(((x * 34.0) + 1.0) * x, 289.0);\n}\n\nfloat snoise(vec2 v){\n const vec4 C = vec4(\n 0.211324865405187, 0.366025403784439,\n -0.577350269189626, 0.024390243902439\n );\n vec2 i = floor(v + dot(v, C.yy));\n vec2 x0 = v - i + dot(i, C.xx);\n vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n vec4 x12 = x0.xyxy + C.xxzz;\n x12.xy -= i1;\n i = mod(i, 289.0);\n\n vec3 p = permute(\n permute(i.y + vec3(0.0, i1.y, 1.0))\n + i.x + vec3(0.0, i1.x, 1.0)\n );\n\n vec3 m = max(\n 0.5 - vec3(\n dot(x0, x0),\n dot(x12.xy, x12.xy),\n dot(x12.zw, x12.zw)\n ), \n 0.0\n );\n m = m * m;\n m = m * m;\n\n vec3 x = 2.0 * fract(p * C.www) - 1.0;\n vec3 h = abs(x) - 0.5;\n vec3 ox = floor(x + 0.5);\n vec3 a0 = x - ox;\n m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);\n\n vec3 g;\n g.x = a0.x * x0.x + h.x * x0.y;\n g.yz = a0.yz * x12.xz + h.yz * x12.yw;\n return 130.0 * dot(m, g);\n}\n\nstruct ColorStop {\n vec3 color;\n float position;\n};\n\n#define COLOR_RAMP(colors, factor, finalColor) { \\\n int index = 0; \\\n for (int i = 0; i < 2; i++) { \\\n ColorStop currentColor = colors[i]; \\\n bool isInBetween = currentColor.position <= factor; \\\n index = int(mix(float(index), float(i), float(isInBetween))); \\\n } \\\n ColorStop currentColor = colors[index]; \\\n ColorStop nextColor = colors[index + 1]; \\\n float range = nextColor.position - currentColor.position; \\\n float lerpFactor = (factor - currentColor.position) / range; \\\n finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \\\n}\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / uResolution;\n \n ColorStop colors[3];\n colors[0] = ColorStop(uColorStops[0], 0.0);\n colors[1] = ColorStop(uColorStops[1], 0.5);\n colors[2] = ColorStop(uColorStops[2], 1.0);\n \n vec3 rampColor;\n COLOR_RAMP(colors, uv.x, rampColor);\n \n float height = snoise(vec2(uv.x * 2.0 + uTime * 0.1, uTime * 0.25)) * 0.5 * uAmplitude;\n height = exp(height);\n height = (uv.y * 2.0 - height + 0.2);\n float intensity = 0.6 * height;\n \n float midPoint = 0.20;\n float auroraAlpha = smoothstep(midPoint - uBlend * 0.5, midPoint + uBlend * 0.5, intensity);\n \n vec3 auroraColor = intensity * rampColor;\n \n fragColor = vec4(auroraColor * auroraAlpha, auroraAlpha);\n}\n`;\n\ninterface AuroraProps {\n colorStops?: string[];\n amplitude?: number;\n blend?: number;\n time?: number;\n speed?: number;\n}\n\nexport default function Aurora(props: AuroraProps) {\n const { colorStops = ['#5227FF', '#7cff67', '#5227FF'], amplitude = 1.0, blend = 0.5 } = props;\n const propsRef = useRef(props);\n propsRef.current = props;\n\n const ctnDom = useRef(null);\n\n useEffect(() => {\n const ctn = ctnDom.current;\n if (!ctn) return;\n\n const renderer = new Renderer({\n alpha: true,\n premultipliedAlpha: true,\n antialias: true\n });\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n gl.canvas.style.backgroundColor = 'transparent';\n\n let program: Program | undefined;\n\n function resize() {\n if (!ctn) return;\n const width = ctn.offsetWidth;\n const height = ctn.offsetHeight;\n renderer.setSize(width, height);\n if (program) {\n program.uniforms.uResolution.value = [width, height];\n }\n }\n window.addEventListener('resize', resize);\n\n const geometry = new Triangle(gl);\n if (geometry.attributes.uv) {\n delete geometry.attributes.uv;\n }\n\n const colorStopsArray = colorStops.map(hex => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n\n program = new Program(gl, {\n vertex: VERT,\n fragment: FRAG,\n uniforms: {\n uTime: { value: 0 },\n uAmplitude: { value: amplitude },\n uColorStops: { value: colorStopsArray },\n uResolution: { value: [ctn.offsetWidth, ctn.offsetHeight] },\n uBlend: { value: blend }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n ctn.appendChild(gl.canvas);\n\n let animateId = 0;\n const update = (t: number) => {\n animateId = requestAnimationFrame(update);\n const { time = t * 0.01, speed = 1.0 } = propsRef.current;\n if (program) {\n program.uniforms.uTime.value = time * speed * 0.1;\n program.uniforms.uAmplitude.value = propsRef.current.amplitude ?? 1.0;\n program.uniforms.uBlend.value = propsRef.current.blend ?? blend;\n const stops = propsRef.current.colorStops ?? colorStops;\n program.uniforms.uColorStops.value = stops.map((hex: string) => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n renderer.render({ scene: mesh });\n }\n };\n animateId = requestAnimationFrame(update);\n\n resize();\n\n return () => {\n cancelAnimationFrame(animateId);\n window.removeEventListener('resize', resize);\n if (ctn && gl.canvas.parentNode === ctn) {\n ctn.removeChild(gl.canvas);\n }\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n }, [amplitude]);\n\n return
;\n}\n","path":"Aurora.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"ogl","version":"^1.0.11"}]} \ No newline at end of file diff --git a/public/jsrepo/balatro-tw.json b/public/jsrepo/balatro-tw.json new file mode 100644 index 00000000..62b55f65 --- /dev/null +++ b/public/jsrepo/balatro-tw.json @@ -0,0 +1 @@ +{"name":"balatro-tw","title":"Balatro","description":"The balatro shader, fully customizalbe and interactive.","type":"registry:block","add":"when-added","files":[{"content":"import { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\n\ninterface BalatroProps {\n spinRotation?: number;\n spinSpeed?: number;\n offset?: [number, number];\n color1?: string;\n color2?: string;\n color3?: string;\n contrast?: number;\n lighting?: number;\n spinAmount?: number;\n pixelFilter?: number;\n spinEase?: number;\n isRotate?: boolean;\n mouseInteraction?: boolean;\n}\n\nfunction hexToVec4(hex: string): [number, number, number, number] {\n let hexStr = hex.replace('#', '');\n let r = 0,\n g = 0,\n b = 0,\n a = 1;\n if (hexStr.length === 6) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n } else if (hexStr.length === 8) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n a = parseInt(hexStr.slice(6, 8), 16) / 255;\n }\n return [r, g, b, a];\n}\n\nconst vertexShader = `\nattribute vec2 uv;\nattribute vec2 position;\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0, 1);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\n#define PI 3.14159265359\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uSpinRotation;\nuniform float uSpinSpeed;\nuniform vec2 uOffset;\nuniform vec4 uColor1;\nuniform vec4 uColor2;\nuniform vec4 uColor3;\nuniform float uContrast;\nuniform float uLighting;\nuniform float uSpinAmount;\nuniform float uPixelFilter;\nuniform float uSpinEase;\nuniform bool uIsRotate;\nuniform vec2 uMouse;\n\nvarying vec2 vUv;\n\nvec4 effect(vec2 screenSize, vec2 screen_coords) {\n float pixel_size = length(screenSize.xy) / uPixelFilter;\n vec2 uv = (floor(screen_coords.xy * (1.0 / pixel_size)) * pixel_size - 0.5 * screenSize.xy) / length(screenSize.xy) - uOffset;\n float uv_len = length(uv);\n \n float speed = (uSpinRotation * uSpinEase * 0.2);\n if(uIsRotate){\n speed = iTime * speed;\n }\n speed += 302.2;\n \n float mouseInfluence = (uMouse.x * 2.0 - 1.0);\n speed += mouseInfluence * 0.1;\n \n float new_pixel_angle = atan(uv.y, uv.x) + speed - uSpinEase * 20.0 * (uSpinAmount * uv_len + (1.0 - uSpinAmount));\n vec2 mid = (screenSize.xy / length(screenSize.xy)) / 2.0;\n uv = (vec2(uv_len * cos(new_pixel_angle) + mid.x, uv_len * sin(new_pixel_angle) + mid.y) - mid);\n \n uv *= 30.0;\n float baseSpeed = iTime * uSpinSpeed;\n speed = baseSpeed + mouseInfluence * 2.0;\n \n vec2 uv2 = vec2(uv.x + uv.y);\n \n for(int i = 0; i < 5; i++) {\n uv2 += sin(max(uv.x, uv.y)) + uv;\n uv += 0.5 * vec2(\n cos(5.1123314 + 0.353 * uv2.y + speed * 0.131121),\n sin(uv2.x - 0.113 * speed)\n );\n uv -= cos(uv.x + uv.y) - sin(uv.x * 0.711 - uv.y);\n }\n \n float contrast_mod = (0.25 * uContrast + 0.5 * uSpinAmount + 1.2);\n float paint_res = min(2.0, max(0.0, length(uv) * 0.035 * contrast_mod));\n float c1p = max(0.0, 1.0 - contrast_mod * abs(1.0 - paint_res));\n float c2p = max(0.0, 1.0 - contrast_mod * abs(paint_res));\n float c3p = 1.0 - min(1.0, c1p + c2p);\n float light = (uLighting - 0.2) * max(c1p * 5.0 - 4.0, 0.0) + uLighting * max(c2p * 5.0 - 4.0, 0.0);\n \n return (0.3 / uContrast) * uColor1 + (1.0 - 0.3 / uContrast) * (uColor1 * c1p + uColor2 * c2p + vec4(c3p * uColor3.rgb, c3p * uColor1.a)) + light;\n}\n\nvoid main() {\n vec2 uv = vUv * iResolution.xy;\n gl_FragColor = effect(iResolution.xy, uv);\n}\n`;\n\nexport default function Balatro({\n spinRotation = -2.0,\n spinSpeed = 7.0,\n offset = [0.0, 0.0],\n color1 = '#DE443B',\n color2 = '#006BB4',\n color3 = '#162325',\n contrast = 3.5,\n lighting = 0.4,\n spinAmount = 0.25,\n pixelFilter = 745.0,\n spinEase = 1.0,\n isRotate = false,\n mouseInteraction = true\n}: BalatroProps) {\n const containerRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const container = containerRef.current;\n const renderer = new Renderer();\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 1);\n\n let program: Program;\n\n function resize() {\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n if (program) {\n program.uniforms.iResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];\n }\n }\n window.addEventListener('resize', resize);\n resize();\n\n const geometry = new Triangle(gl);\n program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height]\n },\n uSpinRotation: { value: spinRotation },\n uSpinSpeed: { value: spinSpeed },\n uOffset: { value: offset },\n uColor1: { value: hexToVec4(color1) },\n uColor2: { value: hexToVec4(color2) },\n uColor3: { value: hexToVec4(color3) },\n uContrast: { value: contrast },\n uLighting: { value: lighting },\n uSpinAmount: { value: spinAmount },\n uPixelFilter: { value: pixelFilter },\n uSpinEase: { value: spinEase },\n uIsRotate: { value: isRotate },\n uMouse: { value: [0.5, 0.5] }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n let animationFrameId: number;\n\n function update(time: number) {\n animationFrameId = requestAnimationFrame(update);\n program.uniforms.iTime.value = time * 0.001;\n renderer.render({ scene: mesh });\n }\n animationFrameId = requestAnimationFrame(update);\n container.appendChild(gl.canvas);\n\n function handleMouseMove(e: MouseEvent) {\n if (!mouseInteraction) return;\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1.0 - (e.clientY - rect.top) / rect.height;\n program.uniforms.uMouse.value = [x, y];\n }\n container.addEventListener('mousemove', handleMouseMove);\n\n return () => {\n cancelAnimationFrame(animationFrameId);\n window.removeEventListener('resize', resize);\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n }, [\n spinRotation,\n spinSpeed,\n offset,\n color1,\n color2,\n color3,\n contrast,\n lighting,\n spinAmount,\n pixelFilter,\n spinEase,\n isRotate,\n mouseInteraction\n ]);\n\n return
;\n}\n","path":"Balatro.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"ogl","version":"^1.0.11"}]} \ No newline at end of file diff --git a/public/jsrepo/balatro.json b/public/jsrepo/balatro.json new file mode 100644 index 00000000..2e62d06c --- /dev/null +++ b/public/jsrepo/balatro.json @@ -0,0 +1 @@ +{"name":"balatro","title":"Balatro","description":"The balatro shader, fully customizalbe and interactive.","type":"registry:block","add":"when-added","files":[{"content":".balatro-container {\n width: 100%;\n height: 100%;\n}\n","path":"Balatro.css","_imports_":[]},{"content":"import { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\n\nimport './Balatro.css';\n\ninterface BalatroProps {\n spinRotation?: number;\n spinSpeed?: number;\n offset?: [number, number];\n color1?: string;\n color2?: string;\n color3?: string;\n contrast?: number;\n lighting?: number;\n spinAmount?: number;\n pixelFilter?: number;\n spinEase?: number;\n isRotate?: boolean;\n mouseInteraction?: boolean;\n}\n\nfunction hexToVec4(hex: string): [number, number, number, number] {\n let hexStr = hex.replace('#', '');\n let r = 0,\n g = 0,\n b = 0,\n a = 1;\n if (hexStr.length === 6) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n } else if (hexStr.length === 8) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n a = parseInt(hexStr.slice(6, 8), 16) / 255;\n }\n return [r, g, b, a];\n}\n\nconst vertexShader = `\nattribute vec2 uv;\nattribute vec2 position;\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0, 1);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\n#define PI 3.14159265359\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uSpinRotation;\nuniform float uSpinSpeed;\nuniform vec2 uOffset;\nuniform vec4 uColor1;\nuniform vec4 uColor2;\nuniform vec4 uColor3;\nuniform float uContrast;\nuniform float uLighting;\nuniform float uSpinAmount;\nuniform float uPixelFilter;\nuniform float uSpinEase;\nuniform bool uIsRotate;\nuniform vec2 uMouse;\n\nvarying vec2 vUv;\n\nvec4 effect(vec2 screenSize, vec2 screen_coords) {\n float pixel_size = length(screenSize.xy) / uPixelFilter;\n vec2 uv = (floor(screen_coords.xy * (1.0 / pixel_size)) * pixel_size - 0.5 * screenSize.xy) / length(screenSize.xy) - uOffset;\n float uv_len = length(uv);\n \n float speed = (uSpinRotation * uSpinEase * 0.2);\n if(uIsRotate){\n speed = iTime * speed;\n }\n speed += 302.2;\n \n float mouseInfluence = (uMouse.x * 2.0 - 1.0);\n speed += mouseInfluence * 0.1;\n \n float new_pixel_angle = atan(uv.y, uv.x) + speed - uSpinEase * 20.0 * (uSpinAmount * uv_len + (1.0 - uSpinAmount));\n vec2 mid = (screenSize.xy / length(screenSize.xy)) / 2.0;\n uv = (vec2(uv_len * cos(new_pixel_angle) + mid.x, uv_len * sin(new_pixel_angle) + mid.y) - mid);\n \n uv *= 30.0;\n float baseSpeed = iTime * uSpinSpeed;\n speed = baseSpeed + mouseInfluence * 2.0;\n \n vec2 uv2 = vec2(uv.x + uv.y);\n \n for(int i = 0; i < 5; i++) {\n uv2 += sin(max(uv.x, uv.y)) + uv;\n uv += 0.5 * vec2(\n cos(5.1123314 + 0.353 * uv2.y + speed * 0.131121),\n sin(uv2.x - 0.113 * speed)\n );\n uv -= cos(uv.x + uv.y) - sin(uv.x * 0.711 - uv.y);\n }\n \n float contrast_mod = (0.25 * uContrast + 0.5 * uSpinAmount + 1.2);\n float paint_res = min(2.0, max(0.0, length(uv) * 0.035 * contrast_mod));\n float c1p = max(0.0, 1.0 - contrast_mod * abs(1.0 - paint_res));\n float c2p = max(0.0, 1.0 - contrast_mod * abs(paint_res));\n float c3p = 1.0 - min(1.0, c1p + c2p);\n float light = (uLighting - 0.2) * max(c1p * 5.0 - 4.0, 0.0) + uLighting * max(c2p * 5.0 - 4.0, 0.0);\n \n return (0.3 / uContrast) * uColor1 + (1.0 - 0.3 / uContrast) * (uColor1 * c1p + uColor2 * c2p + vec4(c3p * uColor3.rgb, c3p * uColor1.a)) + light;\n}\n\nvoid main() {\n vec2 uv = vUv * iResolution.xy;\n gl_FragColor = effect(iResolution.xy, uv);\n}\n`;\n\nexport default function Balatro({\n spinRotation = -2.0,\n spinSpeed = 7.0,\n offset = [0.0, 0.0],\n color1 = '#DE443B',\n color2 = '#006BB4',\n color3 = '#162325',\n contrast = 3.5,\n lighting = 0.4,\n spinAmount = 0.25,\n pixelFilter = 745.0,\n spinEase = 1.0,\n isRotate = false,\n mouseInteraction = true\n}: BalatroProps) {\n const containerRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const container = containerRef.current;\n const renderer = new Renderer();\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 1);\n\n let program: Program;\n\n function resize() {\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n if (program) {\n program.uniforms.iResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];\n }\n }\n window.addEventListener('resize', resize);\n resize();\n\n const geometry = new Triangle(gl);\n program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height]\n },\n uSpinRotation: { value: spinRotation },\n uSpinSpeed: { value: spinSpeed },\n uOffset: { value: offset },\n uColor1: { value: hexToVec4(color1) },\n uColor2: { value: hexToVec4(color2) },\n uColor3: { value: hexToVec4(color3) },\n uContrast: { value: contrast },\n uLighting: { value: lighting },\n uSpinAmount: { value: spinAmount },\n uPixelFilter: { value: pixelFilter },\n uSpinEase: { value: spinEase },\n uIsRotate: { value: isRotate },\n uMouse: { value: [0.5, 0.5] }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n let animationFrameId: number;\n\n function update(time: number) {\n animationFrameId = requestAnimationFrame(update);\n program.uniforms.iTime.value = time * 0.001;\n renderer.render({ scene: mesh });\n }\n animationFrameId = requestAnimationFrame(update);\n container.appendChild(gl.canvas);\n\n function handleMouseMove(e: MouseEvent) {\n if (!mouseInteraction) return;\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1.0 - (e.clientY - rect.top) / rect.height;\n program.uniforms.uMouse.value = [x, y];\n }\n container.addEventListener('mousemove', handleMouseMove);\n\n return () => {\n cancelAnimationFrame(animationFrameId);\n window.removeEventListener('resize', resize);\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n }, [\n spinRotation,\n spinSpeed,\n offset,\n color1,\n color2,\n color3,\n contrast,\n lighting,\n spinAmount,\n pixelFilter,\n spinEase,\n isRotate,\n mouseInteraction\n ]);\n\n return
;\n}\n","path":"Balatro.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"ogl","version":"^1.0.11"}]} \ No newline at end of file diff --git a/public/jsrepo/ballpit-tw.json b/public/jsrepo/ballpit-tw.json new file mode 100644 index 00000000..dc9778b6 --- /dev/null +++ b/public/jsrepo/ballpit-tw.json @@ -0,0 +1 @@ +{"name":"ballpit-tw","title":"Ballpit","description":"Physics ball pit simulation with bouncing colorful spheres.","type":"registry:block","add":"when-added","files":[{"content":"import React, { useRef, useEffect } from 'react';\nimport {\n Clock,\n PerspectiveCamera,\n Scene,\n WebGLRenderer,\n WebGLRendererParameters,\n SRGBColorSpace,\n MathUtils,\n Vector2,\n Vector3,\n MeshPhysicalMaterial,\n ShaderChunk,\n Color,\n Object3D,\n InstancedMesh,\n PMREMGenerator,\n SphereGeometry,\n AmbientLight,\n PointLight,\n ACESFilmicToneMapping,\n Raycaster,\n Plane\n} from 'three';\nimport { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';\nimport { Observer } from 'gsap/Observer';\nimport { gsap } from 'gsap';\n\ngsap.registerPlugin(Observer);\n\ninterface XConfig {\n canvas?: HTMLCanvasElement;\n id?: string;\n rendererOptions?: Partial;\n size?: 'parent' | { width: number; height: number };\n}\n\ninterface SizeData {\n width: number;\n height: number;\n wWidth: number;\n wHeight: number;\n ratio: number;\n pixelRatio: number;\n}\n\nclass X {\n #config: XConfig;\n #postprocessing: any;\n #resizeObserver?: ResizeObserver;\n #intersectionObserver?: IntersectionObserver;\n #resizeTimer?: number;\n #animationFrameId: number = 0;\n #clock: Clock = new Clock();\n #animationState = { elapsed: 0, delta: 0 };\n #isAnimating: boolean = false;\n #isVisible: boolean = false;\n\n canvas!: HTMLCanvasElement;\n camera!: PerspectiveCamera;\n cameraMinAspect?: number;\n cameraMaxAspect?: number;\n cameraFov!: number;\n maxPixelRatio?: number;\n minPixelRatio?: number;\n scene!: Scene;\n renderer!: WebGLRenderer;\n size: SizeData = {\n width: 0,\n height: 0,\n wWidth: 0,\n wHeight: 0,\n ratio: 0,\n pixelRatio: 0\n };\n\n render: () => void = this.#render.bind(this);\n onBeforeRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterResize: (size: SizeData) => void = () => {};\n isDisposed: boolean = false;\n\n constructor(config: XConfig) {\n this.#config = { ...config };\n this.#initCamera();\n this.#initScene();\n this.#initRenderer();\n this.resize();\n this.#initObservers();\n }\n\n #initCamera() {\n this.camera = new PerspectiveCamera();\n this.cameraFov = this.camera.fov;\n }\n\n #initScene() {\n this.scene = new Scene();\n }\n\n #initRenderer() {\n if (this.#config.canvas) {\n this.canvas = this.#config.canvas;\n } else if (this.#config.id) {\n const elem = document.getElementById(this.#config.id);\n if (elem instanceof HTMLCanvasElement) {\n this.canvas = elem;\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas!.style.display = 'block';\n const rendererOptions: WebGLRendererParameters = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#config.rendererOptions ?? {})\n };\n this.renderer = new WebGLRenderer(rendererOptions);\n this.renderer.outputColorSpace = SRGBColorSpace;\n }\n\n #initObservers() {\n if (!(this.#config.size instanceof Object)) {\n window.addEventListener('resize', this.#onResize.bind(this));\n if (this.#config.size === 'parent' && this.canvas.parentNode) {\n this.#resizeObserver = new ResizeObserver(this.#onResize.bind(this));\n this.#resizeObserver.observe(this.canvas.parentNode as Element);\n }\n }\n this.#intersectionObserver = new IntersectionObserver(this.#onIntersection.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#intersectionObserver.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n\n #onResize() {\n if (this.#resizeTimer) clearTimeout(this.#resizeTimer);\n this.#resizeTimer = window.setTimeout(this.resize.bind(this), 100);\n }\n\n resize() {\n let w: number, h: number;\n if (this.#config.size instanceof Object) {\n w = this.#config.size.width;\n h = this.#config.size.height;\n } else if (this.#config.size === 'parent' && this.canvas.parentNode) {\n w = (this.canvas.parentNode as HTMLElement).offsetWidth;\n h = (this.canvas.parentNode as HTMLElement).offsetHeight;\n } else {\n w = window.innerWidth;\n h = window.innerHeight;\n }\n this.size.width = w;\n this.size.height = h;\n this.size.ratio = w / h;\n this.#updateCamera();\n this.#updateRenderer();\n this.onAfterResize(this.size);\n }\n\n #updateCamera() {\n this.camera.aspect = this.size.width / this.size.height;\n if (this.camera.isPerspectiveCamera && this.cameraFov) {\n if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {\n this.#adjustFov(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#adjustFov(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n\n #adjustFov(aspect: number) {\n const tanFov = Math.tan(MathUtils.degToRad(this.cameraFov / 2));\n const newTan = tanFov / (this.camera.aspect / aspect);\n this.camera.fov = 2 * MathUtils.radToDeg(Math.atan(newTan));\n }\n\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const fovRad = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(fovRad / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if ((this.camera as any).isOrthographicCamera) {\n const cam = this.camera as any;\n this.size.wHeight = cam.top - cam.bottom;\n this.size.wWidth = cam.right - cam.left;\n }\n }\n\n #updateRenderer() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#postprocessing?.setSize(this.size.width, this.size.height);\n let pr = window.devicePixelRatio;\n if (this.maxPixelRatio && pr > this.maxPixelRatio) {\n pr = this.maxPixelRatio;\n } else if (this.minPixelRatio && pr < this.minPixelRatio) {\n pr = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(pr);\n this.size.pixelRatio = pr;\n }\n\n get postprocessing() {\n return this.#postprocessing;\n }\n set postprocessing(value: any) {\n this.#postprocessing = value;\n this.render = value.render.bind(value);\n }\n\n #onIntersection(entries: IntersectionObserverEntry[]) {\n this.#isAnimating = entries[0].isIntersecting;\n this.#isAnimating ? this.#startAnimation() : this.#stopAnimation();\n }\n\n #onVisibilityChange() {\n if (this.#isAnimating) {\n document.hidden ? this.#stopAnimation() : this.#startAnimation();\n }\n }\n\n #startAnimation() {\n if (this.#isVisible) return;\n const animateFrame = () => {\n this.#animationFrameId = requestAnimationFrame(animateFrame);\n this.#animationState.delta = this.#clock.getDelta();\n this.#animationState.elapsed += this.#animationState.delta;\n this.onBeforeRender(this.#animationState);\n this.render();\n this.onAfterRender(this.#animationState);\n };\n this.#isVisible = true;\n this.#clock.start();\n animateFrame();\n }\n\n #stopAnimation() {\n if (this.#isVisible) {\n cancelAnimationFrame(this.#animationFrameId);\n this.#isVisible = false;\n this.#clock.stop();\n }\n }\n\n #render() {\n this.renderer.render(this.scene, this.camera);\n }\n\n clear() {\n this.scene.traverse(obj => {\n if ((obj as any).isMesh && typeof (obj as any).material === 'object' && (obj as any).material !== null) {\n Object.keys((obj as any).material).forEach(key => {\n const matProp = (obj as any).material[key];\n if (matProp && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n (obj as any).material.dispose();\n (obj as any).geometry.dispose();\n }\n });\n this.scene.clear();\n }\n\n dispose() {\n this.#onResizeCleanup();\n this.#stopAnimation();\n this.clear();\n this.#postprocessing?.dispose();\n this.renderer.dispose();\n this.isDisposed = true;\n }\n\n #onResizeCleanup() {\n window.removeEventListener('resize', this.#onResize.bind(this));\n this.#resizeObserver?.disconnect();\n this.#intersectionObserver?.disconnect();\n document.removeEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n}\n\ninterface WConfig {\n count: number;\n maxX: number;\n maxY: number;\n maxZ: number;\n maxSize: number;\n minSize: number;\n size0: number;\n gravity: number;\n friction: number;\n wallBounce: number;\n maxVelocity: number;\n controlSphere0?: boolean;\n followCursor?: boolean;\n}\n\nclass W {\n config: WConfig;\n positionData: Float32Array;\n velocityData: Float32Array;\n sizeData: Float32Array;\n center: Vector3 = new Vector3();\n\n constructor(config: WConfig) {\n this.config = config;\n this.positionData = new Float32Array(3 * config.count).fill(0);\n this.velocityData = new Float32Array(3 * config.count).fill(0);\n this.sizeData = new Float32Array(config.count).fill(1);\n this.center = new Vector3();\n this.#initializePositions();\n this.setSizes();\n }\n\n #initializePositions() {\n const { config, positionData } = this;\n this.center.toArray(positionData, 0);\n for (let i = 1; i < config.count; i++) {\n const idx = 3 * i;\n positionData[idx] = MathUtils.randFloatSpread(2 * config.maxX);\n positionData[idx + 1] = MathUtils.randFloatSpread(2 * config.maxY);\n positionData[idx + 2] = MathUtils.randFloatSpread(2 * config.maxZ);\n }\n }\n\n setSizes() {\n const { config, sizeData } = this;\n sizeData[0] = config.size0;\n for (let i = 1; i < config.count; i++) {\n sizeData[i] = MathUtils.randFloat(config.minSize, config.maxSize);\n }\n }\n\n update(deltaInfo: { delta: number }) {\n const { config, center, positionData, sizeData, velocityData } = this;\n let startIdx = 0;\n if (config.controlSphere0) {\n startIdx = 1;\n const firstVec = new Vector3().fromArray(positionData, 0);\n firstVec.lerp(center, 0.1).toArray(positionData, 0);\n new Vector3(0, 0, 0).toArray(velocityData, 0);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n vel.y -= deltaInfo.delta * config.gravity * sizeData[idx];\n vel.multiplyScalar(config.friction);\n vel.clampLength(0, config.maxVelocity);\n pos.add(vel);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n const radius = sizeData[idx];\n for (let jdx = idx + 1; jdx < config.count; jdx++) {\n const otherBase = 3 * jdx;\n const otherPos = new Vector3().fromArray(positionData, otherBase);\n const otherVel = new Vector3().fromArray(velocityData, otherBase);\n const diff = new Vector3().copy(otherPos).sub(pos);\n const dist = diff.length();\n const sumRadius = radius + sizeData[jdx];\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n const correction = diff.normalize().multiplyScalar(0.5 * overlap);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 1));\n pos.sub(correction);\n vel.sub(velCorrection);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n otherPos.add(correction);\n otherVel.add(correction.clone().multiplyScalar(Math.max(otherVel.length(), 1)));\n otherPos.toArray(positionData, otherBase);\n otherVel.toArray(velocityData, otherBase);\n }\n }\n if (config.controlSphere0) {\n const diff = new Vector3().copy(new Vector3().fromArray(positionData, 0)).sub(pos);\n const d = diff.length();\n const sumRadius0 = radius + sizeData[0];\n if (d < sumRadius0) {\n const correction = diff.normalize().multiplyScalar(sumRadius0 - d);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 2));\n pos.sub(correction);\n vel.sub(velCorrection);\n }\n }\n if (Math.abs(pos.x) + radius > config.maxX) {\n pos.x = Math.sign(pos.x) * (config.maxX - radius);\n vel.x = -vel.x * config.wallBounce;\n }\n if (config.gravity === 0) {\n if (Math.abs(pos.y) + radius > config.maxY) {\n pos.y = Math.sign(pos.y) * (config.maxY - radius);\n vel.y = -vel.y * config.wallBounce;\n }\n } else if (pos.y - radius < -config.maxY) {\n pos.y = -config.maxY + radius;\n vel.y = -vel.y * config.wallBounce;\n }\n const maxBoundary = Math.max(config.maxZ, config.maxSize);\n if (Math.abs(pos.z) + radius > maxBoundary) {\n pos.z = Math.sign(pos.z) * (config.maxZ - radius);\n vel.z = -vel.z * config.wallBounce;\n }\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n }\n}\n\nclass Y extends MeshPhysicalMaterial {\n uniforms: { [key: string]: { value: any } } = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n\n constructor(params: any) {\n super(params);\n this.defines = { USE_UV: '' };\n this.onBeforeCompile = shader => {\n Object.assign(shader.uniforms, this.uniforms);\n shader.fragmentShader =\n `\n uniform float thicknessPower;\n uniform float thicknessScale;\n uniform float thicknessDistortion;\n uniform float thicknessAmbient;\n uniform float thicknessAttenuation;\n ` + shader.fragmentShader;\n shader.fragmentShader = shader.fragmentShader.replace(\n 'void main() {',\n `\n void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\n vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\n float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\n #ifdef USE_COLOR\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\n #else\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\n #endif\n reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\n }\n\n void main() {\n `\n );\n const lightsChunk = ShaderChunk.lights_fragment_begin.replaceAll(\n 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',\n `\n RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\n `\n );\n shader.fragmentShader = shader.fragmentShader.replace('#include ', lightsChunk);\n if (this.onBeforeCompile2) this.onBeforeCompile2(shader);\n };\n }\n onBeforeCompile2?: (shader: any) => void;\n}\n\nconst XConfig = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 0xffffff,\n ambientIntensity: 1,\n lightIntensity: 200,\n materialParams: {\n metalness: 0.5,\n roughness: 0.5,\n clearcoat: 1,\n clearcoatRoughness: 0.15\n },\n minSize: 0.5,\n maxSize: 1,\n size0: 1,\n gravity: 0.5,\n friction: 0.9975,\n wallBounce: 0.95,\n maxVelocity: 0.15,\n maxX: 5,\n maxY: 5,\n maxZ: 2,\n controlSphere0: false,\n followCursor: true\n};\n\nconst U = new Object3D();\n\nlet globalPointerActive = false;\nconst pointerPosition = new Vector2();\n\ninterface PointerData {\n position: Vector2;\n nPosition: Vector2;\n hover: boolean;\n touching: boolean;\n onEnter: (data: PointerData) => void;\n onMove: (data: PointerData) => void;\n onClick: (data: PointerData) => void;\n onLeave: (data: PointerData) => void;\n dispose?: () => void;\n}\n\nconst pointerMap = new Map();\n\nfunction createPointerData(options: Partial & { domElement: HTMLElement }): PointerData {\n const defaultData: PointerData = {\n position: new Vector2(),\n nPosition: new Vector2(),\n hover: false,\n touching: false,\n onEnter: () => {},\n onMove: () => {},\n onClick: () => {},\n onLeave: () => {},\n ...options\n };\n if (!pointerMap.has(options.domElement)) {\n pointerMap.set(options.domElement, defaultData);\n if (!globalPointerActive) {\n document.body.addEventListener('pointermove', onPointerMove as EventListener);\n document.body.addEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.addEventListener('click', onPointerClick as EventListener);\n\n document.body.addEventListener('touchstart', onTouchStart as EventListener, {\n passive: false\n });\n document.body.addEventListener('touchmove', onTouchMove as EventListener, {\n passive: false\n });\n document.body.addEventListener('touchend', onTouchEnd as EventListener, {\n passive: false\n });\n document.body.addEventListener('touchcancel', onTouchEnd as EventListener, {\n passive: false\n });\n globalPointerActive = true;\n }\n }\n defaultData.dispose = () => {\n pointerMap.delete(options.domElement);\n if (pointerMap.size === 0) {\n document.body.removeEventListener('pointermove', onPointerMove as EventListener);\n document.body.removeEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.removeEventListener('click', onPointerClick as EventListener);\n\n document.body.removeEventListener('touchstart', onTouchStart as EventListener);\n document.body.removeEventListener('touchmove', onTouchMove as EventListener);\n document.body.removeEventListener('touchend', onTouchEnd as EventListener);\n document.body.removeEventListener('touchcancel', onTouchEnd as EventListener);\n globalPointerActive = false;\n }\n };\n return defaultData;\n}\n\nfunction onPointerMove(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n processPointerInteraction();\n}\n\nfunction processPointerInteraction() {\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && !data.touching) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction onTouchStart(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n data.touching = true;\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchMove(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) {\n if (!data.hover) {\n data.hover = true;\n data.touching = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && data.touching) {\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchEnd() {\n for (const [, data] of pointerMap) {\n if (data.touching) {\n data.touching = false;\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n }\n}\n\nfunction onPointerClick(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) data.onClick(data);\n }\n}\n\nfunction onPointerLeave() {\n for (const data of pointerMap.values()) {\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction updatePointerData(data: PointerData, rect: DOMRect) {\n data.position.set(pointerPosition.x - rect.left, pointerPosition.y - rect.top);\n data.nPosition.set((data.position.x / rect.width) * 2 - 1, (-data.position.y / rect.height) * 2 + 1);\n}\n\nfunction isInside(rect: DOMRect) {\n return (\n pointerPosition.x >= rect.left &&\n pointerPosition.x <= rect.left + rect.width &&\n pointerPosition.y >= rect.top &&\n pointerPosition.y <= rect.top + rect.height\n );\n}\n\nclass Z extends InstancedMesh {\n config: typeof XConfig;\n physics: W;\n ambientLight: AmbientLight | undefined;\n light: PointLight | undefined;\n\n constructor(renderer: WebGLRenderer, params: Partial = {}) {\n const config = { ...XConfig, ...params };\n const roomEnv = new RoomEnvironment();\n const pmrem = new PMREMGenerator(renderer);\n const envTexture = pmrem.fromScene(roomEnv).texture;\n const geometry = new SphereGeometry();\n const material = new Y({ envMap: envTexture, ...config.materialParams });\n material.envMapRotation.x = -Math.PI / 2;\n super(geometry, material, config.count);\n this.config = config;\n this.physics = new W(config);\n this.#setupLights();\n this.setColors(config.colors);\n }\n\n #setupLights() {\n this.ambientLight = new AmbientLight(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new PointLight(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n\n setColors(colors: number[]) {\n if (Array.isArray(colors) && colors.length > 1) {\n const colorUtils = (function (colorsArr: number[]) {\n let baseColors: number[] = colorsArr;\n let colorObjects: Color[] = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n return {\n setColors: (cols: number[]) => {\n baseColors = cols;\n colorObjects = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n },\n getColorAt: (ratio: number, out: Color = new Color()) => {\n const clamped = Math.max(0, Math.min(1, ratio));\n const scaled = clamped * (baseColors.length - 1);\n const idx = Math.floor(scaled);\n const start = colorObjects[idx];\n if (idx >= baseColors.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = colorObjects[idx + 1];\n out.r = start.r + alpha * (end.r - start.r);\n out.g = start.g + alpha * (end.g - start.g);\n out.b = start.b + alpha * (end.b - start.b);\n return out;\n }\n };\n })(colors);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, colorUtils.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light!.color.copy(colorUtils.getColorAt(idx / this.count));\n }\n }\n\n if (!this.instanceColor) return;\n this.instanceColor.needsUpdate = true;\n }\n }\n\n update(deltaInfo: { delta: number }) {\n this.physics.update(deltaInfo);\n for (let idx = 0; idx < this.count; idx++) {\n U.position.fromArray(this.physics.positionData, 3 * idx);\n if (idx === 0 && this.config.followCursor === false) {\n U.scale.setScalar(0);\n } else {\n U.scale.setScalar(this.physics.sizeData[idx]);\n }\n U.updateMatrix();\n this.setMatrixAt(idx, U.matrix);\n if (idx === 0) this.light!.position.copy(U.position);\n }\n this.instanceMatrix.needsUpdate = true;\n }\n}\n\ninterface CreateBallpitReturn {\n three: X;\n spheres: Z;\n setCount: (count: number) => void;\n togglePause: () => void;\n dispose: () => void;\n}\n\nfunction createBallpit(canvas: HTMLCanvasElement, config: any = {}): CreateBallpitReturn {\n const threeInstance = new X({\n canvas,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let spheres: Z;\n threeInstance.renderer.toneMapping = ACESFilmicToneMapping;\n threeInstance.camera.position.set(0, 0, 20);\n threeInstance.camera.lookAt(0, 0, 0);\n threeInstance.cameraMaxAspect = 1.5;\n threeInstance.resize();\n initialize(config);\n const raycaster = new Raycaster();\n const plane = new Plane(new Vector3(0, 0, 1), 0);\n const intersectionPoint = new Vector3();\n let isPaused = false;\n\n canvas.style.touchAction = 'none';\n canvas.style.userSelect = 'none';\n (canvas.style as any).webkitUserSelect = 'none';\n\n const pointerData = createPointerData({\n domElement: canvas,\n onMove() {\n raycaster.setFromCamera(pointerData.nPosition, threeInstance.camera);\n threeInstance.camera.getWorldDirection(plane.normal);\n raycaster.ray.intersectPlane(plane, intersectionPoint);\n spheres.physics.center.copy(intersectionPoint);\n spheres.config.controlSphere0 = true;\n },\n onLeave() {\n spheres.config.controlSphere0 = false;\n }\n });\n function initialize(cfg: any) {\n if (spheres) {\n threeInstance.clear();\n threeInstance.scene.remove(spheres);\n }\n spheres = new Z(threeInstance.renderer, cfg);\n threeInstance.scene.add(spheres);\n }\n threeInstance.onBeforeRender = deltaInfo => {\n if (!isPaused) spheres.update(deltaInfo);\n };\n threeInstance.onAfterResize = size => {\n spheres.config.maxX = size.wWidth / 2;\n spheres.config.maxY = size.wHeight / 2;\n };\n return {\n three: threeInstance,\n get spheres() {\n return spheres;\n },\n setCount(count: number) {\n initialize({ ...spheres.config, count });\n },\n togglePause() {\n isPaused = !isPaused;\n },\n dispose() {\n pointerData.dispose?.();\n threeInstance.dispose();\n }\n };\n}\n\ninterface BallpitProps {\n className?: string;\n followCursor?: boolean;\n [key: string]: any;\n}\n\nconst Ballpit: React.FC = ({ className = '', followCursor = true, ...props }) => {\n const canvasRef = useRef(null);\n const spheresInstanceRef = useRef(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n spheresInstanceRef.current = createBallpit(canvas, {\n followCursor,\n ...props\n });\n\n return () => {\n if (spheresInstanceRef.current) {\n spheresInstanceRef.current.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nexport default Ballpit;\n","path":"Ballpit.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"three","version":"^0.167.1"},{"ecosystem":"js","name":"gsap","version":"^3.13.0"}]} \ No newline at end of file diff --git a/public/jsrepo/ballpit.json b/public/jsrepo/ballpit.json new file mode 100644 index 00000000..6e8adf86 --- /dev/null +++ b/public/jsrepo/ballpit.json @@ -0,0 +1 @@ +{"name":"ballpit","title":"Ballpit","description":"Physics ball pit simulation with bouncing colorful spheres.","type":"registry:block","add":"when-added","files":[{"content":"import React, { useRef, useEffect } from 'react';\nimport {\n Clock,\n PerspectiveCamera,\n Scene,\n WebGLRenderer,\n WebGLRendererParameters,\n SRGBColorSpace,\n MathUtils,\n Vector2,\n Vector3,\n MeshPhysicalMaterial,\n ShaderChunk,\n Color,\n Object3D,\n InstancedMesh,\n PMREMGenerator,\n SphereGeometry,\n AmbientLight,\n PointLight,\n ACESFilmicToneMapping,\n Raycaster,\n Plane\n} from 'three';\nimport { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';\nimport { Observer } from 'gsap/Observer';\nimport { gsap } from 'gsap';\n\ngsap.registerPlugin(Observer);\n\ninterface XConfig {\n canvas?: HTMLCanvasElement;\n id?: string;\n rendererOptions?: Partial;\n size?: 'parent' | { width: number; height: number };\n}\n\ninterface SizeData {\n width: number;\n height: number;\n wWidth: number;\n wHeight: number;\n ratio: number;\n pixelRatio: number;\n}\n\nclass X {\n #config: XConfig;\n #postprocessing: any;\n #resizeObserver?: ResizeObserver;\n #intersectionObserver?: IntersectionObserver;\n #resizeTimer?: number;\n #animationFrameId: number = 0;\n #clock: Clock = new Clock();\n #animationState = { elapsed: 0, delta: 0 };\n #isAnimating: boolean = false;\n #isVisible: boolean = false;\n\n canvas!: HTMLCanvasElement;\n camera!: PerspectiveCamera;\n cameraMinAspect?: number;\n cameraMaxAspect?: number;\n cameraFov!: number;\n maxPixelRatio?: number;\n minPixelRatio?: number;\n scene!: Scene;\n renderer!: WebGLRenderer;\n size: SizeData = {\n width: 0,\n height: 0,\n wWidth: 0,\n wHeight: 0,\n ratio: 0,\n pixelRatio: 0\n };\n\n render: () => void = this.#render.bind(this);\n onBeforeRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterResize: (size: SizeData) => void = () => {};\n isDisposed: boolean = false;\n\n constructor(config: XConfig) {\n this.#config = { ...config };\n this.#initCamera();\n this.#initScene();\n this.#initRenderer();\n this.resize();\n this.#initObservers();\n }\n\n #initCamera() {\n this.camera = new PerspectiveCamera();\n this.cameraFov = this.camera.fov;\n }\n\n #initScene() {\n this.scene = new Scene();\n }\n\n #initRenderer() {\n if (this.#config.canvas) {\n this.canvas = this.#config.canvas;\n } else if (this.#config.id) {\n const elem = document.getElementById(this.#config.id);\n if (elem instanceof HTMLCanvasElement) {\n this.canvas = elem;\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas!.style.display = 'block';\n const rendererOptions: WebGLRendererParameters = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#config.rendererOptions ?? {})\n };\n this.renderer = new WebGLRenderer(rendererOptions);\n this.renderer.outputColorSpace = SRGBColorSpace;\n }\n\n #initObservers() {\n if (!(this.#config.size instanceof Object)) {\n window.addEventListener('resize', this.#onResize.bind(this));\n if (this.#config.size === 'parent' && this.canvas.parentNode) {\n this.#resizeObserver = new ResizeObserver(this.#onResize.bind(this));\n this.#resizeObserver.observe(this.canvas.parentNode as Element);\n }\n }\n this.#intersectionObserver = new IntersectionObserver(this.#onIntersection.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#intersectionObserver.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n\n #onResize() {\n if (this.#resizeTimer) clearTimeout(this.#resizeTimer);\n this.#resizeTimer = window.setTimeout(this.resize.bind(this), 100);\n }\n\n resize() {\n let w: number, h: number;\n if (this.#config.size instanceof Object) {\n w = this.#config.size.width;\n h = this.#config.size.height;\n } else if (this.#config.size === 'parent' && this.canvas.parentNode) {\n w = (this.canvas.parentNode as HTMLElement).offsetWidth;\n h = (this.canvas.parentNode as HTMLElement).offsetHeight;\n } else {\n w = window.innerWidth;\n h = window.innerHeight;\n }\n this.size.width = w;\n this.size.height = h;\n this.size.ratio = w / h;\n this.#updateCamera();\n this.#updateRenderer();\n this.onAfterResize(this.size);\n }\n\n #updateCamera() {\n this.camera.aspect = this.size.width / this.size.height;\n if (this.camera.isPerspectiveCamera && this.cameraFov) {\n if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {\n this.#adjustFov(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#adjustFov(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n\n #adjustFov(aspect: number) {\n const tanFov = Math.tan(MathUtils.degToRad(this.cameraFov / 2));\n const newTan = tanFov / (this.camera.aspect / aspect);\n this.camera.fov = 2 * MathUtils.radToDeg(Math.atan(newTan));\n }\n\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const fovRad = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(fovRad / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if ((this.camera as any).isOrthographicCamera) {\n const cam = this.camera as any;\n this.size.wHeight = cam.top - cam.bottom;\n this.size.wWidth = cam.right - cam.left;\n }\n }\n\n #updateRenderer() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#postprocessing?.setSize(this.size.width, this.size.height);\n let pr = window.devicePixelRatio;\n if (this.maxPixelRatio && pr > this.maxPixelRatio) {\n pr = this.maxPixelRatio;\n } else if (this.minPixelRatio && pr < this.minPixelRatio) {\n pr = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(pr);\n this.size.pixelRatio = pr;\n }\n\n get postprocessing() {\n return this.#postprocessing;\n }\n set postprocessing(value: any) {\n this.#postprocessing = value;\n this.render = value.render.bind(value);\n }\n\n #onIntersection(entries: IntersectionObserverEntry[]) {\n this.#isAnimating = entries[0].isIntersecting;\n this.#isAnimating ? this.#startAnimation() : this.#stopAnimation();\n }\n\n #onVisibilityChange() {\n if (this.#isAnimating) {\n document.hidden ? this.#stopAnimation() : this.#startAnimation();\n }\n }\n\n #startAnimation() {\n if (this.#isVisible) return;\n const animateFrame = () => {\n this.#animationFrameId = requestAnimationFrame(animateFrame);\n this.#animationState.delta = this.#clock.getDelta();\n this.#animationState.elapsed += this.#animationState.delta;\n this.onBeforeRender(this.#animationState);\n this.render();\n this.onAfterRender(this.#animationState);\n };\n this.#isVisible = true;\n this.#clock.start();\n animateFrame();\n }\n\n #stopAnimation() {\n if (this.#isVisible) {\n cancelAnimationFrame(this.#animationFrameId);\n this.#isVisible = false;\n this.#clock.stop();\n }\n }\n\n #render() {\n this.renderer.render(this.scene, this.camera);\n }\n\n clear() {\n this.scene.traverse(obj => {\n if ((obj as any).isMesh && typeof (obj as any).material === 'object' && (obj as any).material !== null) {\n Object.keys((obj as any).material).forEach(key => {\n const matProp = (obj as any).material[key];\n if (matProp && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n (obj as any).material.dispose();\n (obj as any).geometry.dispose();\n }\n });\n this.scene.clear();\n }\n\n dispose() {\n this.#onResizeCleanup();\n this.#stopAnimation();\n this.clear();\n this.#postprocessing?.dispose();\n this.renderer.dispose();\n this.isDisposed = true;\n }\n\n #onResizeCleanup() {\n window.removeEventListener('resize', this.#onResize.bind(this));\n this.#resizeObserver?.disconnect();\n this.#intersectionObserver?.disconnect();\n document.removeEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n}\n\ninterface WConfig {\n count: number;\n maxX: number;\n maxY: number;\n maxZ: number;\n maxSize: number;\n minSize: number;\n size0: number;\n gravity: number;\n friction: number;\n wallBounce: number;\n maxVelocity: number;\n controlSphere0?: boolean;\n followCursor?: boolean;\n}\n\nclass W {\n config: WConfig;\n positionData: Float32Array;\n velocityData: Float32Array;\n sizeData: Float32Array;\n center: Vector3 = new Vector3();\n\n constructor(config: WConfig) {\n this.config = config;\n this.positionData = new Float32Array(3 * config.count).fill(0);\n this.velocityData = new Float32Array(3 * config.count).fill(0);\n this.sizeData = new Float32Array(config.count).fill(1);\n this.center = new Vector3();\n this.#initializePositions();\n this.setSizes();\n }\n\n #initializePositions() {\n const { config, positionData } = this;\n this.center.toArray(positionData, 0);\n for (let i = 1; i < config.count; i++) {\n const idx = 3 * i;\n positionData[idx] = MathUtils.randFloatSpread(2 * config.maxX);\n positionData[idx + 1] = MathUtils.randFloatSpread(2 * config.maxY);\n positionData[idx + 2] = MathUtils.randFloatSpread(2 * config.maxZ);\n }\n }\n\n setSizes() {\n const { config, sizeData } = this;\n sizeData[0] = config.size0;\n for (let i = 1; i < config.count; i++) {\n sizeData[i] = MathUtils.randFloat(config.minSize, config.maxSize);\n }\n }\n\n update(deltaInfo: { delta: number }) {\n const { config, center, positionData, sizeData, velocityData } = this;\n let startIdx = 0;\n if (config.controlSphere0) {\n startIdx = 1;\n const firstVec = new Vector3().fromArray(positionData, 0);\n firstVec.lerp(center, 0.1).toArray(positionData, 0);\n new Vector3(0, 0, 0).toArray(velocityData, 0);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n vel.y -= deltaInfo.delta * config.gravity * sizeData[idx];\n vel.multiplyScalar(config.friction);\n vel.clampLength(0, config.maxVelocity);\n pos.add(vel);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n const radius = sizeData[idx];\n for (let jdx = idx + 1; jdx < config.count; jdx++) {\n const otherBase = 3 * jdx;\n const otherPos = new Vector3().fromArray(positionData, otherBase);\n const otherVel = new Vector3().fromArray(velocityData, otherBase);\n const diff = new Vector3().copy(otherPos).sub(pos);\n const dist = diff.length();\n const sumRadius = radius + sizeData[jdx];\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n const correction = diff.normalize().multiplyScalar(0.5 * overlap);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 1));\n pos.sub(correction);\n vel.sub(velCorrection);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n otherPos.add(correction);\n otherVel.add(correction.clone().multiplyScalar(Math.max(otherVel.length(), 1)));\n otherPos.toArray(positionData, otherBase);\n otherVel.toArray(velocityData, otherBase);\n }\n }\n if (config.controlSphere0) {\n const diff = new Vector3().copy(new Vector3().fromArray(positionData, 0)).sub(pos);\n const d = diff.length();\n const sumRadius0 = radius + sizeData[0];\n if (d < sumRadius0) {\n const correction = diff.normalize().multiplyScalar(sumRadius0 - d);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 2));\n pos.sub(correction);\n vel.sub(velCorrection);\n }\n }\n if (Math.abs(pos.x) + radius > config.maxX) {\n pos.x = Math.sign(pos.x) * (config.maxX - radius);\n vel.x = -vel.x * config.wallBounce;\n }\n if (config.gravity === 0) {\n if (Math.abs(pos.y) + radius > config.maxY) {\n pos.y = Math.sign(pos.y) * (config.maxY - radius);\n vel.y = -vel.y * config.wallBounce;\n }\n } else if (pos.y - radius < -config.maxY) {\n pos.y = -config.maxY + radius;\n vel.y = -vel.y * config.wallBounce;\n }\n const maxBoundary = Math.max(config.maxZ, config.maxSize);\n if (Math.abs(pos.z) + radius > maxBoundary) {\n pos.z = Math.sign(pos.z) * (config.maxZ - radius);\n vel.z = -vel.z * config.wallBounce;\n }\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n }\n}\n\nclass Y extends MeshPhysicalMaterial {\n uniforms: { [key: string]: { value: any } } = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n\n constructor(params: any) {\n super(params);\n this.defines = { USE_UV: '' };\n this.onBeforeCompile = shader => {\n Object.assign(shader.uniforms, this.uniforms);\n shader.fragmentShader =\n `\n uniform float thicknessPower;\n uniform float thicknessScale;\n uniform float thicknessDistortion;\n uniform float thicknessAmbient;\n uniform float thicknessAttenuation;\n ` + shader.fragmentShader;\n shader.fragmentShader = shader.fragmentShader.replace(\n 'void main() {',\n `\n void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\n vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\n float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\n #ifdef USE_COLOR\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\n #else\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\n #endif\n reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\n }\n\n void main() {\n `\n );\n const lightsChunk = ShaderChunk.lights_fragment_begin.replaceAll(\n 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',\n `\n RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\n `\n );\n shader.fragmentShader = shader.fragmentShader.replace('#include ', lightsChunk);\n if (this.onBeforeCompile2) this.onBeforeCompile2(shader);\n };\n }\n onBeforeCompile2?: (shader: any) => void;\n}\n\nconst XConfig = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 0xffffff,\n ambientIntensity: 1,\n lightIntensity: 200,\n materialParams: {\n metalness: 0.5,\n roughness: 0.5,\n clearcoat: 1,\n clearcoatRoughness: 0.15\n },\n minSize: 0.5,\n maxSize: 1,\n size0: 1,\n gravity: 0.5,\n friction: 0.9975,\n wallBounce: 0.95,\n maxVelocity: 0.15,\n maxX: 5,\n maxY: 5,\n maxZ: 2,\n controlSphere0: false,\n followCursor: true\n};\n\nconst U = new Object3D();\n\nlet globalPointerActive = false;\nconst pointerPosition = new Vector2();\n\ninterface PointerData {\n position: Vector2;\n nPosition: Vector2;\n hover: boolean;\n touching: boolean;\n onEnter: (data: PointerData) => void;\n onMove: (data: PointerData) => void;\n onClick: (data: PointerData) => void;\n onLeave: (data: PointerData) => void;\n dispose?: () => void;\n}\n\nconst pointerMap = new Map();\n\nfunction createPointerData(options: Partial & { domElement: HTMLElement }): PointerData {\n const defaultData: PointerData = {\n position: new Vector2(),\n nPosition: new Vector2(),\n hover: false,\n touching: false,\n onEnter: () => {},\n onMove: () => {},\n onClick: () => {},\n onLeave: () => {},\n ...options\n };\n if (!pointerMap.has(options.domElement)) {\n pointerMap.set(options.domElement, defaultData);\n if (!globalPointerActive) {\n document.body.addEventListener('pointermove', onPointerMove as EventListener);\n document.body.addEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.addEventListener('click', onPointerClick as EventListener);\n\n document.body.addEventListener('touchstart', onTouchStart as EventListener, { passive: false });\n document.body.addEventListener('touchmove', onTouchMove as EventListener, { passive: false });\n document.body.addEventListener('touchend', onTouchEnd as EventListener, { passive: false });\n document.body.addEventListener('touchcancel', onTouchEnd as EventListener, { passive: false });\n globalPointerActive = true;\n }\n }\n defaultData.dispose = () => {\n pointerMap.delete(options.domElement);\n if (pointerMap.size === 0) {\n document.body.removeEventListener('pointermove', onPointerMove as EventListener);\n document.body.removeEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.removeEventListener('click', onPointerClick as EventListener);\n\n document.body.removeEventListener('touchstart', onTouchStart as EventListener);\n document.body.removeEventListener('touchmove', onTouchMove as EventListener);\n document.body.removeEventListener('touchend', onTouchEnd as EventListener);\n document.body.removeEventListener('touchcancel', onTouchEnd as EventListener);\n globalPointerActive = false;\n }\n };\n return defaultData;\n}\n\nfunction onPointerMove(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n processPointerInteraction();\n}\n\nfunction processPointerInteraction() {\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && !data.touching) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction onTouchStart(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n data.touching = true;\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchMove(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) {\n if (!data.hover) {\n data.hover = true;\n data.touching = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && data.touching) {\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchEnd() {\n for (const [, data] of pointerMap) {\n if (data.touching) {\n data.touching = false;\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n }\n}\n\nfunction onPointerClick(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) data.onClick(data);\n }\n}\n\nfunction onPointerLeave() {\n for (const data of pointerMap.values()) {\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction updatePointerData(data: PointerData, rect: DOMRect) {\n data.position.set(pointerPosition.x - rect.left, pointerPosition.y - rect.top);\n data.nPosition.set((data.position.x / rect.width) * 2 - 1, (-data.position.y / rect.height) * 2 + 1);\n}\n\nfunction isInside(rect: DOMRect) {\n return (\n pointerPosition.x >= rect.left &&\n pointerPosition.x <= rect.left + rect.width &&\n pointerPosition.y >= rect.top &&\n pointerPosition.y <= rect.top + rect.height\n );\n}\n\nconst { randFloat, randFloatSpread } = MathUtils;\nconst F = new Vector3();\nconst I = new Vector3();\nconst O = new Vector3();\nconst V = new Vector3();\nconst B = new Vector3();\nconst N = new Vector3();\nconst _ = new Vector3();\nconst j = new Vector3();\nconst H = new Vector3();\nconst T = new Vector3();\n\nclass Z extends InstancedMesh {\n config: typeof XConfig;\n physics: W;\n ambientLight: AmbientLight | undefined;\n light: PointLight | undefined;\n\n constructor(renderer: WebGLRenderer, params: Partial = {}) {\n const config = { ...XConfig, ...params };\n const roomEnv = new RoomEnvironment();\n const pmrem = new PMREMGenerator(renderer);\n const envTexture = pmrem.fromScene(roomEnv).texture;\n const geometry = new SphereGeometry();\n const material = new Y({ envMap: envTexture, ...config.materialParams });\n material.envMapRotation.x = -Math.PI / 2;\n super(geometry, material, config.count);\n this.config = config;\n this.physics = new W(config);\n this.#setupLights();\n this.setColors(config.colors);\n }\n\n #setupLights() {\n this.ambientLight = new AmbientLight(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new PointLight(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n\n setColors(colors: number[]) {\n if (Array.isArray(colors) && colors.length > 1) {\n const colorUtils = (function (colorsArr: number[]) {\n let baseColors: number[] = colorsArr;\n let colorObjects: Color[] = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n return {\n setColors: (cols: number[]) => {\n baseColors = cols;\n colorObjects = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n },\n getColorAt: (ratio: number, out: Color = new Color()) => {\n const clamped = Math.max(0, Math.min(1, ratio));\n const scaled = clamped * (baseColors.length - 1);\n const idx = Math.floor(scaled);\n const start = colorObjects[idx];\n if (idx >= baseColors.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = colorObjects[idx + 1];\n out.r = start.r + alpha * (end.r - start.r);\n out.g = start.g + alpha * (end.g - start.g);\n out.b = start.b + alpha * (end.b - start.b);\n return out;\n }\n };\n })(colors);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, colorUtils.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light!.color.copy(colorUtils.getColorAt(idx / this.count));\n }\n }\n\n if (!this.instanceColor) return;\n this.instanceColor.needsUpdate = true;\n }\n }\n\n update(deltaInfo: { delta: number }) {\n this.physics.update(deltaInfo);\n for (let idx = 0; idx < this.count; idx++) {\n U.position.fromArray(this.physics.positionData, 3 * idx);\n if (idx === 0 && this.config.followCursor === false) {\n U.scale.setScalar(0);\n } else {\n U.scale.setScalar(this.physics.sizeData[idx]);\n }\n U.updateMatrix();\n this.setMatrixAt(idx, U.matrix);\n if (idx === 0) this.light!.position.copy(U.position);\n }\n this.instanceMatrix.needsUpdate = true;\n }\n}\n\ninterface CreateBallpitReturn {\n three: X;\n spheres: Z;\n setCount: (count: number) => void;\n togglePause: () => void;\n dispose: () => void;\n}\n\nfunction createBallpit(canvas: HTMLCanvasElement, config: any = {}): CreateBallpitReturn {\n const threeInstance = new X({\n canvas,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let spheres: Z;\n threeInstance.renderer.toneMapping = ACESFilmicToneMapping;\n threeInstance.camera.position.set(0, 0, 20);\n threeInstance.camera.lookAt(0, 0, 0);\n threeInstance.cameraMaxAspect = 1.5;\n threeInstance.resize();\n initialize(config);\n const raycaster = new Raycaster();\n const plane = new Plane(new Vector3(0, 0, 1), 0);\n const intersectionPoint = new Vector3();\n let isPaused = false;\n\n canvas.style.touchAction = 'none';\n canvas.style.userSelect = 'none';\n (canvas.style as any).webkitUserSelect = 'none';\n\n const pointerData = createPointerData({\n domElement: canvas,\n onMove() {\n raycaster.setFromCamera(pointerData.nPosition, threeInstance.camera);\n threeInstance.camera.getWorldDirection(plane.normal);\n raycaster.ray.intersectPlane(plane, intersectionPoint);\n spheres.physics.center.copy(intersectionPoint);\n spheres.config.controlSphere0 = true;\n },\n onLeave() {\n spheres.config.controlSphere0 = false;\n }\n });\n function initialize(cfg: any) {\n if (spheres) {\n threeInstance.clear();\n threeInstance.scene.remove(spheres);\n }\n spheres = new Z(threeInstance.renderer, cfg);\n threeInstance.scene.add(spheres);\n }\n threeInstance.onBeforeRender = deltaInfo => {\n if (!isPaused) spheres.update(deltaInfo);\n };\n threeInstance.onAfterResize = size => {\n spheres.config.maxX = size.wWidth / 2;\n spheres.config.maxY = size.wHeight / 2;\n };\n return {\n three: threeInstance,\n get spheres() {\n return spheres;\n },\n setCount(count: number) {\n initialize({ ...spheres.config, count });\n },\n togglePause() {\n isPaused = !isPaused;\n },\n dispose() {\n pointerData.dispose?.();\n threeInstance.dispose();\n }\n };\n}\n\ninterface BallpitProps {\n className?: string;\n followCursor?: boolean;\n [key: string]: any;\n}\n\nconst Ballpit: React.FC = ({ className = '', followCursor = true, ...props }) => {\n const canvasRef = useRef(null);\n const spheresInstanceRef = useRef(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n spheresInstanceRef.current = createBallpit(canvas, {\n followCursor,\n ...props\n });\n\n return () => {\n if (spheresInstanceRef.current) {\n spheresInstanceRef.current.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nexport default Ballpit;\n","path":"Ballpit.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"three","version":"^0.167.1"},{"ecosystem":"js","name":"gsap","version":"^3.13.0"}]} \ No newline at end of file diff --git a/public/jsrepo/beams-tw.json b/public/jsrepo/beams-tw.json new file mode 100644 index 00000000..4dea5adc --- /dev/null +++ b/public/jsrepo/beams-tw.json @@ -0,0 +1 @@ +{"name":"beams-tw","title":"Beams","description":"Crossing animated ribbons with customizable properties.","type":"registry:block","add":"when-added","files":[{"content":"import { forwardRef, useImperativeHandle, useEffect, useRef, useMemo, FC, ReactNode } from 'react';\n\nimport * as THREE from 'three';\n\nimport { Canvas, useFrame } from '@react-three/fiber';\nimport { PerspectiveCamera } from '@react-three/drei';\nimport { degToRad } from 'three/src/math/MathUtils.js';\n\ntype UniformValue = THREE.IUniform | unknown;\n\ninterface ExtendMaterialConfig {\n header: string;\n vertexHeader?: string;\n fragmentHeader?: string;\n material?: THREE.MeshPhysicalMaterialParameters & { fog?: boolean };\n uniforms?: Record;\n vertex?: Record;\n fragment?: Record;\n}\n\ntype ShaderWithDefines = THREE.ShaderLibShader & {\n defines?: Record;\n};\n\nfunction extendMaterial(\n BaseMaterial: new (params?: THREE.MaterialParameters) => T,\n cfg: ExtendMaterialConfig\n): THREE.ShaderMaterial {\n const physical = THREE.ShaderLib.physical as ShaderWithDefines;\n const { vertexShader: baseVert, fragmentShader: baseFrag, uniforms: baseUniforms } = physical;\n const baseDefines = physical.defines ?? {};\n\n const uniforms: Record = THREE.UniformsUtils.clone(baseUniforms);\n\n const defaults = new BaseMaterial(cfg.material || {}) as T & {\n color?: THREE.Color;\n roughness?: number;\n metalness?: number;\n envMap?: THREE.Texture;\n envMapIntensity?: number;\n };\n\n if (defaults.color) uniforms.diffuse.value = defaults.color;\n if ('roughness' in defaults) uniforms.roughness.value = defaults.roughness;\n if ('metalness' in defaults) uniforms.metalness.value = defaults.metalness;\n if ('envMap' in defaults) uniforms.envMap.value = defaults.envMap;\n if ('envMapIntensity' in defaults) uniforms.envMapIntensity.value = defaults.envMapIntensity;\n\n Object.entries(cfg.uniforms ?? {}).forEach(([key, u]) => {\n uniforms[key] =\n u !== null && typeof u === 'object' && 'value' in u\n ? (u as THREE.IUniform)\n : ({ value: u } as THREE.IUniform);\n });\n\n let vert = `${cfg.header}\\n${cfg.vertexHeader ?? ''}\\n${baseVert}`;\n let frag = `${cfg.header}\\n${cfg.fragmentHeader ?? ''}\\n${baseFrag}`;\n\n for (const [inc, code] of Object.entries(cfg.vertex ?? {})) {\n vert = vert.replace(inc, `${inc}\\n${code}`);\n }\n for (const [inc, code] of Object.entries(cfg.fragment ?? {})) {\n frag = frag.replace(inc, `${inc}\\n${code}`);\n }\n\n const mat = new THREE.ShaderMaterial({\n defines: { ...baseDefines },\n uniforms,\n vertexShader: vert,\n fragmentShader: frag,\n lights: true,\n fog: !!cfg.material?.fog\n });\n\n return mat;\n}\n\nconst CanvasWrapper: FC<{ children: ReactNode }> = ({ children }) => (\n \n {children}\n \n);\n\nconst hexToNormalizedRGB = (hex: string): [number, number, number] => {\n const clean = hex.replace('#', '');\n const r = parseInt(clean.substring(0, 2), 16);\n const g = parseInt(clean.substring(2, 4), 16);\n const b = parseInt(clean.substring(4, 6), 16);\n return [r / 255, g / 255, b / 255];\n};\n\nconst noise = `\nfloat random (in vec2 st) {\n return fract(sin(dot(st.xy,\n vec2(12.9898,78.233)))*\n 43758.5453123);\n}\nfloat noise (in vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n float a = random(i);\n float b = random(i + vec2(1.0, 0.0));\n float c = random(i + vec2(0.0, 1.0));\n float d = random(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(a, b, u.x) +\n (c - a)* u.y * (1.0 - u.x) +\n (d - b) * u.x * u.y;\n}\nvec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}\nvec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}\nvec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}\nfloat cnoise(vec3 P){\n vec3 Pi0 = floor(P);\n vec3 Pi1 = Pi0 + vec3(1.0);\n Pi0 = mod(Pi0, 289.0);\n Pi1 = mod(Pi1, 289.0);\n vec3 Pf0 = fract(P);\n vec3 Pf1 = Pf0 - vec3(1.0);\n vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);\n vec4 iy = vec4(Pi0.yy, Pi1.yy);\n vec4 iz0 = Pi0.zzzz;\n vec4 iz1 = Pi1.zzzz;\n vec4 ixy = permute(permute(ix) + iy);\n vec4 ixy0 = permute(ixy + iz0);\n vec4 ixy1 = permute(ixy + iz1);\n vec4 gx0 = ixy0 / 7.0;\n vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;\n gx0 = fract(gx0);\n vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);\n vec4 sz0 = step(gz0, vec4(0.0));\n gx0 -= sz0 * (step(0.0, gx0) - 0.5);\n gy0 -= sz0 * (step(0.0, gy0) - 0.5);\n vec4 gx1 = ixy1 / 7.0;\n vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;\n gx1 = fract(gx1);\n vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);\n vec4 sz1 = step(gz1, vec4(0.0));\n gx1 -= sz1 * (step(0.0, gx1) - 0.5);\n gy1 -= sz1 * (step(0.0, gy1) - 0.5);\n vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);\n vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);\n vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);\n vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);\n vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);\n vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);\n vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);\n vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);\n vec4 norm0 = taylorInvSqrt(vec4(dot(g000,g000),dot(g010,g010),dot(g100,g100),dot(g110,g110)));\n g000 *= norm0.x; g010 *= norm0.y; g100 *= norm0.z; g110 *= norm0.w;\n vec4 norm1 = taylorInvSqrt(vec4(dot(g001,g001),dot(g011,g011),dot(g101,g101),dot(g111,g111)));\n g001 *= norm1.x; g011 *= norm1.y; g101 *= norm1.z; g111 *= norm1.w;\n float n000 = dot(g000, Pf0);\n float n100 = dot(g100, vec3(Pf1.x,Pf0.yz));\n float n010 = dot(g010, vec3(Pf0.x,Pf1.y,Pf0.z));\n float n110 = dot(g110, vec3(Pf1.xy,Pf0.z));\n float n001 = dot(g001, vec3(Pf0.xy,Pf1.z));\n float n101 = dot(g101, vec3(Pf1.x,Pf0.y,Pf1.z));\n float n011 = dot(g011, vec3(Pf0.x,Pf1.yz));\n float n111 = dot(g111, Pf1);\n vec3 fade_xyz = fade(Pf0);\n vec4 n_z = mix(vec4(n000,n100,n010,n110),vec4(n001,n101,n011,n111),fade_xyz.z);\n vec2 n_yz = mix(n_z.xy,n_z.zw,fade_xyz.y);\n float n_xyz = mix(n_yz.x,n_yz.y,fade_xyz.x);\n return 2.2 * n_xyz;\n}\n`;\n\ninterface BeamsProps {\n beamWidth?: number;\n beamHeight?: number;\n beamNumber?: number;\n lightColor?: string;\n speed?: number;\n noiseIntensity?: number;\n scale?: number;\n rotation?: number;\n}\n\nconst Beams: FC = ({\n beamWidth = 2,\n beamHeight = 15,\n beamNumber = 12,\n lightColor = '#ffffff',\n speed = 2,\n noiseIntensity = 1.75,\n scale = 0.2,\n rotation = 0\n}) => {\n const meshRef = useRef>(null!);\n\n const beamMaterial = useMemo(\n () =>\n extendMaterial(THREE.MeshStandardMaterial, {\n header: `\n varying vec3 vEye;\n varying float vNoise;\n varying vec2 vUv;\n varying vec3 vPosition;\n uniform float time;\n uniform float uSpeed;\n uniform float uNoiseIntensity;\n uniform float uScale;\n ${noise}`,\n vertexHeader: `\n float getPos(vec3 pos) {\n vec3 noisePos =\n vec3(pos.x * 0., pos.y - uv.y, pos.z + time * uSpeed * 3.) * uScale;\n return cnoise(noisePos);\n }\n vec3 getCurrentPos(vec3 pos) {\n vec3 newpos = pos;\n newpos.z += getPos(pos);\n return newpos;\n }\n vec3 getNormal(vec3 pos) {\n vec3 curpos = getCurrentPos(pos);\n vec3 nextposX = getCurrentPos(pos + vec3(0.01, 0.0, 0.0));\n vec3 nextposZ = getCurrentPos(pos + vec3(0.0, -0.01, 0.0));\n vec3 tangentX = normalize(nextposX - curpos);\n vec3 tangentZ = normalize(nextposZ - curpos);\n return normalize(cross(tangentZ, tangentX));\n }`,\n fragmentHeader: '',\n vertex: {\n '#include ': `transformed.z += getPos(transformed.xyz);`,\n '#include ': `objectNormal = getNormal(position.xyz);`\n },\n fragment: {\n '#include ': `\n float randomNoise = noise(gl_FragCoord.xy);\n gl_FragColor.rgb -= randomNoise / 15. * uNoiseIntensity;`\n },\n material: { fog: true },\n uniforms: {\n diffuse: new THREE.Color(...hexToNormalizedRGB('#000000')),\n time: { shared: true, mixed: true, linked: true, value: 0 },\n roughness: 0.3,\n metalness: 0.3,\n uSpeed: { shared: true, mixed: true, linked: true, value: speed },\n envMapIntensity: 10,\n uNoiseIntensity: noiseIntensity,\n uScale: scale\n }\n }),\n [speed, noiseIntensity, scale]\n );\n\n return (\n \n \n \n \n \n \n \n \n \n );\n};\n\nfunction createStackedPlanesBufferGeometry(\n n: number,\n width: number,\n height: number,\n spacing: number,\n heightSegments: number\n): THREE.BufferGeometry {\n const geometry = new THREE.BufferGeometry();\n const numVertices = n * (heightSegments + 1) * 2;\n const numFaces = n * heightSegments * 2;\n const positions = new Float32Array(numVertices * 3);\n const indices = new Uint32Array(numFaces * 3);\n const uvs = new Float32Array(numVertices * 2);\n\n let vertexOffset = 0;\n let indexOffset = 0;\n let uvOffset = 0;\n const totalWidth = n * width + (n - 1) * spacing;\n const xOffsetBase = -totalWidth / 2;\n\n for (let i = 0; i < n; i++) {\n const xOffset = xOffsetBase + i * (width + spacing);\n const uvXOffset = Math.random() * 300;\n const uvYOffset = Math.random() * 300;\n\n for (let j = 0; j <= heightSegments; j++) {\n const y = height * (j / heightSegments - 0.5);\n const v0 = [xOffset, y, 0];\n const v1 = [xOffset + width, y, 0];\n positions.set([...v0, ...v1], vertexOffset * 3);\n\n const uvY = j / heightSegments;\n uvs.set([uvXOffset, uvY + uvYOffset, uvXOffset + 1, uvY + uvYOffset], uvOffset);\n\n if (j < heightSegments) {\n const a = vertexOffset,\n b = vertexOffset + 1,\n c = vertexOffset + 2,\n d = vertexOffset + 3;\n indices.set([a, b, c, c, b, d], indexOffset);\n indexOffset += 6;\n }\n vertexOffset += 2;\n uvOffset += 4;\n }\n }\n\n geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));\n geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));\n geometry.setIndex(new THREE.BufferAttribute(indices, 1));\n geometry.computeVertexNormals();\n return geometry;\n}\n\nconst MergedPlanes = forwardRef<\n THREE.Mesh,\n {\n material: THREE.ShaderMaterial;\n width: number;\n count: number;\n height: number;\n }\n>(({ material, width, count, height }, ref) => {\n const mesh = useRef>(null!);\n useImperativeHandle(ref, () => mesh.current);\n const geometry = useMemo(\n () => createStackedPlanesBufferGeometry(count, width, height, 0, 100),\n [count, width, height]\n );\n useFrame((_, delta) => {\n mesh.current.material.uniforms.time.value += 0.1 * delta;\n });\n return ;\n});\nMergedPlanes.displayName = 'MergedPlanes';\n\nconst PlaneNoise = forwardRef<\n THREE.Mesh,\n {\n material: THREE.ShaderMaterial;\n width: number;\n count: number;\n height: number;\n }\n>((props, ref) => (\n \n));\nPlaneNoise.displayName = 'PlaneNoise';\n\nconst DirLight: FC<{ position: [number, number, number]; color: string }> = ({ position, color }) => {\n const dir = useRef(null!);\n useEffect(() => {\n if (!dir.current) return;\n const cam = dir.current.shadow.camera as THREE.Camera & {\n top: number;\n bottom: number;\n left: number;\n right: number;\n far: number;\n };\n cam.top = 24;\n cam.bottom = -24;\n cam.left = -24;\n cam.right = 24;\n cam.far = 64;\n dir.current.shadow.bias = -0.004;\n }, []);\n return ;\n};\n\nexport default Beams;\n","path":"Beams.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"three","version":"^0.167.1"},{"ecosystem":"js","name":"@react-three/fiber","version":"^9.3.0"},{"ecosystem":"js","name":"@react-three/drei","version":"^10.7.4"}]} \ No newline at end of file diff --git a/public/jsrepo/beams.json b/public/jsrepo/beams.json new file mode 100644 index 00000000..6ecafc25 --- /dev/null +++ b/public/jsrepo/beams.json @@ -0,0 +1 @@ +{"name":"beams","title":"Beams","description":"Crossing animated ribbons with customizable properties.","type":"registry:block","add":"when-added","files":[{"content":".beams-container {\n position: relative;\n width: 100%;\n height: 100%;\n}\n","path":"Beams.css","_imports_":[]},{"content":"import { forwardRef, useImperativeHandle, useEffect, useRef, useMemo, FC, ReactNode } from 'react';\n\nimport * as THREE from 'three';\n\nimport { Canvas, useFrame } from '@react-three/fiber';\nimport { PerspectiveCamera } from '@react-three/drei';\nimport { degToRad } from 'three/src/math/MathUtils.js';\n\nimport './Beams.css';\n\ntype UniformValue = THREE.IUniform | unknown;\n\ninterface ExtendMaterialConfig {\n header: string;\n vertexHeader?: string;\n fragmentHeader?: string;\n material?: THREE.MeshPhysicalMaterialParameters & { fog?: boolean };\n uniforms?: Record;\n vertex?: Record;\n fragment?: Record;\n}\n\ntype ShaderWithDefines = THREE.ShaderLibShader & {\n defines?: Record;\n};\n\nfunction extendMaterial(\n BaseMaterial: new (params?: THREE.MaterialParameters) => T,\n cfg: ExtendMaterialConfig\n): THREE.ShaderMaterial {\n const physical = THREE.ShaderLib.physical as ShaderWithDefines;\n const { vertexShader: baseVert, fragmentShader: baseFrag, uniforms: baseUniforms } = physical;\n const baseDefines = physical.defines ?? {};\n\n const uniforms: Record = THREE.UniformsUtils.clone(baseUniforms);\n\n const defaults = new BaseMaterial(cfg.material || {}) as T & {\n color?: THREE.Color;\n roughness?: number;\n metalness?: number;\n envMap?: THREE.Texture;\n envMapIntensity?: number;\n };\n\n if (defaults.color) uniforms.diffuse.value = defaults.color;\n if ('roughness' in defaults) uniforms.roughness.value = defaults.roughness;\n if ('metalness' in defaults) uniforms.metalness.value = defaults.metalness;\n if ('envMap' in defaults) uniforms.envMap.value = defaults.envMap;\n if ('envMapIntensity' in defaults) uniforms.envMapIntensity.value = defaults.envMapIntensity;\n\n Object.entries(cfg.uniforms ?? {}).forEach(([key, u]) => {\n uniforms[key] =\n u !== null && typeof u === 'object' && 'value' in u\n ? (u as THREE.IUniform)\n : ({ value: u } as THREE.IUniform);\n });\n\n let vert = `${cfg.header}\\n${cfg.vertexHeader ?? ''}\\n${baseVert}`;\n let frag = `${cfg.header}\\n${cfg.fragmentHeader ?? ''}\\n${baseFrag}`;\n\n for (const [inc, code] of Object.entries(cfg.vertex ?? {})) {\n vert = vert.replace(inc, `${inc}\\n${code}`);\n }\n for (const [inc, code] of Object.entries(cfg.fragment ?? {})) {\n frag = frag.replace(inc, `${inc}\\n${code}`);\n }\n\n const mat = new THREE.ShaderMaterial({\n defines: { ...baseDefines },\n uniforms,\n vertexShader: vert,\n fragmentShader: frag,\n lights: true,\n fog: !!cfg.material?.fog\n });\n\n return mat;\n}\n\nconst CanvasWrapper: FC<{ children: ReactNode }> = ({ children }) => (\n \n {children}\n \n);\n\nconst hexToNormalizedRGB = (hex: string): [number, number, number] => {\n const clean = hex.replace('#', '');\n const r = parseInt(clean.substring(0, 2), 16);\n const g = parseInt(clean.substring(2, 4), 16);\n const b = parseInt(clean.substring(4, 6), 16);\n return [r / 255, g / 255, b / 255];\n};\n\nconst noise = `\nfloat random (in vec2 st) {\n return fract(sin(dot(st.xy,\n vec2(12.9898,78.233)))*\n 43758.5453123);\n}\nfloat noise (in vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n float a = random(i);\n float b = random(i + vec2(1.0, 0.0));\n float c = random(i + vec2(0.0, 1.0));\n float d = random(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(a, b, u.x) +\n (c - a)* u.y * (1.0 - u.x) +\n (d - b) * u.x * u.y;\n}\nvec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}\nvec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}\nvec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}\nfloat cnoise(vec3 P){\n vec3 Pi0 = floor(P);\n vec3 Pi1 = Pi0 + vec3(1.0);\n Pi0 = mod(Pi0, 289.0);\n Pi1 = mod(Pi1, 289.0);\n vec3 Pf0 = fract(P);\n vec3 Pf1 = Pf0 - vec3(1.0);\n vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);\n vec4 iy = vec4(Pi0.yy, Pi1.yy);\n vec4 iz0 = Pi0.zzzz;\n vec4 iz1 = Pi1.zzzz;\n vec4 ixy = permute(permute(ix) + iy);\n vec4 ixy0 = permute(ixy + iz0);\n vec4 ixy1 = permute(ixy + iz1);\n vec4 gx0 = ixy0 / 7.0;\n vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;\n gx0 = fract(gx0);\n vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);\n vec4 sz0 = step(gz0, vec4(0.0));\n gx0 -= sz0 * (step(0.0, gx0) - 0.5);\n gy0 -= sz0 * (step(0.0, gy0) - 0.5);\n vec4 gx1 = ixy1 / 7.0;\n vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;\n gx1 = fract(gx1);\n vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);\n vec4 sz1 = step(gz1, vec4(0.0));\n gx1 -= sz1 * (step(0.0, gx1) - 0.5);\n gy1 -= sz1 * (step(0.0, gy1) - 0.5);\n vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);\n vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);\n vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);\n vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);\n vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);\n vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);\n vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);\n vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);\n vec4 norm0 = taylorInvSqrt(vec4(dot(g000,g000),dot(g010,g010),dot(g100,g100),dot(g110,g110)));\n g000 *= norm0.x; g010 *= norm0.y; g100 *= norm0.z; g110 *= norm0.w;\n vec4 norm1 = taylorInvSqrt(vec4(dot(g001,g001),dot(g011,g011),dot(g101,g101),dot(g111,g111)));\n g001 *= norm1.x; g011 *= norm1.y; g101 *= norm1.z; g111 *= norm1.w;\n float n000 = dot(g000, Pf0);\n float n100 = dot(g100, vec3(Pf1.x,Pf0.yz));\n float n010 = dot(g010, vec3(Pf0.x,Pf1.y,Pf0.z));\n float n110 = dot(g110, vec3(Pf1.xy,Pf0.z));\n float n001 = dot(g001, vec3(Pf0.xy,Pf1.z));\n float n101 = dot(g101, vec3(Pf1.x,Pf0.y,Pf1.z));\n float n011 = dot(g011, vec3(Pf0.x,Pf1.yz));\n float n111 = dot(g111, Pf1);\n vec3 fade_xyz = fade(Pf0);\n vec4 n_z = mix(vec4(n000,n100,n010,n110),vec4(n001,n101,n011,n111),fade_xyz.z);\n vec2 n_yz = mix(n_z.xy,n_z.zw,fade_xyz.y);\n float n_xyz = mix(n_yz.x,n_yz.y,fade_xyz.x);\n return 2.2 * n_xyz;\n}\n`;\n\ninterface BeamsProps {\n beamWidth?: number;\n beamHeight?: number;\n beamNumber?: number;\n lightColor?: string;\n speed?: number;\n noiseIntensity?: number;\n scale?: number;\n rotation?: number;\n}\n\nconst Beams: FC = ({\n beamWidth = 2,\n beamHeight = 15,\n beamNumber = 12,\n lightColor = '#ffffff',\n speed = 2,\n noiseIntensity = 1.75,\n scale = 0.2,\n rotation = 0\n}) => {\n const meshRef = useRef>(null!);\n\n const beamMaterial = useMemo(\n () =>\n extendMaterial(THREE.MeshStandardMaterial, {\n header: `\n varying vec3 vEye;\n varying float vNoise;\n varying vec2 vUv;\n varying vec3 vPosition;\n uniform float time;\n uniform float uSpeed;\n uniform float uNoiseIntensity;\n uniform float uScale;\n ${noise}`,\n vertexHeader: `\n float getPos(vec3 pos) {\n vec3 noisePos =\n vec3(pos.x * 0., pos.y - uv.y, pos.z + time * uSpeed * 3.) * uScale;\n return cnoise(noisePos);\n }\n vec3 getCurrentPos(vec3 pos) {\n vec3 newpos = pos;\n newpos.z += getPos(pos);\n return newpos;\n }\n vec3 getNormal(vec3 pos) {\n vec3 curpos = getCurrentPos(pos);\n vec3 nextposX = getCurrentPos(pos + vec3(0.01, 0.0, 0.0));\n vec3 nextposZ = getCurrentPos(pos + vec3(0.0, -0.01, 0.0));\n vec3 tangentX = normalize(nextposX - curpos);\n vec3 tangentZ = normalize(nextposZ - curpos);\n return normalize(cross(tangentZ, tangentX));\n }`,\n fragmentHeader: '',\n vertex: {\n '#include ': `transformed.z += getPos(transformed.xyz);`,\n '#include ': `objectNormal = getNormal(position.xyz);`\n },\n fragment: {\n '#include ': `\n float randomNoise = noise(gl_FragCoord.xy);\n gl_FragColor.rgb -= randomNoise / 15. * uNoiseIntensity;`\n },\n material: { fog: true },\n uniforms: {\n diffuse: new THREE.Color(...hexToNormalizedRGB('#000000')),\n time: { shared: true, mixed: true, linked: true, value: 0 },\n roughness: 0.3,\n metalness: 0.3,\n uSpeed: { shared: true, mixed: true, linked: true, value: speed },\n envMapIntensity: 10,\n uNoiseIntensity: noiseIntensity,\n uScale: scale\n }\n }),\n [speed, noiseIntensity, scale]\n );\n\n return (\n \n \n \n \n \n \n \n \n \n );\n};\n\nfunction createStackedPlanesBufferGeometry(\n n: number,\n width: number,\n height: number,\n spacing: number,\n heightSegments: number\n): THREE.BufferGeometry {\n const geometry = new THREE.BufferGeometry();\n const numVertices = n * (heightSegments + 1) * 2;\n const numFaces = n * heightSegments * 2;\n const positions = new Float32Array(numVertices * 3);\n const indices = new Uint32Array(numFaces * 3);\n const uvs = new Float32Array(numVertices * 2);\n\n let vertexOffset = 0;\n let indexOffset = 0;\n let uvOffset = 0;\n const totalWidth = n * width + (n - 1) * spacing;\n const xOffsetBase = -totalWidth / 2;\n\n for (let i = 0; i < n; i++) {\n const xOffset = xOffsetBase + i * (width + spacing);\n const uvXOffset = Math.random() * 300;\n const uvYOffset = Math.random() * 300;\n\n for (let j = 0; j <= heightSegments; j++) {\n const y = height * (j / heightSegments - 0.5);\n const v0 = [xOffset, y, 0];\n const v1 = [xOffset + width, y, 0];\n positions.set([...v0, ...v1], vertexOffset * 3);\n\n const uvY = j / heightSegments;\n uvs.set([uvXOffset, uvY + uvYOffset, uvXOffset + 1, uvY + uvYOffset], uvOffset);\n\n if (j < heightSegments) {\n const a = vertexOffset,\n b = vertexOffset + 1,\n c = vertexOffset + 2,\n d = vertexOffset + 3;\n indices.set([a, b, c, c, b, d], indexOffset);\n indexOffset += 6;\n }\n vertexOffset += 2;\n uvOffset += 4;\n }\n }\n\n geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));\n geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));\n geometry.setIndex(new THREE.BufferAttribute(indices, 1));\n geometry.computeVertexNormals();\n return geometry;\n}\n\nconst MergedPlanes = forwardRef<\n THREE.Mesh,\n {\n material: THREE.ShaderMaterial;\n width: number;\n count: number;\n height: number;\n }\n>(({ material, width, count, height }, ref) => {\n const mesh = useRef>(null!);\n useImperativeHandle(ref, () => mesh.current);\n const geometry = useMemo(\n () => createStackedPlanesBufferGeometry(count, width, height, 0, 100),\n [count, width, height]\n );\n useFrame((_, delta) => {\n mesh.current.material.uniforms.time.value += 0.1 * delta;\n });\n return ;\n});\nMergedPlanes.displayName = 'MergedPlanes';\n\nconst PlaneNoise = forwardRef<\n THREE.Mesh,\n {\n material: THREE.ShaderMaterial;\n width: number;\n count: number;\n height: number;\n }\n>((props, ref) => (\n \n));\nPlaneNoise.displayName = 'PlaneNoise';\n\nconst DirLight: FC<{ position: [number, number, number]; color: string }> = ({ position, color }) => {\n const dir = useRef(null!);\n useEffect(() => {\n if (!dir.current) return;\n const cam = dir.current.shadow.camera as THREE.Camera & {\n top: number;\n bottom: number;\n left: number;\n right: number;\n far: number;\n };\n cam.top = 24;\n cam.bottom = -24;\n cam.left = -24;\n cam.right = 24;\n cam.far = 64;\n dir.current.shadow.bias = -0.004;\n }, []);\n return ;\n};\n\nexport default Beams;\n","path":"Beams.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"three","version":"^0.167.1"},{"ecosystem":"js","name":"@react-three/fiber","version":"^9.3.0"},{"ecosystem":"js","name":"@react-three/drei","version":"^10.7.4"}]} \ No newline at end of file diff --git a/public/jsrepo/blob-cursor-tw.json b/public/jsrepo/blob-cursor-tw.json new file mode 100644 index 00000000..ab83ab9f --- /dev/null +++ b/public/jsrepo/blob-cursor-tw.json @@ -0,0 +1 @@ +{"name":"blob-cursor-tw","title":"BlobCursor","description":"Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.","type":"registry:block","add":"when-added","files":[{"content":"'use client';\n\nimport React, { useRef, useEffect, useCallback } from 'react';\nimport gsap from 'gsap';\n\nexport interface BlobCursorProps {\n blobType?: 'circle' | 'square';\n fillColor?: string;\n trailCount?: number;\n sizes?: number[];\n innerSizes?: number[];\n innerColor?: string;\n opacities?: number[];\n shadowColor?: string;\n shadowBlur?: number;\n shadowOffsetX?: number;\n shadowOffsetY?: number;\n filterId?: string;\n filterStdDeviation?: number;\n filterColorMatrixValues?: string;\n useFilter?: boolean;\n fastDuration?: number;\n slowDuration?: number;\n fastEase?: string;\n slowEase?: string;\n zIndex?: number;\n}\n\nexport default function BlobCursor({\n blobType = 'circle',\n fillColor = '#5227FF',\n trailCount = 3,\n sizes = [60, 125, 75],\n innerSizes = [20, 35, 25],\n innerColor = 'rgba(255,255,255,0.8)',\n opacities = [0.6, 0.6, 0.6],\n shadowColor = 'rgba(0,0,0,0.75)',\n shadowBlur = 5,\n shadowOffsetX = 10,\n shadowOffsetY = 10,\n filterId = 'blob',\n filterStdDeviation = 30,\n filterColorMatrixValues = '1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 35 -10',\n useFilter = true,\n fastDuration = 0.1,\n slowDuration = 0.5,\n fastEase = 'power3.out',\n slowEase = 'power1.out',\n zIndex = 100\n}: BlobCursorProps) {\n const containerRef = useRef(null);\n const blobsRef = useRef<(HTMLDivElement | null)[]>([]);\n\n const updateOffset = useCallback(() => {\n if (!containerRef.current) return { left: 0, top: 0 };\n const rect = containerRef.current.getBoundingClientRect();\n return { left: rect.left, top: rect.top };\n }, []);\n\n const handleMove = useCallback(\n (e: React.MouseEvent | React.TouchEvent) => {\n const { left, top } = updateOffset();\n const x = 'clientX' in e ? e.clientX : e.touches[0].clientX;\n const y = 'clientY' in e ? e.clientY : e.touches[0].clientY;\n\n blobsRef.current.forEach((el, i) => {\n if (!el) return;\n const isLead = i === 0;\n gsap.to(el, {\n x: x - left,\n y: y - top,\n duration: isLead ? fastDuration : slowDuration,\n ease: isLead ? fastEase : slowEase\n });\n });\n },\n [updateOffset, fastDuration, slowDuration, fastEase, slowEase]\n );\n\n useEffect(() => {\n const onResize = () => updateOffset();\n window.addEventListener('resize', onResize);\n return () => window.removeEventListener('resize', onResize);\n }, [updateOffset]);\n\n return (\n \n {useFilter && (\n \n \n \n \n \n \n )}\n\n \n {Array.from({ length: trailCount }).map((_, i) => (\n {\n blobsRef.current[i] = el;\n }}\n className=\"absolute will-change-transform transform -translate-x-1/2 -translate-y-1/2\"\n style={{\n width: sizes[i],\n height: sizes[i],\n borderRadius: blobType === 'circle' ? '50%' : '0',\n backgroundColor: fillColor,\n opacity: opacities[i],\n boxShadow: `${shadowOffsetX}px ${shadowOffsetY}px ${shadowBlur}px 0 ${shadowColor}`\n }}\n >\n \n
\n ))}\n
\n
\n );\n}\n","path":"BlobCursor.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}]} \ No newline at end of file diff --git a/public/jsrepo/blob-cursor.json b/public/jsrepo/blob-cursor.json new file mode 100644 index 00000000..8375b1b9 --- /dev/null +++ b/public/jsrepo/blob-cursor.json @@ -0,0 +1 @@ +{"name":"blob-cursor","title":"BlobCursor","description":"Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.","type":"registry:block","add":"when-added","files":[{"content":".blob-container {\n position: relative;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n}\n\n.blob-main {\n pointer-events: none;\n position: absolute;\n width: 100%;\n height: 100%;\n overflow: hidden;\n background: transparent;\n user-select: none;\n cursor: default;\n}\n\n.blob {\n position: absolute;\n will-change: transform;\n transform: translate(-50%, -50%);\n}\n\n.inner-dot {\n position: absolute;\n}\n","path":"BlobCursor.css","_imports_":[]},{"content":"'use client';\n\nimport React, { useRef, useEffect, useCallback } from 'react';\nimport gsap from 'gsap';\nimport './BlobCursor.css';\n\nexport interface BlobCursorProps {\n blobType?: 'circle' | 'square';\n fillColor?: string;\n trailCount?: number;\n sizes?: number[];\n innerSizes?: number[];\n innerColor?: string;\n opacities?: number[];\n shadowColor?: string;\n shadowBlur?: number;\n shadowOffsetX?: number;\n shadowOffsetY?: number;\n filterId?: string;\n filterStdDeviation?: number;\n filterColorMatrixValues?: string;\n useFilter?: boolean;\n fastDuration?: number;\n slowDuration?: number;\n fastEase?: string;\n slowEase?: string;\n zIndex?: number;\n}\n\nexport default function BlobCursor({\n blobType = 'circle',\n fillColor = '#5227FF',\n trailCount = 3,\n sizes = [60, 125, 75],\n innerSizes = [20, 35, 25],\n innerColor = 'rgba(255,255,255,0.8)',\n opacities = [0.6, 0.6, 0.6],\n shadowColor = 'rgba(0,0,0,0.75)',\n shadowBlur = 5,\n shadowOffsetX = 10,\n shadowOffsetY = 10,\n filterId = 'blob',\n filterStdDeviation = 30,\n filterColorMatrixValues = '1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 35 -10',\n useFilter = true,\n fastDuration = 0.1,\n slowDuration = 0.5,\n fastEase = 'power3.out',\n slowEase = 'power1.out',\n zIndex = 100\n}: BlobCursorProps) {\n const containerRef = useRef(null);\n const blobsRef = useRef<(HTMLDivElement | null)[]>([]);\n\n const updateOffset = useCallback(() => {\n if (!containerRef.current) return { left: 0, top: 0 };\n const rect = containerRef.current.getBoundingClientRect();\n return { left: rect.left, top: rect.top };\n }, []);\n\n const handleMove = useCallback(\n (e: React.MouseEvent | React.TouchEvent) => {\n const { left, top } = updateOffset();\n const x = 'clientX' in e ? e.clientX : e.touches[0].clientX;\n const y = 'clientY' in e ? e.clientY : e.touches[0].clientY;\n\n blobsRef.current.forEach((el, i) => {\n if (!el) return;\n const isLead = i === 0;\n gsap.to(el, {\n x: x - left,\n y: y - top,\n duration: isLead ? fastDuration : slowDuration,\n ease: isLead ? fastEase : slowEase\n });\n });\n },\n [updateOffset, fastDuration, slowDuration, fastEase, slowEase]\n );\n\n useEffect(() => {\n const onResize = () => updateOffset();\n window.addEventListener('resize', onResize);\n return () => window.removeEventListener('resize', onResize);\n }, [updateOffset]);\n\n return (\n \n {useFilter && (\n \n \n \n \n \n \n )}\n\n
\n {Array.from({ length: trailCount }).map((_, i) => (\n {\n blobsRef.current[i] = el;\n }}\n className=\"blob\"\n style={{\n width: sizes[i],\n height: sizes[i],\n borderRadius: blobType === 'circle' ? '50%' : '0%',\n backgroundColor: fillColor,\n opacity: opacities[i],\n boxShadow: `${shadowOffsetX}px ${shadowOffsetY}px ${shadowBlur}px 0 ${shadowColor}`\n }}\n >\n \n
\n ))}\n
\n \n );\n}\n","path":"BlobCursor.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}]} \ No newline at end of file diff --git a/public/jsrepo/blur-text-tw.json b/public/jsrepo/blur-text-tw.json new file mode 100644 index 00000000..dc89e71c --- /dev/null +++ b/public/jsrepo/blur-text-tw.json @@ -0,0 +1 @@ +{"name":"blur-text-tw","title":"BlurText","description":"Text starts blurred then crisply resolves for a soft-focus reveal effect.","type":"registry:block","add":"when-added","files":[{"content":"import { motion, Transition, Easing } from 'motion/react';\nimport { useEffect, useRef, useState, useMemo } from 'react';\n\ntype BlurTextProps = {\n text?: string;\n delay?: number;\n className?: string;\n animateBy?: 'words' | 'letters';\n direction?: 'top' | 'bottom';\n threshold?: number;\n rootMargin?: string;\n animationFrom?: Record;\n animationTo?: Array>;\n easing?: Easing | Easing[];\n onAnimationComplete?: () => void;\n stepDuration?: number;\n};\n\nconst buildKeyframes = (\n from: Record,\n steps: Array>\n): Record> => {\n const keys = new Set([...Object.keys(from), ...steps.flatMap(s => Object.keys(s))]);\n\n const keyframes: Record> = {};\n keys.forEach(k => {\n keyframes[k] = [from[k], ...steps.map(s => s[k])];\n });\n return keyframes;\n};\n\nconst BlurText: React.FC = ({\n text = '',\n delay = 200,\n className = '',\n animateBy = 'words',\n direction = 'top',\n threshold = 0.1,\n rootMargin = '0px',\n animationFrom,\n animationTo,\n easing = (t: number) => t,\n onAnimationComplete,\n stepDuration = 0.35\n}) => {\n const elements = animateBy === 'words' ? text.split(' ') : text.split('');\n const [inView, setInView] = useState(false);\n const ref = useRef(null);\n\n useEffect(() => {\n if (!ref.current) return;\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n setInView(true);\n observer.unobserve(ref.current as Element);\n }\n },\n { threshold, rootMargin }\n );\n observer.observe(ref.current);\n return () => observer.disconnect();\n }, [threshold, rootMargin]);\n\n const defaultFrom = useMemo(\n () =>\n direction === 'top' ? { filter: 'blur(10px)', opacity: 0, y: -50 } : { filter: 'blur(10px)', opacity: 0, y: 50 },\n [direction]\n );\n\n const defaultTo = useMemo(\n () => [\n {\n filter: 'blur(5px)',\n opacity: 0.5,\n y: direction === 'top' ? 5 : -5\n },\n { filter: 'blur(0px)', opacity: 1, y: 0 }\n ],\n [direction]\n );\n\n const fromSnapshot = animationFrom ?? defaultFrom;\n const toSnapshots = animationTo ?? defaultTo;\n\n const stepCount = toSnapshots.length + 1;\n const totalDuration = stepDuration * (stepCount - 1);\n const times = Array.from({ length: stepCount }, (_, i) => (stepCount === 1 ? 0 : i / (stepCount - 1)));\n\n return (\n

\n {elements.map((segment, index) => {\n const animateKeyframes = buildKeyframes(fromSnapshot, toSnapshots);\n\n const spanTransition: Transition = {\n duration: totalDuration,\n times,\n delay: (index * delay) / 1000,\n ease: easing\n };\n\n return (\n \n {segment === ' ' ? '\\u00A0' : segment}\n {animateBy === 'words' && index < elements.length - 1 && '\\u00A0'}\n \n );\n })}\n

\n );\n};\n\nexport default BlurText;\n","path":"BlurText.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"motion","version":"^12.23.12"}]} \ No newline at end of file diff --git a/public/jsrepo/blur-text.json b/public/jsrepo/blur-text.json new file mode 100644 index 00000000..5d6fcd2d --- /dev/null +++ b/public/jsrepo/blur-text.json @@ -0,0 +1 @@ +{"name":"blur-text","title":"BlurText","description":"Text starts blurred then crisply resolves for a soft-focus reveal effect.","type":"registry:block","add":"when-added","files":[{"content":"import { motion, Transition } from 'motion/react';\nimport { useEffect, useRef, useState, useMemo } from 'react';\n\ntype BlurTextProps = {\n text?: string;\n delay?: number;\n className?: string;\n animateBy?: 'words' | 'letters';\n direction?: 'top' | 'bottom';\n threshold?: number;\n rootMargin?: string;\n animationFrom?: Record;\n animationTo?: Array>;\n easing?: (t: number) => number;\n onAnimationComplete?: () => void;\n stepDuration?: number;\n};\n\nconst buildKeyframes = (\n from: Record,\n steps: Array>\n): Record> => {\n const keys = new Set([...Object.keys(from), ...steps.flatMap(s => Object.keys(s))]);\n\n const keyframes: Record> = {};\n keys.forEach(k => {\n keyframes[k] = [from[k], ...steps.map(s => s[k])];\n });\n return keyframes;\n};\n\nconst BlurText: React.FC = ({\n text = '',\n delay = 200,\n className = '',\n animateBy = 'words',\n direction = 'top',\n threshold = 0.1,\n rootMargin = '0px',\n animationFrom,\n animationTo,\n easing = (t: number) => t,\n onAnimationComplete,\n stepDuration = 0.35\n}) => {\n const elements = animateBy === 'words' ? text.split(' ') : text.split('');\n const [inView, setInView] = useState(false);\n const ref = useRef(null);\n\n useEffect(() => {\n if (!ref.current) return;\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n setInView(true);\n observer.unobserve(ref.current as Element);\n }\n },\n { threshold, rootMargin }\n );\n observer.observe(ref.current);\n return () => observer.disconnect();\n }, [threshold, rootMargin]);\n\n const defaultFrom = useMemo(\n () =>\n direction === 'top' ? { filter: 'blur(10px)', opacity: 0, y: -50 } : { filter: 'blur(10px)', opacity: 0, y: 50 },\n [direction]\n );\n\n const defaultTo = useMemo(\n () => [\n {\n filter: 'blur(5px)',\n opacity: 0.5,\n y: direction === 'top' ? 5 : -5\n },\n { filter: 'blur(0px)', opacity: 1, y: 0 }\n ],\n [direction]\n );\n\n const fromSnapshot = animationFrom ?? defaultFrom;\n const toSnapshots = animationTo ?? defaultTo;\n\n const stepCount = toSnapshots.length + 1;\n const totalDuration = stepDuration * (stepCount - 1);\n const times = Array.from({ length: stepCount }, (_, i) => (stepCount === 1 ? 0 : i / (stepCount - 1)));\n\n return (\n

\n {elements.map((segment, index) => {\n const animateKeyframes = buildKeyframes(fromSnapshot, toSnapshots);\n\n const spanTransition: Transition = {\n duration: totalDuration,\n times,\n delay: (index * delay) / 1000,\n ease: easing\n };\n\n return (\n \n {segment === ' ' ? '\\u00A0' : segment}\n {animateBy === 'words' && index < elements.length - 1 && '\\u00A0'}\n \n );\n })}\n

\n );\n};\n\nexport default BlurText;\n","path":"BlurText.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"motion","version":"^12.23.12"}]} \ No newline at end of file diff --git a/public/jsrepo/bounce-cards-tw.json b/public/jsrepo/bounce-cards-tw.json new file mode 100644 index 00000000..d65d3409 --- /dev/null +++ b/public/jsrepo/bounce-cards-tw.json @@ -0,0 +1 @@ +{"name":"bounce-cards-tw","title":"BounceCards","description":"Cards bounce that bounce in on mount.","type":"registry:block","add":"when-added","files":[{"content":"import { useEffect } from 'react';\nimport { gsap } from 'gsap';\n\ninterface BounceCardsProps {\n className?: string;\n images?: string[];\n containerWidth?: number;\n containerHeight?: number;\n animationDelay?: number;\n animationStagger?: number;\n easeType?: string;\n transformStyles?: string[];\n enableHover?: boolean;\n}\n\nexport default function BounceCards({\n className = '',\n images = [],\n containerWidth = 400,\n containerHeight = 400,\n animationDelay = 0.5,\n animationStagger = 0.06,\n easeType = 'elastic.out(1, 0.8)',\n transformStyles = [\n 'rotate(10deg) translate(-170px)',\n 'rotate(5deg) translate(-85px)',\n 'rotate(-3deg)',\n 'rotate(-10deg) translate(85px)',\n 'rotate(2deg) translate(170px)'\n ],\n enableHover = false\n}: BounceCardsProps) {\n useEffect(() => {\n gsap.fromTo(\n '.card',\n { scale: 0 },\n {\n scale: 1,\n stagger: animationStagger,\n ease: easeType,\n delay: animationDelay\n }\n );\n }, [animationDelay, animationStagger, easeType]);\n\n const getNoRotationTransform = (transformStr: string): string => {\n const hasRotate = /rotate\\([\\s\\S]*?\\)/.test(transformStr);\n if (hasRotate) {\n return transformStr.replace(/rotate\\([\\s\\S]*?\\)/, 'rotate(0deg)');\n } else if (transformStr === 'none') {\n return 'rotate(0deg)';\n } else {\n return `${transformStr} rotate(0deg)`;\n }\n };\n\n const getPushedTransform = (baseTransform: string, offsetX: number): string => {\n const translateRegex = /translate\\(([-0-9.]+)px\\)/;\n const match = baseTransform.match(translateRegex);\n if (match) {\n const currentX = parseFloat(match[1]);\n const newX = currentX + offsetX;\n return baseTransform.replace(translateRegex, `translate(${newX}px)`);\n } else {\n return baseTransform === 'none' ? `translate(${offsetX}px)` : `${baseTransform} translate(${offsetX}px)`;\n }\n };\n\n const pushSiblings = (hoveredIdx: number) => {\n if (!enableHover) return;\n\n images.forEach((_, i) => {\n const selector = `.card-${i}`;\n gsap.killTweensOf(selector);\n\n const baseTransform = transformStyles[i] || 'none';\n\n if (i === hoveredIdx) {\n const noRotation = getNoRotationTransform(baseTransform);\n gsap.to(selector, {\n transform: noRotation,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n } else {\n const offsetX = i < hoveredIdx ? -160 : 160;\n const pushedTransform = getPushedTransform(baseTransform, offsetX);\n\n const distance = Math.abs(hoveredIdx - i);\n const delay = distance * 0.05;\n\n gsap.to(selector, {\n transform: pushedTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n delay,\n overwrite: 'auto'\n });\n }\n });\n };\n\n const resetSiblings = () => {\n if (!enableHover) return;\n\n images.forEach((_, i) => {\n const selector = `.card-${i}`;\n gsap.killTweensOf(selector);\n\n const baseTransform = transformStyles[i] || 'none';\n gsap.to(selector, {\n transform: baseTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n });\n };\n\n return (\n \n {images.map((src, idx) => (\n pushSiblings(idx)}\n onMouseLeave={resetSiblings}\n >\n {`card-${idx}`}\n \n ))}\n \n );\n}\n","path":"BounceCards.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}]} \ No newline at end of file diff --git a/public/jsrepo/bounce-cards.json b/public/jsrepo/bounce-cards.json new file mode 100644 index 00000000..b5afb720 --- /dev/null +++ b/public/jsrepo/bounce-cards.json @@ -0,0 +1 @@ +{"name":"bounce-cards","title":"BounceCards","description":"Cards bounce that bounce in on mount.","type":"registry:block","add":"when-added","files":[{"content":".bounceCardsContainer {\n position: relative;\n display: flex;\n justify-content: center;\n align-items: center;\n width: 400px;\n height: 400px;\n}\n\n.card {\n position: absolute;\n width: 200px;\n aspect-ratio: 1;\n border: 5px solid #fff;\n border-radius: 25px;\n overflow: hidden;\n box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);\n}\n\n.card .image {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n","path":"BounceCards.css","_imports_":[]},{"content":"import { useEffect } from 'react';\nimport { gsap } from 'gsap';\nimport './BounceCards.css';\n\ninterface BounceCardsProps {\n className?: string;\n images?: string[];\n containerWidth?: number;\n containerHeight?: number;\n animationDelay?: number;\n animationStagger?: number;\n easeType?: string;\n transformStyles?: string[];\n enableHover?: boolean;\n}\n\nexport default function BounceCards({\n className = '',\n images = [],\n containerWidth = 400,\n containerHeight = 400,\n animationDelay = 0.5,\n animationStagger = 0.06,\n easeType = 'elastic.out(1, 0.8)',\n transformStyles = [\n 'rotate(10deg) translate(-170px)',\n 'rotate(5deg) translate(-85px)',\n 'rotate(-3deg)',\n 'rotate(-10deg) translate(85px)',\n 'rotate(2deg) translate(170px)'\n ],\n enableHover = false\n}: BounceCardsProps) {\n useEffect(() => {\n gsap.fromTo(\n '.card',\n { scale: 0 },\n {\n scale: 1,\n stagger: animationStagger,\n ease: easeType,\n delay: animationDelay\n }\n );\n }, [animationStagger, easeType, animationDelay]);\n\n const getNoRotationTransform = (transformStr: string): string => {\n const hasRotate = /rotate\\([\\s\\S]*?\\)/.test(transformStr);\n if (hasRotate) {\n return transformStr.replace(/rotate\\([\\s\\S]*?\\)/, 'rotate(0deg)');\n } else if (transformStr === 'none') {\n return 'rotate(0deg)';\n } else {\n return `${transformStr} rotate(0deg)`;\n }\n };\n\n const getPushedTransform = (baseTransform: string, offsetX: number): string => {\n const translateRegex = /translate\\(([-0-9.]+)px\\)/;\n const match = baseTransform.match(translateRegex);\n if (match) {\n const currentX = parseFloat(match[1]);\n const newX = currentX + offsetX;\n return baseTransform.replace(translateRegex, `translate(${newX}px)`);\n } else {\n return baseTransform === 'none' ? `translate(${offsetX}px)` : `${baseTransform} translate(${offsetX}px)`;\n }\n };\n\n const pushSiblings = (hoveredIdx: number) => {\n if (!enableHover) return;\n\n images.forEach((_, i) => {\n gsap.killTweensOf(`.card-${i}`);\n\n const baseTransform = transformStyles[i] || 'none';\n\n if (i === hoveredIdx) {\n const noRotation = getNoRotationTransform(baseTransform);\n gsap.to(`.card-${i}`, {\n transform: noRotation,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n } else {\n const offsetX = i < hoveredIdx ? -160 : 160;\n const pushedTransform = getPushedTransform(baseTransform, offsetX);\n\n const distance = Math.abs(hoveredIdx - i);\n const delay = distance * 0.05;\n\n gsap.to(`.card-${i}`, {\n transform: pushedTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n delay,\n overwrite: 'auto'\n });\n }\n });\n };\n\n const resetSiblings = () => {\n if (!enableHover) return;\n\n images.forEach((_, i) => {\n gsap.killTweensOf(`.card-${i}`);\n const baseTransform = transformStyles[i] || 'none';\n gsap.to(`.card-${i}`, {\n transform: baseTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n });\n };\n\n return (\n \n {images.map((src, idx) => (\n pushSiblings(idx)}\n onMouseLeave={resetSiblings}\n >\n {`card-${idx}`}\n \n ))}\n \n );\n}\n","path":"BounceCards.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}]} \ No newline at end of file diff --git a/public/jsrepo/bubble-menu-tw.json b/public/jsrepo/bubble-menu-tw.json new file mode 100644 index 00000000..31ae275c --- /dev/null +++ b/public/jsrepo/bubble-menu-tw.json @@ -0,0 +1 @@ +{"name":"bubble-menu-tw","title":"BubbleMenu","description":"Floating circular expanding menu with staggered item reveal.","type":"registry:block","add":"when-added","files":[{"content":"import type { CSSProperties, ReactNode } from 'react';\nimport { useEffect, useRef, useState } from 'react';\nimport { gsap } from 'gsap';\n\ntype MenuItem = {\n label: string;\n href: string;\n ariaLabel?: string;\n rotation?: number;\n hoverStyles?: {\n bgColor?: string;\n textColor?: string;\n };\n};\n\nexport type BubbleMenuProps = {\n logo: ReactNode | string;\n onMenuClick?: (open: boolean) => void;\n className?: string;\n style?: CSSProperties;\n menuAriaLabel?: string;\n menuBg?: string;\n menuContentColor?: string;\n useFixedPosition?: boolean;\n items?: MenuItem[];\n animationEase?: string;\n animationDuration?: number;\n staggerDelay?: number;\n};\n\nconst DEFAULT_ITEMS: MenuItem[] = [\n {\n label: 'home',\n href: '#',\n ariaLabel: 'Home',\n rotation: -8,\n hoverStyles: { bgColor: '#3b82f6', textColor: '#ffffff' }\n },\n {\n label: 'about',\n href: '#',\n ariaLabel: 'About',\n rotation: 8,\n hoverStyles: { bgColor: '#10b981', textColor: '#ffffff' }\n },\n {\n label: 'projects',\n href: '#',\n ariaLabel: 'Documentation',\n rotation: 8,\n hoverStyles: { bgColor: '#f59e0b', textColor: '#ffffff' }\n },\n {\n label: 'blog',\n href: '#',\n ariaLabel: 'Blog',\n rotation: 8,\n hoverStyles: { bgColor: '#ef4444', textColor: '#ffffff' }\n },\n {\n label: 'contact',\n href: '#',\n ariaLabel: 'Contact',\n rotation: -8,\n hoverStyles: { bgColor: '#8b5cf6', textColor: '#ffffff' }\n }\n];\n\nexport default function BubbleMenu({\n logo,\n onMenuClick,\n className,\n style,\n menuAriaLabel = 'Toggle menu',\n menuBg = '#fff',\n menuContentColor = '#111',\n useFixedPosition = false,\n items,\n animationEase = 'back.out(1.5)',\n animationDuration = 0.5,\n staggerDelay = 0.12\n}: BubbleMenuProps) {\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const [showOverlay, setShowOverlay] = useState(false);\n\n const overlayRef = useRef(null);\n const bubblesRef = useRef([]);\n const labelRefs = useRef([]);\n\n const menuItems = items?.length ? items : DEFAULT_ITEMS;\n\n const containerClassName = [\n 'bubble-menu',\n useFixedPosition ? 'fixed' : 'absolute',\n 'left-0 right-0 top-8',\n 'flex items-center justify-between',\n 'gap-4 px-8',\n 'pointer-events-none',\n 'z-[1001]',\n className\n ]\n .filter(Boolean)\n .join(' ');\n\n const handleToggle = () => {\n const nextState = !isMenuOpen;\n if (nextState) setShowOverlay(true);\n setIsMenuOpen(nextState);\n onMenuClick?.(nextState);\n };\n\n useEffect(() => {\n const overlay = overlayRef.current;\n const bubbles = bubblesRef.current.filter(Boolean);\n const labels = labelRefs.current.filter(Boolean);\n if (!overlay || !bubbles.length) return;\n\n if (isMenuOpen) {\n gsap.set(overlay, { display: 'flex' });\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.set(bubbles, { scale: 0, transformOrigin: '50% 50%' });\n gsap.set(labels, { y: 24, autoAlpha: 0 });\n\n bubbles.forEach((bubble, i) => {\n const delay = i * staggerDelay + gsap.utils.random(-0.05, 0.05);\n const tl = gsap.timeline({ delay });\n tl.to(bubble, {\n scale: 1,\n duration: animationDuration,\n ease: animationEase\n });\n if (labels[i]) {\n tl.to(\n labels[i],\n {\n y: 0,\n autoAlpha: 1,\n duration: animationDuration,\n ease: 'power3.out'\n },\n '-=' + animationDuration * 0.9\n );\n }\n });\n } else if (showOverlay) {\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.to(labels, {\n y: 24,\n autoAlpha: 0,\n duration: 0.2,\n ease: 'power3.in'\n });\n gsap.to(bubbles, {\n scale: 0,\n duration: 0.2,\n ease: 'power3.in',\n onComplete: () => {\n gsap.set(overlay, { display: 'none' });\n setShowOverlay(false);\n }\n });\n }\n }, [isMenuOpen, showOverlay, animationEase, animationDuration, staggerDelay]);\n\n useEffect(() => {\n const handleResize = () => {\n if (isMenuOpen) {\n const bubbles = bubblesRef.current.filter(Boolean);\n const isDesktop = window.innerWidth >= 900;\n bubbles.forEach((bubble, i) => {\n const item = menuItems[i];\n if (bubble && item) {\n const rotation = isDesktop ? (item.rotation ?? 0) : 0;\n gsap.set(bubble, { rotation });\n }\n });\n }\n };\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, [isMenuOpen, menuItems]);\n\n return (\n <>\n {/* Workaround for silly Tailwind capabilities */}\n \n\n \n\n {showOverlay && (\n \n \n {menuItems.map((item, idx) => (\n \n {\n if (el) bubblesRef.current[idx] = el;\n }}\n >\n {\n if (el) labelRefs.current[idx] = el;\n }}\n >\n {item.label}\n \n \n \n ))}\n \n \n )}\n \n );\n}\n","path":"BubbleMenu.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}]} \ No newline at end of file diff --git a/public/jsrepo/bubble-menu.json b/public/jsrepo/bubble-menu.json new file mode 100644 index 00000000..dbdde418 --- /dev/null +++ b/public/jsrepo/bubble-menu.json @@ -0,0 +1 @@ +{"name":"bubble-menu","title":"BubbleMenu","description":"Floating circular expanding menu with staggered item reveal.","type":"registry:block","add":"when-added","files":[{"content":".bubble-menu {\n left: 0;\n right: 0;\n top: 2em;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n padding: 0 2em;\n pointer-events: none;\n z-index: 99;\n}\n\n.bubble-menu.fixed {\n position: fixed;\n}\n\n.bubble-menu.absolute {\n position: absolute;\n}\n\n.bubble-menu .bubble {\n --bubble-size: 48px;\n width: var(--bubble-size);\n height: var(--bubble-size);\n border-radius: 50%;\n background: #fff;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n pointer-events: auto;\n}\n\n.bubble-menu .logo-bubble,\n.bubble-menu .toggle-bubble {\n will-change: transform;\n}\n\n.bubble-menu .logo-bubble {\n width: auto;\n min-height: var(--bubble-size);\n height: var(--bubble-size);\n padding: 0 16px;\n border-radius: calc(var(--bubble-size) / 2);\n gap: 8px;\n}\n\n.bubble-menu .toggle-bubble {\n width: var(--bubble-size);\n height: var(--bubble-size);\n}\n\n.bubble-menu .bubble-logo {\n max-height: 60%;\n max-width: 100%;\n object-fit: contain;\n display: block;\n}\n\n.bubble-menu .logo-content {\n --logo-max-height: 60%;\n --logo-max-width: 100%;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 120px;\n height: 100%;\n}\n\n.bubble-menu .logo-content > .bubble-logo,\n.bubble-menu .logo-content > img,\n.bubble-menu .logo-content > svg {\n max-height: var(--logo-max-height);\n max-width: var(--logo-max-width);\n}\n\n.bubble-menu .menu-btn {\n border: none;\n background: #fff;\n cursor: pointer;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 0;\n}\n\n.bubble-menu .menu-line {\n width: 26px;\n height: 2px;\n background: #111;\n border-radius: 2px;\n display: block;\n margin: 0 auto;\n transition:\n transform 0.3s ease,\n opacity 0.3s ease;\n transform-origin: center;\n}\n\n.bubble-menu .menu-line + .menu-line {\n margin-top: 6px;\n}\n\n.bubble-menu .menu-btn.open .menu-line:first-child {\n transform: translateY(4px) rotate(45deg);\n}\n\n.bubble-menu .menu-btn.open .menu-line:last-child {\n transform: translateY(-4px) rotate(-45deg);\n}\n\n@media (min-width: 768px) {\n .bubble-menu .bubble {\n --bubble-size: 56px;\n }\n\n .bubble-menu .logo-bubble {\n padding: 0 16px;\n }\n}\n\n.bubble-menu-items {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n pointer-events: none;\n z-index: 98;\n}\n\n.bubble-menu-items.fixed {\n position: fixed;\n}\n\n.bubble-menu-items.absolute {\n position: absolute;\n}\n\n.bubble-menu-items .pill-list {\n list-style: none;\n margin: 0;\n padding: 0 24px;\n display: flex;\n flex-wrap: wrap;\n gap: 0;\n row-gap: 4px;\n width: 100%;\n max-width: 1600px;\n margin-left: auto;\n margin-right: auto;\n pointer-events: auto;\n justify-content: stretch;\n}\n\n.bubble-menu-items .pill-list .pill-spacer {\n width: 100%;\n height: 0;\n pointer-events: none;\n}\n\n.bubble-menu-items .pill-list .pill-col {\n display: flex;\n justify-content: center;\n align-items: stretch;\n flex: 0 0 calc(100% / 3);\n box-sizing: border-box;\n}\n\n.bubble-menu-items .pill-list .pill-col:nth-child(4):nth-last-child(2) {\n margin-left: calc(100% / 6);\n}\n\n.bubble-menu-items .pill-list .pill-col:nth-child(4):last-child {\n margin-left: calc(100% / 3);\n}\n\n.bubble-menu-items .pill-link {\n --pill-bg: #ffffff;\n --pill-color: #111;\n --pill-border: rgba(0, 0, 0, 0.12);\n --item-rot: 0deg;\n --pill-min-h: 160px;\n --hover-bg: #f3f4f6;\n --hover-color: #111;\n width: 100%;\n min-height: var(--pill-min-h);\n padding: clamp(1.5rem, 3vw, 8rem) 0;\n font-size: clamp(1.5rem, 4vw, 4rem);\n font-weight: 400;\n line-height: 0;\n border-radius: 999px;\n background: var(--pill-bg);\n color: var(--pill-color);\n text-decoration: none;\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.1);\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n transition:\n background 0.3s ease,\n color 0.3s ease;\n will-change: transform;\n box-sizing: border-box;\n white-space: nowrap;\n overflow: hidden;\n height: 10px;\n}\n\n@media (min-width: 900px) {\n .bubble-menu-items .pill-link {\n transform: rotate(var(--item-rot));\n }\n\n .bubble-menu-items .pill-link:hover {\n transform: rotate(var(--item-rot)) scale(1.06);\n background: var(--hover-bg);\n color: var(--hover-color);\n }\n\n .bubble-menu-items .pill-link:active {\n transform: rotate(var(--item-rot)) scale(0.94);\n }\n}\n\n.bubble-menu-items .pill-link .pill-label {\n display: inline-block;\n will-change: transform, opacity;\n height: 1.2em;\n line-height: 1.2;\n}\n\n@media (max-width: 899px) {\n .bubble-menu-items {\n padding-top: 0px;\n align-items: flex-start;\n padding-top: 120px;\n }\n\n .bubble-menu-items .pill-list {\n row-gap: 16px;\n }\n\n .bubble-menu-items .pill-list .pill-col {\n flex: 0 0 100%;\n margin-left: 0 !important;\n overflow: visible;\n }\n\n .bubble-menu-items .pill-link {\n font-size: clamp(1.2rem, 3vw, 4rem);\n padding: clamp(1rem, 2vw, 2rem) 0;\n min-height: 80px;\n }\n\n .bubble-menu-items .pill-link:hover {\n transform: scale(1.06);\n background: var(--hover-bg);\n color: var(--hover-color);\n }\n\n .bubble-menu-items .pill-link:active {\n transform: scale(0.94);\n }\n}\n","path":"BubbleMenu.css","_imports_":[]},{"content":"import type { CSSProperties, ReactNode } from 'react';\nimport { useState, useRef, useEffect } from 'react';\nimport { gsap } from 'gsap';\n\nimport './BubbleMenu.css';\n\ntype MenuItem = {\n label: string;\n href: string;\n ariaLabel?: string;\n rotation?: number;\n hoverStyles?: {\n bgColor?: string;\n textColor?: string;\n };\n};\n\nexport type BubbleMenuProps = {\n logo: ReactNode | string;\n onMenuClick?: (open: boolean) => void;\n className?: string;\n style?: CSSProperties;\n menuAriaLabel?: string;\n menuBg?: string;\n menuContentColor?: string;\n useFixedPosition?: boolean;\n items?: MenuItem[];\n animationEase?: string;\n animationDuration?: number;\n staggerDelay?: number;\n};\n\nconst DEFAULT_ITEMS: MenuItem[] = [\n {\n label: 'home',\n href: '#',\n ariaLabel: 'Home',\n rotation: -8,\n hoverStyles: { bgColor: '#3b82f6', textColor: '#ffffff' }\n },\n {\n label: 'about',\n href: '#',\n ariaLabel: 'About',\n rotation: 8,\n hoverStyles: { bgColor: '#10b981', textColor: '#ffffff' }\n },\n {\n label: 'projects',\n href: '#',\n ariaLabel: 'Documentation',\n rotation: 8,\n hoverStyles: { bgColor: '#f59e0b', textColor: '#ffffff' }\n },\n {\n label: 'blog',\n href: '#',\n ariaLabel: 'Blog',\n rotation: 8,\n hoverStyles: { bgColor: '#ef4444', textColor: '#ffffff' }\n },\n {\n label: 'contact',\n href: '#',\n ariaLabel: 'Contact',\n rotation: -8,\n hoverStyles: { bgColor: '#8b5cf6', textColor: '#ffffff' }\n }\n];\n\nexport default function BubbleMenu({\n logo,\n onMenuClick,\n className,\n style,\n menuAriaLabel = 'Toggle menu',\n menuBg = '#fff',\n menuContentColor = '#111',\n useFixedPosition = false,\n items,\n animationEase = 'back.out(1.5)',\n animationDuration = 0.5,\n staggerDelay = 0.12\n}: BubbleMenuProps) {\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const [showOverlay, setShowOverlay] = useState(false);\n\n const overlayRef = useRef(null);\n const bubblesRef = useRef([]);\n const labelRefs = useRef([]);\n\n const menuItems = items?.length ? items : DEFAULT_ITEMS;\n const containerClassName = ['bubble-menu', useFixedPosition ? 'fixed' : 'absolute', className]\n .filter(Boolean)\n .join(' ');\n\n const handleToggle = () => {\n const nextState = !isMenuOpen;\n if (nextState) setShowOverlay(true);\n setIsMenuOpen(nextState);\n onMenuClick?.(nextState);\n };\n\n useEffect(() => {\n const overlay = overlayRef.current;\n const bubbles = bubblesRef.current.filter(Boolean);\n const labels = labelRefs.current.filter(Boolean);\n\n if (!overlay || !bubbles.length) return;\n\n if (isMenuOpen) {\n gsap.set(overlay, { display: 'flex' });\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.set(bubbles, { scale: 0, transformOrigin: '50% 50%' });\n gsap.set(labels, { y: 24, autoAlpha: 0 });\n\n bubbles.forEach((bubble, i) => {\n const delay = i * staggerDelay + gsap.utils.random(-0.05, 0.05);\n const tl = gsap.timeline({ delay });\n\n tl.to(bubble, {\n scale: 1,\n duration: animationDuration,\n ease: animationEase\n });\n if (labels[i]) {\n tl.to(\n labels[i],\n {\n y: 0,\n autoAlpha: 1,\n duration: animationDuration,\n ease: 'power3.out'\n },\n `-=${animationDuration * 0.9}`\n );\n }\n });\n } else if (showOverlay) {\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.to(labels, {\n y: 24,\n autoAlpha: 0,\n duration: 0.2,\n ease: 'power3.in'\n });\n gsap.to(bubbles, {\n scale: 0,\n duration: 0.2,\n ease: 'power3.in',\n onComplete: () => {\n gsap.set(overlay, { display: 'none' });\n setShowOverlay(false);\n }\n });\n }\n }, [isMenuOpen, showOverlay, animationEase, animationDuration, staggerDelay]);\n\n useEffect(() => {\n const handleResize = () => {\n if (isMenuOpen) {\n const bubbles = bubblesRef.current.filter(Boolean);\n const isDesktop = window.innerWidth >= 900;\n\n bubbles.forEach((bubble, i) => {\n const item = menuItems[i];\n if (bubble && item) {\n const rotation = isDesktop ? (item.rotation ?? 0) : 0;\n gsap.set(bubble, { rotation });\n }\n });\n }\n };\n\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, [isMenuOpen, menuItems]);\n\n return (\n <>\n \n {showOverlay && (\n \n
    \n {menuItems.map((item, idx) => (\n
  • \n {\n if (el) bubblesRef.current[idx] = el;\n }}\n >\n {\n if (el) labelRefs.current[idx] = el;\n }}\n >\n {item.label}\n \n \n
  • \n ))}\n
\n \n )}\n \n );\n}\n","path":"BubbleMenu.tsx","_imports_":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}]} \ No newline at end of file diff --git a/public/jsrepo/card-nav-tw.json b/public/jsrepo/card-nav-tw.json new file mode 100644 index 00000000..c59bdc30 --- /dev/null +++ b/public/jsrepo/card-nav-tw.json @@ -0,0 +1 @@ +{"name":"card-nav-tw","title":"CardNav","description":"Expandable navigation bar with card panels revealing nested links.","type":"registry:block","add":"when-added","files":[{"content":"import React, { useLayoutEffect, useRef, useState } from 'react';\nimport { gsap } from 'gsap';\n// use your own icon import if react-icons is not available\nimport { GoArrowUpRight } from 'react-icons/go';\n\ntype CardNavLink = {\n label: string;\n href: string;\n ariaLabel: string;\n};\n\nexport type CardNavItem = {\n label: string;\n bgColor: string;\n textColor: string;\n links: CardNavLink[];\n};\n\nexport interface CardNavProps {\n logo: string;\n logoAlt?: string;\n items: CardNavItem[];\n className?: string;\n ease?: string;\n baseColor?: string;\n menuColor?: string;\n buttonBgColor?: string;\n buttonTextColor?: string;\n}\n\nconst CardNav: React.FC = ({\n logo,\n logoAlt = 'Logo',\n items,\n className = '',\n ease = 'power3.out',\n baseColor = '#fff',\n menuColor,\n buttonBgColor,\n buttonTextColor\n}) => {\n const [isHamburgerOpen, setIsHamburgerOpen] = useState(false);\n const [isExpanded, setIsExpanded] = useState(false);\n const navRef = useRef(null);\n const cardsRef = useRef([]);\n const tlRef = useRef(null);\n\n const calculateHeight = () => {\n const navEl = navRef.current;\n if (!navEl) return 260;\n\n const isMobile = window.matchMedia('(max-width: 768px)').matches;\n if (isMobile) {\n const contentEl = navEl.querySelector('.card-nav-content') as HTMLElement;\n if (contentEl) {\n const wasVisible = contentEl.style.visibility;\n const wasPointerEvents = contentEl.style.pointerEvents;\n const wasPosition = contentEl.style.position;\n const wasHeight = contentEl.style.height;\n\n contentEl.style.visibility = 'visible';\n contentEl.style.pointerEvents = 'auto';\n contentEl.style.position = 'static';\n contentEl.style.height = 'auto';\n\n contentEl.offsetHeight;\n\n const topBar = 60;\n const padding = 16;\n const contentHeight = contentEl.scrollHeight;\n\n contentEl.style.visibility = wasVisible;\n contentEl.style.pointerEvents = wasPointerEvents;\n contentEl.style.position = wasPosition;\n contentEl.style.height = wasHeight;\n\n return topBar + contentHeight + padding;\n }\n }\n return 260;\n };\n\n const createTimeline = () => {\n const navEl = navRef.current;\n if (!navEl) return null;\n\n gsap.set(navEl, { height: 60, overflow: 'hidden' });\n gsap.set(cardsRef.current, { y: 50, opacity: 0 });\n\n const tl = gsap.timeline({ paused: true });\n\n tl.to(navEl, {\n height: calculateHeight,\n duration: 0.4,\n ease\n });\n\n tl.to(cardsRef.current, { y: 0, opacity: 1, duration: 0.4, ease, stagger: 0.08 }, '-=0.1');\n\n return tl;\n };\n\n useLayoutEffect(() => {\n const tl = createTimeline();\n tlRef.current = tl;\n\n return () => {\n tl?.kill();\n tlRef.current = null;\n };\n }, [ease, items]);\n\n useLayoutEffect(() => {\n const handleResize = () => {\n if (!tlRef.current) return;\n\n if (isExpanded) {\n const newHeight = calculateHeight();\n gsap.set(navRef.current, { height: newHeight });\n\n tlRef.current.kill();\n const newTl = createTimeline();\n if (newTl) {\n newTl.progress(1);\n tlRef.current = newTl;\n }\n } else {\n tlRef.current.kill();\n const newTl = createTimeline();\n if (newTl) {\n tlRef.current = newTl;\n }\n }\n };\n\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, [isExpanded]);\n\n const toggleMenu = () => {\n const tl = tlRef.current;\n if (!tl) return;\n if (!isExpanded) {\n setIsHamburgerOpen(true);\n setIsExpanded(true);\n tl.play(0);\n } else {\n setIsHamburgerOpen(false);\n tl.eventCallback('onReverseComplete', () => setIsExpanded(false));\n tl.reverse();\n }\n };\n\n const setCardRef = (i: number) => (el: HTMLDivElement | null) => {\n if (el) cardsRef.current[i] = el;\n };\n\n return (\n \n \n
\n \n \n \n
\n\n
\n {logoAlt}\n
\n\n