├── .gitignore ├── LICENSE ├── README.md ├── next-env.d.ts ├── next.config.ts ├── package.json ├── pages ├── _app.tsx ├── api │ └── avatar │ │ └── [name].tsx └── index.tsx ├── pnpm-lock.yaml ├── public └── favicon.ico ├── styles ├── Home.module.css └── globals.css ├── tsconfig.json └── utils └── gradient.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | test.html 4 | 5 | .next 6 | .DS_Store 7 | .vercel -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Tobias Lins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # avatar ![](https://avatar.vercel.sh/rauchg?size=20) ![](https://avatar.vercel.sh/leerob?size=20) ![](https://avatar.vercel.sh/vercel?size=20) ![](https://avatar.vercel.sh/party?size=20) ![](https://avatar.vercel.sh/edge?size=20) 2 | 3 | Generate beautiful gradient avatars for your application. 4 | 5 | ## Usage 6 | 7 | Provide a username to generate an avatar. Each name will generate a unique `avatar`. Just replace `rauchg` with an `username` or `email`: 8 | 9 | ``` 10 | https://avatar.vercel.sh/rauchg 11 | ``` 12 | 13 | You will receive a `png` image with a size of 120\*120px 14 | 15 | ![Avatar for rauchg](https://avatar.vercel.sh/rauchg) 16 | 17 | ### Adjust Roundness 18 | 19 | ``` 20 | https://avatar.vercel.sh/rauchg?rounded=60 21 | ``` 22 | 23 | ![Avatar for rauchg](https://avatar.vercel.sh/rauchg?rounded=60) 24 | 25 | ### Custom Size 26 | 27 | ``` 28 | https://avatar.vercel.sh/rauchg?size=30 29 | ``` 30 | 31 | ![Avatar for rauchg](https://avatar.vercel.sh/rauchg?size=30) 32 | 33 | ### SVG 34 | 35 | Add the extension `.svg`: 36 | 37 | ``` 38 | https://avatar.vercel.sh/rauchg.svg 39 | ``` 40 | 41 | ### Add Initials 42 | 43 | Add the `text` parameter (requires SVG): 44 | 45 | ``` 46 | https://avatar.vercel.sh/rauchg.svg?text=GR 47 | ``` 48 | 49 | ![Avatar for rauchg](https://avatar.vercel.sh/rauchg.svg?text=GR) 50 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from 'next' 2 | 3 | const nextConfig: NextConfig = { 4 | reactStrictMode: true, 5 | async rewrites() { 6 | return [ 7 | { 8 | source: '/:path*', 9 | destination: '/api/avatar/:path*', 10 | }, 11 | ] 12 | }, 13 | } 14 | 15 | export default nextConfig 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "avatar", 3 | "version": "0.1.0", 4 | "private": true, 5 | "packageManager": "pnpm@9.10.0+sha512.73a29afa36a0d092ece5271de5177ecbf8318d454ecd701343131b8ebc0c1a91c487da46ab77c8e596d6acf1461e3594ced4becedf8921b074fbd8653ed7051c", 6 | "scripts": { 7 | "dev": "next dev --turbopack", 8 | "build": "next build", 9 | "start": "next start", 10 | "lint": "next lint" 11 | }, 12 | "dependencies": { 13 | "@types/node": "22.10.5", 14 | "@types/react": "19.0.2", 15 | "@types/react-dom": "19.0.2", 16 | "next": "15.2.4", 17 | "react": "19.0.0", 18 | "react-dom": "19.0.0", 19 | "tinycolor2": "^1.6.0", 20 | "typescript": "5.7.2" 21 | }, 22 | "devDependencies": { 23 | "@types/tinycolor2": "^1.4.6" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | import type { AppProps } from 'next/app' 3 | 4 | export default function App({ Component, pageProps }: AppProps) { 5 | return 6 | } 7 | -------------------------------------------------------------------------------- /pages/api/avatar/[name].tsx: -------------------------------------------------------------------------------- 1 | import { ImageResponse } from 'next/og' 2 | import type { NextRequest } from 'next/server' 3 | import { renderToReadableStream } from 'react-dom/server' 4 | import { generateGradient } from '../../../utils/gradient' 5 | 6 | export const runtime = 'edge' 7 | 8 | export default async function handler(req: NextRequest) { 9 | const searchParams = req.nextUrl.searchParams 10 | const name = searchParams.get('name') 11 | const text = searchParams.get('text') 12 | const size = Number(searchParams.get('size') || '120') 13 | const rounded = Number(searchParams.get('rounded') || '0') 14 | 15 | const [username, type] = name?.split('.') || [] 16 | const fileType = type?.includes('svg') ? 'svg' : 'png' 17 | 18 | const gradient = await generateGradient(username || `${Math.random()}`) 19 | 20 | const avatar = ( 21 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 44 | {fileType === 'svg' && !!text ? ( 45 | 55 | {text} 56 | 57 | ) : null} 58 | 59 | 60 | ) 61 | 62 | if (fileType === 'svg') { 63 | const stream = await renderToReadableStream(avatar) 64 | return new Response(stream, { 65 | headers: { 66 | 'Content-Type': 'image/svg+xml', 67 | 'Cache-Control': 'public, max-age=604800, immutable', 68 | }, 69 | }) 70 | } 71 | 72 | return new ImageResponse(avatar, { 73 | width: size, 74 | height: size, 75 | }) 76 | } 77 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import Image from 'next/image'; 3 | import styles from '../styles/Home.module.css'; 4 | 5 | export default function Home() { 6 | return ( 7 |
8 | 9 | Vercel Avatar 10 | 11 | 12 | 13 | 14 |
15 |

Avatar

16 | 17 |
18 | Vercel 23 | ID 42 24 | Satori 25 | Next.js 26 |
27 |
28 | 29 | 41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | '@types/node': 12 | specifier: 22.10.5 13 | version: 22.10.5 14 | '@types/react': 15 | specifier: 19.0.2 16 | version: 19.0.2 17 | '@types/react-dom': 18 | specifier: 19.0.2 19 | version: 19.0.2(@types/react@19.0.2) 20 | next: 21 | specifier: 15.2.4 22 | version: 15.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) 23 | react: 24 | specifier: 19.0.0 25 | version: 19.0.0 26 | react-dom: 27 | specifier: 19.0.0 28 | version: 19.0.0(react@19.0.0) 29 | tinycolor2: 30 | specifier: ^1.6.0 31 | version: 1.6.0 32 | typescript: 33 | specifier: 5.7.2 34 | version: 5.7.2 35 | devDependencies: 36 | '@types/tinycolor2': 37 | specifier: ^1.4.6 38 | version: 1.4.6 39 | 40 | packages: 41 | 42 | '@emnapi/runtime@1.4.0': 43 | resolution: {integrity: sha512-64WYIf4UYcdLnbKn/umDlNjQDSS8AgZrI/R9+x5ilkUVFxXcA1Ebl+gQLc/6mERA4407Xof0R7wEyEuj091CVw==} 44 | 45 | '@img/sharp-darwin-arm64@0.33.5': 46 | resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} 47 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 48 | cpu: [arm64] 49 | os: [darwin] 50 | 51 | '@img/sharp-darwin-x64@0.33.5': 52 | resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} 53 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 54 | cpu: [x64] 55 | os: [darwin] 56 | 57 | '@img/sharp-libvips-darwin-arm64@1.0.4': 58 | resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} 59 | cpu: [arm64] 60 | os: [darwin] 61 | 62 | '@img/sharp-libvips-darwin-x64@1.0.4': 63 | resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} 64 | cpu: [x64] 65 | os: [darwin] 66 | 67 | '@img/sharp-libvips-linux-arm64@1.0.4': 68 | resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} 69 | cpu: [arm64] 70 | os: [linux] 71 | 72 | '@img/sharp-libvips-linux-arm@1.0.5': 73 | resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} 74 | cpu: [arm] 75 | os: [linux] 76 | 77 | '@img/sharp-libvips-linux-s390x@1.0.4': 78 | resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} 79 | cpu: [s390x] 80 | os: [linux] 81 | 82 | '@img/sharp-libvips-linux-x64@1.0.4': 83 | resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} 84 | cpu: [x64] 85 | os: [linux] 86 | 87 | '@img/sharp-libvips-linuxmusl-arm64@1.0.4': 88 | resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} 89 | cpu: [arm64] 90 | os: [linux] 91 | 92 | '@img/sharp-libvips-linuxmusl-x64@1.0.4': 93 | resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} 94 | cpu: [x64] 95 | os: [linux] 96 | 97 | '@img/sharp-linux-arm64@0.33.5': 98 | resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} 99 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 100 | cpu: [arm64] 101 | os: [linux] 102 | 103 | '@img/sharp-linux-arm@0.33.5': 104 | resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} 105 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 106 | cpu: [arm] 107 | os: [linux] 108 | 109 | '@img/sharp-linux-s390x@0.33.5': 110 | resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} 111 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 112 | cpu: [s390x] 113 | os: [linux] 114 | 115 | '@img/sharp-linux-x64@0.33.5': 116 | resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} 117 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 118 | cpu: [x64] 119 | os: [linux] 120 | 121 | '@img/sharp-linuxmusl-arm64@0.33.5': 122 | resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} 123 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 124 | cpu: [arm64] 125 | os: [linux] 126 | 127 | '@img/sharp-linuxmusl-x64@0.33.5': 128 | resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} 129 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 130 | cpu: [x64] 131 | os: [linux] 132 | 133 | '@img/sharp-wasm32@0.33.5': 134 | resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} 135 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 136 | cpu: [wasm32] 137 | 138 | '@img/sharp-win32-ia32@0.33.5': 139 | resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} 140 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 141 | cpu: [ia32] 142 | os: [win32] 143 | 144 | '@img/sharp-win32-x64@0.33.5': 145 | resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} 146 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 147 | cpu: [x64] 148 | os: [win32] 149 | 150 | '@next/env@15.2.4': 151 | resolution: {integrity: sha512-+SFtMgoiYP3WoSswuNmxJOCwi06TdWE733D+WPjpXIe4LXGULwEaofiiAy6kbS0+XjM5xF5n3lKuBwN2SnqD9g==} 152 | 153 | '@next/swc-darwin-arm64@15.2.4': 154 | resolution: {integrity: sha512-1AnMfs655ipJEDC/FHkSr0r3lXBgpqKo4K1kiwfUf3iE68rDFXZ1TtHdMvf7D0hMItgDZ7Vuq3JgNMbt/+3bYw==} 155 | engines: {node: '>= 10'} 156 | cpu: [arm64] 157 | os: [darwin] 158 | 159 | '@next/swc-darwin-x64@15.2.4': 160 | resolution: {integrity: sha512-3qK2zb5EwCwxnO2HeO+TRqCubeI/NgCe+kL5dTJlPldV/uwCnUgC7VbEzgmxbfrkbjehL4H9BPztWOEtsoMwew==} 161 | engines: {node: '>= 10'} 162 | cpu: [x64] 163 | os: [darwin] 164 | 165 | '@next/swc-linux-arm64-gnu@15.2.4': 166 | resolution: {integrity: sha512-HFN6GKUcrTWvem8AZN7tT95zPb0GUGv9v0d0iyuTb303vbXkkbHDp/DxufB04jNVD+IN9yHy7y/6Mqq0h0YVaQ==} 167 | engines: {node: '>= 10'} 168 | cpu: [arm64] 169 | os: [linux] 170 | 171 | '@next/swc-linux-arm64-musl@15.2.4': 172 | resolution: {integrity: sha512-Oioa0SORWLwi35/kVB8aCk5Uq+5/ZIumMK1kJV+jSdazFm2NzPDztsefzdmzzpx5oGCJ6FkUC7vkaUseNTStNA==} 173 | engines: {node: '>= 10'} 174 | cpu: [arm64] 175 | os: [linux] 176 | 177 | '@next/swc-linux-x64-gnu@15.2.4': 178 | resolution: {integrity: sha512-yb5WTRaHdkgOqFOZiu6rHV1fAEK0flVpaIN2HB6kxHVSy/dIajWbThS7qON3W9/SNOH2JWkVCyulgGYekMePuw==} 179 | engines: {node: '>= 10'} 180 | cpu: [x64] 181 | os: [linux] 182 | 183 | '@next/swc-linux-x64-musl@15.2.4': 184 | resolution: {integrity: sha512-Dcdv/ix6srhkM25fgXiyOieFUkz+fOYkHlydWCtB0xMST6X9XYI3yPDKBZt1xuhOytONsIFJFB08xXYsxUwJLw==} 185 | engines: {node: '>= 10'} 186 | cpu: [x64] 187 | os: [linux] 188 | 189 | '@next/swc-win32-arm64-msvc@15.2.4': 190 | resolution: {integrity: sha512-dW0i7eukvDxtIhCYkMrZNQfNicPDExt2jPb9AZPpL7cfyUo7QSNl1DjsHjmmKp6qNAqUESyT8YFl/Aw91cNJJg==} 191 | engines: {node: '>= 10'} 192 | cpu: [arm64] 193 | os: [win32] 194 | 195 | '@next/swc-win32-x64-msvc@15.2.4': 196 | resolution: {integrity: sha512-SbnWkJmkS7Xl3kre8SdMF6F/XDh1DTFEhp0jRTj/uB8iPKoU2bb2NDfcu+iifv1+mxQEd1g2vvSxcZbXSKyWiQ==} 197 | engines: {node: '>= 10'} 198 | cpu: [x64] 199 | os: [win32] 200 | 201 | '@swc/counter@0.1.3': 202 | resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} 203 | 204 | '@swc/helpers@0.5.15': 205 | resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} 206 | 207 | '@types/node@22.10.5': 208 | resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} 209 | 210 | '@types/react-dom@19.0.2': 211 | resolution: {integrity: sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg==} 212 | peerDependencies: 213 | '@types/react': ^19.0.0 214 | 215 | '@types/react@19.0.2': 216 | resolution: {integrity: sha512-USU8ZI/xyKJwFTpjSVIrSeHBVAGagkHQKPNbxeWwql/vDmnTIBgx+TJnhFnj1NXgz8XfprU0egV2dROLGpsBEg==} 217 | 218 | '@types/tinycolor2@1.4.6': 219 | resolution: {integrity: sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==} 220 | 221 | busboy@1.6.0: 222 | resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} 223 | engines: {node: '>=10.16.0'} 224 | 225 | caniuse-lite@1.0.30001707: 226 | resolution: {integrity: sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==} 227 | 228 | client-only@0.0.1: 229 | resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} 230 | 231 | color-convert@2.0.1: 232 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 233 | engines: {node: '>=7.0.0'} 234 | 235 | color-name@1.1.4: 236 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 237 | 238 | color-string@1.9.1: 239 | resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} 240 | 241 | color@4.2.3: 242 | resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} 243 | engines: {node: '>=12.5.0'} 244 | 245 | csstype@3.1.3: 246 | resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 247 | 248 | detect-libc@2.0.3: 249 | resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} 250 | engines: {node: '>=8'} 251 | 252 | is-arrayish@0.3.2: 253 | resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} 254 | 255 | nanoid@3.3.11: 256 | resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} 257 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 258 | hasBin: true 259 | 260 | next@15.2.4: 261 | resolution: {integrity: sha512-VwL+LAaPSxEkd3lU2xWbgEOtrM8oedmyhBqaVNmgKB+GvZlCy9rgaEc+y2on0wv+l0oSFqLtYD6dcC1eAedUaQ==} 262 | engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} 263 | hasBin: true 264 | peerDependencies: 265 | '@opentelemetry/api': ^1.1.0 266 | '@playwright/test': ^1.41.2 267 | babel-plugin-react-compiler: '*' 268 | react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 269 | react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 270 | sass: ^1.3.0 271 | peerDependenciesMeta: 272 | '@opentelemetry/api': 273 | optional: true 274 | '@playwright/test': 275 | optional: true 276 | babel-plugin-react-compiler: 277 | optional: true 278 | sass: 279 | optional: true 280 | 281 | picocolors@1.1.1: 282 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 283 | 284 | postcss@8.4.31: 285 | resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} 286 | engines: {node: ^10 || ^12 || >=14} 287 | 288 | react-dom@19.0.0: 289 | resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} 290 | peerDependencies: 291 | react: ^19.0.0 292 | 293 | react@19.0.0: 294 | resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} 295 | engines: {node: '>=0.10.0'} 296 | 297 | scheduler@0.25.0: 298 | resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} 299 | 300 | semver@7.7.1: 301 | resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} 302 | engines: {node: '>=10'} 303 | hasBin: true 304 | 305 | sharp@0.33.5: 306 | resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} 307 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 308 | 309 | simple-swizzle@0.2.2: 310 | resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} 311 | 312 | source-map-js@1.2.1: 313 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 314 | engines: {node: '>=0.10.0'} 315 | 316 | streamsearch@1.1.0: 317 | resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} 318 | engines: {node: '>=10.0.0'} 319 | 320 | styled-jsx@5.1.6: 321 | resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} 322 | engines: {node: '>= 12.0.0'} 323 | peerDependencies: 324 | '@babel/core': '*' 325 | babel-plugin-macros: '*' 326 | react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' 327 | peerDependenciesMeta: 328 | '@babel/core': 329 | optional: true 330 | babel-plugin-macros: 331 | optional: true 332 | 333 | tinycolor2@1.6.0: 334 | resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} 335 | 336 | tslib@2.8.1: 337 | resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 338 | 339 | typescript@5.7.2: 340 | resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} 341 | engines: {node: '>=14.17'} 342 | hasBin: true 343 | 344 | undici-types@6.20.0: 345 | resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} 346 | 347 | snapshots: 348 | 349 | '@emnapi/runtime@1.4.0': 350 | dependencies: 351 | tslib: 2.8.1 352 | optional: true 353 | 354 | '@img/sharp-darwin-arm64@0.33.5': 355 | optionalDependencies: 356 | '@img/sharp-libvips-darwin-arm64': 1.0.4 357 | optional: true 358 | 359 | '@img/sharp-darwin-x64@0.33.5': 360 | optionalDependencies: 361 | '@img/sharp-libvips-darwin-x64': 1.0.4 362 | optional: true 363 | 364 | '@img/sharp-libvips-darwin-arm64@1.0.4': 365 | optional: true 366 | 367 | '@img/sharp-libvips-darwin-x64@1.0.4': 368 | optional: true 369 | 370 | '@img/sharp-libvips-linux-arm64@1.0.4': 371 | optional: true 372 | 373 | '@img/sharp-libvips-linux-arm@1.0.5': 374 | optional: true 375 | 376 | '@img/sharp-libvips-linux-s390x@1.0.4': 377 | optional: true 378 | 379 | '@img/sharp-libvips-linux-x64@1.0.4': 380 | optional: true 381 | 382 | '@img/sharp-libvips-linuxmusl-arm64@1.0.4': 383 | optional: true 384 | 385 | '@img/sharp-libvips-linuxmusl-x64@1.0.4': 386 | optional: true 387 | 388 | '@img/sharp-linux-arm64@0.33.5': 389 | optionalDependencies: 390 | '@img/sharp-libvips-linux-arm64': 1.0.4 391 | optional: true 392 | 393 | '@img/sharp-linux-arm@0.33.5': 394 | optionalDependencies: 395 | '@img/sharp-libvips-linux-arm': 1.0.5 396 | optional: true 397 | 398 | '@img/sharp-linux-s390x@0.33.5': 399 | optionalDependencies: 400 | '@img/sharp-libvips-linux-s390x': 1.0.4 401 | optional: true 402 | 403 | '@img/sharp-linux-x64@0.33.5': 404 | optionalDependencies: 405 | '@img/sharp-libvips-linux-x64': 1.0.4 406 | optional: true 407 | 408 | '@img/sharp-linuxmusl-arm64@0.33.5': 409 | optionalDependencies: 410 | '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 411 | optional: true 412 | 413 | '@img/sharp-linuxmusl-x64@0.33.5': 414 | optionalDependencies: 415 | '@img/sharp-libvips-linuxmusl-x64': 1.0.4 416 | optional: true 417 | 418 | '@img/sharp-wasm32@0.33.5': 419 | dependencies: 420 | '@emnapi/runtime': 1.4.0 421 | optional: true 422 | 423 | '@img/sharp-win32-ia32@0.33.5': 424 | optional: true 425 | 426 | '@img/sharp-win32-x64@0.33.5': 427 | optional: true 428 | 429 | '@next/env@15.2.4': {} 430 | 431 | '@next/swc-darwin-arm64@15.2.4': 432 | optional: true 433 | 434 | '@next/swc-darwin-x64@15.2.4': 435 | optional: true 436 | 437 | '@next/swc-linux-arm64-gnu@15.2.4': 438 | optional: true 439 | 440 | '@next/swc-linux-arm64-musl@15.2.4': 441 | optional: true 442 | 443 | '@next/swc-linux-x64-gnu@15.2.4': 444 | optional: true 445 | 446 | '@next/swc-linux-x64-musl@15.2.4': 447 | optional: true 448 | 449 | '@next/swc-win32-arm64-msvc@15.2.4': 450 | optional: true 451 | 452 | '@next/swc-win32-x64-msvc@15.2.4': 453 | optional: true 454 | 455 | '@swc/counter@0.1.3': {} 456 | 457 | '@swc/helpers@0.5.15': 458 | dependencies: 459 | tslib: 2.8.1 460 | 461 | '@types/node@22.10.5': 462 | dependencies: 463 | undici-types: 6.20.0 464 | 465 | '@types/react-dom@19.0.2(@types/react@19.0.2)': 466 | dependencies: 467 | '@types/react': 19.0.2 468 | 469 | '@types/react@19.0.2': 470 | dependencies: 471 | csstype: 3.1.3 472 | 473 | '@types/tinycolor2@1.4.6': {} 474 | 475 | busboy@1.6.0: 476 | dependencies: 477 | streamsearch: 1.1.0 478 | 479 | caniuse-lite@1.0.30001707: {} 480 | 481 | client-only@0.0.1: {} 482 | 483 | color-convert@2.0.1: 484 | dependencies: 485 | color-name: 1.1.4 486 | optional: true 487 | 488 | color-name@1.1.4: 489 | optional: true 490 | 491 | color-string@1.9.1: 492 | dependencies: 493 | color-name: 1.1.4 494 | simple-swizzle: 0.2.2 495 | optional: true 496 | 497 | color@4.2.3: 498 | dependencies: 499 | color-convert: 2.0.1 500 | color-string: 1.9.1 501 | optional: true 502 | 503 | csstype@3.1.3: {} 504 | 505 | detect-libc@2.0.3: 506 | optional: true 507 | 508 | is-arrayish@0.3.2: 509 | optional: true 510 | 511 | nanoid@3.3.11: {} 512 | 513 | next@15.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0): 514 | dependencies: 515 | '@next/env': 15.2.4 516 | '@swc/counter': 0.1.3 517 | '@swc/helpers': 0.5.15 518 | busboy: 1.6.0 519 | caniuse-lite: 1.0.30001707 520 | postcss: 8.4.31 521 | react: 19.0.0 522 | react-dom: 19.0.0(react@19.0.0) 523 | styled-jsx: 5.1.6(react@19.0.0) 524 | optionalDependencies: 525 | '@next/swc-darwin-arm64': 15.2.4 526 | '@next/swc-darwin-x64': 15.2.4 527 | '@next/swc-linux-arm64-gnu': 15.2.4 528 | '@next/swc-linux-arm64-musl': 15.2.4 529 | '@next/swc-linux-x64-gnu': 15.2.4 530 | '@next/swc-linux-x64-musl': 15.2.4 531 | '@next/swc-win32-arm64-msvc': 15.2.4 532 | '@next/swc-win32-x64-msvc': 15.2.4 533 | sharp: 0.33.5 534 | transitivePeerDependencies: 535 | - '@babel/core' 536 | - babel-plugin-macros 537 | 538 | picocolors@1.1.1: {} 539 | 540 | postcss@8.4.31: 541 | dependencies: 542 | nanoid: 3.3.11 543 | picocolors: 1.1.1 544 | source-map-js: 1.2.1 545 | 546 | react-dom@19.0.0(react@19.0.0): 547 | dependencies: 548 | react: 19.0.0 549 | scheduler: 0.25.0 550 | 551 | react@19.0.0: {} 552 | 553 | scheduler@0.25.0: {} 554 | 555 | semver@7.7.1: 556 | optional: true 557 | 558 | sharp@0.33.5: 559 | dependencies: 560 | color: 4.2.3 561 | detect-libc: 2.0.3 562 | semver: 7.7.1 563 | optionalDependencies: 564 | '@img/sharp-darwin-arm64': 0.33.5 565 | '@img/sharp-darwin-x64': 0.33.5 566 | '@img/sharp-libvips-darwin-arm64': 1.0.4 567 | '@img/sharp-libvips-darwin-x64': 1.0.4 568 | '@img/sharp-libvips-linux-arm': 1.0.5 569 | '@img/sharp-libvips-linux-arm64': 1.0.4 570 | '@img/sharp-libvips-linux-s390x': 1.0.4 571 | '@img/sharp-libvips-linux-x64': 1.0.4 572 | '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 573 | '@img/sharp-libvips-linuxmusl-x64': 1.0.4 574 | '@img/sharp-linux-arm': 0.33.5 575 | '@img/sharp-linux-arm64': 0.33.5 576 | '@img/sharp-linux-s390x': 0.33.5 577 | '@img/sharp-linux-x64': 0.33.5 578 | '@img/sharp-linuxmusl-arm64': 0.33.5 579 | '@img/sharp-linuxmusl-x64': 0.33.5 580 | '@img/sharp-wasm32': 0.33.5 581 | '@img/sharp-win32-ia32': 0.33.5 582 | '@img/sharp-win32-x64': 0.33.5 583 | optional: true 584 | 585 | simple-swizzle@0.2.2: 586 | dependencies: 587 | is-arrayish: 0.3.2 588 | optional: true 589 | 590 | source-map-js@1.2.1: {} 591 | 592 | streamsearch@1.1.0: {} 593 | 594 | styled-jsx@5.1.6(react@19.0.0): 595 | dependencies: 596 | client-only: 0.0.1 597 | react: 19.0.0 598 | 599 | tinycolor2@1.6.0: {} 600 | 601 | tslib@2.8.1: {} 602 | 603 | typescript@5.7.2: {} 604 | 605 | undici-types@6.20.0: {} 606 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vercel/avatar/40be7de8159ef0b6ecf02dc8e3536dee6e02ca76/public/favicon.ico -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 2rem; 3 | } 4 | 5 | .main { 6 | min-height: 100vh; 7 | padding: 4rem 0; 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .footer { 16 | display: flex; 17 | flex: 1; 18 | padding: 2rem 0; 19 | border-top: 1px solid #eaeaea; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .footer a { 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | flex-grow: 1; 29 | } 30 | 31 | .title a { 32 | color: #0070f3; 33 | text-decoration: none; 34 | } 35 | 36 | .title a:hover, 37 | .title a:focus, 38 | .title a:active { 39 | text-decoration: underline; 40 | } 41 | 42 | .title { 43 | margin: 0; 44 | line-height: 1.15; 45 | font-size: 4rem; 46 | } 47 | 48 | .title, 49 | .description { 50 | text-align: center; 51 | } 52 | 53 | .description { 54 | margin: 4rem 0; 55 | line-height: 1.5; 56 | font-size: 1.5rem; 57 | } 58 | 59 | .code { 60 | background: #fafafa; 61 | border-radius: 5px; 62 | padding: 0.75rem; 63 | font-size: 1.1rem; 64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 65 | Bitstream Vera Sans Mono, Courier New, monospace; 66 | } 67 | 68 | .grid { 69 | display: flex; 70 | align-items: center; 71 | justify-content: center; 72 | flex-wrap: wrap; 73 | max-width: 800px; 74 | } 75 | 76 | .card { 77 | margin: 1rem; 78 | padding: 1.5rem; 79 | text-align: left; 80 | color: inherit; 81 | text-decoration: none; 82 | border: 1px solid #eaeaea; 83 | border-radius: 10px; 84 | transition: color 0.15s ease, border-color 0.15s ease; 85 | max-width: 300px; 86 | } 87 | 88 | .card:hover, 89 | .card:focus, 90 | .card:active { 91 | color: #0070f3; 92 | border-color: #0070f3; 93 | } 94 | 95 | .card h2 { 96 | margin: 0 0 1rem 0; 97 | font-size: 1.5rem; 98 | } 99 | 100 | .card p { 101 | margin: 0; 102 | font-size: 1.25rem; 103 | line-height: 1.5; 104 | } 105 | 106 | .logo { 107 | height: 1em; 108 | margin-left: 0.5rem; 109 | } 110 | 111 | @media (max-width: 600px) { 112 | .grid { 113 | width: 100%; 114 | flex-direction: column; 115 | } 116 | } 117 | 118 | @media (prefers-color-scheme: dark) { 119 | .card, 120 | .footer { 121 | border-color: #222; 122 | } 123 | .code { 124 | background: #111; 125 | } 126 | .logo img { 127 | filter: invert(1); 128 | } 129 | } 130 | 131 | .avatar { 132 | border-radius: 50%; 133 | width: 64px; 134 | height: 64px; 135 | } 136 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | 18 | @media (prefers-color-scheme: dark) { 19 | html { 20 | color-scheme: dark; 21 | } 22 | body { 23 | color: white; 24 | background: black; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /utils/gradient.ts: -------------------------------------------------------------------------------- 1 | import color from "tinycolor2"; 2 | 3 | async function hash(str: string): Promise { 4 | let sum = 0; 5 | const buffer = await crypto.subtle.digest('SHA-1', new TextEncoder().encode(str)) 6 | for (const n of new Uint8Array(buffer)) { 7 | sum += n; 8 | } 9 | return sum; 10 | } 11 | 12 | async function hue(str: string): Promise { 13 | const n = await hash(str); 14 | return n % 360; 15 | } 16 | 17 | export async function generateGradient(username: string) { 18 | const h = await hue(username); 19 | const c1 = color({ h, s: 0.95, l: 0.5 }); 20 | const second = c1.triad()[1].toHexString(); 21 | 22 | return { 23 | fromColor: c1.toHexString(), 24 | toColor: second, 25 | }; 26 | } 27 | --------------------------------------------------------------------------------