├── .gitignore ├── .prettierrc ├── README.md ├── bun.lock ├── next.config.ts ├── package.json ├── postcss.config.mjs ├── public ├── apple-touch-icon.png ├── examples │ ├── accel-logo.svg │ ├── adobe-logo.svg │ ├── apple-logo.svg │ ├── base-ui-logo.svg │ ├── basecase-logo.svg │ ├── chrome-logo.png │ ├── cloudflare-logo.svg │ ├── discord-logo.svg │ ├── figma-logo.svg │ ├── fly-logo.svg │ ├── framer-logo.svg │ ├── instagram-logo.svg │ ├── mac-rumors-logo.svg │ ├── nike-logo.svg │ ├── paper-logo.svg │ ├── radix-ui-logo.svg │ ├── rive-logo.svg │ ├── sketch-logo.svg │ ├── slack-logo.svg │ ├── starbucks-logo.svg │ ├── supabase-logo.png │ ├── t3-logo.svg │ ├── techcrunch-logo.svg │ ├── vercel-logo.png │ ├── verge-logo.svg │ └── workos-logo.svg ├── favicon-dev.ico ├── favicon.ico ├── logos │ ├── apple.svg │ ├── chanel.svg │ ├── cloudflare.svg │ ├── discord.svg │ ├── nasa.svg │ ├── nike.svg │ ├── paper.svg │ ├── remix.svg │ ├── vercel.svg │ └── volkswagen.svg └── og-image.png ├── src ├── app │ ├── api │ │ └── user-logo │ │ │ └── route.ts │ ├── compose-refs.ts │ ├── hero │ │ └── liquid-frag.ts │ ├── layout.tsx │ ├── number-input.tsx │ ├── page.tsx │ ├── paper-logo.tsx │ ├── round-optimized.ts │ ├── share │ │ └── [id] │ │ │ └── page.tsx │ ├── styles.css │ └── utilities.css └── hero │ ├── canvas.tsx │ ├── hero.tsx │ ├── params.ts │ ├── parse-logo-image.ts │ └── upload-image.ts ├── tailwind.config.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "printWidth": 120, 6 | "useTabs": false, 7 | "quoteProps": "consistent", 8 | "plugins": [ 9 | "prettier-plugin-tailwindcss" 10 | ], 11 | "tailwindStylesheet": "./src/app/styles.css" 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | image 2 | 3 | Just for fun, make your logo in liquid metal: liquid.paper.design 4 | -------------------------------------------------------------------------------- /bun.lock: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "workspaces": { 4 | "": { 5 | "name": "liquid-logo", 6 | "dependencies": { 7 | "@base-ui-components/react": "1.0.0-alpha.6", 8 | "@paper-design/shaders-react": "0.0.21", 9 | "@vercel/analytics": "^1.5.0", 10 | "@vercel/blob": "0.27.1", 11 | "clsx": "^2.1.1", 12 | "lodash-es": "^4.17.21", 13 | "next": "15.1.7", 14 | "radix-ui": "1.1.3", 15 | "react": "19.0.0", 16 | "react-dom": "19.0.0", 17 | "sonner": "1.7.4", 18 | "ulid": "2.3.0", 19 | }, 20 | "devDependencies": { 21 | "@tailwindcss/postcss": "^4.0.7", 22 | "@types/lodash-es": "^4.17.12", 23 | "@types/node": "^20", 24 | "@types/react": "^19", 25 | "@types/react-dom": "^19", 26 | "postcss": "^8.5.2", 27 | "prettier-plugin-tailwindcss": "^0.6.11", 28 | "tailwindcss": "^4.0.7", 29 | "typescript": "^5", 30 | }, 31 | }, 32 | }, 33 | "packages": { 34 | "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], 35 | 36 | "@babel/runtime": ["@babel/runtime@7.26.9", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg=="], 37 | 38 | "@base-ui-components/react": ["@base-ui-components/react@1.0.0-alpha.6", "", { "dependencies": { "@babel/runtime": "^7.26.7", "@floating-ui/react": "^0.27.3", "@floating-ui/utils": "^0.2.9", "@react-aria/overlays": "^3.25.0", "prop-types": "^15.8.1", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-0Jkp8twk3z3TJNOTUyzrK1dPOF7w1/LH+TYksuZc8TyJzuJx8V2O15BgpMJczvFdSR2ncdN1WQxsTe2ayYJ6cw=="], 39 | 40 | "@emnapi/runtime": ["@emnapi/runtime@1.3.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw=="], 41 | 42 | "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], 43 | 44 | "@floating-ui/core": ["@floating-ui/core@1.6.9", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw=="], 45 | 46 | "@floating-ui/dom": ["@floating-ui/dom@1.6.13", "", { "dependencies": { "@floating-ui/core": "^1.6.0", "@floating-ui/utils": "^0.2.9" } }, "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w=="], 47 | 48 | "@floating-ui/react": ["@floating-ui/react@0.27.4", "", { "dependencies": { "@floating-ui/react-dom": "^2.1.2", "@floating-ui/utils": "^0.2.9", "tabbable": "^6.0.0" }, "peerDependencies": { "react": ">=17.0.0", "react-dom": ">=17.0.0" } }, "sha512-05mXdkUiVh8NCEcYKQ2C9SV9IkZ9k/dFtYmaEIN2riLv80UHoXylgBM76cgPJYfLJM3dJz7UE5MOVH0FypMd2Q=="], 49 | 50 | "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.2", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A=="], 51 | 52 | "@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="], 53 | 54 | "@formatjs/ecma402-abstract": ["@formatjs/ecma402-abstract@2.3.3", "", { "dependencies": { "@formatjs/fast-memoize": "2.2.6", "@formatjs/intl-localematcher": "0.6.0", "decimal.js": "10", "tslib": "2" } }, "sha512-pJT1OkhplSmvvr6i3CWTPvC/FGC06MbN5TNBfRO6Ox62AEz90eMq+dVvtX9Bl3jxCEkS0tATzDarRZuOLw7oFg=="], 55 | 56 | "@formatjs/fast-memoize": ["@formatjs/fast-memoize@2.2.6", "", { "dependencies": { "tslib": "2" } }, "sha512-luIXeE2LJbQnnzotY1f2U2m7xuQNj2DA8Vq4ce1BY9ebRZaoPB1+8eZ6nXpLzsxuW5spQxr7LdCg+CApZwkqkw=="], 57 | 58 | "@formatjs/icu-messageformat-parser": ["@formatjs/icu-messageformat-parser@2.11.1", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.3", "@formatjs/icu-skeleton-parser": "1.8.13", "tslib": "2" } }, "sha512-o0AhSNaOfKoic0Sn1GkFCK4MxdRsw7mPJ5/rBpIqdvcC7MIuyUSW8WChUEvrK78HhNpYOgqCQbINxCTumJLzZA=="], 59 | 60 | "@formatjs/icu-skeleton-parser": ["@formatjs/icu-skeleton-parser@1.8.13", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.3", "tslib": "2" } }, "sha512-N/LIdTvVc1TpJmMt2jVg0Fr1F7Q1qJPdZSCs19unMskCmVQ/sa0H9L8PWt13vq+gLdLg1+pPsvBLydL1Apahjg=="], 61 | 62 | "@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.6.0", "", { "dependencies": { "tslib": "2" } }, "sha512-4rB4g+3hESy1bHSBG3tDFaMY2CH67iT7yne1e+0CLTsGLDcmoEWWpJjjpWVaYgYfYuohIRuo0E+N536gd2ZHZA=="], 63 | 64 | "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], 65 | 66 | "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="], 67 | 68 | "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="], 69 | 70 | "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="], 71 | 72 | "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="], 73 | 74 | "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="], 75 | 76 | "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.0.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="], 77 | 78 | "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="], 79 | 80 | "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="], 81 | 82 | "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="], 83 | 84 | "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="], 85 | 86 | "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="], 87 | 88 | "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.0.4" }, "os": "linux", "cpu": "s390x" }, "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q=="], 89 | 90 | "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="], 91 | 92 | "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="], 93 | 94 | "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="], 95 | 96 | "@img/sharp-wasm32": ["@img/sharp-wasm32@0.33.5", "", { "dependencies": { "@emnapi/runtime": "^1.2.0" }, "cpu": "none" }, "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg=="], 97 | 98 | "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.33.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ=="], 99 | 100 | "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="], 101 | 102 | "@internationalized/date": ["@internationalized/date@3.7.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-VJ5WS3fcVx0bejE/YHfbDKR/yawZgKqn/if+oEeLqNwBtPzVB06olkfcnojTmEMX+gTpH+FlQ69SHNitJ8/erQ=="], 103 | 104 | "@internationalized/message": ["@internationalized/message@3.1.6", "", { "dependencies": { "@swc/helpers": "^0.5.0", "intl-messageformat": "^10.1.0" } }, "sha512-JxbK3iAcTIeNr1p0WIFg/wQJjIzJt9l/2KNY/48vXV7GRGZSv3zMxJsce008fZclk2cDC8y0Ig3odceHO7EfNQ=="], 105 | 106 | "@internationalized/number": ["@internationalized/number@3.6.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-PtrRcJVy7nw++wn4W2OuePQQfTqDzfusSuY1QTtui4wa7r+rGVtR75pO8CyKvHvzyQYi3Q1uO5sY0AsB4e65Bw=="], 107 | 108 | "@internationalized/string": ["@internationalized/string@3.2.5", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-rKs71Zvl2OKOHM+mzAFMIyqR5hI1d1O6BBkMK2/lkfg3fkmVh9Eeg0awcA8W2WqYqDOv6a86DIOlFpggwLtbuw=="], 109 | 110 | "@next/env": ["@next/env@15.1.7", "", {}, "sha512-d9jnRrkuOH7Mhi+LHav2XW91HOgTAWHxjMPkXMGBc9B2b7614P7kjt8tAplRvJpbSt4nbO1lugcT/kAaWzjlLQ=="], 111 | 112 | "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.1.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hPFwzPJDpA8FGj7IKV3Yf1web3oz2YsR8du4amKw8d+jAOHfYHYFpMkoF6vgSY4W6vB29RtZEklK9ayinGiCmQ=="], 113 | 114 | "@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.1.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-2qoas+fO3OQKkU0PBUfwTiw/EYpN+kdAx62cePRyY1LqKtP09Vp5UcUntfZYajop5fDFTjSxCHfZVRxzi+9FYQ=="], 115 | 116 | "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.1.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-sKLLwDX709mPdzxMnRIXLIT9zaX2w0GUlkLYQnKGoXeWUhcvpCrK+yevcwCJPdTdxZEUA0mOXGLdPsGkudGdnA=="], 117 | 118 | "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.1.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-zblK1OQbQWdC8fxdX4fpsHDw+VSpBPGEUX4PhSE9hkaWPrWoeIJn+baX53vbsbDRaDKd7bBNcXRovY1hEhFd7w=="], 119 | 120 | "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.1.7", "", { "os": "linux", "cpu": "x64" }, "sha512-GOzXutxuLvLHFDAPsMP2zDBMl1vfUHHpdNpFGhxu90jEzH6nNIgmtw/s1MDwpTOiM+MT5V8+I1hmVFeAUhkbgQ=="], 121 | 122 | "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.1.7", "", { "os": "linux", "cpu": "x64" }, "sha512-WrZ7jBhR7ATW1z5iEQ0ZJfE2twCNSXbpCSaAunF3BKcVeHFADSI/AW1y5Xt3DzTqPF1FzQlwQTewqetAABhZRQ=="], 123 | 124 | "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.1.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-LDnj1f3OVbou1BqvvXVqouJZKcwq++mV2F+oFHptToZtScIEnhNRJAhJzqAtTE2dB31qDYL45xJwrc+bLeKM2Q=="], 125 | 126 | "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.1.7", "", { "os": "win32", "cpu": "x64" }, "sha512-dC01f1quuf97viOfW05/K8XYv2iuBgAxJZl7mbCKEjMgdQl5JjAKJ0D2qMKZCgPWDeFbFT0Q0nYWwytEW0DWTQ=="], 127 | 128 | "@paper-design/shaders": ["@paper-design/shaders@0.0.19", "", {}, "sha512-Bc6aF6lcT/VJio8ShKghgus2o483NKlbJkvtqw/hkfHVsdwShqndPiZmHHHPtVeuoWDXntxc9Hamcob+klRJew=="], 129 | 130 | "@paper-design/shaders-react": ["@paper-design/shaders-react@0.0.21", "", { "dependencies": { "@paper-design/shaders": "0.0.19", "react": ">=18" } }, "sha512-Momr3YfQ4ADhLQ/hUjv+6C7EL8qNOkrqLo8Tp5S85oRuIs2WVzyYDHUhl1VBifLxqIRRADWHDYZjvgFHYjBd5g=="], 131 | 132 | "@radix-ui/number": ["@radix-ui/number@1.1.0", "", {}, "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ=="], 133 | 134 | "@radix-ui/primitive": ["@radix-ui/primitive@1.1.1", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="], 135 | 136 | "@radix-ui/react-accessible-icon": ["@radix-ui/react-accessible-icon@1.1.2", "", { "dependencies": { "@radix-ui/react-visually-hidden": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+rnMO0SEfzkcHr93RshkQVpOA26MtGOv4pcS9QUnLg4F8+GDmCJ8c2FEPhPz5e7arf31EzbTqJxFbzg3qen14g=="], 137 | 138 | "@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collapsible": "1.1.3", "@radix-ui/react-collection": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-RIQ15mrcvqIkDARJeERSuXSry2N8uYnxkdDetpfmalT/+0ntOXLkFOsh9iwlAsCv+qcmhZjbdJogIm6WBa6c4A=="], 139 | 140 | "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dialog": "1.1.6", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-p4XnPqgej8sZAAReCAKgz1REYZEBLR8hU9Pg27wFnCWIMc8g1ccCs0FjBcy05V15VTu8pAePw/VDYeOm/uZ6yQ=="], 141 | 142 | "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg=="], 143 | 144 | "@radix-ui/react-aspect-ratio": ["@radix-ui/react-aspect-ratio@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TaJxYoCpxJ7vfEkv2PTNox/6zzmpKXT6ewvCuf2tTOIVN45/Jahhlld29Yw4pciOXS2Xq91/rSGEdmEnUWZCqA=="], 145 | 146 | "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.3", "", { "dependencies": { "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Paen00T4P8L8gd9bNsRMw7Cbaz85oxiv+hzomsRZgFm2byltPFDtfcoqlWJ8GyZlIBWgLssJlzLCnKU0G0302g=="], 147 | 148 | "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.1.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw=="], 149 | 150 | "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-jFSerheto1X03MUC0g6R7LedNW9EEGWdg9W1+MlpkMLwGkgkbUXLPBH/KIuWKXUoeYRVY11llqbTBDzuLg7qrw=="], 151 | 152 | "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw=="], 153 | 154 | "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="], 155 | 156 | "@radix-ui/react-context": ["@radix-ui/react-context@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q=="], 157 | 158 | "@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-menu": "2.1.6", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-aUP99QZ3VU84NPsHeaFt4cQUNgJqFsLLOt/RbbWXszZ6MP0DpDyjkFZORr4RpAEx3sUBk+Kc8h13yGtC5Qw8dg=="], 159 | 160 | "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw=="], 161 | 162 | "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg=="], 163 | 164 | "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.5", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg=="], 165 | 166 | "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-menu": "2.1.6", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-no3X7V5fD487wab/ZYSHXq3H37u4NVeLDKI/Ks724X/eEFSSEFYZxWgsIlr1UBeEyDaM29HM5x9p1Nv8DuTYPA=="], 167 | 168 | "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg=="], 169 | 170 | "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA=="], 171 | 172 | "@radix-ui/react-form": ["@radix-ui/react-form@0.1.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-label": "2.1.2", "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Owj1MjLq6/Rp85bgzYI+zRK5APLiWDtXDM63Z39FW15bNdehrcS+FjQgLGQYswFzipYu4GAA+t5w/VqvvNZ3ag=="], 173 | 174 | "@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-E4ozl35jq0VRlrdc4dhHrNSV0JqBb4Jy73WAhBEK7JoYnQ83ED5r0Rb/XdVKw89ReAJN38N492BAPBZQ57VmqQ=="], 175 | 176 | "@radix-ui/react-id": ["@radix-ui/react-id@1.1.0", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA=="], 177 | 178 | "@radix-ui/react-label": ["@radix-ui/react-label@2.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zo1uGMTaNlHehDyFQcDZXRJhUPDuukcnHz0/jnrup0JA6qL+AFpAnty+7VKa9esuU5xTblAZzTGYJKSKaBxBhw=="], 179 | 180 | "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-roving-focus": "1.1.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-callback-ref": "1.1.0", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tBBb5CXDJW3t2mo9WlO7r6GTmWV0F0uzHZVFmlRmYpiSK1CDU5IKojP1pm7oknpBOrFZx/YgBRW9oorPO2S/Lg=="], 181 | 182 | "@radix-ui/react-menubar": ["@radix-ui/react-menubar@1.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-menu": "2.1.6", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-roving-focus": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-FHq7+3DlXwh/7FOM4i0G4bC4vPjiq89VEEvNF4VMLchGnaUuUbE5uKXMUCjdKaOghEEMeiKa5XCa2Pk4kteWmg=="], 183 | 184 | "@radix-ui/react-navigation-menu": ["@radix-ui/react-navigation-menu@1.2.5", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-myMHHQUZ3ZLTi8W381/Vu43Ia0NqakkQZ2vzynMmTUtQQ9kNkjzhOwkZC9TAM5R07OZUVIQyHC06f/9JZJpvvA=="], 185 | 186 | "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg=="], 187 | 188 | "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.2", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA=="], 189 | 190 | "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.4", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA=="], 191 | 192 | "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg=="], 193 | 194 | "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.2", "", { "dependencies": { "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w=="], 195 | 196 | "@radix-ui/react-progress": ["@radix-ui/react-progress@1.1.2", "", { "dependencies": { "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-u1IgJFQ4zNAUTjGdDL5dcl/U8ntOR6jsnhxKb5RKp5Ozwl88xKR9EqRZOe/Mk8tnx0x5tNUe2F+MzsyjqMg0MA=="], 197 | 198 | "@radix-ui/react-radio-group": ["@radix-ui/react-radio-group@1.2.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-roving-focus": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-xtCsqt8Rp09FK50ItqEqTJ7Sxanz8EM8dnkVIhJrc/wkMMomSmXHvYbhv3E7Zx4oXh98aaLt9W679SUYXg4IDA=="], 199 | 200 | "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw=="], 201 | 202 | "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.3", "", { "dependencies": { "@radix-ui/number": "1.1.0", "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-l7+NNBfBYYJa9tNqVcP2AGvxdE3lmE6kFTBXdvHgUaZuy+4wGCL1Cl2AfaR7RKyimj7lZURGLwFO59k4eBnDJQ=="], 203 | 204 | "@radix-ui/react-select": ["@radix-ui/react-select@2.1.6", "", { "dependencies": { "@radix-ui/number": "1.1.0", "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-T6ajELxRvTuAMWH0YmRJ1qez+x4/7Nq7QIx7zJ0VK3qaEWdnWpNbEDnmWldG1zBDwqrLy5aLMUWcoGirVj5kMg=="], 205 | 206 | "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ=="], 207 | 208 | "@radix-ui/react-slider": ["@radix-ui/react-slider@1.2.3", "", { "dependencies": { "@radix-ui/number": "1.1.0", "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-nNrLAWLjGESnhqBqcCNW4w2nn7LxudyMzeB6VgdyAnFLC6kfQgnAjSL2v6UkQTnDctJBlxrmxfplWS4iYjdUTw=="], 209 | 210 | "@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ=="], 211 | 212 | "@radix-ui/react-switch": ["@radix-ui/react-switch@1.1.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1nc+vjEOQkJVsJtWPSiISGT6OKm4SiOdjMo+/icLxo2G4vxz1GntC5MzfL4v8ey9OEfw787QCD1y3mUv0NiFEQ=="], 213 | 214 | "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-roving-focus": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng=="], 215 | 216 | "@radix-ui/react-toast": ["@radix-ui/react-toast@1.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-gN4dpuIVKEgpLn1z5FhzT9mYRUitbfZq9XqN/7kkBMUgFTzTG8x/KszWJugJXHcwxckY8xcKDZPz7kG3o6DsUA=="], 217 | 218 | "@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-lntKchNWx3aCHuWKiDY+8WudiegQvBpDRAYL8dKLRvKEH8VOpl0XX6SSU/bUBqIRJbcTy4+MW06Wv8vgp10rzQ=="], 219 | 220 | "@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-roving-focus": "1.1.2", "@radix-ui/react-toggle": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-JBm6s6aVG/nwuY5eadhU2zDi/IwYS0sDM5ZWb4nymv/hn3hZdkw+gENn0LP4iY1yCd7+bgJaCwueMYJIU3vk4A=="], 221 | 222 | "@radix-ui/react-toolbar": ["@radix-ui/react-toolbar@1.1.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-roving-focus": "1.1.2", "@radix-ui/react-separator": "1.1.2", "@radix-ui/react-toggle-group": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wT20eQ7ScFk+kBMDmHp+lMk18cgxhu35b2Bn5deUcPxiVwfn5vuZgi7NGcHu8ocdkinahmp4FaSZysKDyRVPWQ=="], 223 | 224 | "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.1.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA=="], 225 | 226 | "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw=="], 227 | 228 | "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.1.0", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw=="], 229 | 230 | "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.0", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw=="], 231 | 232 | "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w=="], 233 | 234 | "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og=="], 235 | 236 | "@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.0", "", { "dependencies": { "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ=="], 237 | 238 | "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.0", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw=="], 239 | 240 | "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q=="], 241 | 242 | "@radix-ui/rect": ["@radix-ui/rect@1.1.0", "", {}, "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg=="], 243 | 244 | "@react-aria/focus": ["@react-aria/focus@3.19.1", "", { "dependencies": { "@react-aria/interactions": "^3.23.0", "@react-aria/utils": "^3.27.0", "@react-types/shared": "^3.27.0", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-bix9Bu1Ue7RPcYmjwcjhB14BMu2qzfJ3tMQLqDc9pweJA66nOw8DThy3IfVr8Z7j2PHktOLf9kcbiZpydKHqzg=="], 245 | 246 | "@react-aria/i18n": ["@react-aria/i18n@3.12.5", "", { "dependencies": { "@internationalized/date": "^3.7.0", "@internationalized/message": "^3.1.6", "@internationalized/number": "^3.6.0", "@internationalized/string": "^3.2.5", "@react-aria/ssr": "^3.9.7", "@react-aria/utils": "^3.27.0", "@react-types/shared": "^3.27.0", "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-ooeop2pTG94PuaHoN2OTk2hpkqVuoqgEYxRvnc1t7DVAtsskfhS/gVOTqyWGsxvwAvRi7m/CnDu6FYdeQ/bK5w=="], 247 | 248 | "@react-aria/interactions": ["@react-aria/interactions@3.23.0", "", { "dependencies": { "@react-aria/ssr": "^3.9.7", "@react-aria/utils": "^3.27.0", "@react-types/shared": "^3.27.0", "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-0qR1atBIWrb7FzQ+Tmr3s8uH5mQdyRH78n0krYaG8tng9+u1JlSi8DGRSaC9ezKyNB84m7vHT207xnHXGeJ3Fg=="], 249 | 250 | "@react-aria/overlays": ["@react-aria/overlays@3.25.0", "", { "dependencies": { "@react-aria/focus": "^3.19.1", "@react-aria/i18n": "^3.12.5", "@react-aria/interactions": "^3.23.0", "@react-aria/ssr": "^3.9.7", "@react-aria/utils": "^3.27.0", "@react-aria/visually-hidden": "^3.8.19", "@react-stately/overlays": "^3.6.13", "@react-types/button": "^3.10.2", "@react-types/overlays": "^3.8.12", "@react-types/shared": "^3.27.0", "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-UEqJJ4duowrD1JvwXpPZreBuK79pbyNjNxFUVpFSskpGEJe3oCWwsSDKz7P1O7xbx5OYp+rDiY8fk/sE5rkaKw=="], 251 | 252 | "@react-aria/ssr": ["@react-aria/ssr@3.9.7", "", { "dependencies": { "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg=="], 253 | 254 | "@react-aria/utils": ["@react-aria/utils@3.27.0", "", { "dependencies": { "@react-aria/ssr": "^3.9.7", "@react-stately/utils": "^3.10.5", "@react-types/shared": "^3.27.0", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw=="], 255 | 256 | "@react-aria/visually-hidden": ["@react-aria/visually-hidden@3.8.19", "", { "dependencies": { "@react-aria/interactions": "^3.23.0", "@react-aria/utils": "^3.27.0", "@react-types/shared": "^3.27.0", "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-MZgCCyQ3sdG94J5iJz7I7Ai3IxoN0U5d/+EaUnA1mfK7jf2fSYQBqi6Eyp8sWUYzBTLw4giXB5h0RGAnWzk9hA=="], 257 | 258 | "@react-stately/overlays": ["@react-stately/overlays@3.6.13", "", { "dependencies": { "@react-stately/utils": "^3.10.5", "@react-types/overlays": "^3.8.12", "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-WsU85Gf/b+HbWsnnYw7P/Ila3wD+C37Uk/WbU4/fHgJ26IEOWsPE6wlul8j54NZ1PnLNhV9Fn+Kffi+PaJMQXQ=="], 259 | 260 | "@react-stately/utils": ["@react-stately/utils@3.10.5", "", { "dependencies": { "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-iMQSGcpaecghDIh3mZEpZfoFH3ExBwTtuBEcvZ2XnGzCgQjeYXcMdIUwAfVQLXFTdHUHGF6Gu6/dFrYsCzySBQ=="], 261 | 262 | "@react-types/button": ["@react-types/button@3.10.2", "", { "dependencies": { "@react-types/shared": "^3.27.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-h8SB/BLoCgoBulCpyzaoZ+miKXrolK9XC48+n1dKJXT8g4gImrficurDW6+PRTQWaRai0Q0A6bu8UibZOU4syg=="], 263 | 264 | "@react-types/overlays": ["@react-types/overlays@3.8.12", "", { "dependencies": { "@react-types/shared": "^3.27.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-ZvR1t0YV7/6j+6OD8VozKYjvsXT92+C/2LOIKozy7YUNS5KI4MkXbRZzJvkuRECVZOmx8JXKTUzhghWJM/3QuQ=="], 265 | 266 | "@react-types/shared": ["@react-types/shared@3.27.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-gvznmLhi6JPEf0bsq7SwRYTHAKKq/wcmKqFez9sRdbED+SPMUmK5omfZ6w3EwUFQHbYUa4zPBYedQ7Knv70RMw=="], 267 | 268 | "@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="], 269 | 270 | "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], 271 | 272 | "@tailwindcss/node": ["@tailwindcss/node@4.0.7", "", { "dependencies": { "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "tailwindcss": "4.0.7" } }, "sha512-dkFXufkbRB2mu3FPsW5xLAUWJyexpJA+/VtQj18k3SUiJVLdpgzBd1v1gRRcIpEJj7K5KpxBKfOXlZxT3ZZRuA=="], 273 | 274 | "@tailwindcss/oxide": ["@tailwindcss/oxide@4.0.7", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.0.7", "@tailwindcss/oxide-darwin-arm64": "4.0.7", "@tailwindcss/oxide-darwin-x64": "4.0.7", "@tailwindcss/oxide-freebsd-x64": "4.0.7", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.7", "@tailwindcss/oxide-linux-arm64-gnu": "4.0.7", "@tailwindcss/oxide-linux-arm64-musl": "4.0.7", "@tailwindcss/oxide-linux-x64-gnu": "4.0.7", "@tailwindcss/oxide-linux-x64-musl": "4.0.7", "@tailwindcss/oxide-win32-arm64-msvc": "4.0.7", "@tailwindcss/oxide-win32-x64-msvc": "4.0.7" } }, "sha512-yr6w5YMgjy+B+zkJiJtIYGXW+HNYOPfRPtSs+aqLnKwdEzNrGv4ZuJh9hYJ3mcA+HMq/K1rtFV+KsEr65S558g=="], 275 | 276 | "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.0.7", "", { "os": "android", "cpu": "arm64" }, "sha512-5iQXXcAeOHBZy8ASfHFm1k0O/9wR2E3tKh6+P+ilZZbQiMgu+qrnfpBWYPc3FPuQdWiWb73069WT5D+CAfx/tg=="], 277 | 278 | "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.0.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7yGZtEc5IgVYylqK/2B0yVqoofk4UAbkn1ygNpIJZyrOhbymsfr8uUFCueTu2fUxmAYIfMZ8waWo2dLg/NgLgg=="], 279 | 280 | "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.0.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-tPQDV20fBjb26yWbPqT1ZSoDChomMCiXTKn4jupMSoMCFyU7+OJvIY1ryjqBuY622dEBJ8LnCDDWsnj1lX9nNQ=="], 281 | 282 | "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.0.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-sZqJpTyTZiknU9LLHuByg5GKTW+u3FqM7q7myequAXxKOpAFiOfXpY710FuMY+gjzSapyRbDXJlsTQtCyiTo5w=="], 283 | 284 | "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.0.7", "", { "os": "linux", "cpu": "arm" }, "sha512-PBgvULgeSswjd8cbZ91gdIcIDMdc3TUHV5XemEpxlqt9M8KoydJzkuB/Dt910jYdofOIaTWRL6adG9nJICvU4A=="], 285 | 286 | "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.0.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-By/a2yeh+e9b+C67F88ndSwVJl2A3tcUDb29FbedDi+DZ4Mr07Oqw9Y1DrDrtHIDhIZ3bmmiL1dkH2YxrtV+zw=="], 287 | 288 | "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.0.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-WHYs3cpPEJb/ccyT20NOzopYQkl7JKncNBUbb77YFlwlXMVJLLV3nrXQKhr7DmZxz2ZXqjyUwsj2rdzd9stYdw=="], 289 | 290 | "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.0.7", "", { "os": "linux", "cpu": "x64" }, "sha512-7bP1UyuX9kFxbOwkeIJhBZNevKYPXB6xZI37v09fqi6rqRJR8elybwjMUHm54GVP+UTtJ14ueB1K54Dy1tIO6w=="], 291 | 292 | "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.0.7", "", { "os": "linux", "cpu": "x64" }, "sha512-gBQIV8nL/LuhARNGeroqzXymMzzW5wQzqlteVqOVoqwEfpHOP3GMird5pGFbnpY+NP0fOlsZGrxxOPQ4W/84bQ=="], 293 | 294 | "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.0.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-aH530NFfx0kpQpvYMfWoeG03zGnRCMVlQG8do/5XeahYydz+6SIBxA1tl/cyITSJyWZHyVt6GVNkXeAD30v0Xg=="], 295 | 296 | "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.0.7", "", { "os": "win32", "cpu": "x64" }, "sha512-8Cva6bbJN7ZJx320k7vxGGdU0ewmpfS5A4PudyzUuofdi8MgeINuiiWiPQ0VZCda/GX88K6qp+6UpDZNVr8HMQ=="], 297 | 298 | "@tailwindcss/postcss": ["@tailwindcss/postcss@4.0.7", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.0.7", "@tailwindcss/oxide": "4.0.7", "lightningcss": "^1.29.1", "postcss": "^8.4.41", "tailwindcss": "4.0.7" } }, "sha512-zXcKs1uGssVDlnsQ+iwrkul5GPKvsXPynGCuk/eXLx3DVhHlQKMpA6tXN2oO28x2ki1xRBTfadKiHy2taVvp7g=="], 299 | 300 | "@types/lodash": ["@types/lodash@4.17.15", "", {}, "sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw=="], 301 | 302 | "@types/lodash-es": ["@types/lodash-es@4.17.12", "", { "dependencies": { "@types/lodash": "*" } }, "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ=="], 303 | 304 | "@types/node": ["@types/node@20.17.19", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-LEwC7o1ifqg/6r2gn9Dns0f1rhK+fPFDoMiceTJ6kWmVk6bgXBI/9IOWfVan4WiAavK9pIVWdX0/e3J+eEUh5A=="], 305 | 306 | "@types/react": ["@types/react@19.0.10", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g=="], 307 | 308 | "@types/react-dom": ["@types/react-dom@19.0.4", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg=="], 309 | 310 | "@vercel/analytics": ["@vercel/analytics@1.5.0", "", { "peerDependencies": { "@remix-run/react": "^2", "@sveltejs/kit": "^1 || ^2", "next": ">= 13", "react": "^18 || ^19 || ^19.0.0-rc", "svelte": ">= 4", "vue": "^3", "vue-router": "^4" }, "optionalPeers": ["@remix-run/react", "@sveltejs/kit", "next", "react", "svelte", "vue", "vue-router"] }, "sha512-MYsBzfPki4gthY5HnYN7jgInhAZ7Ac1cYDoRWFomwGHWEX7odTEzbtg9kf/QSo7XEsEAqlQugA6gJ2WS2DEa3g=="], 311 | 312 | "@vercel/blob": ["@vercel/blob@0.27.1", "", { "dependencies": { "async-retry": "^1.3.3", "is-buffer": "^2.0.5", "is-node-process": "^1.2.0", "throttleit": "^2.1.0", "undici": "^5.28.4" } }, "sha512-X5EG9W1cZW+Nbt/XdrJJSl5DzCXXn1JRP5nfFwkpFD03nB6uh6BldwX4syElHcciM4Pih8CS7Ri1mtLCJvxSHA=="], 313 | 314 | "aria-hidden": ["aria-hidden@1.2.4", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A=="], 315 | 316 | "async-retry": ["async-retry@1.3.3", "", { "dependencies": { "retry": "0.13.1" } }, "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw=="], 317 | 318 | "busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="], 319 | 320 | "caniuse-lite": ["caniuse-lite@1.0.30001700", "", {}, "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ=="], 321 | 322 | "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], 323 | 324 | "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], 325 | 326 | "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], 327 | 328 | "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 329 | 330 | "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 331 | 332 | "color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="], 333 | 334 | "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], 335 | 336 | "decimal.js": ["decimal.js@10.5.0", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="], 337 | 338 | "detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], 339 | 340 | "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], 341 | 342 | "enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="], 343 | 344 | "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], 345 | 346 | "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], 347 | 348 | "intl-messageformat": ["intl-messageformat@10.7.15", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.3", "@formatjs/fast-memoize": "2.2.6", "@formatjs/icu-messageformat-parser": "2.11.1", "tslib": "2" } }, "sha512-LRyExsEsefQSBjU2p47oAheoKz+EOJxSLDdjOaEjdriajfHsMXOmV/EhMvYSg9bAgCUHasuAC+mcUBe/95PfIg=="], 349 | 350 | "is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="], 351 | 352 | "is-buffer": ["is-buffer@2.0.5", "", {}, "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ=="], 353 | 354 | "is-node-process": ["is-node-process@1.2.0", "", {}, "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw=="], 355 | 356 | "jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="], 357 | 358 | "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], 359 | 360 | "lightningcss": ["lightningcss@1.29.1", "", { "dependencies": { "detect-libc": "^1.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.29.1", "lightningcss-darwin-x64": "1.29.1", "lightningcss-freebsd-x64": "1.29.1", "lightningcss-linux-arm-gnueabihf": "1.29.1", "lightningcss-linux-arm64-gnu": "1.29.1", "lightningcss-linux-arm64-musl": "1.29.1", "lightningcss-linux-x64-gnu": "1.29.1", "lightningcss-linux-x64-musl": "1.29.1", "lightningcss-win32-arm64-msvc": "1.29.1", "lightningcss-win32-x64-msvc": "1.29.1" } }, "sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q=="], 361 | 362 | "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.29.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HtR5XJ5A0lvCqYAoSv2QdZZyoHNttBpa5EP9aNuzBQeKGfbyH5+UipLWvVzpP4Uml5ej4BYs5I9Lco9u1fECqw=="], 363 | 364 | "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.29.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k33G9IzKUpHy/J/3+9MCO4e+PzaFblsgBjSGlpAaFikeBFm8B/CkO3cKU9oI4g+fjS2KlkLM/Bza9K/aw8wsNA=="], 365 | 366 | "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.29.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0SUW22fv/8kln2LnIdOCmSuXnxgxVC276W5KLTwoehiO0hxkacBxjHOL5EtHD8BAXg2BvuhsJPmVMasvby3LiQ=="], 367 | 368 | "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.29.1", "", { "os": "linux", "cpu": "arm" }, "sha512-sD32pFvlR0kDlqsOZmYqH/68SqUMPNj+0pucGxToXZi4XZgZmqeX/NkxNKCPsswAXU3UeYgDSpGhu05eAufjDg=="], 369 | 370 | "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.29.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-0+vClRIZ6mmJl/dxGuRsE197o1HDEeeRk6nzycSy2GofC2JsY4ifCRnvUWf/CUBQmlrvMzt6SMQNMSEu22csWQ=="], 371 | 372 | "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.29.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-UKMFrG4rL/uHNgelBsDwJcBqVpzNJbzsKkbI3Ja5fg00sgQnHw/VrzUTEc4jhZ+AN2BvQYz/tkHu4vt1kLuJyw=="], 373 | 374 | "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.29.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u1S+xdODy/eEtjADqirA774y3jLcm8RPtYztwReEXoZKdzgsHYPl0s5V52Tst+GKzqjebkULT86XMSxejzfISw=="], 375 | 376 | "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.29.1", "", { "os": "linux", "cpu": "x64" }, "sha512-L0Tx0DtaNUTzXv0lbGCLB/c/qEADanHbu4QdcNOXLIe1i8i22rZRpbT3gpWYsCh9aSL9zFujY/WmEXIatWvXbw=="], 377 | 378 | "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.29.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-QoOVnkIEFfbW4xPi+dpdft/zAKmgLgsRHfJalEPYuJDOWf7cLQzYg0DEh8/sn737FaeMJxHZRc1oBreiwZCjog=="], 379 | 380 | "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.29.1", "", { "os": "win32", "cpu": "x64" }, "sha512-NygcbThNBe4JElP+olyTI/doBNGJvLs3bFCRPdvuCcxZCcCZ71B858IHpdm7L1btZex0FvCmM17FK98Y9MRy1Q=="], 381 | 382 | "lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], 383 | 384 | "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], 385 | 386 | "nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="], 387 | 388 | "next": ["next@15.1.7", "", { "dependencies": { "@next/env": "15.1.7", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.1.7", "@next/swc-darwin-x64": "15.1.7", "@next/swc-linux-arm64-gnu": "15.1.7", "@next/swc-linux-arm64-musl": "15.1.7", "@next/swc-linux-x64-gnu": "15.1.7", "@next/swc-linux-x64-musl": "15.1.7", "@next/swc-win32-arm64-msvc": "15.1.7", "@next/swc-win32-x64-msvc": "15.1.7", "sharp": "^0.33.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-GNeINPGS9c6OZKCvKypbL8GTsT5GhWPp4DM0fzkXJuXMilOO2EeFxuAY6JZbtk6XIl6Ws10ag3xRINDjSO5+wg=="], 389 | 390 | "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], 391 | 392 | "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 393 | 394 | "postcss": ["postcss@8.5.2", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA=="], 395 | 396 | "prettier": ["prettier@3.5.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw=="], 397 | 398 | "prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.6.11", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-import-sort": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-style-order": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-import-sort", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-style-order", "prettier-plugin-svelte"] }, "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA=="], 399 | 400 | "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], 401 | 402 | "radix-ui": ["radix-ui@1.1.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-accessible-icon": "1.1.2", "@radix-ui/react-accordion": "1.2.3", "@radix-ui/react-alert-dialog": "1.1.6", "@radix-ui/react-aspect-ratio": "1.1.2", "@radix-ui/react-avatar": "1.1.3", "@radix-ui/react-checkbox": "1.1.4", "@radix-ui/react-collapsible": "1.1.3", "@radix-ui/react-collection": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-context-menu": "2.2.6", "@radix-ui/react-dialog": "1.1.6", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-dropdown-menu": "2.1.6", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-form": "0.1.2", "@radix-ui/react-hover-card": "1.1.6", "@radix-ui/react-label": "2.1.2", "@radix-ui/react-menu": "2.1.6", "@radix-ui/react-menubar": "1.1.6", "@radix-ui/react-navigation-menu": "1.2.5", "@radix-ui/react-popover": "1.1.6", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-progress": "1.1.2", "@radix-ui/react-radio-group": "1.2.3", "@radix-ui/react-roving-focus": "1.1.2", "@radix-ui/react-scroll-area": "1.2.3", "@radix-ui/react-select": "2.1.6", "@radix-ui/react-separator": "1.1.2", "@radix-ui/react-slider": "1.2.3", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-switch": "1.1.3", "@radix-ui/react-tabs": "1.1.3", "@radix-ui/react-toast": "1.2.6", "@radix-ui/react-toggle": "1.1.2", "@radix-ui/react-toggle-group": "1.1.2", "@radix-ui/react-toolbar": "1.1.2", "@radix-ui/react-tooltip": "1.1.8", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-W8L6soM1vQnIXVvVa31AkQhoZBDPwVoNHhT13R3aB9Qq7ARYIUS9DLaCopRBsbTdZm1NEEPx3rnq659CiNOBDw=="], 403 | 404 | "react": ["react@19.0.0", "", {}, "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="], 405 | 406 | "react-dom": ["react-dom@19.0.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="], 407 | 408 | "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], 409 | 410 | "react-remove-scroll": ["react-remove-scroll@2.6.3", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ=="], 411 | 412 | "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], 413 | 414 | "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], 415 | 416 | "regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="], 417 | 418 | "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], 419 | 420 | "scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="], 421 | 422 | "semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], 423 | 424 | "sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], 425 | 426 | "simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="], 427 | 428 | "sonner": ["sonner@1.7.4", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw=="], 429 | 430 | "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], 431 | 432 | "streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="], 433 | 434 | "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], 435 | 436 | "tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="], 437 | 438 | "tailwindcss": ["tailwindcss@4.0.7", "", {}, "sha512-yH5bPPyapavo7L+547h3c4jcBXcrKwybQRjwdEIVAd9iXRvy/3T1CC6XSQEgZtRySjKfqvo3Cc0ZF1DTheuIdA=="], 439 | 440 | "tapable": ["tapable@2.2.1", "", {}, "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="], 441 | 442 | "throttleit": ["throttleit@2.1.0", "", {}, "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw=="], 443 | 444 | "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 445 | 446 | "typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], 447 | 448 | "ulid": ["ulid@2.3.0", "", { "bin": { "ulid": "./bin/cli.js" } }, "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw=="], 449 | 450 | "undici": ["undici@5.28.5", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA=="], 451 | 452 | "undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="], 453 | 454 | "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], 455 | 456 | "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], 457 | 458 | "use-sync-external-store": ["use-sync-external-store@1.4.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw=="], 459 | 460 | "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], 461 | 462 | "sharp/detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="], 463 | } 464 | } 465 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from 'next'; 2 | 3 | const nextConfig: NextConfig = { 4 | devIndicators: { 5 | appIsrStatus: false, 6 | }, 7 | }; 8 | 9 | export default nextConfig; 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "liquid-logo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --turbopack", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@base-ui-components/react": "1.0.0-alpha.6", 13 | "@paper-design/shaders-react": "0.0.21", 14 | "@vercel/analytics": "^1.5.0", 15 | "@vercel/blob": "0.27.1", 16 | "clsx": "^2.1.1", 17 | "lodash-es": "^4.17.21", 18 | "next": "15.2.3", 19 | "radix-ui": "1.1.3", 20 | "react": "19.0.0", 21 | "react-dom": "19.0.0", 22 | "sonner": "1.7.4", 23 | "ulid": "2.3.0" 24 | }, 25 | "devDependencies": { 26 | "@tailwindcss/postcss": "^4.0.7", 27 | "@types/lodash-es": "^4.17.12", 28 | "@types/node": "^20", 29 | "@types/react": "^19", 30 | "@types/react-dom": "^19", 31 | "postcss": "^8.5.2", 32 | "prettier-plugin-tailwindcss": "^0.6.11", 33 | "tailwindcss": "^4.0.7", 34 | "typescript": "^5" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | '@tailwindcss/postcss': {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paper-design/liquid-logo/77cdaa686063546f228045717fed8b8ae813f52c/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/examples/accel-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/examples/adobe-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/examples/apple-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/examples/base-ui-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/examples/basecase-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/examples/chrome-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paper-design/liquid-logo/77cdaa686063546f228045717fed8b8ae813f52c/public/examples/chrome-logo.png -------------------------------------------------------------------------------- /public/examples/cloudflare-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/examples/discord-logo.svg: -------------------------------------------------------------------------------- 1 | Discord logo -------------------------------------------------------------------------------- /public/examples/figma-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/examples/fly-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/examples/framer-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/examples/instagram-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/examples/mac-rumors-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 10 | 17 | 22 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 43 | -------------------------------------------------------------------------------- /public/examples/nike-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/examples/paper-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/examples/radix-ui-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/examples/rive-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/examples/sketch-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/examples/slack-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | slack-logo-icon 5 | Created with Sketch Beta. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /public/examples/starbucks-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/examples/supabase-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paper-design/liquid-logo/77cdaa686063546f228045717fed8b8ae813f52c/public/examples/supabase-logo.png -------------------------------------------------------------------------------- /public/examples/t3-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/examples/techcrunch-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/examples/vercel-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paper-design/liquid-logo/77cdaa686063546f228045717fed8b8ae813f52c/public/examples/vercel-logo.png -------------------------------------------------------------------------------- /public/examples/verge-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /public/examples/workos-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/favicon-dev.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paper-design/liquid-logo/77cdaa686063546f228045717fed8b8ae813f52c/public/favicon-dev.ico -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paper-design/liquid-logo/77cdaa686063546f228045717fed8b8ae813f52c/public/favicon.ico -------------------------------------------------------------------------------- /public/logos/apple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/logos/chanel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/logos/cloudflare.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/logos/discord.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/logos/nasa.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/logos/nike.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/logos/paper.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/logos/remix.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/logos/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/logos/volkswagen.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paper-design/liquid-logo/77cdaa686063546f228045717fed8b8ae813f52c/public/og-image.png -------------------------------------------------------------------------------- /src/app/api/user-logo/route.ts: -------------------------------------------------------------------------------- 1 | import { put } from '@vercel/blob'; 2 | import { NextResponse } from 'next/server'; 3 | import { ulid } from 'ulid'; 4 | 5 | export async function POST(request: Request): Promise { 6 | if (!request.body) { 7 | return NextResponse.json({ error: 'No image data provided' }, { status: 400 }); 8 | } 9 | 10 | const imageId = ulid(); 11 | const fileName = `${imageId}.png`; 12 | // Note: vercel hashes the file name already, so just use a short input name so the final URL isn't too long 13 | 14 | const result = await put(fileName, request.body, { 15 | access: 'public', 16 | addRandomSuffix: false, 17 | }); 18 | 19 | return NextResponse.json({ imageId }); 20 | } 21 | -------------------------------------------------------------------------------- /src/app/compose-refs.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | type PossibleRef = React.Ref | undefined; 4 | 5 | /** 6 | * Set a given ref to a given value 7 | * This utility takes care of different types of refs: callback refs and RefObject(s) 8 | */ 9 | function setRef(ref: PossibleRef, value: T) { 10 | if (typeof ref === 'function') { 11 | return ref(value); 12 | } else if (ref !== null && ref !== undefined) { 13 | ref.current = value; 14 | } 15 | } 16 | 17 | /** 18 | * A utility to compose multiple refs together 19 | * Accepts callback refs and RefObject(s) 20 | */ 21 | function composeRefs(...refs: PossibleRef[]): React.RefCallback { 22 | return (node) => { 23 | let hasCleanup = false; 24 | const cleanups = refs.map((ref) => { 25 | const cleanup = setRef(ref, node); 26 | if (!hasCleanup && typeof cleanup == 'function') { 27 | hasCleanup = true; 28 | } 29 | return cleanup; 30 | }); 31 | 32 | // React <19 will log an error to the console if a callback ref returns a 33 | // value. We don't use ref cleanups internally so this will only happen if a 34 | // user's ref callback returns a value, which we only expect if they are 35 | // using the cleanup functionality added in React 19. 36 | if (hasCleanup) { 37 | return () => { 38 | for (let i = 0; i < cleanups.length; i++) { 39 | const cleanup = cleanups[i]; 40 | if (typeof cleanup == 'function') { 41 | cleanup(); 42 | } else { 43 | setRef(refs[i], null); 44 | } 45 | } 46 | }; 47 | } 48 | }; 49 | } 50 | 51 | /** 52 | * A custom hook that composes multiple refs 53 | * Accepts callback refs and RefObject(s) 54 | */ 55 | function useComposedRefs(...refs: PossibleRef[]): React.RefCallback { 56 | // eslint-disable-next-line react-hooks/exhaustive-deps 57 | return React.useCallback(composeRefs(...refs), refs); 58 | } 59 | 60 | export { composeRefs, useComposedRefs }; 61 | -------------------------------------------------------------------------------- /src/app/hero/liquid-frag.ts: -------------------------------------------------------------------------------- 1 | export const liquidFragSource = /* glsl */ `#version 300 es 2 | precision mediump float; 3 | 4 | in vec2 vUv; 5 | out vec4 fragColor; 6 | 7 | uniform sampler2D u_image_texture; 8 | uniform float u_time; 9 | uniform float u_ratio; 10 | uniform float u_img_ratio; 11 | uniform float u_patternScale; 12 | uniform float u_refraction; 13 | uniform float u_edge; 14 | uniform float u_patternBlur; 15 | uniform float u_liquid; 16 | 17 | 18 | #define TWO_PI 6.28318530718 19 | #define PI 3.14159265358979323846 20 | 21 | 22 | vec3 mod289(vec3 x) { return x - floor(x * (1. / 289.)) * 289.; } 23 | vec2 mod289(vec2 x) { return x - floor(x * (1. / 289.)) * 289.; } 24 | vec3 permute(vec3 x) { return mod289(((x*34.)+1.)*x); } 25 | float snoise(vec2 v) { 26 | const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439); 27 | vec2 i = floor(v + dot(v, C.yy)); 28 | vec2 x0 = v - i + dot(i, C.xx); 29 | vec2 i1; 30 | i1 = (x0.x > x0.y) ? vec2(1., 0.) : vec2(0., 1.); 31 | vec4 x12 = x0.xyxy + C.xxzz; 32 | x12.xy -= i1; 33 | i = mod289(i); 34 | vec3 p = permute(permute(i.y + vec3(0., i1.y, 1.)) + i.x + vec3(0., i1.x, 1.)); 35 | vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.); 36 | m = m*m; 37 | m = m*m; 38 | vec3 x = 2. * fract(p * C.www) - 1.; 39 | vec3 h = abs(x) - 0.5; 40 | vec3 ox = floor(x + 0.5); 41 | vec3 a0 = x - ox; 42 | m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h); 43 | vec3 g; 44 | g.x = a0.x * x0.x + h.x * x0.y; 45 | g.yz = a0.yz * x12.xz + h.yz * x12.yw; 46 | return 130. * dot(m, g); 47 | } 48 | 49 | vec2 get_img_uv() { 50 | vec2 img_uv = vUv; 51 | img_uv -= .5; 52 | if (u_ratio > u_img_ratio) { 53 | img_uv.x = img_uv.x * u_ratio / u_img_ratio; 54 | } else { 55 | img_uv.y = img_uv.y * u_img_ratio / u_ratio; 56 | } 57 | float scale_factor = 1.; 58 | img_uv *= scale_factor; 59 | img_uv += .5; 60 | 61 | img_uv.y = 1. - img_uv.y; 62 | 63 | return img_uv; 64 | } 65 | vec2 rotate(vec2 uv, float th) { 66 | return mat2(cos(th), sin(th), -sin(th), cos(th)) * uv; 67 | } 68 | float get_color_channel(float c1, float c2, float stripe_p, vec3 w, float extra_blur, float b) { 69 | float ch = c2; 70 | float border = 0.; 71 | float blur = u_patternBlur + extra_blur; 72 | 73 | ch = mix(ch, c1, smoothstep(.0, blur, stripe_p)); 74 | 75 | border = w[0]; 76 | ch = mix(ch, c2, smoothstep(border - blur, border + blur, stripe_p)); 77 | 78 | b = smoothstep(.2, .8, b); 79 | border = w[0] + .4 * (1. - b) * w[1]; 80 | ch = mix(ch, c1, smoothstep(border - blur, border + blur, stripe_p)); 81 | 82 | border = w[0] + .5 * (1. - b) * w[1]; 83 | ch = mix(ch, c2, smoothstep(border - blur, border + blur, stripe_p)); 84 | 85 | border = w[0] + w[1]; 86 | ch = mix(ch, c1, smoothstep(border - blur, border + blur, stripe_p)); 87 | 88 | float gradient_t = (stripe_p - w[0] - w[1]) / w[2]; 89 | float gradient = mix(c1, c2, smoothstep(0., 1., gradient_t)); 90 | ch = mix(ch, gradient, smoothstep(border - blur, border + blur, stripe_p)); 91 | 92 | return ch; 93 | } 94 | 95 | float get_img_frame_alpha(vec2 uv, float img_frame_width) { 96 | float img_frame_alpha = smoothstep(0., img_frame_width, uv.x) * smoothstep(1., 1. - img_frame_width, uv.x); 97 | img_frame_alpha *= smoothstep(0., img_frame_width, uv.y) * smoothstep(1., 1. - img_frame_width, uv.y); 98 | return img_frame_alpha; 99 | } 100 | 101 | void main() { 102 | vec2 uv = vUv; 103 | uv.y = 1. - uv.y; 104 | uv.x *= u_ratio; 105 | 106 | float diagonal = uv.x - uv.y; 107 | 108 | float t = .001 * u_time; 109 | 110 | vec2 img_uv = get_img_uv(); 111 | vec4 img = texture(u_image_texture, img_uv); 112 | 113 | vec3 color = vec3(0.); 114 | float opacity = 1.; 115 | 116 | vec3 color1 = vec3(.98, 0.98, 1.); 117 | vec3 color2 = vec3(.1, .1, .1 + .1 * smoothstep(.7, 1.3, uv.x + uv.y)); 118 | 119 | float edge = img.r; 120 | 121 | 122 | vec2 grad_uv = uv; 123 | grad_uv -= .5; 124 | 125 | float dist = length(grad_uv + vec2(0., .2 * diagonal)); 126 | 127 | grad_uv = rotate(grad_uv, (.25 - .2 * diagonal) * PI); 128 | 129 | float bulge = pow(1.8 * dist, 1.2); 130 | bulge = 1. - bulge; 131 | bulge *= pow(uv.y, .3); 132 | 133 | 134 | float cycle_width = u_patternScale; 135 | float thin_strip_1_ratio = .12 / cycle_width * (1. - .4 * bulge); 136 | float thin_strip_2_ratio = .07 / cycle_width * (1. + .4 * bulge); 137 | float wide_strip_ratio = (1. - thin_strip_1_ratio - thin_strip_2_ratio); 138 | 139 | float thin_strip_1_width = cycle_width * thin_strip_1_ratio; 140 | float thin_strip_2_width = cycle_width * thin_strip_2_ratio; 141 | 142 | opacity = 1. - smoothstep(.9 - .5 * u_edge, 1. - .5 * u_edge, edge); 143 | opacity *= get_img_frame_alpha(img_uv, 0.01); 144 | 145 | 146 | float noise = snoise(uv - t); 147 | 148 | edge += (1. - edge) * u_liquid * noise; 149 | 150 | float refr = 0.; 151 | refr += (1. - bulge); 152 | refr = clamp(refr, 0., 1.); 153 | 154 | float dir = grad_uv.x; 155 | 156 | 157 | dir += diagonal; 158 | 159 | dir -= 2. * noise * diagonal * (smoothstep(0., 1., edge) * smoothstep(1., 0., edge)); 160 | 161 | bulge *= clamp(pow(uv.y, .1), .3, 1.); 162 | dir *= (.1 + (1.1 - edge) * bulge); 163 | 164 | dir *= smoothstep(1., .7, edge); 165 | 166 | dir += .18 * (smoothstep(.1, .2, uv.y) * smoothstep(.4, .2, uv.y)); 167 | dir += .03 * (smoothstep(.1, .2, 1. - uv.y) * smoothstep(.4, .2, 1. - uv.y)); 168 | 169 | dir *= (.5 + .5 * pow(uv.y, 2.)); 170 | 171 | dir *= cycle_width; 172 | 173 | dir -= t; 174 | 175 | float refr_r = refr; 176 | refr_r += .03 * bulge * noise; 177 | float refr_b = 1.3 * refr; 178 | 179 | refr_r += 5. * (smoothstep(-.1, .2, uv.y) * smoothstep(.5, .1, uv.y)) * (smoothstep(.4, .6, bulge) * smoothstep(1., .4, bulge)); 180 | refr_r -= diagonal; 181 | 182 | refr_b += (smoothstep(0., .4, uv.y) * smoothstep(.8, .1, uv.y)) * (smoothstep(.4, .6, bulge) * smoothstep(.8, .4, bulge)); 183 | refr_b -= .2 * edge; 184 | 185 | refr_r *= u_refraction; 186 | refr_b *= u_refraction; 187 | 188 | vec3 w = vec3(thin_strip_1_width, thin_strip_2_width, wide_strip_ratio); 189 | w[1] -= .02 * smoothstep(.0, 1., edge + bulge); 190 | float stripe_r = mod(dir + refr_r, 1.); 191 | float r = get_color_channel(color1.r, color2.r, stripe_r, w, 0.02 + .03 * u_refraction * bulge, bulge); 192 | float stripe_g = mod(dir, 1.); 193 | float g = get_color_channel(color1.g, color2.g, stripe_g, w, 0.01 / (1. - diagonal), bulge); 194 | float stripe_b = mod(dir - refr_b, 1.); 195 | float b = get_color_channel(color1.b, color2.b, stripe_b, w, .01, bulge); 196 | 197 | color = vec3(r, g, b); 198 | 199 | color *= opacity; 200 | 201 | fragColor = vec4(color, opacity); 202 | } 203 | `; 204 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata, Viewport } from 'next/types'; 2 | import './styles.css'; 3 | import { Analytics } from '@vercel/analytics/react'; 4 | 5 | export default function Layout({ children }: React.PropsWithChildren) { 6 | return ( 7 | 8 | 9 | Liquid Metal • Paper 10 | 11 | 12 | {children} 13 | 14 | 15 | 16 | ); 17 | } 18 | 19 | export const metadata: Metadata = { 20 | title: 'Turn your logo into liquid metal • Paper', 21 | description: 'Liquid metal for your logo by paper.design', 22 | icons: { 23 | icon: process.env.NODE_ENV === 'production' ? '/favicon.ico' : '/favicon-dev.ico', 24 | apple: '/apple-touch-icon.png', 25 | }, 26 | openGraph: { 27 | url: 'https://liquid.paper.design', 28 | type: 'website', 29 | locale: 'en_US', 30 | siteName: 'Liquid logo by Paper', 31 | title: 'Turn your logo into liquid metal • Paper', 32 | description: 'Liquid metal for your logo by paper.design', 33 | images: [ 34 | { 35 | url: 'https://liquid.paper.design/og-image.png', 36 | width: 1200, 37 | height: 630, 38 | alt: 'Turn your logo into liquid metal • Paper', 39 | }, 40 | ], 41 | }, 42 | twitter: { 43 | card: 'summary_large_image', 44 | title: 'Turn your logo into liquid metal • Paper', 45 | description: 'Liquid metal for your logo by paper.design', 46 | creator: '@paper', 47 | images: ['https://liquid.paper.design/og-image.png'], 48 | }, 49 | }; 50 | 51 | export const viewport = { 52 | width: 480, 53 | initialScale: 0, 54 | themeColor: '#000', 55 | }; 56 | -------------------------------------------------------------------------------- /src/app/number-input.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useImperativeHandle, useRef, useState } from 'react'; 2 | import { flushSync } from 'react-dom'; 3 | import { useComposedRefs } from './compose-refs'; 4 | 5 | interface NumberInputProps extends React.ComponentProps { 6 | min?: number; 7 | max?: number; 8 | integer?: boolean; 9 | 10 | /** Small and large nudge amounts */ 11 | increments?: [number, number]; 12 | } 13 | 14 | export const NumberInput = ({ 15 | integer = false, 16 | min = -Infinity, 17 | max = Infinity, 18 | increments = [1, 10], 19 | format = (value) => { 20 | const float = parseFloat(value); 21 | return Number.isInteger(float) ? value : float.toFixed(3); 22 | }, 23 | ...props 24 | }: NumberInputProps) => { 25 | const ref = useRef(null); 26 | 27 | return ( 28 | { 33 | if (!ref.current) { 34 | return; 35 | } 36 | 37 | if (event.key === 'ArrowUp' || event.key === 'ArrowDown') { 38 | event.preventDefault(); 39 | const direction = event.key === 'ArrowUp' ? 1 : -1; 40 | const [smallIncrement, largeIncrement] = increments; 41 | let amount = event.shiftKey ? largeIncrement : smallIncrement; 42 | 43 | const defaultNumber = Math.max(min, Math.min(0, max)); 44 | const value = ref.current.value; 45 | 46 | const defaultValue = defaultNumber; 47 | let number = defaultNumber; 48 | let newValue = defaultValue; 49 | 50 | if (value !== null) { 51 | number = integer ? parseInt(value) : parseFloat(value); 52 | } 53 | 54 | if (!Number.isNaN(number)) { 55 | newValue = decimal(Math.min(max, Math.max(min, number + amount * direction))); 56 | } 57 | 58 | flushSync(() => ref.current?.commitValue(newValue.toString())); 59 | ref.current.select(); 60 | } 61 | 62 | props.onKeyDown?.(event); 63 | }} 64 | /> 65 | ); 66 | }; 67 | 68 | export type InputHandle = HTMLInputElement & { 69 | /** A handle to directly set the input's internal value */ 70 | setValue: (value: string) => void; 71 | 72 | /** A handle to save the value: parses, validates, and calls `onValueCommit` */ 73 | commitValue: (value: string) => void; 74 | }; 75 | 76 | export interface InputProps extends React.ComponentPropsWithoutRef<'input'> { 77 | ref?: React.Ref; 78 | 79 | value: string; 80 | defaultValue?: never; 81 | onValueCommit?: (value: string) => void; 82 | 83 | /** 84 | * A function to visually transform the incoming value when it is displayed in the input. 85 | */ 86 | format?: (value: string) => string; 87 | 88 | /** 89 | * A function to parse the value that user entered into the input. 90 | * By default, collapses consecutive whitespace and trims the value. 91 | * 92 | * Should return `null` when the value can't be parsed; the input will 93 | * revert to the previously committed value in this case. 94 | * 95 | * Checks for `!!value` by default. 96 | */ 97 | parse?: (value: string) => string | null; 98 | 99 | /** 100 | * A function used to customise how input contents are selected when clicking into the input. 101 | * Defaults to `(input) => input?.select()` 102 | */ 103 | select?: (input: HTMLInputElement | null) => void; 104 | } 105 | 106 | export function Input({ onValueCommit, format = defaultFormatter, parse = defaultParser, ...props }: InputProps) { 107 | const sourceValue = format(props.value); 108 | const [value, setValue] = useState(sourceValue); 109 | const [input, setInput] = useState(null); 110 | const isDirty = useRef(false); 111 | 112 | // Track the external value prop if the input isn't focused. 113 | // Makes sure that the internal value doesn't get stale if props don't change. 114 | const shouldResetValue = input && document.activeElement !== input && sourceValue !== value; 115 | if (shouldResetValue) { 116 | setValue(sourceValue); 117 | } 118 | 119 | function commitValue(value: string) { 120 | const parsed = parse(value); 121 | 122 | if (parsed === null) { 123 | setValue(sourceValue); 124 | return; 125 | } 126 | 127 | const formatted = format(parsed); 128 | setValue(formatted); 129 | onValueCommit?.(parsed); 130 | } 131 | 132 | const commitValueRef = useRef(commitValue); 133 | commitValueRef.current = commitValue; 134 | 135 | useImperativeHandle( 136 | props.ref, 137 | function () { 138 | if (input) { 139 | return Object.assign(input, { 140 | setValue, 141 | commitValue: function (value: string) { 142 | commitValueRef.current(value); 143 | }, 144 | }); 145 | } 146 | return null; 147 | }, 148 | [input] 149 | ); 150 | 151 | function handleBlur() { 152 | if (isDirty.current) { 153 | commitValue(value); 154 | } 155 | 156 | isDirty.current = false; 157 | } 158 | 159 | // Run the blur handler on unmount 160 | const handleBlurRef = useRef(handleBlur); 161 | handleBlurRef.current = handleBlur; 162 | useEffect(() => { 163 | return () => handleBlurRef.current(); 164 | }, []); 165 | 166 | return ( 167 | { 173 | handleBlur(); 174 | props.onBlur?.(event); 175 | }} 176 | onFocus={(event) => { 177 | event.target.select(); 178 | props.onFocus?.(event); 179 | }} 180 | onChange={(event) => { 181 | isDirty.current = true; 182 | setValue(event.target.value); 183 | props.onChange?.(event); 184 | }} 185 | onBeforeInput={() => { 186 | // Catch input that doesn't result in `onChange` callback 187 | // (Note: `onChange` is still needed to catch character removal) 188 | isDirty.current = true; 189 | }} 190 | onKeyDown={(event) => { 191 | if (event.key === 'Escape') { 192 | if (value === sourceValue) { 193 | input?.blur(); 194 | } else { 195 | flushSync(() => setValue(sourceValue)); 196 | input?.select(); 197 | } 198 | } 199 | 200 | if (event.key === 'Enter') { 201 | // Treat the value as dirty when the Enter key is pressed 202 | isDirty.current = true; 203 | input?.blur(); 204 | } 205 | 206 | props.onKeyDown?.(event); 207 | }} 208 | onPointerDown={(event) => { 209 | handlePointerDown(input); 210 | props.onPointerDown?.(event); 211 | }} 212 | /> 213 | ); 214 | } 215 | 216 | function defaultFormatter(value: string) { 217 | return value; 218 | } 219 | 220 | function defaultParser(value: string) { 221 | return value.trim().replace(/\s+/g, ' ') || null; 222 | } 223 | 224 | /** Autoselects input contents on click */ 225 | export function handlePointerDown(input: HTMLInputElement | null) { 226 | if (document.activeElement !== input) { 227 | if (input) { 228 | // (1) Firefox restores previous selection on focus 229 | // (2) Chrome also restores previous selection, but only when it spans the entire value 230 | // This is at odds with autoselection on click; we want a clear initial state every time. 231 | input.focus(); // Required for Chrome only, as it won't modify selection unless focused. 232 | input.selectionStart = null; 233 | input.selectionEnd = null; 234 | } 235 | 236 | // If input selection changes at any point after pointerdown, we want to cancel autoselection 237 | // on pointerup (even if the user ends up with no selection after the cursor movement). 238 | const handleSelectionChange = () => { 239 | if (input?.selectionStart !== input?.selectionEnd) { 240 | document.removeEventListener('selectionchange', handleSelectionChange); 241 | document.removeEventListener('pointerup', handlePointerUp); 242 | } 243 | }; 244 | 245 | const handlePointerUp = (event: Event) => { 246 | document.removeEventListener('selectionchange', handleSelectionChange); 247 | if (event.target && event.target === input) { 248 | input.select(); 249 | } 250 | }; 251 | 252 | document.addEventListener('selectionchange', handleSelectionChange); 253 | document.addEventListener('pointerup', handlePointerUp, { once: true, passive: true }); 254 | } 255 | } 256 | 257 | export const baseInputProps = { 258 | 'type': 'text', 259 | 'autoCapitalize': 'none', 260 | 'autoComplete': 'off', 261 | 'autoCorrect': 'off', 262 | 'spellCheck': 'false', 263 | // Turn off common password managers 264 | // https://www.stefanjudis.com/snippets/turn-off-password-managers/ 265 | 'data-1p-ignore': 'true', 266 | 'data-lpignore': 'true', 267 | 'data-bwignore': 'true', 268 | 'data-form-type': 'other', 269 | } as const; 270 | 271 | /** 272 | * Fix up floating-point arithmetic issues by applying a incredibly tiny rounding on the value. 273 | * 274 | * Example: 275 | * - `0.05 + 0.01 => 0.060000000000000005` 276 | * - `decimal(0.05 + 0.01) => 0.06` 277 | */ 278 | export function decimal(number: number) { 279 | return +number.toFixed(12); 280 | } 281 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import Page from '@/app/share/[id]/page'; 2 | 3 | const APPLE_LOGO_ID = '01JMFPY99JXXKRQWDAHBY0ARQH'; 4 | 5 | export default function Home() { 6 | return ( 7 | 10 | resolve({ 11 | id: APPLE_LOGO_ID, 12 | }) 13 | ) 14 | } 15 | /> 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/app/paper-logo.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | 3 | export function PaperLogo({ className, color = 'var(--color-blue)', ...props }: React.ComponentProps<'svg'>) { 4 | return ( 5 | 6 | 10 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/app/round-optimized.ts: -------------------------------------------------------------------------------- 1 | /** We use multiplication to shift decimal places to the right so we can work with them as whole numbers */ 2 | const precisionMultipliers = [1, 10, 100, 1000, 10000, 100000, 1000000] as const; 3 | 4 | /** 5 | * Ultra-optimized number rounding function that preserves a given precision non-integer values (2 by default) 6 | * But will chop off any decimals that end up being 0's, soas to return a user friendly display number 7 | * Uses truncate instead of string operations for maximum performance. 8 | * 9 | * @param {number} num - The number to round 10 | * @returns {number} The rounded result 11 | * 12 | * roundOptimized(23.456) -> 23.46 13 | * roundOptimized(23.99999) -> 24 14 | * roundOptimized(-23.456) -> -23.46 15 | * roundOptimized(-23.99999) -> -24 16 | * roundOptimized(23.004) -> 23 17 | * roundOptimized(23.006) -> 23.01 18 | * 19 | * Performance: MUCH faster than using toFixed() or similar string-based methods 20 | * Range: Safe for larger numbers, does not use bitwise operations 21 | */ 22 | export function roundOptimized(num: number, precision: 0 | 1 | 2 | 3 | 4 | 5 | 6 = 2): number { 23 | // Shift decimal point to the right by multiplying 24 | // This lets us work with the decimals we care about as whole numbers 25 | const multiplier = precisionMultipliers[precision]!; 26 | const shifted = num * multiplier; 27 | 28 | // Add or subtract 0.5 before truncating based on sign 29 | // This ensures correct rounding direction for both positive and negative numbers 30 | // Trunc is faster than floor 31 | const rounded = Math.trunc(shifted + (shifted > 0 ? 0.5 : -0.5)); 32 | 33 | // Prefer division here instead of multiplication or you may get weird float precision results like "-2122.2200000000003" 34 | const result = rounded / multiplier; 35 | 36 | // Normalize any zero result to avoid -0 returns 37 | return result === 0 ? 0 : result; 38 | } 39 | -------------------------------------------------------------------------------- /src/app/share/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import { PaperLogo } from '@/app/paper-logo'; 2 | import { Hero } from '@/hero/hero'; 3 | import NextLink from 'next/link'; 4 | import { Fragment, Suspense } from 'react'; 5 | 6 | interface PageProps { 7 | params: Promise<{ id: string }>; 8 | } 9 | 10 | export default async function Page({ params }: PageProps) { 11 | const { id } = await params; 12 | 13 | return ( 14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | @paper 25 | 26 | 27 |
28 | 29 |
30 | 31 | 32 | 33 |
34 | 35 |
36 | {logos.map((group, i) => ( 37 | 38 |
39 | {group.map(({ name, href, src }) => ( 40 | 41 |
42 | {name 43 |
44 | {name} 45 |
46 | ))} 47 |
48 | 49 | {i !== logos.length - 1 &&
} 50 | 51 | ))} 52 |
53 |
54 | ); 55 | } 56 | 57 | const logos = [ 58 | [ 59 | { 60 | name: 'Apple', 61 | href: '/', 62 | src: '/logos/apple.svg', 63 | }, 64 | { 65 | name: 'Nike', 66 | href: '/share/01JMFN4FHEYQY3CBR7B4YBZFK9?edge=0.01', 67 | src: '/logos/nike.svg', 68 | }, 69 | { 70 | name: 'NASA', 71 | href: '/share/01JMFN7R2E6WV297MM6EHBCAW6?edge=0.15', 72 | src: '/logos/nasa.svg', 73 | }, 74 | { 75 | name: 'Chanel', 76 | href: '/share/01JMFNF83EX5DAVWF1TP1469BW?edge=0.15', 77 | src: '/logos/chanel.svg', 78 | }, 79 | { 80 | name: 'Volkswagen', 81 | href: '/share/01JMFPD47QAN0FXMWWQC8YG8SY?edge=0.01', 82 | src: '/logos/volkswagen.svg', 83 | }, 84 | ], 85 | [ 86 | { 87 | name: 'Vercel', 88 | href: '/share/01JMFQ1ESB52205RRGSHCXHCZG?edgeBlur=0.01', 89 | src: '/logos/vercel.svg', 90 | }, 91 | { 92 | name: 'Discord', 93 | href: '/share/01JMFQS93Q6R2VRQ62HTAA2AKG', 94 | src: '/logos/discord.svg', 95 | }, 96 | // { 97 | // name: 'Remix', 98 | // href: '/share/01JMFQ533G3TVC21E96RSAG4KF', 99 | // src: '/logos/remix.svg', 100 | // }, 101 | { 102 | name: 'Paper', 103 | href: '/share/01JMP2MVEWNE5CZYXW94JQME35', 104 | src: '/logos/paper.svg', 105 | }, 106 | ], 107 | ]; 108 | -------------------------------------------------------------------------------- /src/app/styles.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | @import './utilities.css'; 3 | 4 | @font-face { 5 | font-family: Matter; 6 | src: url('https://paper.design/matter.woff2'); 7 | } 8 | 9 | @theme { 10 | --*: initial; 11 | 12 | --breakpoint-sm: 600px; 13 | --breakpoint-md: 1200px; 14 | 15 | --spacing: 1px; 16 | --default-font-family: Matter, sans-serif; 17 | 18 | --color-black: #000; 19 | --color-white: #fff; 20 | --color-transparent: #0000; 21 | --color-currentcolor: currentcolor; 22 | --color-blue: oklch(74% 0.104 258); 23 | --color-button: oklch(61.32% 0.148 258); 24 | --color-focus: oklch(74% 26% 258); 25 | --color-selection: oklch(78% 35% 258 / 50%); 26 | 27 | --text-sm: 16px; 28 | --text-sm--line-height: 24px; 29 | --text-sm--letter-spacing: 0.01em; 30 | 31 | --text-base: 18px; 32 | --text-base--line-height: 28px; 33 | --text-base--letter-spacing: 0.01em; 34 | 35 | --font-weight-extralight: 150; 36 | --font-weight-light: 250; 37 | --font-weight-semilight: 320; 38 | --font-weight-normal: 400; 39 | --font-weight-caption: 430; 40 | --font-weight-medium: 480; 41 | --font-weight-semibold: 550; 42 | --font-weight-bold: 670; 43 | } 44 | 45 | @layer base { 46 | :root { 47 | -moz-osx-font-smoothing: grayscale; 48 | -webkit-font-smoothing: antialiased; 49 | 50 | background-color: #000; 51 | color: white; 52 | 53 | color-scheme: dark; 54 | 55 | font-size: var(--text-base); 56 | line-height: var(--text-base--line-height); 57 | letter-spacing: var(--text-base--letter-spacing); 58 | } 59 | 60 | ::selection { 61 | background-color: var(--color-selection); 62 | } 63 | 64 | a { 65 | text-underline-offset: 4px; 66 | text-decoration-thickness: 1px; 67 | } 68 | } 69 | 70 | body { 71 | min-width: 480px; 72 | } 73 | -------------------------------------------------------------------------------- /src/app/utilities.css: -------------------------------------------------------------------------------- 1 | @utility rounded-* { 2 | border-radius: calc(--value(integer) * 1px); 3 | } 4 | 5 | @utility rounded-* { 6 | border-radius: calc(--value(integer) * 1px); 7 | } 8 | 9 | @utility rounded-t-* { 10 | border-top-left-radius: calc(--value(integer) * 1px); 11 | border-top-right-radius: calc(--value(integer) * 1px); 12 | } 13 | 14 | @utility rounded-b-* { 15 | border-bottom-left-radius: calc(--value(integer) * 1px); 16 | border-bottom-right-radius: calc(--value(integer) * 1px); 17 | } 18 | 19 | @utility rounded-tl-* { 20 | border-top-left-radius: calc(--value(integer) * 1px); 21 | } 22 | 23 | @utility rounded-tr-* { 24 | border-top-right-radius: calc(--value(integer) * 1px); 25 | } 26 | 27 | @utility rounded-bl-* { 28 | border-bottom-left-radius: calc(--value(integer) * 1px); 29 | } 30 | 31 | @utility rounded-br-* { 32 | border-bottom-right-radius: calc(--value(integer) * 1px); 33 | } 34 | 35 | @utility tracking-* { 36 | letter-spacing: calc(--value(integer) * 0.0001em); 37 | } 38 | 39 | @utility scrollbar-none { 40 | scrollbar-width: none; 41 | 42 | &::-webkit-scrollbar { 43 | width: 0px; 44 | height: 0px; 45 | } 46 | } 47 | 48 | @utility scrollbar-thin { 49 | scrollbar-width: thin; 50 | } 51 | -------------------------------------------------------------------------------- /src/hero/canvas.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { liquidFragSource } from '@/app/hero/liquid-frag'; 4 | import { useEffect, useRef, useState } from 'react'; 5 | import { toast } from 'sonner'; 6 | 7 | // uniform sampler2D u_image_texture; 8 | // uniform float u_time; 9 | // uniform float u_ratio; 10 | // uniform float u_img_ratio; 11 | // uniform float u_patternScale; 12 | // uniform float u_refraction; 13 | // uniform float u_edge; 14 | // uniform float u_patternBlur; 15 | // uniform float u_liquid; 16 | 17 | const vertexShaderSource = `#version 300 es 18 | precision mediump float; 19 | 20 | in vec2 a_position; 21 | out vec2 vUv; 22 | 23 | void main() { 24 | vUv = .5 * (a_position + 1.); 25 | gl_Position = vec4(a_position, 0.0, 1.0); 26 | }` as const; 27 | 28 | export type ShaderParams = { 29 | patternScale: number; 30 | refraction: number; 31 | edge: number; 32 | patternBlur: number; 33 | liquid: number; 34 | speed: number; 35 | }; 36 | 37 | export function Canvas({ 38 | imageData, 39 | params, 40 | processing, 41 | }: { 42 | imageData: ImageData; 43 | params: ShaderParams; 44 | processing: boolean; 45 | }) { 46 | const canvasRef = useRef(null); 47 | const [gl, setGl] = useState(null); 48 | const [uniforms, setUniforms] = useState>({}); 49 | /** Keeps track of how long we've been playing, fed into u_time */ 50 | const totalAnimationTime = useRef(0); 51 | const lastRenderTime = useRef(0); 52 | 53 | function updateUniforms() { 54 | if (!gl || !uniforms) return; 55 | gl.uniform1f(uniforms.u_edge, params.edge); 56 | gl.uniform1f(uniforms.u_patternBlur, params.patternBlur); 57 | gl.uniform1f(uniforms.u_time, 0); 58 | gl.uniform1f(uniforms.u_patternScale, params.patternScale); 59 | gl.uniform1f(uniforms.u_refraction, params.refraction); 60 | gl.uniform1f(uniforms.u_liquid, params.liquid); 61 | } 62 | 63 | useEffect(() => { 64 | function initShader() { 65 | const canvas = canvasRef.current; 66 | const gl = canvas?.getContext('webgl2', { 67 | antialias: true, 68 | alpha: true, 69 | }); 70 | if (!canvas || !gl) { 71 | toast.error('Failed to initialize shader. Does your browser support WebGL2?'); 72 | return; 73 | } 74 | 75 | function createShader(gl: WebGL2RenderingContext, sourceCode: string, type: number) { 76 | const shader = gl.createShader(type); 77 | if (!shader) { 78 | toast.error('Failed to create shader'); 79 | return null; 80 | } 81 | 82 | gl.shaderSource(shader, sourceCode); 83 | gl.compileShader(shader); 84 | 85 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 86 | console.error('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader)); 87 | gl.deleteShader(shader); 88 | return null; 89 | } 90 | 91 | return shader; 92 | } 93 | 94 | const vertexShader = createShader(gl, vertexShaderSource, gl.VERTEX_SHADER); 95 | const fragmentShader = createShader(gl, liquidFragSource, gl.FRAGMENT_SHADER); 96 | const program = gl.createProgram(); 97 | if (!program || !vertexShader || !fragmentShader) { 98 | toast.error('Failed to create program or shaders'); 99 | return; 100 | } 101 | 102 | gl.attachShader(program, vertexShader); 103 | gl.attachShader(program, fragmentShader); 104 | gl.linkProgram(program); 105 | 106 | if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { 107 | console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(program)); 108 | return null; 109 | } 110 | 111 | function getUniforms(program: WebGLProgram, gl: WebGL2RenderingContext) { 112 | let uniforms: Record = {}; 113 | let uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); 114 | for (let i = 0; i < uniformCount; i++) { 115 | let uniformName = gl.getActiveUniform(program, i)?.name; 116 | if (!uniformName) continue; 117 | uniforms[uniformName] = gl.getUniformLocation(program, uniformName) as WebGLUniformLocation; 118 | } 119 | return uniforms; 120 | } 121 | const uniforms = getUniforms(program, gl); 122 | setUniforms(uniforms); 123 | 124 | // Vertex position 125 | const vertices = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]); 126 | const vertexBuffer = gl.createBuffer(); 127 | gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); 128 | gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); 129 | 130 | gl.useProgram(program); 131 | 132 | const positionLocation = gl.getAttribLocation(program, 'a_position'); 133 | gl.enableVertexAttribArray(positionLocation); 134 | 135 | gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); 136 | gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); 137 | 138 | setGl(gl); 139 | } 140 | 141 | initShader(); 142 | updateUniforms(); 143 | }, []); 144 | 145 | // Keep uniforms updated 146 | useEffect(() => { 147 | if (!gl || !uniforms) return; 148 | 149 | updateUniforms(); 150 | }, [gl, params, uniforms]); 151 | 152 | // Render every frame 153 | useEffect(() => { 154 | if (!gl || !uniforms) return; 155 | 156 | let renderId: number; 157 | 158 | function render(currentTime: number) { 159 | const deltaTime = currentTime - lastRenderTime.current; 160 | lastRenderTime.current = currentTime; 161 | 162 | // Update the total animation time and time uniform 163 | totalAnimationTime.current += deltaTime * params.speed; 164 | gl!.uniform1f(uniforms.u_time, totalAnimationTime.current); 165 | // Draw! 166 | gl!.drawArrays(gl!.TRIANGLE_STRIP, 0, 4); 167 | // rAF 168 | renderId = requestAnimationFrame(render); 169 | } 170 | 171 | // Kick off the render loop 172 | lastRenderTime.current = performance.now(); 173 | renderId = requestAnimationFrame(render); 174 | 175 | return () => { 176 | cancelAnimationFrame(renderId); 177 | }; 178 | }, [gl, params.speed]); 179 | 180 | useEffect(() => { 181 | const canvasEl = canvasRef.current; 182 | if (!canvasEl || !gl || !uniforms) return; 183 | 184 | function resizeCanvas() { 185 | if (!canvasEl || !gl || !uniforms) return; 186 | const imgRatio = imageData.width / imageData.height; 187 | gl.uniform1f(uniforms.u_img_ratio, imgRatio); 188 | 189 | const side = 1000; 190 | canvasEl.width = side * devicePixelRatio; 191 | canvasEl.height = side * devicePixelRatio; 192 | gl.viewport(0, 0, canvasEl.height, canvasEl.height); 193 | gl.uniform1f(uniforms.u_ratio, 1); 194 | gl.uniform1f(uniforms.u_img_ratio, imgRatio); 195 | } 196 | 197 | resizeCanvas(); 198 | window.addEventListener('resize', resizeCanvas); 199 | 200 | return () => { 201 | window.removeEventListener('resize', resizeCanvas); 202 | }; 203 | }, [gl, uniforms, imageData]); 204 | 205 | useEffect(() => { 206 | if (!gl || !uniforms) return; 207 | 208 | // Delete any existing texture first 209 | const existingTexture = gl.getParameter(gl.TEXTURE_BINDING_2D); 210 | if (existingTexture) { 211 | gl.deleteTexture(existingTexture); 212 | } 213 | 214 | const imageTexture = gl.createTexture(); 215 | gl.activeTexture(gl.TEXTURE0); 216 | gl.bindTexture(gl.TEXTURE_2D, imageTexture); 217 | 218 | // Set texture parameters before uploading the data 219 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 220 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 221 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 222 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 223 | 224 | // Ensure power-of-two dimensions or use appropriate texture parameters 225 | gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); 226 | 227 | try { 228 | gl.texImage2D( 229 | gl.TEXTURE_2D, 230 | 0, 231 | gl.RGBA, 232 | imageData.width, 233 | imageData.height, 234 | 0, 235 | gl.RGBA, 236 | gl.UNSIGNED_BYTE, 237 | imageData.data 238 | ); 239 | 240 | gl.uniform1i(uniforms.u_image_texture, 0); 241 | } catch (e) { 242 | console.error('Error uploading texture:', e); 243 | toast.error('Failed to upload image texture'); 244 | } 245 | 246 | return () => { 247 | // Cleanup texture when component unmounts or imageData changes 248 | if (imageTexture) { 249 | gl.deleteTexture(imageTexture); 250 | } 251 | }; 252 | }, [gl, uniforms, imageData]); 253 | 254 | return ; 255 | } 256 | -------------------------------------------------------------------------------- /src/hero/hero.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useCallback, useEffect, useRef, useState } from 'react'; 4 | import { defaultParams, params, type ShaderParams } from './params'; 5 | import { Canvas } from './canvas'; 6 | import { Slider } from 'radix-ui'; 7 | import { NumberInput } from '@/app/number-input'; 8 | import { roundOptimized } from '@/app/round-optimized'; 9 | import { usePathname, useRouter, useSearchParams } from 'next/navigation'; 10 | import { toast } from 'sonner'; 11 | import { parseLogoImage } from './parse-logo-image'; 12 | import { uploadImage } from '@/hero/upload-image'; 13 | import isEqual from 'lodash-es/isEqual'; 14 | 15 | interface HeroProps { 16 | imageId: string; 17 | } 18 | 19 | type State = ShaderParams & { 20 | background: string; 21 | }; 22 | 23 | const defaultState = { ...defaultParams, background: 'metal' }; 24 | 25 | export function Hero({ imageId }: HeroProps) { 26 | const [state, setState] = useState(defaultState); 27 | const [dragging, setDragging] = useState(false); 28 | const pathname = usePathname(); 29 | const searchParams = useSearchParams(); 30 | 31 | const searchParamsPendingUpdate = useRef(false); 32 | 33 | const stateRef = useRef(state); 34 | 35 | const [imageData, setImageData] = useState(null); 36 | const [processing, setProcessing] = useState(true); 37 | 38 | // Check URL for image ID on mount 39 | useEffect(() => { 40 | setProcessing(true); 41 | 42 | async function updateImageData() { 43 | try { 44 | const res = await fetch(`https://p1ljtcp1ptfohfxm.public.blob.vercel-storage.com/${imageId}.png`); 45 | const blob = await res.blob(); 46 | const bitmap = await createImageBitmap(blob); 47 | 48 | // Create a temporary canvas to turn the image back into imageData for the shader 49 | const canvas = document.createElement('canvas'); 50 | canvas.width = bitmap.width; 51 | canvas.height = bitmap.height; 52 | const ctx = canvas.getContext('2d')!; 53 | ctx.drawImage(bitmap, 0, 0); 54 | const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 55 | setImageData(imageData); 56 | } catch (error) { 57 | console.error(error); 58 | } 59 | 60 | setProcessing(false); 61 | } 62 | 63 | updateImageData(); 64 | }, [imageId]); 65 | 66 | useEffect(() => { 67 | stateRef.current = state; 68 | 69 | // Debounce the history updates (Safari doesn't like frequent URL updates) 70 | const timeoutId = setTimeout(() => { 71 | const searchParams = new URLSearchParams(); 72 | 73 | Object.entries(stateRef.current).forEach(([key, value]) => { 74 | if (typeof value === 'number') { 75 | searchParams.set(key, roundOptimized(value, 4).toString()); 76 | } else { 77 | searchParams.set(key, value); 78 | } 79 | }); 80 | 81 | searchParamsPendingUpdate.current = false; 82 | window.history.replaceState({}, '', pathname + '?' + searchParams.toString()); 83 | }, 250); // Add 250ms delay between updates 84 | 85 | return () => clearTimeout(timeoutId); 86 | }, [state, pathname]); 87 | 88 | useEffect(() => { 89 | if (searchParamsPendingUpdate.current) { 90 | return; 91 | } 92 | 93 | const paramsState: any = {}; 94 | let isEqual = true; 95 | 96 | for (const [key, value] of searchParams.entries()) { 97 | if (!(key in state)) { 98 | continue; 99 | } 100 | 101 | const number = parseFloat(value); 102 | paramsState[key] = Number.isNaN(number) ? value : number; 103 | 104 | // @ts-ignore 105 | let currentValue = stateRef.current[key]; 106 | // Match the precision of the params 107 | if (typeof currentValue === 'number') { 108 | currentValue = roundOptimized(currentValue, 4); 109 | } 110 | 111 | if (paramsState[key] !== currentValue) { 112 | isEqual = false; 113 | } 114 | } 115 | 116 | if (isEqual === false) { 117 | console.log('Updating state from URL params'); 118 | setState((state) => ({ ...state, ...paramsState })); 119 | } 120 | }, [searchParams]); 121 | 122 | const handleFileInput = useCallback((event: React.ChangeEvent) => { 123 | const files = event.target.files; 124 | if (files) { 125 | handleFiles(files); 126 | } 127 | }, []); 128 | 129 | const handleFiles = async (files: FileList) => { 130 | if (files.length > 0) { 131 | const file = files[0]; 132 | const fileType = file.type; 133 | 134 | // Check file size (4.5MB = 4.5 * 1024 * 1024 bytes) 135 | const maxSize = 4.5 * 1024 * 1024; 136 | if (file.size > maxSize) { 137 | toast.error('File size must be less than 4.5MB'); 138 | return; 139 | } 140 | 141 | // Check if file is an image or SVG 142 | if (fileType.startsWith('image/') || fileType === 'image/svg+xml') { 143 | setProcessing(true); 144 | parseLogoImage(file).then(({ imageData, pngBlob }) => { 145 | // Set the image data for the shader to pick up 146 | setImageData(imageData); 147 | setProcessing(false); 148 | 149 | // Upload the image 150 | uploadImage(pngBlob) 151 | .then((imageId) => { 152 | // Update the URL for sharing 153 | if (typeof window !== 'undefined' && typeof imageId === 'string' && imageId.length > 0) { 154 | // const currentParams = searchParams.values.length ? '?' + searchParams.toString() : ''; 155 | window.history.pushState({}, '', `/share/${imageId}`); 156 | } 157 | }) 158 | .catch(console.error) 159 | .finally(() => setProcessing(false)); 160 | }); 161 | } else { 162 | toast.error('Please upload only images or SVG files'); 163 | } 164 | } 165 | }; 166 | 167 | return ( 168 |
{ 171 | event.preventDefault(); 172 | event.stopPropagation(); 173 | setDragging(true); 174 | }} 175 | onDragLeave={(event) => { 176 | event.preventDefault(); 177 | event.stopPropagation(); 178 | setDragging(false); 179 | }} 180 | onDragOver={(event) => { 181 | event.preventDefault(); 182 | event.stopPropagation(); 183 | }} 184 | onDrop={(event) => { 185 | event.preventDefault(); 186 | event.stopPropagation(); 187 | setDragging(false); 188 | const files = event.dataTransfer.files; 189 | handleFiles(files); 190 | }} 191 | > 192 |
{ 196 | switch (state.background) { 197 | case 'metal': 198 | return 'linear-gradient(to bottom, #eee, #b8b8b8)'; 199 | } 200 | return state.background; 201 | })(), 202 | }} 203 | > 204 |
205 | {imageData && } 206 |
207 |
208 | 209 |
210 |
211 | 212 |
213 |
214 | 221 | 222 | 228 | 229 | 235 | 236 | 264 |
265 | 266 | setState((state) => ({ ...state, refraction: value }))} 275 | /> 276 | setState((state) => ({ ...state, edge: value }))} 283 | /> 284 | setState((state) => ({ ...state, patternBlur: value }))} 291 | /> 292 | setState((state) => ({ ...state, liquid: value }))} 299 | /> 300 | setState((state) => ({ ...state, speed: value }))} 307 | /> 308 | (value === '0' || value === '10' ? value : parseFloat(value).toFixed(1))} 315 | onValueChange={(value) => setState((state) => ({ ...state, patternScale: value }))} 316 | /> 317 | 318 |
319 | 326 |

327 | Tips: transparent or white background is required. Shapes work better than words. Use an SVG or a 328 | high-resolution image. 329 |

330 |
331 |
332 |
333 | ); 334 | } 335 | 336 | interface ControlProps { 337 | label: string; 338 | min: number; 339 | max: number; 340 | step: number; 341 | value: number; 342 | format?: (value: string) => string; 343 | onValueChange: (value: number) => void; 344 | } 345 | 346 | function Control({ label, min, max, step, format, value, onValueChange }: ControlProps) { 347 | return ( 348 | <> 349 |
350 | 353 |
354 |
355 | onValueChange(value)}> 356 | 357 | 358 | 359 | 364 | 365 | 366 |
367 |
368 | onValueChange(parseFloat(value))} 377 | /> 378 |
379 | 380 | ); 381 | } 382 | -------------------------------------------------------------------------------- /src/hero/params.ts: -------------------------------------------------------------------------------- 1 | export type ShaderParams = { 2 | patternScale: number; 3 | refraction: number; 4 | edge: number; 5 | patternBlur: number; 6 | liquid: number; 7 | speed: number; 8 | }; 9 | 10 | export const params = { 11 | refraction: { 12 | min: 0, 13 | max: 0.06, 14 | step: 0.001, 15 | default: 0.015, 16 | }, 17 | edge: { 18 | min: 0, 19 | max: 1, 20 | step: 0.01, 21 | default: 0.4, 22 | }, 23 | patternBlur: { 24 | min: 0, 25 | max: 0.05, 26 | step: 0.001, 27 | default: 0.005, 28 | }, 29 | liquid: { 30 | min: 0, 31 | max: 1, 32 | step: 0.01, 33 | default: 0.07, 34 | }, 35 | speed: { 36 | min: 0, 37 | max: 1, 38 | step: 0.01, 39 | default: 0.3, 40 | }, 41 | patternScale: { 42 | min: 1, 43 | max: 10, 44 | step: 0.1, 45 | default: 2, 46 | }, 47 | }; 48 | 49 | /** The default params for the shader in a ShaderParams object */ 50 | export const defaultParams: ShaderParams = Object.fromEntries( 51 | Object.entries(params).map(([key, value]) => [key, value.default]) 52 | ) as ShaderParams; 53 | -------------------------------------------------------------------------------- /src/hero/parse-logo-image.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | /** Cleans up the input image by turning it into a black and white mask with a beveled edge */ 4 | 5 | export function parseLogoImage(file: File): Promise<{ imageData: ImageData; pngBlob: Blob }> { 6 | const canvas = document.createElement('canvas'); 7 | const ctx = canvas.getContext('2d'); 8 | 9 | return new Promise((resolve, reject) => { 10 | if (!file || !ctx) { 11 | reject(new Error('Invalid file or context')); 12 | return; 13 | } 14 | 15 | const img = new Image(); 16 | img.onload = function () { 17 | // Force SVG to load at a high fidelity size if it's an SVG 18 | if (file.type === 'image/svg+xml') { 19 | img.width = 1000; // or whatever base size you prefer 20 | img.height = 1000; 21 | } 22 | 23 | const MAX_SIZE = 1000; 24 | const MIN_SIZE = 500; 25 | let width = img.naturalWidth; 26 | let height = img.naturalHeight; 27 | 28 | // Calculate new dimensions if image is too large or too small 29 | if (width > MAX_SIZE || height > MAX_SIZE || width < MIN_SIZE || height < MIN_SIZE) { 30 | if (width > height) { 31 | if (width > MAX_SIZE) { 32 | height = Math.round((height * MAX_SIZE) / width); 33 | width = MAX_SIZE; 34 | } else if (width < MIN_SIZE) { 35 | height = Math.round((height * MIN_SIZE) / width); 36 | width = MIN_SIZE; 37 | } 38 | } else { 39 | if (height > MAX_SIZE) { 40 | width = Math.round((width * MAX_SIZE) / height); 41 | height = MAX_SIZE; 42 | } else if (height < MIN_SIZE) { 43 | width = Math.round((width * MIN_SIZE) / height); 44 | height = MIN_SIZE; 45 | } 46 | } 47 | } 48 | 49 | canvas.width = width; 50 | canvas.height = height; 51 | 52 | // Draw the user image on an offscreen canvas. 53 | const shapeCanvas = document.createElement('canvas'); 54 | shapeCanvas.width = width; 55 | shapeCanvas.height = height; 56 | const shapeCtx = shapeCanvas.getContext('2d')!; 57 | shapeCtx.drawImage(img, 0, 0, width, height); 58 | 59 | // 1) Build the inside/outside mask: 60 | // Non-shape pixels: pure white (255,255,255,255) or fully transparent. 61 | // Everything else is part of a shape. 62 | const shapeImageData = shapeCtx.getImageData(0, 0, width, height); 63 | const data = shapeImageData.data; 64 | const shapeMask = new Array(width * height).fill(false); 65 | for (var y = 0; y < height; y++) { 66 | for (var x = 0; x < width; x++) { 67 | var idx4 = (y * width + x) * 4; 68 | var r = data[idx4]; 69 | var g = data[idx4 + 1]; 70 | var b = data[idx4 + 2]; 71 | var a = data[idx4 + 3]; 72 | if ((r === 255 && g === 255 && b === 255 && a === 255) || a === 0) { 73 | shapeMask[y * width + x] = false; 74 | } else { 75 | shapeMask[y * width + x] = true; 76 | } 77 | } 78 | } 79 | 80 | function inside(x: number, y: number) { 81 | if (x < 0 || x >= width || y < 0 || y >= height) return false; 82 | return shapeMask[y * width + x]; 83 | } 84 | 85 | // 2) Identify boundary (pixels that have at least one non-shape neighbor) 86 | var boundaryMask = new Array(width * height).fill(false); 87 | for (var y = 0; y < height; y++) { 88 | for (var x = 0; x < width; x++) { 89 | var idx = y * width + x; 90 | if (!shapeMask[idx]) continue; 91 | var isBoundary = false; 92 | for (var ny = y - 1; ny <= y + 1 && !isBoundary; ny++) { 93 | for (var nx = x - 1; nx <= x + 1 && !isBoundary; nx++) { 94 | if (!inside(nx, ny)) { 95 | isBoundary = true; 96 | } 97 | } 98 | } 99 | if (isBoundary) { 100 | boundaryMask[idx] = true; 101 | } 102 | } 103 | } 104 | 105 | // 3) Poisson solve: Δu = -C (i.e. u_xx + u_yy = C), with u=0 at the boundary. 106 | var u = new Float32Array(width * height).fill(0); 107 | var newU = new Float32Array(width * height).fill(0); 108 | var C = 0.01; 109 | var ITERATIONS = 300; 110 | 111 | function getU(x: number, y: number, arr: Float32Array) { 112 | if (x < 0 || x >= width || y < 0 || y >= height) return 0; 113 | if (!shapeMask[y * width + x]) return 0; 114 | return arr[y * width + x]; 115 | } 116 | 117 | for (var iter = 0; iter < ITERATIONS; iter++) { 118 | for (var y = 0; y < height; y++) { 119 | for (var x = 0; x < width; x++) { 120 | var idx = y * width + x; 121 | if (!shapeMask[idx] || boundaryMask[idx]) { 122 | newU[idx] = 0; 123 | continue; 124 | } 125 | var sumN = getU(x + 1, y, u) + getU(x - 1, y, u) + getU(x, y + 1, u) + getU(x, y - 1, u); 126 | newU[idx] = (C + sumN) / 4; 127 | } 128 | } 129 | // Swap u with newU 130 | for (var i = 0; i < width * height; i++) { 131 | u[i] = newU[i]; 132 | } 133 | } 134 | 135 | // 4) Normalize the solution and apply a nonlinear remap. 136 | var maxVal = 0; 137 | for (var i = 0; i < width * height; i++) { 138 | if (u[i] > maxVal) maxVal = u[i]; 139 | } 140 | const alpha = 2.0; // Adjust for contrast. 141 | const outImg = ctx.createImageData(width, height); 142 | 143 | for (var y = 0; y < height; y++) { 144 | for (var x = 0; x < width; x++) { 145 | var idx = y * width + x; 146 | var px = idx * 4; 147 | if (!shapeMask[idx]) { 148 | outImg.data[px] = 255; 149 | outImg.data[px + 1] = 255; 150 | outImg.data[px + 2] = 255; 151 | outImg.data[px + 3] = 255; 152 | } else { 153 | const raw = u[idx] / maxVal; 154 | const remapped = Math.pow(raw, alpha); 155 | const gray = 255 * (1 - remapped); 156 | outImg.data[px] = gray; 157 | outImg.data[px + 1] = gray; 158 | outImg.data[px + 2] = gray; 159 | outImg.data[px + 3] = 255; 160 | } 161 | } 162 | } 163 | ctx.putImageData(outImg, 0, 0); 164 | canvas.toBlob((blob) => { 165 | if (!blob) { 166 | reject(new Error('Failed to create PNG blob')); 167 | return; 168 | } 169 | resolve({ 170 | imageData: outImg, 171 | pngBlob: blob, 172 | }); 173 | }, 'image/png'); 174 | }; 175 | 176 | img.onerror = () => reject(new Error('Failed to load image')); 177 | img.src = URL.createObjectURL(file); 178 | }); 179 | } 180 | -------------------------------------------------------------------------------- /src/hero/upload-image.ts: -------------------------------------------------------------------------------- 1 | import { toast } from 'sonner'; 2 | 3 | export async function uploadImage(pngBlob: Blob): Promise { 4 | try { 5 | const response = await fetch('/api/user-logo', { 6 | method: 'POST', 7 | headers: { 8 | 'Content-Type': 'image/png', 9 | }, 10 | body: pngBlob, 11 | }); 12 | 13 | if (!response.ok) { 14 | throw new Error(`Upload failed: ${response.status} ${response.statusText}`); 15 | } 16 | 17 | const { imageId } = (await response.json()) as { imageId: string }; 18 | 19 | window.history.pushState({}, '', `/${imageId}`); 20 | return imageId; 21 | } catch (error) { 22 | console.error('Error uploading image:', error); 23 | toast.error('Error uploading image, please try again.'); 24 | throw error; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | export default { 4 | content: [ 5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | colors: { 12 | background: "var(--background)", 13 | foreground: "var(--foreground)", 14 | }, 15 | }, 16 | }, 17 | plugins: [], 18 | } satisfies Config; 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "verbatimModuleSyntax": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./src/*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | --------------------------------------------------------------------------------