@howssatoshi/quantumcss 1.4.3 → 1.5.1

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.
@@ -0,0 +1,174 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Theme Auto-Detection Test</title>
7
+ <link rel="stylesheet" href="../dist/quantum.min.css">
8
+ <script src="../src/starlight.js"></script>
9
+ <style>
10
+ body { min-height: 100vh; }
11
+ .demo-section {
12
+ margin: 2rem auto;
13
+ max-width: 600px;
14
+ padding: 2rem;
15
+ }
16
+ .theme-controls {
17
+ display: flex;
18
+ gap: 1rem;
19
+ margin-bottom: 2rem;
20
+ justify-content: center;
21
+ }
22
+ .status-message {
23
+ background: var(--glass-bg);
24
+ border: 1px solid var(--glass-border);
25
+ border-radius: var(--radius-lg);
26
+ padding: 1rem;
27
+ margin-top: 1rem;
28
+ font-family: monospace;
29
+ font-size: 0.875rem;
30
+ }
31
+ </style>
32
+ </head>
33
+ <body>
34
+ <div class="starlight-stars" id="stars"></div>
35
+
36
+ <div class="demo-section starlight-card">
37
+ <h1>🎨 Theme Auto-Detection Test</h1>
38
+
39
+ <div class="theme-controls">
40
+ <button class="btn-starlight" onclick="setTheme('light')">☀️ Light</button>
41
+ <button class="btn-secondary" onclick="setTheme('dark')">🌙 Dark</button>
42
+ <button class="btn-outline" onclick="setTheme('auto')">🖥️ Follow System</button>
43
+ <button class="btn-ghost" onclick="toggleTheme()">🔄 Cycle</button>
44
+ <button class="btn-success" onclick="resetToAuto()">🔄 Reset to Auto</button>
45
+ <button class="btn-warning" onclick="forceRefresh()">🔃 Force Refresh</button>
46
+ </div>
47
+
48
+ <div class="status-message" id="status">
49
+ Loading theme information...
50
+ </div>
51
+
52
+ <h3>Instructions:</h3>
53
+ <ol>
54
+ <li>Click "Follow System" to enable automatic theme switching</li>
55
+ <li>Change your OS theme (macOS: System Settings → Appearance, Windows: Settings → Personalization → Colors)</li>
56
+ <li>The webpage should automatically switch to match your OS</li>
57
+ <li>Try "Cycle" to rotate through all available themes</li>
58
+ </ol>
59
+
60
+ <h3>Current State:</h3>
61
+ <div id="current-state"></div>
62
+ </div>
63
+
64
+ <script>
65
+ // Monitor theme changes
66
+ function updateStatus() {
67
+ const html = document.documentElement;
68
+ const currentTheme = html.getAttribute('data-theme');
69
+ const savedTheme = localStorage.getItem('theme');
70
+ const effectiveTheme = localStorage.getItem('theme-effective');
71
+ const systemPrefers = window.matchMedia('(prefers-color-scheme: light)').matches;
72
+
73
+ const statusEl = document.getElementById('status');
74
+ const stateEl = document.getElementById('current-state');
75
+
76
+ // Debug information
77
+ console.log('Theme Debug:', {
78
+ currentTheme,
79
+ savedTheme,
80
+ effectiveTheme,
81
+ systemPrefers,
82
+ htmlDataTheme: html.getAttribute('data-theme'),
83
+ htmlAttributes: Object.keys(html.attributes).map(attr => `${attr}: ${html.getAttribute(attr)}`)
84
+ });
85
+
86
+ statusEl.innerHTML = `
87
+ <strong>Current Theme:</strong> ${currentTheme || 'null'}<br>
88
+ <strong>Saved Preference:</strong> ${savedTheme || 'none'}<br>
89
+ <strong>Effective Theme:</strong> ${effectiveTheme || currentTheme || 'null'}<br>
90
+ <strong>System Prefers:</strong> ${systemPrefers ? 'light' : 'dark'}
91
+ `;
92
+
93
+ stateEl.innerHTML = `
94
+ <div class="starlight-card" style="padding: 1rem; margin-top: 1rem;">
95
+ <h4>🎯 Theme Configuration:</h4>
96
+ <p><strong>html[data-theme]:</strong> ${currentTheme}</p>
97
+ <p><strong>localStorage theme:</strong> ${savedTheme || 'null'}</p>
98
+ <p><strong>localStorage theme-effective:</strong> ${effectiveTheme || 'null'}</p>
99
+ <p><strong>System prefers-color-scheme:</strong> ${systemPrefers ? 'light' : 'dark'}</p>
100
+
101
+ <h5>💡 How this works:</h5>
102
+ <ul>
103
+ <li>If theme is "auto", the system preference is followed</li>
104
+ <li>System theme changes are automatically detected</li>
105
+ <li>Your manual choice is saved and restored</li>
106
+ </ul>
107
+ </div>
108
+ `;
109
+ }
110
+
111
+ // Listen for theme changes
112
+ window.addEventListener('themechange', (e) => {
113
+ console.log('Theme changed:', e.detail);
114
+ updateStatus();
115
+ });
116
+
117
+ // Listen for system theme changes
118
+ window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', (e) => {
119
+ console.log('System theme changed to:', e.matches ? 'light' : 'dark');
120
+ updateStatus();
121
+ });
122
+
123
+ // Initial update - wait for first themechange event or timeout
124
+ let themeInitialized = false;
125
+ const firstThemeChange = (e) => {
126
+ if (!themeInitialized) {
127
+ themeInitialized = true;
128
+ window.removeEventListener('themechange', firstThemeChange);
129
+ updateStatus();
130
+ }
131
+ };
132
+
133
+ window.addEventListener('themechange', firstThemeChange);
134
+
135
+ document.addEventListener('DOMContentLoaded', () => {
136
+ // Wait for Starlight to initialize, then check status
137
+ setTimeout(() => {
138
+ if (!themeInitialized) {
139
+ // If no themechange event fired yet, check current state
140
+ themeInitialized = true;
141
+ window.removeEventListener('themechange', firstThemeChange);
142
+ updateStatus();
143
+ }
144
+ }, 500); // Longer delay to ensure Starlight initialization
145
+ });
146
+
147
+ // Reset to auto function
148
+ function resetToAuto() {
149
+ console.log('Resetting to auto theme...');
150
+ if (typeof setTheme === 'function') {
151
+ setTheme('auto');
152
+ updateStatus();
153
+ } else {
154
+ console.error('setTheme function not available');
155
+ }
156
+ }
157
+
158
+ // Also update every 2 seconds to show real-time changes
159
+ setInterval(updateStatus, 2000);
160
+
161
+ // Force refresh function
162
+ function forceRefresh() {
163
+ console.log('Forcing theme refresh...');
164
+ // Manually trigger Starlight theme initialization
165
+ if (window.Starlight && window.Starlight.initTheme) {
166
+ window.Starlight.initTheme();
167
+ setTimeout(updateStatus, 100);
168
+ } else {
169
+ console.error('Starlight not available');
170
+ }
171
+ }
172
+ </script>
173
+ </body>
174
+ </html>
@@ -5,14 +5,18 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Nebula Voyage | Starlight Travel</title>
7
7
  <link rel="stylesheet" href="../../dist/quantum.min.css">
8
+ <script src="../../src/starlight.js"></script>
8
9
  <style>
9
10
  body {
10
11
  background: radial-gradient(circle at top right, #08081a, #000);
11
12
  overflow-x: hidden;
12
13
  transition: background-color 0.5s ease, color 0.5s ease;
13
14
  }
15
+ body.dark-mode {
16
+ color: #f1f5f9;
17
+ }
14
18
  body.light-mode {
15
- background: #f1f5f9;
19
+ background: #f1f5f9 !important;
16
20
  color: #1e293b;
17
21
  }
18
22
  .hero-glow {
@@ -246,22 +250,8 @@
246
250
  </footer>
247
251
 
248
252
  <script>
249
- // Theme Toggle Logic
250
- const themeBtn = document.getElementById('theme-btn');
251
- if (themeBtn) {
252
- themeBtn.addEventListener('click', () => {
253
- document.body.classList.toggle('light-mode');
254
- const isLight = document.body.classList.contains('light-mode');
255
-
256
- // Update all icons
257
- document.querySelectorAll('.sun-icon').forEach(icon => {
258
- icon.classList.toggle('hidden', !isLight);
259
- });
260
- document.querySelectorAll('.moon-icon').forEach(icon => {
261
- icon.classList.toggle('hidden', isLight);
262
- });
263
- });
264
- }
253
+ // Theme initialization and management is now handled
254
+ // automatically by Starlight.initTheme() in starlight.js
265
255
  </script>
266
256
  </body>
267
257
  </html>
@@ -4,6 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <title>Verification of Fixes</title>
6
6
  <link rel="stylesheet" href="../dist/quantum.min.css">
7
+ <script src="../src/starlight.js"></script>
7
8
  </head>
8
9
  <body class="p-10 space-y-8 bg-black text-white">
9
10
  <nav class="sticky top-0 z-50 glass p-4 mb-8">
@@ -4,7 +4,8 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Quantum CSS - Verify Presets</title>
7
- <link rel="stylesheet" href="../dist/quantum.css">
7
+ <link rel="stylesheet" href="../dist/quantum.min.css">
8
+ <script src="../src/starlight.js"></script>
8
9
  </head>
9
10
  <body class="bg-gray-50 p-12">
10
11
  <div class="max-w-4xl mx-auto space-y-12">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@howssatoshi/quantumcss",
3
- "version": "1.4.3",
3
+ "version": "1.5.1",
4
4
  "description": "Advanced utility-first CSS framework with JIT generation and modern components",
5
5
  "main": "dist/quantum.min.css",
6
6
  "bin": {
@@ -46,5 +46,11 @@
46
46
  "dependencies": {
47
47
  "chokidar": "^5.0.0",
48
48
  "glob": "^13.0.0"
49
+ },
50
+ "devDependencies": {
51
+ "@eslint/js": "^9.39.2",
52
+ "eslint": "^9.39.2",
53
+ "stylelint": "^17.1.1",
54
+ "stylelint-config-standard": "^40.0.0"
49
55
  }
50
56
  }
package/src/defaults.js CHANGED
@@ -37,6 +37,11 @@ const defaultTheme = {
37
37
  lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
38
38
  xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
39
39
  '2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)',
40
+ },
41
+ maxWidth: {
42
+ 'xs': '20rem', 'sm': '24rem', 'md': '28rem', 'lg': '32rem', 'xl': '36rem',
43
+ '2xl': '42rem', '3xl': '48rem', '4xl': '56rem', '5xl': '64rem', '6xl': '72rem', '7xl': '80rem',
44
+ 'full': '100%', 'min': 'min-content', 'max': 'max-content', 'fit': 'fit-content', 'prose': '65ch',
40
45
  }
41
46
  };
42
47
 
package/src/generator.js CHANGED
@@ -15,6 +15,8 @@ function generateCSS(configPath) {
15
15
  theme.spacing = theme.spacing || {};
16
16
  theme.borderRadius = theme.borderRadius || {};
17
17
  theme.fontSize = theme.fontSize || {};
18
+ theme.maxWidth = theme.maxWidth || {};
19
+ theme.shadows = theme.shadows || {};
18
20
 
19
21
  if (config.theme && config.theme.extend) {
20
22
  const ext = config.theme.extend;
@@ -22,6 +24,8 @@ function generateCSS(configPath) {
22
24
  if (ext.spacing) Object.assign(theme.spacing, ext.spacing);
23
25
  if (ext.borderRadius) Object.assign(theme.borderRadius, ext.borderRadius);
24
26
  if (ext.fontSize) Object.assign(theme.fontSize, ext.fontSize);
27
+ if (ext.maxWidth) Object.assign(theme.maxWidth, ext.maxWidth);
28
+ if (ext.boxShadow) Object.assign(theme.shadows, ext.boxShadow);
25
29
  }
26
30
 
27
31
  const flattenedColors = {};
@@ -58,7 +62,9 @@ function generateCSS(configPath) {
58
62
  while ((match = classAttrRegex.exec(content)) !== null) {
59
63
  match[1].split(/\s+/).forEach(cls => { if (cls) rawClasses.add(cls); });
60
64
  }
61
- } catch (e) {}
65
+ } catch {
66
+ // Ignore errors reading files
67
+ }
62
68
  });
63
69
 
64
70
  const utilities = new Set();
@@ -67,6 +73,15 @@ function generateCSS(configPath) {
67
73
  dark: new Set()
68
74
  };
69
75
 
76
+ /**
77
+ * Escapes a class name for use in a CSS selector
78
+ * @param {string} cls - The raw class name
79
+ * @returns {string} The escaped selector
80
+ */
81
+ const escapeSelector = (cls) => {
82
+ return cls.replace(/([:[\]/.\\])/g, '\\$1');
83
+ };
84
+
70
85
  const sideMap = {
71
86
  p: 'padding', pt: 'padding-top', pr: 'padding-right', pb: 'padding-bottom', pl: 'padding-left',
72
87
  px: ['padding-left', 'padding-right'], py: ['padding-top', 'padding-bottom'],
@@ -159,8 +174,8 @@ function generateCSS(configPath) {
159
174
  } else if (prefix === 'col' && cParts[1] === 'span') {
160
175
  property = 'grid-column'; value = `span ${cParts[2]} / span ${cParts[2]}`;
161
176
  } else if (prefix === 'space') {
162
- const amount = theme.spacing[cParts[2]] || `${parseInt(cParts[2]) * 0.25}rem`;
163
- const escaped = fullCls.replace(/([:[\/])/g, '\\$1');
177
+ const amount = theme.spacing[cParts[2]] || `${parseFloat(cParts[2]) * 0.25}rem`;
178
+ const escaped = escapeSelector(fullCls);
164
179
  customSelector = `.${escaped} > * + *`;
165
180
  property = cParts[1] === 'y' ? 'margin-top' : 'margin-left';
166
181
  value = isNeg ? `-${amount}` : amount;
@@ -169,12 +184,12 @@ function generateCSS(configPath) {
169
184
  if (valKey.startsWith('[') && valKey.endsWith(']')) value = valKey.slice(1, -1);
170
185
  else if (theme.borderRadius[valKey]) value = theme.borderRadius[valKey];
171
186
  else {
172
- const num = parseInt(valKey);
187
+ const num = parseFloat(valKey);
173
188
  value = isNaN(num) ? '0.375rem' : `${num * 0.125}rem`;
174
189
  }
175
190
  } else if (prefix === 'scale') {
176
191
  property = 'transform';
177
- value = `scale(${parseInt(valKey) / 100})`;
192
+ value = `scale(${parseFloat(valKey) / 100})`;
178
193
  } else if (prefix === 'transition') {
179
194
  property = 'transition-property';
180
195
  if (valKey === 'all') value = 'all';
@@ -193,8 +208,12 @@ function generateCSS(configPath) {
193
208
  property = sideMap[prefix];
194
209
  let v = valKey;
195
210
  if (v.startsWith('[') && v.endsWith(']')) v = v.slice(1, -1);
196
- else if (v.includes('/')) v = `${(parseInt(v.split('/')[0])/parseInt(v.split('/')[1])*100).toFixed(2)}%`;
197
- else v = theme.spacing[v] || v;
211
+ else if (v.includes('/')) v = `${(parseFloat(v.split('/')[0]) / parseFloat(v.split('/')[1]) * 100).toFixed(2)}%`;
212
+ else {
213
+ // Priority: 1. Specific theme map (e.g. maxWidth for max-w) 2. spacing map 3. Numeric conversion 4. raw value
214
+ const themeMap = prefix === 'max-w' ? theme.maxWidth : (theme[prefix] || theme.spacing);
215
+ v = (themeMap && themeMap[v]) || theme.spacing[v] || (/^\d+(\.\d+)?$/.test(v) ? `${parseFloat(v) * 0.25}rem` : v);
216
+ }
198
217
  value = isNeg ? (Array.isArray(v) ? v.map(x => `-${x}`) : `-${v}`) : v;
199
218
  } else if (prefix === 'shadow') {
200
219
  if (theme.shadows[valKey]) { property = 'box-shadow'; value = theme.shadows[valKey]; }
@@ -235,7 +254,7 @@ function generateCSS(configPath) {
235
254
 
236
255
  merged.forEach(group => {
237
256
  const { breakpoint, variant, customSelector, rules } = group;
238
- const escapedFull = fullCls.replace(/([:[\/])/g, '\\$1');
257
+ const escapedFull = escapeSelector(fullCls);
239
258
  let selector = customSelector || `.${escapedFull}`;
240
259
  if (variant) { if (variant === 'group-hover') selector = `.group:hover ${selector}`; else selector += `:${variant}`}
241
260