@howssatoshi/quantumcss 1.7.3 → 1.7.4

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/generator.js CHANGED
@@ -11,6 +11,8 @@ function generateCSS(configPath) {
11
11
  const config = fs.existsSync(resolvedPath) ? require(resolvedPath) : { content: [], theme: {} };
12
12
 
13
13
  const theme = JSON.parse(JSON.stringify(defaultTheme || {}));
14
+ const currentUtilityMaps = { ...utilityMaps };
15
+
14
16
  theme.colors = theme.colors || {};
15
17
  theme.spacing = theme.spacing || {};
16
18
  theme.borderRadius = theme.borderRadius || {};
@@ -40,17 +42,59 @@ function generateCSS(configPath) {
40
42
  function resolveColor(key) {
41
43
  if (!key) return null;
42
44
  if (flattenedColors[key]) return flattenedColors[key];
43
- if (key.includes('/')) {
44
- const [base, opacity] = key.split('/');
45
- const color = flattenedColors[base] || flattenedColors[`${base}-500`];
46
- if (color && color.startsWith('#')) {
47
- const r = parseInt(color.slice(1, 3), 16), g = parseInt(color.slice(3, 5), 16), b = parseInt(color.slice(5, 7), 16);
48
- return `rgba(${r}, ${g}, ${b}, ${parseInt(opacity) / 100})`;
45
+ if (key.includes('_')) {
46
+ const parts = key.split('_');
47
+ // If it's a color with opacity (e.g. primary_50)
48
+ if (parts.length === 2 && !isNaN(parseInt(parts[1]))) {
49
+ const [base, opacity] = parts;
50
+ const color = flattenedColors[base] || flattenedColors[`${base}-500`];
51
+ if (color && color.startsWith('#')) {
52
+ const r = parseInt(color.slice(1, 3), 16), g = parseInt(color.slice(3, 5), 16), b = parseInt(color.slice(5, 7), 16);
53
+ return `rgba(${r}, ${g}, ${b}, ${parseInt(opacity) / 100})`;
54
+ }
49
55
  }
50
56
  }
51
57
  return null;
52
58
  }
53
59
 
60
+ const postProcessors = [];
61
+
62
+ // Load Plugins
63
+ if (config.plugins && Array.isArray(config.plugins)) {
64
+ config.plugins.forEach(pluginPath => {
65
+ try {
66
+ // Support both relative paths and node_modules
67
+ let resolvedPluginPath;
68
+ try {
69
+ resolvedPluginPath = require.resolve(pluginPath, { paths: [process.cwd(), __dirname] });
70
+ } catch {
71
+ resolvedPluginPath = path.resolve(process.cwd(), pluginPath);
72
+ }
73
+
74
+ if (fs.existsSync(resolvedPluginPath)) {
75
+ delete require.cache[resolvedPluginPath];
76
+ const plugin = require(resolvedPluginPath);
77
+ if (typeof plugin === 'function') {
78
+ const result = plugin({
79
+ theme,
80
+ utilityMaps: currentUtilityMaps,
81
+ componentPresets: config.componentPresets || {},
82
+ flattenedColors,
83
+ addUtilities: (newUtils) => Object.assign(currentUtilityMaps, newUtils)
84
+ });
85
+
86
+ // If plugin returns an object with transformCSS, add it to postProcessors
87
+ if (result && typeof result.transformCSS === 'function') {
88
+ postProcessors.push(result.transformCSS);
89
+ }
90
+ }
91
+ }
92
+ } catch (err) {
93
+ console.error(`❌ Error loading plugin ${pluginPath}:`, err);
94
+ }
95
+ });
96
+ }
97
+
54
98
  const getRGBA = (color) => {
55
99
  if (!color || color === 'transparent') return 'rgba(0,0,0,0)';
56
100
  if (color.startsWith('rgba')) return color;
@@ -69,15 +113,38 @@ function generateCSS(configPath) {
69
113
 
70
114
  const files = (config.content || []).flatMap(p => glob.sync(p));
71
115
  const rawClasses = new Set();
116
+
117
+ // Scanners
72
118
  const classAttrRegex = /class="([^"]+)"/g;
119
+ const variants = ['sm', 'md', 'lg', 'xl', '2xl', 'dark', 'light', 'hover', 'focus', 'active', 'group-hover'];
120
+ const variantRegexes = variants.map(v => ({
121
+ variant: v,
122
+ regex: new RegExp(`\\s${v}="([^"]+)"`, 'g')
123
+ }));
73
124
 
74
125
  files.forEach(file => {
75
126
  try {
76
127
  const content = fs.readFileSync(file, 'utf8');
128
+
129
+ // 1. Match standard class="..."
77
130
  let match;
78
131
  while ((match = classAttrRegex.exec(content)) !== null) {
79
132
  match[1].split(/\s+/).forEach(cls => { if (cls) rawClasses.add(cls); });
80
133
  }
134
+
135
+ // 2. Match attribute lanes (e.g., md="flex gap-4")
136
+ variantRegexes.forEach(({ variant, regex }) => {
137
+ regex.lastIndex = 0;
138
+ while ((match = regex.exec(content)) !== null) {
139
+ match[1].split(/\s+/).forEach(cls => {
140
+ if (cls) {
141
+ // We convert attribute-based utilities to a canonical internal format
142
+ // using the __ separator, which getRulesForClass already understands.
143
+ rawClasses.add(`${variant}__${cls}`);
144
+ }
145
+ });
146
+ }
147
+ });
81
148
  } catch {
82
149
  // Ignore errors reading files
83
150
  }
@@ -95,7 +162,7 @@ function generateCSS(configPath) {
95
162
  * @returns {string} The escaped selector
96
163
  */
97
164
  const escapeSelector = (cls) => {
98
- return cls.replace(/([:[\]/.\\])/g, '\\$1');
165
+ return cls.replace(/([:.[\]\\])/g, '\\$1');
99
166
  };
100
167
 
101
168
  const sideMap = {
@@ -111,20 +178,30 @@ function generateCSS(configPath) {
111
178
  function getRulesForClass(fullCls, processedPresets = new Set()) {
112
179
  let cls = fullCls, variant = null, breakpoint = null, isNeg = false;
113
180
  if (cls.startsWith('-')) { isNeg = true; cls = cls.substring(1); }
114
- const parts = cls.split(':');
181
+
182
+ // Support both : and __ as separators
183
+ const parts = cls.includes('__') ? cls.split('__') : cls.split(':');
184
+
115
185
  let currentPart = 0;
116
186
  while (currentPart < parts.length) {
117
187
  const p = parts[currentPart];
118
188
  if (breakpoints[p]) { breakpoint = p; }
119
189
  else if (p === 'dark') { breakpoint = 'dark'; }
120
190
  else if (p === 'light') { breakpoint = 'light'; }
121
- else if (['hover', 'focus', 'placeholder', 'group-hover'].includes(p)) { variant = p; }
122
- else { cls = parts.slice(currentPart).join(':'); break; }
191
+ else if (['hover', 'focus', 'active', 'placeholder', 'group-hover'].includes(p)) { variant = p; }
192
+ else {
193
+ // Reconstruct the remaining class part
194
+ cls = parts.slice(currentPart).join(cls.includes('__') ? '__' : ':');
195
+ break;
196
+ }
123
197
  currentPart++;
124
198
  }
125
199
 
126
200
  // Check Presets (User Config & Defaults)
127
- const presetValue = (config.componentPresets && config.componentPresets[cls]) || (utilityMaps[cls] && typeof utilityMaps[cls] === 'string' ? utilityMaps[cls] : null);
201
+ let presetValue = config.componentPresets && config.componentPresets[cls];
202
+ if (!presetValue && currentUtilityMaps[cls] && typeof currentUtilityMaps[cls] === 'string') {
203
+ presetValue = currentUtilityMaps[cls];
204
+ }
128
205
 
129
206
  if (presetValue && !processedPresets.has(cls)) {
130
207
  processedPresets.add(cls);
@@ -146,9 +223,13 @@ function generateCSS(configPath) {
146
223
  }
147
224
 
148
225
  let property = null, value = null, customSelector = null;
149
- if (utilityMaps[cls] && typeof utilityMaps[cls] === 'object') {
150
- const entry = utilityMaps[cls];
151
- if (!Array.isArray(entry)) { property = entry.property; value = entry.value; }
226
+ if (currentUtilityMaps[cls] && typeof currentUtilityMaps[cls] === 'object') {
227
+ const entry = currentUtilityMaps[cls];
228
+ if (!Array.isArray(entry)) {
229
+ property = entry.property;
230
+ value = entry.value;
231
+ if (entry.variant && !variant) variant = entry.variant;
232
+ }
152
233
  else { property = entry; }
153
234
  }
154
235
 
@@ -156,7 +237,10 @@ function generateCSS(configPath) {
156
237
  const cParts = cls.split('-');
157
238
  let prefix = cParts[0], valKey = cParts.slice(1).join('-');
158
239
 
159
- if ((prefix === 'max' || prefix === 'min' || prefix === 'gap' || prefix === 'gap-x' || prefix === 'gap-y') && cParts[1]) {
240
+ if (cParts[0] === 'focus' && cParts[1] === 'glow') {
241
+ prefix = 'focus-glow';
242
+ valKey = cParts.slice(2).join('-');
243
+ } else if ((prefix === 'max' || prefix === 'min' || prefix === 'gap' || prefix === 'gap-x' || prefix === 'gap-y') && cParts[1]) {
160
244
  if (['w', 'h', 'x', 'y'].includes(cParts[1])) {
161
245
  prefix = `${cParts[0]}-${cParts[1]}`;
162
246
  valKey = cParts.slice(2).join('-');
@@ -165,7 +249,7 @@ function generateCSS(configPath) {
165
249
 
166
250
  if (prefix === 'text') {
167
251
  if (theme.fontSize[valKey]) { property = ['font-size', 'line-height']; value = [theme.fontSize[valKey], (valKey.includes('xl') || parseInt(valKey) >= 3) ? '1.2' : '1.5']; }
168
- else { const color = resolveColor(valKey); if (color) { property = 'color'; value = color; } }
252
+ else { const color = resolveColor(valKey); if (color) { property = 'color'; value = `${color} !important`; } }
169
253
  } else if (prefix === 'bg') {
170
254
  if (cParts[1] === 'gradient' && cParts[2] === 'to') {
171
255
  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' };
@@ -176,14 +260,14 @@ function generateCSS(configPath) {
176
260
  const rules = [
177
261
  ' --q-gradient-from-transparent: rgba(0,0,0,0);',
178
262
  ' --q-gradient-to-transparent: rgba(0,0,0,0);',
179
- ` ${property}: ${value};`,
263
+ ` ${property}: ${value} !important;`,
180
264
  ' --q-gradient-stops: var(--q-gradient-from, var(--q-gradient-from-transparent)), var(--q-gradient-to, var(--q-gradient-to-transparent));'
181
265
  ];
182
266
  return [{ breakpoint: null, variant: null, customSelector: null, rules }];
183
267
  }
184
268
  }
185
269
  const color = resolveColor(valKey);
186
- if (color) { property = 'background-color'; value = color; }
270
+ if (color) { property = 'background-color'; value = `${color} !important`; }
187
271
  } else if (prefix === 'from') {
188
272
  const color = resolveColor(valKey);
189
273
  if (color) {
@@ -214,10 +298,10 @@ function generateCSS(configPath) {
214
298
  else if (prefix === 'aspect') {
215
299
  property = ['aspect-ratio', 'width', 'height'];
216
300
  let ratio = 'auto';
217
- if (valKey.startsWith('[') && valKey.endsWith(']')) ratio = valKey.slice(1, -1).replace(/\//g, ' / ');
301
+ if (valKey.startsWith('[') && valKey.endsWith(']')) ratio = valKey.slice(1, -1).replace(/_/g, ' / ');
218
302
  else if (valKey === 'video') ratio = '16 / 9';
219
303
  else if (valKey === 'square') ratio = '1 / 1';
220
- else ratio = valKey.replace(/\//g, ' / ');
304
+ else ratio = valKey.replace(/_/g, ' / ');
221
305
  value = [ratio, '100%', 'auto'];
222
306
  } else if (prefix === 'grid' && cParts[1] === 'cols') {
223
307
  property = 'grid-template-columns'; value = `repeat(${cParts[2]}, minmax(0, 1fr))`;
@@ -258,7 +342,7 @@ function generateCSS(configPath) {
258
342
  property = sideMap[prefix];
259
343
  let v = valKey;
260
344
  if (v.startsWith('[') && v.endsWith(']')) v = v.slice(1, -1);
261
- else if (v.includes('/')) v = `${(parseFloat(v.split('/')[0]) / parseFloat(v.split('/')[1]) * 100).toFixed(2)}%`;
345
+ else if (v.includes('_')) v = `${(parseFloat(v.split('_')[0]) / parseFloat(v.split('_')[1]) * 100).toFixed(2)}%`;
262
346
  else {
263
347
  // Priority: 1. Specific theme map (e.g. maxWidth for max-w) 2. spacing map 3. Numeric conversion 4. raw value
264
348
  const themeMap = prefix === 'max-w' ? theme.maxWidth : (theme[prefix] || theme.spacing);
@@ -268,15 +352,36 @@ function generateCSS(configPath) {
268
352
  } else if (prefix === 'shadow') {
269
353
  if (theme.shadows[valKey]) { property = 'box-shadow'; value = theme.shadows[valKey]; }
270
354
  else if (valKey === '') { property = 'box-shadow'; value = theme.shadows.md || '0 4px 6px -1px rgb(0 0 0 / 0.1)'; }
271
- }
272
- else if (prefix === 'border') {
355
+ } else if (prefix === 'border') {
273
356
  const color = resolveColor(valKey);
274
357
  if (color) { property = 'border-color'; value = color; }
275
358
  else if (['l', 'r', 't', 'b'].includes(cParts[1])) {
276
359
  const sideMapSide = { l: 'left', r: 'right', t: 'top', b: 'bottom' };
277
- property = `border-${sideMapSide[cParts[1]]}-width`;
278
- value = `${cParts[2]}px`;
279
- } else if (!isNaN(parseInt(valKey))) { property = 'border-width'; value = `${parseInt(valKey)}px`; }
360
+ const width = cParts[2] ? `${cParts[2]}px` : '1px';
361
+ property = ['border-width', `border-${sideMapSide[cParts[1]]}-width`, 'border-style'];
362
+ value = ['0', width, 'solid'];
363
+ } else if (!isNaN(parseInt(valKey))) {
364
+ property = ['border-width', 'border-style'];
365
+ value = [`${parseInt(valKey)}px`, 'solid'];
366
+ } else if (['solid', 'dashed', 'dotted', 'double', 'none'].includes(valKey)) {
367
+ property = 'border-style'; value = valKey;
368
+ }
369
+ } else if (prefix === 'focus-glow') {
370
+ const color = resolveColor(valKey) || resolveColor('primary') || '#00d4ff';
371
+ const rgba = getRGBA(color);
372
+ const glowColor = withOpacity(rgba, 0.7);
373
+ const ringColor = withOpacity(rgba, 0.4);
374
+
375
+ const rules = [
376
+ ' outline: none !important;',
377
+ ` box-shadow: 0 0 0 4px ${ringColor}, 0 0 35px ${glowColor} !important;`
378
+ ];
379
+
380
+ // Apply to both focus and active states for better interactive feedback
381
+ return [
382
+ { breakpoint, variant: variant || 'focus', customSelector, rules },
383
+ { breakpoint, variant: 'active', customSelector, rules }
384
+ ];
280
385
  }
281
386
  }
282
387
 
@@ -305,34 +410,70 @@ function generateCSS(configPath) {
305
410
  merged.forEach(group => {
306
411
  const { breakpoint, variant, customSelector, rules } = group;
307
412
  const escapedFull = escapeSelector(fullCls);
308
- let selector = customSelector || `.${escapedFull}`;
309
- if (variant) { if (variant === 'group-hover') selector = `.group:hover ${selector}`; else selector += `:${variant}`}
310
413
 
311
- if (breakpoint === 'light') {
312
- const block = `html[data-theme="light"] ${selector}, body.light-mode ${selector} {
313
- ${rules.join('\n')}
414
+ // Generate multiple selectors for maximum compatibility
415
+ const selectors = [];
416
+
417
+ if (customSelector) {
418
+ selectors.push(customSelector);
419
+ } else {
420
+ // 1. Standard class selector (e.g., .md:flex or .md__flex)
421
+ let classSelector = `.${escapedFull}`;
422
+ if (variant) {
423
+ if (variant === 'group-hover') classSelector = `.group:hover ${classSelector}`;
424
+ else classSelector += `:${variant}`;
425
+ }
426
+ selectors.push(classSelector);
427
+
428
+ // 2. Attribute-based selector (e.g., [md~="flex"])
429
+ // If the class has a variant/breakpoint prefix, we can support it as an attribute
430
+ const separator = fullCls.includes('__') ? '__' : ':';
431
+ const parts = fullCls.split(separator);
432
+ if (parts.length > 1) {
433
+ const prefix = parts[0];
434
+ const utility = parts.slice(1).join(separator);
435
+ let attrSelector = `[${prefix}~="${utility}"]`;
436
+
437
+ if (variant && variant !== 'group-hover' && variant === prefix) {
438
+ // Already covered by attribute name (e.g. [hover~="text-primary"])
439
+ // but we still need the :hover pseudo-class if it's a state
440
+ if (['hover', 'focus', 'active'].includes(variant)) {
441
+ attrSelector += `:${variant}`;
442
+ }
443
+ } else if (variant) {
444
+ if (variant === 'group-hover') attrSelector = `.group:hover ${attrSelector}`;
445
+ else attrSelector += `:${variant}`;
446
+ }
447
+ selectors.push(attrSelector);
448
+ }
449
+ }
450
+
451
+ selectors.forEach(selector => {
452
+ if (breakpoint === 'light') {
453
+ const block = `html[data-theme="light"] ${selector}, body.light-mode ${selector} {
454
+ ${rules.join('\n').trim()}
314
455
  }
315
456
  `;
316
- utilities.add(block);
317
- } else if (breakpoint === 'dark') {
318
- const block = `html[data-theme="dark"] ${selector}, body.dark-mode ${selector} {
319
- ${rules.join('\n')}
457
+ utilities.add(block);
458
+ } else if (breakpoint === 'dark') {
459
+ const block = `html[data-theme="dark"] ${selector}, body.dark-mode ${selector} {
460
+ ${rules.join('\n').trim()}
320
461
  }
321
462
  `;
322
- utilities.add(block);
323
- // Also add to media query for system preference
324
- const mediaBlock = `${selector} {
325
- ${rules.join('\n')}
463
+ utilities.add(block);
464
+ const mediaBlock = `${selector} {
465
+ ${rules.join('\n').trim()}
326
466
  }
327
467
  `;
328
- responsiveUtils['dark'].add(mediaBlock);
329
- } else {
330
- const block = `${selector} {
331
- ${rules.join('\n')}
468
+ responsiveUtils['dark'].add(mediaBlock);
469
+ } else {
470
+ const block = `${selector} {
471
+ ${rules.join('\n').trim()}
332
472
  }
333
473
  `;
334
- if (breakpoint) responsiveUtils[breakpoint].add(block); else utilities.add(block);
335
- }
474
+ if (breakpoint) responsiveUtils[breakpoint].add(block); else utilities.add(block);
475
+ }
476
+ });
336
477
  });
337
478
  }
338
479
 
@@ -380,6 +521,11 @@ ${rules.join('\n')}
380
521
  css += `\n@media (prefers-color-scheme: dark) {\n${Array.from(responsiveUtils.dark).map(u => ' ' + u.replace(/\n/g, '\n ')).join('\n').trimEnd()}\n}\n`;
381
522
  }
382
523
 
524
+ // Apply Post-Processors
525
+ postProcessors.forEach(processor => {
526
+ css = processor(css);
527
+ });
528
+
383
529
  return css;
384
530
  }
385
531
 
package/src/starlight.js CHANGED
@@ -139,6 +139,12 @@ const Starlight = {
139
139
  e.stopPropagation();
140
140
  const isActive = toggle.classList.toggle('active');
141
141
  menu.classList.toggle('active', isActive);
142
+
143
+ // Allow nav to wrap when menu is open so full-width menu stacks below
144
+ const parentNav = menu.closest('.starlight-nav') || menu.closest('.nav-glass');
145
+ if (parentNav) {
146
+ parentNav.style.flexFlow = isActive ? 'row wrap' : 'nowrap';
147
+ }
142
148
  });
143
149
 
144
150
  document.addEventListener('click', (e) => {
@@ -105,7 +105,7 @@
105
105
 
106
106
  /* Glassmorphism */
107
107
  --q-glass-bg: rgb(255 255 255 / 3%);
108
- --q-glass-border: rgb(255 255 255 / 10%);
108
+ --q-glass-border: rgb(255 255 255 / 5%);
109
109
  --q-glass-blur: blur(16px);
110
110
 
111
111
  /* Transitions */
@@ -190,6 +190,17 @@ input, textarea, select, button {
190
190
  padding: 0;
191
191
  margin: 0;
192
192
  background: transparent;
193
+ transition: box-shadow var(--q-transition-base), border-color var(--q-transition-base), transform var(--q-transition-base);
194
+ }
195
+
196
+ input:focus,
197
+ textarea:focus,
198
+ select:focus,
199
+ button:focus,
200
+ input:active,
201
+ button:active {
202
+ outline: none;
203
+ box-shadow: 0 0 0 3px rgba(0, 212, 255, 0.15), 0 0 20px rgba(0, 212, 255, 0.3);
193
204
  }
194
205
 
195
206
  textarea {
@@ -1,68 +1,3 @@
1
- /*!
2
- * QuantumCSS Component Utilities & Variants
3
- * Advanced component patterns, states, and interactive utilities
4
- */
5
-
6
- /* Hover State Utilities */
7
- .hover\:text-primary:hover { color: var(--q-color-primary); }
8
- .hover\:text-secondary:hover { color: var(--q-color-secondary); }
9
- .hover\:text-white:hover { color: #fff; }
10
- .hover\:text-black:hover { color: #000; }
11
-
12
- .hover\:bg-primary:hover { background-color: var(--q-color-primary); }
13
- .hover\:bg-secondary:hover { background-color: var(--q-color-secondary); }
14
- .hover\:bg-gray-100:hover { background-color: #f3f4f6; }
15
- .hover\:bg-gray-200:hover { background-color: #e5e7eb; }
16
-
17
- .hover\:border-primary:hover { border-color: var(--q-color-primary); }
18
- .hover\:border-secondary:hover { border-color: var(--q-color-secondary); }
19
-
20
- .hover\:shadow-lg:hover { box-shadow: var(--q-shadow-lg); }
21
- .hover\:shadow-xl:hover { box-shadow: var(--q-shadow-xl); }
22
-
23
- .hover\:scale-105:hover { transform: scale(1.05); }
24
- .hover\:scale-110:hover { transform: scale(1.1); }
25
- .hover\:scale-95:hover { transform: scale(0.95); }
26
-
27
- .hover\:rotate-90:hover { transform: rotate(90deg); }
28
- .hover\:rotate-180:hover { transform: rotate(180deg); }
29
-
30
- .hover\:opacity-75:hover { opacity: 0.75; }
31
- .hover\:opacity-50:hover { opacity: 0.5; }
32
- .hover\:opacity-100:hover { opacity: 1; }
33
-
34
- /* Focus State Utilities */
35
- .focus\:outline-none:focus { outline: none; }
36
- .focus\:outline:focus { outline: 2px solid; outline-offset: 2px; }
37
- .focus\:outline-primary:focus { outline-color: var(--q-color-primary); }
38
- .focus\:outline-secondary:focus { outline-color: var(--q-color-secondary); }
39
-
40
- .focus\:ring:focus { box-shadow: 0 0 0 3px; }
41
- .focus\:ring-primary:focus { box-shadow: 0 0 0 3px var(--q-color-primary); }
42
- .focus\:ring-secondary:focus { box-shadow: 0 0 0 3px var(--q-color-secondary); }
43
-
44
- .focus\:border-primary:focus { border-color: var(--q-color-primary); }
45
- .focus\:border-secondary:focus { border-color: var(--q-color-secondary); }
46
-
47
- /* Active State Utilities */
48
- .active\:scale-95:active { transform: scale(0.95); }
49
- .active\:scale-90:active { transform: scale(0.9); }
50
-
51
- .active\:bg-primary:active { background-color: var(--q-color-primary); }
52
- .active\:bg-secondary:active { background-color: var(--q-color-secondary); }
53
-
54
- /* Disabled State Utilities */
55
- .disabled\:opacity-50:disabled { opacity: 0.5; }
56
- .disabled\:opacity-75:disabled { opacity: 0.75; }
57
- .disabled\:cursor-not-allowed:disabled { cursor: not-allowed; }
58
- .disabled\:pointer-events-none:disabled { pointer-events: none; }
59
-
60
- /* Group Hover Utilities */
61
- .group:hover .group-hover\:text-primary { color: var(--q-color-primary); }
62
- .group:hover .group-hover\:text-white { color: #fff; }
63
-
64
- .group:hover .group-hover\:bg-primary { background-color: var(--q-color-primary); }
65
- .group:hover .group-hover\:bg-secondary { background-color: var(--q-color-secondary); }
66
1
 
67
2
  .group:hover .group-hover\:opacity-100 { opacity: 1; }
68
3
  .group:hover .group-hover\:scale-110 { transform: scale(1.1); }
@@ -652,7 +587,6 @@ body.light-mode .tab-button.active {
652
587
 
653
588
  .progress-bar {
654
589
  height: 100%;
655
- background-color: var(--q-color-primary);
656
590
  transition: width var(--q-duration-300) var(--q-ease-in-out);
657
591
  }
658
592
 
@@ -857,7 +791,6 @@ body.light-mode .table tbody tr:hover {
857
791
 
858
792
  /* Main content area */
859
793
  .main-content {
860
- padding: 1.5rem 2rem;
861
794
  overflow-y: auto;
862
795
  overflow-x: hidden;
863
796
  }
@@ -921,7 +854,6 @@ body.light-mode .top-nav {
921
854
  }
922
855
 
923
856
  .main-content {
924
- padding: 1rem;
925
857
  }
926
858
 
927
859
  .page-header {
@@ -1442,7 +1374,7 @@ body.light-mode .user-email {
1442
1374
  color: #34d399;
1443
1375
  }
1444
1376
 
1445
- .status-active::before {
1377
+ .status-active__:before {
1446
1378
  background: #34d399;
1447
1379
  }
1448
1380
 
@@ -1460,7 +1392,7 @@ body.light-mode .user-email {
1460
1392
  color: #f87171;
1461
1393
  }
1462
1394
 
1463
- .status-inactive::before {
1395
+ .status-inactive__:before {
1464
1396
  background: #f87171;
1465
1397
  }
1466
1398