facehash 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,329 +1,190 @@
1
1
  # facehash
2
2
 
3
- Deterministic avatar faces from any string. Zero dependencies, unstyled, React 18/19 compatible.
4
-
5
- Following the [shadcn/ui](https://ui.shadcn.com/) philosophy: **unstyled, composable, and yours to customize**.
3
+ Deterministic avatar faces from any string. Zero dependencies, works with React 18/19 and Next.js 15/16.
6
4
 
7
5
  <p align="center">
8
- <img src="https://cossistant.com/facehash-preview.png" alt="facehash examples" width="600" />
6
+ <img src="https://facehash.dev/og-image.png" alt="facehash examples" width="600" />
9
7
  </p>
10
8
 
11
- ## Features
12
-
13
- - **Deterministic** - Same string always generates the same face
14
- - **Unstyled** - Zero CSS included, bring your own Tailwind/CSS
15
- - **Composable** - Use standalone or as part of Avatar compound component
16
- - **Dark mode ready** - Built-in light/dark color palette support
17
- - **Accessible** - Proper ARIA attributes included
18
- - **Tiny** - Zero dependencies (React is a peer dep)
19
- - **TypeScript** - Full type safety
20
-
21
9
  ## Installation
22
10
 
23
11
  ```bash
24
12
  npm install facehash
25
- # or
26
- pnpm add facehash
27
- # or
28
- bun add facehash
29
13
  ```
30
14
 
31
15
  ## Quick Start
32
16
 
33
- ### Standalone FacehashAvatar
34
-
35
- The simplest way to get started - just a fun face!
17
+ ### React Component
36
18
 
37
19
  ```tsx
38
- import { FacehashAvatar } from "facehash";
39
-
40
- function App() {
41
- return (
42
- <FacehashAvatar
43
- name="John Doe"
44
- className="w-10 h-10 rounded-full"
45
- />
46
- );
47
- }
20
+ import { Facehash } from "facehash";
21
+
22
+ <Facehash name="john@example.com" />
48
23
  ```
49
24
 
50
- ### With Avatar Compound Component
25
+ Same string = same face. Always.
26
+
27
+ ### Next.js API Route (Image Generation)
51
28
 
52
- For image avatars with FacehashAvatar as fallback:
29
+ Generate PNG avatar images via API endpoint — perfect for emails, Open Graph images, or anywhere you need a URL.
53
30
 
54
31
  ```tsx
55
- import { Avatar, AvatarImage, AvatarFallback } from "facehash";
32
+ // app/api/avatar/route.ts
33
+ import { toFacehashHandler } from "facehash/next";
34
+
35
+ export const { GET } = toFacehashHandler();
36
+ ```
56
37
 
57
- function UserAvatar({ user }) {
58
- return (
59
- <Avatar className="w-10 h-10 rounded-full overflow-hidden">
60
- <AvatarImage src={user.avatarUrl} alt={user.name} />
61
- <AvatarFallback name={user.name} facehash />
62
- </Avatar>
63
- );
64
- }
38
+ Then use it:
39
+ ```
40
+ GET /api/avatar?name=john@example.com
41
+ GET /api/avatar?name=john&size=200&variant=solid
65
42
  ```
66
43
 
67
- ## Usage Examples
44
+ Returns a PNG image. Cached for 1 year by default.
68
45
 
69
- ### Basic Styling with Tailwind
46
+ ## Props
70
47
 
71
- ```tsx
72
- <FacehashAvatar
73
- name="Jane Smith"
74
- className="w-12 h-12 rounded-xl bg-gray-100 dark:bg-gray-800"
75
- />
76
- ```
48
+ | Prop | Type | Default | Description |
49
+ |------|------|---------|-------------|
50
+ | `name` | `string` | Required | String to generate face from |
51
+ | `size` | `number \| string` | `40` | Size in pixels or CSS units |
52
+ | `colors` | `string[]` | - | Array of hex/hsl colors |
53
+ | `colorClasses` | `string[]` | - | Array of Tailwind classes (use instead of `colors`) |
54
+ | `variant` | `"gradient" \| "solid"` | `"gradient"` | Background style |
55
+ | `intensity3d` | `"none" \| "subtle" \| "medium" \| "dramatic"` | `"dramatic"` | 3D rotation effect |
56
+ | `interactive` | `boolean` | `true` | Animate on hover |
57
+ | `showInitial` | `boolean` | `true` | Show first letter below face |
77
58
 
78
- ### Custom Color Palette
59
+ ## Examples
60
+
61
+ ### Custom Colors
79
62
 
80
63
  ```tsx
81
- <FacehashAvatar
82
- name="Alex Johnson"
64
+ <Facehash
65
+ name="alice"
83
66
  colors={["#264653", "#2a9d8f", "#e9c46a", "#f4a261", "#e76f51"]}
84
- className="w-16 h-16 rounded-full"
85
67
  />
86
68
  ```
87
69
 
88
- ### Light/Dark Mode Colors
70
+ ### With Tailwind Classes
89
71
 
90
72
  ```tsx
91
- <FacehashAvatar
92
- name="Sam Wilson"
93
- colorsLight={["#fce7f3", "#fef3c7", "#dbeafe"]}
94
- colorsDark={["#db2777", "#d97706", "#2563eb"]}
95
- colorScheme="auto" // Uses CSS prefers-color-scheme
96
- className="w-10 h-10 rounded-full"
73
+ <Facehash
74
+ name="bob"
75
+ colorClasses={["bg-pink-500", "bg-blue-500", "bg-green-500"]}
76
+ className="rounded-full"
97
77
  />
98
78
  ```
99
79
 
100
- ### Without 3D Effect
80
+ ### Flat Style (No 3D)
101
81
 
102
82
  ```tsx
103
- <FacehashAvatar
104
- name="Chris Brown"
105
- enable3D={false}
106
- className="w-10 h-10 rounded-lg"
107
- />
83
+ <Facehash name="charlie" intensity3d="none" variant="solid" />
108
84
  ```
109
85
 
110
86
  ### Without Initial Letter
111
87
 
112
88
  ```tsx
113
- <FacehashAvatar
114
- name="Taylor Swift"
115
- showInitial={false}
116
- className="w-10 h-10 rounded-full"
117
- />
89
+ <Facehash name="diana" showInitial={false} />
118
90
  ```
119
91
 
120
- ### Avatar with Initials Fallback
121
-
122
- ```tsx
123
- <Avatar className="w-10 h-10 rounded-full bg-gray-200">
124
- <AvatarImage src="/photo.jpg" alt="User" />
125
- <AvatarFallback name="John Doe" className="text-sm font-medium" />
126
- </Avatar>
127
- ```
92
+ ## Avatar with Image Fallback
128
93
 
129
- ### Avatar with Custom Fallback
94
+ For image avatars that fall back to Facehash when the image fails:
130
95
 
131
96
  ```tsx
132
- import { User } from "lucide-react";
97
+ import { Avatar, AvatarImage, AvatarFallback } from "facehash";
133
98
 
134
- <Avatar className="w-10 h-10 rounded-full bg-gray-200">
99
+ <Avatar style={{ width: 40, height: 40, borderRadius: "50%", overflow: "hidden" }}>
135
100
  <AvatarImage src="/photo.jpg" alt="User" />
136
- <AvatarFallback>
137
- <User className="w-5 h-5 text-gray-500" />
138
- </AvatarFallback>
101
+ <AvatarFallback name="john@example.com" />
139
102
  </Avatar>
140
103
  ```
141
104
 
142
- ### Delayed Fallback (Prevent Flash)
105
+ `AvatarFallback` renders a `Facehash` by default. For initials instead:
143
106
 
144
107
  ```tsx
145
- <Avatar className="w-10 h-10 rounded-full">
146
- <AvatarImage src="/slow-loading-image.jpg" />
147
- <AvatarFallback name="John" facehash delayMs={600} />
148
- </Avatar>
108
+ <AvatarFallback name="John Doe" facehash={false} />
149
109
  ```
150
110
 
151
- ## Styling with CSS
152
-
153
- ### Using CSS Variables (Dark Mode)
154
-
155
- When using `colorScheme="auto"`, the component sets CSS custom properties:
111
+ ## Next.js API Route
156
112
 
157
- ```css
158
- /* Apply the background based on color scheme */
159
- [data-facehash-avatar][data-color-scheme="auto"] {
160
- background-color: var(--facehash-avatar-bg-light);
161
- }
113
+ The `facehash/next` export provides a route handler factory for generating avatar images server-side. This is useful for:
162
114
 
163
- @media (prefers-color-scheme: dark) {
164
- [data-facehash-avatar][data-color-scheme="auto"] {
165
- background-color: var(--facehash-avatar-bg-dark);
166
- }
167
- }
168
- ```
169
-
170
- ### Using Data Attributes
115
+ - Email avatars (where you need an image URL)
116
+ - Open Graph / social sharing images
117
+ - Any context where you need a URL instead of a React component
171
118
 
172
- All components expose data attributes for styling:
119
+ ### Basic Setup
173
120
 
174
- ```css
175
- /* Root avatar */
176
- [data-avatar] { }
177
- [data-avatar][data-state="loading"] { }
178
- [data-avatar][data-state="loaded"] { }
179
- [data-avatar][data-state="error"] { }
180
-
181
- /* Facehash avatar */
182
- [data-facehash-avatar] { }
183
- [data-facehash-avatar-face] { }
184
- [data-facehash-avatar-initial] { }
121
+ ```tsx
122
+ // app/api/avatar/route.ts
123
+ import { toFacehashHandler } from "facehash/next";
185
124
 
186
- /* Image and fallback */
187
- [data-avatar-image] { }
188
- [data-avatar-fallback] { }
125
+ export const { GET } = toFacehashHandler();
189
126
  ```
190
127
 
191
- ## API Reference
192
-
193
- ### FacehashAvatar
194
-
195
- | Prop | Type | Default | Description |
196
- |------|------|---------|-------------|
197
- | `name` | `string` | Required | Name used to generate deterministic avatar |
198
- | `colors` | `string[]` | Tailwind colors | Base color palette |
199
- | `colorsLight` | `string[]` | Light variants | Colors for light mode |
200
- | `colorsDark` | `string[]` | Dark variants | Colors for dark mode |
201
- | `colorScheme` | `"light" \| "dark" \| "auto"` | `"auto"` | Color scheme to use |
202
- | `showInitial` | `boolean` | `true` | Show first letter below face |
203
- | `enable3D` | `boolean` | `true` | Enable 3D sphere rotation effect |
204
-
205
- ### Avatar
206
-
207
- | Prop | Type | Default | Description |
208
- |------|------|---------|-------------|
209
- | `asChild` | `boolean` | `false` | Render as child element (Slot pattern) |
210
-
211
- ### AvatarImage
212
-
213
- | Prop | Type | Default | Description |
214
- |------|------|---------|-------------|
215
- | `src` | `string \| null` | - | Image source URL |
216
- | `alt` | `string` | `""` | Alt text for accessibility |
217
- | `onLoadingStatusChange` | `(status) => void` | - | Callback on status change |
218
-
219
- ### AvatarFallback
220
-
221
- | Prop | Type | Default | Description |
222
- |------|------|---------|-------------|
223
- | `name` | `string` | `""` | Name for initials/FacehashAvatar |
224
- | `facehash` | `boolean` | `false` | Use FacehashAvatar instead of initials |
225
- | `facehashProps` | `FacehashAvatarProps` | - | Props to pass to FacehashAvatar |
226
- | `delayMs` | `number` | `0` | Delay before showing fallback |
227
- | `children` | `ReactNode` | - | Custom fallback content |
228
-
229
- ## Exports
128
+ ### With Custom Defaults
230
129
 
231
130
  ```tsx
232
- // Components
233
- import {
234
- FacehashAvatar,
235
- Avatar,
236
- AvatarImage,
237
- AvatarFallback,
238
- } from "facehash";
239
-
240
- // Face components (for custom compositions)
241
- import {
242
- RoundFace,
243
- CrossFace,
244
- LineFace,
245
- CurvedFace,
246
- FACES,
247
- } from "facehash";
248
-
249
- // Utilities
250
- import {
251
- stringHash,
252
- DEFAULT_COLORS,
253
- DEFAULT_COLORS_LIGHT,
254
- DEFAULT_COLORS_DARK,
255
- } from "facehash";
256
-
257
- // Hooks
258
- import { useAvatarContext } from "facehash";
131
+ export const { GET } = toFacehashHandler({
132
+ size: 200, // Default image size (default: 400)
133
+ variant: "solid", // "gradient" | "solid" (default: "gradient")
134
+ showInitial: false, // Show first letter (default: true)
135
+ colors: ["#ff6b6b", "#4ecdc4", "#45b7d1"], // Custom color palette
136
+ cacheControl: "public, max-age=86400", // Custom cache header
137
+ });
259
138
  ```
260
139
 
261
- ## Recipes
140
+ ### Query Parameters
262
141
 
263
- ### Next.js App Router
142
+ All options can be overridden via URL query parameters:
264
143
 
265
- ```tsx
266
- // components/user-avatar.tsx
267
- "use client";
144
+ | Parameter | Type | Description |
145
+ |-----------|------|-------------|
146
+ | `name` | `string` | **Required.** String to generate avatar from |
147
+ | `size` | `number` | Image size in pixels (16-2000) |
148
+ | `variant` | `string` | `"gradient"` or `"solid"` |
149
+ | `showInitial` | `boolean` | `"true"` or `"false"` |
150
+ | `colors` | `string` | Comma-separated hex colors (e.g., `#ff0000,#00ff00`) |
268
151
 
269
- import { Avatar, AvatarImage, AvatarFallback } from "facehash";
152
+ ### Example URLs
270
153
 
271
- export function UserAvatar({ user }: { user: { name: string; image?: string } }) {
272
- return (
273
- <Avatar className="relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full">
274
- {user.image && <AvatarImage src={user.image} alt={user.name} />}
275
- <AvatarFallback
276
- name={user.name}
277
- facehash
278
- className="flex h-full w-full items-center justify-center"
279
- />
280
- </Avatar>
281
- );
282
- }
154
+ ```
155
+ /api/avatar?name=john@example.com
156
+ /api/avatar?name=Alice&size=128
157
+ /api/avatar?name=Bob&variant=solid&showInitial=false
158
+ /api/avatar?name=Team&colors=%23ff6b6b,%234ecdc4,%2345b7d1
283
159
  ```
284
160
 
285
- ### Tailwind with Ring
161
+ ### Caching
286
162
 
287
- ```tsx
288
- <Avatar className="h-10 w-10 rounded-full ring-2 ring-white dark:ring-gray-900">
289
- <AvatarImage src={url} />
290
- <AvatarFallback name="John Doe" facehash />
291
- </Avatar>
292
- ```
163
+ By default, responses include `Cache-Control: public, max-age=31536000, immutable` (1 year). Same name always generates the same image, so aggressive caching is safe.
293
164
 
294
- ### Avatar Group
165
+ ## Exports
295
166
 
296
167
  ```tsx
297
- function AvatarGroup({ users }) {
298
- return (
299
- <div className="flex -space-x-2">
300
- {users.map((user) => (
301
- <Avatar
302
- key={user.id}
303
- className="h-8 w-8 rounded-full ring-2 ring-white"
304
- >
305
- <AvatarImage src={user.avatar} />
306
- <AvatarFallback name={user.name} facehash />
307
- </Avatar>
308
- ))}
309
- </div>
310
- );
311
- }
312
- ```
168
+ // Main component
169
+ import { Facehash } from "facehash";
313
170
 
314
- ## Browser Support
171
+ // Avatar compound components
172
+ import { Avatar, AvatarImage, AvatarFallback } from "facehash";
315
173
 
316
- - Chrome 88+
317
- - Firefox 78+
318
- - Safari 14+
319
- - Edge 88+
174
+ // Individual face components (for custom use)
175
+ import { RoundFace, CrossFace, LineFace, CurvedFace, FACES } from "facehash";
320
176
 
321
- ## Credits
177
+ // Utilities
178
+ import { stringHash } from "facehash";
322
179
 
323
- Built with love by the [Cossistant](https://cossistant.com) team.
180
+ // Types
181
+ import type { FacehashProps, AvatarProps, AvatarFallbackProps, AvatarImageProps } from "facehash";
324
182
 
325
- Inspired by [Boring Avatars](https://boringavatars.com/) by Josep Martins.
183
+ // Next.js route handler (facehash/next)
184
+ import { toFacehashHandler } from "facehash/next";
185
+ import type { FacehashHandlerOptions, FacehashHandler } from "facehash/next";
186
+ ```
326
187
 
327
188
  ## License
328
189
 
329
- MIT
190
+ MIT — Built by [Cossistant](https://cossistant.com)
package/hash.js ADDED
@@ -0,0 +1,21 @@
1
+ //#region src/utils/hash.ts
2
+ /**
3
+ * Generates a consistent numeric hash from a string.
4
+ * Used to deterministically select faces and colors for avatars.
5
+ *
6
+ * @param str - The input string to hash
7
+ * @returns A positive 32-bit integer hash
8
+ */
9
+ function stringHash(str) {
10
+ let hash = 0;
11
+ for (let i = 0; i < str.length; i++) {
12
+ const char = str.charCodeAt(i);
13
+ hash = (hash << 5) - hash + char;
14
+ hash &= hash;
15
+ }
16
+ return Math.abs(hash);
17
+ }
18
+
19
+ //#endregion
20
+ export { stringHash as t };
21
+ //# sourceMappingURL=hash.js.map
package/hash.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.js","names":[],"sources":["../src/utils/hash.ts"],"sourcesContent":["/**\n * Generates a consistent numeric hash from a string.\n * Used to deterministically select faces and colors for avatars.\n *\n * @param str - The input string to hash\n * @returns A positive 32-bit integer hash\n */\nexport function stringHash(str: string): number {\n\tlet hash = 0;\n\tfor (let i = 0; i < str.length; i++) {\n\t\tconst char = str.charCodeAt(i);\n\t\thash = (hash << 5) - hash + char;\n\t\thash &= hash; // Convert to 32bit integer\n\t}\n\treturn Math.abs(hash);\n}\n"],"mappings":";;;;;;;;AAOA,SAAgB,WAAW,KAAqB;CAC/C,IAAI,OAAO;AACX,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;EACpC,MAAM,OAAO,IAAI,WAAW,EAAE;AAC9B,UAAQ,QAAQ,KAAK,OAAO;AAC5B,UAAQ;;AAET,QAAO,KAAK,IAAI,KAAK"}
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/facehash.tsx","../src/avatar.tsx","../src/avatar-fallback.tsx","../src/avatar-image.tsx","../src/faces.tsx","../src/utils/hash.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/facehash.tsx","../src/avatar.tsx","../src/avatar-fallback.tsx","../src/avatar-image.tsx","../src/faces.tsx","../src/utils/hash.ts"],"sourcesContent":[],"mappings":"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"}
package/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { t as stringHash } from "./hash.js";
1
2
  import * as React from "react";
2
3
  import { jsx, jsxs } from "react/jsx-runtime";
3
4
 
@@ -108,25 +109,6 @@ const FACES = [
108
109
  CurvedFace
109
110
  ];
110
111
 
111
- //#endregion
112
- //#region src/utils/hash.ts
113
- /**
114
- * Generates a consistent numeric hash from a string.
115
- * Used to deterministically select faces and colors for avatars.
116
- *
117
- * @param str - The input string to hash
118
- * @returns A positive 32-bit integer hash
119
- */
120
- function stringHash(str) {
121
- let hash = 0;
122
- for (let i = 0; i < str.length; i++) {
123
- const char = str.charCodeAt(i);
124
- hash = (hash << 5) - hash + char;
125
- hash &= hash;
126
- }
127
- return Math.abs(hash);
128
- }
129
-
130
112
  //#endregion
131
113
  //#region src/facehash.tsx
132
114
  const INTENSITY_PRESETS = {
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["RoundFace: React.FC<FaceProps>","CrossFace: React.FC<FaceProps>","LineFace: React.FC<FaceProps>","CurvedFace: React.FC<FaceProps>","DEFAULT_GRADIENT_STYLE: React.CSSProperties","contextValue: AvatarContextValue"],"sources":["../src/faces.tsx","../src/utils/hash.ts","../src/facehash.tsx","../src/avatar.tsx","../src/avatar-fallback.tsx","../src/avatar-image.tsx"],"sourcesContent":["import type * as React from \"react\";\n\nexport type FaceProps = {\n\tclassName?: string;\n\tstyle?: React.CSSProperties;\n};\n\n/**\n * Round eyes face - simple circular eyes\n */\nexport const RoundFace: React.FC<FaceProps> = ({ className, style }) => (\n\t<svg\n\t\taria-hidden=\"true\"\n\t\tclassName={className}\n\t\tfill=\"none\"\n\t\tstyle={style}\n\t\tviewBox=\"0 0 63 15\"\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t>\n\t\t<title>Round Eyes</title>\n\t\t<path\n\t\t\td=\"M62.4 7.2C62.4 11.1765 59.1765 14.4 55.2 14.4C51.2236 14.4 48 11.1765 48 7.2C48 3.22355 51.2236 0 55.2 0C59.1765 0 62.4 3.22355 62.4 7.2Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t\t<path\n\t\t\td=\"M14.4 7.2C14.4 11.1765 11.1765 14.4 7.2 14.4C3.22355 14.4 0 11.1765 0 7.2C0 3.22355 3.22355 0 7.2 0C11.1765 0 14.4 3.22355 14.4 7.2Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t</svg>\n);\n\n/**\n * Cross eyes face - X-shaped eyes\n */\nexport const CrossFace: React.FC<FaceProps> = ({ className, style }) => (\n\t<svg\n\t\taria-hidden=\"true\"\n\t\tclassName={className}\n\t\tfill=\"none\"\n\t\tstyle={style}\n\t\tviewBox=\"0 0 71 23\"\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t>\n\t\t<title>Cross Eyes</title>\n\t\t<path\n\t\t\td=\"M11.5 0C12.9411 0 13.6619 0.000460386 14.1748 0.354492C14.3742 0.49213 14.547 0.664882 14.6846 0.864258C15.0384 1.37711 15.0391 2.09739 15.0391 3.53809V7.96094H19.4619C20.9027 7.96094 21.6229 7.9615 22.1357 8.31543C22.3352 8.45308 22.5079 8.62578 22.6455 8.8252C22.9995 9.3381 23 10.0589 23 11.5C23 12.9408 22.9995 13.661 22.6455 14.1738C22.5079 14.3733 22.3352 14.5459 22.1357 14.6836C21.6229 15.0375 20.9027 15.0381 19.4619 15.0381H15.0391V19.4619C15.0391 20.9026 15.0384 21.6229 14.6846 22.1357C14.547 22.3351 14.3742 22.5079 14.1748 22.6455C13.6619 22.9995 12.9411 23 11.5 23C10.0592 23 9.33903 22.9994 8.82617 22.6455C8.62674 22.5079 8.45309 22.3352 8.31543 22.1357C7.96175 21.6229 7.96191 20.9024 7.96191 19.4619V15.0381H3.53809C2.0973 15.0381 1.37711 15.0375 0.864258 14.6836C0.664834 14.5459 0.492147 14.3733 0.354492 14.1738C0.000498831 13.661 -5.88036e-08 12.9408 0 11.5C6.2999e-08 10.0589 0.000460356 9.3381 0.354492 8.8252C0.492144 8.62578 0.664842 8.45308 0.864258 8.31543C1.37711 7.9615 2.09731 7.96094 3.53809 7.96094H7.96191V3.53809C7.96191 2.09765 7.96175 1.37709 8.31543 0.864258C8.45309 0.664828 8.62674 0.492149 8.82617 0.354492C9.33903 0.000555366 10.0592 1.62347e-09 11.5 0Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t\t<path\n\t\t\td=\"M58.7695 0C60.2107 0 60.9314 0.000460386 61.4443 0.354492C61.6437 0.49213 61.8165 0.664882 61.9541 0.864258C62.308 1.37711 62.3086 2.09739 62.3086 3.53809V7.96094H66.7314C68.1722 7.96094 68.8924 7.9615 69.4053 8.31543C69.6047 8.45308 69.7774 8.62578 69.915 8.8252C70.2691 9.3381 70.2695 10.0589 70.2695 11.5C70.2695 12.9408 70.269 13.661 69.915 14.1738C69.7774 14.3733 69.6047 14.5459 69.4053 14.6836C68.8924 15.0375 68.1722 15.0381 66.7314 15.0381H62.3086V19.4619C62.3086 20.9026 62.308 21.6229 61.9541 22.1357C61.8165 22.3351 61.6437 22.5079 61.4443 22.6455C60.9314 22.9995 60.2107 23 58.7695 23C57.3287 23 56.6086 22.9994 56.0957 22.6455C55.8963 22.5079 55.7226 22.3352 55.585 22.1357C55.2313 21.6229 55.2314 20.9024 55.2314 19.4619V15.0381H50.8076C49.3668 15.0381 48.6466 15.0375 48.1338 14.6836C47.9344 14.5459 47.7617 14.3733 47.624 14.1738C47.27 13.661 47.2695 12.9408 47.2695 11.5C47.2695 10.0589 47.27 9.3381 47.624 8.8252C47.7617 8.62578 47.9344 8.45308 48.1338 8.31543C48.6466 7.9615 49.3668 7.96094 50.8076 7.96094H55.2314V3.53809C55.2314 2.09765 55.2313 1.37709 55.585 0.864258C55.7226 0.664828 55.8963 0.492149 56.0957 0.354492C56.6086 0.000555366 57.3287 1.62347e-09 58.7695 0Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t</svg>\n);\n\n/**\n * Line eyes face - horizontal line eyes\n */\nexport const LineFace: React.FC<FaceProps> = ({ className, style }) => (\n\t<svg\n\t\taria-hidden=\"true\"\n\t\tclassName={className}\n\t\tfill=\"none\"\n\t\tstyle={style}\n\t\tviewBox=\"0 0 82 8\"\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t>\n\t\t<title>Line Eyes</title>\n\t\t<path\n\t\t\td=\"M3.53125 0.164063C4.90133 0.164063 5.58673 0.163893 6.08301 0.485352C6.31917 0.638428 6.52075 0.840012 6.67383 1.07617C6.99555 1.57252 6.99512 2.25826 6.99512 3.62891C6.99512 4.99911 6.99536 5.68438 6.67383 6.18066C6.52075 6.41682 6.31917 6.61841 6.08301 6.77148C5.58672 7.09305 4.90147 7.09277 3.53125 7.09277C2.16062 7.09277 1.47486 7.09319 0.978516 6.77148C0.742356 6.61841 0.540772 6.41682 0.387695 6.18066C0.0662401 5.68439 0.0664063 4.999 0.0664063 3.62891C0.0664063 2.25838 0.0660571 1.57251 0.387695 1.07617C0.540772 0.840012 0.742356 0.638428 0.978516 0.485352C1.47485 0.163744 2.16076 0.164063 3.53125 0.164063Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t\t<path\n\t\t\td=\"M25.1836 0.164063C26.5542 0.164063 27.24 0.163638 27.7363 0.485352C27.9724 0.638384 28.1731 0.8401 28.3262 1.07617C28.6479 1.57252 28.6484 2.25825 28.6484 3.62891C28.6484 4.99931 28.6478 5.68436 28.3262 6.18066C28.1731 6.41678 27.9724 6.61842 27.7363 6.77148C27.24 7.09321 26.5542 7.09277 25.1836 7.09277H11.3262C9.95557 7.09277 9.26978 7.09317 8.77344 6.77148C8.53728 6.61841 8.33569 6.41682 8.18262 6.18066C7.86115 5.68438 7.86133 4.99902 7.86133 3.62891C7.86133 2.25835 7.86096 1.57251 8.18262 1.07617C8.33569 0.840012 8.53728 0.638428 8.77344 0.485352C9.26977 0.163768 9.95572 0.164063 11.3262 0.164063H25.1836Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t\t<path\n\t\t\td=\"M78.2034 7.09325C76.8333 7.09325 76.1479 7.09342 75.6516 6.77197C75.4155 6.61889 75.2139 6.4173 75.0608 6.18114C74.7391 5.6848 74.7395 4.99905 74.7395 3.62841C74.7395 2.2582 74.7393 1.57294 75.0608 1.07665C75.2139 0.840493 75.4155 0.638909 75.6516 0.485832C76.1479 0.164271 76.8332 0.164543 78.2034 0.164543C79.574 0.164543 80.2598 0.164122 80.7561 0.485832C80.9923 0.638909 81.1939 0.840493 81.347 1.07665C81.6684 1.57293 81.6682 2.25831 81.6682 3.62841C81.6682 4.99894 81.6686 5.68481 81.347 6.18114C81.1939 6.4173 80.9923 6.61889 80.7561 6.77197C80.2598 7.09357 79.5739 7.09325 78.2034 7.09325Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t\t<path\n\t\t\td=\"M56.5511 7.09325C55.1804 7.09325 54.4947 7.09368 53.9983 6.77197C53.7622 6.61893 53.5615 6.41722 53.4085 6.18114C53.0868 5.6848 53.0862 4.99907 53.0862 3.62841C53.0862 2.258 53.0868 1.57296 53.4085 1.07665C53.5615 0.840539 53.7622 0.638898 53.9983 0.485832C54.4947 0.164105 55.1804 0.164543 56.5511 0.164543H70.4085C71.7791 0.164543 72.4649 0.164146 72.9612 0.485832C73.1974 0.638909 73.399 0.840493 73.552 1.07665C73.8735 1.57293 73.8733 2.25829 73.8733 3.62841C73.8733 4.99896 73.8737 5.68481 73.552 6.18114C73.399 6.4173 73.1974 6.61889 72.9612 6.77197C72.4649 7.09355 71.7789 7.09325 70.4085 7.09325H56.5511Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t</svg>\n);\n\n/**\n * Curved eyes face - sleepy/happy curved eyes\n */\nexport const CurvedFace: React.FC<FaceProps> = ({ className, style }) => (\n\t<svg\n\t\taria-hidden=\"true\"\n\t\tclassName={className}\n\t\tfill=\"none\"\n\t\tstyle={style}\n\t\tviewBox=\"0 0 63 9\"\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t>\n\t\t<title>Curved Eyes</title>\n\t\t<path\n\t\t\td=\"M0 5.06511C0 4.94513 0 4.88513 0.00771184 4.79757C0.0483059 4.33665 0.341025 3.76395 0.690821 3.46107C0.757274 3.40353 0.783996 3.38422 0.837439 3.34559C2.40699 2.21129 6.03888 0 10.5 0C14.9611 0 18.593 2.21129 20.1626 3.34559C20.216 3.38422 20.2427 3.40353 20.3092 3.46107C20.659 3.76395 20.9517 4.33665 20.9923 4.79757C21 4.88513 21 4.94513 21 5.06511C21 6.01683 21 6.4927 20.9657 6.6754C20.7241 7.96423 19.8033 8.55941 18.5289 8.25054C18.3483 8.20676 17.8198 7.96876 16.7627 7.49275C14.975 6.68767 12.7805 6 10.5 6C8.21954 6 6.02504 6.68767 4.23727 7.49275C3.18025 7.96876 2.65174 8.20676 2.47108 8.25054C1.19668 8.55941 0.275917 7.96423 0.0342566 6.6754C0 6.4927 0 6.01683 0 5.06511Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t\t<path\n\t\t\td=\"M42 5.06511C42 4.94513 42 4.88513 42.0077 4.79757C42.0483 4.33665 42.341 3.76395 42.6908 3.46107C42.7573 3.40353 42.784 3.38422 42.8374 3.34559C44.407 2.21129 48.0389 0 52.5 0C56.9611 0 60.593 2.21129 62.1626 3.34559C62.216 3.38422 62.2427 3.40353 62.3092 3.46107C62.659 3.76395 62.9517 4.33665 62.9923 4.79757C63 4.88513 63 4.94513 63 5.06511C63 6.01683 63 6.4927 62.9657 6.6754C62.7241 7.96423 61.8033 8.55941 60.5289 8.25054C60.3483 8.20676 59.8198 7.96876 58.7627 7.49275C56.975 6.68767 54.7805 6 52.5 6C50.2195 6 48.025 6.68767 46.2373 7.49275C45.1802 7.96876 44.6517 8.20676 44.4711 8.25054C43.1967 8.55941 42.2759 7.96423 42.0343 6.6754C42 6.4927 42 6.01683 42 5.06511Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t</svg>\n);\n\n/**\n * All available face components\n */\nexport const FACES = [RoundFace, CrossFace, LineFace, CurvedFace] as const;\n\nexport type FaceComponent = (typeof FACES)[number];\n","/**\n * Generates a consistent numeric hash from a string.\n * Used to deterministically select faces and colors for avatars.\n *\n * @param str - The input string to hash\n * @returns A positive 32-bit integer hash\n */\nexport function stringHash(str: string): number {\n\tlet hash = 0;\n\tfor (let i = 0; i < str.length; i++) {\n\t\tconst char = str.charCodeAt(i);\n\t\thash = (hash << 5) - hash + char;\n\t\thash &= hash; // Convert to 32bit integer\n\t}\n\treturn Math.abs(hash);\n}\n","import * as React from \"react\";\nimport { FACES } from \"./faces\";\nimport { stringHash } from \"./utils/hash\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type Intensity3D = \"none\" | \"subtle\" | \"medium\" | \"dramatic\";\nexport type Variant = \"gradient\" | \"solid\";\n\nexport interface FacehashProps\n\textends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\"> {\n\t/**\n\t * String to generate a deterministic face from.\n\t * Same string always produces the same face.\n\t */\n\tname: string;\n\n\t/**\n\t * Size in pixels or CSS units.\n\t * @default 40\n\t */\n\tsize?: number | string;\n\n\t/**\n\t * Background style.\n\t * - \"gradient\": Adds gradient overlay (default)\n\t * - \"solid\": Plain background color\n\t * @default \"gradient\"\n\t */\n\tvariant?: Variant;\n\n\t/**\n\t * 3D effect intensity.\n\t * @default \"dramatic\"\n\t */\n\tintensity3d?: Intensity3D;\n\n\t/**\n\t * Enable hover interaction.\n\t * When true, face \"looks straight\" on hover.\n\t * @default true\n\t */\n\tinteractive?: boolean;\n\n\t/**\n\t * Show first letter of name below the face.\n\t * @default true\n\t */\n\tshowInitial?: boolean;\n\n\t/**\n\t * Hex color array for inline styles.\n\t * Use this OR colorClasses, not both.\n\t */\n\tcolors?: string[];\n\n\t/**\n\t * Tailwind class array for background colors.\n\t * Example: [\"bg-pink-500 dark:bg-pink-600\", \"bg-blue-500 dark:bg-blue-600\"]\n\t * Use this OR colors, not both.\n\t */\n\tcolorClasses?: string[];\n\n\t/**\n\t * Custom gradient overlay class (Tailwind).\n\t * When provided, replaces the default pure CSS gradient.\n\t * Only used when variant=\"gradient\".\n\t */\n\tgradientOverlayClass?: string;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst INTENSITY_PRESETS = {\n\tnone: {\n\t\trotateRange: 0,\n\t\ttranslateZ: 0,\n\t\tperspective: \"none\",\n\t},\n\tsubtle: {\n\t\trotateRange: 5,\n\t\ttranslateZ: 4,\n\t\tperspective: \"800px\",\n\t},\n\tmedium: {\n\t\trotateRange: 10,\n\t\ttranslateZ: 8,\n\t\tperspective: \"500px\",\n\t},\n\tdramatic: {\n\t\trotateRange: 15,\n\t\ttranslateZ: 12,\n\t\tperspective: \"300px\",\n\t},\n} as const;\n\nconst SPHERE_POSITIONS = [\n\t{ x: -1, y: 1 }, // down-right\n\t{ x: 1, y: 1 }, // up-right\n\t{ x: 1, y: 0 }, // up\n\t{ x: 0, y: 1 }, // right\n\t{ x: -1, y: 0 }, // down\n\t{ x: 0, y: 0 }, // center\n\t{ x: 0, y: -1 }, // left\n\t{ x: -1, y: -1 }, // down-left\n\t{ x: 1, y: -1 }, // up-left\n] as const;\n\n// Default gradient as pure CSS (works without Tailwind)\n// Matches: bg-[radial-gradient(ellipse_100%_100%_at_50%_50%,_COLOR_0%,_transparent_60%)]\n// Light mode: white glow in center, Dark mode: dark overlay in center\nconst DEFAULT_GRADIENT_STYLE: React.CSSProperties = {\n\tbackground:\n\t\t\"radial-gradient(ellipse 100% 100% at 50% 50%, rgba(255,255,255,0.15) 0%, transparent 60%)\",\n};\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * Facehash - Deterministic avatar faces from any string.\n *\n * @example\n * ```tsx\n * // With Tailwind classes\n * <Facehash\n * name=\"John\"\n * colorClasses={[\"bg-pink-500\", \"bg-blue-500\"]}\n * />\n *\n * // With hex colors\n * <Facehash\n * name=\"John\"\n * colors={[\"#ec4899\", \"#3b82f6\"]}\n * />\n *\n * // Plain color (no gradient)\n * <Facehash name=\"John\" variant=\"solid\" />\n * ```\n */\nexport const Facehash = React.forwardRef<HTMLDivElement, FacehashProps>(\n\t(\n\t\t{\n\t\t\tname,\n\t\t\tsize = 40,\n\t\t\tvariant = \"gradient\",\n\t\t\tintensity3d = \"dramatic\",\n\t\t\tinteractive = true,\n\t\t\tshowInitial = true,\n\t\t\tcolors,\n\t\t\tcolorClasses,\n\t\t\tgradientOverlayClass,\n\t\t\tclassName,\n\t\t\tstyle,\n\t\t\tonMouseEnter,\n\t\t\tonMouseLeave,\n\t\t\t...props\n\t\t},\n\t\tref\n\t) => {\n\t\tconst [isHovered, setIsHovered] = React.useState(false);\n\n\t\t// Generate deterministic values from name\n\t\tconst { FaceComponent, colorIndex, rotation } = React.useMemo(() => {\n\t\t\tconst hash = stringHash(name);\n\t\t\tconst faceIndex = hash % FACES.length;\n\t\t\tconst colorsLength = colorClasses?.length ?? colors?.length ?? 1;\n\t\t\tconst _colorIndex = hash % colorsLength;\n\t\t\tconst positionIndex = hash % SPHERE_POSITIONS.length;\n\t\t\tconst position = SPHERE_POSITIONS[positionIndex] ?? { x: 0, y: 0 };\n\n\t\t\treturn {\n\t\t\t\tFaceComponent: FACES[faceIndex] ?? FACES[0],\n\t\t\t\tcolorIndex: _colorIndex,\n\t\t\t\trotation: position,\n\t\t\t};\n\t\t}, [name, colors?.length, colorClasses?.length]);\n\n\t\t// Get intensity preset\n\t\tconst preset = INTENSITY_PRESETS[intensity3d];\n\n\t\t// Calculate 3D transform\n\t\tconst transform = React.useMemo(() => {\n\t\t\tif (intensity3d === \"none\") {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst rotateX =\n\t\t\t\tisHovered && interactive ? 0 : rotation.x * preset.rotateRange;\n\t\t\tconst rotateY =\n\t\t\t\tisHovered && interactive ? 0 : rotation.y * preset.rotateRange;\n\n\t\t\treturn `rotateX(${rotateX}deg) rotateY(${rotateY}deg) translateZ(${preset.translateZ}px)`;\n\t\t}, [intensity3d, isHovered, interactive, rotation, preset]);\n\n\t\t// Size style\n\t\tconst sizeValue = typeof size === \"number\" ? `${size}px` : size;\n\n\t\t// Initial letter\n\t\tconst initial = name.charAt(0).toUpperCase();\n\n\t\t// Background: either hex color (inline) or class\n\t\tconst bgColorClass = colorClasses?.[colorIndex];\n\t\tconst bgColorHex = colors?.[colorIndex];\n\n\t\t// Event handlers\n\t\tconst handleMouseEnter = React.useCallback(\n\t\t\t(e: React.MouseEvent<HTMLDivElement>) => {\n\t\t\t\tif (interactive) {\n\t\t\t\t\tsetIsHovered(true);\n\t\t\t\t}\n\t\t\t\tonMouseEnter?.(e);\n\t\t\t},\n\t\t\t[interactive, onMouseEnter]\n\t\t);\n\n\t\tconst handleMouseLeave = React.useCallback(\n\t\t\t(e: React.MouseEvent<HTMLDivElement>) => {\n\t\t\t\tif (interactive) {\n\t\t\t\t\tsetIsHovered(false);\n\t\t\t\t}\n\t\t\t\tonMouseLeave?.(e);\n\t\t\t},\n\t\t\t[interactive, onMouseLeave]\n\t\t);\n\n\t\treturn (\n\t\t\t// biome-ignore lint/a11y/noNoninteractiveElementInteractions: Hover effect is purely cosmetic\n\t\t\t// biome-ignore lint/a11y/noStaticElementInteractions: This is a decorative avatar component\n\t\t\t<div\n\t\t\t\tclassName={[bgColorClass, className].filter(Boolean).join(\" \")}\n\t\t\t\tdata-facehash=\"\"\n\t\t\t\tdata-interactive={interactive || undefined}\n\t\t\t\tonMouseEnter={handleMouseEnter}\n\t\t\t\tonMouseLeave={handleMouseLeave}\n\t\t\t\tref={ref}\n\t\t\t\tstyle={{\n\t\t\t\t\t// Size\n\t\t\t\t\twidth: sizeValue,\n\t\t\t\t\theight: sizeValue,\n\t\t\t\t\t// Layout\n\t\t\t\t\tposition: \"relative\",\n\t\t\t\t\tdisplay: \"flex\",\n\t\t\t\t\talignItems: \"center\",\n\t\t\t\t\tjustifyContent: \"center\",\n\t\t\t\t\toverflow: \"hidden\",\n\t\t\t\t\t// Container for cqw units\n\t\t\t\t\tcontainerType: \"size\",\n\t\t\t\t\t// 3D setup\n\t\t\t\t\t...(intensity3d !== \"none\" && {\n\t\t\t\t\t\tperspective: preset.perspective,\n\t\t\t\t\t\ttransformStyle: \"preserve-3d\",\n\t\t\t\t\t}),\n\t\t\t\t\t// Background color (hex) - only if no colorClasses\n\t\t\t\t\t...(bgColorHex && !bgColorClass && { backgroundColor: bgColorHex }),\n\t\t\t\t\t// User styles (last to allow overrides)\n\t\t\t\t\t...style,\n\t\t\t\t}}\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t{/* Gradient overlay */}\n\t\t\t\t{variant === \"gradient\" && (\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName={gradientOverlayClass}\n\t\t\t\t\t\tdata-facehash-gradient=\"\"\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\tinset: 0,\n\t\t\t\t\t\t\tpointerEvents: \"none\",\n\t\t\t\t\t\t\tzIndex: 1,\n\t\t\t\t\t\t\t// Use default pure CSS gradient if no class provided\n\t\t\t\t\t\t\t...(gradientOverlayClass ? {} : DEFAULT_GRADIENT_STYLE),\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t)}\n\n\t\t\t\t{/* Face container with 3D transform */}\n\t\t\t\t<div\n\t\t\t\t\tdata-facehash-face=\"\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\tinset: 0,\n\t\t\t\t\t\tdisplay: \"flex\",\n\t\t\t\t\t\tflexDirection: \"column\",\n\t\t\t\t\t\talignItems: \"center\",\n\t\t\t\t\t\tjustifyContent: \"center\",\n\t\t\t\t\t\tzIndex: 2,\n\t\t\t\t\t\ttransform,\n\t\t\t\t\t\ttransformStyle: intensity3d !== \"none\" ? \"preserve-3d\" : undefined,\n\t\t\t\t\t\ttransition: interactive\n\t\t\t\t\t\t\t? \"transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)\"\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\t// Default to black text/icons for contrast on colored backgrounds\n\t\t\t\t\t\tcolor: \"#000000\",\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{/* Face SVG */}\n\t\t\t\t\t<FaceComponent\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\twidth: \"60%\",\n\t\t\t\t\t\t\theight: \"auto\",\n\t\t\t\t\t\t\tmaxWidth: \"90%\",\n\t\t\t\t\t\t\tmaxHeight: \"40%\",\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\n\t\t\t\t\t{/* Initial letter */}\n\t\t\t\t\t{showInitial && (\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tdata-facehash-initial=\"\"\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\tmarginTop: \"8%\",\n\t\t\t\t\t\t\t\tfontSize: \"26cqw\",\n\t\t\t\t\t\t\t\tlineHeight: 1,\n\t\t\t\t\t\t\t\tfontFamily: \"monospace\",\n\t\t\t\t\t\t\t\tfontWeight: \"bold\",\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{initial}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t}\n);\n\nFacehash.displayName = \"Facehash\";\n","import * as React from \"react\";\n\ntype ImageLoadingStatus = \"idle\" | \"loading\" | \"loaded\" | \"error\";\n\nexport type AvatarContextValue = {\n\timageLoadingStatus: ImageLoadingStatus;\n\tonImageLoadingStatusChange: (status: ImageLoadingStatus) => void;\n};\n\nconst AvatarContext = React.createContext<AvatarContextValue | null>(null);\n\n/**\n * Hook to access the Avatar context.\n * Throws an error if used outside of Avatar.\n */\nexport const useAvatarContext = () => {\n\tconst context = React.useContext(AvatarContext);\n\tif (!context) {\n\t\tthrow new Error(\n\t\t\t\"Avatar compound components must be rendered within an Avatar component\"\n\t\t);\n\t}\n\treturn context;\n};\n\nexport type AvatarProps = React.HTMLAttributes<HTMLSpanElement> & {\n\t/**\n\t * Render as a different element using the asChild pattern.\n\t * When true, Avatar renders its child and merges props.\n\t */\n\tasChild?: boolean;\n};\n\n/**\n * Root avatar component that provides context for image loading state.\n *\n * @example\n * ```tsx\n * <Avatar className=\"w-10 h-10 rounded-full overflow-hidden\">\n * <AvatarImage src=\"/photo.jpg\" alt=\"John\" />\n * <AvatarFallback name=\"John Doe\" />\n * </Avatar>\n * ```\n */\nexport const Avatar = React.forwardRef<HTMLSpanElement, AvatarProps>(\n\t({ children, className, style, asChild = false, ...props }, ref) => {\n\t\tconst [imageLoadingStatus, setImageLoadingStatus] =\n\t\t\tReact.useState<ImageLoadingStatus>(\"idle\");\n\n\t\tconst contextValue: AvatarContextValue = React.useMemo(\n\t\t\t() => ({\n\t\t\t\timageLoadingStatus,\n\t\t\t\tonImageLoadingStatusChange: setImageLoadingStatus,\n\t\t\t}),\n\t\t\t[imageLoadingStatus]\n\t\t);\n\n\t\tconst Element = asChild ? React.Fragment : \"span\";\n\t\tconst elementProps = asChild\n\t\t\t? {}\n\t\t\t: {\n\t\t\t\t\tref,\n\t\t\t\t\tclassName,\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\tposition: \"relative\" as const,\n\t\t\t\t\t\tdisplay: \"flex\",\n\t\t\t\t\t\talignItems: \"center\",\n\t\t\t\t\t\tjustifyContent: \"center\",\n\t\t\t\t\t\tflexShrink: 0,\n\t\t\t\t\t\toverflow: \"hidden\",\n\t\t\t\t\t\t...style,\n\t\t\t\t\t},\n\t\t\t\t\t\"data-avatar\": \"\",\n\t\t\t\t\t\"data-state\": imageLoadingStatus,\n\t\t\t\t\t...props,\n\t\t\t\t};\n\n\t\treturn (\n\t\t\t<AvatarContext.Provider value={contextValue}>\n\t\t\t\t<Element {...elementProps}>{children}</Element>\n\t\t\t</AvatarContext.Provider>\n\t\t);\n\t}\n);\n\nAvatar.displayName = \"Avatar\";\n","import * as React from \"react\";\nimport { useAvatarContext } from \"./avatar\";\nimport { Facehash, type FacehashProps } from \"./facehash\";\n\nconst WHITESPACE_REGEX = /\\s+/;\n\nexport type AvatarFallbackProps = Omit<\n\tReact.HTMLAttributes<HTMLSpanElement>,\n\t\"children\"\n> & {\n\t/**\n\t * The name to derive initials and Facehash from.\n\t */\n\tname?: string;\n\n\t/**\n\t * Delay in milliseconds before showing the fallback.\n\t * Useful to prevent flashing when images load quickly.\n\t * @default 0\n\t */\n\tdelayMs?: number;\n\n\t/**\n\t * Custom children to render instead of initials or Facehash.\n\t */\n\tchildren?: React.ReactNode;\n\n\t/**\n\t * Use the Facehash component as fallback instead of initials.\n\t * @default true\n\t */\n\tfacehash?: boolean;\n\n\t/**\n\t * Props to pass to the Facehash component.\n\t */\n\tfacehashProps?: Omit<FacehashProps, \"name\">;\n};\n\n/**\n * Extracts initials from a name string.\n */\nfunction getInitials(name: string): string {\n\tconst parts = name.trim().split(WHITESPACE_REGEX);\n\tif (parts.length === 0) {\n\t\treturn \"\";\n\t}\n\tif (parts.length === 1) {\n\t\treturn parts[0]?.charAt(0).toUpperCase() || \"\";\n\t}\n\n\tconst firstInitial = parts[0]?.charAt(0) || \"\";\n\tconst lastInitial = parts.at(-1)?.charAt(0) || \"\";\n\treturn (firstInitial + lastInitial).toUpperCase();\n}\n\n/**\n * Fallback component that displays when the image fails to load.\n * Uses Facehash by default, can show initials or custom content.\n *\n * @example\n * ```tsx\n * // With Facehash (default)\n * <AvatarFallback name=\"John Doe\" />\n *\n * // With initials\n * <AvatarFallback name=\"John Doe\" facehash={false} />\n *\n * // With custom content\n * <AvatarFallback>\n * <UserIcon />\n * </AvatarFallback>\n * ```\n */\nexport const AvatarFallback = React.forwardRef<\n\tHTMLSpanElement,\n\tAvatarFallbackProps\n>(\n\t(\n\t\t{\n\t\t\tname = \"\",\n\t\t\tdelayMs = 0,\n\t\t\tchildren,\n\t\t\tfacehash = true,\n\t\t\tfacehashProps,\n\t\t\tclassName,\n\t\t\tstyle,\n\t\t\t...props\n\t\t},\n\t\tref\n\t) => {\n\t\tconst { imageLoadingStatus } = useAvatarContext();\n\t\tconst [canRender, setCanRender] = React.useState(delayMs === 0);\n\n\t\tReact.useEffect(() => {\n\t\t\tif (delayMs > 0) {\n\t\t\t\tconst timerId = window.setTimeout(() => setCanRender(true), delayMs);\n\t\t\t\treturn () => window.clearTimeout(timerId);\n\t\t\t}\n\t\t}, [delayMs]);\n\n\t\tconst initials = React.useMemo(() => getInitials(name), [name]);\n\n\t\tconst shouldRender =\n\t\t\tcanRender &&\n\t\t\timageLoadingStatus !== \"loaded\" &&\n\t\t\timageLoadingStatus !== \"loading\";\n\n\t\tif (!shouldRender) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Custom children take precedence\n\t\tif (children) {\n\t\t\treturn (\n\t\t\t\t<span\n\t\t\t\t\tclassName={className}\n\t\t\t\t\tdata-avatar-fallback=\"\"\n\t\t\t\t\tref={ref}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tdisplay: \"flex\",\n\t\t\t\t\t\talignItems: \"center\",\n\t\t\t\t\t\tjustifyContent: \"center\",\n\t\t\t\t\t\twidth: \"100%\",\n\t\t\t\t\t\theight: \"100%\",\n\t\t\t\t\t\t...style,\n\t\t\t\t\t}}\n\t\t\t\t\t{...props}\n\t\t\t\t>\n\t\t\t\t\t{children}\n\t\t\t\t</span>\n\t\t\t);\n\t\t}\n\n\t\t// Facehash mode (default)\n\t\tif (facehash) {\n\t\t\treturn (\n\t\t\t\t<Facehash\n\t\t\t\t\tclassName={className}\n\t\t\t\t\tdata-avatar-fallback=\"\"\n\t\t\t\t\tname={name}\n\t\t\t\t\tref={ref as React.Ref<HTMLDivElement>}\n\t\t\t\t\tsize=\"100%\"\n\t\t\t\t\t{...facehashProps}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\t...style,\n\t\t\t\t\t}}\n\t\t\t\t\t{...props}\n\t\t\t\t/>\n\t\t\t);\n\t\t}\n\n\t\t// Initials mode\n\t\treturn (\n\t\t\t<span\n\t\t\t\tclassName={className}\n\t\t\t\tdata-avatar-fallback=\"\"\n\t\t\t\tref={ref}\n\t\t\t\tstyle={{\n\t\t\t\t\tdisplay: \"flex\",\n\t\t\t\t\talignItems: \"center\",\n\t\t\t\t\tjustifyContent: \"center\",\n\t\t\t\t\twidth: \"100%\",\n\t\t\t\t\theight: \"100%\",\n\t\t\t\t\t...style,\n\t\t\t\t}}\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t{initials}\n\t\t\t</span>\n\t\t);\n\t}\n);\n\nAvatarFallback.displayName = \"AvatarFallback\";\n","import * as React from \"react\";\nimport { useAvatarContext } from \"./avatar\";\n\ntype ImageLoadingStatus = \"idle\" | \"loading\" | \"loaded\" | \"error\";\n\nexport type AvatarImageProps = Omit<\n\tReact.ImgHTMLAttributes<HTMLImageElement>,\n\t\"src\"\n> & {\n\t/**\n\t * The image source URL. If empty or undefined, triggers error state.\n\t */\n\tsrc?: string | null;\n\n\t/**\n\t * Callback when the image loading status changes.\n\t */\n\tonLoadingStatusChange?: (status: ImageLoadingStatus) => void;\n};\n\n/**\n * Image component that syncs its loading state with the Avatar context.\n * Automatically hides when loading fails, allowing fallback to show.\n *\n * @example\n * ```tsx\n * <Avatar>\n * <AvatarImage src=\"/photo.jpg\" alt=\"User\" />\n * <AvatarFallback name=\"John Doe\" />\n * </Avatar>\n * ```\n */\nexport const AvatarImage = React.forwardRef<HTMLImageElement, AvatarImageProps>(\n\t(\n\t\t{ src, alt = \"\", className, style, onLoadingStatusChange, ...props },\n\t\tref\n\t) => {\n\t\tconst { imageLoadingStatus, onImageLoadingStatusChange } =\n\t\t\tuseAvatarContext();\n\n\t\tconst imageRef = React.useRef<HTMLImageElement>(null);\n\t\t// biome-ignore lint/style/noNonNullAssertion: ref is guaranteed to be set\n\t\tReact.useImperativeHandle(ref, () => imageRef.current!);\n\n\t\tconst updateStatus = React.useCallback(\n\t\t\t(status: ImageLoadingStatus) => {\n\t\t\t\tonImageLoadingStatusChange(status);\n\t\t\t\tonLoadingStatusChange?.(status);\n\t\t\t},\n\t\t\t[onImageLoadingStatusChange, onLoadingStatusChange]\n\t\t);\n\n\t\tReact.useLayoutEffect(() => {\n\t\t\tif (!src) {\n\t\t\t\tupdateStatus(\"error\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet isMounted = true;\n\t\t\tconst image = new Image();\n\n\t\t\tconst setStatus = (status: ImageLoadingStatus) => {\n\t\t\t\tif (!isMounted) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tupdateStatus(status);\n\t\t\t};\n\n\t\t\tsetStatus(\"loading\");\n\n\t\t\timage.onload = () => setStatus(\"loaded\");\n\t\t\timage.onerror = () => setStatus(\"error\");\n\t\t\timage.src = src;\n\n\t\t\treturn () => {\n\t\t\t\tisMounted = false;\n\t\t\t};\n\t\t}, [src, updateStatus]);\n\n\t\tif (imageLoadingStatus !== \"loaded\") {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn (\n\t\t\t// biome-ignore lint/performance/noImgElement: This is a library component, not a Next.js app\n\t\t\t// biome-ignore lint/nursery/useImageSize: Size is controlled by parent container\n\t\t\t<img\n\t\t\t\talt={alt}\n\t\t\t\tclassName={className}\n\t\t\t\tdata-avatar-image=\"\"\n\t\t\t\tref={imageRef}\n\t\t\t\tsrc={src ?? undefined}\n\t\t\t\tstyle={{\n\t\t\t\t\taspectRatio: \"1 / 1\",\n\t\t\t\t\twidth: \"100%\",\n\t\t\t\t\theight: \"100%\",\n\t\t\t\t\tobjectFit: \"cover\",\n\t\t\t\t\t...style,\n\t\t\t\t}}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t);\n\t}\n);\n\nAvatarImage.displayName = \"AvatarImage\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["RoundFace: React.FC<FaceProps>","CrossFace: React.FC<FaceProps>","LineFace: React.FC<FaceProps>","CurvedFace: React.FC<FaceProps>","DEFAULT_GRADIENT_STYLE: React.CSSProperties","contextValue: AvatarContextValue"],"sources":["../src/faces.tsx","../src/facehash.tsx","../src/avatar.tsx","../src/avatar-fallback.tsx","../src/avatar-image.tsx"],"sourcesContent":["import type * as React from \"react\";\n\nexport type FaceProps = {\n\tclassName?: string;\n\tstyle?: React.CSSProperties;\n};\n\n/**\n * Round eyes face - simple circular eyes\n */\nexport const RoundFace: React.FC<FaceProps> = ({ className, style }) => (\n\t<svg\n\t\taria-hidden=\"true\"\n\t\tclassName={className}\n\t\tfill=\"none\"\n\t\tstyle={style}\n\t\tviewBox=\"0 0 63 15\"\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t>\n\t\t<title>Round Eyes</title>\n\t\t<path\n\t\t\td=\"M62.4 7.2C62.4 11.1765 59.1765 14.4 55.2 14.4C51.2236 14.4 48 11.1765 48 7.2C48 3.22355 51.2236 0 55.2 0C59.1765 0 62.4 3.22355 62.4 7.2Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t\t<path\n\t\t\td=\"M14.4 7.2C14.4 11.1765 11.1765 14.4 7.2 14.4C3.22355 14.4 0 11.1765 0 7.2C0 3.22355 3.22355 0 7.2 0C11.1765 0 14.4 3.22355 14.4 7.2Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t</svg>\n);\n\n/**\n * Cross eyes face - X-shaped eyes\n */\nexport const CrossFace: React.FC<FaceProps> = ({ className, style }) => (\n\t<svg\n\t\taria-hidden=\"true\"\n\t\tclassName={className}\n\t\tfill=\"none\"\n\t\tstyle={style}\n\t\tviewBox=\"0 0 71 23\"\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t>\n\t\t<title>Cross Eyes</title>\n\t\t<path\n\t\t\td=\"M11.5 0C12.9411 0 13.6619 0.000460386 14.1748 0.354492C14.3742 0.49213 14.547 0.664882 14.6846 0.864258C15.0384 1.37711 15.0391 2.09739 15.0391 3.53809V7.96094H19.4619C20.9027 7.96094 21.6229 7.9615 22.1357 8.31543C22.3352 8.45308 22.5079 8.62578 22.6455 8.8252C22.9995 9.3381 23 10.0589 23 11.5C23 12.9408 22.9995 13.661 22.6455 14.1738C22.5079 14.3733 22.3352 14.5459 22.1357 14.6836C21.6229 15.0375 20.9027 15.0381 19.4619 15.0381H15.0391V19.4619C15.0391 20.9026 15.0384 21.6229 14.6846 22.1357C14.547 22.3351 14.3742 22.5079 14.1748 22.6455C13.6619 22.9995 12.9411 23 11.5 23C10.0592 23 9.33903 22.9994 8.82617 22.6455C8.62674 22.5079 8.45309 22.3352 8.31543 22.1357C7.96175 21.6229 7.96191 20.9024 7.96191 19.4619V15.0381H3.53809C2.0973 15.0381 1.37711 15.0375 0.864258 14.6836C0.664834 14.5459 0.492147 14.3733 0.354492 14.1738C0.000498831 13.661 -5.88036e-08 12.9408 0 11.5C6.2999e-08 10.0589 0.000460356 9.3381 0.354492 8.8252C0.492144 8.62578 0.664842 8.45308 0.864258 8.31543C1.37711 7.9615 2.09731 7.96094 3.53809 7.96094H7.96191V3.53809C7.96191 2.09765 7.96175 1.37709 8.31543 0.864258C8.45309 0.664828 8.62674 0.492149 8.82617 0.354492C9.33903 0.000555366 10.0592 1.62347e-09 11.5 0Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t\t<path\n\t\t\td=\"M58.7695 0C60.2107 0 60.9314 0.000460386 61.4443 0.354492C61.6437 0.49213 61.8165 0.664882 61.9541 0.864258C62.308 1.37711 62.3086 2.09739 62.3086 3.53809V7.96094H66.7314C68.1722 7.96094 68.8924 7.9615 69.4053 8.31543C69.6047 8.45308 69.7774 8.62578 69.915 8.8252C70.2691 9.3381 70.2695 10.0589 70.2695 11.5C70.2695 12.9408 70.269 13.661 69.915 14.1738C69.7774 14.3733 69.6047 14.5459 69.4053 14.6836C68.8924 15.0375 68.1722 15.0381 66.7314 15.0381H62.3086V19.4619C62.3086 20.9026 62.308 21.6229 61.9541 22.1357C61.8165 22.3351 61.6437 22.5079 61.4443 22.6455C60.9314 22.9995 60.2107 23 58.7695 23C57.3287 23 56.6086 22.9994 56.0957 22.6455C55.8963 22.5079 55.7226 22.3352 55.585 22.1357C55.2313 21.6229 55.2314 20.9024 55.2314 19.4619V15.0381H50.8076C49.3668 15.0381 48.6466 15.0375 48.1338 14.6836C47.9344 14.5459 47.7617 14.3733 47.624 14.1738C47.27 13.661 47.2695 12.9408 47.2695 11.5C47.2695 10.0589 47.27 9.3381 47.624 8.8252C47.7617 8.62578 47.9344 8.45308 48.1338 8.31543C48.6466 7.9615 49.3668 7.96094 50.8076 7.96094H55.2314V3.53809C55.2314 2.09765 55.2313 1.37709 55.585 0.864258C55.7226 0.664828 55.8963 0.492149 56.0957 0.354492C56.6086 0.000555366 57.3287 1.62347e-09 58.7695 0Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t</svg>\n);\n\n/**\n * Line eyes face - horizontal line eyes\n */\nexport const LineFace: React.FC<FaceProps> = ({ className, style }) => (\n\t<svg\n\t\taria-hidden=\"true\"\n\t\tclassName={className}\n\t\tfill=\"none\"\n\t\tstyle={style}\n\t\tviewBox=\"0 0 82 8\"\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t>\n\t\t<title>Line Eyes</title>\n\t\t<path\n\t\t\td=\"M3.53125 0.164063C4.90133 0.164063 5.58673 0.163893 6.08301 0.485352C6.31917 0.638428 6.52075 0.840012 6.67383 1.07617C6.99555 1.57252 6.99512 2.25826 6.99512 3.62891C6.99512 4.99911 6.99536 5.68438 6.67383 6.18066C6.52075 6.41682 6.31917 6.61841 6.08301 6.77148C5.58672 7.09305 4.90147 7.09277 3.53125 7.09277C2.16062 7.09277 1.47486 7.09319 0.978516 6.77148C0.742356 6.61841 0.540772 6.41682 0.387695 6.18066C0.0662401 5.68439 0.0664063 4.999 0.0664063 3.62891C0.0664063 2.25838 0.0660571 1.57251 0.387695 1.07617C0.540772 0.840012 0.742356 0.638428 0.978516 0.485352C1.47485 0.163744 2.16076 0.164063 3.53125 0.164063Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t\t<path\n\t\t\td=\"M25.1836 0.164063C26.5542 0.164063 27.24 0.163638 27.7363 0.485352C27.9724 0.638384 28.1731 0.8401 28.3262 1.07617C28.6479 1.57252 28.6484 2.25825 28.6484 3.62891C28.6484 4.99931 28.6478 5.68436 28.3262 6.18066C28.1731 6.41678 27.9724 6.61842 27.7363 6.77148C27.24 7.09321 26.5542 7.09277 25.1836 7.09277H11.3262C9.95557 7.09277 9.26978 7.09317 8.77344 6.77148C8.53728 6.61841 8.33569 6.41682 8.18262 6.18066C7.86115 5.68438 7.86133 4.99902 7.86133 3.62891C7.86133 2.25835 7.86096 1.57251 8.18262 1.07617C8.33569 0.840012 8.53728 0.638428 8.77344 0.485352C9.26977 0.163768 9.95572 0.164063 11.3262 0.164063H25.1836Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t\t<path\n\t\t\td=\"M78.2034 7.09325C76.8333 7.09325 76.1479 7.09342 75.6516 6.77197C75.4155 6.61889 75.2139 6.4173 75.0608 6.18114C74.7391 5.6848 74.7395 4.99905 74.7395 3.62841C74.7395 2.2582 74.7393 1.57294 75.0608 1.07665C75.2139 0.840493 75.4155 0.638909 75.6516 0.485832C76.1479 0.164271 76.8332 0.164543 78.2034 0.164543C79.574 0.164543 80.2598 0.164122 80.7561 0.485832C80.9923 0.638909 81.1939 0.840493 81.347 1.07665C81.6684 1.57293 81.6682 2.25831 81.6682 3.62841C81.6682 4.99894 81.6686 5.68481 81.347 6.18114C81.1939 6.4173 80.9923 6.61889 80.7561 6.77197C80.2598 7.09357 79.5739 7.09325 78.2034 7.09325Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t\t<path\n\t\t\td=\"M56.5511 7.09325C55.1804 7.09325 54.4947 7.09368 53.9983 6.77197C53.7622 6.61893 53.5615 6.41722 53.4085 6.18114C53.0868 5.6848 53.0862 4.99907 53.0862 3.62841C53.0862 2.258 53.0868 1.57296 53.4085 1.07665C53.5615 0.840539 53.7622 0.638898 53.9983 0.485832C54.4947 0.164105 55.1804 0.164543 56.5511 0.164543H70.4085C71.7791 0.164543 72.4649 0.164146 72.9612 0.485832C73.1974 0.638909 73.399 0.840493 73.552 1.07665C73.8735 1.57293 73.8733 2.25829 73.8733 3.62841C73.8733 4.99896 73.8737 5.68481 73.552 6.18114C73.399 6.4173 73.1974 6.61889 72.9612 6.77197C72.4649 7.09355 71.7789 7.09325 70.4085 7.09325H56.5511Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t</svg>\n);\n\n/**\n * Curved eyes face - sleepy/happy curved eyes\n */\nexport const CurvedFace: React.FC<FaceProps> = ({ className, style }) => (\n\t<svg\n\t\taria-hidden=\"true\"\n\t\tclassName={className}\n\t\tfill=\"none\"\n\t\tstyle={style}\n\t\tviewBox=\"0 0 63 9\"\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t>\n\t\t<title>Curved Eyes</title>\n\t\t<path\n\t\t\td=\"M0 5.06511C0 4.94513 0 4.88513 0.00771184 4.79757C0.0483059 4.33665 0.341025 3.76395 0.690821 3.46107C0.757274 3.40353 0.783996 3.38422 0.837439 3.34559C2.40699 2.21129 6.03888 0 10.5 0C14.9611 0 18.593 2.21129 20.1626 3.34559C20.216 3.38422 20.2427 3.40353 20.3092 3.46107C20.659 3.76395 20.9517 4.33665 20.9923 4.79757C21 4.88513 21 4.94513 21 5.06511C21 6.01683 21 6.4927 20.9657 6.6754C20.7241 7.96423 19.8033 8.55941 18.5289 8.25054C18.3483 8.20676 17.8198 7.96876 16.7627 7.49275C14.975 6.68767 12.7805 6 10.5 6C8.21954 6 6.02504 6.68767 4.23727 7.49275C3.18025 7.96876 2.65174 8.20676 2.47108 8.25054C1.19668 8.55941 0.275917 7.96423 0.0342566 6.6754C0 6.4927 0 6.01683 0 5.06511Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t\t<path\n\t\t\td=\"M42 5.06511C42 4.94513 42 4.88513 42.0077 4.79757C42.0483 4.33665 42.341 3.76395 42.6908 3.46107C42.7573 3.40353 42.784 3.38422 42.8374 3.34559C44.407 2.21129 48.0389 0 52.5 0C56.9611 0 60.593 2.21129 62.1626 3.34559C62.216 3.38422 62.2427 3.40353 62.3092 3.46107C62.659 3.76395 62.9517 4.33665 62.9923 4.79757C63 4.88513 63 4.94513 63 5.06511C63 6.01683 63 6.4927 62.9657 6.6754C62.7241 7.96423 61.8033 8.55941 60.5289 8.25054C60.3483 8.20676 59.8198 7.96876 58.7627 7.49275C56.975 6.68767 54.7805 6 52.5 6C50.2195 6 48.025 6.68767 46.2373 7.49275C45.1802 7.96876 44.6517 8.20676 44.4711 8.25054C43.1967 8.55941 42.2759 7.96423 42.0343 6.6754C42 6.4927 42 6.01683 42 5.06511Z\"\n\t\t\tfill=\"currentColor\"\n\t\t/>\n\t</svg>\n);\n\n/**\n * All available face components\n */\nexport const FACES = [RoundFace, CrossFace, LineFace, CurvedFace] as const;\n\nexport type FaceComponent = (typeof FACES)[number];\n","import * as React from \"react\";\nimport { FACES } from \"./faces\";\nimport { stringHash } from \"./utils/hash\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type Intensity3D = \"none\" | \"subtle\" | \"medium\" | \"dramatic\";\nexport type Variant = \"gradient\" | \"solid\";\n\nexport interface FacehashProps\n\textends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\"> {\n\t/**\n\t * String to generate a deterministic face from.\n\t * Same string always produces the same face.\n\t */\n\tname: string;\n\n\t/**\n\t * Size in pixels or CSS units.\n\t * @default 40\n\t */\n\tsize?: number | string;\n\n\t/**\n\t * Background style.\n\t * - \"gradient\": Adds gradient overlay (default)\n\t * - \"solid\": Plain background color\n\t * @default \"gradient\"\n\t */\n\tvariant?: Variant;\n\n\t/**\n\t * 3D effect intensity.\n\t * @default \"dramatic\"\n\t */\n\tintensity3d?: Intensity3D;\n\n\t/**\n\t * Enable hover interaction.\n\t * When true, face \"looks straight\" on hover.\n\t * @default true\n\t */\n\tinteractive?: boolean;\n\n\t/**\n\t * Show first letter of name below the face.\n\t * @default true\n\t */\n\tshowInitial?: boolean;\n\n\t/**\n\t * Hex color array for inline styles.\n\t * Use this OR colorClasses, not both.\n\t */\n\tcolors?: string[];\n\n\t/**\n\t * Tailwind class array for background colors.\n\t * Example: [\"bg-pink-500 dark:bg-pink-600\", \"bg-blue-500 dark:bg-blue-600\"]\n\t * Use this OR colors, not both.\n\t */\n\tcolorClasses?: string[];\n\n\t/**\n\t * Custom gradient overlay class (Tailwind).\n\t * When provided, replaces the default pure CSS gradient.\n\t * Only used when variant=\"gradient\".\n\t */\n\tgradientOverlayClass?: string;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst INTENSITY_PRESETS = {\n\tnone: {\n\t\trotateRange: 0,\n\t\ttranslateZ: 0,\n\t\tperspective: \"none\",\n\t},\n\tsubtle: {\n\t\trotateRange: 5,\n\t\ttranslateZ: 4,\n\t\tperspective: \"800px\",\n\t},\n\tmedium: {\n\t\trotateRange: 10,\n\t\ttranslateZ: 8,\n\t\tperspective: \"500px\",\n\t},\n\tdramatic: {\n\t\trotateRange: 15,\n\t\ttranslateZ: 12,\n\t\tperspective: \"300px\",\n\t},\n} as const;\n\nconst SPHERE_POSITIONS = [\n\t{ x: -1, y: 1 }, // down-right\n\t{ x: 1, y: 1 }, // up-right\n\t{ x: 1, y: 0 }, // up\n\t{ x: 0, y: 1 }, // right\n\t{ x: -1, y: 0 }, // down\n\t{ x: 0, y: 0 }, // center\n\t{ x: 0, y: -1 }, // left\n\t{ x: -1, y: -1 }, // down-left\n\t{ x: 1, y: -1 }, // up-left\n] as const;\n\n// Default gradient as pure CSS (works without Tailwind)\n// Matches: bg-[radial-gradient(ellipse_100%_100%_at_50%_50%,_COLOR_0%,_transparent_60%)]\n// Light mode: white glow in center, Dark mode: dark overlay in center\nconst DEFAULT_GRADIENT_STYLE: React.CSSProperties = {\n\tbackground:\n\t\t\"radial-gradient(ellipse 100% 100% at 50% 50%, rgba(255,255,255,0.15) 0%, transparent 60%)\",\n};\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * Facehash - Deterministic avatar faces from any string.\n *\n * @example\n * ```tsx\n * // With Tailwind classes\n * <Facehash\n * name=\"John\"\n * colorClasses={[\"bg-pink-500\", \"bg-blue-500\"]}\n * />\n *\n * // With hex colors\n * <Facehash\n * name=\"John\"\n * colors={[\"#ec4899\", \"#3b82f6\"]}\n * />\n *\n * // Plain color (no gradient)\n * <Facehash name=\"John\" variant=\"solid\" />\n * ```\n */\nexport const Facehash = React.forwardRef<HTMLDivElement, FacehashProps>(\n\t(\n\t\t{\n\t\t\tname,\n\t\t\tsize = 40,\n\t\t\tvariant = \"gradient\",\n\t\t\tintensity3d = \"dramatic\",\n\t\t\tinteractive = true,\n\t\t\tshowInitial = true,\n\t\t\tcolors,\n\t\t\tcolorClasses,\n\t\t\tgradientOverlayClass,\n\t\t\tclassName,\n\t\t\tstyle,\n\t\t\tonMouseEnter,\n\t\t\tonMouseLeave,\n\t\t\t...props\n\t\t},\n\t\tref\n\t) => {\n\t\tconst [isHovered, setIsHovered] = React.useState(false);\n\n\t\t// Generate deterministic values from name\n\t\tconst { FaceComponent, colorIndex, rotation } = React.useMemo(() => {\n\t\t\tconst hash = stringHash(name);\n\t\t\tconst faceIndex = hash % FACES.length;\n\t\t\tconst colorsLength = colorClasses?.length ?? colors?.length ?? 1;\n\t\t\tconst _colorIndex = hash % colorsLength;\n\t\t\tconst positionIndex = hash % SPHERE_POSITIONS.length;\n\t\t\tconst position = SPHERE_POSITIONS[positionIndex] ?? { x: 0, y: 0 };\n\n\t\t\treturn {\n\t\t\t\tFaceComponent: FACES[faceIndex] ?? FACES[0],\n\t\t\t\tcolorIndex: _colorIndex,\n\t\t\t\trotation: position,\n\t\t\t};\n\t\t}, [name, colors?.length, colorClasses?.length]);\n\n\t\t// Get intensity preset\n\t\tconst preset = INTENSITY_PRESETS[intensity3d];\n\n\t\t// Calculate 3D transform\n\t\tconst transform = React.useMemo(() => {\n\t\t\tif (intensity3d === \"none\") {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst rotateX =\n\t\t\t\tisHovered && interactive ? 0 : rotation.x * preset.rotateRange;\n\t\t\tconst rotateY =\n\t\t\t\tisHovered && interactive ? 0 : rotation.y * preset.rotateRange;\n\n\t\t\treturn `rotateX(${rotateX}deg) rotateY(${rotateY}deg) translateZ(${preset.translateZ}px)`;\n\t\t}, [intensity3d, isHovered, interactive, rotation, preset]);\n\n\t\t// Size style\n\t\tconst sizeValue = typeof size === \"number\" ? `${size}px` : size;\n\n\t\t// Initial letter\n\t\tconst initial = name.charAt(0).toUpperCase();\n\n\t\t// Background: either hex color (inline) or class\n\t\tconst bgColorClass = colorClasses?.[colorIndex];\n\t\tconst bgColorHex = colors?.[colorIndex];\n\n\t\t// Event handlers\n\t\tconst handleMouseEnter = React.useCallback(\n\t\t\t(e: React.MouseEvent<HTMLDivElement>) => {\n\t\t\t\tif (interactive) {\n\t\t\t\t\tsetIsHovered(true);\n\t\t\t\t}\n\t\t\t\tonMouseEnter?.(e);\n\t\t\t},\n\t\t\t[interactive, onMouseEnter]\n\t\t);\n\n\t\tconst handleMouseLeave = React.useCallback(\n\t\t\t(e: React.MouseEvent<HTMLDivElement>) => {\n\t\t\t\tif (interactive) {\n\t\t\t\t\tsetIsHovered(false);\n\t\t\t\t}\n\t\t\t\tonMouseLeave?.(e);\n\t\t\t},\n\t\t\t[interactive, onMouseLeave]\n\t\t);\n\n\t\treturn (\n\t\t\t// biome-ignore lint/a11y/noNoninteractiveElementInteractions: Hover effect is purely cosmetic\n\t\t\t// biome-ignore lint/a11y/noStaticElementInteractions: This is a decorative avatar component\n\t\t\t<div\n\t\t\t\tclassName={[bgColorClass, className].filter(Boolean).join(\" \")}\n\t\t\t\tdata-facehash=\"\"\n\t\t\t\tdata-interactive={interactive || undefined}\n\t\t\t\tonMouseEnter={handleMouseEnter}\n\t\t\t\tonMouseLeave={handleMouseLeave}\n\t\t\t\tref={ref}\n\t\t\t\tstyle={{\n\t\t\t\t\t// Size\n\t\t\t\t\twidth: sizeValue,\n\t\t\t\t\theight: sizeValue,\n\t\t\t\t\t// Layout\n\t\t\t\t\tposition: \"relative\",\n\t\t\t\t\tdisplay: \"flex\",\n\t\t\t\t\talignItems: \"center\",\n\t\t\t\t\tjustifyContent: \"center\",\n\t\t\t\t\toverflow: \"hidden\",\n\t\t\t\t\t// Container for cqw units\n\t\t\t\t\tcontainerType: \"size\",\n\t\t\t\t\t// 3D setup\n\t\t\t\t\t...(intensity3d !== \"none\" && {\n\t\t\t\t\t\tperspective: preset.perspective,\n\t\t\t\t\t\ttransformStyle: \"preserve-3d\",\n\t\t\t\t\t}),\n\t\t\t\t\t// Background color (hex) - only if no colorClasses\n\t\t\t\t\t...(bgColorHex && !bgColorClass && { backgroundColor: bgColorHex }),\n\t\t\t\t\t// User styles (last to allow overrides)\n\t\t\t\t\t...style,\n\t\t\t\t}}\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t{/* Gradient overlay */}\n\t\t\t\t{variant === \"gradient\" && (\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName={gradientOverlayClass}\n\t\t\t\t\t\tdata-facehash-gradient=\"\"\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\tinset: 0,\n\t\t\t\t\t\t\tpointerEvents: \"none\",\n\t\t\t\t\t\t\tzIndex: 1,\n\t\t\t\t\t\t\t// Use default pure CSS gradient if no class provided\n\t\t\t\t\t\t\t...(gradientOverlayClass ? {} : DEFAULT_GRADIENT_STYLE),\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t)}\n\n\t\t\t\t{/* Face container with 3D transform */}\n\t\t\t\t<div\n\t\t\t\t\tdata-facehash-face=\"\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\tinset: 0,\n\t\t\t\t\t\tdisplay: \"flex\",\n\t\t\t\t\t\tflexDirection: \"column\",\n\t\t\t\t\t\talignItems: \"center\",\n\t\t\t\t\t\tjustifyContent: \"center\",\n\t\t\t\t\t\tzIndex: 2,\n\t\t\t\t\t\ttransform,\n\t\t\t\t\t\ttransformStyle: intensity3d !== \"none\" ? \"preserve-3d\" : undefined,\n\t\t\t\t\t\ttransition: interactive\n\t\t\t\t\t\t\t? \"transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)\"\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\t// Default to black text/icons for contrast on colored backgrounds\n\t\t\t\t\t\tcolor: \"#000000\",\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{/* Face SVG */}\n\t\t\t\t\t<FaceComponent\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\twidth: \"60%\",\n\t\t\t\t\t\t\theight: \"auto\",\n\t\t\t\t\t\t\tmaxWidth: \"90%\",\n\t\t\t\t\t\t\tmaxHeight: \"40%\",\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\n\t\t\t\t\t{/* Initial letter */}\n\t\t\t\t\t{showInitial && (\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tdata-facehash-initial=\"\"\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\tmarginTop: \"8%\",\n\t\t\t\t\t\t\t\tfontSize: \"26cqw\",\n\t\t\t\t\t\t\t\tlineHeight: 1,\n\t\t\t\t\t\t\t\tfontFamily: \"monospace\",\n\t\t\t\t\t\t\t\tfontWeight: \"bold\",\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{initial}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t}\n);\n\nFacehash.displayName = \"Facehash\";\n","import * as React from \"react\";\n\ntype ImageLoadingStatus = \"idle\" | \"loading\" | \"loaded\" | \"error\";\n\nexport type AvatarContextValue = {\n\timageLoadingStatus: ImageLoadingStatus;\n\tonImageLoadingStatusChange: (status: ImageLoadingStatus) => void;\n};\n\nconst AvatarContext = React.createContext<AvatarContextValue | null>(null);\n\n/**\n * Hook to access the Avatar context.\n * Throws an error if used outside of Avatar.\n */\nexport const useAvatarContext = () => {\n\tconst context = React.useContext(AvatarContext);\n\tif (!context) {\n\t\tthrow new Error(\n\t\t\t\"Avatar compound components must be rendered within an Avatar component\"\n\t\t);\n\t}\n\treturn context;\n};\n\nexport type AvatarProps = React.HTMLAttributes<HTMLSpanElement> & {\n\t/**\n\t * Render as a different element using the asChild pattern.\n\t * When true, Avatar renders its child and merges props.\n\t */\n\tasChild?: boolean;\n};\n\n/**\n * Root avatar component that provides context for image loading state.\n *\n * @example\n * ```tsx\n * <Avatar className=\"w-10 h-10 rounded-full overflow-hidden\">\n * <AvatarImage src=\"/photo.jpg\" alt=\"John\" />\n * <AvatarFallback name=\"John Doe\" />\n * </Avatar>\n * ```\n */\nexport const Avatar = React.forwardRef<HTMLSpanElement, AvatarProps>(\n\t({ children, className, style, asChild = false, ...props }, ref) => {\n\t\tconst [imageLoadingStatus, setImageLoadingStatus] =\n\t\t\tReact.useState<ImageLoadingStatus>(\"idle\");\n\n\t\tconst contextValue: AvatarContextValue = React.useMemo(\n\t\t\t() => ({\n\t\t\t\timageLoadingStatus,\n\t\t\t\tonImageLoadingStatusChange: setImageLoadingStatus,\n\t\t\t}),\n\t\t\t[imageLoadingStatus]\n\t\t);\n\n\t\tconst Element = asChild ? React.Fragment : \"span\";\n\t\tconst elementProps = asChild\n\t\t\t? {}\n\t\t\t: {\n\t\t\t\t\tref,\n\t\t\t\t\tclassName,\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\tposition: \"relative\" as const,\n\t\t\t\t\t\tdisplay: \"flex\",\n\t\t\t\t\t\talignItems: \"center\",\n\t\t\t\t\t\tjustifyContent: \"center\",\n\t\t\t\t\t\tflexShrink: 0,\n\t\t\t\t\t\toverflow: \"hidden\",\n\t\t\t\t\t\t...style,\n\t\t\t\t\t},\n\t\t\t\t\t\"data-avatar\": \"\",\n\t\t\t\t\t\"data-state\": imageLoadingStatus,\n\t\t\t\t\t...props,\n\t\t\t\t};\n\n\t\treturn (\n\t\t\t<AvatarContext.Provider value={contextValue}>\n\t\t\t\t<Element {...elementProps}>{children}</Element>\n\t\t\t</AvatarContext.Provider>\n\t\t);\n\t}\n);\n\nAvatar.displayName = \"Avatar\";\n","import * as React from \"react\";\nimport { useAvatarContext } from \"./avatar\";\nimport { Facehash, type FacehashProps } from \"./facehash\";\n\nconst WHITESPACE_REGEX = /\\s+/;\n\nexport type AvatarFallbackProps = Omit<\n\tReact.HTMLAttributes<HTMLSpanElement>,\n\t\"children\"\n> & {\n\t/**\n\t * The name to derive initials and Facehash from.\n\t */\n\tname?: string;\n\n\t/**\n\t * Delay in milliseconds before showing the fallback.\n\t * Useful to prevent flashing when images load quickly.\n\t * @default 0\n\t */\n\tdelayMs?: number;\n\n\t/**\n\t * Custom children to render instead of initials or Facehash.\n\t */\n\tchildren?: React.ReactNode;\n\n\t/**\n\t * Use the Facehash component as fallback instead of initials.\n\t * @default true\n\t */\n\tfacehash?: boolean;\n\n\t/**\n\t * Props to pass to the Facehash component.\n\t */\n\tfacehashProps?: Omit<FacehashProps, \"name\">;\n};\n\n/**\n * Extracts initials from a name string.\n */\nfunction getInitials(name: string): string {\n\tconst parts = name.trim().split(WHITESPACE_REGEX);\n\tif (parts.length === 0) {\n\t\treturn \"\";\n\t}\n\tif (parts.length === 1) {\n\t\treturn parts[0]?.charAt(0).toUpperCase() || \"\";\n\t}\n\n\tconst firstInitial = parts[0]?.charAt(0) || \"\";\n\tconst lastInitial = parts.at(-1)?.charAt(0) || \"\";\n\treturn (firstInitial + lastInitial).toUpperCase();\n}\n\n/**\n * Fallback component that displays when the image fails to load.\n * Uses Facehash by default, can show initials or custom content.\n *\n * @example\n * ```tsx\n * // With Facehash (default)\n * <AvatarFallback name=\"John Doe\" />\n *\n * // With initials\n * <AvatarFallback name=\"John Doe\" facehash={false} />\n *\n * // With custom content\n * <AvatarFallback>\n * <UserIcon />\n * </AvatarFallback>\n * ```\n */\nexport const AvatarFallback = React.forwardRef<\n\tHTMLSpanElement,\n\tAvatarFallbackProps\n>(\n\t(\n\t\t{\n\t\t\tname = \"\",\n\t\t\tdelayMs = 0,\n\t\t\tchildren,\n\t\t\tfacehash = true,\n\t\t\tfacehashProps,\n\t\t\tclassName,\n\t\t\tstyle,\n\t\t\t...props\n\t\t},\n\t\tref\n\t) => {\n\t\tconst { imageLoadingStatus } = useAvatarContext();\n\t\tconst [canRender, setCanRender] = React.useState(delayMs === 0);\n\n\t\tReact.useEffect(() => {\n\t\t\tif (delayMs > 0) {\n\t\t\t\tconst timerId = window.setTimeout(() => setCanRender(true), delayMs);\n\t\t\t\treturn () => window.clearTimeout(timerId);\n\t\t\t}\n\t\t}, [delayMs]);\n\n\t\tconst initials = React.useMemo(() => getInitials(name), [name]);\n\n\t\tconst shouldRender =\n\t\t\tcanRender &&\n\t\t\timageLoadingStatus !== \"loaded\" &&\n\t\t\timageLoadingStatus !== \"loading\";\n\n\t\tif (!shouldRender) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Custom children take precedence\n\t\tif (children) {\n\t\t\treturn (\n\t\t\t\t<span\n\t\t\t\t\tclassName={className}\n\t\t\t\t\tdata-avatar-fallback=\"\"\n\t\t\t\t\tref={ref}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tdisplay: \"flex\",\n\t\t\t\t\t\talignItems: \"center\",\n\t\t\t\t\t\tjustifyContent: \"center\",\n\t\t\t\t\t\twidth: \"100%\",\n\t\t\t\t\t\theight: \"100%\",\n\t\t\t\t\t\t...style,\n\t\t\t\t\t}}\n\t\t\t\t\t{...props}\n\t\t\t\t>\n\t\t\t\t\t{children}\n\t\t\t\t</span>\n\t\t\t);\n\t\t}\n\n\t\t// Facehash mode (default)\n\t\tif (facehash) {\n\t\t\treturn (\n\t\t\t\t<Facehash\n\t\t\t\t\tclassName={className}\n\t\t\t\t\tdata-avatar-fallback=\"\"\n\t\t\t\t\tname={name}\n\t\t\t\t\tref={ref as React.Ref<HTMLDivElement>}\n\t\t\t\t\tsize=\"100%\"\n\t\t\t\t\t{...facehashProps}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\t...style,\n\t\t\t\t\t}}\n\t\t\t\t\t{...props}\n\t\t\t\t/>\n\t\t\t);\n\t\t}\n\n\t\t// Initials mode\n\t\treturn (\n\t\t\t<span\n\t\t\t\tclassName={className}\n\t\t\t\tdata-avatar-fallback=\"\"\n\t\t\t\tref={ref}\n\t\t\t\tstyle={{\n\t\t\t\t\tdisplay: \"flex\",\n\t\t\t\t\talignItems: \"center\",\n\t\t\t\t\tjustifyContent: \"center\",\n\t\t\t\t\twidth: \"100%\",\n\t\t\t\t\theight: \"100%\",\n\t\t\t\t\t...style,\n\t\t\t\t}}\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t{initials}\n\t\t\t</span>\n\t\t);\n\t}\n);\n\nAvatarFallback.displayName = \"AvatarFallback\";\n","import * as React from \"react\";\nimport { useAvatarContext } from \"./avatar\";\n\ntype ImageLoadingStatus = \"idle\" | \"loading\" | \"loaded\" | \"error\";\n\nexport type AvatarImageProps = Omit<\n\tReact.ImgHTMLAttributes<HTMLImageElement>,\n\t\"src\"\n> & {\n\t/**\n\t * The image source URL. If empty or undefined, triggers error state.\n\t */\n\tsrc?: string | null;\n\n\t/**\n\t * Callback when the image loading status changes.\n\t */\n\tonLoadingStatusChange?: (status: ImageLoadingStatus) => void;\n};\n\n/**\n * Image component that syncs its loading state with the Avatar context.\n * Automatically hides when loading fails, allowing fallback to show.\n *\n * @example\n * ```tsx\n * <Avatar>\n * <AvatarImage src=\"/photo.jpg\" alt=\"User\" />\n * <AvatarFallback name=\"John Doe\" />\n * </Avatar>\n * ```\n */\nexport const AvatarImage = React.forwardRef<HTMLImageElement, AvatarImageProps>(\n\t(\n\t\t{ src, alt = \"\", className, style, onLoadingStatusChange, ...props },\n\t\tref\n\t) => {\n\t\tconst { imageLoadingStatus, onImageLoadingStatusChange } =\n\t\t\tuseAvatarContext();\n\n\t\tconst imageRef = React.useRef<HTMLImageElement>(null);\n\t\t// biome-ignore lint/style/noNonNullAssertion: ref is guaranteed to be set\n\t\tReact.useImperativeHandle(ref, () => imageRef.current!);\n\n\t\tconst updateStatus = React.useCallback(\n\t\t\t(status: ImageLoadingStatus) => {\n\t\t\t\tonImageLoadingStatusChange(status);\n\t\t\t\tonLoadingStatusChange?.(status);\n\t\t\t},\n\t\t\t[onImageLoadingStatusChange, onLoadingStatusChange]\n\t\t);\n\n\t\tReact.useLayoutEffect(() => {\n\t\t\tif (!src) {\n\t\t\t\tupdateStatus(\"error\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet isMounted = true;\n\t\t\tconst image = new Image();\n\n\t\t\tconst setStatus = (status: ImageLoadingStatus) => {\n\t\t\t\tif (!isMounted) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tupdateStatus(status);\n\t\t\t};\n\n\t\t\tsetStatus(\"loading\");\n\n\t\t\timage.onload = () => setStatus(\"loaded\");\n\t\t\timage.onerror = () => setStatus(\"error\");\n\t\t\timage.src = src;\n\n\t\t\treturn () => {\n\t\t\t\tisMounted = false;\n\t\t\t};\n\t\t}, [src, updateStatus]);\n\n\t\tif (imageLoadingStatus !== \"loaded\") {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn (\n\t\t\t// biome-ignore lint/performance/noImgElement: This is a library component, not a Next.js app\n\t\t\t// biome-ignore lint/nursery/useImageSize: Size is controlled by parent container\n\t\t\t<img\n\t\t\t\talt={alt}\n\t\t\t\tclassName={className}\n\t\t\t\tdata-avatar-image=\"\"\n\t\t\t\tref={imageRef}\n\t\t\t\tsrc={src ?? undefined}\n\t\t\t\tstyle={{\n\t\t\t\t\taspectRatio: \"1 / 1\",\n\t\t\t\t\twidth: \"100%\",\n\t\t\t\t\theight: \"100%\",\n\t\t\t\t\tobjectFit: \"cover\",\n\t\t\t\t\t...style,\n\t\t\t\t}}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t);\n\t}\n);\n\nAvatarImage.displayName = \"AvatarImage\";\n"],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ import { ImageResponse } from "next/og";
2
+ import { NextRequest } from "next/server";
3
+
4
+ //#region src/core/facehash-data.d.ts
5
+ type Variant = "gradient" | "solid";
6
+ type FaceType = "round" | "cross" | "line" | "curved";
7
+ type FacehashData = {
8
+ /** The face type to render */
9
+ faceType: FaceType;
10
+ /** Index into the colors array */
11
+ colorIndex: number;
12
+ /** Rotation position for 3D effect (-1, 0, or 1 for each axis) */
13
+ rotation: {
14
+ x: number;
15
+ y: number;
16
+ };
17
+ /** First letter of the name, uppercase */
18
+ initial: string;
19
+ };
20
+ /**
21
+ * Default color palette using Tailwind CSS color values.
22
+ */
23
+ declare const DEFAULT_COLORS: readonly ["#ec4899", "#f59e0b", "#3b82f6", "#f97316", "#10b981"];
24
+ //#endregion
25
+ //#region src/next/handler.d.ts
26
+ type FacehashHandlerOptions = {
27
+ /**
28
+ * Default image size in pixels.
29
+ * Can be overridden via `?size=` query param.
30
+ * @default 400
31
+ */
32
+ size?: number;
33
+ /**
34
+ * Default background style.
35
+ * Can be overridden via `?variant=` query param.
36
+ * @default "gradient"
37
+ */
38
+ variant?: Variant;
39
+ /**
40
+ * Default for showing initial letter.
41
+ * Can be overridden via `?showInitial=` query param.
42
+ * @default true
43
+ */
44
+ showInitial?: boolean;
45
+ /**
46
+ * Default color palette (hex colors).
47
+ * Can be overridden via `?colors=` query param (comma-separated).
48
+ * @default ["#ec4899", "#f59e0b", "#3b82f6", "#f97316", "#10b981"]
49
+ */
50
+ colors?: string[];
51
+ /**
52
+ * Cache-Control header value.
53
+ * Set to `null` to disable caching.
54
+ * @default "public, max-age=31536000, immutable"
55
+ */
56
+ cacheControl?: string | null;
57
+ };
58
+ type FacehashHandler = {
59
+ GET: (request: NextRequest) => Promise<ImageResponse>;
60
+ };
61
+ /**
62
+ * Creates a Next.js route handler for generating Facehash avatar images.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * // app/api/avatar/route.ts
67
+ * import { toFacehashHandler } from "facehash/next";
68
+ *
69
+ * export const { GET } = toFacehashHandler();
70
+ * ```
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * // With custom defaults
75
+ * export const { GET } = toFacehashHandler({
76
+ * size: 200,
77
+ * variant: "solid",
78
+ * colors: ["#ff0000", "#00ff00", "#0000ff"],
79
+ * });
80
+ * ```
81
+ *
82
+ * Query parameters:
83
+ * - `name` (required): String to generate avatar from
84
+ * - `size`: Image size in pixels (default: 400)
85
+ * - `variant`: "gradient" or "solid" (default: "gradient")
86
+ * - `showInitial`: "true" or "false" (default: "true")
87
+ * - `colors`: Comma-separated hex colors (e.g., "#ff0000,#00ff00")
88
+ */
89
+ declare function toFacehashHandler(options?: FacehashHandlerOptions): FacehashHandler;
90
+ //#endregion
91
+ export { DEFAULT_COLORS, type FaceType, type FacehashData, type FacehashHandler, type FacehashHandlerOptions, type Variant, toFacehashHandler };
92
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/core/facehash-data.ts","../../src/next/handler.tsx"],"sourcesContent":[],"mappings":";;;;KAMY,OAAA;KAEA,QAAA;KASA,YAAA;EAXA;EAEA,QAAA,EAWD,QAXS;EASR;EAqCC,UAAA,EAAA,MAMH;;;;IC9CE,CAAA,EAAA,MAAA;EAqCA,CAAA;EACI;EAAwB,OAAA,EAAA,MAAA;CAAR;;;;cDEnB;;;KCxCD,sBAAA;EDRA;AAEZ;AASA;AAqCA;;;;ACxCA;AAqCA;;;EACgC,OAAA,CAAA,EAzBrB,OAyBqB;EAAO;AAkFvC;;;;;;;;;;;;;;;;;;KAnFY,eAAA;iBACI,gBAAgB,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkFxB,iBAAA,WACN,yBACP"}
package/next/index.js ADDED
@@ -0,0 +1,286 @@
1
+ import { t as stringHash } from "../hash.js";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { ImageResponse } from "next/og";
4
+
5
+ //#region src/core/facehash-data.ts
6
+ const FACE_TYPES = [
7
+ "round",
8
+ "cross",
9
+ "line",
10
+ "curved"
11
+ ];
12
+ const SPHERE_POSITIONS = [
13
+ {
14
+ x: -1,
15
+ y: 1
16
+ },
17
+ {
18
+ x: 1,
19
+ y: 1
20
+ },
21
+ {
22
+ x: 1,
23
+ y: 0
24
+ },
25
+ {
26
+ x: 0,
27
+ y: 1
28
+ },
29
+ {
30
+ x: -1,
31
+ y: 0
32
+ },
33
+ {
34
+ x: 0,
35
+ y: 0
36
+ },
37
+ {
38
+ x: 0,
39
+ y: -1
40
+ },
41
+ {
42
+ x: -1,
43
+ y: -1
44
+ },
45
+ {
46
+ x: 1,
47
+ y: -1
48
+ }
49
+ ];
50
+ /**
51
+ * Default color palette using Tailwind CSS color values.
52
+ */
53
+ const DEFAULT_COLORS = [
54
+ "#ec4899",
55
+ "#f59e0b",
56
+ "#3b82f6",
57
+ "#f97316",
58
+ "#10b981"
59
+ ];
60
+ /**
61
+ * Computes deterministic face properties from a name string.
62
+ * Pure function with no side effects or React dependencies.
63
+ */
64
+ function computeFacehash(options) {
65
+ const { name, colorsLength = DEFAULT_COLORS.length } = options;
66
+ const hash = stringHash(name);
67
+ const faceIndex = hash % FACE_TYPES.length;
68
+ const colorIndex = hash % colorsLength;
69
+ const position = SPHERE_POSITIONS[hash % SPHERE_POSITIONS.length] ?? {
70
+ x: 0,
71
+ y: 0
72
+ };
73
+ return {
74
+ faceType: FACE_TYPES[faceIndex] ?? "round",
75
+ colorIndex,
76
+ rotation: position,
77
+ initial: name.charAt(0).toUpperCase()
78
+ };
79
+ }
80
+ const FALLBACK_COLOR = "#ec4899";
81
+ /**
82
+ * Gets a color from an array by index, with fallback to default colors.
83
+ */
84
+ function getColor(colors, index) {
85
+ const palette = colors && colors.length > 0 ? colors : DEFAULT_COLORS;
86
+ return palette[index % palette.length] ?? FALLBACK_COLOR;
87
+ }
88
+
89
+ //#endregion
90
+ //#region src/next/faces-svg.ts
91
+ /**
92
+ * SVG path data for each face type.
93
+ * Used for static image generation with Satori/ImageResponse.
94
+ */
95
+ const FACE_SVG_DATA = {
96
+ round: {
97
+ viewBox: "0 0 63 15",
98
+ paths: ["M62.4 7.2C62.4 11.1765 59.1765 14.4 55.2 14.4C51.2236 14.4 48 11.1765 48 7.2C48 3.22355 51.2236 0 55.2 0C59.1765 0 62.4 3.22355 62.4 7.2Z", "M14.4 7.2C14.4 11.1765 11.1765 14.4 7.2 14.4C3.22355 14.4 0 11.1765 0 7.2C0 3.22355 3.22355 0 7.2 0C11.1765 0 14.4 3.22355 14.4 7.2Z"]
99
+ },
100
+ cross: {
101
+ viewBox: "0 0 71 23",
102
+ paths: ["M11.5 0C12.9411 0 13.6619 0.000460386 14.1748 0.354492C14.3742 0.49213 14.547 0.664882 14.6846 0.864258C15.0384 1.37711 15.0391 2.09739 15.0391 3.53809V7.96094H19.4619C20.9027 7.96094 21.6229 7.9615 22.1357 8.31543C22.3352 8.45308 22.5079 8.62578 22.6455 8.8252C22.9995 9.3381 23 10.0589 23 11.5C23 12.9408 22.9995 13.661 22.6455 14.1738C22.5079 14.3733 22.3352 14.5459 22.1357 14.6836C21.6229 15.0375 20.9027 15.0381 19.4619 15.0381H15.0391V19.4619C15.0391 20.9026 15.0384 21.6229 14.6846 22.1357C14.547 22.3351 14.3742 22.5079 14.1748 22.6455C13.6619 22.9995 12.9411 23 11.5 23C10.0592 23 9.33903 22.9994 8.82617 22.6455C8.62674 22.5079 8.45309 22.3352 8.31543 22.1357C7.96175 21.6229 7.96191 20.9024 7.96191 19.4619V15.0381H3.53809C2.0973 15.0381 1.37711 15.0375 0.864258 14.6836C0.664834 14.5459 0.492147 14.3733 0.354492 14.1738C0.000498831 13.661 -5.88036e-08 12.9408 0 11.5C6.2999e-08 10.0589 0.000460356 9.3381 0.354492 8.8252C0.492144 8.62578 0.664842 8.45308 0.864258 8.31543C1.37711 7.9615 2.09731 7.96094 3.53809 7.96094H7.96191V3.53809C7.96191 2.09765 7.96175 1.37709 8.31543 0.864258C8.45309 0.664828 8.62674 0.492149 8.82617 0.354492C9.33903 0.000555366 10.0592 1.62347e-09 11.5 0Z", "M58.7695 0C60.2107 0 60.9314 0.000460386 61.4443 0.354492C61.6437 0.49213 61.8165 0.664882 61.9541 0.864258C62.308 1.37711 62.3086 2.09739 62.3086 3.53809V7.96094H66.7314C68.1722 7.96094 68.8924 7.9615 69.4053 8.31543C69.6047 8.45308 69.7774 8.62578 69.915 8.8252C70.2691 9.3381 70.2695 10.0589 70.2695 11.5C70.2695 12.9408 70.269 13.661 69.915 14.1738C69.7774 14.3733 69.6047 14.5459 69.4053 14.6836C68.8924 15.0375 68.1722 15.0381 66.7314 15.0381H62.3086V19.4619C62.3086 20.9026 62.308 21.6229 61.9541 22.1357C61.8165 22.3351 61.6437 22.5079 61.4443 22.6455C60.9314 22.9995 60.2107 23 58.7695 23C57.3287 23 56.6086 22.9994 56.0957 22.6455C55.8963 22.5079 55.7226 22.3352 55.585 22.1357C55.2313 21.6229 55.2314 20.9024 55.2314 19.4619V15.0381H50.8076C49.3668 15.0381 48.6466 15.0375 48.1338 14.6836C47.9344 14.5459 47.7617 14.3733 47.624 14.1738C47.27 13.661 47.2695 12.9408 47.2695 11.5C47.2695 10.0589 47.27 9.3381 47.624 8.8252C47.7617 8.62578 47.9344 8.45308 48.1338 8.31543C48.6466 7.9615 49.3668 7.96094 50.8076 7.96094H55.2314V3.53809C55.2314 2.09765 55.2313 1.37709 55.585 0.864258C55.7226 0.664828 55.8963 0.492149 56.0957 0.354492C56.6086 0.000555366 57.3287 1.62347e-09 58.7695 0Z"]
103
+ },
104
+ line: {
105
+ viewBox: "0 0 82 8",
106
+ paths: [
107
+ "M3.53125 0.164063C4.90133 0.164063 5.58673 0.163893 6.08301 0.485352C6.31917 0.638428 6.52075 0.840012 6.67383 1.07617C6.99555 1.57252 6.99512 2.25826 6.99512 3.62891C6.99512 4.99911 6.99536 5.68438 6.67383 6.18066C6.52075 6.41682 6.31917 6.61841 6.08301 6.77148C5.58672 7.09305 4.90147 7.09277 3.53125 7.09277C2.16062 7.09277 1.47486 7.09319 0.978516 6.77148C0.742356 6.61841 0.540772 6.41682 0.387695 6.18066C0.0662401 5.68439 0.0664063 4.999 0.0664063 3.62891C0.0664063 2.25838 0.0660571 1.57251 0.387695 1.07617C0.540772 0.840012 0.742356 0.638428 0.978516 0.485352C1.47485 0.163744 2.16076 0.164063 3.53125 0.164063Z",
108
+ "M25.1836 0.164063C26.5542 0.164063 27.24 0.163638 27.7363 0.485352C27.9724 0.638384 28.1731 0.8401 28.3262 1.07617C28.6479 1.57252 28.6484 2.25825 28.6484 3.62891C28.6484 4.99931 28.6478 5.68436 28.3262 6.18066C28.1731 6.41678 27.9724 6.61842 27.7363 6.77148C27.24 7.09321 26.5542 7.09277 25.1836 7.09277H11.3262C9.95557 7.09277 9.26978 7.09317 8.77344 6.77148C8.53728 6.61841 8.33569 6.41682 8.18262 6.18066C7.86115 5.68438 7.86133 4.99902 7.86133 3.62891C7.86133 2.25835 7.86096 1.57251 8.18262 1.07617C8.33569 0.840012 8.53728 0.638428 8.77344 0.485352C9.26977 0.163768 9.95572 0.164063 11.3262 0.164063H25.1836Z",
109
+ "M78.2034 7.09325C76.8333 7.09325 76.1479 7.09342 75.6516 6.77197C75.4155 6.61889 75.2139 6.4173 75.0608 6.18114C74.7391 5.6848 74.7395 4.99905 74.7395 3.62841C74.7395 2.2582 74.7393 1.57294 75.0608 1.07665C75.2139 0.840493 75.4155 0.638909 75.6516 0.485832C76.1479 0.164271 76.8332 0.164543 78.2034 0.164543C79.574 0.164543 80.2598 0.164122 80.7561 0.485832C80.9923 0.638909 81.1939 0.840493 81.347 1.07665C81.6684 1.57293 81.6682 2.25831 81.6682 3.62841C81.6682 4.99894 81.6686 5.68481 81.347 6.18114C81.1939 6.4173 80.9923 6.61889 80.7561 6.77197C80.2598 7.09357 79.5739 7.09325 78.2034 7.09325Z",
110
+ "M56.5511 7.09325C55.1804 7.09325 54.4947 7.09368 53.9983 6.77197C53.7622 6.61893 53.5615 6.41722 53.4085 6.18114C53.0868 5.6848 53.0862 4.99907 53.0862 3.62841C53.0862 2.258 53.0868 1.57296 53.4085 1.07665C53.5615 0.840539 53.7622 0.638898 53.9983 0.485832C54.4947 0.164105 55.1804 0.164543 56.5511 0.164543H70.4085C71.7791 0.164543 72.4649 0.164146 72.9612 0.485832C73.1974 0.638909 73.399 0.840493 73.552 1.07665C73.8735 1.57293 73.8733 2.25829 73.8733 3.62841C73.8733 4.99896 73.8737 5.68481 73.552 6.18114C73.399 6.4173 73.1974 6.61889 72.9612 6.77197C72.4649 7.09355 71.7789 7.09325 70.4085 7.09325H56.5511Z"
111
+ ]
112
+ },
113
+ curved: {
114
+ viewBox: "0 0 63 9",
115
+ paths: ["M0 5.06511C0 4.94513 0 4.88513 0.00771184 4.79757C0.0483059 4.33665 0.341025 3.76395 0.690821 3.46107C0.757274 3.40353 0.783996 3.38422 0.837439 3.34559C2.40699 2.21129 6.03888 0 10.5 0C14.9611 0 18.593 2.21129 20.1626 3.34559C20.216 3.38422 20.2427 3.40353 20.3092 3.46107C20.659 3.76395 20.9517 4.33665 20.9923 4.79757C21 4.88513 21 4.94513 21 5.06511C21 6.01683 21 6.4927 20.9657 6.6754C20.7241 7.96423 19.8033 8.55941 18.5289 8.25054C18.3483 8.20676 17.8198 7.96876 16.7627 7.49275C14.975 6.68767 12.7805 6 10.5 6C8.21954 6 6.02504 6.68767 4.23727 7.49275C3.18025 7.96876 2.65174 8.20676 2.47108 8.25054C1.19668 8.55941 0.275917 7.96423 0.0342566 6.6754C0 6.4927 0 6.01683 0 5.06511Z", "M42 5.06511C42 4.94513 42 4.88513 42.0077 4.79757C42.0483 4.33665 42.341 3.76395 42.6908 3.46107C42.7573 3.40353 42.784 3.38422 42.8374 3.34559C44.407 2.21129 48.0389 0 52.5 0C56.9611 0 60.593 2.21129 62.1626 3.34559C62.216 3.38422 62.2427 3.40353 62.3092 3.46107C62.659 3.76395 62.9517 4.33665 62.9923 4.79757C63 4.88513 63 4.94513 63 5.06511C63 6.01683 63 6.4927 62.9657 6.6754C62.7241 7.96423 61.8033 8.55941 60.5289 8.25054C60.3483 8.20676 59.8198 7.96876 58.7627 7.49275C56.975 6.68767 54.7805 6 52.5 6C50.2195 6 48.025 6.68767 46.2373 7.49275C45.1802 7.96876 44.6517 8.20676 44.4711 8.25054C43.1967 8.55941 42.2759 7.96423 42.0343 6.6754C42 6.4927 42 6.01683 42 5.06511Z"]
116
+ }
117
+ };
118
+
119
+ //#endregion
120
+ //#region src/next/image.tsx
121
+ /**
122
+ * Static Facehash image component for use with ImageResponse.
123
+ * Uses only Satori-compatible CSS (flexbox, no transforms).
124
+ */
125
+ function FacehashImage({ data, backgroundColor, size, variant, showInitial }) {
126
+ const { faceType, initial } = data;
127
+ const svgData = FACE_SVG_DATA[faceType];
128
+ const [, , vbWidth, vbHeight] = svgData.viewBox.split(" ").map(Number);
129
+ const aspectRatio = (vbWidth ?? 1) / (vbHeight ?? 1);
130
+ const faceWidth = size * .6;
131
+ const faceHeight = faceWidth / aspectRatio;
132
+ const fontSize = size * .26;
133
+ return /* @__PURE__ */ jsxs("div", {
134
+ style: {
135
+ width: size,
136
+ height: size,
137
+ display: "flex",
138
+ flexDirection: "column",
139
+ alignItems: "center",
140
+ justifyContent: "center",
141
+ backgroundColor,
142
+ position: "relative"
143
+ },
144
+ children: [variant === "gradient" && /* @__PURE__ */ jsx("div", { style: {
145
+ position: "absolute",
146
+ top: 0,
147
+ left: 0,
148
+ right: 0,
149
+ bottom: 0,
150
+ background: "radial-gradient(ellipse 100% 100% at 50% 50%, rgba(255,255,255,0.15) 0%, transparent 60%)"
151
+ } }), /* @__PURE__ */ jsxs("div", {
152
+ style: {
153
+ display: "flex",
154
+ flexDirection: "column",
155
+ alignItems: "center",
156
+ justifyContent: "center"
157
+ },
158
+ children: [/* @__PURE__ */ jsx("svg", {
159
+ "aria-label": "Avatar face",
160
+ fill: "none",
161
+ height: faceHeight,
162
+ role: "img",
163
+ viewBox: svgData.viewBox,
164
+ width: faceWidth,
165
+ xmlns: "http://www.w3.org/2000/svg",
166
+ children: svgData.paths.map((d, i) => /* @__PURE__ */ jsx("path", {
167
+ d,
168
+ fill: "black"
169
+ }, i))
170
+ }), showInitial && /* @__PURE__ */ jsx("span", {
171
+ style: {
172
+ marginTop: size * .08,
173
+ fontSize,
174
+ lineHeight: 1,
175
+ fontFamily: "monospace",
176
+ fontWeight: 700,
177
+ color: "black"
178
+ },
179
+ children: initial
180
+ })]
181
+ })]
182
+ });
183
+ }
184
+
185
+ //#endregion
186
+ //#region src/next/handler.tsx
187
+ const HEX_COLOR_REGEX = /^#[0-9A-Fa-f]{3,8}$/;
188
+ function parseBoolean(value, defaultValue) {
189
+ if (value === null) return defaultValue;
190
+ return value === "true" || value === "1";
191
+ }
192
+ function parseNumber(value, defaultValue, min = 1, max = 2e3) {
193
+ if (value === null) return defaultValue;
194
+ const num = Number.parseInt(value, 10);
195
+ if (Number.isNaN(num)) return defaultValue;
196
+ return Math.min(Math.max(num, min), max);
197
+ }
198
+ function parseColors(value) {
199
+ if (!value) return;
200
+ const colors = value.split(",").map((c) => c.trim()).filter((c) => HEX_COLOR_REGEX.test(c));
201
+ return colors.length > 0 ? colors : void 0;
202
+ }
203
+ function parseVariant(value) {
204
+ if (value === "gradient" || value === "solid") return value;
205
+ }
206
+ /**
207
+ * Creates a Next.js route handler for generating Facehash avatar images.
208
+ *
209
+ * @example
210
+ * ```ts
211
+ * // app/api/avatar/route.ts
212
+ * import { toFacehashHandler } from "facehash/next";
213
+ *
214
+ * export const { GET } = toFacehashHandler();
215
+ * ```
216
+ *
217
+ * @example
218
+ * ```ts
219
+ * // With custom defaults
220
+ * export const { GET } = toFacehashHandler({
221
+ * size: 200,
222
+ * variant: "solid",
223
+ * colors: ["#ff0000", "#00ff00", "#0000ff"],
224
+ * });
225
+ * ```
226
+ *
227
+ * Query parameters:
228
+ * - `name` (required): String to generate avatar from
229
+ * - `size`: Image size in pixels (default: 400)
230
+ * - `variant`: "gradient" or "solid" (default: "gradient")
231
+ * - `showInitial`: "true" or "false" (default: "true")
232
+ * - `colors`: Comma-separated hex colors (e.g., "#ff0000,#00ff00")
233
+ */
234
+ function toFacehashHandler(options = {}) {
235
+ const { size: defaultSize = 400, variant: defaultVariant = "gradient", showInitial: defaultShowInitial = true, colors: defaultColors = [...DEFAULT_COLORS], cacheControl = "public, max-age=31536000, immutable" } = options;
236
+ async function GET(request) {
237
+ const searchParams = request.nextUrl.searchParams;
238
+ const name = searchParams.get("name");
239
+ if (!name) return new ImageResponse(/* @__PURE__ */ jsx("div", {
240
+ style: {
241
+ width: "100%",
242
+ height: "100%",
243
+ display: "flex",
244
+ alignItems: "center",
245
+ justifyContent: "center",
246
+ backgroundColor: "#f3f4f6",
247
+ color: "#6b7280",
248
+ fontSize: 24,
249
+ fontFamily: "sans-serif"
250
+ },
251
+ children: "Missing ?name= parameter"
252
+ }), {
253
+ width: defaultSize,
254
+ height: defaultSize,
255
+ status: 400,
256
+ headers: { "Content-Type": "image/png" }
257
+ });
258
+ const size = parseNumber(searchParams.get("size"), defaultSize, 16, 2e3);
259
+ const variant = parseVariant(searchParams.get("variant")) ?? defaultVariant;
260
+ const showInitial = parseBoolean(searchParams.get("showInitial"), defaultShowInitial);
261
+ const colors = parseColors(searchParams.get("colors")) ?? defaultColors;
262
+ const data = computeFacehash({
263
+ name,
264
+ colorsLength: colors.length
265
+ });
266
+ const backgroundColor = getColor(colors, data.colorIndex);
267
+ const headers = { "Content-Type": "image/png" };
268
+ if (cacheControl) headers["Cache-Control"] = cacheControl;
269
+ return new ImageResponse(/* @__PURE__ */ jsx(FacehashImage, {
270
+ backgroundColor,
271
+ data,
272
+ showInitial,
273
+ size,
274
+ variant
275
+ }), {
276
+ width: size,
277
+ height: size,
278
+ headers
279
+ });
280
+ }
281
+ return { GET };
282
+ }
283
+
284
+ //#endregion
285
+ export { DEFAULT_COLORS, toFacehashHandler };
286
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["FACE_TYPES: readonly FaceType[]","FACE_SVG_DATA: Record<\n\tFaceType,\n\t{\n\t\tviewBox: string;\n\t\tpaths: string[];\n\t}\n>","headers: Record<string, string>"],"sources":["../../src/core/facehash-data.ts","../../src/next/faces-svg.ts","../../src/next/image.tsx","../../src/next/handler.tsx"],"sourcesContent":["import { stringHash } from \"../utils/hash\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type Variant = \"gradient\" | \"solid\";\n\nexport type FaceType = \"round\" | \"cross\" | \"line\" | \"curved\";\n\nexport const FACE_TYPES: readonly FaceType[] = [\n\t\"round\",\n\t\"cross\",\n\t\"line\",\n\t\"curved\",\n] as const;\n\nexport type FacehashData = {\n\t/** The face type to render */\n\tfaceType: FaceType;\n\t/** Index into the colors array */\n\tcolorIndex: number;\n\t/** Rotation position for 3D effect (-1, 0, or 1 for each axis) */\n\trotation: { x: number; y: number };\n\t/** First letter of the name, uppercase */\n\tinitial: string;\n};\n\nexport type ComputeFacehashOptions = {\n\t/** String to generate face from */\n\tname: string;\n\t/** Number of colors available (for modulo) */\n\tcolorsLength?: number;\n};\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst SPHERE_POSITIONS = [\n\t{ x: -1, y: 1 }, // down-right\n\t{ x: 1, y: 1 }, // up-right\n\t{ x: 1, y: 0 }, // up\n\t{ x: 0, y: 1 }, // right\n\t{ x: -1, y: 0 }, // down\n\t{ x: 0, y: 0 }, // center\n\t{ x: 0, y: -1 }, // left\n\t{ x: -1, y: -1 }, // down-left\n\t{ x: 1, y: -1 }, // up-left\n] as const;\n\n/**\n * Default color palette using Tailwind CSS color values.\n */\nexport const DEFAULT_COLORS = [\n\t\"#ec4899\", // pink-500\n\t\"#f59e0b\", // amber-500\n\t\"#3b82f6\", // blue-500\n\t\"#f97316\", // orange-500\n\t\"#10b981\", // emerald-500\n] as const;\n\n// ============================================================================\n// Core Functions\n// ============================================================================\n\n/**\n * Computes deterministic face properties from a name string.\n * Pure function with no side effects or React dependencies.\n */\nexport function computeFacehash(options: ComputeFacehashOptions): FacehashData {\n\tconst { name, colorsLength = DEFAULT_COLORS.length } = options;\n\n\tconst hash = stringHash(name);\n\tconst faceIndex = hash % FACE_TYPES.length;\n\tconst colorIndex = hash % colorsLength;\n\tconst positionIndex = hash % SPHERE_POSITIONS.length;\n\tconst position = SPHERE_POSITIONS[positionIndex] ?? { x: 0, y: 0 };\n\n\treturn {\n\t\tfaceType: FACE_TYPES[faceIndex] ?? \"round\",\n\t\tcolorIndex,\n\t\trotation: position,\n\t\tinitial: name.charAt(0).toUpperCase(),\n\t};\n}\n\nconst FALLBACK_COLOR = \"#ec4899\"; // pink-500\n\n/**\n * Gets a color from an array by index, with fallback to default colors.\n */\nexport function getColor(\n\tcolors: readonly string[] | undefined,\n\tindex: number\n): string {\n\tconst palette = colors && colors.length > 0 ? colors : DEFAULT_COLORS;\n\treturn palette[index % palette.length] ?? FALLBACK_COLOR;\n}\n","import type { FaceType } from \"../core\";\n\n/**\n * SVG path data for each face type.\n * Used for static image generation with Satori/ImageResponse.\n */\nexport const FACE_SVG_DATA: Record<\n\tFaceType,\n\t{\n\t\tviewBox: string;\n\t\tpaths: string[];\n\t}\n> = {\n\tround: {\n\t\tviewBox: \"0 0 63 15\",\n\t\tpaths: [\n\t\t\t\"M62.4 7.2C62.4 11.1765 59.1765 14.4 55.2 14.4C51.2236 14.4 48 11.1765 48 7.2C48 3.22355 51.2236 0 55.2 0C59.1765 0 62.4 3.22355 62.4 7.2Z\",\n\t\t\t\"M14.4 7.2C14.4 11.1765 11.1765 14.4 7.2 14.4C3.22355 14.4 0 11.1765 0 7.2C0 3.22355 3.22355 0 7.2 0C11.1765 0 14.4 3.22355 14.4 7.2Z\",\n\t\t],\n\t},\n\tcross: {\n\t\tviewBox: \"0 0 71 23\",\n\t\tpaths: [\n\t\t\t\"M11.5 0C12.9411 0 13.6619 0.000460386 14.1748 0.354492C14.3742 0.49213 14.547 0.664882 14.6846 0.864258C15.0384 1.37711 15.0391 2.09739 15.0391 3.53809V7.96094H19.4619C20.9027 7.96094 21.6229 7.9615 22.1357 8.31543C22.3352 8.45308 22.5079 8.62578 22.6455 8.8252C22.9995 9.3381 23 10.0589 23 11.5C23 12.9408 22.9995 13.661 22.6455 14.1738C22.5079 14.3733 22.3352 14.5459 22.1357 14.6836C21.6229 15.0375 20.9027 15.0381 19.4619 15.0381H15.0391V19.4619C15.0391 20.9026 15.0384 21.6229 14.6846 22.1357C14.547 22.3351 14.3742 22.5079 14.1748 22.6455C13.6619 22.9995 12.9411 23 11.5 23C10.0592 23 9.33903 22.9994 8.82617 22.6455C8.62674 22.5079 8.45309 22.3352 8.31543 22.1357C7.96175 21.6229 7.96191 20.9024 7.96191 19.4619V15.0381H3.53809C2.0973 15.0381 1.37711 15.0375 0.864258 14.6836C0.664834 14.5459 0.492147 14.3733 0.354492 14.1738C0.000498831 13.661 -5.88036e-08 12.9408 0 11.5C6.2999e-08 10.0589 0.000460356 9.3381 0.354492 8.8252C0.492144 8.62578 0.664842 8.45308 0.864258 8.31543C1.37711 7.9615 2.09731 7.96094 3.53809 7.96094H7.96191V3.53809C7.96191 2.09765 7.96175 1.37709 8.31543 0.864258C8.45309 0.664828 8.62674 0.492149 8.82617 0.354492C9.33903 0.000555366 10.0592 1.62347e-09 11.5 0Z\",\n\t\t\t\"M58.7695 0C60.2107 0 60.9314 0.000460386 61.4443 0.354492C61.6437 0.49213 61.8165 0.664882 61.9541 0.864258C62.308 1.37711 62.3086 2.09739 62.3086 3.53809V7.96094H66.7314C68.1722 7.96094 68.8924 7.9615 69.4053 8.31543C69.6047 8.45308 69.7774 8.62578 69.915 8.8252C70.2691 9.3381 70.2695 10.0589 70.2695 11.5C70.2695 12.9408 70.269 13.661 69.915 14.1738C69.7774 14.3733 69.6047 14.5459 69.4053 14.6836C68.8924 15.0375 68.1722 15.0381 66.7314 15.0381H62.3086V19.4619C62.3086 20.9026 62.308 21.6229 61.9541 22.1357C61.8165 22.3351 61.6437 22.5079 61.4443 22.6455C60.9314 22.9995 60.2107 23 58.7695 23C57.3287 23 56.6086 22.9994 56.0957 22.6455C55.8963 22.5079 55.7226 22.3352 55.585 22.1357C55.2313 21.6229 55.2314 20.9024 55.2314 19.4619V15.0381H50.8076C49.3668 15.0381 48.6466 15.0375 48.1338 14.6836C47.9344 14.5459 47.7617 14.3733 47.624 14.1738C47.27 13.661 47.2695 12.9408 47.2695 11.5C47.2695 10.0589 47.27 9.3381 47.624 8.8252C47.7617 8.62578 47.9344 8.45308 48.1338 8.31543C48.6466 7.9615 49.3668 7.96094 50.8076 7.96094H55.2314V3.53809C55.2314 2.09765 55.2313 1.37709 55.585 0.864258C55.7226 0.664828 55.8963 0.492149 56.0957 0.354492C56.6086 0.000555366 57.3287 1.62347e-09 58.7695 0Z\",\n\t\t],\n\t},\n\tline: {\n\t\tviewBox: \"0 0 82 8\",\n\t\tpaths: [\n\t\t\t\"M3.53125 0.164063C4.90133 0.164063 5.58673 0.163893 6.08301 0.485352C6.31917 0.638428 6.52075 0.840012 6.67383 1.07617C6.99555 1.57252 6.99512 2.25826 6.99512 3.62891C6.99512 4.99911 6.99536 5.68438 6.67383 6.18066C6.52075 6.41682 6.31917 6.61841 6.08301 6.77148C5.58672 7.09305 4.90147 7.09277 3.53125 7.09277C2.16062 7.09277 1.47486 7.09319 0.978516 6.77148C0.742356 6.61841 0.540772 6.41682 0.387695 6.18066C0.0662401 5.68439 0.0664063 4.999 0.0664063 3.62891C0.0664063 2.25838 0.0660571 1.57251 0.387695 1.07617C0.540772 0.840012 0.742356 0.638428 0.978516 0.485352C1.47485 0.163744 2.16076 0.164063 3.53125 0.164063Z\",\n\t\t\t\"M25.1836 0.164063C26.5542 0.164063 27.24 0.163638 27.7363 0.485352C27.9724 0.638384 28.1731 0.8401 28.3262 1.07617C28.6479 1.57252 28.6484 2.25825 28.6484 3.62891C28.6484 4.99931 28.6478 5.68436 28.3262 6.18066C28.1731 6.41678 27.9724 6.61842 27.7363 6.77148C27.24 7.09321 26.5542 7.09277 25.1836 7.09277H11.3262C9.95557 7.09277 9.26978 7.09317 8.77344 6.77148C8.53728 6.61841 8.33569 6.41682 8.18262 6.18066C7.86115 5.68438 7.86133 4.99902 7.86133 3.62891C7.86133 2.25835 7.86096 1.57251 8.18262 1.07617C8.33569 0.840012 8.53728 0.638428 8.77344 0.485352C9.26977 0.163768 9.95572 0.164063 11.3262 0.164063H25.1836Z\",\n\t\t\t\"M78.2034 7.09325C76.8333 7.09325 76.1479 7.09342 75.6516 6.77197C75.4155 6.61889 75.2139 6.4173 75.0608 6.18114C74.7391 5.6848 74.7395 4.99905 74.7395 3.62841C74.7395 2.2582 74.7393 1.57294 75.0608 1.07665C75.2139 0.840493 75.4155 0.638909 75.6516 0.485832C76.1479 0.164271 76.8332 0.164543 78.2034 0.164543C79.574 0.164543 80.2598 0.164122 80.7561 0.485832C80.9923 0.638909 81.1939 0.840493 81.347 1.07665C81.6684 1.57293 81.6682 2.25831 81.6682 3.62841C81.6682 4.99894 81.6686 5.68481 81.347 6.18114C81.1939 6.4173 80.9923 6.61889 80.7561 6.77197C80.2598 7.09357 79.5739 7.09325 78.2034 7.09325Z\",\n\t\t\t\"M56.5511 7.09325C55.1804 7.09325 54.4947 7.09368 53.9983 6.77197C53.7622 6.61893 53.5615 6.41722 53.4085 6.18114C53.0868 5.6848 53.0862 4.99907 53.0862 3.62841C53.0862 2.258 53.0868 1.57296 53.4085 1.07665C53.5615 0.840539 53.7622 0.638898 53.9983 0.485832C54.4947 0.164105 55.1804 0.164543 56.5511 0.164543H70.4085C71.7791 0.164543 72.4649 0.164146 72.9612 0.485832C73.1974 0.638909 73.399 0.840493 73.552 1.07665C73.8735 1.57293 73.8733 2.25829 73.8733 3.62841C73.8733 4.99896 73.8737 5.68481 73.552 6.18114C73.399 6.4173 73.1974 6.61889 72.9612 6.77197C72.4649 7.09355 71.7789 7.09325 70.4085 7.09325H56.5511Z\",\n\t\t],\n\t},\n\tcurved: {\n\t\tviewBox: \"0 0 63 9\",\n\t\tpaths: [\n\t\t\t\"M0 5.06511C0 4.94513 0 4.88513 0.00771184 4.79757C0.0483059 4.33665 0.341025 3.76395 0.690821 3.46107C0.757274 3.40353 0.783996 3.38422 0.837439 3.34559C2.40699 2.21129 6.03888 0 10.5 0C14.9611 0 18.593 2.21129 20.1626 3.34559C20.216 3.38422 20.2427 3.40353 20.3092 3.46107C20.659 3.76395 20.9517 4.33665 20.9923 4.79757C21 4.88513 21 4.94513 21 5.06511C21 6.01683 21 6.4927 20.9657 6.6754C20.7241 7.96423 19.8033 8.55941 18.5289 8.25054C18.3483 8.20676 17.8198 7.96876 16.7627 7.49275C14.975 6.68767 12.7805 6 10.5 6C8.21954 6 6.02504 6.68767 4.23727 7.49275C3.18025 7.96876 2.65174 8.20676 2.47108 8.25054C1.19668 8.55941 0.275917 7.96423 0.0342566 6.6754C0 6.4927 0 6.01683 0 5.06511Z\",\n\t\t\t\"M42 5.06511C42 4.94513 42 4.88513 42.0077 4.79757C42.0483 4.33665 42.341 3.76395 42.6908 3.46107C42.7573 3.40353 42.784 3.38422 42.8374 3.34559C44.407 2.21129 48.0389 0 52.5 0C56.9611 0 60.593 2.21129 62.1626 3.34559C62.216 3.38422 62.2427 3.40353 62.3092 3.46107C62.659 3.76395 62.9517 4.33665 62.9923 4.79757C63 4.88513 63 4.94513 63 5.06511C63 6.01683 63 6.4927 62.9657 6.6754C62.7241 7.96423 61.8033 8.55941 60.5289 8.25054C60.3483 8.20676 59.8198 7.96876 58.7627 7.49275C56.975 6.68767 54.7805 6 52.5 6C50.2195 6 48.025 6.68767 46.2373 7.49275C45.1802 7.96876 44.6517 8.20676 44.4711 8.25054C43.1967 8.55941 42.2759 7.96423 42.0343 6.6754C42 6.4927 42 6.01683 42 5.06511Z\",\n\t\t],\n\t},\n};\n","import type { FacehashData, Variant } from \"../core\";\nimport { FACE_SVG_DATA } from \"./faces-svg\";\n\nexport type FacehashImageProps = {\n\t/** Computed facehash data */\n\tdata: FacehashData;\n\t/** Background color (hex) */\n\tbackgroundColor: string;\n\t/** Image size in pixels */\n\tsize: number;\n\t/** Background style variant */\n\tvariant: Variant;\n\t/** Show initial letter */\n\tshowInitial: boolean;\n};\n\n/**\n * Static Facehash image component for use with ImageResponse.\n * Uses only Satori-compatible CSS (flexbox, no transforms).\n */\nexport function FacehashImage({\n\tdata,\n\tbackgroundColor,\n\tsize,\n\tvariant,\n\tshowInitial,\n}: FacehashImageProps) {\n\tconst { faceType, initial } = data;\n\tconst svgData = FACE_SVG_DATA[faceType];\n\n\t// Calculate SVG dimensions based on viewBox\n\tconst [, , vbWidth, vbHeight] = svgData.viewBox.split(\" \").map(Number);\n\tconst aspectRatio = (vbWidth ?? 1) / (vbHeight ?? 1);\n\n\t// Face takes up ~60% of the container width\n\tconst faceWidth = size * 0.6;\n\tconst faceHeight = faceWidth / aspectRatio;\n\n\t// Font size for initial (26% of size, matching cqw from React component)\n\tconst fontSize = size * 0.26;\n\n\treturn (\n\t\t<div\n\t\t\tstyle={{\n\t\t\t\twidth: size,\n\t\t\t\theight: size,\n\t\t\t\tdisplay: \"flex\",\n\t\t\t\tflexDirection: \"column\",\n\t\t\t\talignItems: \"center\",\n\t\t\t\tjustifyContent: \"center\",\n\t\t\t\tbackgroundColor,\n\t\t\t\tposition: \"relative\",\n\t\t\t}}\n\t\t>\n\t\t\t{/* Gradient overlay */}\n\t\t\t{variant === \"gradient\" && (\n\t\t\t\t<div\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t\tleft: 0,\n\t\t\t\t\t\tright: 0,\n\t\t\t\t\t\tbottom: 0,\n\t\t\t\t\t\tbackground:\n\t\t\t\t\t\t\t\"radial-gradient(ellipse 100% 100% at 50% 50%, rgba(255,255,255,0.15) 0%, transparent 60%)\",\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t)}\n\n\t\t\t{/* Face container */}\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tdisplay: \"flex\",\n\t\t\t\t\tflexDirection: \"column\",\n\t\t\t\t\talignItems: \"center\",\n\t\t\t\t\tjustifyContent: \"center\",\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{/* Face SVG */}\n\t\t\t\t<svg\n\t\t\t\t\taria-label=\"Avatar face\"\n\t\t\t\t\tfill=\"none\"\n\t\t\t\t\theight={faceHeight}\n\t\t\t\t\trole=\"img\"\n\t\t\t\t\tviewBox={svgData.viewBox}\n\t\t\t\t\twidth={faceWidth}\n\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t>\n\t\t\t\t\t{svgData.paths.map((d, i) => (\n\t\t\t\t\t\t<path d={d} fill=\"black\" key={i} />\n\t\t\t\t\t))}\n\t\t\t\t</svg>\n\n\t\t\t\t{/* Initial letter */}\n\t\t\t\t{showInitial && (\n\t\t\t\t\t<span\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tmarginTop: size * 0.08,\n\t\t\t\t\t\t\tfontSize,\n\t\t\t\t\t\t\tlineHeight: 1,\n\t\t\t\t\t\t\tfontFamily: \"monospace\",\n\t\t\t\t\t\t\tfontWeight: 700,\n\t\t\t\t\t\t\tcolor: \"black\",\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{initial}\n\t\t\t\t\t</span>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n","import { ImageResponse } from \"next/og\";\nimport type { NextRequest } from \"next/server\";\nimport {\n\tcomputeFacehash,\n\tDEFAULT_COLORS,\n\tgetColor,\n\ttype Variant,\n} from \"../core\";\nimport { FacehashImage } from \"./image\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type FacehashHandlerOptions = {\n\t/**\n\t * Default image size in pixels.\n\t * Can be overridden via `?size=` query param.\n\t * @default 400\n\t */\n\tsize?: number;\n\n\t/**\n\t * Default background style.\n\t * Can be overridden via `?variant=` query param.\n\t * @default \"gradient\"\n\t */\n\tvariant?: Variant;\n\n\t/**\n\t * Default for showing initial letter.\n\t * Can be overridden via `?showInitial=` query param.\n\t * @default true\n\t */\n\tshowInitial?: boolean;\n\n\t/**\n\t * Default color palette (hex colors).\n\t * Can be overridden via `?colors=` query param (comma-separated).\n\t * @default [\"#ec4899\", \"#f59e0b\", \"#3b82f6\", \"#f97316\", \"#10b981\"]\n\t */\n\tcolors?: string[];\n\n\t/**\n\t * Cache-Control header value.\n\t * Set to `null` to disable caching.\n\t * @default \"public, max-age=31536000, immutable\"\n\t */\n\tcacheControl?: string | null;\n};\n\nexport type FacehashHandler = {\n\tGET: (request: NextRequest) => Promise<ImageResponse>;\n};\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nconst HEX_COLOR_REGEX = /^#[0-9A-Fa-f]{3,8}$/;\n\nfunction parseBoolean(value: string | null, defaultValue: boolean): boolean {\n\tif (value === null) {\n\t\treturn defaultValue;\n\t}\n\treturn value === \"true\" || value === \"1\";\n}\n\nfunction parseNumber(\n\tvalue: string | null,\n\tdefaultValue: number,\n\tmin = 1,\n\tmax = 2000\n): number {\n\tif (value === null) {\n\t\treturn defaultValue;\n\t}\n\tconst num = Number.parseInt(value, 10);\n\tif (Number.isNaN(num)) {\n\t\treturn defaultValue;\n\t}\n\treturn Math.min(Math.max(num, min), max);\n}\n\nfunction parseColors(value: string | null): string[] | undefined {\n\tif (!value) {\n\t\treturn;\n\t}\n\tconst colors = value\n\t\t.split(\",\")\n\t\t.map((c) => c.trim())\n\t\t.filter((c) => HEX_COLOR_REGEX.test(c));\n\treturn colors.length > 0 ? colors : undefined;\n}\n\nfunction parseVariant(value: string | null): Variant | undefined {\n\tif (value === \"gradient\" || value === \"solid\") {\n\t\treturn value;\n\t}\n\treturn;\n}\n\n// ============================================================================\n// Main Export\n// ============================================================================\n\n/**\n * Creates a Next.js route handler for generating Facehash avatar images.\n *\n * @example\n * ```ts\n * // app/api/avatar/route.ts\n * import { toFacehashHandler } from \"facehash/next\";\n *\n * export const { GET } = toFacehashHandler();\n * ```\n *\n * @example\n * ```ts\n * // With custom defaults\n * export const { GET } = toFacehashHandler({\n * size: 200,\n * variant: \"solid\",\n * colors: [\"#ff0000\", \"#00ff00\", \"#0000ff\"],\n * });\n * ```\n *\n * Query parameters:\n * - `name` (required): String to generate avatar from\n * - `size`: Image size in pixels (default: 400)\n * - `variant`: \"gradient\" or \"solid\" (default: \"gradient\")\n * - `showInitial`: \"true\" or \"false\" (default: \"true\")\n * - `colors`: Comma-separated hex colors (e.g., \"#ff0000,#00ff00\")\n */\nexport function toFacehashHandler(\n\toptions: FacehashHandlerOptions = {}\n): FacehashHandler {\n\tconst {\n\t\tsize: defaultSize = 400,\n\t\tvariant: defaultVariant = \"gradient\",\n\t\tshowInitial: defaultShowInitial = true,\n\t\tcolors: defaultColors = [...DEFAULT_COLORS],\n\t\tcacheControl = \"public, max-age=31536000, immutable\",\n\t} = options;\n\n\tasync function GET(request: NextRequest): Promise<ImageResponse> {\n\t\tconst searchParams = request.nextUrl.searchParams;\n\n\t\t// Parse name (required)\n\t\tconst name = searchParams.get(\"name\");\n\t\tif (!name) {\n\t\t\treturn new ImageResponse(\n\t\t\t\t<div\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\twidth: \"100%\",\n\t\t\t\t\t\theight: \"100%\",\n\t\t\t\t\t\tdisplay: \"flex\",\n\t\t\t\t\t\talignItems: \"center\",\n\t\t\t\t\t\tjustifyContent: \"center\",\n\t\t\t\t\t\tbackgroundColor: \"#f3f4f6\",\n\t\t\t\t\t\tcolor: \"#6b7280\",\n\t\t\t\t\t\tfontSize: 24,\n\t\t\t\t\t\tfontFamily: \"sans-serif\",\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\tMissing ?name= parameter\n\t\t\t\t</div>,\n\t\t\t\t{\n\t\t\t\t\twidth: defaultSize,\n\t\t\t\t\theight: defaultSize,\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": \"image/png\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\n\t\t// Parse options from query params (override defaults)\n\t\tconst size = parseNumber(searchParams.get(\"size\"), defaultSize, 16, 2000);\n\t\tconst variant = parseVariant(searchParams.get(\"variant\")) ?? defaultVariant;\n\t\tconst showInitial = parseBoolean(\n\t\t\tsearchParams.get(\"showInitial\"),\n\t\t\tdefaultShowInitial\n\t\t);\n\t\tconst colors = parseColors(searchParams.get(\"colors\")) ?? defaultColors;\n\n\t\t// Compute facehash data\n\t\tconst data = computeFacehash({\n\t\t\tname,\n\t\t\tcolorsLength: colors.length,\n\t\t});\n\n\t\t// Get background color\n\t\tconst backgroundColor = getColor(colors, data.colorIndex);\n\n\t\t// Build response headers\n\t\tconst headers: Record<string, string> = {\n\t\t\t\"Content-Type\": \"image/png\",\n\t\t};\n\n\t\tif (cacheControl) {\n\t\t\theaders[\"Cache-Control\"] = cacheControl;\n\t\t}\n\n\t\t// Generate image\n\t\treturn new ImageResponse(\n\t\t\t<FacehashImage\n\t\t\t\tbackgroundColor={backgroundColor}\n\t\t\t\tdata={data}\n\t\t\t\tshowInitial={showInitial}\n\t\t\t\tsize={size}\n\t\t\t\tvariant={variant}\n\t\t\t/>,\n\t\t\t{\n\t\t\t\twidth: size,\n\t\t\t\theight: size,\n\t\t\t\theaders,\n\t\t\t}\n\t\t);\n\t}\n\n\treturn { GET };\n}\n"],"mappings":";;;;;AAUA,MAAaA,aAAkC;CAC9C;CACA;CACA;CACA;CACA;AAwBD,MAAM,mBAAmB;CACxB;EAAE,GAAG;EAAI,GAAG;EAAG;CACf;EAAE,GAAG;EAAG,GAAG;EAAG;CACd;EAAE,GAAG;EAAG,GAAG;EAAG;CACd;EAAE,GAAG;EAAG,GAAG;EAAG;CACd;EAAE,GAAG;EAAI,GAAG;EAAG;CACf;EAAE,GAAG;EAAG,GAAG;EAAG;CACd;EAAE,GAAG;EAAG,GAAG;EAAI;CACf;EAAE,GAAG;EAAI,GAAG;EAAI;CAChB;EAAE,GAAG;EAAG,GAAG;EAAI;CACf;;;;AAKD,MAAa,iBAAiB;CAC7B;CACA;CACA;CACA;CACA;CACA;;;;;AAUD,SAAgB,gBAAgB,SAA+C;CAC9E,MAAM,EAAE,MAAM,eAAe,eAAe,WAAW;CAEvD,MAAM,OAAO,WAAW,KAAK;CAC7B,MAAM,YAAY,OAAO,WAAW;CACpC,MAAM,aAAa,OAAO;CAE1B,MAAM,WAAW,iBADK,OAAO,iBAAiB,WACM;EAAE,GAAG;EAAG,GAAG;EAAG;AAElE,QAAO;EACN,UAAU,WAAW,cAAc;EACnC;EACA,UAAU;EACV,SAAS,KAAK,OAAO,EAAE,CAAC,aAAa;EACrC;;AAGF,MAAM,iBAAiB;;;;AAKvB,SAAgB,SACf,QACA,OACS;CACT,MAAM,UAAU,UAAU,OAAO,SAAS,IAAI,SAAS;AACvD,QAAO,QAAQ,QAAQ,QAAQ,WAAW;;;;;;;;;AC3F3C,MAAaC,gBAMT;CACH,OAAO;EACN,SAAS;EACT,OAAO,CACN,6IACA,uIACA;EACD;CACD,OAAO;EACN,SAAS;EACT,OAAO,CACN,grCACA,2qCACA;EACD;CACD,MAAM;EACL,SAAS;EACT,OAAO;GACN;GACA;GACA;GACA;GACA;EACD;CACD,QAAQ;EACP,SAAS;EACT,OAAO,CACN,mrBACA,uqBACA;EACD;CACD;;;;;;;;ACvBD,SAAgB,cAAc,EAC7B,MACA,iBACA,MACA,SACA,eACsB;CACtB,MAAM,EAAE,UAAU,YAAY;CAC9B,MAAM,UAAU,cAAc;CAG9B,MAAM,KAAK,SAAS,YAAY,QAAQ,QAAQ,MAAM,IAAI,CAAC,IAAI,OAAO;CACtE,MAAM,eAAe,WAAW,MAAM,YAAY;CAGlD,MAAM,YAAY,OAAO;CACzB,MAAM,aAAa,YAAY;CAG/B,MAAM,WAAW,OAAO;AAExB,QACC,qBAAC;EACA,OAAO;GACN,OAAO;GACP,QAAQ;GACR,SAAS;GACT,eAAe;GACf,YAAY;GACZ,gBAAgB;GAChB;GACA,UAAU;GACV;aAGA,YAAY,cACZ,oBAAC,SACA,OAAO;GACN,UAAU;GACV,KAAK;GACL,MAAM;GACN,OAAO;GACP,QAAQ;GACR,YACC;GACD,GACA,EAIH,qBAAC;GACA,OAAO;IACN,SAAS;IACT,eAAe;IACf,YAAY;IACZ,gBAAgB;IAChB;cAGD,oBAAC;IACA,cAAW;IACX,MAAK;IACL,QAAQ;IACR,MAAK;IACL,SAAS,QAAQ;IACjB,OAAO;IACP,OAAM;cAEL,QAAQ,MAAM,KAAK,GAAG,MACtB,oBAAC;KAAQ;KAAG,MAAK;OAAa,EAAK,CAClC;KACG,EAGL,eACA,oBAAC;IACA,OAAO;KACN,WAAW,OAAO;KAClB;KACA,YAAY;KACZ,YAAY;KACZ,YAAY;KACZ,OAAO;KACP;cAEA;KACK;IAEH;GACD;;;;;AClDR,MAAM,kBAAkB;AAExB,SAAS,aAAa,OAAsB,cAAgC;AAC3E,KAAI,UAAU,KACb,QAAO;AAER,QAAO,UAAU,UAAU,UAAU;;AAGtC,SAAS,YACR,OACA,cACA,MAAM,GACN,MAAM,KACG;AACT,KAAI,UAAU,KACb,QAAO;CAER,MAAM,MAAM,OAAO,SAAS,OAAO,GAAG;AACtC,KAAI,OAAO,MAAM,IAAI,CACpB,QAAO;AAER,QAAO,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,EAAE,IAAI;;AAGzC,SAAS,YAAY,OAA4C;AAChE,KAAI,CAAC,MACJ;CAED,MAAM,SAAS,MACb,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,QAAQ,MAAM,gBAAgB,KAAK,EAAE,CAAC;AACxC,QAAO,OAAO,SAAS,IAAI,SAAS;;AAGrC,SAAS,aAAa,OAA2C;AAChE,KAAI,UAAU,cAAc,UAAU,QACrC,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCT,SAAgB,kBACf,UAAkC,EAAE,EAClB;CAClB,MAAM,EACL,MAAM,cAAc,KACpB,SAAS,iBAAiB,YAC1B,aAAa,qBAAqB,MAClC,QAAQ,gBAAgB,CAAC,GAAG,eAAe,EAC3C,eAAe,0CACZ;CAEJ,eAAe,IAAI,SAA8C;EAChE,MAAM,eAAe,QAAQ,QAAQ;EAGrC,MAAM,OAAO,aAAa,IAAI,OAAO;AACrC,MAAI,CAAC,KACJ,QAAO,IAAI,cACV,oBAAC;GACA,OAAO;IACN,OAAO;IACP,QAAQ;IACR,SAAS;IACT,YAAY;IACZ,gBAAgB;IAChB,iBAAiB;IACjB,OAAO;IACP,UAAU;IACV,YAAY;IACZ;aACD;IAEK,EACN;GACC,OAAO;GACP,QAAQ;GACR,QAAQ;GACR,SAAS,EACR,gBAAgB,aAChB;GACD,CACD;EAIF,MAAM,OAAO,YAAY,aAAa,IAAI,OAAO,EAAE,aAAa,IAAI,IAAK;EACzE,MAAM,UAAU,aAAa,aAAa,IAAI,UAAU,CAAC,IAAI;EAC7D,MAAM,cAAc,aACnB,aAAa,IAAI,cAAc,EAC/B,mBACA;EACD,MAAM,SAAS,YAAY,aAAa,IAAI,SAAS,CAAC,IAAI;EAG1D,MAAM,OAAO,gBAAgB;GAC5B;GACA,cAAc,OAAO;GACrB,CAAC;EAGF,MAAM,kBAAkB,SAAS,QAAQ,KAAK,WAAW;EAGzD,MAAMC,UAAkC,EACvC,gBAAgB,aAChB;AAED,MAAI,aACH,SAAQ,mBAAmB;AAI5B,SAAO,IAAI,cACV,oBAAC;GACiB;GACX;GACO;GACP;GACG;IACR,EACF;GACC,OAAO;GACP,QAAQ;GACR;GACA,CACD;;AAGF,QAAO,EAAE,KAAK"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "facehash",
3
3
  "type": "module",
4
- "version": "0.0.2",
4
+ "version": "0.0.4",
5
5
  "private": false,
6
6
  "author": "Cossistant team",
7
7
  "description": "Deterministic avatar faces from any string. Lightweight, interactive, pure CSS. Works with any framework.",
@@ -10,6 +10,10 @@
10
10
  "avatar",
11
11
  "react",
12
12
  "react-avatar",
13
+ "nextjs",
14
+ "next",
15
+ "og-image",
16
+ "image-generation",
13
17
  "deterministic",
14
18
  "generative",
15
19
  "profile-picture",
@@ -42,11 +46,15 @@
42
46
  "directory": "packages/facehash"
43
47
  },
44
48
  "license": "MIT",
45
- "homepage": "https://cossistant.com",
49
+ "homepage": "https://facehash.dev",
46
50
  "exports": {
47
51
  ".": {
48
52
  "types": "./index.d.ts",
49
53
  "import": "./index.js"
54
+ },
55
+ "./next": {
56
+ "types": "./next/index.d.ts",
57
+ "import": "./next/index.js"
50
58
  }
51
59
  },
52
60
  "main": "./index.js",
@@ -55,11 +63,15 @@
55
63
  "peerDependencies": {
56
64
  "react": ">=18 <20",
57
65
  "react-dom": ">=18 <20",
58
- "@types/react": ""
66
+ "@types/react": "",
67
+ "next": ">=15"
59
68
  },
60
69
  "peerDependenciesMeta": {
61
70
  "@types/react": {
62
71
  "optional": true
72
+ },
73
+ "next": {
74
+ "optional": true
63
75
  }
64
76
  },
65
77
  "publishConfig": {