├── .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     
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 | 
16 |
17 | ### Adjust Roundness
18 |
19 | ```
20 | https://avatar.vercel.sh/rauchg?rounded=60
21 | ```
22 |
23 | 
24 |
25 | ### Custom Size
26 |
27 | ```
28 | https://avatar.vercel.sh/rauchg?size=30
29 | ```
30 |
31 | 
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 | 
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 |
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 |
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 |
--------------------------------------------------------------------------------