mnfst 0.5.192 → 0.5.194

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,9 +23,18 @@ Get [CDN links](https://manifestx.dev/getting-started/setup) for existing projec
23
23
  **Alpine Plugins:**
24
24
  - Color Themes
25
25
  - Components
26
- - Data Sources
26
+ - Data Sources (local, API & Appwrite)
27
+ - Authentication & Teams (Appwrite)
27
28
  - Localization
28
29
  - SPA Routing
30
+ - Payments
31
+ - Chat
32
+ - Charts
33
+ - Presence & Status
34
+ - Virtual Scrolling
35
+ - Datepicker, Colorpicker & Combobox
36
+ - Markdown, Code & Icons
37
+ - Native App Support (Capacitor)
29
38
 
30
39
  **UX/UI:**
31
40
  - Global Theme
@@ -38,7 +47,14 @@ Get [CDN links](https://manifestx.dev/getting-started/setup) for existing projec
38
47
 
39
48
  ## 📚 Documentation
40
49
 
41
- For full documentation visit [manifestx.dev](https://manifestx.dev).
50
+ For full documentation visit [manifestx.dev](https://manifestx.dev) — every article is searchable with ⌘K.
51
+
52
+ <br>
53
+
54
+ ## 🛠 Development
55
+
56
+ - `npm run start:src` — dev server for the `src/` test project
57
+ - `npm run build` — regenerate `lib/` from `src/` and re-sync `packages/*`
42
58
 
43
59
  <br>
44
60
 
@@ -51,9 +67,11 @@ Each package publishes independently to npm. The `release:*` scripts auto-bump t
51
67
  | `npm run release` | `mnfst` | (root) |
52
68
  | `npm run release:run` | `mnfst-run` | `packages/run/` |
53
69
  | `npm run release:render` | `mnfst-render` | `packages/render/` |
70
+ | `npm run release:publish` | `mnfst-publish` | `packages/publish/` |
71
+ | `npm run release:types` | `mnfst-types` | `packages/types/` |
54
72
  | `npm run release:starter` | `mnfst-starter` | `packages/create-starter/` |
55
73
 
56
- Run only the scripts whose package you actually changed.
74
+ Run only the scripts whose package you actually changed (`npm run release:all` publishes everything).
57
75
 
58
76
  For a **minor** or **major** bump, run `npm version minor` (or `major`) inside the relevant package directory before publishing:
59
77
  ```sh
@@ -58,11 +58,8 @@
58
58
  width: 1rem;
59
59
  height: 1rem;
60
60
  background-color: color-mix(in oklch, var(--color-field-surface, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent)) 50%, var(--color-field-inverse, oklch(43.9% 0 0)));
61
- -webkit-mask-image: var(--icon-accordion, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 256 256'%3E%3Cpath fill='%23000' d='m184.49 136.49l-80 80a12 12 0 0 1-17-17L159 128L87.51 56.49a12 12 0 1 1 17-17l80 80a12 12 0 0 1-.02 17'/%3E%3C/svg%3E"));
62
61
  mask-image: var(--icon-accordion, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 256 256'%3E%3Cpath fill='%23000' d='m184.49 136.49l-80 80a12 12 0 0 1-17-17L159 128L87.51 56.49a12 12 0 1 1 17-17l80 80a12 12 0 0 1-.02 17'/%3E%3C/svg%3E"));
63
- -webkit-mask-repeat: no-repeat;
64
62
  mask-repeat: no-repeat;
65
- -webkit-mask-size: 100% 100%;
66
63
  mask-size: 100% 100%;
67
64
  transform: rotate(0);
68
65
  transition: 0.25s transform ease
@@ -1147,8 +1147,7 @@ function initializeAuthMagic() {
1147
1147
  if (typeof value === 'function') {
1148
1148
  return value.bind(store);
1149
1149
  }
1150
- // CRITICAL: If property exists but is not a function, check if it should be a convenience method
1151
- // This handles cases where the store was recreated and methods are missing
1150
+ // Non-function value that should be a convenience method → store was recreated
1152
1151
  if (typeof prop === 'string') {
1153
1152
  const convenienceMethodNames = [
1154
1153
  'isCreatingTeam', 'isUpdatingTeam', 'isDeletingTeam', 'isInvitingMember',
@@ -1163,12 +1162,10 @@ function initializeAuthMagic() {
1163
1162
  ];
1164
1163
 
1165
1164
  if (convenienceMethodNames.includes(prop)) {
1166
- // This should be a function but isn't - try to reinitialize synchronously
1165
+ // Reinitialize synchronously, then re-check
1167
1166
  if (window.ManifestAppwriteAuthTeamsConvenience && window.ManifestAppwriteAuthTeamsConvenience.initialize) {
1168
1167
  try {
1169
- // Call initialize which will check and re-add methods if needed
1170
1168
  window.ManifestAppwriteAuthTeamsConvenience.initialize();
1171
- // Immediately check again - initialize should have added the method
1172
1169
  const reinitializedValue = store[prop];
1173
1170
  if (typeof reinitializedValue === 'function') {
1174
1171
  return reinitializedValue.bind(store);
@@ -1177,8 +1174,7 @@ function initializeAuthMagic() {
1177
1174
  // Failed to reinitialize, continue to fallback
1178
1175
  }
1179
1176
  }
1180
- // Return a safe fallback function that returns false/empty
1181
- // This prevents "is not a function" errors while methods are being reinitialized
1177
+ // Safe fallbacks while methods reinitialize
1182
1178
  if (prop.startsWith('is') || prop.startsWith('can') || prop.startsWith('has')) {
1183
1179
  return () => false;
1184
1180
  }
@@ -1191,8 +1187,7 @@ function initializeAuthMagic() {
1191
1187
  return () => ({ success: false, error: 'Method not initialized' });
1192
1188
  }
1193
1189
  }
1194
- // CRITICAL: Handle null values - return loading proxy to allow safe chaining
1195
- // This prevents errors when accessing $auth.user.email when user is null
1190
+ // Null/undefined: loading proxy keeps $auth.user.email chains safe
1196
1191
  if (value === null || value === undefined) {
1197
1192
  return createAuthLoadingProxy();
1198
1193
  }
@@ -1232,9 +1227,7 @@ function initializeAuthMagic() {
1232
1227
  return value;
1233
1228
  }
1234
1229
 
1235
- // CRITICAL: If property doesn't exist, check if convenience methods need reinitialization
1236
- // This prevents "$auth.isCreatingTeam is not a function" errors after idle/reinitialization
1237
- // Only check for known convenience method names to avoid unnecessary work
1230
+ // Missing property: reinitialize known convenience methods (guards post-idle errors)
1238
1231
  const convenienceMethodNames = [
1239
1232
  'isCreatingTeam', 'isUpdatingTeam', 'isDeletingTeam', 'isInvitingMember',
1240
1233
  'isUpdatingMember', 'isDeletingMember', 'createTeamFromName', 'updateCurrentTeamName',
@@ -3141,8 +3134,7 @@ function validateRoleConfig(memberRoles, creatorRole) {
3141
3134
  const errors = [];
3142
3135
  const warnings = [];
3143
3136
 
3144
- // If teams not enabled, roles are ignored (graceful degradation)
3145
- // This validation assumes teams are enabled
3137
+ // Assumes teams enabled; roles are ignored otherwise
3146
3138
 
3147
3139
  // Validate memberRoles structure
3148
3140
  if (memberRoles && typeof memberRoles !== 'object') {
@@ -3167,8 +3159,7 @@ function validateRoleConfig(memberRoles, creatorRole) {
3167
3159
  if (typeof permission !== 'string') {
3168
3160
  errors.push(`Role "${roleName}" has invalid permission type. Permissions must be strings.`);
3169
3161
  } else if (!OWNER_PERMISSIONS.includes(permission)) {
3170
- // Custom permission - this is allowed, just log for info
3171
- // No error, as custom permissions are valid
3162
+ // Custom permissions are valid; no error
3172
3163
  }
3173
3164
  }
3174
3165
  }
@@ -3223,8 +3214,7 @@ async function getUserGeneratedRoles(teamId, appwrite) {
3223
3214
  const prefs = await appwrite.teams.getPrefs({ teamId });
3224
3215
  return prefs?.roles || null;
3225
3216
  } catch (error) {
3226
- // Team preferences might not have roles yet, or team might be deleted (404)
3227
- // Silently return null for deleted teams (expected behavior)
3217
+ // Prefs may lack roles, or team deleted (404) → null
3228
3218
  if (error.message && error.message.includes('could not be found')) {
3229
3219
  return null;
3230
3220
  }
@@ -3347,20 +3337,12 @@ function hasPermission(userRoles, permission, memberRoles, userGeneratedRoles =
3347
3337
  return userRoles.includes('owner');
3348
3338
  }
3349
3339
 
3350
- // IMPORTANT: When custom roles are defined, we ONLY check custom roles, NOT the owner role.
3351
- // This is because Appwrite automatically grants "owner" role to users with custom roles
3352
- // that have native permissions, but we want to restrict them to ONLY the permissions
3353
- // explicitly defined in their custom role(s).
3354
-
3355
- // Get user's custom roles (excluding "owner")
3340
+ // With custom roles defined, check ONLY custom roles — Appwrite auto-grants "owner"
3341
+ // alongside them, but permissions must stay limited to the custom role(s).
3356
3342
  const customRoles = userRoles.filter(role => role !== 'owner');
3357
3343
 
3358
- // If user has no custom roles (only "owner" or empty), grant all permissions
3359
- // This handles edge cases where:
3360
- // - User's role was deleted
3361
- // - User was never assigned a custom role
3344
+ // No custom roles (deleted or never assigned) → full owner permissions
3362
3345
  if (customRoles.length === 0) {
3363
- // User has no custom roles, so they should have all owner permissions
3364
3346
  return true;
3365
3347
  }
3366
3348
 
@@ -6582,9 +6564,7 @@ function initializeOAuth() {
6582
6564
  const waitForStore = () => {
6583
6565
  const store = Alpine.store('auth');
6584
6566
  if (store && !store.loginOAuth) {
6585
- // Add OAuth method to store
6586
- // Note: Appwrite accepts any provider string (google, github, etc.) and validates on their side
6587
- // No need to maintain a registry of supported providers
6567
+ // Appwrite validates provider strings server-side; no registry needed here
6588
6568
  store.loginOAuth = async function (provider, successUrl = window.location.href, failureUrl = window.location.href) {
6589
6569
  if (!this._appwrite) {
6590
6570
  this._appwrite = await config.getAppwriteClient();
@@ -6604,11 +6584,8 @@ function initializeOAuth() {
6604
6584
  const cleanSuccessUrl = `${currentUrl.origin}${currentUrl.pathname}`;
6605
6585
  const cleanFailureUrl = `${currentUrl.origin}${currentUrl.pathname}`;
6606
6586
 
6607
- // Delete any existing anonymous sessions before OAuth
6608
- // This prevents conflicts where anonymous sessions might interfere with OAuth
6609
- // Appwrite will create a new account for OAuth if needed.
6610
- // EXCEPTION: when guest upgrade is enabled we keep the anonymous session
6611
- // active so Appwrite can link the OAuth identity to it (preserving teams).
6587
+ // Drop anonymous sessions before OAuth — except with guestUpgrade, where the
6588
+ // session stays so Appwrite links the OAuth identity to it (preserving teams)
6612
6589
  if (this.isAnonymous && this.session && !appwriteConfig?.guestUpgrade) {
6613
6590
  try {
6614
6591
  await this._appwrite.account.deleteSession(this.session.$id);
@@ -6641,9 +6618,8 @@ function initializeOAuth() {
6641
6618
  this.error = null;
6642
6619
 
6643
6620
  try {
6644
- // Use createOAuth2Token (like the working implementation)
6645
- // This returns a token/redirect URL that we manually navigate to
6646
- // After OAuth, Appwrite redirects back with userId and secret in URL params
6621
+ // createOAuth2Token returns a URL we navigate to; Appwrite redirects
6622
+ // back with userId + secret in URL params
6647
6623
  const token = await this._appwrite.account.createOAuth2Token(
6648
6624
  provider,
6649
6625
  cleanSuccessUrl,
@@ -6651,8 +6627,7 @@ function initializeOAuth() {
6651
6627
  ['email'] // Scopes
6652
6628
  );
6653
6629
 
6654
- // Check for redirectUrl - Appwrite may return it in various formats
6655
- // Try multiple property names and formats
6630
+ // Appwrite may return the redirect URL in several formats
6656
6631
  let redirectUrl = null;
6657
6632
 
6658
6633
  if (typeof token === 'string') {
@@ -6669,30 +6644,23 @@ function initializeOAuth() {
6669
6644
  }
6670
6645
  }
6671
6646
 
6672
- // Clear error state before redirect (whether we found URL or not)
6673
- // This prevents any error flash before redirect
6647
+ // Clear error before redirect to avoid an error flash
6674
6648
  this.error = null;
6675
6649
 
6676
6650
  if (redirectUrl) {
6677
- // Use requestAnimationFrame to ensure Alpine processes the error clearing
6678
- // before redirect happens, preventing error flash
6651
+ // rAF lets Alpine process the error clear before navigating
6679
6652
  requestAnimationFrame(() => {
6680
6653
  window.location.href = redirectUrl;
6681
6654
  });
6682
- // Return immediately - redirect will happen asynchronously
6683
6655
  return { success: true, redirectUrl: redirectUrl };
6684
6656
  } else {
6685
- // If we can't find redirect URL, log it but don't show error to user
6686
- // The redirect might still work via Appwrite's internal handling
6657
+ // No extractable URL: warn but stay silent — Appwrite's own redirect may still fire
6687
6658
  console.warn('[Manifest Appwrite Auth] Could not extract redirect URL from token:', token);
6688
- // Don't set error - just return failure silently
6689
- // This prevents error flash when redirect might still succeed
6690
6659
  this.inProgress = false;
6691
6660
  return { success: false, error: 'Could not extract redirect URL' };
6692
6661
  }
6693
6662
  } catch (error) {
6694
- // Don't show "No redirect URL" errors - they're usually false positives
6695
- // Only show other meaningful errors
6663
+ // "No redirect URL" errors are usually false positives; surface the rest
6696
6664
  if (!error.message.includes('No redirect URL') && !error.message.includes('redirect')) {
6697
6665
  this.error = error.message;
6698
6666
  this.inProgress = false;
@@ -6738,8 +6706,7 @@ function handleOAuthCallbacks() {
6738
6706
  }
6739
6707
  if (storedProvider) {
6740
6708
  store._oauthProvider = storedProvider;
6741
- // Keep it in localStorage (cleared on logout)
6742
- // This allows us to show the correct provider name even after page refresh
6709
+ // Stays in localStorage until logout so the provider name survives refresh
6743
6710
  } else {
6744
6711
  console.warn('[Manifest Appwrite Auth] No OAuth provider found in storage');
6745
6712
  }
@@ -1220,13 +1220,8 @@ window.ManifestDataQueries = {
1220
1220
 
1221
1221
  /* Manifest Data Sources - Pagination (Appwrite) */
1222
1222
 
1223
- /**
1224
- * Get first page of results (cursor-based)
1225
- * @param {string} dataSourceName - Name of the data source
1226
- * @param {number} limit - Number of items per page
1227
- * @param {Array} baseQueries - Base queries to apply (from manifest or scope)
1228
- * @returns {Promise<{items: Array, cursor: string|null, total: number, hasMore: boolean}>}
1229
- */
1223
+ // First page, cursor-based
1224
+ // Returns { items, cursor, total, hasMore }
1230
1225
  async function getFirstPage(dataSourceName, limit, baseQueries = []) {
1231
1226
  const manifest = await window.ManifestDataConfig.ensureManifest();
1232
1227
  if (!manifest?.data) {
@@ -1307,14 +1302,8 @@ async function getFirstPage(dataSourceName, limit, baseQueries = []) {
1307
1302
  }
1308
1303
  }
1309
1304
 
1310
- /**
1311
- * Get next page of results (cursor-based)
1312
- * @param {string} dataSourceName - Name of the data source
1313
- * @param {string} cursor - Cursor from previous page
1314
- * @param {number} limit - Number of items per page
1315
- * @param {Array} baseQueries - Base queries to apply
1316
- * @returns {Promise<{items: Array, cursor: string|null, total: number, hasMore: boolean}>}
1317
- */
1305
+ // Next page from cursor
1306
+ // Returns { items, cursor, total, hasMore }
1318
1307
  async function getNextPage(dataSourceName, cursor, limit, baseQueries = []) {
1319
1308
  if (!cursor) {
1320
1309
  throw new Error('[Manifest Data] Cursor is required for next page');
@@ -1392,14 +1381,8 @@ async function getNextPage(dataSourceName, cursor, limit, baseQueries = []) {
1392
1381
  }
1393
1382
  }
1394
1383
 
1395
- /**
1396
- * Get previous page of results (cursor-based)
1397
- * @param {string} dataSourceName - Name of the data source
1398
- * @param {string} cursor - Cursor from current page
1399
- * @param {number} limit - Number of items per page
1400
- * @param {Array} baseQueries - Base queries to apply
1401
- * @returns {Promise<{items: Array, cursor: string|null, total: number, hasMore: boolean}>}
1402
- */
1384
+ // Previous page from cursor
1385
+ // Returns { items, cursor, total, hasMore }
1403
1386
  async function getPrevPage(dataSourceName, cursor, limit, baseQueries = []) {
1404
1387
  if (!cursor) {
1405
1388
  throw new Error('[Manifest Data] Cursor is required for previous page');
@@ -1477,14 +1460,8 @@ async function getPrevPage(dataSourceName, cursor, limit, baseQueries = []) {
1477
1460
  }
1478
1461
  }
1479
1462
 
1480
- /**
1481
- * Get specific page (offset-based)
1482
- * @param {string} dataSourceName - Name of the data source
1483
- * @param {number} pageNumber - Page number (1-based)
1484
- * @param {number} limit - Number of items per page
1485
- * @param {Array} baseQueries - Base queries to apply
1486
- * @returns {Promise<{items: Array, page: number, total: number, totalPages: number, hasMore: boolean}>}
1487
- */
1463
+ // Specific page (1-based), offset-based
1464
+ // Returns { items, page, total, totalPages, hasMore }
1488
1465
  async function getPage(dataSourceName, pageNumber, limit, baseQueries = []) {
1489
1466
  if (pageNumber < 1) {
1490
1467
  throw new Error('[Manifest Data] Page number must be >= 1');
@@ -71,11 +71,13 @@
71
71
  appearance: base-select;
72
72
 
73
73
  &::picker-icon {
74
- content: "⌄";
75
- height: calc(var(--spacing-field-height, 2.25rem) * 0.5);
76
- transform: scaleY(0.7);
77
- font-size: 20px;
78
- line-height: 0.4
74
+ content: "";
75
+ width: 1rem;
76
+ height: 1rem;
77
+ background-color: currentColor;
78
+ mask-repeat: no-repeat;
79
+ mask-size: 100% 100%;
80
+ mask-image: var(--icon-chevron-down, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m6 9 6 6 6-6'/%3E%3C/svg%3E"))
79
81
  }
80
82
  }
81
83
  }
@@ -184,9 +184,9 @@
184
184
 
185
185
  /* On a filled segment (bar/slice center) — read against the fill */
186
186
  &.inverse {
187
- fill: black;
187
+ fill: var(--color-content-stark, oklch(20.5% 0 0));
188
188
  paint-order: stroke;
189
- stroke: rgba(255, 255, 255, 0.75);
189
+ stroke: color-mix(in oklch, var(--color-page, oklch(98.5% 0 0)) 75%, transparent);
190
190
  stroke-width: 1.5px
191
191
  }
192
192
  }
@@ -4,6 +4,8 @@
4
4
 
5
5
  @layer components {
6
6
 
7
+ /* Deliberately unflattened: manifest.input.css supplies the field chrome via
8
+ a lower-specificity rule that sorts later, so this must outrank it. */
7
9
  input[type=checkbox]:not([role=switch], .unstyle) {
8
10
  position: relative;
9
11
  min-width: calc(var(--spacing-field-height, 2.25rem) * 0.6);
@@ -37,11 +39,8 @@
37
39
  width: 60%;
38
40
  height: 60%;
39
41
  background-color: var(--color-field-inverse, oklch(43.9% 0 0));
40
- -webkit-mask-image: var(--icon-check, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='currentColor' d='m0 11l2-2l5 5L18 3l2 2L7 18z'/%3E%3C/svg%3E"));
41
42
  mask-image: var(--icon-check, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='currentColor' d='m0 11l2-2l5 5L18 3l2 2L7 18z'/%3E%3C/svg%3E"));
42
- -webkit-mask-repeat: no-repeat;
43
43
  mask-repeat: no-repeat;
44
- -webkit-mask-size: 100% 100%;
45
44
  mask-size: 100% 100%;
46
45
  transform-origin: center;
47
46
  transition: var(--transition, all .05s ease-in-out);
@@ -3,9 +3,8 @@
3
3
  /* https://manifestx.dev/styles/code
4
4
  */
5
5
 
6
- @layer base, components, utilities;
6
+ @layer base, components, utilities, syntax;
7
7
 
8
- @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap');
9
8
 
10
9
  :root {
11
10
 
@@ -80,136 +79,137 @@
80
79
  --color-code-char: #deb887;
81
80
  }
82
81
 
83
- /* Code syntax styles for highlight.js */
84
- @layer utilities {
82
+ /* Code syntax styles for highlight.js. Own layer, ordered after utilities, so
83
+ token colors outrank the block chrome without !important. */
84
+ @layer syntax {
85
85
  .hljs-comment {
86
- color: var(--color-code-comment) !important
86
+ color: var(--color-code-comment)
87
87
  }
88
88
 
89
89
  .hljs-keyword {
90
- color: var(--color-code-keyword) !important
90
+ color: var(--color-code-keyword)
91
91
  }
92
92
 
93
93
  .hljs-string {
94
- color: var(--color-code-string) !important
94
+ color: var(--color-code-string)
95
95
  }
96
96
 
97
97
  .hljs-number {
98
- color: var(--color-code-number) !important
98
+ color: var(--color-code-number)
99
99
  }
100
100
 
101
101
  .hljs-literal {
102
- color: var(--color-code-constant) !important
102
+ color: var(--color-code-constant)
103
103
  }
104
104
 
105
105
  .hljs-type {
106
- color: var(--color-code-class-name) !important
106
+ color: var(--color-code-class-name)
107
107
  }
108
108
 
109
109
  .hljs-variable {
110
- color: var(--color-code-property) !important
110
+ color: var(--color-code-property)
111
111
  }
112
112
 
113
113
  .hljs-variable.language_ {
114
- color: var(--color-code-keyword) !important
114
+ color: var(--color-code-keyword)
115
115
  }
116
116
 
117
117
  .hljs-variable.constant_ {
118
- color: var(--color-code-constant) !important
118
+ color: var(--color-code-constant)
119
119
  }
120
120
 
121
121
  .hljs-title {
122
- color: var(--color-code-function) !important
122
+ color: var(--color-code-function)
123
123
  }
124
124
 
125
125
  .hljs-title.class_.inherited__ {
126
- color: var(--color-code-class-name) !important
126
+ color: var(--color-code-class-name)
127
127
  }
128
128
 
129
129
  .hljs-title.function_.invoke__ {
130
- color: var(--color-code-function) !important
130
+ color: var(--color-code-function)
131
131
  }
132
132
 
133
133
  .hljs-params {
134
- color: var(--color-code-property) !important
134
+ color: var(--color-code-property)
135
135
  }
136
136
 
137
137
  .hljs-doctag {
138
- color: var(--color-code-keyword) !important;
139
- font-weight: 600 !important
138
+ color: var(--color-code-keyword);
139
+ font-weight: 600
140
140
  }
141
141
 
142
142
  .hljs-meta {
143
- color: var(--color-code-comment) !important
143
+ color: var(--color-code-comment)
144
144
  }
145
145
 
146
146
  .hljs-meta.prompt_ {
147
- color: var(--color-code-keyword) !important
147
+ color: var(--color-code-keyword)
148
148
  }
149
149
 
150
150
  .hljs-meta.keyword_ {
151
- color: var(--color-code-keyword) !important
151
+ color: var(--color-code-keyword)
152
152
  }
153
153
 
154
154
  .hljs-meta.string_ {
155
- color: var(--color-code-string) !important
155
+ color: var(--color-code-string)
156
156
  }
157
157
 
158
158
  .hljs-section {
159
- color: var(--color-code-keyword) !important;
160
- font-weight: 600 !important
159
+ color: var(--color-code-keyword);
160
+ font-weight: 600
161
161
  }
162
162
 
163
163
  .hljs-name {
164
- color: var(--color-code-tag) !important
164
+ color: var(--color-code-tag)
165
165
  }
166
166
 
167
167
  .hljs-attribute {
168
- color: var(--color-code-attr-name) !important
168
+ color: var(--color-code-attr-name)
169
169
  }
170
170
 
171
171
  .hljs-bullet {
172
- color: var(--color-code-punctuation) !important
172
+ color: var(--color-code-punctuation)
173
173
  }
174
174
 
175
175
  .hljs-code {
176
- color: var(--color-code-property) !important
176
+ color: var(--color-code-property)
177
177
  }
178
178
 
179
179
  .hljs-formula {
180
- color: var(--color-code-number) !important
180
+ color: var(--color-code-number)
181
181
  }
182
182
 
183
183
  .hljs-quote {
184
- color: var(--color-code-string) !important
184
+ color: var(--color-code-string)
185
185
  }
186
186
 
187
187
  .hljs-selector-id {
188
- color: var(--color-code-selector) !important
188
+ color: var(--color-code-selector)
189
189
  }
190
190
 
191
191
  .hljs-selector-class {
192
- color: var(--color-code-selector) !important
192
+ color: var(--color-code-selector)
193
193
  }
194
194
 
195
195
  .hljs-selector-attr {
196
- color: var(--color-code-selector) !important
196
+ color: var(--color-code-selector)
197
197
  }
198
198
 
199
199
  .hljs-selector-pseudo {
200
- color: var(--color-code-selector) !important
200
+ color: var(--color-code-selector)
201
201
  }
202
202
 
203
203
  .hljs-template-tag {
204
- color: var(--color-code-tag) !important
204
+ color: var(--color-code-tag)
205
205
  }
206
206
 
207
207
  .hljs-template-variable {
208
- color: var(--color-code-property) !important
208
+ color: var(--color-code-property)
209
209
  }
210
210
 
211
211
  .hljs-subst {
212
- color: var(--color-code-string) !important
212
+ color: var(--color-code-string)
213
213
  }
214
214
  }
215
215
 
@@ -407,7 +407,6 @@
407
407
  font-weight: unset;
408
408
  color: unset;
409
409
  background: unset;
410
- border: unset;
411
410
  box-shadow: unset;
412
411
  border: 0 none;
413
412
  white-space: normal;
@@ -1 +1 @@
1
- @layer base, components, utilities;@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap");:root{--icon-code-copy:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Crect width='14' height='14' x='8' y='8' rx='2' ry='2'/%3E%3Cpath d='M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2'/%3E%3C/g%3E%3C/svg%3E");--icon-code-copied:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBjbGFzcz0ibHVjaWRlIGx1Y2lkZS1jb3B5LWNoZWNrLWljb24gbHVjaWRlLWNvcHktY2hlY2siIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0ibTEyIDE1IDIgMiA0LTQiLz48cmVjdCB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIHg9IjgiIHk9IjgiIHJ4PSIyIiByeT0iMiIvPjxwYXRoIGQ9Ik00IDE2Yy0xLjEgMC0yLS45LTItMlY0YzAtMS4xLjktMiAyLTJoMTBjMS4xIDAgMiAuOSAyIDIiLz48L3N2Zz4=");--icon-code-expand:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m6 9 6 6 6-6'/%3E%3C/svg%3E");--color-code-keyword:#b8860b;--color-code-string:#8b4513;--color-code-comment:gray;--color-code-function:peru;--color-code-number:sienna;--color-code-operator:#2f4f4f;--color-code-class-name:#daa520;--color-code-tag:#4682b4;--color-code-attr-name:#ff8c00;--color-code-attr-value:#8b4513;--color-code-property:sienna;--color-code-selector:#4682b4;--color-code-punctuation:#2f4f4f;--color-code-builtin:#b8860b;--color-code-constant:sienna;--color-code-boolean:sienna;--color-code-regex:#8b4513;--color-code-symbol:#daa520;--color-code-entity:#daa520;--color-code-url:sienna;--color-code-atrule:#b8860b;--color-code-rule:#4682b4;--color-code-doctype:gray;--color-code-cdata:gray;--color-code-prolog:gray;--color-code-namespace:gray;--color-code-important:#b8860b;--color-code-inserted:#228b22;--color-code-deleted:#dc143c;--color-code-char:#8b4513}.dark{--color-code-keyword:#f4a460;--color-code-string:#deb887;--color-code-comment:#8b8b8b;--color-code-function:#daa520;--color-code-number:tan;--color-code-operator:wheat;--color-code-class-name:peru;--color-code-tag:#87ceeb;--color-code-attr-name:gold;--color-code-attr-value:#deb887;--color-code-property:tan;--color-code-selector:#87ceeb;--color-code-punctuation:wheat;--color-code-builtin:#f4a460;--color-code-constant:tan;--color-code-boolean:tan;--color-code-regex:#deb887;--color-code-symbol:peru;--color-code-entity:peru;--color-code-url:tan;--color-code-atrule:#f4a460;--color-code-rule:#98fb98;--color-code-doctype:#8b8b8b;--color-code-cdata:#8b8b8b;--color-code-prolog:#8b8b8b;--color-code-namespace:#8b8b8b;--color-code-important:#f4a460;--color-code-inserted:#98fb98;--color-code-deleted:#f08080;--color-code-char:#deb887}@layer utilities{.hljs-comment{color:var(--color-code-comment)!important}.hljs-keyword{color:var(--color-code-keyword)!important}.hljs-string{color:var(--color-code-string)!important}.hljs-number{color:var(--color-code-number)!important}.hljs-literal{color:var(--color-code-constant)!important}.hljs-type{color:var(--color-code-class-name)!important}.hljs-variable{color:var(--color-code-property)!important}.hljs-variable.language_{color:var(--color-code-keyword)!important}.hljs-variable.constant_{color:var(--color-code-constant)!important}.hljs-title{color:var(--color-code-function)!important}.hljs-title.class_.inherited__{color:var(--color-code-class-name)!important}.hljs-title.function_.invoke__{color:var(--color-code-function)!important}.hljs-params{color:var(--color-code-property)!important}.hljs-doctag{color:var(--color-code-keyword)!important;font-weight:600!important}.hljs-meta{color:var(--color-code-comment)!important}.hljs-meta.keyword_,.hljs-meta.prompt_{color:var(--color-code-keyword)!important}.hljs-meta.string_{color:var(--color-code-string)!important}.hljs-section{color:var(--color-code-keyword)!important;font-weight:600!important}.hljs-name{color:var(--color-code-tag)!important}.hljs-attribute{color:var(--color-code-attr-name)!important}.hljs-bullet{color:var(--color-code-punctuation)!important}.hljs-code{color:var(--color-code-property)!important}.hljs-formula{color:var(--color-code-number)!important}.hljs-quote{color:var(--color-code-string)!important}.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo{color:var(--color-code-selector)!important}.hljs-template-tag{color:var(--color-code-tag)!important}.hljs-template-variable{color:var(--color-code-property)!important}.hljs-subst{color:var(--color-code-string)!important}}@layer components{:where(code[role=button]){height:fit-content}:where(.code-copied-icon){background-color:currentColor;display:inline-block;height:1em;mask-image:var(--icon-code-copied);mask-position:center;mask-repeat:no-repeat;mask-size:1em;vertical-align:middle;width:1em;@supports (height:1lh){height:1lh;vertical-align:top}}:where([x-code],[x-code-group]):not(.unstyle){position:relative;:where(header):not(:where(:is(aside.frame) header)){align-items:center;background-color:color-mix(in oklch,var(--color-content-stark,oklch(43.9% 0 0)) 8%,transparent);color:var(--color-content-neutral,oklch(43.9% 0 0));display:flex;flex-direction:row;font-family:var(--font-sans,sans-serif);font-size:inherit;min-height:var(--spacing-field-height,2.25rem);padding-inline-end:var(--spacing-field-height);padding-inline-start:calc(var(--spacing, .25rem)*4);width:100%;&:has(button[role=tab]){padding-inline-start:calc(var(--spacing, .25rem)*2)}& [role=tablist]{display:flex;flex-direction:row;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;&::-webkit-scrollbar{display:none}& button[role=tab]{background:transparent;border:0;color:inherit;flex-shrink:0;font-family:inherit;font-size:inherit;height:fit-content;justify-content:start;&:hover{background-color:var(--color-field-surface,color-mix(in oklch,oklch(20.5% 0 0) 10%,transparent));color:var(--color-content-stark,oklch(43.9% 0 0))}&[aria-selected=true]{color:var(--color-brand-content,oklch(68.1% .162 75.834));pointer-events:none;position:relative}}}}& button.copy{background-color:transparent;font-size:75%;inset-inline-end:0;position:absolute;top:0;&:hover{background-color:transparent;&:after{background-color:var(--color-field-inverse,oklch(43.9% 0 0))}}&:after{background-color:var(--color-content-neutral,oklch(43.9% 0 0));content:"";display:block;height:.8125rem;mask-image:var(--icon-code-copy);mask-repeat:no-repeat;mask-size:contain;width:.8125rem}&.copied:after{mask-image:var(--icon-code-copied)}}:where(header)::-webkit-scrollbar{display:none}& .lines{background-color:var(--color-page,oklch(98.5% 0 0));color:var(--color-content-subtle,oklch(55.6% 0 0));display:inline-flex;flex-direction:column;font-family:inherit;font-size:inherit;inset-inline-start:0;line-height:inherit;padding:calc(var(--spacing, .25rem)*4);pointer-events:none;position:absolute;text-align:end;user-select:none}&:has(.lines) code{padding-inline-start:calc(var(--spacing, .25rem)*8 + 2ch)}:where(code){flex:1 1 100%;min-width:0;white-space:pre;white-space-collapse:preserve}& button.expand{align-items:center;background:transparent;border:0;border-radius:0;color:var(--color-content-subtle,oklch(55.6% 0 0));cursor:pointer;display:flex;font-family:var(--font-sans,sans-serif);font-size:inherit;height:var(--spacing-field-height,2.25rem);justify-content:center;order:99;padding:0 calc(var(--spacing, .25rem)*2);transition:var(--transition,all .05s ease-in-out);width:100%;&:hover{background:color-mix(in oklch,var(--color-content-stark,oklch(43.9% 0 0)) 8%,transparent);color:var(--color-content-neutral,oklch(43.9% 0 0))}}&[data-collapsed] .lines,&[data-collapsed] code{mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - 3em),transparent calc(100% - .75em),transparent);max-height:calc(var(--collapse-lines, 20)*1.7em + var(--spacing, .25rem)*8);overflow:hidden}:where(aside.frame){background:unset;border:0;box-shadow:unset;color:unset;font-family:unset;font-size:unset;font-weight:unset;line-height:unset;white-space:normal;white-space-collapse:collapse}}:where([x-code-group]){flex-flow:column;:where([x-code]){background:transparent;border:0;border-radius:0}}:where([x-code]):has(>header){& .lines{top:var(--spacing-field-height,2.25rem)}}}
1
+ @layer base, components, utilities, syntax;:root{--icon-code-copy:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Crect width='14' height='14' x='8' y='8' rx='2' ry='2'/%3E%3Cpath d='M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2'/%3E%3C/g%3E%3C/svg%3E");--icon-code-copied:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBjbGFzcz0ibHVjaWRlIGx1Y2lkZS1jb3B5LWNoZWNrLWljb24gbHVjaWRlLWNvcHktY2hlY2siIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0ibTEyIDE1IDIgMiA0LTQiLz48cmVjdCB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIHg9IjgiIHk9IjgiIHJ4PSIyIiByeT0iMiIvPjxwYXRoIGQ9Ik00IDE2Yy0xLjEgMC0yLS45LTItMlY0YzAtMS4xLjktMiAyLTJoMTBjMS4xIDAgMiAuOSAyIDIiLz48L3N2Zz4=");--icon-code-expand:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m6 9 6 6 6-6'/%3E%3C/svg%3E");--color-code-keyword:#b8860b;--color-code-string:#8b4513;--color-code-comment:gray;--color-code-function:peru;--color-code-number:sienna;--color-code-operator:#2f4f4f;--color-code-class-name:#daa520;--color-code-tag:#4682b4;--color-code-attr-name:#ff8c00;--color-code-attr-value:#8b4513;--color-code-property:sienna;--color-code-selector:#4682b4;--color-code-punctuation:#2f4f4f;--color-code-builtin:#b8860b;--color-code-constant:sienna;--color-code-boolean:sienna;--color-code-regex:#8b4513;--color-code-symbol:#daa520;--color-code-entity:#daa520;--color-code-url:sienna;--color-code-atrule:#b8860b;--color-code-rule:#4682b4;--color-code-doctype:gray;--color-code-cdata:gray;--color-code-prolog:gray;--color-code-namespace:gray;--color-code-important:#b8860b;--color-code-inserted:#228b22;--color-code-deleted:#dc143c;--color-code-char:#8b4513}.dark{--color-code-keyword:#f4a460;--color-code-string:#deb887;--color-code-comment:#8b8b8b;--color-code-function:#daa520;--color-code-number:tan;--color-code-operator:wheat;--color-code-class-name:peru;--color-code-tag:#87ceeb;--color-code-attr-name:gold;--color-code-attr-value:#deb887;--color-code-property:tan;--color-code-selector:#87ceeb;--color-code-punctuation:wheat;--color-code-builtin:#f4a460;--color-code-constant:tan;--color-code-boolean:tan;--color-code-regex:#deb887;--color-code-symbol:peru;--color-code-entity:peru;--color-code-url:tan;--color-code-atrule:#f4a460;--color-code-rule:#98fb98;--color-code-doctype:#8b8b8b;--color-code-cdata:#8b8b8b;--color-code-prolog:#8b8b8b;--color-code-namespace:#8b8b8b;--color-code-important:#f4a460;--color-code-inserted:#98fb98;--color-code-deleted:#f08080;--color-code-char:#deb887}@layer syntax{.hljs-comment{color:var(--color-code-comment)}.hljs-keyword{color:var(--color-code-keyword)}.hljs-string{color:var(--color-code-string)}.hljs-number{color:var(--color-code-number)}.hljs-literal{color:var(--color-code-constant)}.hljs-type{color:var(--color-code-class-name)}.hljs-variable{color:var(--color-code-property)}.hljs-variable.language_{color:var(--color-code-keyword)}.hljs-variable.constant_{color:var(--color-code-constant)}.hljs-title{color:var(--color-code-function)}.hljs-title.class_.inherited__{color:var(--color-code-class-name)}.hljs-title.function_.invoke__{color:var(--color-code-function)}.hljs-params{color:var(--color-code-property)}.hljs-doctag{color:var(--color-code-keyword);font-weight:600}.hljs-meta{color:var(--color-code-comment)}.hljs-meta.keyword_,.hljs-meta.prompt_{color:var(--color-code-keyword)}.hljs-meta.string_{color:var(--color-code-string)}.hljs-section{color:var(--color-code-keyword);font-weight:600}.hljs-name{color:var(--color-code-tag)}.hljs-attribute{color:var(--color-code-attr-name)}.hljs-bullet{color:var(--color-code-punctuation)}.hljs-code{color:var(--color-code-property)}.hljs-formula{color:var(--color-code-number)}.hljs-quote{color:var(--color-code-string)}.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo{color:var(--color-code-selector)}.hljs-template-tag{color:var(--color-code-tag)}.hljs-template-variable{color:var(--color-code-property)}.hljs-subst{color:var(--color-code-string)}}@layer components{:where(code[role=button]){height:fit-content}:where(.code-copied-icon){background-color:currentColor;display:inline-block;height:1em;mask-image:var(--icon-code-copied);mask-position:center;mask-repeat:no-repeat;mask-size:1em;vertical-align:middle;width:1em;@supports (height:1lh){height:1lh;vertical-align:top}}:where([x-code],[x-code-group]):not(.unstyle){position:relative;:where(header):not(:where(:is(aside.frame) header)){align-items:center;background-color:color-mix(in oklch,var(--color-content-stark,oklch(43.9% 0 0)) 8%,transparent);color:var(--color-content-neutral,oklch(43.9% 0 0));display:flex;flex-direction:row;font-family:var(--font-sans,sans-serif);font-size:inherit;min-height:var(--spacing-field-height,2.25rem);padding-inline-end:var(--spacing-field-height);padding-inline-start:calc(var(--spacing, .25rem)*4);width:100%;&:has(button[role=tab]){padding-inline-start:calc(var(--spacing, .25rem)*2)}& [role=tablist]{display:flex;flex-direction:row;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;&::-webkit-scrollbar{display:none}& button[role=tab]{background:transparent;border:0;color:inherit;flex-shrink:0;font-family:inherit;font-size:inherit;height:fit-content;justify-content:start;&:hover{background-color:var(--color-field-surface,color-mix(in oklch,oklch(20.5% 0 0) 10%,transparent));color:var(--color-content-stark,oklch(43.9% 0 0))}&[aria-selected=true]{color:var(--color-brand-content,oklch(68.1% .162 75.834));pointer-events:none;position:relative}}}}& button.copy{background-color:transparent;font-size:75%;inset-inline-end:0;position:absolute;top:0;&:hover{background-color:transparent;&:after{background-color:var(--color-field-inverse,oklch(43.9% 0 0))}}&:after{background-color:var(--color-content-neutral,oklch(43.9% 0 0));content:"";display:block;height:.8125rem;mask-image:var(--icon-code-copy);mask-repeat:no-repeat;mask-size:contain;width:.8125rem}&.copied:after{mask-image:var(--icon-code-copied)}}:where(header)::-webkit-scrollbar{display:none}& .lines{background-color:var(--color-page,oklch(98.5% 0 0));color:var(--color-content-subtle,oklch(55.6% 0 0));display:inline-flex;flex-direction:column;font-family:inherit;font-size:inherit;inset-inline-start:0;line-height:inherit;padding:calc(var(--spacing, .25rem)*4);pointer-events:none;position:absolute;text-align:end;user-select:none}&:has(.lines) code{padding-inline-start:calc(var(--spacing, .25rem)*8 + 2ch)}:where(code){flex:1 1 100%;min-width:0;white-space:pre;white-space-collapse:preserve}& button.expand{align-items:center;background:transparent;border:0;border-radius:0;color:var(--color-content-subtle,oklch(55.6% 0 0));cursor:pointer;display:flex;font-family:var(--font-sans,sans-serif);font-size:inherit;height:var(--spacing-field-height,2.25rem);justify-content:center;order:99;padding:0 calc(var(--spacing, .25rem)*2);transition:var(--transition,all .05s ease-in-out);width:100%;&:hover{background:color-mix(in oklch,var(--color-content-stark,oklch(43.9% 0 0)) 8%,transparent);color:var(--color-content-neutral,oklch(43.9% 0 0))}}&[data-collapsed] .lines,&[data-collapsed] code{mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - 3em),transparent calc(100% - .75em),transparent);max-height:calc(var(--collapse-lines, 20)*1.7em + var(--spacing, .25rem)*8);overflow:hidden}:where(aside.frame){background:unset;border:0;box-shadow:unset;color:unset;font-family:unset;font-size:unset;font-weight:unset;line-height:unset;white-space:normal;white-space-collapse:collapse}}:where([x-code-group]){flex-flow:column;:where([x-code]){background:transparent;border:0;border-radius:0}}:where([x-code]):has(>header){& .lines{top:var(--spacing-field-height,2.25rem)}}}
@@ -2,6 +2,31 @@
2
2
 
3
3
  @layer base, components, utilities;
4
4
 
5
+ /* mnfst:popover-base:start — self-contained so this stylesheet works on
6
+ its own; the build strips it from manifest.css. Edit styles/snippets/manifest.popover.css. */
7
+ [popover]:not(.unstyle):not(:popover-open) {
8
+ display: none;
9
+ }
10
+
11
+ @layer base {
12
+
13
+ @keyframes mnfst-popover-in {
14
+ from {
15
+ transform: scale(.9)
16
+ }
17
+
18
+ to {
19
+ transform: none
20
+ }
21
+ }
22
+
23
+ :where([popover]):not(.unstyle):popover-open {
24
+ display: flex;
25
+ animation: mnfst-popover-in .15s ease-in both
26
+ }
27
+ }
28
+ /* mnfst:popover-base:end */
29
+
5
30
  :root {
6
31
  --color-picker-swatch: var(--color-line, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent));
7
32
  --icon-color-solid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='lucide lucide-paint-roller-icon lucide-paint-roller'%3E%3Crect width='16' height='6' x='2' y='2' rx='2'/%3E%3Cpath d='M10 16v-2a2 2 0 0 1 2-2h8a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2'/%3E%3Crect width='4' height='6' x='8' y='16' rx='1'/%3E%3C/svg%3E");