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..ad766ef6 --- /dev/null +++ b/jsrepo.config.ts @@ -0,0 +1,146 @@ +import { defineConfig, type RegistryItem } from 'jsrepo'; +import { output } from '@jsrepo/shadcn'; +import { type Category, componentMetadata, type Variant } from './src/constants/Information'; + +export default defineConfig({ + registry: { + name: '@react-bits', + description: + 'An open source collection of animated, interactive & fully customizable React components for building stunning, memorable user interfaces.', + homepage: 'https://reactbits.dev', + authors: ['David Haz'], + bugs: 'https://github.com/DavidHDev/react-bits/issues', + 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'], + outputs: [output({ dir: 'public/r', format: true })], + items: [ + ...Object.values(componentMetadata).map(component => + defineComponent({ + title: component.name, + description: component.description, + category: component.category, + categories: [component.category], + meta: component.meta, + variants: component.variants + }) + ) + ].flat() + } +}); + +/** + * Define a component to be exposed from the registry. Creates the 4 different variants of the component and ensures the correct files are included. + * + * @param title The title of the component. + * @param description The description of the component. + * @param category The category of the component. + * @param categories Organize the component into multiple categories. + * @param meta Optional meta data for the component. + * @param variants The variants of the component that are available through the registry (default: all variants) + * @returns An array of RegistryItem objects. + */ +function defineComponent({ + title, + description, + category, + categories, + meta, + variants = ['JS-CSS', 'JS-TW', 'TS-CSS', 'TS-TW'] +}: { + title: string; + description: string; + category: Category; + categories?: string[]; + meta?: Record; + variants?: readonly Variant[]; +}): RegistryItem[] { + const baseItem: Omit = { + title, + description, + type: 'registry:component', + categories: [category, ...(categories ?? [])], + meta + }; + + // this might warrant a bit of explanation + // basically we check if the variant is included in the variants array and if so we return the item as part of an array + // otherwise we return an empty array + // we then spread that array empty or otherwise into the return array + return [ + // JS + CSS + ...(variants.includes('JS-CSS') + ? [ + { + ...baseItem, + name: `${baseItem.title}-JS-CSS`, + files: [ + { + path: `src/content/${category}/${title}` + } + ] + } + ] + : []), + + // JS + Tailwind + ...(variants.includes('JS-TW') + ? [ + { + ...baseItem, + name: `${baseItem.title}-JS-TW`, + files: [ + { + path: `src/tailwind/${category}/${title}` + } + ] + } + ] + : []), + + // TS + CSS + ...(variants.includes('TS-CSS') + ? [ + { + ...baseItem, + name: `${baseItem.title}-TS-CSS`, + files: [ + { + path: `src/ts-default/${category}/${title}` + } + ] + } + ] + : []), + + // TS + Tailwind + ...(variants.includes('TS-TW') + ? [ + { + ...baseItem, + name: `${baseItem.title}-TS-TW`, + files: [ + { + path: `src/ts-tailwind/${category}/${title}` + } + ] + } + ] + : []) + ]; +} diff --git a/package-lock.json b/package-lock.json index 6a195599..ba51e700 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ "three": "^0.167.1" }, "devDependencies": { + "@jsrepo/shadcn": "^0.0.1-beta.27", "@types/matter-js": "^0.19.8", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", @@ -57,7 +58,7 @@ "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.25", "postcss-safe-parser": "^7.0.1", "prettier": "^3.6.2", "shadcn": "^3.1.0", @@ -88,43 +89,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 +632,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 +683,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 +1751,16 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsrepo/shadcn": { + "version": "0.0.1-beta.27", + "resolved": "https://registry.npmjs.org/@jsrepo/shadcn/-/shadcn-0.0.1-beta.27.tgz", + "integrity": "sha512-COEptO3xE7xLdFQbgL56xDW2tQChadjQnDMqsN3+R+fUMHHLdvUE48E4IvemhvDrh/+1gsldN8P8UppHrSEKZw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "jsrepo": "3.0.0-beta.25" + } + }, "node_modules/@mediapipe/tasks-vision": { "version": "0.10.17", "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz", @@ -1992,372 +1913,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,44 +1938,95 @@ "dev": true, "license": "MIT" }, - "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==", + "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.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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-x64": { + "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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "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": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "darwin" + "freebsd" ], "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==", + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "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": [ - "x64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "darwin" + "linux" ], "engines": { - "node": ">=14.0.0" + "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==", + "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" ], @@ -2431,13 +2037,13 @@ "linux" ], "engines": { - "node": ">=14.0.0" + "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 +2054,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 +2071,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 +2122,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 +2139,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 +2153,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 +2186,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,13 +2203,13 @@ "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": { @@ -2568,6 +2221,19 @@ "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/sxzz" + } + }, "node_modules/@react-three/drei": { "version": "10.7.6", "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.6.tgz", @@ -3021,16 +2687,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 +3040,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 +3124,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 +3285,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 +3300,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 +3314,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 +3334,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 +3353,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 +3373,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 +3384,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 +3396,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 +3410,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 +3425,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 +4278,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 +4348,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 +4365,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 +4411,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 +4572,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 +4588,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 +4640,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 +4670,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 +4821,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", @@ -5486,29 +4924,16 @@ "node": ">=18" } }, - "node_modules/class-variance-authority": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", - "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", - "license": "Apache-2.0", - "dependencies": { - "clsx": "^2.1.1" - }, - "funding": { - "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" + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://polar.sh/cva" } }, "node_modules/cli-cursor": { @@ -5641,19 +5066,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 +5077,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": { @@ -5719,67 +5131,6 @@ "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==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/content-disposition": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", @@ -5891,16 +5242,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 +5312,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 +5403,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 +5438,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 +5461,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 +5557,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 +5990,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 +6035,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 +6058,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 +6078,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 +6226,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 +6283,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 +6485,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 +6493,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 +6776,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 +7106,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 +7592,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 +7835,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 +7894,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 +7928,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.25", + "resolved": "https://registry.npmjs.org/jsrepo/-/jsrepo-3.0.0-beta.25.tgz", + "integrity": "sha512-k6gNfnGaazmXriATEcIz1n72Ajkr9lze7dbI5A0GAYbNTrodEZ0+7sOjWlyxttwAZo8+QodY+Jd3L0bN2oQiRQ==", "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" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vue": "^3.0.0" }, - "engines": { - "node": ">=10" + "peerDependenciesMeta": { + "svelte": { + "optional": true + }, + "vue": { + "optional": true + } } }, "node_modules/jsx-ast-utils": { @@ -9231,13 +8273,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 +8524,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", @@ -9944,45 +8956,12 @@ "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" - } - }, "node_modules/ogl": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ogl/-/ogl-1.0.11.tgz", "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 +9001,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 +9098,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 +9230,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 +9298,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 +9439,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 +9577,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 +9992,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 +10419,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", @@ -12015,12 +10913,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 +10956,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 +11108,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 +11131,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 +11421,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 +11440,44 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/unconfig": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-7.4.1.tgz", + "integrity": "sha512-uyQ7LElcGizrOGZyIq9KU+xkuEjcRf9IpmDTkCSYv5mEeZzrXSj6rb51C0L+WTedsmAoVxW9WKrLWhSwebIM9Q==", + "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.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/unconfig-core": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/unconfig-core/-/unconfig-core-7.4.1.tgz", + "integrity": "sha512-Bp/bPZjV2Vl/fofoA2OYLSnw1Z0MOhCX7zHnVCYrazpfZvseBbGhwcNQMxsg185Mqh7VZQqK3C8hFG/Dyng+yA==", + "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 +11492,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 +11587,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 +11660,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 +11679,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 +11690,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 +11794,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 +11804,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,13 +11936,6 @@ "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", diff --git a/package.json b/package.json index 1fa8a4a3..b1736bd4 100644 --- a/package.json +++ b/package.json @@ -4,17 +4,12 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", - "build": "npm run jsrepo:build && npm run shadcn:build && npm run llms:text && vite build", + "dev": "concurrently -n \"registry,docs\" -c \"blue,green\" \"npm run registry:dev\" \"vite\"", + "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", + "registry:dev": "jsrepo build --watch", "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", "format": "prettier --write ." }, @@ -58,6 +53,7 @@ "three": "^0.167.1" }, "devDependencies": { + "@jsrepo/shadcn": "^0.0.1-beta.27", "@types/matter-js": "^0.19.8", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", @@ -68,7 +64,7 @@ "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.25", "postcss-safe-parser": "^7.0.1", "prettier": "^3.6.2", "shadcn": "^3.1.0", diff --git a/public/r/ASCIIText-JS-CSS.json b/public/r/ASCIIText-JS-CSS.json index 801537b0..fee9400d 100644 --- a/public/r/ASCIIText-JS-CSS.json +++ b/public/r/ASCIIText-JS-CSS.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "ASCIIText-JS-CSS", - "type": "registry:block", - "title": "ASCIIText", - "description": "Renders text with an animated ASCII background for a retro feel.", - "dependencies": [ - "three" - ], - "files": [ - { - "path": "public/default/src/content/TextAnimations/ASCIIText/ASCIIText.jsx", - "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\nMath.map = function (n, start, stop, start2, stop2) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n};\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\nclass AsciiFilter {\n constructor(renderer, { fontSize, fontFamily, charset, invert } = {}) {\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 this.context.webkitImageSmoothingEnabled = false;\n this.context.mozImageSmoothingEnabled = false;\n this.context.msImageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width, height) {\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 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 render(scene, camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\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 onMouseMove(e) {\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, w, h) {\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\nclass CanvasTxt {\n constructor(txt, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' } = {}) {\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 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 render() {\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 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\nclass CanvAscii {\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerElem,\n width,\n height\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, h) {\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) {\n const e = evt.touches ? evt.touches[0] : evt;\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.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 = Math.map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = Math.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(obj => {\n if (obj.isMesh && typeof obj.material === 'object' && obj.material !== null) {\n Object.keys(obj.material).forEach(key => {\n const matProp = obj.material[key];\n if (matProp !== null && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n obj.material.dispose();\n obj.geometry.dispose();\n }\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\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}) {\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 { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\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 { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "ASCIIText-JS-CSS", + "title": "ASCIIText", + "description": "Renders text with an animated ASCII background for a retro feel.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "ASCIIText/ASCIIText.jsx", + "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\nMath.map = function (n, start, stop, start2, stop2) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n};\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\nclass AsciiFilter {\n constructor(renderer, { fontSize, fontFamily, charset, invert } = {}) {\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 this.context.webkitImageSmoothingEnabled = false;\n this.context.mozImageSmoothingEnabled = false;\n this.context.msImageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width, height) {\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 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 render(scene, camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\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 onMouseMove(e) {\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, w, h) {\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\nclass CanvasTxt {\n constructor(txt, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' } = {}) {\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 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 render() {\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 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\nclass CanvAscii {\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerElem,\n width,\n height\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, h) {\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) {\n const e = evt.touches ? evt.touches[0] : evt;\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.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 = Math.map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = Math.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(obj => {\n if (obj.isMesh && typeof obj.material === 'object' && obj.material !== null) {\n Object.keys(obj.material).forEach(key => {\n const matProp = obj.material[key];\n if (matProp !== null && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n obj.material.dispose();\n obj.geometry.dispose();\n }\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\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}) {\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 { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\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 { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\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" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1" + ] } \ No newline at end of file diff --git a/public/r/ASCIIText-JS-TW.json b/public/r/ASCIIText-JS-TW.json index ae355c7d..3176fbed 100644 --- a/public/r/ASCIIText-JS-TW.json +++ b/public/r/ASCIIText-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "ASCIIText-JS-TW", - "type": "registry:block", - "title": "ASCIIText", - "description": "Renders text with an animated ASCII background for a retro feel.", - "dependencies": [ - "three" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/TextAnimations/ASCIIText/ASCIIText.jsx", - "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\nMath.map = function (n, start, stop, start2, stop2) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n};\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\nclass AsciiFilter {\n constructor(renderer, { fontSize, fontFamily, charset, invert } = {}) {\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 this.context.webkitImageSmoothingEnabled = false;\n this.context.mozImageSmoothingEnabled = false;\n this.context.msImageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width, height) {\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 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 = '0';\n this.pre.style.top = '0';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n\n render(scene, camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\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 onMouseMove(e) {\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, w, h) {\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\nclass CanvasTxt {\n constructor(txt, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' } = {}) {\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 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 render() {\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 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\nclass CanvAscii {\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerElem,\n width,\n height\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, h) {\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) {\n const e = evt.touches ? evt.touches[0] : evt;\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.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 = Math.map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = Math.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(obj => {\n if (obj.isMesh && typeof obj.material === 'object' && obj.material !== null) {\n Object.keys(obj.material).forEach(key => {\n const matProp = obj.material[key];\n if (matProp !== null && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n obj.material.dispose();\n obj.geometry.dispose();\n }\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\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}) {\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 { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\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 { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "ASCIIText-JS-TW", + "title": "ASCIIText", + "description": "Renders text with an animated ASCII background for a retro feel.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "ASCIIText/ASCIIText.jsx", + "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\nMath.map = function (n, start, stop, start2, stop2) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n};\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\nclass AsciiFilter {\n constructor(renderer, { fontSize, fontFamily, charset, invert } = {}) {\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 this.context.webkitImageSmoothingEnabled = false;\n this.context.mozImageSmoothingEnabled = false;\n this.context.msImageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width, height) {\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 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 = '0';\n this.pre.style.top = '0';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n\n render(scene, camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\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 onMouseMove(e) {\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, w, h) {\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\nclass CanvasTxt {\n constructor(txt, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' } = {}) {\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 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 render() {\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 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\nclass CanvAscii {\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerElem,\n width,\n height\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, h) {\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) {\n const e = evt.touches ? evt.touches[0] : evt;\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.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 = Math.map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = Math.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(obj => {\n if (obj.isMesh && typeof obj.material === 'object' && obj.material !== null) {\n Object.keys(obj.material).forEach(key => {\n const matProp = obj.material[key];\n if (matProp !== null && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n obj.material.dispose();\n obj.geometry.dispose();\n }\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\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}) {\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 { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\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 { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\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" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1" + ] } \ No newline at end of file diff --git a/public/r/ASCIIText-TS-CSS.json b/public/r/ASCIIText-TS-CSS.json index b2d1c89a..246db9ba 100644 --- a/public/r/ASCIIText-TS-CSS.json +++ b/public/r/ASCIIText-TS-CSS.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "ASCIIText-TS-CSS", - "type": "registry:block", - "title": "ASCIIText", - "description": "Renders text with an animated ASCII background for a retro feel.", - "dependencies": [ - "three" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/TextAnimations/ASCIIText/ASCIIText.tsx", - "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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "ASCIIText-TS-CSS", + "title": "ASCIIText", + "description": "Renders text with an animated ASCII background for a retro feel.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "ASCIIText/ASCIIText.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1" + ] } \ No newline at end of file diff --git a/public/r/ASCIIText-TS-TW.json b/public/r/ASCIIText-TS-TW.json index d7f93070..de6ef57e 100644 --- a/public/r/ASCIIText-TS-TW.json +++ b/public/r/ASCIIText-TS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "ASCIIText-TS-TW", - "type": "registry:block", - "title": "ASCIIText", - "description": "Renders text with an animated ASCII background for a retro feel.", - "dependencies": [ - "three" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/TextAnimations/ASCIIText/ASCIIText.tsx", - "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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "ASCIIText-TS-TW", + "title": "ASCIIText", + "description": "Renders text with an animated ASCII background for a retro feel.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "ASCIIText/ASCIIText.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1" + ] } \ No newline at end of file diff --git a/public/r/AnimatedContent-JS-CSS.json b/public/r/AnimatedContent-JS-CSS.json index a4d80fc7..b8a5b94a 100644 --- a/public/r/AnimatedContent-JS-CSS.json +++ b/public/r/AnimatedContent-JS-CSS.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "AnimatedContent-JS-CSS", - "type": "registry:block", - "title": "AnimatedContent", - "description": "Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/default/src/content/Animations/AnimatedContent/AnimatedContent.jsx", - "content": "import { useRef, useEffect } from 'react';\nimport { gsap } from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ngsap.registerPlugin(ScrollTrigger);\n\nconst AnimatedContent = ({\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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "AnimatedContent-JS-CSS", + "title": "AnimatedContent", + "description": "Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "AnimatedContent/AnimatedContent.jsx", + "content": "import { useRef, useEffect } from 'react';\nimport { gsap } from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ngsap.registerPlugin(ScrollTrigger);\n\nconst AnimatedContent = ({\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" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/AnimatedContent-JS-TW.json b/public/r/AnimatedContent-JS-TW.json index 38cf7b78..5ae48780 100644 --- a/public/r/AnimatedContent-JS-TW.json +++ b/public/r/AnimatedContent-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "AnimatedContent-JS-TW", - "type": "registry:block", - "title": "AnimatedContent", - "description": "Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Animations/AnimatedContent/AnimatedContent.jsx", - "content": "import { useRef, useEffect } from 'react';\nimport { gsap } from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ngsap.registerPlugin(ScrollTrigger);\n\nconst AnimatedContent = ({\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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "AnimatedContent-JS-TW", + "title": "AnimatedContent", + "description": "Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "AnimatedContent/AnimatedContent.jsx", + "content": "import { useRef, useEffect } from 'react';\nimport { gsap } from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ngsap.registerPlugin(ScrollTrigger);\n\nconst AnimatedContent = ({\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" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/AnimatedContent-TS-CSS.json b/public/r/AnimatedContent-TS-CSS.json index cfecbbb5..739db733 100644 --- a/public/r/AnimatedContent-TS-CSS.json +++ b/public/r/AnimatedContent-TS-CSS.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "AnimatedContent-TS-CSS", - "type": "registry:block", - "title": "AnimatedContent", - "description": "Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/Animations/AnimatedContent/AnimatedContent.tsx", - "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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "AnimatedContent-TS-CSS", + "title": "AnimatedContent", + "description": "Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "AnimatedContent/AnimatedContent.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/AnimatedContent-TS-TW.json b/public/r/AnimatedContent-TS-TW.json index 4429c246..a1c961d1 100644 --- a/public/r/AnimatedContent-TS-TW.json +++ b/public/r/AnimatedContent-TS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "AnimatedContent-TS-TW", - "type": "registry:block", - "title": "AnimatedContent", - "description": "Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/Animations/AnimatedContent/AnimatedContent.tsx", - "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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "AnimatedContent-TS-TW", + "title": "AnimatedContent", + "description": "Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "AnimatedContent/AnimatedContent.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/AnimatedList-JS-CSS.json b/public/r/AnimatedList-JS-CSS.json index 8042ff22..7ea94d53 100644 --- a/public/r/AnimatedList-JS-CSS.json +++ b/public/r/AnimatedList-JS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "AnimatedList-JS-CSS", - "type": "registry:block", - "title": "AnimatedList", - "description": "List items enter with staggered motion variants for polished reveals.", - "dependencies": [ - "motion" - ], - "files": [ - { - "path": "public/default/src/content/Components/AnimatedList/AnimatedList.jsx", - "content": "import { useRef, useState, useEffect } from 'react';\nimport { motion, useInView } from 'motion/react';\nimport './AnimatedList.css';\n\nconst AnimatedItem = ({ children, delay = 0, index, onMouseEnter, onClick }) => {\n const ref = useRef(null);\n const inView = useInView(ref, { amount: 0.5, triggerOnce: false });\n return (\n \n {children}\n \n );\n};\n\nconst AnimatedList = ({\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 => {\n const { scrollTop, scrollHeight, clientHeight } = e.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 => {\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}\"]`);\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", - "type": "registry:component" - }, - { - "path": "public/default/src/content/Components/AnimatedList/AnimatedList.css", - "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: #060010;\n}\n\n.scroll-list::-webkit-scrollbar-thumb {\n background: #271e37;\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: #170d27;\n border-radius: 8px;\n margin-bottom: 1rem;\n}\n\n.item.selected {\n background-color: #271e37;\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", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "AnimatedList-JS-CSS", + "title": "AnimatedList", + "description": "List items enter with staggered motion variants for polished reveals.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "AnimatedList/AnimatedList.css", + "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: #060010;\n}\n\n.scroll-list::-webkit-scrollbar-thumb {\n background: #271e37;\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: #170d27;\n border-radius: 8px;\n margin-bottom: 1rem;\n}\n\n.item.selected {\n background-color: #271e37;\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" + }, + { + "type": "registry:component", + "path": "AnimatedList/AnimatedList.jsx", + "content": "import { useRef, useState, useEffect } from 'react';\nimport { motion, useInView } from 'motion/react';\nimport './AnimatedList.css';\n\nconst AnimatedItem = ({ children, delay = 0, index, onMouseEnter, onClick }) => {\n const ref = useRef(null);\n const inView = useInView(ref, { amount: 0.5, triggerOnce: false });\n return (\n \n {children}\n \n );\n};\n\nconst AnimatedList = ({\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 => {\n const { scrollTop, scrollHeight, clientHeight } = e.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 => {\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}\"]`);\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" + } + ], + "registryDependencies": [], + "dependencies": [ + "motion@^12.23.12" + ] } \ No newline at end of file diff --git a/public/r/AnimatedList-JS-TW.json b/public/r/AnimatedList-JS-TW.json index 16b95d45..2945d7cc 100644 --- a/public/r/AnimatedList-JS-TW.json +++ b/public/r/AnimatedList-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "AnimatedList-JS-TW", - "type": "registry:block", - "title": "AnimatedList", - "description": "List items enter with staggered motion variants for polished reveals.", - "dependencies": [ - "motion" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Components/AnimatedList/AnimatedList.jsx", - "content": "import { useRef, useState, useEffect } from 'react';\nimport { motion, useInView } from 'motion/react';\n\nconst AnimatedItem = ({ children, delay = 0, index, onMouseEnter, onClick }) => {\n const ref = useRef(null);\n const inView = useInView(ref, { amount: 0.5, triggerOnce: false });\n return (\n \n {children}\n \n );\n};\n\nconst AnimatedList = ({\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 => {\n const { scrollTop, scrollHeight, clientHeight } = e.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 => {\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}\"]`);\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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "AnimatedList-JS-TW", + "title": "AnimatedList", + "description": "List items enter with staggered motion variants for polished reveals.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "AnimatedList/AnimatedList.jsx", + "content": "import { useRef, useState, useEffect } from 'react';\nimport { motion, useInView } from 'motion/react';\n\nconst AnimatedItem = ({ children, delay = 0, index, onMouseEnter, onClick }) => {\n const ref = useRef(null);\n const inView = useInView(ref, { amount: 0.5, triggerOnce: false });\n return (\n \n {children}\n \n );\n};\n\nconst AnimatedList = ({\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 => {\n const { scrollTop, scrollHeight, clientHeight } = e.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 => {\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}\"]`);\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" + } + ], + "registryDependencies": [], + "dependencies": [ + "motion@^12.23.12" + ] } \ No newline at end of file diff --git a/public/r/AnimatedList-TS-CSS.json b/public/r/AnimatedList-TS-CSS.json index 7886e8f2..d8ee6777 100644 --- a/public/r/AnimatedList-TS-CSS.json +++ b/public/r/AnimatedList-TS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "AnimatedList-TS-CSS", - "type": "registry:block", - "title": "AnimatedList", - "description": "List items enter with staggered motion variants for polished reveals.", - "dependencies": [ - "motion" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/Components/AnimatedList/AnimatedList.tsx", - "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", - "type": "registry:component" - }, - { - "path": "public/ts/default/src/ts-default/Components/AnimatedList/AnimatedList.css", - "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", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "AnimatedList-TS-CSS", + "title": "AnimatedList", + "description": "List items enter with staggered motion variants for polished reveals.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "AnimatedList/AnimatedList.css", + "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" + }, + { + "type": "registry:component", + "path": "AnimatedList/AnimatedList.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "motion@^12.23.12" + ] } \ No newline at end of file diff --git a/public/r/AnimatedList-TS-TW.json b/public/r/AnimatedList-TS-TW.json index bebdf815..84ef36aa 100644 --- a/public/r/AnimatedList-TS-TW.json +++ b/public/r/AnimatedList-TS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "AnimatedList-TS-TW", - "type": "registry:block", - "title": "AnimatedList", - "description": "List items enter with staggered motion variants for polished reveals.", - "dependencies": [ - "motion" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/Components/AnimatedList/AnimatedList.tsx", - "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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "AnimatedList-TS-TW", + "title": "AnimatedList", + "description": "List items enter with staggered motion variants for polished reveals.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "AnimatedList/AnimatedList.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "motion@^12.23.12" + ] } \ No newline at end of file diff --git a/public/r/Aurora-JS-CSS.json b/public/r/Aurora-JS-CSS.json index 2d1f1263..0521aa4a 100644 --- a/public/r/Aurora-JS-CSS.json +++ b/public/r/Aurora-JS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Aurora-JS-CSS", - "type": "registry:block", - "title": "Aurora", - "description": "Flowing aurora gradient background.", - "dependencies": [ - "ogl" - ], - "files": [ - { - "path": "public/default/src/content/Backgrounds/Aurora/Aurora.jsx", - "content": "import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\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\nexport default function Aurora(props) {\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;\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 => {\n animateId = requestAnimationFrame(update);\n const { time = t * 0.01, speed = 1.0 } = propsRef.current;\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 => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n renderer.render({ scene: mesh });\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 // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [amplitude]);\n\n return
;\n}\n", - "type": "registry:component" - }, - { - "path": "public/default/src/content/Backgrounds/Aurora/Aurora.css", - "content": ".aurora-container {\n width: 100%;\n height: 100%;\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Aurora-JS-CSS", + "title": "Aurora", + "description": "Flowing aurora gradient background.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "Aurora/Aurora.css", + "content": ".aurora-container {\n width: 100%;\n height: 100%;\n}\n" + }, + { + "type": "registry:component", + "path": "Aurora/Aurora.jsx", + "content": "import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\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\nexport default function Aurora(props) {\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;\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 => {\n animateId = requestAnimationFrame(update);\n const { time = t * 0.01, speed = 1.0 } = propsRef.current;\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 => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n renderer.render({ scene: mesh });\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 // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [amplitude]);\n\n return
;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "ogl@^1.0.11" + ] } \ No newline at end of file diff --git a/public/r/Aurora-JS-TW.json b/public/r/Aurora-JS-TW.json index 9d473a0b..d953202c 100644 --- a/public/r/Aurora-JS-TW.json +++ b/public/r/Aurora-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Aurora-JS-TW", - "type": "registry:block", - "title": "Aurora", - "description": "Flowing aurora gradient background.", - "dependencies": [ - "ogl" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Backgrounds/Aurora/Aurora.jsx", - "content": "import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\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\nexport default function Aurora(props) {\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;\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 => {\n animateId = requestAnimationFrame(update);\n const { time = t * 0.01, speed = 1.0 } = propsRef.current;\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 => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n renderer.render({ scene: mesh });\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 // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [amplitude]);\n\n return
;\n}\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Aurora-JS-TW", + "title": "Aurora", + "description": "Flowing aurora gradient background.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "Aurora/Aurora.jsx", + "content": "import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\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\nexport default function Aurora(props) {\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;\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 => {\n animateId = requestAnimationFrame(update);\n const { time = t * 0.01, speed = 1.0 } = propsRef.current;\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 => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n renderer.render({ scene: mesh });\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 // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [amplitude]);\n\n return
;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "ogl@^1.0.11" + ] } \ No newline at end of file diff --git a/public/r/Aurora-TS-CSS.json b/public/r/Aurora-TS-CSS.json index deffc191..5e4d8e4e 100644 --- a/public/r/Aurora-TS-CSS.json +++ b/public/r/Aurora-TS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Aurora-TS-CSS", - "type": "registry:block", - "title": "Aurora", - "description": "Flowing aurora gradient background.", - "dependencies": [ - "ogl" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/Backgrounds/Aurora/Aurora.tsx", - "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", - "type": "registry:component" - }, - { - "path": "public/ts/default/src/ts-default/Backgrounds/Aurora/Aurora.css", - "content": ".aurora-container {\n width: 100%;\n height: 100%;\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Aurora-TS-CSS", + "title": "Aurora", + "description": "Flowing aurora gradient background.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "Aurora/Aurora.css", + "content": ".aurora-container {\n width: 100%;\n height: 100%;\n}\n" + }, + { + "type": "registry:component", + "path": "Aurora/Aurora.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "ogl@^1.0.11" + ] } \ No newline at end of file diff --git a/public/r/Aurora-TS-TW.json b/public/r/Aurora-TS-TW.json index 31bca370..6cce137e 100644 --- a/public/r/Aurora-TS-TW.json +++ b/public/r/Aurora-TS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Aurora-TS-TW", - "type": "registry:block", - "title": "Aurora", - "description": "Flowing aurora gradient background.", - "dependencies": [ - "ogl" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/Backgrounds/Aurora/Aurora.tsx", - "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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Aurora-TS-TW", + "title": "Aurora", + "description": "Flowing aurora gradient background.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "Aurora/Aurora.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "ogl@^1.0.11" + ] } \ No newline at end of file diff --git a/public/r/Balatro-JS-CSS.json b/public/r/Balatro-JS-CSS.json index 08d4a61c..c03e2335 100644 --- a/public/r/Balatro-JS-CSS.json +++ b/public/r/Balatro-JS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Balatro-JS-CSS", - "type": "registry:block", - "title": "Balatro", - "description": "The balatro shader, fully customizalbe and interactive.", - "dependencies": [ - "ogl" - ], - "files": [ - { - "path": "public/default/src/content/Backgrounds/Balatro/Balatro.jsx", - "content": "import { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\n\nimport './Balatro.css';\n\nfunction hexToVec4(hex) {\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}) {\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;\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;\n\n function update(time) {\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) {\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 containerRef\n ]);\n\n return
;\n}\n", - "type": "registry:component" - }, - { - "path": "public/default/src/content/Backgrounds/Balatro/Balatro.css", - "content": ".balatro-container {\n width: 100%;\n height: 100%;\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Balatro-JS-CSS", + "title": "Balatro", + "description": "The balatro shader, fully customizalbe and interactive.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "Balatro/Balatro.css", + "content": ".balatro-container {\n width: 100%;\n height: 100%;\n}\n" + }, + { + "type": "registry:component", + "path": "Balatro/Balatro.jsx", + "content": "import { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\n\nimport './Balatro.css';\n\nfunction hexToVec4(hex) {\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}) {\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;\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;\n\n function update(time) {\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) {\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 containerRef\n ]);\n\n return
;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "ogl@^1.0.11" + ] } \ No newline at end of file diff --git a/public/r/Balatro-JS-TW.json b/public/r/Balatro-JS-TW.json index 7acd2f0d..298e2bd6 100644 --- a/public/r/Balatro-JS-TW.json +++ b/public/r/Balatro-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Balatro-JS-TW", - "type": "registry:block", - "title": "Balatro", - "description": "The balatro shader, fully customizalbe and interactive.", - "dependencies": [ - "ogl" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Backgrounds/Balatro/Balatro.jsx", - "content": "import { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\n\nfunction hexToVec4(hex) {\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}) {\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;\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;\n\n function update(time) {\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) {\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 containerRef\n ]);\n\n return
;\n}\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Balatro-JS-TW", + "title": "Balatro", + "description": "The balatro shader, fully customizalbe and interactive.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "Balatro/Balatro.jsx", + "content": "import { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\n\nfunction hexToVec4(hex) {\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}) {\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;\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;\n\n function update(time) {\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) {\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 containerRef\n ]);\n\n return
;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "ogl@^1.0.11" + ] } \ No newline at end of file diff --git a/public/r/Balatro-TS-CSS.json b/public/r/Balatro-TS-CSS.json index 0585eed8..8efd9b1e 100644 --- a/public/r/Balatro-TS-CSS.json +++ b/public/r/Balatro-TS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Balatro-TS-CSS", - "type": "registry:block", - "title": "Balatro", - "description": "The balatro shader, fully customizalbe and interactive.", - "dependencies": [ - "ogl" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/Backgrounds/Balatro/Balatro.tsx", - "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", - "type": "registry:component" - }, - { - "path": "public/ts/default/src/ts-default/Backgrounds/Balatro/Balatro.css", - "content": ".balatro-container {\n width: 100%;\n height: 100%;\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Balatro-TS-CSS", + "title": "Balatro", + "description": "The balatro shader, fully customizalbe and interactive.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "Balatro/Balatro.css", + "content": ".balatro-container {\n width: 100%;\n height: 100%;\n}\n" + }, + { + "type": "registry:component", + "path": "Balatro/Balatro.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "ogl@^1.0.11" + ] } \ No newline at end of file diff --git a/public/r/Balatro-TS-TW.json b/public/r/Balatro-TS-TW.json index fe54bcf5..763229ab 100644 --- a/public/r/Balatro-TS-TW.json +++ b/public/r/Balatro-TS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Balatro-TS-TW", - "type": "registry:block", - "title": "Balatro", - "description": "The balatro shader, fully customizalbe and interactive.", - "dependencies": [ - "ogl" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/Backgrounds/Balatro/Balatro.tsx", - "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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Balatro-TS-TW", + "title": "Balatro", + "description": "The balatro shader, fully customizalbe and interactive.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "Balatro/Balatro.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "ogl@^1.0.11" + ] } \ No newline at end of file diff --git a/public/r/Ballpit-JS-CSS.json b/public/r/Ballpit-JS-CSS.json index 119d6cf6..fa0c174c 100644 --- a/public/r/Ballpit-JS-CSS.json +++ b/public/r/Ballpit-JS-CSS.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Ballpit-JS-CSS", - "type": "registry:block", - "title": "Ballpit", - "description": "Physics ball pit simulation with bouncing colorful spheres.", - "dependencies": [ - "three" - ], - "files": [ - { - "path": "public/default/src/content/Backgrounds/Ballpit/Ballpit.jsx", - "content": "import { useRef, useEffect } from 'react';\nimport {\n Clock as e,\n PerspectiveCamera as t,\n Scene as i,\n WebGLRenderer as s,\n SRGBColorSpace as n,\n MathUtils as o,\n Vector2 as r,\n Vector3 as a,\n MeshPhysicalMaterial as c,\n ShaderChunk as h,\n Color as l,\n Object3D as m,\n InstancedMesh as d,\n PMREMGenerator as p,\n SphereGeometry as g,\n AmbientLight as f,\n PointLight as u,\n ACESFilmicToneMapping as v,\n Raycaster as y,\n Plane as w\n} from 'three';\nimport { RoomEnvironment as z } from 'three/examples/jsm/environments/RoomEnvironment.js';\n\nclass x {\n #e;\n canvas;\n camera;\n cameraMinAspect;\n cameraMaxAspect;\n cameraFov;\n maxPixelRatio;\n minPixelRatio;\n scene;\n renderer;\n #t;\n size = { width: 0, height: 0, wWidth: 0, wHeight: 0, ratio: 0, pixelRatio: 0 };\n render = this.#i;\n onBeforeRender = () => {};\n onAfterRender = () => {};\n onAfterResize = () => {};\n #s = false;\n #n = false;\n isDisposed = false;\n #o;\n #r;\n #a;\n #c = new e();\n #h = { elapsed: 0, delta: 0 };\n #l;\n constructor(e) {\n this.#e = { ...e };\n this.#m();\n this.#d();\n this.#p();\n this.resize();\n this.#g();\n }\n #m() {\n this.camera = new t();\n this.cameraFov = this.camera.fov;\n }\n #d() {\n this.scene = new i();\n }\n #p() {\n if (this.#e.canvas) {\n this.canvas = this.#e.canvas;\n } else if (this.#e.id) {\n this.canvas = document.getElementById(this.#e.id);\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas.style.display = 'block';\n const e = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#e.rendererOptions ?? {})\n };\n this.renderer = new s(e);\n this.renderer.outputColorSpace = n;\n }\n #g() {\n if (!(this.#e.size instanceof Object)) {\n window.addEventListener('resize', this.#f.bind(this));\n if (this.#e.size === 'parent' && this.canvas.parentNode) {\n this.#r = new ResizeObserver(this.#f.bind(this));\n this.#r.observe(this.canvas.parentNode);\n }\n }\n this.#o = new IntersectionObserver(this.#u.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#o.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#v.bind(this));\n }\n #y() {\n window.removeEventListener('resize', this.#f.bind(this));\n this.#r?.disconnect();\n this.#o?.disconnect();\n document.removeEventListener('visibilitychange', this.#v.bind(this));\n }\n #u(e) {\n this.#s = e[0].isIntersecting;\n this.#s ? this.#w() : this.#z();\n }\n #v() {\n if (this.#s) {\n document.hidden ? this.#z() : this.#w();\n }\n }\n #f() {\n if (this.#a) clearTimeout(this.#a);\n this.#a = setTimeout(this.resize.bind(this), 100);\n }\n resize() {\n let e, t;\n if (this.#e.size instanceof Object) {\n e = this.#e.size.width;\n t = this.#e.size.height;\n } else if (this.#e.size === 'parent' && this.canvas.parentNode) {\n e = this.canvas.parentNode.offsetWidth;\n t = this.canvas.parentNode.offsetHeight;\n } else {\n e = window.innerWidth;\n t = window.innerHeight;\n }\n this.size.width = e;\n this.size.height = t;\n this.size.ratio = e / t;\n this.#x();\n this.#b();\n this.onAfterResize(this.size);\n }\n #x() {\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.#A(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#A(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n #A(e) {\n const t = Math.tan(o.degToRad(this.cameraFov / 2)) / (this.camera.aspect / e);\n this.camera.fov = 2 * o.radToDeg(Math.atan(t));\n }\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const e = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(e / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if (this.camera.isOrthographicCamera) {\n this.size.wHeight = this.camera.top - this.camera.bottom;\n this.size.wWidth = this.camera.right - this.camera.left;\n }\n }\n #b() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#t?.setSize(this.size.width, this.size.height);\n let e = window.devicePixelRatio;\n if (this.maxPixelRatio && e > this.maxPixelRatio) {\n e = this.maxPixelRatio;\n } else if (this.minPixelRatio && e < this.minPixelRatio) {\n e = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(e);\n this.size.pixelRatio = e;\n }\n get postprocessing() {\n return this.#t;\n }\n set postprocessing(e) {\n this.#t = e;\n this.render = e.render.bind(e);\n }\n #w() {\n if (this.#n) return;\n const animate = () => {\n this.#l = requestAnimationFrame(animate);\n this.#h.delta = this.#c.getDelta();\n this.#h.elapsed += this.#h.delta;\n this.onBeforeRender(this.#h);\n this.render();\n this.onAfterRender(this.#h);\n };\n this.#n = true;\n this.#c.start();\n animate();\n }\n #z() {\n if (this.#n) {\n cancelAnimationFrame(this.#l);\n this.#n = false;\n this.#c.stop();\n }\n }\n #i() {\n this.renderer.render(this.scene, this.camera);\n }\n clear() {\n this.scene.traverse(e => {\n if (e.isMesh && typeof e.material === 'object' && e.material !== null) {\n Object.keys(e.material).forEach(t => {\n const i = e.material[t];\n if (i !== null && typeof i === 'object' && typeof i.dispose === 'function') {\n i.dispose();\n }\n });\n e.material.dispose();\n e.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n dispose() {\n this.#y();\n this.#z();\n this.clear();\n this.#t?.dispose();\n this.renderer.dispose();\n this.isDisposed = true;\n }\n}\n\nconst b = new Map(),\n A = new r();\nlet R = false;\nfunction S(e) {\n const t = {\n position: new r(),\n nPosition: new r(),\n hover: false,\n touching: false,\n onEnter() {},\n onMove() {},\n onClick() {},\n onLeave() {},\n ...e\n };\n (function (e, t) {\n if (!b.has(e)) {\n b.set(e, t);\n if (!R) {\n document.body.addEventListener('pointermove', M);\n document.body.addEventListener('pointerleave', L);\n document.body.addEventListener('click', C);\n\n document.body.addEventListener('touchstart', TouchStart, { passive: false });\n document.body.addEventListener('touchmove', TouchMove, { passive: false });\n document.body.addEventListener('touchend', TouchEnd, { passive: false });\n document.body.addEventListener('touchcancel', TouchEnd, { passive: false });\n\n R = true;\n }\n }\n })(e.domElement, t);\n t.dispose = () => {\n const t = e.domElement;\n b.delete(t);\n if (b.size === 0) {\n document.body.removeEventListener('pointermove', M);\n document.body.removeEventListener('pointerleave', L);\n document.body.removeEventListener('click', C);\n\n document.body.removeEventListener('touchstart', TouchStart);\n document.body.removeEventListener('touchmove', TouchMove);\n document.body.removeEventListener('touchend', TouchEnd);\n document.body.removeEventListener('touchcancel', TouchEnd);\n\n R = false;\n }\n };\n return t;\n}\n\nfunction M(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n processInteraction();\n}\n\nfunction processInteraction() {\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n if (D(i)) {\n P(t, i);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && !t.touching) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction C(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n P(t, i);\n if (D(i)) t.onClick(t);\n }\n}\n\nfunction L() {\n for (const t of b.values()) {\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction TouchStart(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n if (D(rect)) {\n t.touching = true;\n P(t, rect);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchMove(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n P(t, rect);\n\n if (D(rect)) {\n if (!t.hover) {\n t.hover = true;\n t.touching = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && t.touching) {\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchEnd() {\n for (const [, t] of b) {\n if (t.touching) {\n t.touching = false;\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n }\n}\n\nfunction P(e, t) {\n const { position: i, nPosition: s } = e;\n i.x = A.x - t.left;\n i.y = A.y - t.top;\n s.x = (i.x / t.width) * 2 - 1;\n s.y = (-i.y / t.height) * 2 + 1;\n}\nfunction D(e) {\n const { x: t, y: i } = A;\n const { left: s, top: n, width: o, height: r } = e;\n return t >= s && t <= s + o && i >= n && i <= n + r;\n}\n\nconst { randFloat: k, randFloatSpread: E } = o;\nconst F = new a();\nconst I = new a();\nconst O = new a();\nconst V = new a();\nconst B = new a();\nconst N = new a();\nconst _ = new a();\nconst j = new a();\nconst H = new a();\nconst T = new a();\n\nclass W {\n constructor(e) {\n this.config = e;\n this.positionData = new Float32Array(3 * e.count).fill(0);\n this.velocityData = new Float32Array(3 * e.count).fill(0);\n this.sizeData = new Float32Array(e.count).fill(1);\n this.center = new a();\n this.#R();\n this.setSizes();\n }\n #R() {\n const { config: e, positionData: t } = this;\n this.center.toArray(t, 0);\n for (let i = 1; i < e.count; i++) {\n const s = 3 * i;\n t[s] = E(2 * e.maxX);\n t[s + 1] = E(2 * e.maxY);\n t[s + 2] = E(2 * e.maxZ);\n }\n }\n setSizes() {\n const { config: e, sizeData: t } = this;\n t[0] = e.size0;\n for (let i = 1; i < e.count; i++) {\n t[i] = k(e.minSize, e.maxSize);\n }\n }\n update(e) {\n const { config: t, center: i, positionData: s, sizeData: n, velocityData: o } = this;\n let r = 0;\n if (t.controlSphere0) {\n r = 1;\n F.fromArray(s, 0);\n F.lerp(i, 0.1).toArray(s, 0);\n V.set(0, 0, 0).toArray(o, 0);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n B.y -= e.delta * t.gravity * n[idx];\n B.multiplyScalar(t.friction);\n B.clampLength(0, t.maxVelocity);\n I.add(B);\n I.toArray(s, base);\n B.toArray(o, base);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n const radius = n[idx];\n for (let jdx = idx + 1; jdx < t.count; jdx++) {\n const otherBase = 3 * jdx;\n O.fromArray(s, otherBase);\n N.fromArray(o, otherBase);\n const otherRadius = n[jdx];\n _.copy(O).sub(I);\n const dist = _.length();\n const sumRadius = radius + otherRadius;\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n j.copy(_)\n .normalize()\n .multiplyScalar(0.5 * overlap);\n H.copy(j).multiplyScalar(Math.max(B.length(), 1));\n T.copy(j).multiplyScalar(Math.max(N.length(), 1));\n I.sub(j);\n B.sub(H);\n I.toArray(s, base);\n B.toArray(o, base);\n O.add(j);\n N.add(T);\n O.toArray(s, otherBase);\n N.toArray(o, otherBase);\n }\n }\n if (t.controlSphere0) {\n _.copy(F).sub(I);\n const dist = _.length();\n const sumRadius0 = radius + n[0];\n if (dist < sumRadius0) {\n const diff = sumRadius0 - dist;\n j.copy(_.normalize()).multiplyScalar(diff);\n H.copy(j).multiplyScalar(Math.max(B.length(), 2));\n I.sub(j);\n B.sub(H);\n }\n }\n if (Math.abs(I.x) + radius > t.maxX) {\n I.x = Math.sign(I.x) * (t.maxX - radius);\n B.x = -B.x * t.wallBounce;\n }\n if (t.gravity === 0) {\n if (Math.abs(I.y) + radius > t.maxY) {\n I.y = Math.sign(I.y) * (t.maxY - radius);\n B.y = -B.y * t.wallBounce;\n }\n } else if (I.y - radius < -t.maxY) {\n I.y = -t.maxY + radius;\n B.y = -B.y * t.wallBounce;\n }\n const maxBoundary = Math.max(t.maxZ, t.maxSize);\n if (Math.abs(I.z) + radius > maxBoundary) {\n I.z = Math.sign(I.z) * (t.maxZ - radius);\n B.z = -B.z * t.wallBounce;\n }\n I.toArray(s, base);\n B.toArray(o, base);\n }\n }\n}\n\nclass Y extends c {\n constructor(e) {\n super(e);\n this.uniforms = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n this.defines.USE_UV = '';\n this.onBeforeCompile = e => {\n Object.assign(e.uniforms, this.uniforms);\n e.fragmentShader =\n '\\n uniform float thicknessPower;\\n uniform float thicknessScale;\\n uniform float thicknessDistortion;\\n uniform float thicknessAmbient;\\n uniform float thicknessAttenuation;\\n ' +\n e.fragmentShader;\n e.fragmentShader = e.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 t = h.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 e.fragmentShader = e.fragmentShader.replace('#include ', t);\n if (this.onBeforeCompile2) this.onBeforeCompile2(e);\n };\n }\n}\n\nconst X = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 16777215,\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 m();\n\nclass Z extends d {\n constructor(e, t = {}) {\n const i = { ...X, ...t };\n const s = new z();\n const n = new p(e, 0.04).fromScene(s).texture;\n const o = new g();\n const r = new Y({ envMap: n, ...i.materialParams });\n r.envMapRotation.x = -Math.PI / 2;\n super(o, r, i.count);\n this.config = i;\n this.physics = new W(i);\n this.#S();\n this.setColors(i.colors);\n }\n #S() {\n this.ambientLight = new f(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new u(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n setColors(e) {\n if (Array.isArray(e) && e.length > 1) {\n const t = (function (e) {\n let t, i;\n function setColors(e) {\n t = e;\n i = [];\n t.forEach(col => {\n i.push(new l(col));\n });\n }\n setColors(e);\n return {\n setColors,\n getColorAt: function (ratio, out = new l()) {\n const scaled = Math.max(0, Math.min(1, ratio)) * (t.length - 1);\n const idx = Math.floor(scaled);\n const start = i[idx];\n if (idx >= t.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = i[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 })(e);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, t.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light.color.copy(t.getColorAt(idx / this.count));\n }\n }\n this.instanceColor.needsUpdate = true;\n }\n }\n update(e) {\n this.physics.update(e);\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\nfunction createBallpit(e, t = {}) {\n const i = new x({\n canvas: e,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let s;\n i.renderer.toneMapping = v;\n i.camera.position.set(0, 0, 20);\n i.camera.lookAt(0, 0, 0);\n i.cameraMaxAspect = 1.5;\n i.resize();\n initialize(t);\n const n = new y();\n const o = new w(new a(0, 0, 1), 0);\n const r = new a();\n let c = false;\n\n e.style.touchAction = 'none';\n e.style.userSelect = 'none';\n e.style.webkitUserSelect = 'none';\n\n const h = S({\n domElement: e,\n onMove() {\n n.setFromCamera(h.nPosition, i.camera);\n i.camera.getWorldDirection(o.normal);\n n.ray.intersectPlane(o, r);\n s.physics.center.copy(r);\n s.config.controlSphere0 = true;\n },\n onLeave() {\n s.config.controlSphere0 = false;\n }\n });\n function initialize(e) {\n if (s) {\n i.clear();\n i.scene.remove(s);\n }\n s = new Z(i.renderer, e);\n i.scene.add(s);\n }\n i.onBeforeRender = e => {\n if (!c) s.update(e);\n };\n i.onAfterResize = e => {\n s.config.maxX = e.wWidth / 2;\n s.config.maxY = e.wHeight / 2;\n };\n return {\n three: i,\n get spheres() {\n return s;\n },\n setCount(e) {\n initialize({ ...s.config, count: e });\n },\n togglePause() {\n c = !c;\n },\n dispose() {\n h.dispose();\n i.dispose();\n }\n };\n}\n\nconst Ballpit = ({ 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, { followCursor, ...props });\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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Ballpit-JS-CSS", + "title": "Ballpit", + "description": "Physics ball pit simulation with bouncing colorful spheres.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "Ballpit/Ballpit.jsx", + "content": "import { useRef, useEffect } from 'react';\nimport {\n Clock as e,\n PerspectiveCamera as t,\n Scene as i,\n WebGLRenderer as s,\n SRGBColorSpace as n,\n MathUtils as o,\n Vector2 as r,\n Vector3 as a,\n MeshPhysicalMaterial as c,\n ShaderChunk as h,\n Color as l,\n Object3D as m,\n InstancedMesh as d,\n PMREMGenerator as p,\n SphereGeometry as g,\n AmbientLight as f,\n PointLight as u,\n ACESFilmicToneMapping as v,\n Raycaster as y,\n Plane as w\n} from 'three';\nimport { RoomEnvironment as z } from 'three/examples/jsm/environments/RoomEnvironment.js';\n\nclass x {\n #e;\n canvas;\n camera;\n cameraMinAspect;\n cameraMaxAspect;\n cameraFov;\n maxPixelRatio;\n minPixelRatio;\n scene;\n renderer;\n #t;\n size = { width: 0, height: 0, wWidth: 0, wHeight: 0, ratio: 0, pixelRatio: 0 };\n render = this.#i;\n onBeforeRender = () => {};\n onAfterRender = () => {};\n onAfterResize = () => {};\n #s = false;\n #n = false;\n isDisposed = false;\n #o;\n #r;\n #a;\n #c = new e();\n #h = { elapsed: 0, delta: 0 };\n #l;\n constructor(e) {\n this.#e = { ...e };\n this.#m();\n this.#d();\n this.#p();\n this.resize();\n this.#g();\n }\n #m() {\n this.camera = new t();\n this.cameraFov = this.camera.fov;\n }\n #d() {\n this.scene = new i();\n }\n #p() {\n if (this.#e.canvas) {\n this.canvas = this.#e.canvas;\n } else if (this.#e.id) {\n this.canvas = document.getElementById(this.#e.id);\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas.style.display = 'block';\n const e = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#e.rendererOptions ?? {})\n };\n this.renderer = new s(e);\n this.renderer.outputColorSpace = n;\n }\n #g() {\n if (!(this.#e.size instanceof Object)) {\n window.addEventListener('resize', this.#f.bind(this));\n if (this.#e.size === 'parent' && this.canvas.parentNode) {\n this.#r = new ResizeObserver(this.#f.bind(this));\n this.#r.observe(this.canvas.parentNode);\n }\n }\n this.#o = new IntersectionObserver(this.#u.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#o.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#v.bind(this));\n }\n #y() {\n window.removeEventListener('resize', this.#f.bind(this));\n this.#r?.disconnect();\n this.#o?.disconnect();\n document.removeEventListener('visibilitychange', this.#v.bind(this));\n }\n #u(e) {\n this.#s = e[0].isIntersecting;\n this.#s ? this.#w() : this.#z();\n }\n #v() {\n if (this.#s) {\n document.hidden ? this.#z() : this.#w();\n }\n }\n #f() {\n if (this.#a) clearTimeout(this.#a);\n this.#a = setTimeout(this.resize.bind(this), 100);\n }\n resize() {\n let e, t;\n if (this.#e.size instanceof Object) {\n e = this.#e.size.width;\n t = this.#e.size.height;\n } else if (this.#e.size === 'parent' && this.canvas.parentNode) {\n e = this.canvas.parentNode.offsetWidth;\n t = this.canvas.parentNode.offsetHeight;\n } else {\n e = window.innerWidth;\n t = window.innerHeight;\n }\n this.size.width = e;\n this.size.height = t;\n this.size.ratio = e / t;\n this.#x();\n this.#b();\n this.onAfterResize(this.size);\n }\n #x() {\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.#A(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#A(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n #A(e) {\n const t = Math.tan(o.degToRad(this.cameraFov / 2)) / (this.camera.aspect / e);\n this.camera.fov = 2 * o.radToDeg(Math.atan(t));\n }\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const e = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(e / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if (this.camera.isOrthographicCamera) {\n this.size.wHeight = this.camera.top - this.camera.bottom;\n this.size.wWidth = this.camera.right - this.camera.left;\n }\n }\n #b() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#t?.setSize(this.size.width, this.size.height);\n let e = window.devicePixelRatio;\n if (this.maxPixelRatio && e > this.maxPixelRatio) {\n e = this.maxPixelRatio;\n } else if (this.minPixelRatio && e < this.minPixelRatio) {\n e = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(e);\n this.size.pixelRatio = e;\n }\n get postprocessing() {\n return this.#t;\n }\n set postprocessing(e) {\n this.#t = e;\n this.render = e.render.bind(e);\n }\n #w() {\n if (this.#n) return;\n const animate = () => {\n this.#l = requestAnimationFrame(animate);\n this.#h.delta = this.#c.getDelta();\n this.#h.elapsed += this.#h.delta;\n this.onBeforeRender(this.#h);\n this.render();\n this.onAfterRender(this.#h);\n };\n this.#n = true;\n this.#c.start();\n animate();\n }\n #z() {\n if (this.#n) {\n cancelAnimationFrame(this.#l);\n this.#n = false;\n this.#c.stop();\n }\n }\n #i() {\n this.renderer.render(this.scene, this.camera);\n }\n clear() {\n this.scene.traverse(e => {\n if (e.isMesh && typeof e.material === 'object' && e.material !== null) {\n Object.keys(e.material).forEach(t => {\n const i = e.material[t];\n if (i !== null && typeof i === 'object' && typeof i.dispose === 'function') {\n i.dispose();\n }\n });\n e.material.dispose();\n e.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n dispose() {\n this.#y();\n this.#z();\n this.clear();\n this.#t?.dispose();\n this.renderer.dispose();\n this.isDisposed = true;\n }\n}\n\nconst b = new Map(),\n A = new r();\nlet R = false;\nfunction S(e) {\n const t = {\n position: new r(),\n nPosition: new r(),\n hover: false,\n touching: false,\n onEnter() {},\n onMove() {},\n onClick() {},\n onLeave() {},\n ...e\n };\n (function (e, t) {\n if (!b.has(e)) {\n b.set(e, t);\n if (!R) {\n document.body.addEventListener('pointermove', M);\n document.body.addEventListener('pointerleave', L);\n document.body.addEventListener('click', C);\n\n document.body.addEventListener('touchstart', TouchStart, { passive: false });\n document.body.addEventListener('touchmove', TouchMove, { passive: false });\n document.body.addEventListener('touchend', TouchEnd, { passive: false });\n document.body.addEventListener('touchcancel', TouchEnd, { passive: false });\n\n R = true;\n }\n }\n })(e.domElement, t);\n t.dispose = () => {\n const t = e.domElement;\n b.delete(t);\n if (b.size === 0) {\n document.body.removeEventListener('pointermove', M);\n document.body.removeEventListener('pointerleave', L);\n document.body.removeEventListener('click', C);\n\n document.body.removeEventListener('touchstart', TouchStart);\n document.body.removeEventListener('touchmove', TouchMove);\n document.body.removeEventListener('touchend', TouchEnd);\n document.body.removeEventListener('touchcancel', TouchEnd);\n\n R = false;\n }\n };\n return t;\n}\n\nfunction M(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n processInteraction();\n}\n\nfunction processInteraction() {\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n if (D(i)) {\n P(t, i);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && !t.touching) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction C(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n P(t, i);\n if (D(i)) t.onClick(t);\n }\n}\n\nfunction L() {\n for (const t of b.values()) {\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction TouchStart(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n if (D(rect)) {\n t.touching = true;\n P(t, rect);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchMove(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n P(t, rect);\n\n if (D(rect)) {\n if (!t.hover) {\n t.hover = true;\n t.touching = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && t.touching) {\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchEnd() {\n for (const [, t] of b) {\n if (t.touching) {\n t.touching = false;\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n }\n}\n\nfunction P(e, t) {\n const { position: i, nPosition: s } = e;\n i.x = A.x - t.left;\n i.y = A.y - t.top;\n s.x = (i.x / t.width) * 2 - 1;\n s.y = (-i.y / t.height) * 2 + 1;\n}\nfunction D(e) {\n const { x: t, y: i } = A;\n const { left: s, top: n, width: o, height: r } = e;\n return t >= s && t <= s + o && i >= n && i <= n + r;\n}\n\nconst { randFloat: k, randFloatSpread: E } = o;\nconst F = new a();\nconst I = new a();\nconst O = new a();\nconst V = new a();\nconst B = new a();\nconst N = new a();\nconst _ = new a();\nconst j = new a();\nconst H = new a();\nconst T = new a();\n\nclass W {\n constructor(e) {\n this.config = e;\n this.positionData = new Float32Array(3 * e.count).fill(0);\n this.velocityData = new Float32Array(3 * e.count).fill(0);\n this.sizeData = new Float32Array(e.count).fill(1);\n this.center = new a();\n this.#R();\n this.setSizes();\n }\n #R() {\n const { config: e, positionData: t } = this;\n this.center.toArray(t, 0);\n for (let i = 1; i < e.count; i++) {\n const s = 3 * i;\n t[s] = E(2 * e.maxX);\n t[s + 1] = E(2 * e.maxY);\n t[s + 2] = E(2 * e.maxZ);\n }\n }\n setSizes() {\n const { config: e, sizeData: t } = this;\n t[0] = e.size0;\n for (let i = 1; i < e.count; i++) {\n t[i] = k(e.minSize, e.maxSize);\n }\n }\n update(e) {\n const { config: t, center: i, positionData: s, sizeData: n, velocityData: o } = this;\n let r = 0;\n if (t.controlSphere0) {\n r = 1;\n F.fromArray(s, 0);\n F.lerp(i, 0.1).toArray(s, 0);\n V.set(0, 0, 0).toArray(o, 0);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n B.y -= e.delta * t.gravity * n[idx];\n B.multiplyScalar(t.friction);\n B.clampLength(0, t.maxVelocity);\n I.add(B);\n I.toArray(s, base);\n B.toArray(o, base);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n const radius = n[idx];\n for (let jdx = idx + 1; jdx < t.count; jdx++) {\n const otherBase = 3 * jdx;\n O.fromArray(s, otherBase);\n N.fromArray(o, otherBase);\n const otherRadius = n[jdx];\n _.copy(O).sub(I);\n const dist = _.length();\n const sumRadius = radius + otherRadius;\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n j.copy(_)\n .normalize()\n .multiplyScalar(0.5 * overlap);\n H.copy(j).multiplyScalar(Math.max(B.length(), 1));\n T.copy(j).multiplyScalar(Math.max(N.length(), 1));\n I.sub(j);\n B.sub(H);\n I.toArray(s, base);\n B.toArray(o, base);\n O.add(j);\n N.add(T);\n O.toArray(s, otherBase);\n N.toArray(o, otherBase);\n }\n }\n if (t.controlSphere0) {\n _.copy(F).sub(I);\n const dist = _.length();\n const sumRadius0 = radius + n[0];\n if (dist < sumRadius0) {\n const diff = sumRadius0 - dist;\n j.copy(_.normalize()).multiplyScalar(diff);\n H.copy(j).multiplyScalar(Math.max(B.length(), 2));\n I.sub(j);\n B.sub(H);\n }\n }\n if (Math.abs(I.x) + radius > t.maxX) {\n I.x = Math.sign(I.x) * (t.maxX - radius);\n B.x = -B.x * t.wallBounce;\n }\n if (t.gravity === 0) {\n if (Math.abs(I.y) + radius > t.maxY) {\n I.y = Math.sign(I.y) * (t.maxY - radius);\n B.y = -B.y * t.wallBounce;\n }\n } else if (I.y - radius < -t.maxY) {\n I.y = -t.maxY + radius;\n B.y = -B.y * t.wallBounce;\n }\n const maxBoundary = Math.max(t.maxZ, t.maxSize);\n if (Math.abs(I.z) + radius > maxBoundary) {\n I.z = Math.sign(I.z) * (t.maxZ - radius);\n B.z = -B.z * t.wallBounce;\n }\n I.toArray(s, base);\n B.toArray(o, base);\n }\n }\n}\n\nclass Y extends c {\n constructor(e) {\n super(e);\n this.uniforms = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n this.defines.USE_UV = '';\n this.onBeforeCompile = e => {\n Object.assign(e.uniforms, this.uniforms);\n e.fragmentShader =\n '\\n uniform float thicknessPower;\\n uniform float thicknessScale;\\n uniform float thicknessDistortion;\\n uniform float thicknessAmbient;\\n uniform float thicknessAttenuation;\\n ' +\n e.fragmentShader;\n e.fragmentShader = e.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 t = h.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 e.fragmentShader = e.fragmentShader.replace('#include ', t);\n if (this.onBeforeCompile2) this.onBeforeCompile2(e);\n };\n }\n}\n\nconst X = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 16777215,\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 m();\n\nclass Z extends d {\n constructor(e, t = {}) {\n const i = { ...X, ...t };\n const s = new z();\n const n = new p(e, 0.04).fromScene(s).texture;\n const o = new g();\n const r = new Y({ envMap: n, ...i.materialParams });\n r.envMapRotation.x = -Math.PI / 2;\n super(o, r, i.count);\n this.config = i;\n this.physics = new W(i);\n this.#S();\n this.setColors(i.colors);\n }\n #S() {\n this.ambientLight = new f(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new u(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n setColors(e) {\n if (Array.isArray(e) && e.length > 1) {\n const t = (function (e) {\n let t, i;\n function setColors(e) {\n t = e;\n i = [];\n t.forEach(col => {\n i.push(new l(col));\n });\n }\n setColors(e);\n return {\n setColors,\n getColorAt: function (ratio, out = new l()) {\n const scaled = Math.max(0, Math.min(1, ratio)) * (t.length - 1);\n const idx = Math.floor(scaled);\n const start = i[idx];\n if (idx >= t.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = i[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 })(e);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, t.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light.color.copy(t.getColorAt(idx / this.count));\n }\n }\n this.instanceColor.needsUpdate = true;\n }\n }\n update(e) {\n this.physics.update(e);\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\nfunction createBallpit(e, t = {}) {\n const i = new x({\n canvas: e,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let s;\n i.renderer.toneMapping = v;\n i.camera.position.set(0, 0, 20);\n i.camera.lookAt(0, 0, 0);\n i.cameraMaxAspect = 1.5;\n i.resize();\n initialize(t);\n const n = new y();\n const o = new w(new a(0, 0, 1), 0);\n const r = new a();\n let c = false;\n\n e.style.touchAction = 'none';\n e.style.userSelect = 'none';\n e.style.webkitUserSelect = 'none';\n\n const h = S({\n domElement: e,\n onMove() {\n n.setFromCamera(h.nPosition, i.camera);\n i.camera.getWorldDirection(o.normal);\n n.ray.intersectPlane(o, r);\n s.physics.center.copy(r);\n s.config.controlSphere0 = true;\n },\n onLeave() {\n s.config.controlSphere0 = false;\n }\n });\n function initialize(e) {\n if (s) {\n i.clear();\n i.scene.remove(s);\n }\n s = new Z(i.renderer, e);\n i.scene.add(s);\n }\n i.onBeforeRender = e => {\n if (!c) s.update(e);\n };\n i.onAfterResize = e => {\n s.config.maxX = e.wWidth / 2;\n s.config.maxY = e.wHeight / 2;\n };\n return {\n three: i,\n get spheres() {\n return s;\n },\n setCount(e) {\n initialize({ ...s.config, count: e });\n },\n togglePause() {\n c = !c;\n },\n dispose() {\n h.dispose();\n i.dispose();\n }\n };\n}\n\nconst Ballpit = ({ 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, { followCursor, ...props });\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" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1" + ] } \ No newline at end of file diff --git a/public/r/Ballpit-JS-TW.json b/public/r/Ballpit-JS-TW.json index e65cc6ab..eda060c6 100644 --- a/public/r/Ballpit-JS-TW.json +++ b/public/r/Ballpit-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Ballpit-JS-TW", - "type": "registry:block", - "title": "Ballpit", - "description": "Physics ball pit simulation with bouncing colorful spheres.", - "dependencies": [ - "three" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Backgrounds/Ballpit/Ballpit.jsx", - "content": "import { useRef, useEffect } from 'react';\nimport {\n Clock as e,\n PerspectiveCamera as t,\n Scene as i,\n WebGLRenderer as s,\n SRGBColorSpace as n,\n MathUtils as o,\n Vector2 as r,\n Vector3 as a,\n MeshPhysicalMaterial as c,\n ShaderChunk as h,\n Color as l,\n Object3D as m,\n InstancedMesh as d,\n PMREMGenerator as p,\n SphereGeometry as g,\n AmbientLight as f,\n PointLight as u,\n ACESFilmicToneMapping as v,\n Raycaster as y,\n Plane as w\n} from 'three';\nimport { RoomEnvironment as z } from 'three/examples/jsm/environments/RoomEnvironment.js';\n\nclass x {\n #e;\n canvas;\n camera;\n cameraMinAspect;\n cameraMaxAspect;\n cameraFov;\n maxPixelRatio;\n minPixelRatio;\n scene;\n renderer;\n #t;\n size = { width: 0, height: 0, wWidth: 0, wHeight: 0, ratio: 0, pixelRatio: 0 };\n render = this.#i;\n onBeforeRender = () => {};\n onAfterRender = () => {};\n onAfterResize = () => {};\n #s = false;\n #n = false;\n isDisposed = false;\n #o;\n #r;\n #a;\n #c = new e();\n #h = { elapsed: 0, delta: 0 };\n #l;\n constructor(e) {\n this.#e = { ...e };\n this.#m();\n this.#d();\n this.#p();\n this.resize();\n this.#g();\n }\n #m() {\n this.camera = new t();\n this.cameraFov = this.camera.fov;\n }\n #d() {\n this.scene = new i();\n }\n #p() {\n if (this.#e.canvas) {\n this.canvas = this.#e.canvas;\n } else if (this.#e.id) {\n this.canvas = document.getElementById(this.#e.id);\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas.style.display = 'block';\n const e = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#e.rendererOptions ?? {})\n };\n this.renderer = new s(e);\n this.renderer.outputColorSpace = n;\n }\n #g() {\n if (!(this.#e.size instanceof Object)) {\n window.addEventListener('resize', this.#f.bind(this));\n if (this.#e.size === 'parent' && this.canvas.parentNode) {\n this.#r = new ResizeObserver(this.#f.bind(this));\n this.#r.observe(this.canvas.parentNode);\n }\n }\n this.#o = new IntersectionObserver(this.#u.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#o.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#v.bind(this));\n }\n #y() {\n window.removeEventListener('resize', this.#f.bind(this));\n this.#r?.disconnect();\n this.#o?.disconnect();\n document.removeEventListener('visibilitychange', this.#v.bind(this));\n }\n #u(e) {\n this.#s = e[0].isIntersecting;\n this.#s ? this.#w() : this.#z();\n }\n #v() {\n if (this.#s) {\n document.hidden ? this.#z() : this.#w();\n }\n }\n #f() {\n if (this.#a) clearTimeout(this.#a);\n this.#a = setTimeout(this.resize.bind(this), 100);\n }\n resize() {\n let e, t;\n if (this.#e.size instanceof Object) {\n e = this.#e.size.width;\n t = this.#e.size.height;\n } else if (this.#e.size === 'parent' && this.canvas.parentNode) {\n e = this.canvas.parentNode.offsetWidth;\n t = this.canvas.parentNode.offsetHeight;\n } else {\n e = window.innerWidth;\n t = window.innerHeight;\n }\n this.size.width = e;\n this.size.height = t;\n this.size.ratio = e / t;\n this.#x();\n this.#b();\n this.onAfterResize(this.size);\n }\n #x() {\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.#A(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#A(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n #A(e) {\n const t = Math.tan(o.degToRad(this.cameraFov / 2)) / (this.camera.aspect / e);\n this.camera.fov = 2 * o.radToDeg(Math.atan(t));\n }\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const e = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(e / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if (this.camera.isOrthographicCamera) {\n this.size.wHeight = this.camera.top - this.camera.bottom;\n this.size.wWidth = this.camera.right - this.camera.left;\n }\n }\n #b() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#t?.setSize(this.size.width, this.size.height);\n let e = window.devicePixelRatio;\n if (this.maxPixelRatio && e > this.maxPixelRatio) {\n e = this.maxPixelRatio;\n } else if (this.minPixelRatio && e < this.minPixelRatio) {\n e = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(e);\n this.size.pixelRatio = e;\n }\n get postprocessing() {\n return this.#t;\n }\n set postprocessing(e) {\n this.#t = e;\n this.render = e.render.bind(e);\n }\n #w() {\n if (this.#n) return;\n const animate = () => {\n this.#l = requestAnimationFrame(animate);\n this.#h.delta = this.#c.getDelta();\n this.#h.elapsed += this.#h.delta;\n this.onBeforeRender(this.#h);\n this.render();\n this.onAfterRender(this.#h);\n };\n this.#n = true;\n this.#c.start();\n animate();\n }\n #z() {\n if (this.#n) {\n cancelAnimationFrame(this.#l);\n this.#n = false;\n this.#c.stop();\n }\n }\n #i() {\n this.renderer.render(this.scene, this.camera);\n }\n clear() {\n this.scene.traverse(e => {\n if (e.isMesh && typeof e.material === 'object' && e.material !== null) {\n Object.keys(e.material).forEach(t => {\n const i = e.material[t];\n if (i !== null && typeof i === 'object' && typeof i.dispose === 'function') {\n i.dispose();\n }\n });\n e.material.dispose();\n e.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n dispose() {\n this.#y();\n this.#z();\n this.clear();\n this.#t?.dispose();\n this.renderer.dispose();\n this.isDisposed = true;\n }\n}\n\nconst b = new Map(),\n A = new r();\nlet R = false;\nfunction S(e) {\n const t = {\n position: new r(),\n nPosition: new r(),\n hover: false,\n touching: false,\n onEnter() {},\n onMove() {},\n onClick() {},\n onLeave() {},\n ...e\n };\n (function (e, t) {\n if (!b.has(e)) {\n b.set(e, t);\n if (!R) {\n document.body.addEventListener('pointermove', M);\n document.body.addEventListener('pointerleave', L);\n document.body.addEventListener('click', C);\n\n document.body.addEventListener('touchstart', TouchStart, { passive: false });\n document.body.addEventListener('touchmove', TouchMove, { passive: false });\n document.body.addEventListener('touchend', TouchEnd, { passive: false });\n document.body.addEventListener('touchcancel', TouchEnd, { passive: false });\n\n R = true;\n }\n }\n })(e.domElement, t);\n t.dispose = () => {\n const t = e.domElement;\n b.delete(t);\n if (b.size === 0) {\n document.body.removeEventListener('pointermove', M);\n document.body.removeEventListener('pointerleave', L);\n document.body.removeEventListener('click', C);\n\n document.body.removeEventListener('touchstart', TouchStart);\n document.body.removeEventListener('touchmove', TouchMove);\n document.body.removeEventListener('touchend', TouchEnd);\n document.body.removeEventListener('touchcancel', TouchEnd);\n\n R = false;\n }\n };\n return t;\n}\n\nfunction M(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n processInteraction();\n}\n\nfunction processInteraction() {\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n if (D(i)) {\n P(t, i);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && !t.touching) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction C(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n P(t, i);\n if (D(i)) t.onClick(t);\n }\n}\n\nfunction L() {\n for (const t of b.values()) {\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction TouchStart(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n if (D(rect)) {\n t.touching = true;\n P(t, rect);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchMove(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n P(t, rect);\n\n if (D(rect)) {\n if (!t.hover) {\n t.hover = true;\n t.touching = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && t.touching) {\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchEnd() {\n for (const [, t] of b) {\n if (t.touching) {\n t.touching = false;\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n }\n}\n\nfunction P(e, t) {\n const { position: i, nPosition: s } = e;\n i.x = A.x - t.left;\n i.y = A.y - t.top;\n s.x = (i.x / t.width) * 2 - 1;\n s.y = (-i.y / t.height) * 2 + 1;\n}\nfunction D(e) {\n const { x: t, y: i } = A;\n const { left: s, top: n, width: o, height: r } = e;\n return t >= s && t <= s + o && i >= n && i <= n + r;\n}\n\nconst { randFloat: k, randFloatSpread: E } = o;\nconst F = new a();\nconst I = new a();\nconst O = new a();\nconst V = new a();\nconst B = new a();\nconst N = new a();\nconst _ = new a();\nconst j = new a();\nconst H = new a();\nconst T = new a();\n\nclass W {\n constructor(e) {\n this.config = e;\n this.positionData = new Float32Array(3 * e.count).fill(0);\n this.velocityData = new Float32Array(3 * e.count).fill(0);\n this.sizeData = new Float32Array(e.count).fill(1);\n this.center = new a();\n this.#R();\n this.setSizes();\n }\n #R() {\n const { config: e, positionData: t } = this;\n this.center.toArray(t, 0);\n for (let i = 1; i < e.count; i++) {\n const s = 3 * i;\n t[s] = E(2 * e.maxX);\n t[s + 1] = E(2 * e.maxY);\n t[s + 2] = E(2 * e.maxZ);\n }\n }\n setSizes() {\n const { config: e, sizeData: t } = this;\n t[0] = e.size0;\n for (let i = 1; i < e.count; i++) {\n t[i] = k(e.minSize, e.maxSize);\n }\n }\n update(e) {\n const { config: t, center: i, positionData: s, sizeData: n, velocityData: o } = this;\n let r = 0;\n if (t.controlSphere0) {\n r = 1;\n F.fromArray(s, 0);\n F.lerp(i, 0.1).toArray(s, 0);\n V.set(0, 0, 0).toArray(o, 0);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n B.y -= e.delta * t.gravity * n[idx];\n B.multiplyScalar(t.friction);\n B.clampLength(0, t.maxVelocity);\n I.add(B);\n I.toArray(s, base);\n B.toArray(o, base);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n const radius = n[idx];\n for (let jdx = idx + 1; jdx < t.count; jdx++) {\n const otherBase = 3 * jdx;\n O.fromArray(s, otherBase);\n N.fromArray(o, otherBase);\n const otherRadius = n[jdx];\n _.copy(O).sub(I);\n const dist = _.length();\n const sumRadius = radius + otherRadius;\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n j.copy(_)\n .normalize()\n .multiplyScalar(0.5 * overlap);\n H.copy(j).multiplyScalar(Math.max(B.length(), 1));\n T.copy(j).multiplyScalar(Math.max(N.length(), 1));\n I.sub(j);\n B.sub(H);\n I.toArray(s, base);\n B.toArray(o, base);\n O.add(j);\n N.add(T);\n O.toArray(s, otherBase);\n N.toArray(o, otherBase);\n }\n }\n if (t.controlSphere0) {\n _.copy(F).sub(I);\n const dist = _.length();\n const sumRadius0 = radius + n[0];\n if (dist < sumRadius0) {\n const diff = sumRadius0 - dist;\n j.copy(_.normalize()).multiplyScalar(diff);\n H.copy(j).multiplyScalar(Math.max(B.length(), 2));\n I.sub(j);\n B.sub(H);\n }\n }\n if (Math.abs(I.x) + radius > t.maxX) {\n I.x = Math.sign(I.x) * (t.maxX - radius);\n B.x = -B.x * t.wallBounce;\n }\n if (t.gravity === 0) {\n if (Math.abs(I.y) + radius > t.maxY) {\n I.y = Math.sign(I.y) * (t.maxY - radius);\n B.y = -B.y * t.wallBounce;\n }\n } else if (I.y - radius < -t.maxY) {\n I.y = -t.maxY + radius;\n B.y = -B.y * t.wallBounce;\n }\n const maxBoundary = Math.max(t.maxZ, t.maxSize);\n if (Math.abs(I.z) + radius > maxBoundary) {\n I.z = Math.sign(I.z) * (t.maxZ - radius);\n B.z = -B.z * t.wallBounce;\n }\n I.toArray(s, base);\n B.toArray(o, base);\n }\n }\n}\n\nclass Y extends c {\n constructor(e) {\n super(e);\n this.uniforms = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n this.defines.USE_UV = '';\n this.onBeforeCompile = e => {\n Object.assign(e.uniforms, this.uniforms);\n e.fragmentShader =\n '\\n uniform float thicknessPower;\\n uniform float thicknessScale;\\n uniform float thicknessDistortion;\\n uniform float thicknessAmbient;\\n uniform float thicknessAttenuation;\\n ' +\n e.fragmentShader;\n e.fragmentShader = e.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 t = h.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 e.fragmentShader = e.fragmentShader.replace('#include ', t);\n if (this.onBeforeCompile2) this.onBeforeCompile2(e);\n };\n }\n}\n\nconst X = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 16777215,\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 m();\n\nclass Z extends d {\n constructor(e, t = {}) {\n const i = { ...X, ...t };\n const s = new z();\n const n = new p(e, 0.04).fromScene(s).texture;\n const o = new g();\n const r = new Y({ envMap: n, ...i.materialParams });\n r.envMapRotation.x = -Math.PI / 2;\n super(o, r, i.count);\n this.config = i;\n this.physics = new W(i);\n this.#S();\n this.setColors(i.colors);\n }\n #S() {\n this.ambientLight = new f(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new u(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n setColors(e) {\n if (Array.isArray(e) && e.length > 1) {\n const t = (function (e) {\n let t, i;\n function setColors(e) {\n t = e;\n i = [];\n t.forEach(col => {\n i.push(new l(col));\n });\n }\n setColors(e);\n return {\n setColors,\n getColorAt: function (ratio, out = new l()) {\n const scaled = Math.max(0, Math.min(1, ratio)) * (t.length - 1);\n const idx = Math.floor(scaled);\n const start = i[idx];\n if (idx >= t.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = i[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 })(e);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, t.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light.color.copy(t.getColorAt(idx / this.count));\n }\n }\n this.instanceColor.needsUpdate = true;\n }\n }\n update(e) {\n this.physics.update(e);\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\nfunction createBallpit(e, t = {}) {\n const i = new x({\n canvas: e,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let s;\n i.renderer.toneMapping = v;\n i.camera.position.set(0, 0, 20);\n i.camera.lookAt(0, 0, 0);\n i.cameraMaxAspect = 1.5;\n i.resize();\n initialize(t);\n const n = new y();\n const o = new w(new a(0, 0, 1), 0);\n const r = new a();\n let c = false;\n\n e.style.touchAction = 'none';\n e.style.userSelect = 'none';\n e.style.webkitUserSelect = 'none';\n\n const h = S({\n domElement: e,\n onMove() {\n n.setFromCamera(h.nPosition, i.camera);\n i.camera.getWorldDirection(o.normal);\n n.ray.intersectPlane(o, r);\n s.physics.center.copy(r);\n s.config.controlSphere0 = true;\n },\n onLeave() {\n s.config.controlSphere0 = false;\n }\n });\n function initialize(e) {\n if (s) {\n i.clear();\n i.scene.remove(s);\n }\n s = new Z(i.renderer, e);\n i.scene.add(s);\n }\n i.onBeforeRender = e => {\n if (!c) s.update(e);\n };\n i.onAfterResize = e => {\n s.config.maxX = e.wWidth / 2;\n s.config.maxY = e.wHeight / 2;\n };\n return {\n three: i,\n get spheres() {\n return s;\n },\n setCount(e) {\n initialize({ ...s.config, count: e });\n },\n togglePause() {\n c = !c;\n },\n dispose() {\n h.dispose();\n i.dispose();\n }\n };\n}\n\nconst Ballpit = ({ 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, { followCursor, ...props });\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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Ballpit-JS-TW", + "title": "Ballpit", + "description": "Physics ball pit simulation with bouncing colorful spheres.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "Ballpit/Ballpit.jsx", + "content": "import { useRef, useEffect } from 'react';\nimport {\n Clock as e,\n PerspectiveCamera as t,\n Scene as i,\n WebGLRenderer as s,\n SRGBColorSpace as n,\n MathUtils as o,\n Vector2 as r,\n Vector3 as a,\n MeshPhysicalMaterial as c,\n ShaderChunk as h,\n Color as l,\n Object3D as m,\n InstancedMesh as d,\n PMREMGenerator as p,\n SphereGeometry as g,\n AmbientLight as f,\n PointLight as u,\n ACESFilmicToneMapping as v,\n Raycaster as y,\n Plane as w\n} from 'three';\nimport { RoomEnvironment as z } from 'three/examples/jsm/environments/RoomEnvironment.js';\n\nclass x {\n #e;\n canvas;\n camera;\n cameraMinAspect;\n cameraMaxAspect;\n cameraFov;\n maxPixelRatio;\n minPixelRatio;\n scene;\n renderer;\n #t;\n size = { width: 0, height: 0, wWidth: 0, wHeight: 0, ratio: 0, pixelRatio: 0 };\n render = this.#i;\n onBeforeRender = () => {};\n onAfterRender = () => {};\n onAfterResize = () => {};\n #s = false;\n #n = false;\n isDisposed = false;\n #o;\n #r;\n #a;\n #c = new e();\n #h = { elapsed: 0, delta: 0 };\n #l;\n constructor(e) {\n this.#e = { ...e };\n this.#m();\n this.#d();\n this.#p();\n this.resize();\n this.#g();\n }\n #m() {\n this.camera = new t();\n this.cameraFov = this.camera.fov;\n }\n #d() {\n this.scene = new i();\n }\n #p() {\n if (this.#e.canvas) {\n this.canvas = this.#e.canvas;\n } else if (this.#e.id) {\n this.canvas = document.getElementById(this.#e.id);\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas.style.display = 'block';\n const e = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#e.rendererOptions ?? {})\n };\n this.renderer = new s(e);\n this.renderer.outputColorSpace = n;\n }\n #g() {\n if (!(this.#e.size instanceof Object)) {\n window.addEventListener('resize', this.#f.bind(this));\n if (this.#e.size === 'parent' && this.canvas.parentNode) {\n this.#r = new ResizeObserver(this.#f.bind(this));\n this.#r.observe(this.canvas.parentNode);\n }\n }\n this.#o = new IntersectionObserver(this.#u.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#o.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#v.bind(this));\n }\n #y() {\n window.removeEventListener('resize', this.#f.bind(this));\n this.#r?.disconnect();\n this.#o?.disconnect();\n document.removeEventListener('visibilitychange', this.#v.bind(this));\n }\n #u(e) {\n this.#s = e[0].isIntersecting;\n this.#s ? this.#w() : this.#z();\n }\n #v() {\n if (this.#s) {\n document.hidden ? this.#z() : this.#w();\n }\n }\n #f() {\n if (this.#a) clearTimeout(this.#a);\n this.#a = setTimeout(this.resize.bind(this), 100);\n }\n resize() {\n let e, t;\n if (this.#e.size instanceof Object) {\n e = this.#e.size.width;\n t = this.#e.size.height;\n } else if (this.#e.size === 'parent' && this.canvas.parentNode) {\n e = this.canvas.parentNode.offsetWidth;\n t = this.canvas.parentNode.offsetHeight;\n } else {\n e = window.innerWidth;\n t = window.innerHeight;\n }\n this.size.width = e;\n this.size.height = t;\n this.size.ratio = e / t;\n this.#x();\n this.#b();\n this.onAfterResize(this.size);\n }\n #x() {\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.#A(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#A(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n #A(e) {\n const t = Math.tan(o.degToRad(this.cameraFov / 2)) / (this.camera.aspect / e);\n this.camera.fov = 2 * o.radToDeg(Math.atan(t));\n }\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const e = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(e / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if (this.camera.isOrthographicCamera) {\n this.size.wHeight = this.camera.top - this.camera.bottom;\n this.size.wWidth = this.camera.right - this.camera.left;\n }\n }\n #b() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#t?.setSize(this.size.width, this.size.height);\n let e = window.devicePixelRatio;\n if (this.maxPixelRatio && e > this.maxPixelRatio) {\n e = this.maxPixelRatio;\n } else if (this.minPixelRatio && e < this.minPixelRatio) {\n e = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(e);\n this.size.pixelRatio = e;\n }\n get postprocessing() {\n return this.#t;\n }\n set postprocessing(e) {\n this.#t = e;\n this.render = e.render.bind(e);\n }\n #w() {\n if (this.#n) return;\n const animate = () => {\n this.#l = requestAnimationFrame(animate);\n this.#h.delta = this.#c.getDelta();\n this.#h.elapsed += this.#h.delta;\n this.onBeforeRender(this.#h);\n this.render();\n this.onAfterRender(this.#h);\n };\n this.#n = true;\n this.#c.start();\n animate();\n }\n #z() {\n if (this.#n) {\n cancelAnimationFrame(this.#l);\n this.#n = false;\n this.#c.stop();\n }\n }\n #i() {\n this.renderer.render(this.scene, this.camera);\n }\n clear() {\n this.scene.traverse(e => {\n if (e.isMesh && typeof e.material === 'object' && e.material !== null) {\n Object.keys(e.material).forEach(t => {\n const i = e.material[t];\n if (i !== null && typeof i === 'object' && typeof i.dispose === 'function') {\n i.dispose();\n }\n });\n e.material.dispose();\n e.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n dispose() {\n this.#y();\n this.#z();\n this.clear();\n this.#t?.dispose();\n this.renderer.dispose();\n this.isDisposed = true;\n }\n}\n\nconst b = new Map(),\n A = new r();\nlet R = false;\nfunction S(e) {\n const t = {\n position: new r(),\n nPosition: new r(),\n hover: false,\n touching: false,\n onEnter() {},\n onMove() {},\n onClick() {},\n onLeave() {},\n ...e\n };\n (function (e, t) {\n if (!b.has(e)) {\n b.set(e, t);\n if (!R) {\n document.body.addEventListener('pointermove', M);\n document.body.addEventListener('pointerleave', L);\n document.body.addEventListener('click', C);\n\n document.body.addEventListener('touchstart', TouchStart, { passive: false });\n document.body.addEventListener('touchmove', TouchMove, { passive: false });\n document.body.addEventListener('touchend', TouchEnd, { passive: false });\n document.body.addEventListener('touchcancel', TouchEnd, { passive: false });\n\n R = true;\n }\n }\n })(e.domElement, t);\n t.dispose = () => {\n const t = e.domElement;\n b.delete(t);\n if (b.size === 0) {\n document.body.removeEventListener('pointermove', M);\n document.body.removeEventListener('pointerleave', L);\n document.body.removeEventListener('click', C);\n\n document.body.removeEventListener('touchstart', TouchStart);\n document.body.removeEventListener('touchmove', TouchMove);\n document.body.removeEventListener('touchend', TouchEnd);\n document.body.removeEventListener('touchcancel', TouchEnd);\n\n R = false;\n }\n };\n return t;\n}\n\nfunction M(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n processInteraction();\n}\n\nfunction processInteraction() {\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n if (D(i)) {\n P(t, i);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && !t.touching) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction C(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n P(t, i);\n if (D(i)) t.onClick(t);\n }\n}\n\nfunction L() {\n for (const t of b.values()) {\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction TouchStart(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n if (D(rect)) {\n t.touching = true;\n P(t, rect);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchMove(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n P(t, rect);\n\n if (D(rect)) {\n if (!t.hover) {\n t.hover = true;\n t.touching = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && t.touching) {\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchEnd() {\n for (const [, t] of b) {\n if (t.touching) {\n t.touching = false;\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n }\n}\n\nfunction P(e, t) {\n const { position: i, nPosition: s } = e;\n i.x = A.x - t.left;\n i.y = A.y - t.top;\n s.x = (i.x / t.width) * 2 - 1;\n s.y = (-i.y / t.height) * 2 + 1;\n}\nfunction D(e) {\n const { x: t, y: i } = A;\n const { left: s, top: n, width: o, height: r } = e;\n return t >= s && t <= s + o && i >= n && i <= n + r;\n}\n\nconst { randFloat: k, randFloatSpread: E } = o;\nconst F = new a();\nconst I = new a();\nconst O = new a();\nconst V = new a();\nconst B = new a();\nconst N = new a();\nconst _ = new a();\nconst j = new a();\nconst H = new a();\nconst T = new a();\n\nclass W {\n constructor(e) {\n this.config = e;\n this.positionData = new Float32Array(3 * e.count).fill(0);\n this.velocityData = new Float32Array(3 * e.count).fill(0);\n this.sizeData = new Float32Array(e.count).fill(1);\n this.center = new a();\n this.#R();\n this.setSizes();\n }\n #R() {\n const { config: e, positionData: t } = this;\n this.center.toArray(t, 0);\n for (let i = 1; i < e.count; i++) {\n const s = 3 * i;\n t[s] = E(2 * e.maxX);\n t[s + 1] = E(2 * e.maxY);\n t[s + 2] = E(2 * e.maxZ);\n }\n }\n setSizes() {\n const { config: e, sizeData: t } = this;\n t[0] = e.size0;\n for (let i = 1; i < e.count; i++) {\n t[i] = k(e.minSize, e.maxSize);\n }\n }\n update(e) {\n const { config: t, center: i, positionData: s, sizeData: n, velocityData: o } = this;\n let r = 0;\n if (t.controlSphere0) {\n r = 1;\n F.fromArray(s, 0);\n F.lerp(i, 0.1).toArray(s, 0);\n V.set(0, 0, 0).toArray(o, 0);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n B.y -= e.delta * t.gravity * n[idx];\n B.multiplyScalar(t.friction);\n B.clampLength(0, t.maxVelocity);\n I.add(B);\n I.toArray(s, base);\n B.toArray(o, base);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n const radius = n[idx];\n for (let jdx = idx + 1; jdx < t.count; jdx++) {\n const otherBase = 3 * jdx;\n O.fromArray(s, otherBase);\n N.fromArray(o, otherBase);\n const otherRadius = n[jdx];\n _.copy(O).sub(I);\n const dist = _.length();\n const sumRadius = radius + otherRadius;\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n j.copy(_)\n .normalize()\n .multiplyScalar(0.5 * overlap);\n H.copy(j).multiplyScalar(Math.max(B.length(), 1));\n T.copy(j).multiplyScalar(Math.max(N.length(), 1));\n I.sub(j);\n B.sub(H);\n I.toArray(s, base);\n B.toArray(o, base);\n O.add(j);\n N.add(T);\n O.toArray(s, otherBase);\n N.toArray(o, otherBase);\n }\n }\n if (t.controlSphere0) {\n _.copy(F).sub(I);\n const dist = _.length();\n const sumRadius0 = radius + n[0];\n if (dist < sumRadius0) {\n const diff = sumRadius0 - dist;\n j.copy(_.normalize()).multiplyScalar(diff);\n H.copy(j).multiplyScalar(Math.max(B.length(), 2));\n I.sub(j);\n B.sub(H);\n }\n }\n if (Math.abs(I.x) + radius > t.maxX) {\n I.x = Math.sign(I.x) * (t.maxX - radius);\n B.x = -B.x * t.wallBounce;\n }\n if (t.gravity === 0) {\n if (Math.abs(I.y) + radius > t.maxY) {\n I.y = Math.sign(I.y) * (t.maxY - radius);\n B.y = -B.y * t.wallBounce;\n }\n } else if (I.y - radius < -t.maxY) {\n I.y = -t.maxY + radius;\n B.y = -B.y * t.wallBounce;\n }\n const maxBoundary = Math.max(t.maxZ, t.maxSize);\n if (Math.abs(I.z) + radius > maxBoundary) {\n I.z = Math.sign(I.z) * (t.maxZ - radius);\n B.z = -B.z * t.wallBounce;\n }\n I.toArray(s, base);\n B.toArray(o, base);\n }\n }\n}\n\nclass Y extends c {\n constructor(e) {\n super(e);\n this.uniforms = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n this.defines.USE_UV = '';\n this.onBeforeCompile = e => {\n Object.assign(e.uniforms, this.uniforms);\n e.fragmentShader =\n '\\n uniform float thicknessPower;\\n uniform float thicknessScale;\\n uniform float thicknessDistortion;\\n uniform float thicknessAmbient;\\n uniform float thicknessAttenuation;\\n ' +\n e.fragmentShader;\n e.fragmentShader = e.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 t = h.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 e.fragmentShader = e.fragmentShader.replace('#include ', t);\n if (this.onBeforeCompile2) this.onBeforeCompile2(e);\n };\n }\n}\n\nconst X = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 16777215,\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 m();\n\nclass Z extends d {\n constructor(e, t = {}) {\n const i = { ...X, ...t };\n const s = new z();\n const n = new p(e, 0.04).fromScene(s).texture;\n const o = new g();\n const r = new Y({ envMap: n, ...i.materialParams });\n r.envMapRotation.x = -Math.PI / 2;\n super(o, r, i.count);\n this.config = i;\n this.physics = new W(i);\n this.#S();\n this.setColors(i.colors);\n }\n #S() {\n this.ambientLight = new f(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new u(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n setColors(e) {\n if (Array.isArray(e) && e.length > 1) {\n const t = (function (e) {\n let t, i;\n function setColors(e) {\n t = e;\n i = [];\n t.forEach(col => {\n i.push(new l(col));\n });\n }\n setColors(e);\n return {\n setColors,\n getColorAt: function (ratio, out = new l()) {\n const scaled = Math.max(0, Math.min(1, ratio)) * (t.length - 1);\n const idx = Math.floor(scaled);\n const start = i[idx];\n if (idx >= t.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = i[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 })(e);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, t.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light.color.copy(t.getColorAt(idx / this.count));\n }\n }\n this.instanceColor.needsUpdate = true;\n }\n }\n update(e) {\n this.physics.update(e);\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\nfunction createBallpit(e, t = {}) {\n const i = new x({\n canvas: e,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let s;\n i.renderer.toneMapping = v;\n i.camera.position.set(0, 0, 20);\n i.camera.lookAt(0, 0, 0);\n i.cameraMaxAspect = 1.5;\n i.resize();\n initialize(t);\n const n = new y();\n const o = new w(new a(0, 0, 1), 0);\n const r = new a();\n let c = false;\n\n e.style.touchAction = 'none';\n e.style.userSelect = 'none';\n e.style.webkitUserSelect = 'none';\n\n const h = S({\n domElement: e,\n onMove() {\n n.setFromCamera(h.nPosition, i.camera);\n i.camera.getWorldDirection(o.normal);\n n.ray.intersectPlane(o, r);\n s.physics.center.copy(r);\n s.config.controlSphere0 = true;\n },\n onLeave() {\n s.config.controlSphere0 = false;\n }\n });\n function initialize(e) {\n if (s) {\n i.clear();\n i.scene.remove(s);\n }\n s = new Z(i.renderer, e);\n i.scene.add(s);\n }\n i.onBeforeRender = e => {\n if (!c) s.update(e);\n };\n i.onAfterResize = e => {\n s.config.maxX = e.wWidth / 2;\n s.config.maxY = e.wHeight / 2;\n };\n return {\n three: i,\n get spheres() {\n return s;\n },\n setCount(e) {\n initialize({ ...s.config, count: e });\n },\n togglePause() {\n c = !c;\n },\n dispose() {\n h.dispose();\n i.dispose();\n }\n };\n}\n\nconst Ballpit = ({ 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, { followCursor, ...props });\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" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1" + ] } \ No newline at end of file diff --git a/public/r/Ballpit-TS-CSS.json b/public/r/Ballpit-TS-CSS.json index 027f12a1..49685ea7 100644 --- a/public/r/Ballpit-TS-CSS.json +++ b/public/r/Ballpit-TS-CSS.json @@ -1,18 +1,19 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Ballpit-TS-CSS", - "type": "registry:block", - "title": "Ballpit", - "description": "Physics ball pit simulation with bouncing colorful spheres.", - "dependencies": [ - "gsap", - "three" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/Backgrounds/Ballpit/Ballpit.tsx", - "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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Ballpit-TS-CSS", + "title": "Ballpit", + "description": "Physics ball pit simulation with bouncing colorful spheres.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "Ballpit/Ballpit.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1", + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/Ballpit-TS-TW.json b/public/r/Ballpit-TS-TW.json index 1a7d448c..acc2a2bb 100644 --- a/public/r/Ballpit-TS-TW.json +++ b/public/r/Ballpit-TS-TW.json @@ -1,18 +1,19 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Ballpit-TS-TW", - "type": "registry:block", - "title": "Ballpit", - "description": "Physics ball pit simulation with bouncing colorful spheres.", - "dependencies": [ - "gsap", - "three" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/Backgrounds/Ballpit/Ballpit.tsx", - "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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Ballpit-TS-TW", + "title": "Ballpit", + "description": "Physics ball pit simulation with bouncing colorful spheres.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "Ballpit/Ballpit.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1", + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/Beams-JS-CSS.json b/public/r/Beams-JS-CSS.json index 25c61584..f07e7548 100644 --- a/public/r/Beams-JS-CSS.json +++ b/public/r/Beams-JS-CSS.json @@ -1,24 +1,25 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Beams-JS-CSS", - "type": "registry:block", - "title": "Beams", - "description": "Crossing animated ribbons with customizable properties.", - "dependencies": [ - "@react-three/drei", - "@react-three/fiber", - "three" - ], - "files": [ - { - "path": "public/default/src/content/Backgrounds/Beams/Beams.jsx", - "content": "/* eslint-disable react/no-unknown-property */\nimport { forwardRef, useImperativeHandle, useEffect, useRef, useMemo } 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\nfunction extendMaterial(BaseMaterial, cfg) {\n const physical = THREE.ShaderLib.physical;\n const { vertexShader: baseVert, fragmentShader: baseFrag, uniforms: baseUniforms } = physical;\n const baseDefines = physical.defines ?? {};\n\n const uniforms = THREE.UniformsUtils.clone(baseUniforms);\n\n const defaults = new BaseMaterial(cfg.material || {});\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] = u !== null && typeof u === 'object' && 'value' in u ? u : { value: u };\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 = ({ children }) => (\n \n {children}\n \n);\n\nconst hexToNormalizedRGB = hex => {\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\nconst Beams = ({\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 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, width, height, spacing, heightSegments) {\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(({ 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((props, ref) => (\n \n));\nPlaneNoise.displayName = 'PlaneNoise';\n\nconst DirLight = ({ position, color }) => {\n const dir = useRef(null);\n useEffect(() => {\n if (!dir.current) return;\n const cam = dir.current.shadow.camera;\n if (!cam) return;\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 // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n return ;\n};\n\nexport default Beams;\n", - "type": "registry:component" - }, - { - "path": "public/default/src/content/Backgrounds/Beams/Beams.css", - "content": ".beams-container {\n position: relative;\n width: 100%;\n height: 100%;\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Beams-JS-CSS", + "title": "Beams", + "description": "Crossing animated ribbons with customizable properties.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "Beams/Beams.css", + "content": ".beams-container {\n position: relative;\n width: 100%;\n height: 100%;\n}\n" + }, + { + "type": "registry:component", + "path": "Beams/Beams.jsx", + "content": "/* eslint-disable react/no-unknown-property */\nimport { forwardRef, useImperativeHandle, useEffect, useRef, useMemo } 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\nfunction extendMaterial(BaseMaterial, cfg) {\n const physical = THREE.ShaderLib.physical;\n const { vertexShader: baseVert, fragmentShader: baseFrag, uniforms: baseUniforms } = physical;\n const baseDefines = physical.defines ?? {};\n\n const uniforms = THREE.UniformsUtils.clone(baseUniforms);\n\n const defaults = new BaseMaterial(cfg.material || {});\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] = u !== null && typeof u === 'object' && 'value' in u ? u : { value: u };\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 = ({ children }) => (\n \n {children}\n \n);\n\nconst hexToNormalizedRGB = hex => {\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\nconst Beams = ({\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 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, width, height, spacing, heightSegments) {\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(({ 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((props, ref) => (\n \n));\nPlaneNoise.displayName = 'PlaneNoise';\n\nconst DirLight = ({ position, color }) => {\n const dir = useRef(null);\n useEffect(() => {\n if (!dir.current) return;\n const cam = dir.current.shadow.camera;\n if (!cam) return;\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 // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n return ;\n};\n\nexport default Beams;\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1", + "@react-three/fiber@^9.3.0", + "@react-three/drei@^10.7.4" + ] } \ No newline at end of file diff --git a/public/r/Beams-JS-TW.json b/public/r/Beams-JS-TW.json index c0b5b39d..42b9e063 100644 --- a/public/r/Beams-JS-TW.json +++ b/public/r/Beams-JS-TW.json @@ -1,19 +1,20 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Beams-JS-TW", - "type": "registry:block", - "title": "Beams", - "description": "Crossing animated ribbons with customizable properties.", - "dependencies": [ - "@react-three/drei", - "@react-three/fiber", - "three" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Backgrounds/Beams/Beams.jsx", - "content": "/* eslint-disable react/no-unknown-property */\nimport { forwardRef, useImperativeHandle, useEffect, useRef, useMemo } 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\nfunction extendMaterial(BaseMaterial, cfg) {\n const physical = THREE.ShaderLib.physical;\n const { vertexShader: baseVert, fragmentShader: baseFrag, uniforms: baseUniforms } = physical;\n const baseDefines = physical.defines ?? {};\n\n const uniforms = THREE.UniformsUtils.clone(baseUniforms);\n\n const defaults = new BaseMaterial(cfg.material || {});\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] = u !== null && typeof u === 'object' && 'value' in u ? u : { value: u };\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 = ({ children }) => (\n \n {children}\n \n);\n\nconst hexToNormalizedRGB = hex => {\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\nconst Beams = ({\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 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, width, height, spacing, heightSegments) {\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(({ 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((props, ref) => (\n \n));\nPlaneNoise.displayName = 'PlaneNoise';\n\nconst DirLight = ({ position, color }) => {\n const dir = useRef(null);\n useEffect(() => {\n if (!dir.current) return;\n const cam = dir.current.shadow.camera;\n if (!cam) return;\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 // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n return ;\n};\n\nexport default Beams;\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Beams-JS-TW", + "title": "Beams", + "description": "Crossing animated ribbons with customizable properties.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "Beams/Beams.jsx", + "content": "/* eslint-disable react/no-unknown-property */\nimport { forwardRef, useImperativeHandle, useEffect, useRef, useMemo } 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\nfunction extendMaterial(BaseMaterial, cfg) {\n const physical = THREE.ShaderLib.physical;\n const { vertexShader: baseVert, fragmentShader: baseFrag, uniforms: baseUniforms } = physical;\n const baseDefines = physical.defines ?? {};\n\n const uniforms = THREE.UniformsUtils.clone(baseUniforms);\n\n const defaults = new BaseMaterial(cfg.material || {});\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] = u !== null && typeof u === 'object' && 'value' in u ? u : { value: u };\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 = ({ children }) => (\n \n {children}\n \n);\n\nconst hexToNormalizedRGB = hex => {\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\nconst Beams = ({\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 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, width, height, spacing, heightSegments) {\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(({ 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((props, ref) => (\n \n));\nPlaneNoise.displayName = 'PlaneNoise';\n\nconst DirLight = ({ position, color }) => {\n const dir = useRef(null);\n useEffect(() => {\n if (!dir.current) return;\n const cam = dir.current.shadow.camera;\n if (!cam) return;\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 // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n return ;\n};\n\nexport default Beams;\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1", + "@react-three/fiber@^9.3.0", + "@react-three/drei@^10.7.4" + ] } \ No newline at end of file diff --git a/public/r/Beams-TS-CSS.json b/public/r/Beams-TS-CSS.json index c189ef98..15abe779 100644 --- a/public/r/Beams-TS-CSS.json +++ b/public/r/Beams-TS-CSS.json @@ -1,24 +1,25 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Beams-TS-CSS", - "type": "registry:block", - "title": "Beams", - "description": "Crossing animated ribbons with customizable properties.", - "dependencies": [ - "@react-three/drei", - "@react-three/fiber", - "three" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/Backgrounds/Beams/Beams.tsx", - "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", - "type": "registry:component" - }, - { - "path": "public/ts/default/src/ts-default/Backgrounds/Beams/Beams.css", - "content": ".beams-container {\n position: relative;\n width: 100%;\n height: 100%;\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Beams-TS-CSS", + "title": "Beams", + "description": "Crossing animated ribbons with customizable properties.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "Beams/Beams.css", + "content": ".beams-container {\n position: relative;\n width: 100%;\n height: 100%;\n}\n" + }, + { + "type": "registry:component", + "path": "Beams/Beams.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1", + "@react-three/fiber@^9.3.0", + "@react-three/drei@^10.7.4" + ] } \ No newline at end of file diff --git a/public/r/Beams-TS-TW.json b/public/r/Beams-TS-TW.json index 13772c8b..f2116768 100644 --- a/public/r/Beams-TS-TW.json +++ b/public/r/Beams-TS-TW.json @@ -1,19 +1,20 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Beams-TS-TW", - "type": "registry:block", - "title": "Beams", - "description": "Crossing animated ribbons with customizable properties.", - "dependencies": [ - "@react-three/drei", - "@react-three/fiber", - "three" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/Backgrounds/Beams/Beams.tsx", - "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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Beams-TS-TW", + "title": "Beams", + "description": "Crossing animated ribbons with customizable properties.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "Beams/Beams.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1", + "@react-three/fiber@^9.3.0", + "@react-three/drei@^10.7.4" + ] } \ No newline at end of file diff --git a/public/r/BlobCursor-JS-CSS.json b/public/r/BlobCursor-JS-CSS.json index d7416ade..29dfa672 100644 --- a/public/r/BlobCursor-JS-CSS.json +++ b/public/r/BlobCursor-JS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BlobCursor-JS-CSS", - "type": "registry:block", - "title": "BlobCursor", - "description": "Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/default/src/content/Animations/BlobCursor/BlobCursor.jsx", - "content": "'use client';\n\nimport { useRef, useEffect, useCallback } from 'react';\nimport gsap from 'gsap';\nimport './BlobCursor.css';\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}) {\n const containerRef = useRef(null);\n const blobsRef = useRef([]);\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 => {\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 (blobsRef.current[i] = el)}\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", - "type": "registry:component" - }, - { - "path": "public/default/src/content/Animations/BlobCursor/BlobCursor.css", - "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", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BlobCursor-JS-CSS", + "title": "BlobCursor", + "description": "Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "BlobCursor/BlobCursor.css", + "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" + }, + { + "type": "registry:component", + "path": "BlobCursor/BlobCursor.jsx", + "content": "'use client';\n\nimport { useRef, useEffect, useCallback } from 'react';\nimport gsap from 'gsap';\nimport './BlobCursor.css';\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}) {\n const containerRef = useRef(null);\n const blobsRef = useRef([]);\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 => {\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 (blobsRef.current[i] = el)}\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" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BlobCursor-JS-TW.json b/public/r/BlobCursor-JS-TW.json index 3e9e387c..2fe0760d 100644 --- a/public/r/BlobCursor-JS-TW.json +++ b/public/r/BlobCursor-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BlobCursor-JS-TW", - "type": "registry:block", - "title": "BlobCursor", - "description": "Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Animations/BlobCursor/BlobCursor.jsx", - "content": "'use client';\n\nimport { useRef, useEffect, useCallback } from 'react';\nimport gsap from 'gsap';\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}) {\n const containerRef = useRef(null);\n const blobsRef = useRef([]);\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 => {\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 (blobsRef.current[i] = el)}\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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BlobCursor-JS-TW", + "title": "BlobCursor", + "description": "Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "BlobCursor/BlobCursor.jsx", + "content": "'use client';\n\nimport { useRef, useEffect, useCallback } from 'react';\nimport gsap from 'gsap';\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}) {\n const containerRef = useRef(null);\n const blobsRef = useRef([]);\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 => {\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 (blobsRef.current[i] = el)}\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" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BlobCursor-TS-CSS.json b/public/r/BlobCursor-TS-CSS.json index c00d3e24..ea761f41 100644 --- a/public/r/BlobCursor-TS-CSS.json +++ b/public/r/BlobCursor-TS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BlobCursor-TS-CSS", - "type": "registry:block", - "title": "BlobCursor", - "description": "Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/Animations/BlobCursor/BlobCursor.tsx", - "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", - "type": "registry:component" - }, - { - "path": "public/ts/default/src/ts-default/Animations/BlobCursor/BlobCursor.css", - "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", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BlobCursor-TS-CSS", + "title": "BlobCursor", + "description": "Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "BlobCursor/BlobCursor.css", + "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" + }, + { + "type": "registry:component", + "path": "BlobCursor/BlobCursor.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BlobCursor-TS-TW.json b/public/r/BlobCursor-TS-TW.json index cf0c1ab8..7bcf374c 100644 --- a/public/r/BlobCursor-TS-TW.json +++ b/public/r/BlobCursor-TS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BlobCursor-TS-TW", - "type": "registry:block", - "title": "BlobCursor", - "description": "Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/Animations/BlobCursor/BlobCursor.tsx", - "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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BlobCursor-TS-TW", + "title": "BlobCursor", + "description": "Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "BlobCursor/BlobCursor.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BlurText-JS-CSS.json b/public/r/BlurText-JS-CSS.json index 26c75098..7c339956 100644 --- a/public/r/BlurText-JS-CSS.json +++ b/public/r/BlurText-JS-CSS.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BlurText-JS-CSS", - "type": "registry:block", - "title": "BlurText", - "description": "Text starts blurred then crisply resolves for a soft-focus reveal effect.", - "dependencies": [ - "motion" - ], - "files": [ - { - "path": "public/default/src/content/TextAnimations/BlurText/BlurText.jsx", - "content": "import { motion } from 'motion/react';\nimport { useEffect, useRef, useState, useMemo } from 'react';\n\nconst buildKeyframes = (from, steps) => {\n const keys = new Set([...Object.keys(from), ...steps.flatMap(s => Object.keys(s))]);\n\n const keyframes = {};\n keys.forEach(k => {\n keyframes[k] = [from[k], ...steps.map(s => s[k])];\n });\n return keyframes;\n};\n\nconst BlurText = ({\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 => 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);\n }\n },\n { threshold, rootMargin }\n );\n observer.observe(ref.current);\n return () => observer.disconnect();\n // eslint-disable-next-line react-hooks/exhaustive-deps\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 = {\n duration: totalDuration,\n times,\n delay: (index * delay) / 1000\n };\n spanTransition.ease = easing;\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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BlurText-JS-CSS", + "title": "BlurText", + "description": "Text starts blurred then crisply resolves for a soft-focus reveal effect.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "BlurText/BlurText.jsx", + "content": "import { motion } from 'motion/react';\nimport { useEffect, useRef, useState, useMemo } from 'react';\n\nconst buildKeyframes = (from, steps) => {\n const keys = new Set([...Object.keys(from), ...steps.flatMap(s => Object.keys(s))]);\n\n const keyframes = {};\n keys.forEach(k => {\n keyframes[k] = [from[k], ...steps.map(s => s[k])];\n });\n return keyframes;\n};\n\nconst BlurText = ({\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 => 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);\n }\n },\n { threshold, rootMargin }\n );\n observer.observe(ref.current);\n return () => observer.disconnect();\n // eslint-disable-next-line react-hooks/exhaustive-deps\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 = {\n duration: totalDuration,\n times,\n delay: (index * delay) / 1000\n };\n spanTransition.ease = easing;\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" + } + ], + "registryDependencies": [], + "dependencies": [ + "motion@^12.23.12" + ] } \ No newline at end of file diff --git a/public/r/BlurText-JS-TW.json b/public/r/BlurText-JS-TW.json index 82eecaea..20ffc15d 100644 --- a/public/r/BlurText-JS-TW.json +++ b/public/r/BlurText-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BlurText-JS-TW", - "type": "registry:block", - "title": "BlurText", - "description": "Text starts blurred then crisply resolves for a soft-focus reveal effect.", - "dependencies": [ - "motion" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/TextAnimations/BlurText/BlurText.jsx", - "content": "import { motion } from 'motion/react';\nimport { useEffect, useRef, useState, useMemo } from 'react';\n\nconst buildKeyframes = (from, steps) => {\n const keys = new Set([...Object.keys(from), ...steps.flatMap(s => Object.keys(s))]);\n\n const keyframes = {};\n keys.forEach(k => {\n keyframes[k] = [from[k], ...steps.map(s => s[k])];\n });\n return keyframes;\n};\n\nconst BlurText = ({\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 => 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);\n }\n },\n { threshold, rootMargin }\n );\n observer.observe(ref.current);\n return () => observer.disconnect();\n // eslint-disable-next-line react-hooks/exhaustive-deps\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 = {\n duration: totalDuration,\n times,\n delay: (index * delay) / 1000\n };\n spanTransition.ease = easing;\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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BlurText-JS-TW", + "title": "BlurText", + "description": "Text starts blurred then crisply resolves for a soft-focus reveal effect.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "BlurText/BlurText.jsx", + "content": "import { motion } from 'motion/react';\nimport { useEffect, useRef, useState, useMemo } from 'react';\n\nconst buildKeyframes = (from, steps) => {\n const keys = new Set([...Object.keys(from), ...steps.flatMap(s => Object.keys(s))]);\n\n const keyframes = {};\n keys.forEach(k => {\n keyframes[k] = [from[k], ...steps.map(s => s[k])];\n });\n return keyframes;\n};\n\nconst BlurText = ({\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 => 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);\n }\n },\n { threshold, rootMargin }\n );\n observer.observe(ref.current);\n return () => observer.disconnect();\n // eslint-disable-next-line react-hooks/exhaustive-deps\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 = {\n duration: totalDuration,\n times,\n delay: (index * delay) / 1000\n };\n spanTransition.ease = easing;\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" + } + ], + "registryDependencies": [], + "dependencies": [ + "motion@^12.23.12" + ] } \ No newline at end of file diff --git a/public/r/BlurText-TS-CSS.json b/public/r/BlurText-TS-CSS.json index 4a763fd9..cc0c432a 100644 --- a/public/r/BlurText-TS-CSS.json +++ b/public/r/BlurText-TS-CSS.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BlurText-TS-CSS", - "type": "registry:block", - "title": "BlurText", - "description": "Text starts blurred then crisply resolves for a soft-focus reveal effect.", - "dependencies": [ - "motion" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/TextAnimations/BlurText/BlurText.tsx", - "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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BlurText-TS-CSS", + "title": "BlurText", + "description": "Text starts blurred then crisply resolves for a soft-focus reveal effect.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "BlurText/BlurText.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "motion@^12.23.12" + ] } \ No newline at end of file diff --git a/public/r/BlurText-TS-TW.json b/public/r/BlurText-TS-TW.json index f1e64bab..a65b1758 100644 --- a/public/r/BlurText-TS-TW.json +++ b/public/r/BlurText-TS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BlurText-TS-TW", - "type": "registry:block", - "title": "BlurText", - "description": "Text starts blurred then crisply resolves for a soft-focus reveal effect.", - "dependencies": [ - "motion" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/TextAnimations/BlurText/BlurText.tsx", - "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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BlurText-TS-TW", + "title": "BlurText", + "description": "Text starts blurred then crisply resolves for a soft-focus reveal effect.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "BlurText/BlurText.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "motion@^12.23.12" + ] } \ No newline at end of file diff --git a/public/r/BounceCards-JS-CSS.json b/public/r/BounceCards-JS-CSS.json index cb81b291..be8370f2 100644 --- a/public/r/BounceCards-JS-CSS.json +++ b/public/r/BounceCards-JS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BounceCards-JS-CSS", - "type": "registry:block", - "title": "BounceCards", - "description": "Cards bounce that bounce in on mount.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/default/src/content/Components/BounceCards/BounceCards.jsx", - "content": "import { useEffect } from 'react';\nimport { gsap } from 'gsap';\nimport './BounceCards.css';\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 = true\n}) {\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 => {\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, offsetX) => {\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 => {\n if (!enableHover) return;\n images.forEach((_, i) => {\n gsap.killTweensOf(`.card-${i}`);\n\n const baseTransform = transformStyles[i] || 'none';\n\n if (i === hoveredIdx) {\n const noRotationTransform = getNoRotationTransform(baseTransform);\n gsap.to(`.card-${i}`, {\n transform: noRotationTransform,\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 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", - "type": "registry:component" - }, - { - "path": "public/default/src/content/Components/BounceCards/BounceCards.css", - "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", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BounceCards-JS-CSS", + "title": "BounceCards", + "description": "Cards bounce that bounce in on mount.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "BounceCards/BounceCards.css", + "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" + }, + { + "type": "registry:component", + "path": "BounceCards/BounceCards.jsx", + "content": "import { useEffect } from 'react';\nimport { gsap } from 'gsap';\nimport './BounceCards.css';\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 = true\n}) {\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 => {\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, offsetX) => {\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 => {\n if (!enableHover) return;\n images.forEach((_, i) => {\n gsap.killTweensOf(`.card-${i}`);\n\n const baseTransform = transformStyles[i] || 'none';\n\n if (i === hoveredIdx) {\n const noRotationTransform = getNoRotationTransform(baseTransform);\n gsap.to(`.card-${i}`, {\n transform: noRotationTransform,\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 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" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BounceCards-JS-TW.json b/public/r/BounceCards-JS-TW.json index b7e4eefe..d665de1d 100644 --- a/public/r/BounceCards-JS-TW.json +++ b/public/r/BounceCards-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BounceCards-JS-TW", - "type": "registry:block", - "title": "BounceCards", - "description": "Cards bounce that bounce in on mount.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Components/BounceCards/BounceCards.jsx", - "content": "import { useEffect } from 'react';\nimport { gsap } from 'gsap';\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}) {\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 => {\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, offsetX) => {\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 => {\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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BounceCards-JS-TW", + "title": "BounceCards", + "description": "Cards bounce that bounce in on mount.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "BounceCards/BounceCards.jsx", + "content": "import { useEffect } from 'react';\nimport { gsap } from 'gsap';\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}) {\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 => {\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, offsetX) => {\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 => {\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" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BounceCards-TS-CSS.json b/public/r/BounceCards-TS-CSS.json index c281bb47..7c44ae43 100644 --- a/public/r/BounceCards-TS-CSS.json +++ b/public/r/BounceCards-TS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BounceCards-TS-CSS", - "type": "registry:block", - "title": "BounceCards", - "description": "Cards bounce that bounce in on mount.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/Components/BounceCards/BounceCards.tsx", - "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", - "type": "registry:component" - }, - { - "path": "public/ts/default/src/ts-default/Components/BounceCards/BounceCards.css", - "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", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BounceCards-TS-CSS", + "title": "BounceCards", + "description": "Cards bounce that bounce in on mount.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "BounceCards/BounceCards.css", + "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" + }, + { + "type": "registry:component", + "path": "BounceCards/BounceCards.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BounceCards-TS-TW.json b/public/r/BounceCards-TS-TW.json index 713d13dd..2ee81bb1 100644 --- a/public/r/BounceCards-TS-TW.json +++ b/public/r/BounceCards-TS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BounceCards-TS-TW", - "type": "registry:block", - "title": "BounceCards", - "description": "Cards bounce that bounce in on mount.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/Components/BounceCards/BounceCards.tsx", - "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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BounceCards-TS-TW", + "title": "BounceCards", + "description": "Cards bounce that bounce in on mount.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "BounceCards/BounceCards.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BubbleMenu-JS-CSS.json b/public/r/BubbleMenu-JS-CSS.json index 0f8f6d1b..f7b22bde 100644 --- a/public/r/BubbleMenu-JS-CSS.json +++ b/public/r/BubbleMenu-JS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BubbleMenu-JS-CSS", - "type": "registry:block", - "title": "BubbleMenu", - "description": "Floating circular expanding menu with staggered item reveal.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/default/src/content/Components/BubbleMenu/BubbleMenu.jsx", - "content": "import { useState, useRef, useEffect } from 'react';\nimport { gsap } from 'gsap';\n\nimport './BubbleMenu.css';\n\nconst DEFAULT_ITEMS = [\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}) {\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", - "type": "registry:component" - }, - { - "path": "public/default/src/content/Components/BubbleMenu/BubbleMenu.css", - "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", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BubbleMenu-JS-CSS", + "title": "BubbleMenu", + "description": "Floating circular expanding menu with staggered item reveal.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "BubbleMenu/BubbleMenu.css", + "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" + }, + { + "type": "registry:component", + "path": "BubbleMenu/BubbleMenu.jsx", + "content": "import { useState, useRef, useEffect } from 'react';\nimport { gsap } from 'gsap';\n\nimport './BubbleMenu.css';\n\nconst DEFAULT_ITEMS = [\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}) {\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" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BubbleMenu-JS-TW.json b/public/r/BubbleMenu-JS-TW.json index 3fce1b2e..b113d1e1 100644 --- a/public/r/BubbleMenu-JS-TW.json +++ b/public/r/BubbleMenu-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BubbleMenu-JS-TW", - "type": "registry:block", - "title": "BubbleMenu", - "description": "Floating circular expanding menu with staggered item reveal.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Components/BubbleMenu/BubbleMenu.jsx", - "content": "import { useEffect, useRef, useState } from 'react';\nimport { gsap } from 'gsap';\n\nconst DEFAULT_ITEMS = [\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}) {\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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BubbleMenu-JS-TW", + "title": "BubbleMenu", + "description": "Floating circular expanding menu with staggered item reveal.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "BubbleMenu/BubbleMenu.jsx", + "content": "import { useEffect, useRef, useState } from 'react';\nimport { gsap } from 'gsap';\n\nconst DEFAULT_ITEMS = [\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}) {\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" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BubbleMenu-TS-CSS.json b/public/r/BubbleMenu-TS-CSS.json index b2d42510..60f502ae 100644 --- a/public/r/BubbleMenu-TS-CSS.json +++ b/public/r/BubbleMenu-TS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BubbleMenu-TS-CSS", - "type": "registry:block", - "title": "BubbleMenu", - "description": "Floating circular expanding menu with staggered item reveal.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/Components/BubbleMenu/BubbleMenu.tsx", - "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", - "type": "registry:component" - }, - { - "path": "public/ts/default/src/ts-default/Components/BubbleMenu/BubbleMenu.css", - "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", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BubbleMenu-TS-CSS", + "title": "BubbleMenu", + "description": "Floating circular expanding menu with staggered item reveal.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "BubbleMenu/BubbleMenu.css", + "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" + }, + { + "type": "registry:component", + "path": "BubbleMenu/BubbleMenu.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BubbleMenu-TS-TW.json b/public/r/BubbleMenu-TS-TW.json index 9231174b..79ffeae4 100644 --- a/public/r/BubbleMenu-TS-TW.json +++ b/public/r/BubbleMenu-TS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BubbleMenu-TS-TW", - "type": "registry:block", - "title": "BubbleMenu", - "description": "Floating circular expanding menu with staggered item reveal.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/Components/BubbleMenu/BubbleMenu.tsx", - "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", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BubbleMenu-TS-TW", + "title": "BubbleMenu", + "description": "Floating circular expanding menu with staggered item reveal.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "BubbleMenu/BubbleMenu.tsx", + "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" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/CardNav-JS-CSS.json b/public/r/CardNav-JS-CSS.json index b0153f2e..61639676 100644 --- a/public/r/CardNav-JS-CSS.json +++ b/public/r/CardNav-JS-CSS.json @@ -1,22 +1,24 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "CardNav-JS-CSS", - "type": "registry:block", - "title": "CardNav", - "description": "Expandable navigation bar with card panels revealing nested links.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/default/src/content/Components/CardNav/CardNav.jsx", - "content": "import { 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';\nimport './CardNav.css';\n\nconst CardNav = ({\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');\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 // eslint-disable-next-line react-hooks/exhaustive-deps\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 // eslint-disable-next-line react-hooks/exhaustive-deps\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 => el => {\n if (el) cardsRef.current[i] = el;\n };\n\n return (\n
\n \n
\n );\n};\n\nexport default CardNav;\n", - "type": "registry:component" - }, - { - "path": "public/default/src/content/Components/CardNav/CardNav.css", - "content": ".card-nav-container {\n position: absolute;\n top: 2em;\n left: 50%;\n transform: translateX(-50%);\n width: 90%;\n max-width: 800px;\n z-index: 99;\n box-sizing: border-box;\n}\n\n.card-nav {\n display: block;\n height: 60px;\n padding: 0;\n background-color: white;\n border: 0.5px solid rgba(255, 255, 255, 0.1);\n border-radius: 0.75rem;\n box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n position: relative;\n overflow: hidden;\n will-change: height;\n}\n\n.card-nav-top {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 60px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.5rem 0.45rem 0.55rem 1.1rem;\n z-index: 2;\n}\n\n.hamburger-menu {\n height: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n gap: 6px;\n}\n\n.hamburger-menu:hover .hamburger-line {\n opacity: 0.75;\n}\n\n.hamburger-line {\n width: 30px;\n height: 2px;\n background-color: currentColor;\n transition:\n transform 0.25s ease,\n opacity 0.2s ease,\n margin 0.3s ease;\n transform-origin: 50% 50%;\n}\n\n.hamburger-menu.open .hamburger-line:first-child {\n transform: translateY(4px) rotate(45deg);\n}\n\n.hamburger-menu.open .hamburger-line:last-child {\n transform: translateY(-4px) rotate(-45deg);\n}\n\n.logo-container {\n display: flex;\n align-items: center;\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n}\n\n.logo {\n height: 28px;\n}\n\n.card-nav-cta-button {\n background-color: #111;\n color: white;\n border: none;\n border-radius: calc(0.75rem - 0.35rem);\n padding: 0 1rem;\n height: 100%;\n font-weight: 500;\n cursor: pointer;\n transition: background-color 0.3s ease;\n align-items: center;\n}\n\n.card-nav-cta-button:hover {\n background-color: #333;\n}\n\n.card-nav-content {\n position: absolute;\n left: 0;\n right: 0;\n top: 60px;\n bottom: 0;\n padding: 0.5rem;\n display: flex;\n align-items: flex-end;\n gap: 12px;\n visibility: hidden;\n pointer-events: none;\n z-index: 1;\n}\n\n.card-nav.open .card-nav-content {\n visibility: visible;\n pointer-events: auto;\n}\n\n.nav-card {\n height: 100%;\n flex: 1 1 0;\n min-width: 0;\n border-radius: calc(0.75rem - 0.2rem);\n position: relative;\n display: flex;\n flex-direction: column;\n padding: 12px 16px;\n gap: 8px;\n user-select: none;\n}\n\n.nav-card-label {\n font-weight: 400;\n font-size: 22px;\n letter-spacing: -0.5px;\n}\n\n.nav-card-links {\n margin-top: auto;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.nav-card-link {\n font-size: 16px;\n cursor: pointer;\n text-decoration: none;\n transition: opacity 0.3s ease;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.nav-card-link:hover {\n opacity: 0.75;\n}\n\n@media (max-width: 768px) {\n .card-nav-container {\n width: 90%;\n top: 1.2em;\n }\n\n .card-nav-top {\n padding: 0.5rem 1rem;\n justify-content: space-between;\n }\n\n .hamburger-menu {\n order: 2;\n }\n\n .logo-container {\n position: static;\n transform: none;\n order: 1;\n }\n\n .card-nav-cta-button {\n display: none;\n }\n\n .card-nav-content {\n flex-direction: column;\n align-items: stretch;\n gap: 8px;\n padding: 0.5rem;\n bottom: 0;\n justify-content: flex-start;\n }\n\n .nav-card {\n height: auto;\n min-height: 60px;\n flex: 1 1 auto;\n max-height: none;\n }\n\n .nav-card-label {\n font-size: 18px;\n }\n\n .nav-card-link {\n font-size: 15px;\n }\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "CardNav-JS-CSS", + "title": "CardNav", + "description": "Expandable navigation bar with card panels revealing nested links.", + "type": "registry:component", + "files": [ + { + "type": "registry:component", + "path": "CardNav/CardNav.css", + "content": ".card-nav-container {\n position: absolute;\n top: 2em;\n left: 50%;\n transform: translateX(-50%);\n width: 90%;\n max-width: 800px;\n z-index: 99;\n box-sizing: border-box;\n}\n\n.card-nav {\n display: block;\n height: 60px;\n padding: 0;\n background-color: white;\n border: 0.5px solid rgba(255, 255, 255, 0.1);\n border-radius: 0.75rem;\n box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n position: relative;\n overflow: hidden;\n will-change: height;\n}\n\n.card-nav-top {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 60px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.5rem 0.45rem 0.55rem 1.1rem;\n z-index: 2;\n}\n\n.hamburger-menu {\n height: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n gap: 6px;\n}\n\n.hamburger-menu:hover .hamburger-line {\n opacity: 0.75;\n}\n\n.hamburger-line {\n width: 30px;\n height: 2px;\n background-color: currentColor;\n transition:\n transform 0.25s ease,\n opacity 0.2s ease,\n margin 0.3s ease;\n transform-origin: 50% 50%;\n}\n\n.hamburger-menu.open .hamburger-line:first-child {\n transform: translateY(4px) rotate(45deg);\n}\n\n.hamburger-menu.open .hamburger-line:last-child {\n transform: translateY(-4px) rotate(-45deg);\n}\n\n.logo-container {\n display: flex;\n align-items: center;\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n}\n\n.logo {\n height: 28px;\n}\n\n.card-nav-cta-button {\n background-color: #111;\n color: white;\n border: none;\n border-radius: calc(0.75rem - 0.35rem);\n padding: 0 1rem;\n height: 100%;\n font-weight: 500;\n cursor: pointer;\n transition: background-color 0.3s ease;\n align-items: center;\n}\n\n.card-nav-cta-button:hover {\n background-color: #333;\n}\n\n.card-nav-content {\n position: absolute;\n left: 0;\n right: 0;\n top: 60px;\n bottom: 0;\n padding: 0.5rem;\n display: flex;\n align-items: flex-end;\n gap: 12px;\n visibility: hidden;\n pointer-events: none;\n z-index: 1;\n}\n\n.card-nav.open .card-nav-content {\n visibility: visible;\n pointer-events: auto;\n}\n\n.nav-card {\n height: 100%;\n flex: 1 1 0;\n min-width: 0;\n border-radius: calc(0.75rem - 0.2rem);\n position: relative;\n display: flex;\n flex-direction: column;\n padding: 12px 16px;\n gap: 8px;\n user-select: none;\n}\n\n.nav-card-label {\n font-weight: 400;\n font-size: 22px;\n letter-spacing: -0.5px;\n}\n\n.nav-card-links {\n margin-top: auto;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.nav-card-link {\n font-size: 16px;\n cursor: pointer;\n text-decoration: none;\n transition: opacity 0.3s ease;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.nav-card-link:hover {\n opacity: 0.75;\n}\n\n@media (max-width: 768px) {\n .card-nav-container {\n width: 90%;\n top: 1.2em;\n }\n\n .card-nav-top {\n padding: 0.5rem 1rem;\n justify-content: space-between;\n }\n\n .hamburger-menu {\n order: 2;\n }\n\n .logo-container {\n position: static;\n transform: none;\n order: 1;\n }\n\n .card-nav-cta-button {\n display: none;\n }\n\n .card-nav-content {\n flex-direction: column;\n align-items: stretch;\n gap: 8px;\n padding: 0.5rem;\n bottom: 0;\n justify-content: flex-start;\n }\n\n .nav-card {\n height: auto;\n min-height: 60px;\n flex: 1 1 auto;\n max-height: none;\n }\n\n .nav-card-label {\n font-size: 18px;\n }\n\n .nav-card-link {\n font-size: 15px;\n }\n}\n" + }, + { + "type": "registry:component", + "path": "CardNav/CardNav.jsx", + "content": "import { 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';\nimport './CardNav.css';\n\nconst CardNav = ({\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');\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 // eslint-disable-next-line react-hooks/exhaustive-deps\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 // eslint-disable-next-line react-hooks/exhaustive-deps\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 => el => {\n if (el) cardsRef.current[i] = el;\n };\n\n return (\n
\n \n
\n );\n};\n\nexport default CardNav;\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0", + "react-icons@^5.5.0" + ] } \ No newline at end of file diff --git a/public/r/CardNav-JS-TW.json b/public/r/CardNav-JS-TW.json index 4ad24442..e6b808a7 100644 --- a/public/r/CardNav-JS-TW.json +++ b/public/r/CardNav-JS-TW.json @@ -1,17 +1,19 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "CardNav-JS-TW", - "type": "registry:block", - "title": "CardNav", - "description": "Expandable navigation bar with card panels revealing nested links.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Components/CardNav/CardNav.jsx", - "content": "import { 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\nconst CardNav = ({\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');\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 // eslint-disable-next-line react-hooks/exhaustive-deps\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 // eslint-disable-next-line react-hooks/exhaustive-deps\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 => el => {\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