@net-protocol/profiles 0.1.5 → 0.1.7
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 +15 -1
- package/dist/index.d.mts +17 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +298 -79
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +298 -80
- package/dist/index.mjs.map +1 -1
- package/dist/react.js.map +1 -1
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,8 +12,9 @@ yarn add @net-protocol/profiles
|
|
|
12
12
|
|
|
13
13
|
## Features
|
|
14
14
|
|
|
15
|
-
- **Read profile data**: Profile picture, X username, bio, canvas content
|
|
15
|
+
- **Read profile data**: Profile picture, X username, bio, canvas content, custom CSS themes
|
|
16
16
|
- **Write profile data**: Utilities to prepare Storage.put() transactions
|
|
17
|
+
- **CSS theming**: Demo themes, AI prompt generation, CSS sanitization, and theme selector definitions
|
|
17
18
|
- **Efficient batch reads**: `useBasicUserProfileMetadata` batches multiple reads
|
|
18
19
|
- **Built on net-storage**: Uses the Net Storage SDK for underlying storage operations
|
|
19
20
|
|
|
@@ -106,6 +107,7 @@ function UpdateProfile() {
|
|
|
106
107
|
| Display Name | User-chosen display name | Max 25 characters |
|
|
107
108
|
| Token Address | ERC-20 token that represents you | Valid EVM address (0x-prefixed) |
|
|
108
109
|
| Canvas | Custom HTML profile page | For advanced customization |
|
|
110
|
+
| CSS Theme | Custom CSS for profile styling | Max 10KB, scoped under `.profile-themed` |
|
|
109
111
|
|
|
110
112
|
## Storage Keys
|
|
111
113
|
|
|
@@ -115,6 +117,7 @@ function UpdateProfile() {
|
|
|
115
117
|
| `PROFILE_X_USERNAME_STORAGE_KEY` | X username (legacy, prefer metadata) | Plain string |
|
|
116
118
|
| `PROFILE_METADATA_STORAGE_KEY` | Profile metadata JSON | `{ x_username: "handle", bio: "...", display_name: "...", token_address: "0x..." }` |
|
|
117
119
|
| `PROFILE_CANVAS_STORAGE_KEY` | Custom HTML canvas | HTML string |
|
|
120
|
+
| `PROFILE_CSS_STORAGE_KEY` | Custom CSS theme | CSS string (max 10KB) |
|
|
118
121
|
|
|
119
122
|
## API Reference
|
|
120
123
|
|
|
@@ -123,6 +126,7 @@ function UpdateProfile() {
|
|
|
123
126
|
- `useProfilePicture({ chainId, userAddress })` - Fetch profile picture URL
|
|
124
127
|
- `useProfileXUsername({ chainId, userAddress })` - Fetch X username
|
|
125
128
|
- `useProfileCanvas({ chainId, userAddress })` - Fetch canvas HTML
|
|
129
|
+
- `useProfileCSS({ chainId, userAddress })` - Fetch custom CSS theme
|
|
126
130
|
- `useBasicUserProfileMetadata({ chainId, userAddress })` - Batch fetch picture, username, bio, display name, and token address
|
|
127
131
|
|
|
128
132
|
### Utilities (from `@net-protocol/profiles`)
|
|
@@ -140,6 +144,16 @@ function UpdateProfile() {
|
|
|
140
144
|
- `isValidDisplayName(displayName)` - Validate display name format (max 25 chars, no control chars)
|
|
141
145
|
- `getTokenAddressStorageArgs(tokenAddress)` - Prepare token address update args
|
|
142
146
|
- `isValidTokenAddress(address)` - Validate EVM token address format
|
|
147
|
+
- `getProfileCSSStorageArgs(css)` - Prepare CSS theme update args
|
|
148
|
+
- `isValidCSS(css)` - Validate CSS (size limit, no script injection)
|
|
149
|
+
- `sanitizeCSS(css)` - Strip dangerous patterns (`<script>`, `javascript:`, `expression()`, `behavior:`, `@import`, `</style>`)
|
|
150
|
+
|
|
151
|
+
### Theme Utilities (from `@net-protocol/profiles`)
|
|
152
|
+
|
|
153
|
+
- `THEME_SELECTORS` - Array of all themeable CSS selectors/variables with descriptions
|
|
154
|
+
- `DEMO_THEMES` - Built-in demo themes (use `buildCSSPrompt()` or CLI `--list-themes` to discover names)
|
|
155
|
+
- `buildCSSPrompt()` - Generate an AI prompt describing the full theming surface
|
|
156
|
+
- `MAX_CSS_SIZE` - Maximum CSS size in bytes (10KB)
|
|
143
157
|
|
|
144
158
|
## Dependencies
|
|
145
159
|
|
package/dist/index.d.mts
CHANGED
|
@@ -178,6 +178,14 @@ declare const MAX_CSS_SIZE: number;
|
|
|
178
178
|
* ```
|
|
179
179
|
*/
|
|
180
180
|
declare function getProfileCSSStorageArgs(cssContent: string): ProfileStorageArgs;
|
|
181
|
+
/**
|
|
182
|
+
* Sanitize user CSS to prevent injection attacks.
|
|
183
|
+
* - Strips </style> (which could break out of the style element during SSR)
|
|
184
|
+
* - Strips <script> tags
|
|
185
|
+
* - Removes javascript: URIs, expression(), behavior: (legacy IE vectors)
|
|
186
|
+
* - Removes @import rules (could load external resources / exfiltrate data)
|
|
187
|
+
*/
|
|
188
|
+
declare function sanitizeCSS(css: string): string;
|
|
181
189
|
/**
|
|
182
190
|
* Validate CSS content
|
|
183
191
|
* Returns true if valid (non-empty, within size limit, no script injection)
|
|
@@ -219,6 +227,10 @@ declare const THEME_SELECTORS: ThemeSelector[];
|
|
|
219
227
|
/**
|
|
220
228
|
* Demo themes that users can choose from as starting points.
|
|
221
229
|
* Each is a complete CSS string ready to store on-chain.
|
|
230
|
+
*
|
|
231
|
+
* Themes include CSS variable overrides, @keyframes animations,
|
|
232
|
+
* backdrop-filter effects, and full component selector coverage
|
|
233
|
+
* including .profile-content overrides.
|
|
222
234
|
*/
|
|
223
235
|
declare const DEMO_THEMES: Record<string, {
|
|
224
236
|
name: string;
|
|
@@ -228,8 +240,12 @@ declare const DEMO_THEMES: Record<string, {
|
|
|
228
240
|
* Build an AI prompt that describes the available theming surface.
|
|
229
241
|
* Feed this to an LLM alongside a user's description to generate CSS.
|
|
230
242
|
*
|
|
243
|
+
* The prompt includes per-selector documentation, animation guidance,
|
|
244
|
+
* !important rules for beating Tailwind utilities, and supported
|
|
245
|
+
* properties like backdrop-filter and box-shadow.
|
|
246
|
+
*
|
|
231
247
|
* @returns A prompt string listing all available selectors and usage rules
|
|
232
248
|
*/
|
|
233
249
|
declare function buildCSSPrompt(): string;
|
|
234
250
|
|
|
235
|
-
export { DEMO_THEMES, MAX_CSS_SIZE, PROFILE_CANVAS_STORAGE_KEY, PROFILE_CANVAS_TOPIC, PROFILE_CSS_STORAGE_KEY, PROFILE_CSS_TOPIC, PROFILE_METADATA_STORAGE_KEY, PROFILE_METADATA_TOPIC, PROFILE_PICTURE_STORAGE_KEY, PROFILE_PICTURE_TOPIC, PROFILE_X_USERNAME_STORAGE_KEY, ProfileMetadata, ProfileStorageArgs, THEME_SELECTORS, type ThemeSelector, buildCSSPrompt, getBioStorageArgs, getBytesArgsForStorage, getDisplayNameStorageArgs, getProfileCSSStorageArgs, getProfileCanvasStorageArgs, getProfileMetadataStorageArgs, getProfilePictureStorageArgs, getTokenAddressStorageArgs, getValueArgForStorage, getXUsernameStorageArgs, isValidBio, isValidCSS, isValidDisplayName, isValidTokenAddress, isValidUrl, isValidXUsername, parseProfileMetadata };
|
|
251
|
+
export { DEMO_THEMES, MAX_CSS_SIZE, PROFILE_CANVAS_STORAGE_KEY, PROFILE_CANVAS_TOPIC, PROFILE_CSS_STORAGE_KEY, PROFILE_CSS_TOPIC, PROFILE_METADATA_STORAGE_KEY, PROFILE_METADATA_TOPIC, PROFILE_PICTURE_STORAGE_KEY, PROFILE_PICTURE_TOPIC, PROFILE_X_USERNAME_STORAGE_KEY, ProfileMetadata, ProfileStorageArgs, THEME_SELECTORS, type ThemeSelector, buildCSSPrompt, getBioStorageArgs, getBytesArgsForStorage, getDisplayNameStorageArgs, getProfileCSSStorageArgs, getProfileCanvasStorageArgs, getProfileMetadataStorageArgs, getProfilePictureStorageArgs, getTokenAddressStorageArgs, getValueArgForStorage, getXUsernameStorageArgs, isValidBio, isValidCSS, isValidDisplayName, isValidTokenAddress, isValidUrl, isValidXUsername, parseProfileMetadata, sanitizeCSS };
|
package/dist/index.d.ts
CHANGED
|
@@ -178,6 +178,14 @@ declare const MAX_CSS_SIZE: number;
|
|
|
178
178
|
* ```
|
|
179
179
|
*/
|
|
180
180
|
declare function getProfileCSSStorageArgs(cssContent: string): ProfileStorageArgs;
|
|
181
|
+
/**
|
|
182
|
+
* Sanitize user CSS to prevent injection attacks.
|
|
183
|
+
* - Strips </style> (which could break out of the style element during SSR)
|
|
184
|
+
* - Strips <script> tags
|
|
185
|
+
* - Removes javascript: URIs, expression(), behavior: (legacy IE vectors)
|
|
186
|
+
* - Removes @import rules (could load external resources / exfiltrate data)
|
|
187
|
+
*/
|
|
188
|
+
declare function sanitizeCSS(css: string): string;
|
|
181
189
|
/**
|
|
182
190
|
* Validate CSS content
|
|
183
191
|
* Returns true if valid (non-empty, within size limit, no script injection)
|
|
@@ -219,6 +227,10 @@ declare const THEME_SELECTORS: ThemeSelector[];
|
|
|
219
227
|
/**
|
|
220
228
|
* Demo themes that users can choose from as starting points.
|
|
221
229
|
* Each is a complete CSS string ready to store on-chain.
|
|
230
|
+
*
|
|
231
|
+
* Themes include CSS variable overrides, @keyframes animations,
|
|
232
|
+
* backdrop-filter effects, and full component selector coverage
|
|
233
|
+
* including .profile-content overrides.
|
|
222
234
|
*/
|
|
223
235
|
declare const DEMO_THEMES: Record<string, {
|
|
224
236
|
name: string;
|
|
@@ -228,8 +240,12 @@ declare const DEMO_THEMES: Record<string, {
|
|
|
228
240
|
* Build an AI prompt that describes the available theming surface.
|
|
229
241
|
* Feed this to an LLM alongside a user's description to generate CSS.
|
|
230
242
|
*
|
|
243
|
+
* The prompt includes per-selector documentation, animation guidance,
|
|
244
|
+
* !important rules for beating Tailwind utilities, and supported
|
|
245
|
+
* properties like backdrop-filter and box-shadow.
|
|
246
|
+
*
|
|
231
247
|
* @returns A prompt string listing all available selectors and usage rules
|
|
232
248
|
*/
|
|
233
249
|
declare function buildCSSPrompt(): string;
|
|
234
250
|
|
|
235
|
-
export { DEMO_THEMES, MAX_CSS_SIZE, PROFILE_CANVAS_STORAGE_KEY, PROFILE_CANVAS_TOPIC, PROFILE_CSS_STORAGE_KEY, PROFILE_CSS_TOPIC, PROFILE_METADATA_STORAGE_KEY, PROFILE_METADATA_TOPIC, PROFILE_PICTURE_STORAGE_KEY, PROFILE_PICTURE_TOPIC, PROFILE_X_USERNAME_STORAGE_KEY, ProfileMetadata, ProfileStorageArgs, THEME_SELECTORS, type ThemeSelector, buildCSSPrompt, getBioStorageArgs, getBytesArgsForStorage, getDisplayNameStorageArgs, getProfileCSSStorageArgs, getProfileCanvasStorageArgs, getProfileMetadataStorageArgs, getProfilePictureStorageArgs, getTokenAddressStorageArgs, getValueArgForStorage, getXUsernameStorageArgs, isValidBio, isValidCSS, isValidDisplayName, isValidTokenAddress, isValidUrl, isValidXUsername, parseProfileMetadata };
|
|
251
|
+
export { DEMO_THEMES, MAX_CSS_SIZE, PROFILE_CANVAS_STORAGE_KEY, PROFILE_CANVAS_TOPIC, PROFILE_CSS_STORAGE_KEY, PROFILE_CSS_TOPIC, PROFILE_METADATA_STORAGE_KEY, PROFILE_METADATA_TOPIC, PROFILE_PICTURE_STORAGE_KEY, PROFILE_PICTURE_TOPIC, PROFILE_X_USERNAME_STORAGE_KEY, ProfileMetadata, ProfileStorageArgs, THEME_SELECTORS, type ThemeSelector, buildCSSPrompt, getBioStorageArgs, getBytesArgsForStorage, getDisplayNameStorageArgs, getProfileCSSStorageArgs, getProfileCanvasStorageArgs, getProfileMetadataStorageArgs, getProfilePictureStorageArgs, getTokenAddressStorageArgs, getValueArgForStorage, getXUsernameStorageArgs, isValidBio, isValidCSS, isValidDisplayName, isValidTokenAddress, isValidUrl, isValidXUsername, parseProfileMetadata, sanitizeCSS };
|
package/dist/index.js
CHANGED
|
@@ -131,6 +131,9 @@ function getProfileCSSStorageArgs(cssContent) {
|
|
|
131
131
|
bytesValue
|
|
132
132
|
};
|
|
133
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
|
+
}
|
|
134
137
|
function isValidCSS(css) {
|
|
135
138
|
if (!css || css.trim().length === 0) return false;
|
|
136
139
|
if (Buffer.byteLength(css, "utf-8") > MAX_CSS_SIZE) return false;
|
|
@@ -139,6 +142,8 @@ function isValidCSS(css) {
|
|
|
139
142
|
if (lowerCSS.includes("behavior:")) return false;
|
|
140
143
|
if (lowerCSS.includes("javascript:")) return false;
|
|
141
144
|
if (/<script/i.test(css)) return false;
|
|
145
|
+
if (lowerCSS.includes("</style")) return false;
|
|
146
|
+
if (/@import\b/.test(lowerCSS)) return false;
|
|
142
147
|
return true;
|
|
143
148
|
}
|
|
144
149
|
|
|
@@ -220,118 +225,331 @@ var THEME_SELECTORS = [
|
|
|
220
225
|
description: "Border radius (e.g. '0.5rem')",
|
|
221
226
|
category: "variable"
|
|
222
227
|
},
|
|
223
|
-
// --- Layout selectors
|
|
228
|
+
// --- Layout selectors ---
|
|
224
229
|
{
|
|
225
230
|
selector: ".profile-themed",
|
|
226
231
|
description: "Root wrapper for all themed profile content",
|
|
227
232
|
category: "layout"
|
|
228
233
|
},
|
|
229
|
-
// --- Component selectors
|
|
234
|
+
// --- Component selectors ---
|
|
230
235
|
{
|
|
231
|
-
selector: ".profile-
|
|
232
|
-
description: "Profile header
|
|
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",
|
|
233
238
|
category: "component"
|
|
234
239
|
},
|
|
235
240
|
{
|
|
236
|
-
selector: ".profile-
|
|
237
|
-
description: "Tab navigation bar",
|
|
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",
|
|
238
243
|
category: "component"
|
|
239
244
|
},
|
|
240
245
|
{
|
|
241
|
-
selector: ".profile-
|
|
242
|
-
description: "Main content area below tabs",
|
|
246
|
+
selector: ".profile-content",
|
|
247
|
+
description: "Main content area below tabs (posts, canvas, feed, activity)",
|
|
243
248
|
category: "component"
|
|
244
249
|
}
|
|
245
250
|
];
|
|
246
251
|
var DEMO_THEMES = {
|
|
247
|
-
|
|
248
|
-
name: "
|
|
249
|
-
css:
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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%;
|
|
253
320
|
--primary-foreground: 0 0% 100%;
|
|
254
|
-
--
|
|
255
|
-
--
|
|
256
|
-
--
|
|
257
|
-
--
|
|
258
|
-
--
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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; }`
|
|
265
354
|
},
|
|
266
|
-
|
|
267
|
-
name: "
|
|
268
|
-
css:
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
--
|
|
274
|
-
--
|
|
275
|
-
--
|
|
276
|
-
--
|
|
277
|
-
--
|
|
278
|
-
--
|
|
279
|
-
--
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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; }`
|
|
284
398
|
},
|
|
285
|
-
|
|
286
|
-
name: "
|
|
287
|
-
css:
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
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; }`
|
|
303
513
|
}
|
|
304
514
|
};
|
|
305
515
|
function buildCSSPrompt() {
|
|
306
516
|
const variableLines = THEME_SELECTORS.filter(
|
|
307
517
|
(s) => s.category === "variable"
|
|
308
518
|
).map((s) => ` ${s.selector}: ${s.description}`).join("\n");
|
|
309
|
-
const layoutLines = THEME_SELECTORS.filter((s) => s.category === "layout").map((s) => ` ${s.selector} \u2014 ${s.description}`).join("\n");
|
|
310
|
-
const componentLines = THEME_SELECTORS.filter(
|
|
311
|
-
(s) => s.category === "component"
|
|
312
|
-
).map((s) => ` ${s.selector} \u2014 ${s.description}`).join("\n");
|
|
313
519
|
return `You are a CSS theme generator for a user profile page.
|
|
314
|
-
All styles MUST be scoped under
|
|
520
|
+
All styles MUST be scoped under .profile-themed.
|
|
315
521
|
|
|
316
|
-
## CSS Variables (
|
|
317
|
-
|
|
522
|
+
## CSS Variables (set inside .profile-themed { ... })
|
|
523
|
+
HSL values WITHOUT hsl() wrapper (e.g. "210 40% 98%"):
|
|
318
524
|
${variableLines}
|
|
319
525
|
|
|
320
|
-
##
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
|
325
539
|
|
|
326
540
|
## Rules
|
|
327
541
|
1. All selectors MUST start with .profile-themed
|
|
328
|
-
2.
|
|
329
|
-
3.
|
|
330
|
-
4.
|
|
331
|
-
5.
|
|
332
|
-
6.
|
|
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%)"
|
|
333
551
|
|
|
334
|
-
|
|
552
|
+
Output ONLY the CSS.`;
|
|
335
553
|
}
|
|
336
554
|
|
|
337
555
|
Object.defineProperty(exports, "STORAGE_CONTRACT", {
|
|
@@ -368,5 +586,6 @@ exports.isValidTokenAddress = isValidTokenAddress;
|
|
|
368
586
|
exports.isValidUrl = isValidUrl;
|
|
369
587
|
exports.isValidXUsername = isValidXUsername;
|
|
370
588
|
exports.parseProfileMetadata = parseProfileMetadata;
|
|
589
|
+
exports.sanitizeCSS = sanitizeCSS;
|
|
371
590
|
//# sourceMappingURL=index.js.map
|
|
372
591
|
//# sourceMappingURL=index.js.map
|