@kilivi/payloadcms-theme-management 1.1.2 → 2.1.0

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.
Files changed (214) hide show
  1. package/README.md +318 -847
  2. package/dist/components/AppearanceSessionControls.d.ts +15 -0
  3. package/dist/components/AppearanceSessionControls.d.ts.map +1 -0
  4. package/dist/components/AppearanceSessionControls.js +152 -0
  5. package/dist/components/AppearanceSessionControls.js.map +1 -0
  6. package/dist/components/ColorModeToggle.css +42 -0
  7. package/dist/components/ColorModeToggle.d.ts +40 -0
  8. package/dist/components/ColorModeToggle.d.ts.map +1 -0
  9. package/dist/components/ColorModeToggle.js +111 -0
  10. package/dist/components/ColorModeToggle.js.map +1 -0
  11. package/dist/components/ServerThemeInjector.d.ts.map +1 -1
  12. package/dist/components/ServerThemeInjector.js +41 -14
  13. package/dist/components/ServerThemeInjector.js.map +1 -1
  14. package/dist/components/ThemeLivePreviewSync.d.ts +1 -0
  15. package/dist/components/ThemeLivePreviewSync.d.ts.map +1 -1
  16. package/dist/components/ThemeLivePreviewSync.js +1 -1
  17. package/dist/components/ThemeLivePreviewSync.js.map +1 -1
  18. package/dist/components/ThemeSwitcher.d.ts +36 -0
  19. package/dist/components/ThemeSwitcher.d.ts.map +1 -0
  20. package/dist/components/ThemeSwitcher.js +89 -0
  21. package/dist/components/ThemeSwitcher.js.map +1 -0
  22. package/dist/components/typographyPreviewUtils.d.ts.map +1 -1
  23. package/dist/components/typographyPreviewUtils.js +8 -1
  24. package/dist/components/typographyPreviewUtils.js.map +1 -1
  25. package/dist/constants/devicePreviews.d.ts +35 -0
  26. package/dist/constants/devicePreviews.d.ts.map +1 -0
  27. package/dist/constants/devicePreviews.js +43 -0
  28. package/dist/constants/devicePreviews.js.map +1 -0
  29. package/dist/constants/themeFonts.js +9 -9
  30. package/dist/constants/themeFonts.js.map +1 -1
  31. package/dist/extended-presets.d.ts +0 -4
  32. package/dist/extended-presets.d.ts.map +1 -1
  33. package/dist/extended-presets.js +1 -18
  34. package/dist/extended-presets.js.map +1 -1
  35. package/dist/extended-themes.d.ts.map +1 -1
  36. package/dist/extended-themes.js +428 -0
  37. package/dist/extended-themes.js.map +1 -1
  38. package/dist/fields/AccessibilityAuditField.d.ts +2 -0
  39. package/dist/fields/AccessibilityAuditField.d.ts.map +1 -0
  40. package/dist/fields/AccessibilityAuditField.js +303 -0
  41. package/dist/fields/AccessibilityAuditField.js.map +1 -0
  42. package/dist/fields/AppearancePreviewField.d.ts +2 -0
  43. package/dist/fields/AppearancePreviewField.d.ts.map +1 -0
  44. package/dist/fields/AppearancePreviewField.js +763 -0
  45. package/dist/fields/AppearancePreviewField.js.map +1 -0
  46. package/dist/fields/ColorPickerField.d.ts.map +1 -1
  47. package/dist/fields/ColorPickerField.js +89 -85
  48. package/dist/fields/ColorPickerField.js.map +1 -1
  49. package/dist/fields/FontSelectField.d.ts +6 -0
  50. package/dist/fields/FontSelectField.d.ts.map +1 -1
  51. package/dist/fields/FontSelectField.js +195 -122
  52. package/dist/fields/FontSelectField.js.map +1 -1
  53. package/dist/fields/PaletteGeneratorField.d.ts +7 -0
  54. package/dist/fields/PaletteGeneratorField.d.ts.map +1 -0
  55. package/dist/fields/PaletteGeneratorField.js +327 -0
  56. package/dist/fields/PaletteGeneratorField.js.map +1 -0
  57. package/dist/fields/StylePresetField.d.ts +3 -0
  58. package/dist/fields/StylePresetField.d.ts.map +1 -0
  59. package/dist/fields/StylePresetField.js +462 -0
  60. package/dist/fields/StylePresetField.js.map +1 -0
  61. package/dist/fields/ThemeColorPickerField.css +115 -35
  62. package/dist/fields/ThemeColorPickerField.d.ts.map +1 -1
  63. package/dist/fields/ThemeColorPickerField.js +204 -70
  64. package/dist/fields/ThemeColorPickerField.js.map +1 -1
  65. package/dist/fields/ThemeExportField.d.ts +6 -0
  66. package/dist/fields/ThemeExportField.d.ts.map +1 -0
  67. package/dist/fields/ThemeExportField.js +202 -0
  68. package/dist/fields/ThemeExportField.js.map +1 -0
  69. package/dist/fields/ThemePresetImportField.d.ts.map +1 -1
  70. package/dist/fields/ThemePresetImportField.js +56 -25
  71. package/dist/fields/ThemePresetImportField.js.map +1 -1
  72. package/dist/fields/ThemePreviewField.css +382 -5
  73. package/dist/fields/ThemePreviewField.d.ts.map +1 -1
  74. package/dist/fields/ThemePreviewField.js +268 -170
  75. package/dist/fields/ThemePreviewField.js.map +1 -1
  76. package/dist/fields/ThemeTokenSelectField.d.ts.map +1 -1
  77. package/dist/fields/ThemeTokenSelectField.js +278 -80
  78. package/dist/fields/ThemeTokenSelectField.js.map +1 -1
  79. package/dist/fields/colorFieldHelpers.d.ts +90 -0
  80. package/dist/fields/colorFieldHelpers.d.ts.map +1 -0
  81. package/dist/fields/colorFieldHelpers.js +133 -0
  82. package/dist/fields/colorFieldHelpers.js.map +1 -0
  83. package/dist/fields/colorModeFields.d.ts +1 -1
  84. package/dist/fields/colorModeFields.d.ts.map +1 -1
  85. package/dist/fields/colorModeFields.js +49 -7
  86. package/dist/fields/colorModeFields.js.map +1 -1
  87. package/dist/fields/themeConfigurationField.d.ts +8 -1
  88. package/dist/fields/themeConfigurationField.d.ts.map +1 -1
  89. package/dist/fields/themeConfigurationField.js +961 -29
  90. package/dist/fields/themeConfigurationField.js.map +1 -1
  91. package/dist/globals/ThemeSettings/FontPicker/FontPreview.d.ts.map +1 -1
  92. package/dist/globals/ThemeSettings/FontPicker/FontPreview.js +8 -4
  93. package/dist/globals/ThemeSettings/FontPicker/FontPreview.js.map +1 -1
  94. package/dist/globals/ThemeSettings/FontPicker/index.d.ts.map +1 -1
  95. package/dist/globals/ThemeSettings/FontPicker/index.js +81 -26
  96. package/dist/globals/ThemeSettings/FontPicker/index.js.map +1 -1
  97. package/dist/globals/ThemeSettings/FontPicker/styles.css +98 -41
  98. package/dist/globals/ThemeSettings/FontPicker/translations.d.ts +6 -0
  99. package/dist/globals/ThemeSettings/FontPicker/translations.d.ts.map +1 -1
  100. package/dist/globals/ThemeSettings/FontPicker/translations.js +14 -2
  101. package/dist/globals/ThemeSettings/FontPicker/translations.js.map +1 -1
  102. package/dist/globals/ThemeSettings/config.d.ts.map +1 -1
  103. package/dist/globals/ThemeSettings/config.js +76 -79
  104. package/dist/globals/ThemeSettings/config.js.map +1 -1
  105. package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.d.ts.map +1 -1
  106. package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.js +63 -62
  107. package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.js.map +1 -1
  108. package/dist/globals/ThemeSettings/fields/TypographyInfoField.d.ts.map +1 -1
  109. package/dist/globals/ThemeSettings/fields/TypographyInfoField.js +22 -25
  110. package/dist/globals/ThemeSettings/fields/TypographyInfoField.js.map +1 -1
  111. package/dist/hooks/useAppearanceSession.d.ts +50 -0
  112. package/dist/hooks/useAppearanceSession.d.ts.map +1 -0
  113. package/dist/hooks/useAppearanceSession.js +91 -0
  114. package/dist/hooks/useAppearanceSession.js.map +1 -0
  115. package/dist/hooks/useThemeTranslations.d.ts +13 -0
  116. package/dist/hooks/useThemeTranslations.d.ts.map +1 -0
  117. package/dist/hooks/useThemeTranslations.js +22 -0
  118. package/dist/hooks/useThemeTranslations.js.map +1 -0
  119. package/dist/i18n.d.ts +53 -0
  120. package/dist/i18n.d.ts.map +1 -0
  121. package/dist/i18n.js +53 -0
  122. package/dist/i18n.js.map +1 -0
  123. package/dist/index.d.ts +10 -4
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +130 -32
  126. package/dist/index.js.map +1 -1
  127. package/dist/payload-types.d.ts +24 -0
  128. package/dist/payload-types.d.ts.map +1 -1
  129. package/dist/payload-types.js.map +1 -1
  130. package/dist/presets.d.ts +8 -0
  131. package/dist/presets.d.ts.map +1 -1
  132. package/dist/presets.js.map +1 -1
  133. package/dist/providers/Theme/index.d.ts.map +1 -1
  134. package/dist/providers/Theme/index.js +75 -11
  135. package/dist/providers/Theme/index.js.map +1 -1
  136. package/dist/providers/Theme/themeConfig.d.ts.map +1 -1
  137. package/dist/providers/Theme/themeConfig.js +104 -0
  138. package/dist/providers/Theme/themeConfig.js.map +1 -1
  139. package/dist/providers/Theme/types.d.ts +1 -1
  140. package/dist/providers/Theme/types.d.ts.map +1 -1
  141. package/dist/providers/Theme/types.js +10 -1
  142. package/dist/providers/Theme/types.js.map +1 -1
  143. package/dist/style-presets.d.ts +66 -0
  144. package/dist/style-presets.d.ts.map +1 -0
  145. package/dist/style-presets.js +556 -0
  146. package/dist/style-presets.js.map +1 -0
  147. package/dist/translations.d.ts +159 -1
  148. package/dist/translations.d.ts.map +1 -1
  149. package/dist/translations.js +310 -9
  150. package/dist/translations.js.map +1 -1
  151. package/dist/tweakcn-special-themes.d.ts.map +1 -1
  152. package/dist/tweakcn-special-themes.js +585 -9
  153. package/dist/tweakcn-special-themes.js.map +1 -1
  154. package/dist/types/css.d.js +3 -0
  155. package/dist/types/css.d.js.map +1 -0
  156. package/dist/types.d.ts +30 -0
  157. package/dist/types.d.ts.map +1 -1
  158. package/dist/types.js +1 -1
  159. package/dist/types.js.map +1 -1
  160. package/dist/utilities/getThemeSettings.d.ts.map +1 -1
  161. package/dist/utilities/getThemeSettings.js +2 -14
  162. package/dist/utilities/getThemeSettings.js.map +1 -1
  163. package/dist/utils/contrast.d.ts +63 -0
  164. package/dist/utils/contrast.d.ts.map +1 -0
  165. package/dist/utils/contrast.js +158 -0
  166. package/dist/utils/contrast.js.map +1 -0
  167. package/dist/utils/exportTokens.d.ts +30 -0
  168. package/dist/utils/exportTokens.d.ts.map +1 -0
  169. package/dist/utils/exportTokens.js +183 -0
  170. package/dist/utils/exportTokens.js.map +1 -0
  171. package/dist/utils/generatePalette.d.ts +46 -0
  172. package/dist/utils/generatePalette.d.ts.map +1 -0
  173. package/dist/utils/generatePalette.js +127 -0
  174. package/dist/utils/generatePalette.js.map +1 -0
  175. package/dist/utils/generateThemeCSS.d.ts.map +1 -1
  176. package/dist/utils/generateThemeCSS.js +2 -40
  177. package/dist/utils/generateThemeCSS.js.map +1 -1
  178. package/dist/utils/livePreviewThemeRuntime.d.ts +16 -2
  179. package/dist/utils/livePreviewThemeRuntime.d.ts.map +1 -1
  180. package/dist/utils/livePreviewThemeRuntime.js +37 -14
  181. package/dist/utils/livePreviewThemeRuntime.js.map +1 -1
  182. package/dist/utils/localizedText.d.ts +8 -0
  183. package/dist/utils/localizedText.d.ts.map +1 -0
  184. package/dist/utils/localizedText.js +21 -0
  185. package/dist/utils/localizedText.js.map +1 -0
  186. package/dist/utils/resolveThemeConfiguration.d.ts +45 -1
  187. package/dist/utils/resolveThemeConfiguration.d.ts.map +1 -1
  188. package/dist/utils/resolveThemeConfiguration.js +12 -5
  189. package/dist/utils/resolveThemeConfiguration.js.map +1 -1
  190. package/dist/utils/themeColors.d.ts +3 -0
  191. package/dist/utils/themeColors.d.ts.map +1 -1
  192. package/dist/utils/themeColors.js +30 -1
  193. package/dist/utils/themeColors.js.map +1 -1
  194. package/dist/utils/themeHtmlAttributes.d.ts +32 -12
  195. package/dist/utils/themeHtmlAttributes.d.ts.map +1 -1
  196. package/dist/utils/themeHtmlAttributes.js +38 -5
  197. package/dist/utils/themeHtmlAttributes.js.map +1 -1
  198. package/dist/utils/themeUtils.d.ts +3 -5
  199. package/dist/utils/themeUtils.d.ts.map +1 -1
  200. package/dist/utils/themeUtils.js +416 -6
  201. package/dist/utils/themeUtils.js.map +1 -1
  202. package/dist/utils/tweakcnConverter.d.ts +1 -3
  203. package/dist/utils/tweakcnConverter.d.ts.map +1 -1
  204. package/dist/utils/tweakcnConverter.js +1 -3
  205. package/dist/utils/tweakcnConverter.js.map +1 -1
  206. package/package.json +11 -5
  207. package/dist/components/ThemePreview.d.ts +0 -3
  208. package/dist/components/ThemePreview.d.ts.map +0 -1
  209. package/dist/components/ThemePreview.js +0 -321
  210. package/dist/components/ThemePreview.js.map +0 -1
  211. package/dist/plugin.d.ts +0 -2
  212. package/dist/plugin.d.ts.map +0 -1
  213. package/dist/plugin.js +0 -3
  214. package/dist/plugin.js.map +0 -1
@@ -5,7 +5,7 @@ import { borderRadiusPresets } from '../providers/Theme/themeConfig.js';
5
5
  const cssRules = [];
6
6
  const customization = config.customization;
7
7
  const themeSelector = config.theme ? `:root[data-theme='${config.theme}']` : ':root';
8
- // Start root CSS rules
8
+ // ── :root block — CSS custom properties ─────────────────────────────────
9
9
  cssRules.push(`${themeSelector} {`);
10
10
  applyCustomizationRootRules(cssRules, customization);
11
11
  applyBorderRadiusRule(cssRules, config);
@@ -13,12 +13,16 @@ import { borderRadiusPresets } from '../providers/Theme/themeConfig.js';
13
13
  applySpacingRule(cssRules, config);
14
14
  applyAnimationRule(cssRules, config);
15
15
  applyTypographyRules(cssRules, config);
16
+ applyVisualEffectsCSSVars(cssRules, config);
17
+ applyComponentStyleCSSVars(cssRules, config);
16
18
  cssRules.push('}');
17
- // Color mode specific overrides
19
+ // ── Element-level effect rules (outside :root) ──────────────────────────
20
+ applyEffectElementRules(cssRules, config);
21
+ // ── Color mode overrides ────────────────────────────────────────────────
18
22
  applyDarkModeOverrides(cssRules, config, customization, themeSelector);
19
- // Animation level: none - respect prefers-reduced-motion
23
+ // ── Animation: none — respect prefers-reduced-motion ───────────────────
20
24
  applyReducedMotionOverride(cssRules, config);
21
- // Custom CSS
25
+ // ── Custom CSS ──────────────────────────────────────────────────────────
22
26
  appendCustomCSS(cssRules, customization);
23
27
  return cssRules.join('\n');
24
28
  }
@@ -128,7 +132,7 @@ function applyAnimationRule(rules, config) {
128
132
  easing: 'ease-out',
129
133
  scale: '1'
130
134
  },
131
- full: {
135
+ high: {
132
136
  duration: '0.3s',
133
137
  easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
134
138
  scale: '1.2'
@@ -136,7 +140,6 @@ function applyAnimationRule(rules, config) {
136
140
  };
137
141
  const level = config.animationLevel;
138
142
  const settings = animationSettings[level] ?? animationSettings.medium;
139
- if (!settings) return;
140
143
  rules.push(` --animation-duration: ${settings.duration};`);
141
144
  rules.push(` --animation-easing: ${settings.easing};`);
142
145
  rules.push(` --animation-scale: ${settings.scale};`);
@@ -172,6 +175,413 @@ function applyTypographyRules(rules, config) {
172
175
  });
173
176
  }
174
177
  }
178
+ function getVE(config) {
179
+ return config.visualEffects ?? null;
180
+ }
181
+ function getCS(config) {
182
+ return config.componentStyles ?? null;
183
+ }
184
+ const SHADOW_MAP = {
185
+ none: 'none',
186
+ subtle: '0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04)',
187
+ medium: '0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1)',
188
+ strong: '0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)',
189
+ dramatic: '0 25px 50px -12px rgba(0,0,0,0.25)'
190
+ };
191
+ const BLUR_PX_MAP = {
192
+ none: '0px',
193
+ slight: '4px',
194
+ medium: '8px',
195
+ strong: '16px',
196
+ heavy: '24px'
197
+ };
198
+ /**
199
+ * CSS custom properties for visual effects (emitted inside :root {})
200
+ */ function applyVisualEffectsCSSVars(rules, config) {
201
+ const ve = getVE(config);
202
+ if (!ve) return;
203
+ if (ve.effectStyle) rules.push(` --effect-style: ${ve.effectStyle};`);
204
+ if (ve.shadowIntensity) {
205
+ rules.push(` --shadow-intensity: ${ve.shadowIntensity};`);
206
+ const shadow = SHADOW_MAP[ve.shadowIntensity];
207
+ if (shadow) rules.push(` --card-shadow: ${shadow};`);
208
+ }
209
+ if (ve.backdropBlur) {
210
+ const blurPx = BLUR_PX_MAP[ve.backdropBlur] ?? '0px';
211
+ rules.push(` --backdrop-blur: ${blurPx};`);
212
+ // Token form (none|slight|...) — read by the client ThemeProvider to mirror
213
+ // the SSR data-backdrop-blur attribute.
214
+ rules.push(` --backdrop-blur-level: ${ve.backdropBlur};`);
215
+ }
216
+ if (ve.borderStyle) rules.push(` --border-style: ${ve.borderStyle};`);
217
+ if (ve.borderWidth) rules.push(` --border-width: ${ve.borderWidth};`);
218
+ if (ve.glassOpacity != null) {
219
+ const opacity = Math.min(100, Math.max(0, ve.glassOpacity)) / 100;
220
+ rules.push(` --glass-opacity: ${opacity.toFixed(2)};`);
221
+ }
222
+ // Always emit --card-bg-opacity and --card-backdrop-filter so .glass-card / .glass
223
+ // fallback rules in consumer globals.css can be overridden regardless of cached state.
224
+ // Glass effect: translucent card; all other effects: reset to opaque/none.
225
+ const opacity = ve.glassOpacity != null ? (ve.glassOpacity / 100).toFixed(2) : '0.60';
226
+ const blur = BLUR_PX_MAP[ve.backdropBlur ?? 'medium'] ?? '8px';
227
+ rules.push(` --card-bg-opacity: ${opacity};`);
228
+ rules.push(` --card-backdrop-filter: blur(${blur});`);
229
+ rules.push(` --card-bg-opacity-non-glass: 1.0;`);
230
+ rules.push(` --card-backdrop-filter-non-glass: none;`);
231
+ }
232
+ /**
233
+ * CSS custom properties for component styles (emitted inside :root {})
234
+ */ function applyComponentStyleCSSVars(rules, config) {
235
+ // Brand gradient composite — always emitted so gradient buttons / cards / text
236
+ // can reference a single token. Falls back to primary→accent when the editor
237
+ // has not set explicit gradient stops.
238
+ rules.push(' --gradient-brand: linear-gradient(' + 'var(--gradient-angle, 135deg), ' + 'var(--gradient-from, var(--primary)), ' + 'var(--gradient-via, var(--gradient-from, var(--primary))), ' + 'var(--gradient-to, var(--accent)));');
239
+ const cs = getCS(config);
240
+ if (!cs) return;
241
+ if (cs.buttonVariant) rules.push(` --button-variant: ${cs.buttonVariant};`);
242
+ if (cs.buttonSize) rules.push(` --button-size: ${cs.buttonSize};`);
243
+ if (cs.cardStyle) rules.push(` --card-style: ${cs.cardStyle};`);
244
+ if (cs.cardHoverEffect) rules.push(` --card-hover-effect: ${cs.cardHoverEffect};`);
245
+ if (cs.imageStyle) rules.push(` --image-style: ${cs.imageStyle};`);
246
+ if (cs.navbarStyle) rules.push(` --navbar-style: ${cs.navbarStyle};`);
247
+ if (cs.footerStyle) rules.push(` --footer-style: ${cs.footerStyle};`);
248
+ if (cs.linkStyle) rules.push(` --link-style: ${cs.linkStyle};`);
249
+ if (cs.enableScrollReveal != null) {
250
+ rules.push(` --scroll-reveal: ${cs.enableScrollReveal ? '1' : '0'};`);
251
+ }
252
+ if (cs.enableHoverAnimations != null) {
253
+ rules.push(` --hover-animations: ${cs.enableHoverAnimations ? '1' : '0'};`);
254
+ }
255
+ }
256
+ /**
257
+ * Element-level CSS overrides based on effect + component styles.
258
+ * These rules are emitted OUTSIDE the :root {} block so they target
259
+ * actual DOM elements. They rely on data-effect-style / data-card-style
260
+ * attributes set on <html> by layout.tsx (SSR) and ThemeProvider (client).
261
+ */ function applyEffectElementRules(rules, config) {
262
+ const ve = getVE(config);
263
+ const cs = getCS(config);
264
+ if (!ve && !cs) return;
265
+ rules.push('');
266
+ rules.push('/* ── Effect element overrides ── */');
267
+ // ── Glass effect ──────────────────────────────────────────────────────
268
+ // NOTE: All effect-style blocks are ALWAYS emitted regardless of the saved
269
+ // effectStyle value. The CSS selectors are scoped by html[data-effect-style='X']
270
+ // so only the matching data-attribute activates the rules. This means the CSS
271
+ // is always correct even when stale cached/draft data is served.
272
+ {
273
+ const opacity = ve?.glassOpacity != null ? ve.glassOpacity : 60;
274
+ const blur = BLUR_PX_MAP[ve?.backdropBlur ?? 'strong'] ?? '16px';
275
+ rules.push(`html[data-effect-style='glass'] [data-card],`);
276
+ rules.push(`html[data-effect-style='glass'] .glass-card,`);
277
+ rules.push(`html[data-effect-style='glass'] .glass {`);
278
+ rules.push(` background-color: color-mix(in srgb, var(--card) ${opacity}%, transparent) !important;`);
279
+ rules.push(` backdrop-filter: blur(${blur}) saturate(150%) !important;`);
280
+ rules.push(` -webkit-backdrop-filter: blur(${blur}) saturate(150%) !important;`);
281
+ rules.push(` border-color: color-mix(in srgb, var(--border) 40%, transparent) !important;`);
282
+ rules.push(` box-shadow: 0 4px 24px rgba(0,0,0,0.08), inset 0 1px 0 rgba(255,255,255,0.1) !important;`);
283
+ rules.push(`}`);
284
+ rules.push(`html[data-effect-style='glass'] [data-card]:hover,`);
285
+ rules.push(`html[data-effect-style='glass'] .glass-card:hover,`);
286
+ rules.push(`html[data-effect-style='glass'] .glass:hover {`);
287
+ rules.push(` background-color: color-mix(in srgb, var(--card) ${Math.min(opacity + 10, 90)}%, transparent) !important;`);
288
+ rules.push(` box-shadow: 0 8px 32px rgba(0,0,0,0.12), inset 0 1px 0 rgba(255,255,255,0.15) !important;`);
289
+ rules.push(`}`);
290
+ // Apply the glass surface to the navbar container, but never to a <nav>
291
+ // nested inside a <header> (or <footer>) — otherwise the inner nav paints a
292
+ // second glass layer on top of the header's, producing a visible double-tint.
293
+ // A standalone top-level <nav> (header-less layouts) still gets the surface.
294
+ rules.push(`html[data-effect-style='glass'] header, html[data-effect-style='glass'] nav:not(header nav):not(footer nav) {`);
295
+ rules.push(` background-color: color-mix(in srgb, var(--background) 65%, transparent) !important;`);
296
+ rules.push(` backdrop-filter: blur(${blur}) saturate(150%) !important;`);
297
+ rules.push(` -webkit-backdrop-filter: blur(${blur}) saturate(150%) !important;`);
298
+ rules.push(`}`);
299
+ }
300
+ // ── Neumorphic effect ──────────────────────────────────────────────────
301
+ {
302
+ rules.push(`html[data-effect-style='neumorphic'] [data-card],`);
303
+ rules.push(`html[data-effect-style='neumorphic'] .glass-card,`);
304
+ rules.push(`html[data-effect-style='neumorphic'] .glass {`);
305
+ rules.push(` background-color: var(--background) !important;`);
306
+ rules.push(` border: none !important;`);
307
+ rules.push(` backdrop-filter: none !important;`);
308
+ rules.push(` -webkit-backdrop-filter: none !important;`);
309
+ rules.push(` box-shadow: 6px 6px 12px rgba(0,0,0,0.12), -6px -6px 12px rgba(255,255,255,0.75) !important;`);
310
+ rules.push(`}`);
311
+ rules.push(`html[data-effect-style='neumorphic'] [data-card]:hover,`);
312
+ rules.push(`html[data-effect-style='neumorphic'] .glass-card:hover,`);
313
+ rules.push(`html[data-effect-style='neumorphic'] .glass:hover {`);
314
+ rules.push(` box-shadow: 8px 8px 16px rgba(0,0,0,0.15), -8px -8px 16px rgba(255,255,255,0.85) !important;`);
315
+ rules.push(`}`);
316
+ rules.push(`html[data-effect-style='neumorphic'][data-theme-mode='dark'] [data-card],`);
317
+ rules.push(`html[data-effect-style='neumorphic'].dark [data-card],`);
318
+ rules.push(`html[data-effect-style='neumorphic'][data-theme-mode='dark'] .glass-card,`);
319
+ rules.push(`html[data-effect-style='neumorphic'].dark .glass-card {`);
320
+ rules.push(` box-shadow: 6px 6px 12px rgba(0,0,0,0.4), -6px -6px 12px rgba(255,255,255,0.04) !important;`);
321
+ rules.push(`}`);
322
+ }
323
+ // ── Clay effect ────────────────────────────────────────────────────────
324
+ {
325
+ rules.push(`html[data-effect-style='clay'] [data-card],`);
326
+ rules.push(`html[data-effect-style='clay'] .glass-card,`);
327
+ rules.push(`html[data-effect-style='clay'] .glass {`);
328
+ rules.push(` border-radius: var(--radius-large, 1.5rem) !important;`);
329
+ rules.push(` box-shadow: 4px 4px 0px rgba(0,0,0,0.22), 0 8px 20px rgba(0,0,0,0.08) !important;`);
330
+ rules.push(` border: none !important;`);
331
+ rules.push(` backdrop-filter: none !important;`);
332
+ rules.push(` -webkit-backdrop-filter: none !important;`);
333
+ rules.push(` transition: transform 200ms ease, box-shadow 200ms ease !important;`);
334
+ rules.push(`}`);
335
+ rules.push(`html[data-effect-style='clay'] [data-card]:hover,`);
336
+ rules.push(`html[data-effect-style='clay'] .glass-card:hover,`);
337
+ rules.push(`html[data-effect-style='clay'] .glass:hover {`);
338
+ rules.push(` transform: translateY(-3px) !important;`);
339
+ rules.push(` box-shadow: 6px 6px 0px rgba(0,0,0,0.25), 0 12px 24px rgba(0,0,0,0.12) !important;`);
340
+ rules.push(`}`);
341
+ }
342
+ // ── Elevated effect ────────────────────────────────────────────────────
343
+ {
344
+ const shadow = ve?.shadowIntensity ? SHADOW_MAP[ve.shadowIntensity] ?? SHADOW_MAP.medium : SHADOW_MAP.medium;
345
+ rules.push(`html[data-effect-style='elevated'] [data-card],`);
346
+ rules.push(`html[data-effect-style='elevated'] .glass-card,`);
347
+ rules.push(`html[data-effect-style='elevated'] .glass {`);
348
+ rules.push(` box-shadow: ${shadow} !important;`);
349
+ rules.push(` border: none !important;`);
350
+ rules.push(` backdrop-filter: none !important;`);
351
+ rules.push(` -webkit-backdrop-filter: none !important;`);
352
+ rules.push(` transition: box-shadow 200ms ease, transform 200ms ease !important;`);
353
+ rules.push(`}`);
354
+ rules.push(`html[data-effect-style='elevated'] [data-card]:hover,`);
355
+ rules.push(`html[data-effect-style='elevated'] .glass-card:hover,`);
356
+ rules.push(`html[data-effect-style='elevated'] .glass:hover {`);
357
+ rules.push(` box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.1) !important;`);
358
+ rules.push(` transform: translateY(-2px) !important;`);
359
+ rules.push(`}`);
360
+ }
361
+ // ── Flat effect ────────────────────────────────────────────────────────
362
+ {
363
+ rules.push(`html[data-effect-style='flat'] [data-card],`);
364
+ rules.push(`html[data-effect-style='flat'] .glass-card,`);
365
+ rules.push(`html[data-effect-style='flat'] .glass {`);
366
+ rules.push(` box-shadow: none !important;`);
367
+ rules.push(` backdrop-filter: none !important;`);
368
+ rules.push(` -webkit-backdrop-filter: none !important;`);
369
+ rules.push(`}`);
370
+ }
371
+ // ── Border overrides ───────────────────────────────────────────────────
372
+ if (ve?.borderWidth && ve.borderWidth !== '1px' && ve.borderWidth !== '0px') {
373
+ rules.push(`html[data-border-width='${ve.borderWidth}'] [data-card],`);
374
+ rules.push(`html[data-border-width='${ve.borderWidth}'] .glass-card {`);
375
+ rules.push(` border-width: ${ve.borderWidth} !important;`);
376
+ rules.push(`}`);
377
+ }
378
+ // ── Card style: bordered (used by brutalism etc.) ──────────────────────
379
+ if (cs?.cardStyle === 'bordered') {
380
+ const bw = ve?.borderWidth ?? '2px';
381
+ rules.push(`html[data-card-style='bordered'] [data-card],`);
382
+ rules.push(`html[data-card-style='bordered'] .glass-card {`);
383
+ rules.push(` border-width: ${bw} !important;`);
384
+ rules.push(` border-style: solid !important;`);
385
+ rules.push(` border-color: var(--foreground) !important;`);
386
+ rules.push(` box-shadow: 4px 4px 0 var(--foreground) !important;`);
387
+ rules.push(` border-radius: 0 !important;`);
388
+ rules.push(` background-color: var(--card) !important;`);
389
+ rules.push(` backdrop-filter: none !important;`);
390
+ rules.push(` -webkit-backdrop-filter: none !important;`);
391
+ rules.push(`}`);
392
+ rules.push(`html[data-card-style='bordered'] [data-card]:hover,`);
393
+ rules.push(`html[data-card-style='bordered'] .glass-card:hover {`);
394
+ rules.push(` box-shadow: 6px 6px 0 var(--foreground) !important;`);
395
+ rules.push(` transform: translate(-2px, -2px) !important;`);
396
+ rules.push(`}`);
397
+ }
398
+ // ── Card style: gradient-border ────────────────────────────────────────
399
+ if (cs?.cardStyle === 'gradient-border') {
400
+ rules.push(`html[data-card-style='gradient-border'] [data-card],`);
401
+ rules.push(`html[data-card-style='gradient-border'] .glass-card {`);
402
+ rules.push(` border-image: var(--gradient-brand) 1 !important;`);
403
+ rules.push(` border-width: 2px !important;`);
404
+ rules.push(` border-style: solid !important;`);
405
+ rules.push(`}`);
406
+ }
407
+ // ── Navbar styles ──────────────────────────────────────────────────────
408
+ const navbarStyle = cs?.navbarStyle;
409
+ if (navbarStyle === 'blur') {
410
+ rules.push(`html[data-navbar-style='blur'] header {`);
411
+ rules.push(` background-color: color-mix(in srgb, var(--background) 70%, transparent) !important;`);
412
+ rules.push(` backdrop-filter: blur(12px) !important;`);
413
+ rules.push(` -webkit-backdrop-filter: blur(12px) !important;`);
414
+ rules.push(`}`);
415
+ } else if (navbarStyle === 'transparent') {
416
+ rules.push(`html[data-navbar-style='transparent'] header {`);
417
+ rules.push(` background-color: transparent !important;`);
418
+ rules.push(` border-bottom-color: transparent !important;`);
419
+ rules.push(`}`);
420
+ } else if (navbarStyle === 'floating') {
421
+ rules.push(`html[data-navbar-style='floating'] header {`);
422
+ rules.push(` margin: 12px 16px 0;`);
423
+ rules.push(` border-radius: var(--radius-large, 1.5rem);`);
424
+ rules.push(` border: 1px solid var(--border);`);
425
+ rules.push(` box-shadow: 0 4px 24px rgba(0,0,0,0.08);`);
426
+ rules.push(`}`);
427
+ } else if (navbarStyle === 'minimal') {
428
+ rules.push(`html[data-navbar-style='minimal'] header {`);
429
+ rules.push(` border-bottom-color: transparent !important;`);
430
+ rules.push(` box-shadow: none !important;`);
431
+ rules.push(`}`);
432
+ }
433
+ // ── Button variant: brutal ─────────────────────────────────────────────
434
+ if (cs?.buttonVariant === 'brutal') {
435
+ rules.push(`html[data-button-variant='brutal'] [data-btn] {`);
436
+ rules.push(` border: 2px solid var(--foreground) !important;`);
437
+ rules.push(` border-radius: 0 !important;`);
438
+ rules.push(` box-shadow: 3px 3px 0 var(--foreground) !important;`);
439
+ rules.push(` transition: box-shadow 100ms ease, transform 100ms ease !important;`);
440
+ rules.push(`}`);
441
+ rules.push(`html[data-button-variant='brutal'] [data-btn]:hover {`);
442
+ rules.push(` box-shadow: 5px 5px 0 var(--foreground) !important;`);
443
+ rules.push(` transform: translate(-2px, -2px) !important;`);
444
+ rules.push(`}`);
445
+ rules.push(`html[data-button-variant='brutal'] [data-btn]:active {`);
446
+ rules.push(` box-shadow: 1px 1px 0 var(--foreground) !important;`);
447
+ rules.push(` transform: translate(2px, 2px) !important;`);
448
+ rules.push(`}`);
449
+ }
450
+ // ── Button variant: pill ───────────────────────────────────────────────
451
+ if (cs?.buttonVariant === 'pill') {
452
+ rules.push(`html[data-button-variant='pill'] [data-btn] {`);
453
+ rules.push(` border-radius: 9999px !important;`);
454
+ rules.push(`}`);
455
+ }
456
+ // ── Button variant: ghost ──────────────────────────────────────────────
457
+ if (cs?.buttonVariant === 'outlined') {
458
+ rules.push(`html[data-button-variant='outlined'] [data-btn] {`);
459
+ rules.push(` background-color: transparent !important;`);
460
+ rules.push(` border: 1px solid var(--border) !important;`);
461
+ rules.push(` box-shadow: none !important;`);
462
+ rules.push(`}`);
463
+ }
464
+ // ── Card hover effects ─────────────────────────────────────────────────
465
+ if (cs?.cardHoverEffect === 'lift') {
466
+ rules.push(`html[data-card-hover='lift'] [data-card]:hover,`);
467
+ rules.push(`html[data-card-hover='lift'] .glass-card:hover {`);
468
+ rules.push(` transform: translateY(-6px) !important;`);
469
+ rules.push(` box-shadow: 0 20px 40px rgba(0,0,0,0.12) !important;`);
470
+ rules.push(`}`);
471
+ } else if (cs?.cardHoverEffect === 'scale') {
472
+ rules.push(`html[data-card-hover='scale'] [data-card]:hover,`);
473
+ rules.push(`html[data-card-hover='scale'] .glass-card:hover {`);
474
+ rules.push(` transform: scale(1.02) !important;`);
475
+ rules.push(`}`);
476
+ } else if (cs?.cardHoverEffect === 'glow') {
477
+ rules.push(`html[data-card-hover='glow'] [data-card]:hover,`);
478
+ rules.push(`html[data-card-hover='glow'] .glass-card:hover {`);
479
+ rules.push(` box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 25%, transparent), 0 8px 32px rgba(0,0,0,0.1) !important;`);
480
+ rules.push(`}`);
481
+ } else if (cs?.cardHoverEffect === 'tilt') {
482
+ rules.push(`html[data-card-hover='tilt'] [data-card],`);
483
+ rules.push(`html[data-card-hover='tilt'] .glass-card {`);
484
+ rules.push(` transition: transform 200ms ease !important;`);
485
+ rules.push(`}`);
486
+ rules.push(`html[data-card-hover='tilt'] [data-card]:hover,`);
487
+ rules.push(`html[data-card-hover='tilt'] .glass-card:hover {`);
488
+ rules.push(` transform: perspective(1000px) rotateX(2deg) rotateY(-2deg) !important;`);
489
+ rules.push(`}`);
490
+ }
491
+ // ── Button variant: gradient ───────────────────────────────────────────
492
+ if (cs?.buttonVariant === 'gradient') {
493
+ rules.push(`html[data-button-variant='gradient'] [data-btn] {`);
494
+ rules.push(` background-image: var(--gradient-brand) !important;`);
495
+ rules.push(` background-color: transparent !important;`);
496
+ rules.push(` color: var(--primary-foreground) !important;`);
497
+ rules.push(` border: none !important;`);
498
+ rules.push(`}`);
499
+ }
500
+ // ── Button sizes ────────────────────────────────────────────────────────
501
+ if (cs?.buttonSize) {
502
+ const sizeMap = {
503
+ small: {
504
+ py: '0.375rem',
505
+ px: '0.75rem',
506
+ fs: '0.8125rem'
507
+ },
508
+ medium: {
509
+ py: '0.5rem',
510
+ px: '1rem',
511
+ fs: '0.875rem'
512
+ },
513
+ large: {
514
+ py: '0.625rem',
515
+ px: '1.5rem',
516
+ fs: '1rem'
517
+ },
518
+ xl: {
519
+ py: '0.875rem',
520
+ px: '2rem',
521
+ fs: '1.125rem'
522
+ }
523
+ };
524
+ const s = sizeMap[cs.buttonSize] ?? sizeMap.medium;
525
+ rules.push(`html[data-button-size='${cs.buttonSize}'] [data-btn] {`);
526
+ rules.push(` padding: ${s.py} ${s.px} !important;`);
527
+ rules.push(` font-size: ${s.fs} !important;`);
528
+ rules.push(`}`);
529
+ }
530
+ // ── Image styles ───────────────────────────────────────────────────────
531
+ // Target both [data-img] attributes and the .themed-image helper class.
532
+ if (cs?.imageStyle && cs.imageStyle !== 'default') {
533
+ const style = cs.imageStyle;
534
+ const sel = `html[data-image-style='${style}'] [data-img], html[data-image-style='${style}'] .themed-image`;
535
+ const hoverSel = `html[data-image-style='${style}'] [data-img]:hover, html[data-image-style='${style}'] .themed-image:hover`;
536
+ if (style === 'rounded') {
537
+ rules.push(`${sel} { border-radius: var(--radius-large, 1rem) !important; overflow: hidden; }`);
538
+ } else if (style === 'circle') {
539
+ rules.push(`${sel} { border-radius: 9999px !important; aspect-ratio: 1 / 1; object-fit: cover; }`);
540
+ } else if (style === 'grayscale') {
541
+ rules.push(`${sel} { filter: grayscale(1); transition: filter 300ms ease; }`);
542
+ rules.push(`${hoverSel} { filter: grayscale(0); }`);
543
+ } else if (style === 'duotone') {
544
+ rules.push(`${sel} { filter: grayscale(1) contrast(1.05); mix-blend-mode: luminosity; }`);
545
+ } else if (style === 'vignette') {
546
+ rules.push(`${sel} { box-shadow: inset 0 0 60px rgba(0,0,0,0.45); }`);
547
+ } else if (style === 'polaroid') {
548
+ rules.push(`${sel} { background: #fff; padding: 10px 10px 36px; box-shadow: 0 6px 16px rgba(0,0,0,0.18); border-radius: 2px; }`);
549
+ }
550
+ }
551
+ // ── Link styles ─────────────────────────────────────────────────────────
552
+ if (cs?.linkStyle) {
553
+ const style = cs.linkStyle;
554
+ const sel = `html[data-link-style='${style}'] [data-link], html[data-link-style='${style}'] .themed-link`;
555
+ const hoverSel = `html[data-link-style='${style}'] [data-link]:hover, html[data-link-style='${style}'] .themed-link:hover`;
556
+ if (style === 'underline') {
557
+ rules.push(`${sel} { text-decoration: underline; text-underline-offset: 0.15em; }`);
558
+ } else if (style === 'none') {
559
+ rules.push(`${sel} { text-decoration: none; }`);
560
+ } else if (style === 'underline-hover') {
561
+ rules.push(`${sel} { text-decoration: none; }`);
562
+ rules.push(`${hoverSel} { text-decoration: underline; text-underline-offset: 0.15em; }`);
563
+ } else if (style === 'highlight') {
564
+ rules.push(`${sel} { text-decoration: none; background-image: linear-gradient(transparent 60%, color-mix(in srgb, var(--primary) 28%, transparent) 0); }`);
565
+ } else if (style === 'animated') {
566
+ rules.push(`${sel} { text-decoration: none; background-image: var(--gradient-brand); background-size: 0% 2px; background-position: 0 100%; background-repeat: no-repeat; transition: background-size 300ms ease; }`);
567
+ rules.push(`${hoverSel} { background-size: 100% 2px; }`);
568
+ }
569
+ }
570
+ // ── Footer styles ─────────────────────────────────────────────────────────
571
+ if (cs?.footerStyle === 'dark') {
572
+ rules.push(`html[data-footer-style='dark'] footer { background-color: var(--foreground) !important; color: var(--background) !important; }`);
573
+ } else if (cs?.footerStyle === 'full-color') {
574
+ rules.push(`html[data-footer-style='full-color'] footer { background-color: var(--primary) !important; color: var(--primary-foreground) !important; }`);
575
+ } else if (cs?.footerStyle === 'gradient-top') {
576
+ rules.push(`html[data-footer-style='gradient-top'] footer { border-top: 3px solid transparent; border-image: var(--gradient-brand) 1; }`);
577
+ } else if (cs?.footerStyle === 'minimal') {
578
+ rules.push(`html[data-footer-style='minimal'] footer { background: transparent !important; border-top: 1px solid var(--border) !important; box-shadow: none !important; }`);
579
+ }
580
+ // ── Hover animations off — neutralise interactive transforms ──────────────
581
+ if (cs?.enableHoverAnimations === false) {
582
+ rules.push(`html[data-hover-animations='off'] [data-card]:hover, html[data-hover-animations='off'] .glass-card:hover, html[data-hover-animations='off'] [data-btn]:hover { transform: none !important; }`);
583
+ }
584
+ }
175
585
  function applyDarkModeOverrides(rules, config, customization, selector) {
176
586
  if (config.colorMode !== 'dark' && config.colorMode !== 'auto') return;
177
587
  rules.push('');