@shadowob/shared 0.3.4 → 0.4.1
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/dist/chunk-6H4LIJZC.js +0 -0
- package/dist/chunk-EMLX23LF.js +59 -0
- package/dist/chunk-PXKHJSTK.js +328 -0
- package/dist/constants/index.cjs +87 -0
- package/dist/constants/index.d.cts +55 -0
- package/dist/constants/index.d.ts +55 -0
- package/dist/constants/index.js +10 -0
- package/dist/index.cjs +424 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +35 -0
- package/dist/types/index.cjs +18 -0
- package/dist/types/index.d.cts +278 -0
- package/dist/types/index.d.ts +278 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/index.cjs +362 -0
- package/dist/utils/index.d.cts +41 -0
- package/dist/utils/index.d.ts +41 -0
- package/dist/utils/index.js +26 -0
- package/package.json +45 -7
- package/__tests__/constants.test.ts +0 -69
- package/__tests__/utils.test.ts +0 -80
- package/src/constants/events.ts +0 -28
- package/src/constants/index.ts +0 -2
- package/src/constants/limits.ts +0 -30
- package/src/index.ts +0 -3
- package/src/types/agent.types.ts +0 -44
- package/src/types/channel.types.ts +0 -36
- package/src/types/friendship.types.ts +0 -27
- package/src/types/index.ts +0 -6
- package/src/types/message.types.ts +0 -126
- package/src/types/server.types.ts +0 -38
- package/src/types/user.types.ts +0 -40
- package/src/utils/avatar-generator.ts +0 -231
- package/src/utils/index.ts +0 -24
- package/src/utils/pixel-cats.ts +0 -139
- package/tsconfig.json +0 -8
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
export type CatPattern = 'none' | 'tabby' | 'tuxedo' | 'siamese' | 'calico' | 'bicolor'
|
|
2
|
-
export type CatExpression =
|
|
3
|
-
| 'smile'
|
|
4
|
-
| 'open'
|
|
5
|
-
| 'flat'
|
|
6
|
-
| 'sad'
|
|
7
|
-
| 'surprised'
|
|
8
|
-
| 'kawaii'
|
|
9
|
-
| 'winking'
|
|
10
|
-
| 'smirk'
|
|
11
|
-
export type CatDecoration = 'none' | 'glasses' | 'blush' | 'scar' | 'flower' | 'fish' | 'headband'
|
|
12
|
-
export type BgPattern = 'none' | 'dots' | 'stripes' | 'grid' | 'stars'
|
|
13
|
-
|
|
14
|
-
export interface CatConfig {
|
|
15
|
-
bg: string
|
|
16
|
-
bgPattern: BgPattern
|
|
17
|
-
body: string
|
|
18
|
-
pattern: CatPattern
|
|
19
|
-
patternColor: string
|
|
20
|
-
eyeColor: string
|
|
21
|
-
expression: CatExpression
|
|
22
|
-
decoration: CatDecoration
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const COLORS = {
|
|
26
|
-
bg: [
|
|
27
|
-
'transparent',
|
|
28
|
-
'#1e1f22',
|
|
29
|
-
'#313338',
|
|
30
|
-
'#5865F2',
|
|
31
|
-
'#23a559',
|
|
32
|
-
'#da373c',
|
|
33
|
-
'#f472b6',
|
|
34
|
-
'#3b82f6',
|
|
35
|
-
'#fbbf24',
|
|
36
|
-
'#a855f7',
|
|
37
|
-
'#1abc9c',
|
|
38
|
-
'#f39c12',
|
|
39
|
-
'#e74c3c',
|
|
40
|
-
],
|
|
41
|
-
body: [
|
|
42
|
-
'#2d2d30',
|
|
43
|
-
'#e8842c',
|
|
44
|
-
'#e8e8e8',
|
|
45
|
-
'#7a7a80',
|
|
46
|
-
'#d4a574',
|
|
47
|
-
'#6b8094',
|
|
48
|
-
'#f472b6',
|
|
49
|
-
'#c8d6e5',
|
|
50
|
-
'#3e2723',
|
|
51
|
-
'#bdc3c7',
|
|
52
|
-
'#ffb8b8',
|
|
53
|
-
],
|
|
54
|
-
eyes: [
|
|
55
|
-
'#f8e71c',
|
|
56
|
-
'#00f3ff',
|
|
57
|
-
'#4ade80',
|
|
58
|
-
'#60a5fa',
|
|
59
|
-
'#a855f7',
|
|
60
|
-
'#fbbf24',
|
|
61
|
-
'#f87171',
|
|
62
|
-
'#ffc0cb',
|
|
63
|
-
'#1dd1a1',
|
|
64
|
-
'#e056fd',
|
|
65
|
-
],
|
|
66
|
-
pattern: ['#1a1a1c', '#ffffff', '#5a4a46', '#3d3d40', '#9a9aa0', '#d1ccc0', '#2d3436'],
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function getRandomElement<T>(arr: readonly T[]): T {
|
|
70
|
-
return arr[Math.floor(Math.random() * arr.length)]!
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function generateRandomCatConfig(): CatConfig {
|
|
74
|
-
return {
|
|
75
|
-
bg: getRandomElement(COLORS.bg),
|
|
76
|
-
bgPattern: getRandomElement(['none', 'dots', 'stripes', 'grid', 'stars'] as BgPattern[]),
|
|
77
|
-
body: getRandomElement(COLORS.body),
|
|
78
|
-
pattern: getRandomElement([
|
|
79
|
-
'none',
|
|
80
|
-
'tabby',
|
|
81
|
-
'tuxedo',
|
|
82
|
-
'siamese',
|
|
83
|
-
'calico',
|
|
84
|
-
'bicolor',
|
|
85
|
-
] as CatPattern[]),
|
|
86
|
-
patternColor: getRandomElement(COLORS.pattern),
|
|
87
|
-
eyeColor: getRandomElement(COLORS.eyes),
|
|
88
|
-
expression: getRandomElement([
|
|
89
|
-
'smile',
|
|
90
|
-
'open',
|
|
91
|
-
'flat',
|
|
92
|
-
'sad',
|
|
93
|
-
'surprised',
|
|
94
|
-
'kawaii',
|
|
95
|
-
'winking',
|
|
96
|
-
'smirk',
|
|
97
|
-
] as CatExpression[]),
|
|
98
|
-
decoration: getRandomElement([
|
|
99
|
-
'none',
|
|
100
|
-
'glasses',
|
|
101
|
-
'blush',
|
|
102
|
-
'scar',
|
|
103
|
-
'flower',
|
|
104
|
-
'fish',
|
|
105
|
-
'headband',
|
|
106
|
-
] as CatDecoration[]),
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export function renderCatSvg(config: CatConfig): string {
|
|
111
|
-
const { bg, bgPattern, body, pattern, patternColor, eyeColor, expression, decoration } = config
|
|
112
|
-
const stroke = '#1a1a1c'
|
|
113
|
-
|
|
114
|
-
let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">`
|
|
115
|
-
|
|
116
|
-
if (bg && bg !== 'transparent') {
|
|
117
|
-
svg += `<rect width="100" height="100" fill="${bg}" rx="20" />`
|
|
118
|
-
|
|
119
|
-
const pColor = `rgba(255,255,255,0.15)`
|
|
120
|
-
const cleanBg = bg.replace('#', '')
|
|
121
|
-
if (bgPattern === 'dots') {
|
|
122
|
-
svg += `<pattern id="p-${cleanBg}-dots" x="0" y="0" width="10" height="10" patternUnits="userSpaceOnUse"><circle cx="2" cy="2" r="2" fill="${pColor}"/></pattern><rect width="100" height="100" fill="url(#p-${cleanBg}-dots)" rx="20" />`
|
|
123
|
-
} else if (bgPattern === 'stripes') {
|
|
124
|
-
svg += `<pattern id="p-${cleanBg}-str" x="0" y="0" width="12" height="12" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><line x1="0" y1="0" x2="0" y2="12" stroke="${pColor}" stroke-width="4"/></pattern><rect width="100" height="100" fill="url(#p-${cleanBg}-str)" rx="20" />`
|
|
125
|
-
} else if (bgPattern === 'grid') {
|
|
126
|
-
svg += `<pattern id="p-${cleanBg}-grid" width="16" height="16" patternUnits="userSpaceOnUse"><path d="M 16 0 L 0 0 0 16" fill="none" stroke="${pColor}" stroke-width="1"/></pattern><rect width="100" height="100" fill="url(#p-${cleanBg}-grid)" rx="20" />`
|
|
127
|
-
} else if (bgPattern === 'stars') {
|
|
128
|
-
svg += `<pattern id="p-${cleanBg}-star" width="20" height="20" patternUnits="userSpaceOnUse"><path d="M10,2 L12,8 L18,8 L13,12 L15,18 L10,14 L5,18 L7,12 L2,8 L8,8 Z" fill="${pColor}" transform="scale(0.5) translate(5,5)"/></pattern><rect width="100" height="100" fill="url(#p-${cleanBg}-star)" rx="20" />`
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
svg += `<ellipse cx="50" cy="85" rx="30" ry="6" fill="rgba(0,0,0,0.2)"/>`
|
|
133
|
-
|
|
134
|
-
// Ears
|
|
135
|
-
svg += `<path d="M22,45 C15,22 28,18 34,22 C38,25 40,38 40,38" fill="${body}" stroke="${stroke}" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>`
|
|
136
|
-
svg += `<path d="M78,45 C85,22 72,18 66,22 C62,25 60,38 60,38" fill="${body}" stroke="${stroke}" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>`
|
|
137
|
-
svg += `<path d="M26,38 C22,26 29,24 33,26 C35,28 36,34 36,34" fill="#ffb8b8" opacity="0.8"/>`
|
|
138
|
-
svg += `<path d="M74,38 C78,26 71,24 67,26 C65,28 64,34 64,34" fill="#ffb8b8" opacity="0.8"/>`
|
|
139
|
-
|
|
140
|
-
// Face
|
|
141
|
-
svg += `<ellipse cx="50" cy="58" rx="38" ry="30" fill="${body}" stroke="${stroke}" stroke-width="2.5"/>`
|
|
142
|
-
|
|
143
|
-
// Patterns
|
|
144
|
-
if (pattern === 'tabby') {
|
|
145
|
-
svg += `<path d="M50,30 L50,40 M42,32 L46,39 M58,32 L54,39" stroke="${patternColor}" stroke-width="3" stroke-linecap="round" opacity="0.7"/>`
|
|
146
|
-
svg += `<path d="M18,55 L26,57 M20,62 L27,62" stroke="${patternColor}" stroke-width="2.5" stroke-linecap="round" opacity="0.7"/>`
|
|
147
|
-
svg += `<path d="M82,55 L74,57 M80,62 L73,62" stroke="${patternColor}" stroke-width="2.5" stroke-linecap="round" opacity="0.7"/>`
|
|
148
|
-
} else if (pattern === 'tuxedo') {
|
|
149
|
-
svg += `<path d="M50,56 C30,68 28,88 50,88 C72,88 70,68 50,56" fill="${patternColor}" opacity="0.95"/>`
|
|
150
|
-
svg += `<ellipse cx="50" cy="65" rx="16" ry="12" fill="${patternColor}" opacity="0.95"/>`
|
|
151
|
-
} else if (pattern === 'siamese') {
|
|
152
|
-
svg += `<ellipse cx="50" cy="62" rx="20" ry="16" fill="${patternColor}" opacity="0.6"/>`
|
|
153
|
-
} else if (pattern === 'calico') {
|
|
154
|
-
svg += `<path d="M25,40 Q35,30 45,45 Q35,55 25,40" fill="${patternColor}" opacity="0.8"/>`
|
|
155
|
-
svg += `<path d="M75,45 Q65,60 55,50 Q65,35 75,45" fill="#e8842c" opacity="0.8"/>`
|
|
156
|
-
} else if (pattern === 'bicolor') {
|
|
157
|
-
svg += `<path d="M12,58 Q30,30 50,40 Q60,70 50,88 Q12,88 12,58 Z" fill="${patternColor}" opacity="0.8"/>`
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (decoration === 'blush') {
|
|
161
|
-
svg += `<ellipse cx="28" cy="62" rx="5" ry="3" fill="#ff7675" opacity="0.7"/>`
|
|
162
|
-
svg += `<ellipse cx="72" cy="62" rx="5" ry="3" fill="#ff7675" opacity="0.7"/>`
|
|
163
|
-
}
|
|
164
|
-
if (decoration === 'scar') {
|
|
165
|
-
svg += `<path d="M28,42 L40,52 M30,48 L35,43 M34,51 L39,46 M32,53 L37,48" stroke="#d63031" stroke-width="1.5" stroke-linecap="round"/>`
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Eyes
|
|
169
|
-
const drawEye = (cx: number, cy: number, lookDir: number = 0) => {
|
|
170
|
-
if (expression === 'kawaii') {
|
|
171
|
-
return `<path d="M${cx - 8},${cy} Q${cx},${cy - 8} ${cx + 8},${cy} Q${cx},${cy - 3} ${cx - 8},${cy}" fill="${eyeColor}" stroke="${stroke}" stroke-width="1.5"/><circle cx="${cx + lookDir}" cy="${cy - 2}" r="3" fill="white"/><circle cx="${cx - 3 + lookDir}" cy="${cy}" r="1" fill="white"/>`
|
|
172
|
-
} else if (expression === 'winking' && cx > 50) {
|
|
173
|
-
return `<path d="M${cx - 7},${cy + 2} Q${cx},${cy - 4} ${cx + 7},${cy + 2}" fill="none" stroke="${stroke}" stroke-width="2.5" stroke-linecap="round"/>`
|
|
174
|
-
} else if (expression === 'surprised') {
|
|
175
|
-
return `<circle cx="${cx}" cy="${cy}" r="8" fill="white" stroke="${stroke}" stroke-width="1.5"/><circle cx="${cx}" cy="${cy}" r="3" fill="${eyeColor}"/>`
|
|
176
|
-
} else {
|
|
177
|
-
return `<circle cx="${cx}" cy="${cy}" r="7.5" fill="${eyeColor}" stroke="${stroke}" stroke-width="1.5"/><circle cx="${cx - 2 + lookDir}" cy="${cy - 2}" r="2.5" fill="white"/><circle cx="${cx + 2 + lookDir}" cy="${cy + 1}" r="1" fill="white"/>`
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (expression === 'smirk') {
|
|
182
|
-
svg += `<path d="M26,50 L42,50" stroke="${stroke}" stroke-width="2.5" stroke-linecap="round"/>`
|
|
183
|
-
svg += drawEye(66, 52, 2)
|
|
184
|
-
} else {
|
|
185
|
-
svg += drawEye(34, 52, 0)
|
|
186
|
-
svg += drawEye(66, 52, 0)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (decoration === 'glasses') {
|
|
190
|
-
svg += `<circle cx="34" cy="52" r="11" fill="rgba(255,255,255,0.2)" stroke="#2d3436" stroke-width="2.5"/>`
|
|
191
|
-
svg += `<circle cx="66" cy="52" r="11" fill="rgba(255,255,255,0.2)" stroke="#2d3436" stroke-width="2.5"/>`
|
|
192
|
-
svg += `<path d="M45,50 Q50,48 55,50" fill="none" stroke="#2d3436" stroke-width="2.5" stroke-linecap="round"/>`
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Nose
|
|
196
|
-
svg += `<path d="M47,62 L53,62 L50,65 Z" fill="#ff9ff3" stroke="${stroke}" stroke-width="1" stroke-linejoin="round"/>`
|
|
197
|
-
|
|
198
|
-
// Mouth
|
|
199
|
-
if (expression === 'smile' || expression === 'kawaii' || expression === 'winking') {
|
|
200
|
-
svg += `<path d="M42,67 Q46,72 50,67 M50,67 Q54,72 58,67" fill="none" stroke="${stroke}" stroke-width="2" stroke-linecap="round"/>`
|
|
201
|
-
} else if (expression === 'open') {
|
|
202
|
-
svg += `<path d="M46,67 Q50,75 54,67 Z" fill="#ff7675" stroke="${stroke}" stroke-width="1.5" stroke-linejoin="round"/>`
|
|
203
|
-
} else if (expression === 'flat') {
|
|
204
|
-
svg += `<path d="M47,68 L53,68" stroke="${stroke}" stroke-width="2" stroke-linecap="round"/>`
|
|
205
|
-
} else if (expression === 'sad') {
|
|
206
|
-
svg += `<path d="M43,70 Q50,64 57,70" fill="none" stroke="${stroke}" stroke-width="2" stroke-linecap="round"/>`
|
|
207
|
-
} else if (expression === 'surprised') {
|
|
208
|
-
svg += `<circle cx="50" cy="70" r="3" fill="#1a1a1c"/>`
|
|
209
|
-
} else if (expression === 'smirk') {
|
|
210
|
-
svg += `<path d="M46,67 Q52,69 56,64" fill="none" stroke="${stroke}" stroke-width="2" stroke-linecap="round"/>`
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Whiskers
|
|
214
|
-
svg += `<path d="M28,62 L15,60 M28,65 L14,66" stroke="${stroke}" stroke-width="1.5" stroke-linecap="round" opacity="0.6"/>`
|
|
215
|
-
svg += `<path d="M72,62 L85,60 M72,65 L86,66" stroke="${stroke}" stroke-width="1.5" stroke-linecap="round" opacity="0.6"/>`
|
|
216
|
-
|
|
217
|
-
// Decorations
|
|
218
|
-
if (decoration === 'headband') {
|
|
219
|
-
svg += `<path d="M16,35 Q50,20 84,35" fill="none" stroke="#e17055" stroke-width="6" stroke-linecap="round"/>`
|
|
220
|
-
svg += `<path d="M50,15 L60,25 L50,28 L40,25 Z" fill="#fab1a0" stroke="#e17055" stroke-width="2"/>`
|
|
221
|
-
} else if (decoration === 'flower') {
|
|
222
|
-
svg += `<path d="M75,30 Q80,20 85,30 Q95,25 85,35 Q90,45 80,40 Q70,45 75,35 Q65,25 75,30 Z" fill="#ffeaa7" stroke="#fdcb6e" stroke-width="1.5"/>`
|
|
223
|
-
svg += `<circle cx="80" cy="33" r="3" fill="#d63031"/>`
|
|
224
|
-
} else if (decoration === 'fish') {
|
|
225
|
-
svg += `<path d="M40,82 Q50,75 60,82 L65,78 L65,86 L60,82 Q50,89 40,82 Z" fill="#81ecec" stroke="${stroke}" stroke-width="1.5"/>`
|
|
226
|
-
svg += `<circle cx="45" cy="81" r="1" fill="${stroke}"/>`
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
svg += `</svg>`
|
|
230
|
-
return `data:image/svg+xml,${encodeURIComponent(svg.replace(/\n\s*/g, ''))}`
|
|
231
|
-
}
|
package/src/utils/index.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { customAlphabet } from 'nanoid'
|
|
2
|
-
|
|
3
|
-
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
|
4
|
-
|
|
5
|
-
export const generateInviteCode = customAlphabet(alphabet, 8)
|
|
6
|
-
|
|
7
|
-
export function formatDate(date: string | Date): string {
|
|
8
|
-
const d = typeof date === 'string' ? new Date(date) : date
|
|
9
|
-
return d.toISOString()
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function isValidEmail(email: string): boolean {
|
|
13
|
-
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function slugify(text: string): string {
|
|
17
|
-
return text
|
|
18
|
-
.toLowerCase()
|
|
19
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
20
|
-
.replace(/^-+|-+$/g, '')
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export * from './avatar-generator'
|
|
24
|
-
export * from './pixel-cats'
|
package/src/utils/pixel-cats.ts
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pixel Art Cat Avatar System
|
|
3
|
-
* 8 unique cat variants with distinct color schemes inspired by the Shadow logo
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
interface CatColors {
|
|
7
|
-
body: string
|
|
8
|
-
stroke: string
|
|
9
|
-
earInner: string
|
|
10
|
-
eyeL: string
|
|
11
|
-
eyeR: string
|
|
12
|
-
nose: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const variants: CatColors[] = [
|
|
16
|
-
// 0: Shadow — Black cat (logo style)
|
|
17
|
-
{
|
|
18
|
-
body: '#2d2d30',
|
|
19
|
-
stroke: '#1a1a1c',
|
|
20
|
-
earInner: '#3d3d40',
|
|
21
|
-
eyeL: '#f8e71c',
|
|
22
|
-
eyeR: '#00f3ff',
|
|
23
|
-
nose: '#3a2a26',
|
|
24
|
-
},
|
|
25
|
-
// 1: Mikan — Orange tabby
|
|
26
|
-
{
|
|
27
|
-
body: '#e8842c',
|
|
28
|
-
stroke: '#1a1a1c',
|
|
29
|
-
earInner: '#f5a623',
|
|
30
|
-
eyeL: '#4ade80',
|
|
31
|
-
eyeR: '#4ade80',
|
|
32
|
-
nose: '#d46b1a',
|
|
33
|
-
},
|
|
34
|
-
// 2: Yuki — White cat
|
|
35
|
-
{
|
|
36
|
-
body: '#e8e8e8',
|
|
37
|
-
stroke: '#a0a0a0',
|
|
38
|
-
earInner: '#ffc0cb',
|
|
39
|
-
eyeL: '#60a5fa',
|
|
40
|
-
eyeR: '#60a5fa',
|
|
41
|
-
nose: '#f5a0b0',
|
|
42
|
-
},
|
|
43
|
-
// 3: Haiiro — Gray cat
|
|
44
|
-
{
|
|
45
|
-
body: '#7a7a80',
|
|
46
|
-
stroke: '#4a4a50',
|
|
47
|
-
earInner: '#9a9aa0',
|
|
48
|
-
eyeL: '#fbbf24',
|
|
49
|
-
eyeR: '#fbbf24',
|
|
50
|
-
nose: '#5a4a46',
|
|
51
|
-
},
|
|
52
|
-
// 4: Tuxedo — Black & white accents
|
|
53
|
-
{
|
|
54
|
-
body: '#2d2d30',
|
|
55
|
-
stroke: '#1a1a1c',
|
|
56
|
-
earInner: '#e0e0e0',
|
|
57
|
-
eyeL: '#22c55e',
|
|
58
|
-
eyeR: '#22c55e',
|
|
59
|
-
nose: '#3a2a26',
|
|
60
|
-
},
|
|
61
|
-
// 5: Mocha — Cream/beige
|
|
62
|
-
{
|
|
63
|
-
body: '#d4a574',
|
|
64
|
-
stroke: '#8b6914',
|
|
65
|
-
earInner: '#e8c9a0',
|
|
66
|
-
eyeL: '#d97706',
|
|
67
|
-
eyeR: '#d97706',
|
|
68
|
-
nose: '#a0705a',
|
|
69
|
-
},
|
|
70
|
-
// 6: Blue — Russian blue
|
|
71
|
-
{
|
|
72
|
-
body: '#6b8094',
|
|
73
|
-
stroke: '#3d5060',
|
|
74
|
-
earInner: '#8ba0b4',
|
|
75
|
-
eyeL: '#22c55e',
|
|
76
|
-
eyeR: '#22c55e',
|
|
77
|
-
nose: '#5a6a76',
|
|
78
|
-
},
|
|
79
|
-
// 7: Sakura — Fantasy pink
|
|
80
|
-
{
|
|
81
|
-
body: '#f472b6',
|
|
82
|
-
stroke: '#be185d',
|
|
83
|
-
earInner: '#f9a8d4',
|
|
84
|
-
eyeL: '#a855f7',
|
|
85
|
-
eyeR: '#c084fc',
|
|
86
|
-
nose: '#d44a8c',
|
|
87
|
-
},
|
|
88
|
-
]
|
|
89
|
-
|
|
90
|
-
function makeCatSvg(c: CatColors): string {
|
|
91
|
-
return [
|
|
92
|
-
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">',
|
|
93
|
-
`<path d="M22,45 Q15,22 28,22 Q34,22 40,38" fill="${c.body}" stroke="${c.stroke}" stroke-width="2.5" stroke-linecap="round"/>`,
|
|
94
|
-
`<path d="M78,45 Q85,22 72,22 Q66,22 60,38" fill="${c.body}" stroke="${c.stroke}" stroke-width="2.5" stroke-linecap="round"/>`,
|
|
95
|
-
`<path d="M26,38 Q22,26 29,26 Q33,26 36,34" fill="${c.earInner}" opacity="0.5"/>`,
|
|
96
|
-
`<path d="M74,38 Q78,26 71,26 Q67,26 64,34" fill="${c.earInner}" opacity="0.5"/>`,
|
|
97
|
-
`<ellipse cx="50" cy="58" rx="36" ry="28" fill="${c.body}" stroke="${c.stroke}" stroke-width="2.5"/>`,
|
|
98
|
-
`<circle cx="35" cy="52" r="7" fill="${c.eyeL}" stroke="${c.stroke}" stroke-width="1.5"/>`,
|
|
99
|
-
`<circle cx="33" cy="49" r="2.5" fill="white"/>`,
|
|
100
|
-
`<circle cx="65" cy="52" r="7" fill="${c.eyeR}" stroke="${c.stroke}" stroke-width="1.5"/>`,
|
|
101
|
-
`<circle cx="63" cy="49" r="2.5" fill="white"/>`,
|
|
102
|
-
`<ellipse cx="50" cy="62" rx="3.5" ry="2.2" fill="${c.nose}"/>`,
|
|
103
|
-
`<path d="M42,67 Q46,72 50,67" fill="none" stroke="${c.stroke}" stroke-width="2" stroke-linecap="round"/>`,
|
|
104
|
-
`<path d="M50,67 Q54,72 58,67" fill="none" stroke="${c.stroke}" stroke-width="2" stroke-linecap="round"/>`,
|
|
105
|
-
'</svg>',
|
|
106
|
-
].join('')
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const catDataUris = variants.map((v) => {
|
|
110
|
-
const svg = makeCatSvg(v)
|
|
111
|
-
return `data:image/svg+xml,${encodeURIComponent(svg)}`
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
/** Get avatar data URI by index (0-7) */
|
|
115
|
-
export function getCatAvatar(index: number): string {
|
|
116
|
-
return catDataUris[index % catDataUris.length]!
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** Get deterministic avatar by user ID string */
|
|
120
|
-
export function getCatAvatarByUserId(userId: string): string {
|
|
121
|
-
let hash = 0
|
|
122
|
-
for (let i = 0; i < userId.length; i++) {
|
|
123
|
-
hash = ((hash << 5) - hash + userId.charCodeAt(i)) | 0
|
|
124
|
-
}
|
|
125
|
-
return getCatAvatar(Math.abs(hash) % catDataUris.length)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/** Get all cat avatars for selection UI */
|
|
129
|
-
export function getAllCatAvatars(): { index: number; name: string; dataUri: string }[] {
|
|
130
|
-
const names = ['影子', '蜜柑', '小雪', '灰灰', '燕尾服', '摩卡', '蓝蓝', '小樱']
|
|
131
|
-
return catDataUris.map((uri, i) => ({ index: i, name: names[i]!, dataUri: uri }))
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/** Get the raw SVG string for a cat variant (useful for generating PNG files) */
|
|
135
|
-
export function getCatSvgString(index: number): string {
|
|
136
|
-
return makeCatSvg(variants[index % variants.length]!)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export const CAT_AVATAR_COUNT = variants.length
|