@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.
@@ -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
- }
@@ -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'
@@ -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
package/tsconfig.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "rootDir": "./src",
5
- "outDir": "./dist"
6
- },
7
- "include": ["src"]
8
- }