@howssatoshi/quantumcss 1.6.1 → 1.7.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.
package/src/defaults.js CHANGED
@@ -17,6 +17,21 @@ const defaultTheme = {
17
17
  100: '#d1fae5',
18
18
  500: '#10b981'
19
19
  },
20
+ purple: {
21
+ 50: '#faf5ff', 100: '#f3e8ff', 200: '#e9d5ff', 300: '#d8b4fe', 400: '#c084fc', 500: '#a855f7', 600: '#9333ea', 700: '#7e22ce'
22
+ },
23
+ yellow: {
24
+ 50: '#fefce8', 100: '#fef9c3', 200: '#fef08a', 300: '#fde047', 400: '#facc15', 500: '#eab308', 600: '#ca8a04', 700: '#a16207'
25
+ },
26
+ pink: {
27
+ 50: '#fdf2f8', 100: '#fce7f3', 200: '#fbcfe8', 300: '#f9a8d4', 400: '#f472b6', 500: '#ec4899', 600: '#db2777', 700: '#be185d'
28
+ },
29
+ cyan: {
30
+ 50: '#ecfeff', 100: '#cffafe', 200: '#a5f3fc', 300: '#67e8f9', 400: '#22d3ee', 500: '#06b6d4', 600: '#0891b2', 700: '#0e7490'
31
+ },
32
+ indigo: {
33
+ 50: '#eef2ff', 100: '#e0e7ff', 200: '#c7d2fe', 300: '#a5b4fc', 400: '#818cf8', 500: '#6366f1', 600: '#4f46e5', 700: '#4338ca'
34
+ },
20
35
  starlight: {
21
36
  blue: '#00d4ff', peach: '#ffb38a', orange: '#ff7e5f', deep: '#08081a',
22
37
  },
@@ -65,6 +80,7 @@ const utilityMaps = {
65
80
  'min-w-0': { property: 'min-width', value: '0' },
66
81
  'overflow-hidden': { property: 'overflow', value: 'hidden' },
67
82
  'overflow-visible': { property: 'overflow', value: 'visible' },
83
+ 'bg-clip-text': { property: ['background-clip', '-webkit-background-clip'], value: ['text', 'text'] },
68
84
  'border-none': { property: 'border-width', value: '0' },
69
85
  'bg-transparent': { property: 'background-color', value: 'transparent' },
70
86
  'relative': { property: 'position', value: 'relative' },
@@ -77,6 +93,49 @@ const utilityMaps = {
77
93
  'w-5': { property: 'width', value: '1.25rem' },
78
94
  'h-5': { property: 'height', value: '1.25rem' },
79
95
  'z-10': { property: 'z-index', value: '10' },
96
+ 'svg-fluid': {
97
+ property: ['width', 'height', 'max-width', 'display'],
98
+ value: ['100%', 'auto', '100%', 'block']
99
+ },
100
+ 'svg-contain': { property: 'object-fit', value: 'contain' },
101
+ 'svg-cover': { property: 'object-fit', value: 'cover' },
102
+ 'vector-non-scaling': { property: 'vector-effect', value: 'non-scaling-stroke' },
103
+ 'overlay-base': {
104
+ property: ['position', 'top', 'left', 'transform'],
105
+ value: ['absolute', '50%', '50%', 'translate(-50%, -50%)']
106
+ },
107
+ 'overlay-top-left': { property: ['position', 'top', 'left'], value: ['absolute', '0', '0'] },
108
+ 'overlay-top-right': { property: ['position', 'top', 'right'], value: ['absolute', '0', '0'] },
109
+ 'overlay-bottom-left': { property: ['position', 'bottom', 'left'], value: ['absolute', '0', '0'] },
110
+ 'overlay-bottom-right': { property: ['position', 'bottom', 'right'], value: ['absolute', '0', '0'] },
111
+ 'nav-base': {
112
+ property: ['display', 'flex-direction', 'align-items', 'width', 'position', 'top', 'z-index'],
113
+ value: ['flex', 'row', 'center', '100%', 'sticky', '0', '1000']
114
+ },
115
+ 'card-base': {
116
+ property: ['display', 'flex-direction', 'align-items', 'padding', 'overflow', 'border-radius', 'transition', 'position'],
117
+ value: ['flex', 'column', 'stretch', 'var(--q-space-10)', 'hidden', 'var(--q-radius-lg)', 'all 0.3s ease', 'relative']
118
+ },
119
+ 'btn-base': {
120
+ property: ['display', 'align-items', 'justify-content', 'cursor', 'transition', 'font-weight', 'border-radius', 'border'],
121
+ value: ['inline-flex', 'center', 'center', 'pointer', 'all 0.2s ease', '600', 'var(--q-radius-md)', 'none']
122
+ },
123
+ 'input-base': {
124
+ property: ['display', 'width', 'appearance', 'transition', 'border-radius', 'border'],
125
+ value: ['block', '100%', 'none', 'all 0.2s ease', 'var(--q-radius-md)', 'none']
126
+ },
127
+ 'dialog-base': {
128
+ property: ['display', 'flex-direction', 'align-items', 'padding', 'overflow-y', 'border-radius', 'position'],
129
+ value: ['flex', 'column', 'stretch', 'var(--q-space-10)', 'auto', 'var(--q-radius-xl)', 'relative']
130
+ },
131
+ 'modal-fixed': {
132
+ property: ['position', 'top', 'left', 'transform', 'max-width', 'max-height', 'z-index'],
133
+ value: ['fixed', '50%', '50%', 'translate(-50%, -50%)', '90vw', '90vh', '2000']
134
+ },
135
+ 'badge-base': {
136
+ property: ['display', 'align-items', 'justify-content', 'font-size', 'font-weight', 'text-transform', 'letter-spacing', 'border-radius', 'border'],
137
+ value: ['inline-flex', 'center', 'center', '0.75rem', '600', 'uppercase', '0.05em', 'var(--q-radius-sm)', 'none']
138
+ },
80
139
  'form-row': {
81
140
  property: ['display', 'justify-content', 'align-items', 'gap'],
82
141
  value: ['flex', 'space-between', 'center', '1rem']
@@ -89,45 +148,43 @@ const utilityMaps = {
89
148
  property: ['background-color', 'backdrop-filter', '-webkit-backdrop-filter', 'border-width', 'border-color'],
90
149
  value: ['rgba(255, 255, 255, 0.03)', 'blur(16px)', 'blur(16px)', '1px', 'rgba(255, 255, 255, 0.1)']
91
150
  },
92
- 'glow-blue': { property: 'box-shadow', value: '0 0 30px rgba(0, 212, 255, 0.25)' },
93
- 'bg-starlight': { property: 'background', value: 'linear-gradient(135deg, #ffb38a 0%, #00d4ff 100%)' },
151
+ 'bg-starlight': { property: 'background', value: 'linear-gradient(135deg, var(--q-color-starlight-peach) 0%, var(--q-color-starlight-blue) 100%)' },
94
152
  'text-gradient-starlight': {
95
153
  property: ['background', '-webkit-background-clip', '-webkit-text-fill-color', 'display'],
96
- value: ['linear-gradient(to right, #ffb38a, #00d4ff)', 'text', 'transparent', 'inline-block']
154
+ value: ['linear-gradient(to right, var(--q-color-starlight-peach), var(--q-color-starlight-blue))', 'text', 'transparent', 'inline-block']
97
155
  },
98
- 'btn-starlight': {
99
- property: ['background', 'color', 'border', 'box-shadow', 'font-weight', 'transition', 'height', 'padding', 'display', 'align-items', 'justify-content', 'border-radius', 'cursor'],
100
- value: ['linear-gradient(135deg, #ffb38a 0%, #00d4ff 100%)', '#000', 'none', '0 0 20px rgba(0, 212, 255, 0.3)', '700', 'all 0.2s ease', '3rem', '0 1.5rem', 'inline-flex', 'center', 'center', '0.5rem', 'pointer']
156
+ 'theme-starlight': {
157
+ property: ['background', 'color', 'border-color', 'box-shadow'],
158
+ value: ['linear-gradient(135deg, var(--q-color-starlight-peach) 0%, var(--q-color-starlight-blue) 100%)', '#000', 'transparent', '0 0 30px rgba(0, 212, 255, 0.2)']
101
159
  },
102
- 'btn-secondary': {
103
- property: ['background', 'color', 'border', 'font-weight', 'transition', 'height', 'padding', 'display', 'align-items', 'justify-content', 'border-radius', 'cursor', 'backdrop-filter', '-webkit-backdrop-filter'],
104
- value: ['rgba(255, 255, 255, 0.05)', '#ffffff', '1px solid rgba(255, 255, 255, 0.15)', '700', 'all 0.2s ease', '3rem', '0 1.5rem', 'inline-flex', 'center', 'center', '0.5rem', 'pointer', 'blur(12px)', 'blur(12px)']
160
+ 'theme-glass': {
161
+ property: ['background-color', 'backdrop-filter', '-webkit-backdrop-filter', 'border-color', 'border-width'],
162
+ value: ['rgba(255, 255, 255, 0.05)', 'blur(16px)', 'blur(16px)', 'rgba(255, 255, 255, 0.1)', '1px']
105
163
  },
106
- 'input-starlight': {
107
- property: ['background-color', 'border', 'color', 'border-radius', 'padding', 'appearance', 'transition', 'height'],
108
- value: ['rgba(255, 255, 255, 0.04)', '1px solid rgba(255, 255, 255, 0.15)', 'inherit', '0.75rem', '0 1rem', 'none', 'all 0.2s ease', '3rem']
164
+ 'theme-glass-dark': {
165
+ property: ['background-color', 'backdrop-filter', '-webkit-backdrop-filter', 'border-color', 'border-width'],
166
+ value: ['rgba(255, 255, 255, 0.05)', 'blur(24px)', 'blur(24px)', 'rgba(255, 255, 255, 0.1)', '1px']
109
167
  },
110
- 'textarea-starlight': {
111
- property: ['background-color', 'border', 'color', 'border-radius', 'padding', 'appearance', 'transition', 'min-height', 'width', 'display'],
112
- value: ['rgba(255, 255, 255, 0.04)', '1px solid rgba(255, 255, 255, 0.15)', 'inherit', '0.75rem', '1rem', 'none', 'all 0.2s ease', '8rem', '100%', 'block']
113
- },
114
- 'checkbox-starlight': {
115
- property: ['appearance', 'width', 'height', 'background', 'border', 'border-radius', 'cursor', 'transition', 'position', 'display', 'align-items', 'justify-content'],
116
- value: ['none', '1.25rem', '1.25rem', 'rgba(255, 255, 255, 0.05)', '1px solid rgba(255, 255, 255, 0.2)', '0.375rem', 'pointer', 'all 0.2s ease', 'relative', 'inline-flex', 'center', 'center']
117
- },
118
- 'search-container': 'relative block w-full',
168
+ 'glow-blue': { property: 'box-shadow', value: '0 0 30px rgba(0, 212, 255, 0.25)' },
169
+ 'glow-starlight': { property: 'box-shadow', value: '0 0 30px rgba(0, 212, 255, 0.25)' },
170
+ 'glow-peach': { property: 'box-shadow', value: '0 0 30px rgba(255, 179, 138, 0.25)' },
171
+ 'glow-orange': { property: 'box-shadow', value: '0 0 30px rgba(255, 126, 95, 0.25)' },
172
+ 'btn-starlight': 'btn-base theme-starlight h-12 px-6',
173
+ 'btn-secondary': 'btn-base theme-glass-dark h-12 px-6 border-white/15',
174
+ 'input-starlight': 'input-base theme-glass-dark h-12 px-4 border-white/15',
175
+ 'textarea-starlight': 'input-base theme-glass-dark p-4 min-h-32 border-white/15',
176
+ 'checkbox-starlight': 'btn-base theme-glass-dark w-5 h-5 border-white/20',
177
+ 'search-container': 'relative block w-full',
119
178
  'search-input': 'pl-12 w-full',
120
179
  'search-icon': 'absolute left-4 top-1/2 -translate-y-1/2 pointer-events-none z-10 w-5 h-5',
121
- 'nav-glass': {
122
- property: ['background', 'backdrop-filter', '-webkit-backdrop-filter', 'border-bottom', 'width', 'display', 'flex-direction', 'padding', 'position', 'top', 'z-index'],
123
- value: ['rgba(255, 255, 255, 0.05)', 'blur(24px)', 'blur(24px)', '1px solid rgba(255, 255, 255, 0.1)', '100%', 'flex', 'column', '0', 'sticky', '0', '1000']
124
- },
125
- 'starlight-nav': 'nav-glass w-full sticky top-0 z-[1000]',
126
- 'starlight-search': 'search-container w-full max-w-144',
127
- 'starlight-dashboard': 'dashboard-grid grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8',
128
- 'starlight-gallery': 'starlight-gallery grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4',
129
- 'starlight-form': 'starlight-card grid grid-cols-1 md:grid-cols-2 gap-8',
130
- 'starlight-dialog': 'starlight-dialog ani-scale-in'
180
+ 'nav-glass': 'nav-base theme-glass-dark',
181
+ 'starlight-card': 'card-base theme-glass-dark',
182
+ 'starlight-nav': 'nav-base theme-glass-dark px-4 md:px-8',
183
+ 'starlight-search': 'search-container theme-glass-dark rounded-xl',
184
+ 'starlight-dashboard': 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8',
185
+ 'starlight-gallery': 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4',
186
+ 'starlight-form': 'starlight-card grid grid-cols-1 md:grid-cols-2 gap-8 items-start',
187
+ 'starlight-dialog': 'dialog-base modal-fixed theme-glass-dark ani-scale-in'
131
188
  };
132
189
 
133
190
  module.exports = { defaultTheme, utilityMaps };
package/src/generator.js CHANGED
@@ -51,6 +51,22 @@ function generateCSS(configPath) {
51
51
  return null;
52
52
  }
53
53
 
54
+ const getRGBA = (color) => {
55
+ if (!color || color === 'transparent') return 'rgba(0,0,0,0)';
56
+ if (color.startsWith('rgba')) return color;
57
+ if (color.startsWith('#')) {
58
+ const hex = color.length === 4 ? `#${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}` : color;
59
+ const r = parseInt(hex.slice(1, 3), 16), g = parseInt(hex.slice(3, 5), 16), b = parseInt(hex.slice(5, 7), 16);
60
+ return `rgba(${r}, ${g}, ${b}, 1)`;
61
+ }
62
+ return color;
63
+ };
64
+
65
+ const withOpacity = (rgba, alpha) => {
66
+ if (!rgba.startsWith('rgba')) return rgba;
67
+ return rgba.replace(/[\d.]+\)$/, `${alpha})`);
68
+ };
69
+
54
70
  const files = (config.content || []).flatMap(p => glob.sync(p));
55
71
  const rawClasses = new Set();
56
72
  const classAttrRegex = /class="([^"]+)"/g;
@@ -150,8 +166,42 @@ function generateCSS(configPath) {
150
166
  if (prefix === 'text') {
151
167
  if (theme.fontSize[valKey]) { property = ['font-size', 'line-height']; value = [theme.fontSize[valKey], (valKey.includes('xl') || parseInt(valKey) >= 3) ? '1.2' : '1.5']; }
152
168
  else { const color = resolveColor(valKey); if (color) { property = 'color'; value = color; } }
153
- } else if (prefix === 'bg') { const color = resolveColor(valKey); if (color) { property = 'background-color'; value = color; } }
154
- else if (prefix === 'z') {
169
+ } else if (prefix === 'bg') {
170
+ if (cParts[1] === 'gradient' && cParts[2] === 'to') {
171
+ const dirMap = { r: 'to right', l: 'to left', t: 'to top', b: 'to bottom', tr: 'to top right', tl: 'to top left', br: 'to bottom right', bl: 'to bottom left' };
172
+ const dir = dirMap[cParts[3]];
173
+ if (dir) {
174
+ property = 'background-image';
175
+ value = `linear-gradient(${dir}, var(--q-gradient-stops))`;
176
+ const rules = [
177
+ ' --q-gradient-from-transparent: rgba(0,0,0,0);',
178
+ ' --q-gradient-to-transparent: rgba(0,0,0,0);',
179
+ ` ${property}: ${value};`,
180
+ ' --q-gradient-stops: var(--q-gradient-from, var(--q-gradient-from-transparent)), var(--q-gradient-to, var(--q-gradient-to-transparent));'
181
+ ];
182
+ return [{ breakpoint: null, variant: null, customSelector: null, rules }];
183
+ }
184
+ }
185
+ const color = resolveColor(valKey);
186
+ if (color) { property = 'background-color'; value = color; }
187
+ } else if (prefix === 'from') {
188
+ const color = resolveColor(valKey);
189
+ if (color) {
190
+ const rgba = getRGBA(color);
191
+ property = ['--q-gradient-from', '--q-gradient-to', '--q-gradient-from-transparent', '--q-gradient-stops'];
192
+ value = [rgba, withOpacity(rgba, 0), withOpacity(rgba, 0), 'var(--q-gradient-from), var(--q-gradient-to)'];
193
+ }
194
+ } else if (prefix === 'via') {
195
+ const color = resolveColor(valKey);
196
+ if (color) {
197
+ const rgba = getRGBA(color);
198
+ property = ['--q-gradient-to', '--q-gradient-to-transparent', '--q-gradient-stops'];
199
+ value = [withOpacity(rgba, 0), withOpacity(rgba, 0), `var(--q-gradient-from), ${rgba}, var(--q-gradient-to)`];
200
+ }
201
+ } else if (prefix === 'to') {
202
+ const color = resolveColor(valKey);
203
+ if (color) { property = '--q-gradient-to'; value = getRGBA(color); }
204
+ } else if (prefix === 'z') {
155
205
  if (valKey.startsWith('[') && valKey.endsWith(']')) value = valKey.slice(1, -1);
156
206
  else value = isNeg ? `-${valKey}` : valKey;
157
207
  property = 'z-index';
@@ -259,11 +309,23 @@ function generateCSS(configPath) {
259
309
  if (variant) { if (variant === 'group-hover') selector = `.group:hover ${selector}`; else selector += `:${variant}`}
260
310
 
261
311
  if (breakpoint === 'light') {
262
- const block = `body.light-mode ${selector} {
312
+ const block = `html[data-theme="light"] ${selector}, body.light-mode ${selector} {
313
+ ${rules.join('\n')}
314
+ }
315
+ `;
316
+ utilities.add(block);
317
+ } else if (breakpoint === 'dark') {
318
+ const block = `html[data-theme="dark"] ${selector}, body.dark-mode ${selector} {
263
319
  ${rules.join('\n')}
264
320
  }
265
321
  `;
266
322
  utilities.add(block);
323
+ // Also add to media query for system preference
324
+ const mediaBlock = `${selector} {
325
+ ${rules.join('\n')}
326
+ }
327
+ `;
328
+ responsiveUtils['dark'].add(mediaBlock);
267
329
  } else {
268
330
  const block = `${selector} {
269
331
  ${rules.join('\n')}
@@ -275,7 +337,39 @@ ${rules.join('\n')}
275
337
  }
276
338
 
277
339
  rawClasses.forEach(processClass);
278
- let css = '/* Quantum CSS JIT Output */\n' + Array.from(utilities).join('\n');
340
+
341
+ let rootVariables = ':root {\n';
342
+
343
+ // Recursively flatten theme for variables
344
+ const generateVars = (obj, prefix = '') => {
345
+ Object.entries(obj).forEach(([key, value]) => {
346
+ if (typeof value === 'string') {
347
+ rootVariables += ` --q-${prefix}${key}: ${value};\n`;
348
+ } else if (typeof value === 'object' && value !== null) {
349
+ generateVars(value, `${prefix}${key}-`);
350
+ }
351
+ });
352
+ };
353
+
354
+ // Map theme keys to standard prefixes
355
+ const themeMap = {
356
+ colors: 'color-',
357
+ spacing: 'space-',
358
+ borderRadius: 'radius-',
359
+ shadows: 'shadow-',
360
+ fontSize: 'size-',
361
+ maxWidth: 'max-w-'
362
+ };
363
+
364
+ Object.entries(themeMap).forEach(([themeKey, varPrefix]) => {
365
+ if (theme[themeKey]) {
366
+ generateVars(theme[themeKey], varPrefix);
367
+ }
368
+ });
369
+
370
+ rootVariables += '}\n\n';
371
+
372
+ let css = '/* Quantum CSS JIT Output */\n' + rootVariables + Array.from(utilities).join('\n');
279
373
  Object.entries(breakpoints).forEach(([name, width]) => {
280
374
  if (responsiveUtils[name] && responsiveUtils[name].size > 0) {
281
375
  css += `\n@media (min-width: ${width}) {\n${Array.from(responsiveUtils[name]).map(u => ' ' + u.replace(/\n/g, '\n ')).join('\n').trimEnd()}\n}\n`;
package/src/starlight.js CHANGED
@@ -7,15 +7,23 @@ const Starlight = {
7
7
  /**
8
8
  * Initializes a randomized star field in the target container.
9
9
  * @param {string} selector - CSS selector for the container (default: '.starlight-stars')
10
- * @param {number} count - Number of stars to generate (default: 150)
10
+ * @param {number} count - Number of stars to generate (default: 100)
11
11
  */
12
- initStars(selector = '.starlight-stars', count = 150) {
12
+ initStars(selector = '.starlight-stars', count = 100) {
13
13
  const containers = document.querySelectorAll(selector);
14
14
 
15
+ // Check for reduced motion preference
16
+ const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
17
+
15
18
  containers.forEach(container => {
16
19
  // Clear existing stars if any
17
20
  container.innerHTML = '';
18
21
 
22
+ // If user prefers reduced motion, maybe skip or show static stars
23
+ if (prefersReducedMotion) {
24
+ count = Math.floor(count / 2); // Show fewer stars if motion is reduced
25
+ }
26
+
19
27
  for (let i = 0; i < count; i++) {
20
28
  const star = document.createElement('div');
21
29
  star.className = 'star';
@@ -30,7 +38,12 @@ const Starlight = {
30
38
  star.style.height = `${size}px`;
31
39
 
32
40
  // Randomize animation duration (2s to 5s)
33
- star.style.setProperty('--duration', `${Math.random() * 3 + 2}s`);
41
+ if (!prefersReducedMotion) {
42
+ star.style.setProperty('--q-duration', `${Math.random() * 3 + 2}s`);
43
+ } else {
44
+ star.style.animation = 'none';
45
+ star.style.opacity = (Math.random() * 0.3 + 0.1).toString();
46
+ }
34
47
 
35
48
  container.appendChild(star);
36
49
  }
@@ -242,8 +255,11 @@ const Starlight = {
242
255
  });
243
256
 
244
257
  // Fallback for legacy accordion-header (backward compatibility)
258
+ // Only target headers that are NOT already managed by a data-accordion container
245
259
  const legacyHeaders = document.querySelectorAll('.accordion-header:not([data-accordion])');
246
260
  legacyHeaders.forEach(header => {
261
+ if (header.closest('[data-accordion]')) return;
262
+
247
263
  const item = header.parentElement;
248
264
  const group = item.closest('.accordion-group');
249
265
 
@@ -43,11 +43,11 @@
43
43
  }
44
44
 
45
45
  .ani-twinkle {
46
- animation: star-twinkle var(--twinkle-duration, 3s) ease-in-out infinite;
46
+ animation: star-twinkle var(--q-twinkle-duration, 3s) ease-in-out infinite;
47
47
  }
48
48
 
49
49
  .ani-orbit {
50
- animation: orbit var(--orbit-duration, 10s) linear infinite;
50
+ animation: orbit var(--q-orbit-duration, 10s) linear infinite;
51
51
  }
52
52
 
53
53
  .ani-svg-draw {