@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.
- package/README.md +8 -7
- package/dist/quantum.min.css +588 -381
- package/examples/blog-template.html +2 -16
- package/examples/gaming-template.html +0 -20
- package/examples/index.html +5 -8
- package/examples/kitchen-sink.html +1 -1
- package/examples/news-template.html +64 -80
- package/examples/shopping/index.html +48 -40
- package/examples/starlight.html +6 -73
- package/examples/theme-test.html +174 -0
- package/examples/travel/index.html +7 -17
- package/examples/verify_fixes.html +1 -0
- package/examples/verify_presets.html +2 -1
- package/package.json +7 -1
- package/src/defaults.js +5 -0
- package/src/generator.js +27 -8
- package/src/starlight.js +406 -15
- package/src/styles/quantum-animations.css +3 -2
- package/src/styles/quantum-base.css +299 -95
- package/src/styles/quantum-components.css +51 -50
- package/src/styles/quantum-responsive.css +5 -4
- package/src/styles/starlight.css +184 -184
|
@@ -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
|
|
250
|
-
|
|
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.
|
|
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
|
|
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]] || `${
|
|
163
|
-
const escaped = fullCls
|
|
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 =
|
|
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(${
|
|
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 = `${(
|
|
197
|
-
else
|
|
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
|
|
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
|
|