@net-protocol/profiles 0.1.4 → 0.1.6
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 +6 -2
- package/dist/index.d.mts +96 -3
- package/dist/index.d.ts +96 -3
- package/dist/index.js +443 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +435 -1
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +28 -3
- package/dist/react.d.ts +28 -3
- package/dist/react.js +22 -0
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +22 -1
- package/dist/react.mjs.map +1 -1
- package/dist/{types-DSRRz8Ug.d.mts → types-DDKLfc0r.d.mts} +1 -0
- package/dist/{types-DSRRz8Ug.d.ts → types-DDKLfc0r.d.ts} +1 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -12,6 +12,8 @@ var PROFILE_METADATA_STORAGE_KEY = "net-beta0.0.1-profile-metadata";
|
|
|
12
12
|
var PROFILE_PICTURE_TOPIC = "profile-picture";
|
|
13
13
|
var PROFILE_METADATA_TOPIC = "profile-metadata";
|
|
14
14
|
var PROFILE_CANVAS_TOPIC = "profile-canvas";
|
|
15
|
+
var PROFILE_CSS_STORAGE_KEY = "net-beta0.0.1-profile-css";
|
|
16
|
+
var PROFILE_CSS_TOPIC = "profile-css";
|
|
15
17
|
function getValueArgForStorage(value) {
|
|
16
18
|
return viem.stringToHex(value);
|
|
17
19
|
}
|
|
@@ -117,21 +119,460 @@ function isValidTokenAddress(address) {
|
|
|
117
119
|
if (!address) return false;
|
|
118
120
|
return /^0x[a-fA-F0-9]{40}$/.test(address);
|
|
119
121
|
}
|
|
122
|
+
var MAX_CSS_SIZE = 10 * 1024;
|
|
123
|
+
function getProfileCSSStorageArgs(cssContent) {
|
|
124
|
+
const { bytesKey, bytesValue } = getBytesArgsForStorage(
|
|
125
|
+
PROFILE_CSS_STORAGE_KEY,
|
|
126
|
+
cssContent
|
|
127
|
+
);
|
|
128
|
+
return {
|
|
129
|
+
bytesKey,
|
|
130
|
+
topic: PROFILE_CSS_TOPIC,
|
|
131
|
+
bytesValue
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function sanitizeCSS(css) {
|
|
135
|
+
return css.replace(/<\/style>/gi, "").replace(/<script[\s\S]*?<\/script>/gi, "").replace(/javascript\s*:/gi, "").replace(/expression\s*\(/gi, "").replace(/behavior\s*:/gi, "").replace(/@import\b[^;]*;?/gi, "");
|
|
136
|
+
}
|
|
137
|
+
function isValidCSS(css) {
|
|
138
|
+
if (!css || css.trim().length === 0) return false;
|
|
139
|
+
if (Buffer.byteLength(css, "utf-8") > MAX_CSS_SIZE) return false;
|
|
140
|
+
const lowerCSS = css.toLowerCase();
|
|
141
|
+
if (lowerCSS.includes("expression(")) return false;
|
|
142
|
+
if (lowerCSS.includes("behavior:")) return false;
|
|
143
|
+
if (lowerCSS.includes("javascript:")) return false;
|
|
144
|
+
if (/<script/i.test(css)) return false;
|
|
145
|
+
if (lowerCSS.includes("</style")) return false;
|
|
146
|
+
if (/@import\b/.test(lowerCSS)) return false;
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/theme-selectors.ts
|
|
151
|
+
var THEME_SELECTORS = [
|
|
152
|
+
// --- CSS Variables (stable) ---
|
|
153
|
+
{
|
|
154
|
+
selector: "--background",
|
|
155
|
+
description: "Page background color (HSL values, e.g. '210 40% 2%')",
|
|
156
|
+
category: "variable"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
selector: "--foreground",
|
|
160
|
+
description: "Default text color",
|
|
161
|
+
category: "variable"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
selector: "--primary",
|
|
165
|
+
description: "Primary accent color (buttons, links, headings)",
|
|
166
|
+
category: "variable"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
selector: "--primary-foreground",
|
|
170
|
+
description: "Text on primary-colored elements",
|
|
171
|
+
category: "variable"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
selector: "--secondary",
|
|
175
|
+
description: "Secondary accent color",
|
|
176
|
+
category: "variable"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
selector: "--secondary-foreground",
|
|
180
|
+
description: "Text on secondary-colored elements",
|
|
181
|
+
category: "variable"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
selector: "--muted",
|
|
185
|
+
description: "Muted/subdued background",
|
|
186
|
+
category: "variable"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
selector: "--muted-foreground",
|
|
190
|
+
description: "Text on muted backgrounds",
|
|
191
|
+
category: "variable"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
selector: "--accent",
|
|
195
|
+
description: "Accent color for highlights",
|
|
196
|
+
category: "variable"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
selector: "--accent-foreground",
|
|
200
|
+
description: "Text on accent-colored elements",
|
|
201
|
+
category: "variable"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
selector: "--card",
|
|
205
|
+
description: "Card/panel background color",
|
|
206
|
+
category: "variable"
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
selector: "--card-foreground",
|
|
210
|
+
description: "Text inside cards",
|
|
211
|
+
category: "variable"
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
selector: "--border",
|
|
215
|
+
description: "Border color",
|
|
216
|
+
category: "variable"
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
selector: "--ring",
|
|
220
|
+
description: "Focus ring color",
|
|
221
|
+
category: "variable"
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
selector: "--radius",
|
|
225
|
+
description: "Border radius (e.g. '0.5rem')",
|
|
226
|
+
category: "variable"
|
|
227
|
+
},
|
|
228
|
+
// --- Layout selectors ---
|
|
229
|
+
{
|
|
230
|
+
selector: ".profile-themed",
|
|
231
|
+
description: "Root wrapper for all themed profile content",
|
|
232
|
+
category: "layout"
|
|
233
|
+
},
|
|
234
|
+
// --- Component selectors ---
|
|
235
|
+
{
|
|
236
|
+
selector: ".profile-header",
|
|
237
|
+
description: "Profile header card (name, avatar, bio, stat pills). Uses bg-gradient from-gray-900 to-gray-800, border-green-500",
|
|
238
|
+
category: "component"
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
selector: ".profile-tabs",
|
|
242
|
+
description: "Tab navigation bar (Canvas, Posts, Feed, Activity). Uses bg-gray-800, border-gray-700. Active tab uses bg-green-600",
|
|
243
|
+
category: "component"
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
selector: ".profile-content",
|
|
247
|
+
description: "Main content area below tabs (posts, canvas, feed, activity)",
|
|
248
|
+
category: "component"
|
|
249
|
+
}
|
|
250
|
+
];
|
|
251
|
+
var DEMO_THEMES = {
|
|
252
|
+
checkerboard: {
|
|
253
|
+
name: "Checkerboard",
|
|
254
|
+
css: `@keyframes checker-scroll {
|
|
255
|
+
0% { background-position: 0 0; }
|
|
256
|
+
100% { background-position: 40px 40px; }
|
|
257
|
+
}
|
|
258
|
+
.profile-themed {
|
|
259
|
+
--primary: 0 0% 100%;
|
|
260
|
+
--primary-foreground: 0 0% 0%;
|
|
261
|
+
--card: 0 0% 5%;
|
|
262
|
+
--card-foreground: 0 0% 95%;
|
|
263
|
+
--border: 0 0% 30%;
|
|
264
|
+
--ring: 0 0% 100%;
|
|
265
|
+
--muted-foreground: 0 0% 60%;
|
|
266
|
+
--radius: 0px;
|
|
267
|
+
color: #e0e0e0;
|
|
268
|
+
background-image: repeating-conic-gradient(#333 0% 25%, #111 0% 50%);
|
|
269
|
+
background-size: 40px 40px;
|
|
270
|
+
animation: checker-scroll 3s linear infinite;
|
|
271
|
+
}
|
|
272
|
+
.profile-themed .profile-header {
|
|
273
|
+
background: rgba(0,0,0,0.4) !important;
|
|
274
|
+
background-color: rgba(0,0,0,0.4) !important;
|
|
275
|
+
background-image: none !important;
|
|
276
|
+
border-color: #fff !important;
|
|
277
|
+
border-width: 2px;
|
|
278
|
+
border-radius: 0 !important;
|
|
279
|
+
backdrop-filter: blur(4px);
|
|
280
|
+
}
|
|
281
|
+
.profile-themed .profile-tabs {
|
|
282
|
+
background: rgba(0,0,0,0.35) !important;
|
|
283
|
+
background-color: rgba(0,0,0,0.35) !important;
|
|
284
|
+
border-color: #555 !important;
|
|
285
|
+
border-radius: 0 !important;
|
|
286
|
+
backdrop-filter: blur(4px);
|
|
287
|
+
}
|
|
288
|
+
.profile-themed .profile-tabs button { color: #888 !important; }
|
|
289
|
+
.profile-themed .profile-tabs button.bg-green-600 {
|
|
290
|
+
background-color: rgba(255,255,255,0.9) !important;
|
|
291
|
+
color: #000 !important;
|
|
292
|
+
border-radius: 0 !important;
|
|
293
|
+
}
|
|
294
|
+
.profile-themed .profile-content .border-green-400 {
|
|
295
|
+
border-color: #555 !important;
|
|
296
|
+
border-radius: 0 !important;
|
|
297
|
+
background: rgba(0,0,0,0.35) !important;
|
|
298
|
+
background-color: rgba(0,0,0,0.35) !important;
|
|
299
|
+
backdrop-filter: blur(4px);
|
|
300
|
+
}
|
|
301
|
+
.profile-themed .profile-content .text-green-400 { color: #fff !important; }
|
|
302
|
+
.profile-themed .profile-content .text-green-300 { color: #ccc !important; }
|
|
303
|
+
.profile-themed .profile-content .text-white { color: #e0e0e0 !important; }
|
|
304
|
+
.profile-themed .profile-content .text-gray-500 { color: #666 !important; }
|
|
305
|
+
.profile-themed .profile-content .text-gray-400 { color: #888 !important; }`
|
|
306
|
+
},
|
|
307
|
+
neonPulse: {
|
|
308
|
+
name: "Neon Pulse",
|
|
309
|
+
css: `@keyframes neon-glow {
|
|
310
|
+
0%, 100% { border-color: #ff00ff; box-shadow: 0 0 15px #ff00ff44; }
|
|
311
|
+
33% { border-color: #00ffff; box-shadow: 0 0 15px #00ffff44; }
|
|
312
|
+
66% { border-color: #ffff00; box-shadow: 0 0 15px #ffff0044; }
|
|
313
|
+
}
|
|
314
|
+
@keyframes hue-rotate {
|
|
315
|
+
0% { filter: hue-rotate(0deg); }
|
|
316
|
+
100% { filter: hue-rotate(360deg); }
|
|
317
|
+
}
|
|
318
|
+
.profile-themed {
|
|
319
|
+
--primary: 300 100% 60%;
|
|
320
|
+
--primary-foreground: 0 0% 100%;
|
|
321
|
+
--card: 260 80% 4%;
|
|
322
|
+
--card-foreground: 280 50% 92%;
|
|
323
|
+
--border: 300 100% 40%;
|
|
324
|
+
--ring: 300 100% 60%;
|
|
325
|
+
--muted-foreground: 280 30% 55%;
|
|
326
|
+
color: #e8d0ff;
|
|
327
|
+
}
|
|
328
|
+
.profile-themed .profile-header {
|
|
329
|
+
background: linear-gradient(135deg, #1a0030, #0d001a) !important;
|
|
330
|
+
border-width: 2px !important;
|
|
331
|
+
border-style: solid !important;
|
|
332
|
+
animation: neon-glow 4s ease-in-out infinite;
|
|
333
|
+
}
|
|
334
|
+
.profile-themed .profile-tabs {
|
|
335
|
+
background-color: #0d001a !important;
|
|
336
|
+
border-color: #6600aa !important;
|
|
337
|
+
}
|
|
338
|
+
.profile-themed .profile-tabs button { color: #aa66dd !important; }
|
|
339
|
+
.profile-themed .profile-tabs button.bg-green-600 {
|
|
340
|
+
background: linear-gradient(90deg, #ff00ff, #00ffff) !important;
|
|
341
|
+
color: #000 !important;
|
|
342
|
+
animation: hue-rotate 6s linear infinite;
|
|
343
|
+
}
|
|
344
|
+
.profile-themed .profile-content .border-green-400 {
|
|
345
|
+
border-width: 1px !important;
|
|
346
|
+
border-style: solid !important;
|
|
347
|
+
animation: neon-glow 4s ease-in-out infinite;
|
|
348
|
+
}
|
|
349
|
+
.profile-themed .profile-content .text-green-400 { color: #ff66ff !important; }
|
|
350
|
+
.profile-themed .profile-content .text-green-300 { color: #cc88ff !important; }
|
|
351
|
+
.profile-themed .profile-content .text-white { color: #e8d0ff !important; }
|
|
352
|
+
.profile-themed .profile-content .text-gray-500 { color: #6644aa !important; }
|
|
353
|
+
.profile-themed .profile-content .text-gray-400 { color: #9966cc !important; }`
|
|
354
|
+
},
|
|
355
|
+
sunset: {
|
|
356
|
+
name: "Sunset",
|
|
357
|
+
css: `@keyframes sunset-shift {
|
|
358
|
+
0%, 100% { background-position: 0% 50%; }
|
|
359
|
+
50% { background-position: 100% 50%; }
|
|
360
|
+
}
|
|
361
|
+
.profile-themed {
|
|
362
|
+
--primary: 25 100% 55%;
|
|
363
|
+
--primary-foreground: 0 0% 100%;
|
|
364
|
+
--card: 15 60% 6%;
|
|
365
|
+
--card-foreground: 35 80% 90%;
|
|
366
|
+
--border: 20 80% 30%;
|
|
367
|
+
--ring: 25 100% 55%;
|
|
368
|
+
--muted-foreground: 20 40% 50%;
|
|
369
|
+
color: #fde4c8;
|
|
370
|
+
background: linear-gradient(135deg, #1a0a00, #2d0a1e, #0a0a2d, #1a0a00);
|
|
371
|
+
background-size: 400% 400%;
|
|
372
|
+
animation: sunset-shift 15s ease-in-out infinite;
|
|
373
|
+
}
|
|
374
|
+
.profile-themed .profile-header {
|
|
375
|
+
background: linear-gradient(135deg, #3d1200, #2d0a1e, #1a0033) !important;
|
|
376
|
+
border-color: #ff6600 !important;
|
|
377
|
+
border-width: 2px;
|
|
378
|
+
box-shadow: 0 0 30px #ff440022;
|
|
379
|
+
}
|
|
380
|
+
.profile-themed .profile-tabs {
|
|
381
|
+
background-color: #1a0a00dd !important;
|
|
382
|
+
border-color: #663300 !important;
|
|
383
|
+
}
|
|
384
|
+
.profile-themed .profile-tabs button { color: #cc8855 !important; }
|
|
385
|
+
.profile-themed .profile-tabs button.bg-green-600 {
|
|
386
|
+
background: linear-gradient(90deg, #ff4400, #ff8800) !important;
|
|
387
|
+
color: #fff !important;
|
|
388
|
+
}
|
|
389
|
+
.profile-themed .profile-content .border-green-400 {
|
|
390
|
+
border-color: #ff660044 !important;
|
|
391
|
+
box-shadow: 0 0 10px #ff440011;
|
|
392
|
+
}
|
|
393
|
+
.profile-themed .profile-content .text-green-400 { color: #ff8844 !important; }
|
|
394
|
+
.profile-themed .profile-content .text-green-300 { color: #ffaa66 !important; }
|
|
395
|
+
.profile-themed .profile-content .text-white { color: #fde4c8 !important; }
|
|
396
|
+
.profile-themed .profile-content .text-gray-500 { color: #8a6040 !important; }
|
|
397
|
+
.profile-themed .profile-content .text-gray-400 { color: #bb8866 !important; }`
|
|
398
|
+
},
|
|
399
|
+
psychedelic: {
|
|
400
|
+
name: "Dreamscape",
|
|
401
|
+
css: `@keyframes dreamDrift {
|
|
402
|
+
0% { background-position: 0% 50%; }
|
|
403
|
+
25% { background-position: 100% 30%; }
|
|
404
|
+
50% { background-position: 80% 100%; }
|
|
405
|
+
75% { background-position: 20% 60%; }
|
|
406
|
+
100% { background-position: 0% 50%; }
|
|
407
|
+
}
|
|
408
|
+
@keyframes floatCircles {
|
|
409
|
+
0% { transform: translate(0, 0) rotate(0deg); }
|
|
410
|
+
33% { transform: translate(20px, -30px) rotate(120deg); }
|
|
411
|
+
66% { transform: translate(-15px, 20px) rotate(240deg); }
|
|
412
|
+
100% { transform: translate(0, 0) rotate(360deg); }
|
|
413
|
+
}
|
|
414
|
+
@keyframes flowBorder {
|
|
415
|
+
0% { background-position: 0 0, 0% 0%; }
|
|
416
|
+
100% { background-position: 0 0, 300% 300%; }
|
|
417
|
+
}
|
|
418
|
+
@keyframes glowPulse {
|
|
419
|
+
0% { box-shadow: 0 0 15px hsl(270 60% 70% / 0.3), 0 0 30px hsl(270 60% 65% / 0.1); }
|
|
420
|
+
50% { box-shadow: 0 0 25px hsl(220 60% 70% / 0.4), 0 0 50px hsl(220 50% 65% / 0.15); }
|
|
421
|
+
100% { box-shadow: 0 0 15px hsl(270 60% 70% / 0.3), 0 0 30px hsl(270 60% 65% / 0.1); }
|
|
422
|
+
}
|
|
423
|
+
@keyframes tabGlow {
|
|
424
|
+
0% { background-position: 0% 50%; }
|
|
425
|
+
50% { background-position: 100% 50%; }
|
|
426
|
+
100% { background-position: 0% 50%; }
|
|
427
|
+
}
|
|
428
|
+
.profile-themed {
|
|
429
|
+
--background: 260 30% 6%;
|
|
430
|
+
--foreground: 250 30% 90%;
|
|
431
|
+
--primary: 270 60% 72%;
|
|
432
|
+
--primary-foreground: 260 30% 10%;
|
|
433
|
+
--secondary: 220 50% 65%;
|
|
434
|
+
--secondary-foreground: 260 30% 10%;
|
|
435
|
+
--muted: 260 20% 15%;
|
|
436
|
+
--muted-foreground: 250 25% 65%;
|
|
437
|
+
--accent: 200 50% 70%;
|
|
438
|
+
--accent-foreground: 260 30% 10%;
|
|
439
|
+
--card: 260 25% 10%;
|
|
440
|
+
--card-foreground: 250 30% 90%;
|
|
441
|
+
--border: 270 40% 50%;
|
|
442
|
+
--ring: 270 60% 72%;
|
|
443
|
+
--radius: 0.75rem;
|
|
444
|
+
color: hsl(250 30% 90%) !important;
|
|
445
|
+
position: relative;
|
|
446
|
+
overflow: hidden;
|
|
447
|
+
background-image: linear-gradient(-45deg, hsl(260 30% 8%), hsl(270 50% 30%), hsl(220 40% 25%), hsl(280 40% 20%), hsl(240 30% 12%)) !important;
|
|
448
|
+
background-size: 400% 400%;
|
|
449
|
+
animation: dreamDrift 20s ease-in-out infinite;
|
|
450
|
+
}
|
|
451
|
+
.profile-themed::after {
|
|
452
|
+
content: "";
|
|
453
|
+
position: absolute;
|
|
454
|
+
top: 40px;
|
|
455
|
+
left: 30px;
|
|
456
|
+
width: 80px;
|
|
457
|
+
height: 80px;
|
|
458
|
+
border-radius: 50%;
|
|
459
|
+
background: hsl(270 60% 80% / 0.12);
|
|
460
|
+
box-shadow:
|
|
461
|
+
200px 100px 0 40px hsl(220 60% 80% / 0.1),
|
|
462
|
+
50px 300px 0 60px hsl(280 50% 75% / 0.1),
|
|
463
|
+
320px 400px 0 35px hsl(200 50% 80% / 0.12),
|
|
464
|
+
150px 550px 0 50px hsl(260 50% 75% / 0.1);
|
|
465
|
+
filter: blur(10px);
|
|
466
|
+
animation: floatCircles 30s ease-in-out infinite;
|
|
467
|
+
z-index: 2;
|
|
468
|
+
pointer-events: none;
|
|
469
|
+
}
|
|
470
|
+
.profile-themed .profile-header {
|
|
471
|
+
background: hsl(260 25% 10% / 0.75) !important;
|
|
472
|
+
background-image: none !important;
|
|
473
|
+
border-color: hsl(270 40% 50% / 0.3) !important;
|
|
474
|
+
backdrop-filter: blur(30px) saturate(140%);
|
|
475
|
+
box-shadow: 0 0 30px hsl(270 50% 60% / 0.15);
|
|
476
|
+
}
|
|
477
|
+
.profile-themed .profile-tabs {
|
|
478
|
+
background-color: hsl(260 25% 12% / 0.8) !important;
|
|
479
|
+
border-color: hsl(270 40% 50% / 0.3) !important;
|
|
480
|
+
backdrop-filter: blur(20px) saturate(140%);
|
|
481
|
+
}
|
|
482
|
+
.profile-themed .profile-tabs button {
|
|
483
|
+
color: hsl(250 25% 65%) !important;
|
|
484
|
+
}
|
|
485
|
+
.profile-themed .profile-tabs button.bg-green-600 {
|
|
486
|
+
background-image: linear-gradient(90deg, hsl(270 60% 65%), hsl(220 50% 65%), hsl(280 50% 70%)) !important;
|
|
487
|
+
background-size: 300% 300%;
|
|
488
|
+
animation: tabGlow 8s ease infinite;
|
|
489
|
+
color: hsl(0 0% 100%) !important;
|
|
490
|
+
}
|
|
491
|
+
.profile-themed .profile-content {
|
|
492
|
+
background: hsl(260 25% 8% / 0.6) !important;
|
|
493
|
+
}
|
|
494
|
+
.profile-themed .profile-content .border-green-400 {
|
|
495
|
+
border: 2px solid transparent !important;
|
|
496
|
+
background-image:
|
|
497
|
+
linear-gradient(hsl(260 25% 12% / 0.85), hsl(260 25% 12% / 0.85)),
|
|
498
|
+
linear-gradient(135deg, hsl(270 60% 65%), hsl(220 50% 65%), hsl(200 50% 70%), hsl(280 50% 70%), hsl(270 60% 65%)) !important;
|
|
499
|
+
background-origin: border-box !important;
|
|
500
|
+
background-clip: padding-box, border-box !important;
|
|
501
|
+
background-size: 100% 100%, 400% 400% !important;
|
|
502
|
+
backdrop-filter: blur(15px) saturate(140%);
|
|
503
|
+
animation: flowBorder 6s linear infinite, glowPulse 4s ease-in-out infinite;
|
|
504
|
+
}
|
|
505
|
+
.profile-themed .profile-content .text-green-400 {
|
|
506
|
+
color: hsl(270 60% 75%) !important;
|
|
507
|
+
text-shadow: 0 0 12px hsl(270 60% 70% / 0.4);
|
|
508
|
+
}
|
|
509
|
+
.profile-themed .profile-content .text-green-300 { color: hsl(220 50% 75%) !important; }
|
|
510
|
+
.profile-themed .profile-content .text-white { color: hsl(250 30% 90%) !important; }
|
|
511
|
+
.profile-themed .profile-content .text-gray-500 { color: hsl(250 20% 50%) !important; }
|
|
512
|
+
.profile-themed .profile-content .text-gray-400 { color: hsl(250 25% 65%) !important; }`
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
function buildCSSPrompt() {
|
|
516
|
+
const variableLines = THEME_SELECTORS.filter(
|
|
517
|
+
(s) => s.category === "variable"
|
|
518
|
+
).map((s) => ` ${s.selector}: ${s.description}`).join("\n");
|
|
519
|
+
return `You are a CSS theme generator for a user profile page.
|
|
520
|
+
All styles MUST be scoped under .profile-themed.
|
|
521
|
+
|
|
522
|
+
## CSS Variables (set inside .profile-themed { ... })
|
|
523
|
+
HSL values WITHOUT hsl() wrapper (e.g. "210 40% 98%"):
|
|
524
|
+
${variableLines}
|
|
525
|
+
|
|
526
|
+
## Component Selectors
|
|
527
|
+
.profile-themed \u2014 root wrapper; set CSS variables, \`color\`, and optionally background/background-image/animation for full-page effects
|
|
528
|
+
.profile-themed .profile-header \u2014 header card; override background, background-image: none, border-color
|
|
529
|
+
.profile-themed .profile-tabs \u2014 tab bar; override background-color, border-color
|
|
530
|
+
.profile-themed .profile-tabs button \u2014 inactive tab text color
|
|
531
|
+
.profile-themed .profile-tabs button.bg-green-600 \u2014 active tab background + color
|
|
532
|
+
.profile-themed .profile-content \u2014 content area below tabs
|
|
533
|
+
.profile-themed .profile-content .border-green-400 \u2014 post card borders + background
|
|
534
|
+
.profile-themed .profile-content .text-green-400 \u2014 post links/usernames
|
|
535
|
+
.profile-themed .profile-content .text-green-300 \u2014 secondary links
|
|
536
|
+
.profile-themed .profile-content .text-white \u2014 post body text
|
|
537
|
+
.profile-themed .profile-content .text-gray-500 \u2014 timestamps
|
|
538
|
+
.profile-themed .profile-content .text-gray-400 \u2014 secondary text
|
|
539
|
+
|
|
540
|
+
## Rules
|
|
541
|
+
1. All selectors MUST start with .profile-themed
|
|
542
|
+
2. Use !important on color, background, background-color, background-image, border-color overrides (needed to beat Tailwind utilities)
|
|
543
|
+
3. Set background-image: none !important on .profile-header to clear its default gradient
|
|
544
|
+
4. Set \`color\` on .profile-themed for inherited text color
|
|
545
|
+
5. @keyframes animations are encouraged \u2014 use for backgrounds, borders, glows
|
|
546
|
+
6. IMPORTANT: Do NOT use \`background\` shorthand with !important if you animate background-position \u2014 the shorthand locks background-position with !important and animations cannot override it. Use \`background-image\` instead.
|
|
547
|
+
7. backdrop-filter, box-shadow, and gradients are supported
|
|
548
|
+
8. Use valid CSS only \u2014 no JS, no expressions, no imports
|
|
549
|
+
9. Keep under 10KB
|
|
550
|
+
10. HSL values are bare: "210 40% 98%" not "hsl(210, 40%, 98%)"
|
|
551
|
+
|
|
552
|
+
Output ONLY the CSS.`;
|
|
553
|
+
}
|
|
120
554
|
|
|
121
555
|
Object.defineProperty(exports, "STORAGE_CONTRACT", {
|
|
122
556
|
enumerable: true,
|
|
123
557
|
get: function () { return storage.STORAGE_CONTRACT; }
|
|
124
558
|
});
|
|
559
|
+
exports.DEMO_THEMES = DEMO_THEMES;
|
|
560
|
+
exports.MAX_CSS_SIZE = MAX_CSS_SIZE;
|
|
125
561
|
exports.PROFILE_CANVAS_STORAGE_KEY = PROFILE_CANVAS_STORAGE_KEY;
|
|
126
562
|
exports.PROFILE_CANVAS_TOPIC = PROFILE_CANVAS_TOPIC;
|
|
563
|
+
exports.PROFILE_CSS_STORAGE_KEY = PROFILE_CSS_STORAGE_KEY;
|
|
564
|
+
exports.PROFILE_CSS_TOPIC = PROFILE_CSS_TOPIC;
|
|
127
565
|
exports.PROFILE_METADATA_STORAGE_KEY = PROFILE_METADATA_STORAGE_KEY;
|
|
128
566
|
exports.PROFILE_METADATA_TOPIC = PROFILE_METADATA_TOPIC;
|
|
129
567
|
exports.PROFILE_PICTURE_STORAGE_KEY = PROFILE_PICTURE_STORAGE_KEY;
|
|
130
568
|
exports.PROFILE_PICTURE_TOPIC = PROFILE_PICTURE_TOPIC;
|
|
131
569
|
exports.PROFILE_X_USERNAME_STORAGE_KEY = PROFILE_X_USERNAME_STORAGE_KEY;
|
|
570
|
+
exports.THEME_SELECTORS = THEME_SELECTORS;
|
|
571
|
+
exports.buildCSSPrompt = buildCSSPrompt;
|
|
132
572
|
exports.getBioStorageArgs = getBioStorageArgs;
|
|
133
573
|
exports.getBytesArgsForStorage = getBytesArgsForStorage;
|
|
134
574
|
exports.getDisplayNameStorageArgs = getDisplayNameStorageArgs;
|
|
575
|
+
exports.getProfileCSSStorageArgs = getProfileCSSStorageArgs;
|
|
135
576
|
exports.getProfileCanvasStorageArgs = getProfileCanvasStorageArgs;
|
|
136
577
|
exports.getProfileMetadataStorageArgs = getProfileMetadataStorageArgs;
|
|
137
578
|
exports.getProfilePictureStorageArgs = getProfilePictureStorageArgs;
|
|
@@ -139,10 +580,12 @@ exports.getTokenAddressStorageArgs = getTokenAddressStorageArgs;
|
|
|
139
580
|
exports.getValueArgForStorage = getValueArgForStorage;
|
|
140
581
|
exports.getXUsernameStorageArgs = getXUsernameStorageArgs;
|
|
141
582
|
exports.isValidBio = isValidBio;
|
|
583
|
+
exports.isValidCSS = isValidCSS;
|
|
142
584
|
exports.isValidDisplayName = isValidDisplayName;
|
|
143
585
|
exports.isValidTokenAddress = isValidTokenAddress;
|
|
144
586
|
exports.isValidUrl = isValidUrl;
|
|
145
587
|
exports.isValidXUsername = isValidXUsername;
|
|
146
588
|
exports.parseProfileMetadata = parseProfileMetadata;
|
|
589
|
+
exports.sanitizeCSS = sanitizeCSS;
|
|
147
590
|
//# sourceMappingURL=index.js.map
|
|
148
591
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/constants.ts","../src/utils.ts"],"names":["stringToHex","toBytes32"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Profile-related storage keys\n *\n * Using descriptive keys under 32 bytes to avoid hashing complexity and work seamlessly\n * with existing storage infrastructure. The key includes app prefix and versioning for\n * clarity and future-proofing.\n *\n * NOTE: if we change these keys, users will not be able to see their profile data\n */\nexport const PROFILE_CANVAS_STORAGE_KEY = \"net-beta0.0.1-profile-canvas\";\nexport const PROFILE_PICTURE_STORAGE_KEY = \"net-beta0.0.1-profile-picture\";\nexport const PROFILE_X_USERNAME_STORAGE_KEY =\n \"net-beta0.0.1-profile-x-username\";\nexport const PROFILE_METADATA_STORAGE_KEY = \"net-beta0.0.1-profile-metadata\";\n\n/**\n * Topic strings used when writing to storage\n * These are the second argument to Storage.put()\n */\nexport const PROFILE_PICTURE_TOPIC = \"profile-picture\";\nexport const PROFILE_METADATA_TOPIC = \"profile-metadata\";\nexport const PROFILE_CANVAS_TOPIC = \"profile-canvas\";\n","import { stringToHex } from \"viem\";\nimport { toBytes32 } from \"@net-protocol/core\";\nimport {\n PROFILE_PICTURE_STORAGE_KEY,\n PROFILE_METADATA_STORAGE_KEY,\n PROFILE_CANVAS_STORAGE_KEY,\n PROFILE_PICTURE_TOPIC,\n PROFILE_METADATA_TOPIC,\n PROFILE_CANVAS_TOPIC,\n} from \"./constants\";\nimport type { ProfileMetadata, ProfileStorageArgs } from \"./types\";\n\n/**\n * Convert a string value to hex for storage\n */\nexport function getValueArgForStorage(value: string): `0x${string}` {\n return stringToHex(value);\n}\n\n/**\n * Get storage args (key as bytes32, value as hex)\n */\nexport function getBytesArgsForStorage(\n key: string,\n value: string\n): { bytesKey: `0x${string}`; bytesValue: `0x${string}` } {\n const bytesKey = toBytes32(key) as `0x${string}`;\n const bytesValue = getValueArgForStorage(value);\n return { bytesKey, bytesValue };\n}\n\n/**\n * Prepare transaction arguments for updating profile picture\n *\n * @param imageUrl - The URL of the profile picture\n * @returns Arguments for Storage.put() - [bytesKey, topic, bytesValue]\n *\n * @example\n * ```ts\n * const args = getProfilePictureStorageArgs(\"https://example.com/image.jpg\");\n * // Use with wagmi writeContract:\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfilePictureStorageArgs(\n imageUrl: string\n): ProfileStorageArgs {\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_PICTURE_STORAGE_KEY,\n imageUrl\n );\n return {\n bytesKey,\n topic: PROFILE_PICTURE_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Prepare transaction arguments for updating profile metadata (X username, etc.)\n *\n * @param metadata - Profile metadata object to store\n * @returns Arguments for Storage.put() - [bytesKey, topic, bytesValue]\n *\n * @example\n * ```ts\n * const args = getProfileMetadataStorageArgs({ x_username: \"myusername\" });\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfileMetadataStorageArgs(\n metadata: ProfileMetadata\n): ProfileStorageArgs {\n const jsonString = JSON.stringify(metadata);\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_METADATA_STORAGE_KEY,\n jsonString\n );\n return {\n bytesKey,\n topic: PROFILE_METADATA_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Prepare transaction arguments for updating X username\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * Note: Username is stored WITHOUT the @ prefix. The @ is stripped if provided.\n *\n * @param username - X/Twitter username (with or without @)\n * @returns Arguments for Storage.put()\n */\nexport function getXUsernameStorageArgs(username: string): ProfileStorageArgs {\n // Strip @ prefix if present - store username without @\n const normalizedUsername = username.startsWith(\"@\")\n ? username.slice(1)\n : username;\n return getProfileMetadataStorageArgs({ x_username: normalizedUsername });\n}\n\n/**\n * Prepare transaction arguments for updating profile canvas (HTML content)\n *\n * @param htmlContent - HTML content for the profile canvas\n * @returns Arguments for Storage.put()\n *\n * @example\n * ```ts\n * const args = getProfileCanvasStorageArgs(\"<div>My custom profile</div>\");\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfileCanvasStorageArgs(\n htmlContent: string\n): ProfileStorageArgs {\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_CANVAS_STORAGE_KEY,\n htmlContent\n );\n return {\n bytesKey,\n topic: PROFILE_CANVAS_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Parse profile metadata JSON and extract profile data\n *\n * @param jsonData - JSON string from storage\n * @returns Parsed profile metadata or undefined if invalid\n */\nexport function parseProfileMetadata(\n jsonData: string\n): ProfileMetadata | undefined {\n try {\n const parsed = JSON.parse(jsonData);\n const storedUsername =\n parsed?.x_username &&\n typeof parsed.x_username === \"string\" &&\n parsed.x_username.length > 0\n ? parsed.x_username\n : undefined;\n\n // Strip @ if present for backwards compatibility with older stored data\n const usernameWithoutAt = storedUsername?.startsWith(\"@\")\n ? storedUsername.slice(1)\n : storedUsername;\n\n // Extract bio if present\n const bio =\n parsed?.bio && typeof parsed.bio === \"string\" && parsed.bio.length > 0\n ? parsed.bio\n : undefined;\n\n // Extract display name if present\n const display_name =\n parsed?.display_name &&\n typeof parsed.display_name === \"string\" &&\n parsed.display_name.length > 0\n ? parsed.display_name\n : undefined;\n\n // Extract token address if present (stored as lowercase)\n const token_address =\n parsed?.token_address &&\n typeof parsed.token_address === \"string\" &&\n parsed.token_address.length > 0\n ? parsed.token_address.toLowerCase()\n : undefined;\n\n return {\n x_username: usernameWithoutAt,\n bio,\n display_name,\n token_address,\n };\n } catch {\n return undefined;\n }\n}\n\n/**\n * Validate that a string is a valid URL\n */\nexport function isValidUrl(url: string): boolean {\n if (!url) return false;\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Validate X/Twitter username format\n * Returns true if valid (alphanumeric and underscores, 1-15 chars)\n */\nexport function isValidXUsername(username: string): boolean {\n if (!username) return false;\n // Remove @ prefix if present\n const cleanUsername = username.startsWith(\"@\") ? username.slice(1) : username;\n // X usernames: 1-15 chars, alphanumeric and underscores only\n return /^[a-zA-Z0-9_]{1,15}$/.test(cleanUsername);\n}\n\n/**\n * Validate bio format\n * Returns true if valid (max 280 chars, no control characters except newlines)\n */\nexport function isValidBio(bio: string): boolean {\n if (!bio) return false;\n if (bio.length > 280) return false;\n // Allow printable characters, spaces, and newlines. Disallow other control chars.\n // eslint-disable-next-line no-control-regex\n const hasControlChars = /[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]/.test(bio);\n return !hasControlChars;\n}\n\n/**\n * Validate display name format\n * Returns true if valid (1-25 characters, no control characters except spaces)\n */\nexport function isValidDisplayName(displayName: string): boolean {\n if (!displayName) return false;\n if (displayName.length > 25) return false;\n // Disallow control characters\n // eslint-disable-next-line no-control-regex\n const hasControlChars = /[\\x00-\\x1F\\x7F]/.test(displayName);\n return !hasControlChars;\n}\n\n/**\n * Prepare transaction arguments for updating bio\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * @param bio - The bio text\n * @returns Arguments for Storage.put()\n */\nexport function getBioStorageArgs(bio: string): ProfileStorageArgs {\n return getProfileMetadataStorageArgs({ bio });\n}\n\n/**\n * Prepare transaction arguments for updating display name\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * @param displayName - The display name (max 25 characters)\n * @returns Arguments for Storage.put()\n */\nexport function getDisplayNameStorageArgs(\n displayName: string\n): ProfileStorageArgs {\n return getProfileMetadataStorageArgs({ display_name: displayName });\n}\n\n/**\n * Prepare transaction arguments for updating token address\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * @param tokenAddress - The ERC-20 token contract address (stored as lowercase)\n * @returns Arguments for Storage.put()\n */\nexport function getTokenAddressStorageArgs(\n tokenAddress: string\n): ProfileStorageArgs {\n return getProfileMetadataStorageArgs({\n token_address: tokenAddress.toLowerCase(),\n });\n}\n\n/**\n * Validate that a string is a valid EVM token address\n * Returns true if valid (0x-prefixed, 40 hex characters)\n */\nexport function isValidTokenAddress(address: string): boolean {\n if (!address) return false;\n return /^0x[a-fA-F0-9]{40}$/.test(address);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/utils.ts","../src/theme-selectors.ts"],"names":["stringToHex","toBytes32"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Profile-related storage keys\n *\n * Using descriptive keys under 32 bytes to avoid hashing complexity and work seamlessly\n * with existing storage infrastructure. The key includes app prefix and versioning for\n * clarity and future-proofing.\n *\n * NOTE: if we change these keys, users will not be able to see their profile data\n */\nexport const PROFILE_CANVAS_STORAGE_KEY = \"net-beta0.0.1-profile-canvas\";\nexport const PROFILE_PICTURE_STORAGE_KEY = \"net-beta0.0.1-profile-picture\";\nexport const PROFILE_X_USERNAME_STORAGE_KEY =\n \"net-beta0.0.1-profile-x-username\";\nexport const PROFILE_METADATA_STORAGE_KEY = \"net-beta0.0.1-profile-metadata\";\n\n/**\n * Topic strings used when writing to storage\n * These are the second argument to Storage.put()\n */\nexport const PROFILE_PICTURE_TOPIC = \"profile-picture\";\nexport const PROFILE_METADATA_TOPIC = \"profile-metadata\";\nexport const PROFILE_CANVAS_TOPIC = \"profile-canvas\";\nexport const PROFILE_CSS_STORAGE_KEY = \"net-beta0.0.1-profile-css\";\nexport const PROFILE_CSS_TOPIC = \"profile-css\";\n","import { stringToHex } from \"viem\";\nimport { toBytes32 } from \"@net-protocol/core\";\nimport {\n PROFILE_PICTURE_STORAGE_KEY,\n PROFILE_METADATA_STORAGE_KEY,\n PROFILE_CANVAS_STORAGE_KEY,\n PROFILE_CSS_STORAGE_KEY,\n PROFILE_PICTURE_TOPIC,\n PROFILE_METADATA_TOPIC,\n PROFILE_CANVAS_TOPIC,\n PROFILE_CSS_TOPIC,\n} from \"./constants\";\nimport type { ProfileMetadata, ProfileStorageArgs } from \"./types\";\n\n/**\n * Convert a string value to hex for storage\n */\nexport function getValueArgForStorage(value: string): `0x${string}` {\n return stringToHex(value);\n}\n\n/**\n * Get storage args (key as bytes32, value as hex)\n */\nexport function getBytesArgsForStorage(\n key: string,\n value: string\n): { bytesKey: `0x${string}`; bytesValue: `0x${string}` } {\n const bytesKey = toBytes32(key) as `0x${string}`;\n const bytesValue = getValueArgForStorage(value);\n return { bytesKey, bytesValue };\n}\n\n/**\n * Prepare transaction arguments for updating profile picture\n *\n * @param imageUrl - The URL of the profile picture\n * @returns Arguments for Storage.put() - [bytesKey, topic, bytesValue]\n *\n * @example\n * ```ts\n * const args = getProfilePictureStorageArgs(\"https://example.com/image.jpg\");\n * // Use with wagmi writeContract:\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfilePictureStorageArgs(\n imageUrl: string\n): ProfileStorageArgs {\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_PICTURE_STORAGE_KEY,\n imageUrl\n );\n return {\n bytesKey,\n topic: PROFILE_PICTURE_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Prepare transaction arguments for updating profile metadata (X username, etc.)\n *\n * @param metadata - Profile metadata object to store\n * @returns Arguments for Storage.put() - [bytesKey, topic, bytesValue]\n *\n * @example\n * ```ts\n * const args = getProfileMetadataStorageArgs({ x_username: \"myusername\" });\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfileMetadataStorageArgs(\n metadata: ProfileMetadata\n): ProfileStorageArgs {\n const jsonString = JSON.stringify(metadata);\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_METADATA_STORAGE_KEY,\n jsonString\n );\n return {\n bytesKey,\n topic: PROFILE_METADATA_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Prepare transaction arguments for updating X username\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * Note: Username is stored WITHOUT the @ prefix. The @ is stripped if provided.\n *\n * @param username - X/Twitter username (with or without @)\n * @returns Arguments for Storage.put()\n */\nexport function getXUsernameStorageArgs(username: string): ProfileStorageArgs {\n // Strip @ prefix if present - store username without @\n const normalizedUsername = username.startsWith(\"@\")\n ? username.slice(1)\n : username;\n return getProfileMetadataStorageArgs({ x_username: normalizedUsername });\n}\n\n/**\n * Prepare transaction arguments for updating profile canvas (HTML content)\n *\n * @param htmlContent - HTML content for the profile canvas\n * @returns Arguments for Storage.put()\n *\n * @example\n * ```ts\n * const args = getProfileCanvasStorageArgs(\"<div>My custom profile</div>\");\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfileCanvasStorageArgs(\n htmlContent: string\n): ProfileStorageArgs {\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_CANVAS_STORAGE_KEY,\n htmlContent\n );\n return {\n bytesKey,\n topic: PROFILE_CANVAS_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Parse profile metadata JSON and extract profile data\n *\n * @param jsonData - JSON string from storage\n * @returns Parsed profile metadata or undefined if invalid\n */\nexport function parseProfileMetadata(\n jsonData: string\n): ProfileMetadata | undefined {\n try {\n const parsed = JSON.parse(jsonData);\n const storedUsername =\n parsed?.x_username &&\n typeof parsed.x_username === \"string\" &&\n parsed.x_username.length > 0\n ? parsed.x_username\n : undefined;\n\n // Strip @ if present for backwards compatibility with older stored data\n const usernameWithoutAt = storedUsername?.startsWith(\"@\")\n ? storedUsername.slice(1)\n : storedUsername;\n\n // Extract bio if present\n const bio =\n parsed?.bio && typeof parsed.bio === \"string\" && parsed.bio.length > 0\n ? parsed.bio\n : undefined;\n\n // Extract display name if present\n const display_name =\n parsed?.display_name &&\n typeof parsed.display_name === \"string\" &&\n parsed.display_name.length > 0\n ? parsed.display_name\n : undefined;\n\n // Extract token address if present (stored as lowercase)\n const token_address =\n parsed?.token_address &&\n typeof parsed.token_address === \"string\" &&\n parsed.token_address.length > 0\n ? parsed.token_address.toLowerCase()\n : undefined;\n\n return {\n x_username: usernameWithoutAt,\n bio,\n display_name,\n token_address,\n };\n } catch {\n return undefined;\n }\n}\n\n/**\n * Validate that a string is a valid URL\n */\nexport function isValidUrl(url: string): boolean {\n if (!url) return false;\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Validate X/Twitter username format\n * Returns true if valid (alphanumeric and underscores, 1-15 chars)\n */\nexport function isValidXUsername(username: string): boolean {\n if (!username) return false;\n // Remove @ prefix if present\n const cleanUsername = username.startsWith(\"@\") ? username.slice(1) : username;\n // X usernames: 1-15 chars, alphanumeric and underscores only\n return /^[a-zA-Z0-9_]{1,15}$/.test(cleanUsername);\n}\n\n/**\n * Validate bio format\n * Returns true if valid (max 280 chars, no control characters except newlines)\n */\nexport function isValidBio(bio: string): boolean {\n if (!bio) return false;\n if (bio.length > 280) return false;\n // Allow printable characters, spaces, and newlines. Disallow other control chars.\n // eslint-disable-next-line no-control-regex\n const hasControlChars = /[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]/.test(bio);\n return !hasControlChars;\n}\n\n/**\n * Validate display name format\n * Returns true if valid (1-25 characters, no control characters except spaces)\n */\nexport function isValidDisplayName(displayName: string): boolean {\n if (!displayName) return false;\n if (displayName.length > 25) return false;\n // Disallow control characters\n // eslint-disable-next-line no-control-regex\n const hasControlChars = /[\\x00-\\x1F\\x7F]/.test(displayName);\n return !hasControlChars;\n}\n\n/**\n * Prepare transaction arguments for updating bio\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * @param bio - The bio text\n * @returns Arguments for Storage.put()\n */\nexport function getBioStorageArgs(bio: string): ProfileStorageArgs {\n return getProfileMetadataStorageArgs({ bio });\n}\n\n/**\n * Prepare transaction arguments for updating display name\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * @param displayName - The display name (max 25 characters)\n * @returns Arguments for Storage.put()\n */\nexport function getDisplayNameStorageArgs(\n displayName: string\n): ProfileStorageArgs {\n return getProfileMetadataStorageArgs({ display_name: displayName });\n}\n\n/**\n * Prepare transaction arguments for updating token address\n * This is a convenience wrapper around getProfileMetadataStorageArgs\n *\n * @param tokenAddress - The ERC-20 token contract address (stored as lowercase)\n * @returns Arguments for Storage.put()\n */\nexport function getTokenAddressStorageArgs(\n tokenAddress: string\n): ProfileStorageArgs {\n return getProfileMetadataStorageArgs({\n token_address: tokenAddress.toLowerCase(),\n });\n}\n\n/**\n * Validate that a string is a valid EVM token address\n * Returns true if valid (0x-prefixed, 40 hex characters)\n */\nexport function isValidTokenAddress(address: string): boolean {\n if (!address) return false;\n return /^0x[a-fA-F0-9]{40}$/.test(address);\n}\n\n/**\n * Maximum CSS size in bytes (10KB — CSS should be small)\n */\nexport const MAX_CSS_SIZE = 10 * 1024;\n\n/**\n * Prepare transaction arguments for updating profile custom CSS\n *\n * @param cssContent - CSS string to store\n * @returns Arguments for Storage.put() - [bytesKey, topic, bytesValue]\n *\n * @example\n * ```ts\n * const args = getProfileCSSStorageArgs(\".profile-themed { --primary: 210 40% 98%; }\");\n * writeContract({\n * abi: STORAGE_CONTRACT.abi,\n * address: STORAGE_CONTRACT.address,\n * functionName: \"put\",\n * args: [args.bytesKey, args.topic, args.bytesValue],\n * });\n * ```\n */\nexport function getProfileCSSStorageArgs(\n cssContent: string\n): ProfileStorageArgs {\n const { bytesKey, bytesValue } = getBytesArgsForStorage(\n PROFILE_CSS_STORAGE_KEY,\n cssContent\n );\n return {\n bytesKey,\n topic: PROFILE_CSS_TOPIC,\n bytesValue,\n };\n}\n\n/**\n * Sanitize user CSS to prevent injection attacks.\n * - Strips </style> (which could break out of the style element during SSR)\n * - Strips <script> tags\n * - Removes javascript: URIs, expression(), behavior: (legacy IE vectors)\n * - Removes @import rules (could load external resources / exfiltrate data)\n */\nexport function sanitizeCSS(css: string): string {\n return css\n .replace(/<\\/style>/gi, \"\")\n .replace(/<script[\\s\\S]*?<\\/script>/gi, \"\")\n .replace(/javascript\\s*:/gi, \"\")\n .replace(/expression\\s*\\(/gi, \"\")\n .replace(/behavior\\s*:/gi, \"\")\n .replace(/@import\\b[^;]*;?/gi, \"\");\n}\n\n/**\n * Validate CSS content\n * Returns true if valid (non-empty, within size limit, no script injection)\n */\nexport function isValidCSS(css: string): boolean {\n if (!css || css.trim().length === 0) return false;\n if (Buffer.byteLength(css, \"utf-8\") > MAX_CSS_SIZE) return false;\n const lowerCSS = css.toLowerCase();\n // Block script injection via CSS expressions/behavior/url(javascript:)\n if (lowerCSS.includes(\"expression(\")) return false;\n if (lowerCSS.includes(\"behavior:\")) return false;\n if (lowerCSS.includes(\"javascript:\")) return false;\n if (/<script/i.test(css)) return false;\n // Block </style> which could break out of the style element during SSR\n if (lowerCSS.includes(\"</style\")) return false;\n // Block @import which could load external resources / exfiltrate data\n if (/@import\\b/.test(lowerCSS)) return false;\n return true;\n}\n","/**\n * Single source of truth for profile CSS theming.\n *\n * This file defines:\n * 1. THEME_SELECTORS — the stable set of CSS selectors/variables available for theming\n * 2. DEMO_THEMES — example CSS themes users can apply\n * 3. buildCSSPrompt() — generates an AI prompt describing what can be themed\n *\n * When the profile page structure changes, update THEME_SELECTORS here and\n * everything downstream (AI prompt, docs, validation) stays in sync.\n */\n\n/**\n * A themeable selector or CSS variable group\n */\nexport interface ThemeSelector {\n /** CSS selector or variable name */\n selector: string;\n /** Human-readable description of what it targets */\n description: string;\n /** Category for grouping in documentation */\n category: \"variable\" | \"layout\" | \"component\";\n}\n\n/**\n * All themeable selectors available for profile CSS.\n *\n * CSS variables (category: \"variable\") are the most stable —\n * they survive page restructuring because they're part of the\n * shadcn/Tailwind design system.\n *\n * Layout and component selectors may break when the page structure changes.\n */\nexport const THEME_SELECTORS: ThemeSelector[] = [\n // --- CSS Variables (stable) ---\n {\n selector: \"--background\",\n description: \"Page background color (HSL values, e.g. '210 40% 2%')\",\n category: \"variable\",\n },\n {\n selector: \"--foreground\",\n description: \"Default text color\",\n category: \"variable\",\n },\n {\n selector: \"--primary\",\n description: \"Primary accent color (buttons, links, headings)\",\n category: \"variable\",\n },\n {\n selector: \"--primary-foreground\",\n description: \"Text on primary-colored elements\",\n category: \"variable\",\n },\n {\n selector: \"--secondary\",\n description: \"Secondary accent color\",\n category: \"variable\",\n },\n {\n selector: \"--secondary-foreground\",\n description: \"Text on secondary-colored elements\",\n category: \"variable\",\n },\n {\n selector: \"--muted\",\n description: \"Muted/subdued background\",\n category: \"variable\",\n },\n {\n selector: \"--muted-foreground\",\n description: \"Text on muted backgrounds\",\n category: \"variable\",\n },\n {\n selector: \"--accent\",\n description: \"Accent color for highlights\",\n category: \"variable\",\n },\n {\n selector: \"--accent-foreground\",\n description: \"Text on accent-colored elements\",\n category: \"variable\",\n },\n {\n selector: \"--card\",\n description: \"Card/panel background color\",\n category: \"variable\",\n },\n {\n selector: \"--card-foreground\",\n description: \"Text inside cards\",\n category: \"variable\",\n },\n {\n selector: \"--border\",\n description: \"Border color\",\n category: \"variable\",\n },\n {\n selector: \"--ring\",\n description: \"Focus ring color\",\n category: \"variable\",\n },\n {\n selector: \"--radius\",\n description: \"Border radius (e.g. '0.5rem')\",\n category: \"variable\",\n },\n\n // --- Layout selectors ---\n {\n selector: \".profile-themed\",\n description: \"Root wrapper for all themed profile content\",\n category: \"layout\",\n },\n\n // --- Component selectors ---\n {\n selector: \".profile-header\",\n description:\n \"Profile header card (name, avatar, bio, stat pills). Uses bg-gradient from-gray-900 to-gray-800, border-green-500\",\n category: \"component\",\n },\n {\n selector: \".profile-tabs\",\n description:\n \"Tab navigation bar (Canvas, Posts, Feed, Activity). Uses bg-gray-800, border-gray-700. Active tab uses bg-green-600\",\n category: \"component\",\n },\n {\n selector: \".profile-content\",\n description: \"Main content area below tabs (posts, canvas, feed, activity)\",\n category: \"component\",\n },\n];\n\n/**\n * Demo themes that users can choose from as starting points.\n * Each is a complete CSS string ready to store on-chain.\n *\n * Themes include CSS variable overrides, @keyframes animations,\n * backdrop-filter effects, and full component selector coverage\n * including .profile-content overrides.\n */\nexport const DEMO_THEMES: Record<string, { name: string; css: string }> = {\n checkerboard: {\n name: \"Checkerboard\",\n css: `@keyframes checker-scroll {\n 0% { background-position: 0 0; }\n 100% { background-position: 40px 40px; }\n}\n.profile-themed {\n --primary: 0 0% 100%;\n --primary-foreground: 0 0% 0%;\n --card: 0 0% 5%;\n --card-foreground: 0 0% 95%;\n --border: 0 0% 30%;\n --ring: 0 0% 100%;\n --muted-foreground: 0 0% 60%;\n --radius: 0px;\n color: #e0e0e0;\n background-image: repeating-conic-gradient(#333 0% 25%, #111 0% 50%);\n background-size: 40px 40px;\n animation: checker-scroll 3s linear infinite;\n}\n.profile-themed .profile-header {\n background: rgba(0,0,0,0.4) !important;\n background-color: rgba(0,0,0,0.4) !important;\n background-image: none !important;\n border-color: #fff !important;\n border-width: 2px;\n border-radius: 0 !important;\n backdrop-filter: blur(4px);\n}\n.profile-themed .profile-tabs {\n background: rgba(0,0,0,0.35) !important;\n background-color: rgba(0,0,0,0.35) !important;\n border-color: #555 !important;\n border-radius: 0 !important;\n backdrop-filter: blur(4px);\n}\n.profile-themed .profile-tabs button { color: #888 !important; }\n.profile-themed .profile-tabs button.bg-green-600 {\n background-color: rgba(255,255,255,0.9) !important;\n color: #000 !important;\n border-radius: 0 !important;\n}\n.profile-themed .profile-content .border-green-400 {\n border-color: #555 !important;\n border-radius: 0 !important;\n background: rgba(0,0,0,0.35) !important;\n background-color: rgba(0,0,0,0.35) !important;\n backdrop-filter: blur(4px);\n}\n.profile-themed .profile-content .text-green-400 { color: #fff !important; }\n.profile-themed .profile-content .text-green-300 { color: #ccc !important; }\n.profile-themed .profile-content .text-white { color: #e0e0e0 !important; }\n.profile-themed .profile-content .text-gray-500 { color: #666 !important; }\n.profile-themed .profile-content .text-gray-400 { color: #888 !important; }`,\n },\n neonPulse: {\n name: \"Neon Pulse\",\n css: `@keyframes neon-glow {\n 0%, 100% { border-color: #ff00ff; box-shadow: 0 0 15px #ff00ff44; }\n 33% { border-color: #00ffff; box-shadow: 0 0 15px #00ffff44; }\n 66% { border-color: #ffff00; box-shadow: 0 0 15px #ffff0044; }\n}\n@keyframes hue-rotate {\n 0% { filter: hue-rotate(0deg); }\n 100% { filter: hue-rotate(360deg); }\n}\n.profile-themed {\n --primary: 300 100% 60%;\n --primary-foreground: 0 0% 100%;\n --card: 260 80% 4%;\n --card-foreground: 280 50% 92%;\n --border: 300 100% 40%;\n --ring: 300 100% 60%;\n --muted-foreground: 280 30% 55%;\n color: #e8d0ff;\n}\n.profile-themed .profile-header {\n background: linear-gradient(135deg, #1a0030, #0d001a) !important;\n border-width: 2px !important;\n border-style: solid !important;\n animation: neon-glow 4s ease-in-out infinite;\n}\n.profile-themed .profile-tabs {\n background-color: #0d001a !important;\n border-color: #6600aa !important;\n}\n.profile-themed .profile-tabs button { color: #aa66dd !important; }\n.profile-themed .profile-tabs button.bg-green-600 {\n background: linear-gradient(90deg, #ff00ff, #00ffff) !important;\n color: #000 !important;\n animation: hue-rotate 6s linear infinite;\n}\n.profile-themed .profile-content .border-green-400 {\n border-width: 1px !important;\n border-style: solid !important;\n animation: neon-glow 4s ease-in-out infinite;\n}\n.profile-themed .profile-content .text-green-400 { color: #ff66ff !important; }\n.profile-themed .profile-content .text-green-300 { color: #cc88ff !important; }\n.profile-themed .profile-content .text-white { color: #e8d0ff !important; }\n.profile-themed .profile-content .text-gray-500 { color: #6644aa !important; }\n.profile-themed .profile-content .text-gray-400 { color: #9966cc !important; }`,\n },\n sunset: {\n name: \"Sunset\",\n css: `@keyframes sunset-shift {\n 0%, 100% { background-position: 0% 50%; }\n 50% { background-position: 100% 50%; }\n}\n.profile-themed {\n --primary: 25 100% 55%;\n --primary-foreground: 0 0% 100%;\n --card: 15 60% 6%;\n --card-foreground: 35 80% 90%;\n --border: 20 80% 30%;\n --ring: 25 100% 55%;\n --muted-foreground: 20 40% 50%;\n color: #fde4c8;\n background: linear-gradient(135deg, #1a0a00, #2d0a1e, #0a0a2d, #1a0a00);\n background-size: 400% 400%;\n animation: sunset-shift 15s ease-in-out infinite;\n}\n.profile-themed .profile-header {\n background: linear-gradient(135deg, #3d1200, #2d0a1e, #1a0033) !important;\n border-color: #ff6600 !important;\n border-width: 2px;\n box-shadow: 0 0 30px #ff440022;\n}\n.profile-themed .profile-tabs {\n background-color: #1a0a00dd !important;\n border-color: #663300 !important;\n}\n.profile-themed .profile-tabs button { color: #cc8855 !important; }\n.profile-themed .profile-tabs button.bg-green-600 {\n background: linear-gradient(90deg, #ff4400, #ff8800) !important;\n color: #fff !important;\n}\n.profile-themed .profile-content .border-green-400 {\n border-color: #ff660044 !important;\n box-shadow: 0 0 10px #ff440011;\n}\n.profile-themed .profile-content .text-green-400 { color: #ff8844 !important; }\n.profile-themed .profile-content .text-green-300 { color: #ffaa66 !important; }\n.profile-themed .profile-content .text-white { color: #fde4c8 !important; }\n.profile-themed .profile-content .text-gray-500 { color: #8a6040 !important; }\n.profile-themed .profile-content .text-gray-400 { color: #bb8866 !important; }`,\n },\n psychedelic: {\n name: \"Dreamscape\",\n css: `@keyframes dreamDrift {\n 0% { background-position: 0% 50%; }\n 25% { background-position: 100% 30%; }\n 50% { background-position: 80% 100%; }\n 75% { background-position: 20% 60%; }\n 100% { background-position: 0% 50%; }\n}\n@keyframes floatCircles {\n 0% { transform: translate(0, 0) rotate(0deg); }\n 33% { transform: translate(20px, -30px) rotate(120deg); }\n 66% { transform: translate(-15px, 20px) rotate(240deg); }\n 100% { transform: translate(0, 0) rotate(360deg); }\n}\n@keyframes flowBorder {\n 0% { background-position: 0 0, 0% 0%; }\n 100% { background-position: 0 0, 300% 300%; }\n}\n@keyframes glowPulse {\n 0% { box-shadow: 0 0 15px hsl(270 60% 70% / 0.3), 0 0 30px hsl(270 60% 65% / 0.1); }\n 50% { box-shadow: 0 0 25px hsl(220 60% 70% / 0.4), 0 0 50px hsl(220 50% 65% / 0.15); }\n 100% { box-shadow: 0 0 15px hsl(270 60% 70% / 0.3), 0 0 30px hsl(270 60% 65% / 0.1); }\n}\n@keyframes tabGlow {\n 0% { background-position: 0% 50%; }\n 50% { background-position: 100% 50%; }\n 100% { background-position: 0% 50%; }\n}\n.profile-themed {\n --background: 260 30% 6%;\n --foreground: 250 30% 90%;\n --primary: 270 60% 72%;\n --primary-foreground: 260 30% 10%;\n --secondary: 220 50% 65%;\n --secondary-foreground: 260 30% 10%;\n --muted: 260 20% 15%;\n --muted-foreground: 250 25% 65%;\n --accent: 200 50% 70%;\n --accent-foreground: 260 30% 10%;\n --card: 260 25% 10%;\n --card-foreground: 250 30% 90%;\n --border: 270 40% 50%;\n --ring: 270 60% 72%;\n --radius: 0.75rem;\n color: hsl(250 30% 90%) !important;\n position: relative;\n overflow: hidden;\n background-image: linear-gradient(-45deg, hsl(260 30% 8%), hsl(270 50% 30%), hsl(220 40% 25%), hsl(280 40% 20%), hsl(240 30% 12%)) !important;\n background-size: 400% 400%;\n animation: dreamDrift 20s ease-in-out infinite;\n}\n.profile-themed::after {\n content: \"\";\n position: absolute;\n top: 40px;\n left: 30px;\n width: 80px;\n height: 80px;\n border-radius: 50%;\n background: hsl(270 60% 80% / 0.12);\n box-shadow:\n 200px 100px 0 40px hsl(220 60% 80% / 0.1),\n 50px 300px 0 60px hsl(280 50% 75% / 0.1),\n 320px 400px 0 35px hsl(200 50% 80% / 0.12),\n 150px 550px 0 50px hsl(260 50% 75% / 0.1);\n filter: blur(10px);\n animation: floatCircles 30s ease-in-out infinite;\n z-index: 2;\n pointer-events: none;\n}\n.profile-themed .profile-header {\n background: hsl(260 25% 10% / 0.75) !important;\n background-image: none !important;\n border-color: hsl(270 40% 50% / 0.3) !important;\n backdrop-filter: blur(30px) saturate(140%);\n box-shadow: 0 0 30px hsl(270 50% 60% / 0.15);\n}\n.profile-themed .profile-tabs {\n background-color: hsl(260 25% 12% / 0.8) !important;\n border-color: hsl(270 40% 50% / 0.3) !important;\n backdrop-filter: blur(20px) saturate(140%);\n}\n.profile-themed .profile-tabs button {\n color: hsl(250 25% 65%) !important;\n}\n.profile-themed .profile-tabs button.bg-green-600 {\n background-image: linear-gradient(90deg, hsl(270 60% 65%), hsl(220 50% 65%), hsl(280 50% 70%)) !important;\n background-size: 300% 300%;\n animation: tabGlow 8s ease infinite;\n color: hsl(0 0% 100%) !important;\n}\n.profile-themed .profile-content {\n background: hsl(260 25% 8% / 0.6) !important;\n}\n.profile-themed .profile-content .border-green-400 {\n border: 2px solid transparent !important;\n background-image:\n linear-gradient(hsl(260 25% 12% / 0.85), hsl(260 25% 12% / 0.85)),\n linear-gradient(135deg, hsl(270 60% 65%), hsl(220 50% 65%), hsl(200 50% 70%), hsl(280 50% 70%), hsl(270 60% 65%)) !important;\n background-origin: border-box !important;\n background-clip: padding-box, border-box !important;\n background-size: 100% 100%, 400% 400% !important;\n backdrop-filter: blur(15px) saturate(140%);\n animation: flowBorder 6s linear infinite, glowPulse 4s ease-in-out infinite;\n}\n.profile-themed .profile-content .text-green-400 {\n color: hsl(270 60% 75%) !important;\n text-shadow: 0 0 12px hsl(270 60% 70% / 0.4);\n}\n.profile-themed .profile-content .text-green-300 { color: hsl(220 50% 75%) !important; }\n.profile-themed .profile-content .text-white { color: hsl(250 30% 90%) !important; }\n.profile-themed .profile-content .text-gray-500 { color: hsl(250 20% 50%) !important; }\n.profile-themed .profile-content .text-gray-400 { color: hsl(250 25% 65%) !important; }`,\n },\n};\n\n/**\n * Build an AI prompt that describes the available theming surface.\n * Feed this to an LLM alongside a user's description to generate CSS.\n *\n * The prompt includes per-selector documentation, animation guidance,\n * !important rules for beating Tailwind utilities, and supported\n * properties like backdrop-filter and box-shadow.\n *\n * @returns A prompt string listing all available selectors and usage rules\n */\nexport function buildCSSPrompt(): string {\n const variableLines = THEME_SELECTORS.filter(\n (s) => s.category === \"variable\"\n )\n .map((s) => ` ${s.selector}: ${s.description}`)\n .join(\"\\n\");\n\n return `You are a CSS theme generator for a user profile page.\nAll styles MUST be scoped under .profile-themed.\n\n## CSS Variables (set inside .profile-themed { ... })\nHSL values WITHOUT hsl() wrapper (e.g. \"210 40% 98%\"):\n${variableLines}\n\n## Component Selectors\n .profile-themed — root wrapper; set CSS variables, \\`color\\`, and optionally background/background-image/animation for full-page effects\n .profile-themed .profile-header — header card; override background, background-image: none, border-color\n .profile-themed .profile-tabs — tab bar; override background-color, border-color\n .profile-themed .profile-tabs button — inactive tab text color\n .profile-themed .profile-tabs button.bg-green-600 — active tab background + color\n .profile-themed .profile-content — content area below tabs\n .profile-themed .profile-content .border-green-400 — post card borders + background\n .profile-themed .profile-content .text-green-400 — post links/usernames\n .profile-themed .profile-content .text-green-300 — secondary links\n .profile-themed .profile-content .text-white — post body text\n .profile-themed .profile-content .text-gray-500 — timestamps\n .profile-themed .profile-content .text-gray-400 — secondary text\n\n## Rules\n1. All selectors MUST start with .profile-themed\n2. Use !important on color, background, background-color, background-image, border-color overrides (needed to beat Tailwind utilities)\n3. Set background-image: none !important on .profile-header to clear its default gradient\n4. Set \\`color\\` on .profile-themed for inherited text color\n5. @keyframes animations are encouraged — use for backgrounds, borders, glows\n6. IMPORTANT: Do NOT use \\`background\\` shorthand with !important if you animate background-position — the shorthand locks background-position with !important and animations cannot override it. Use \\`background-image\\` instead.\n7. backdrop-filter, box-shadow, and gradients are supported\n8. Use valid CSS only — no JS, no expressions, no imports\n9. Keep under 10KB\n10. HSL values are bare: \"210 40% 98%\" not \"hsl(210, 40%, 98%)\"\n\nOutput ONLY the CSS.`;\n}\n"]}
|