@howssatoshi/quantumcss 1.1.0 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@howssatoshi/quantumcss",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Advanced utility-first CSS framework with JIT generation and modern components",
5
5
  "main": "dist/quantum.min.css",
6
6
  "bin": {
@@ -16,12 +16,13 @@
16
16
  "build": "node scripts/build.js --minify",
17
17
  "build:dev": "node scripts/build.js",
18
18
  "watch": "node scripts/build.js --watch",
19
+ "docs": "node scripts/build.js --docs",
19
20
  "generate": "node src/cli.js",
20
21
  "generate:watch": "node src/cli.js --watch"
21
22
  },
22
23
  "repository": {
23
24
  "type": "git",
24
- "url": "https://github.com/macroadster/quantumcss.git"
25
+ "url": "git+https://github.com/macroadster/quantumcss.git"
25
26
  },
26
27
  "bugs": {
27
28
  "url": "https://github.com/macroadster/quantumcss/issues"
package/src/cli.js CHANGED
@@ -20,8 +20,61 @@ function build(outputPath) {
20
20
  }
21
21
  }
22
22
 
23
+ function scaffold(template, targetPath) {
24
+ const templatesDir = path.resolve(__dirname, '../examples');
25
+ const templateMap = {
26
+ gaming: 'gaming-template.html',
27
+ blog: 'blog-template.html',
28
+ travel: 'travel/index.html',
29
+ shopping: 'shopping/index.html',
30
+ starlight: 'starlight.html',
31
+ news: 'news-template.html',
32
+ docs: 'kitchen-sink.html',
33
+ demo: 'demo.html'
34
+ };
35
+
36
+ const templateFile = templateMap[template];
37
+ if (!templateFile) {
38
+ console.error(`❌ Error: Unknown template '${template}'. Available: ${Object.keys(templateMap).join(', ')}`);
39
+ process.exit(1);
40
+ }
41
+
42
+ const srcPath = path.join(templatesDir, templateFile);
43
+ if (!fs.existsSync(srcPath)) {
44
+ console.error(`❌ Error: Template file not found at ${srcPath}`);
45
+ process.exit(1);
46
+ }
47
+
48
+ let content = fs.readFileSync(srcPath, 'utf8');
49
+
50
+ // Adjust stylesheet path in template
51
+ content = content.replace(/href="(\.\.\/)*dist\/quantum(\.min)?\.css"/, 'href="dist/quantum.css"');
52
+
53
+ const fullTargetPath = path.resolve(targetPath);
54
+ const targetDir = path.dirname(fullTargetPath);
55
+ if (!fs.existsSync(targetDir)) {
56
+ fs.mkdirSync(targetDir, { recursive: true });
57
+ }
58
+
59
+ fs.writeFileSync(fullTargetPath, content);
60
+ console.log(`🚀 Successfully scaffolded '${template}' template to ${targetPath}`);
61
+ console.log(`👉 Run 'quantumcss' to generate the required CSS.`);
62
+ }
63
+
23
64
  function main() {
24
65
  const args = process.argv.slice(2);
66
+
67
+ if (args[0] === 'scaffold') {
68
+ const template = args[1];
69
+ const targetPath = args[2] || 'index.html';
70
+ if (!template) {
71
+ console.error('❌ Error: Please specify a template name (e.g., gaming, blog, travel)');
72
+ process.exit(1);
73
+ }
74
+ scaffold(template, targetPath);
75
+ return;
76
+ }
77
+
25
78
  const watch = args.includes('--watch') || args.includes('-w');
26
79
  const outputPath = args.find(a => !a.startsWith('-')) || 'dist/quantum.css';
27
80
 
package/src/defaults.js CHANGED
@@ -77,8 +77,8 @@ const utilityMaps = {
77
77
  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.75rem', 'pointer']
78
78
  },
79
79
  'btn-secondary': {
80
- property: ['background', 'color', 'border', 'font-weight', 'transition', 'height', 'padding', 'display', 'align-items', 'justify-content', 'border-radius', 'cursor'],
81
- value: ['rgba(255, 255, 255, 0.05)', 'inherit', '1px solid rgba(255, 255, 255, 0.15)', '700', 'all 0.2s ease', '3rem', '0 1.5rem', 'inline-flex', 'center', 'center', '0.75rem', 'pointer']
80
+ property: ['background', 'color', 'border', 'font-weight', 'transition', 'height', 'padding', 'display', 'align-items', 'justify-content', 'border-radius', 'cursor', 'backdrop-filter', '-webkit-backdrop-filter'],
81
+ value: ['rgba(255, 255, 255, 0.05)', 'inherit', '1px solid rgba(255, 255, 255, 0.15)', '700', 'all 0.2s ease', '3rem', '0 1.5rem', 'inline-flex', 'center', 'center', '0.75rem', 'pointer', 'blur(12px)', 'blur(12px)']
82
82
  },
83
83
  'input-starlight': {
84
84
  property: ['background-color', 'border', 'color', 'border-radius', 'padding', 'appearance', 'transition', 'height'],
package/src/generator.js CHANGED
@@ -62,7 +62,10 @@ function generateCSS(configPath) {
62
62
  });
63
63
 
64
64
  const utilities = new Set();
65
- const responsiveUtils = { sm: new Set(), md: new Set(), lg: new Set(), xl: new Set(), '2xl': new Set() };
65
+ const responsiveUtils = {
66
+ sm: new Set(), md: new Set(), lg: new Set(), xl: new Set(), '2xl': new Set(),
67
+ dark: new Set()
68
+ };
66
69
 
67
70
  const sideMap = {
68
71
  p: 'padding', pt: 'padding-top', pr: 'padding-right', pb: 'padding-bottom', pl: 'padding-left',
@@ -74,7 +77,7 @@ function generateCSS(configPath) {
74
77
  gap: 'gap', 'gap-x': 'column-gap', 'gap-y': 'row-gap'
75
78
  };
76
79
 
77
- function processClass(fullCls) {
80
+ function getRulesForClass(fullCls) {
78
81
  let cls = fullCls, variant = null, breakpoint = null, isNeg = false;
79
82
  if (cls.startsWith('-')) { isNeg = true; cls = cls.substring(1); }
80
83
  const parts = cls.split(':');
@@ -82,11 +85,34 @@ function generateCSS(configPath) {
82
85
  while (currentPart < parts.length) {
83
86
  const p = parts[currentPart];
84
87
  if (breakpoints[p]) { breakpoint = p; }
88
+ else if (p === 'dark') { breakpoint = 'dark'; }
85
89
  else if (['hover', 'focus', 'placeholder', 'group-hover'].includes(p)) { variant = p; }
86
90
  else { cls = parts.slice(currentPart).join(':'); break; }
87
91
  currentPart++;
88
92
  }
89
93
 
94
+ // Check Presets
95
+ if (config.componentPresets && config.componentPresets[cls]) {
96
+ const presetClasses = config.componentPresets[cls].split(/\s+/);
97
+ let allGroups = [];
98
+ presetClasses.forEach(pCls => {
99
+ const subGroups = getRulesForClass(pCls);
100
+ if (subGroups) {
101
+ subGroups.forEach(group => {
102
+ // Apply the preset's own breakpoint/variant to sub-groups if they don't have one?
103
+ // Actually, usually presets are used as base classes.
104
+ // If someone does md:btn-primary, we want the md: to apply to all rules in the preset.
105
+ allGroups.push({
106
+ breakpoint: breakpoint || group.breakpoint,
107
+ variant: variant || group.variant,
108
+ rules: group.rules
109
+ });
110
+ });
111
+ }
112
+ });
113
+ return allGroups;
114
+ }
115
+
90
116
  let property = null, value = null, customSelector = null;
91
117
  if (utilityMaps[cls]) {
92
118
  const entry = utilityMaps[cls];
@@ -148,6 +174,11 @@ function generateCSS(configPath) {
148
174
  } else if (prefix === 'duration') {
149
175
  property = 'transition-duration';
150
176
  value = `${valKey}ms`;
177
+ } else if (prefix === 'backdrop' && cParts[1] === 'blur') {
178
+ property = ['backdrop-filter', '-webkit-backdrop-filter'];
179
+ const blurMap = { sm: '4px', md: '12px', lg: '16px', xl: '24px' };
180
+ const blurVal = blurMap[cParts[2]] || (isNaN(parseInt(cParts[2])) ? '8px' : `${cParts[2]}px`);
181
+ value = `blur(${blurVal})`;
151
182
  } else if (sideMap[prefix]) {
152
183
  property = sideMap[prefix];
153
184
  let v = valKey;
@@ -167,25 +198,53 @@ function generateCSS(configPath) {
167
198
  }
168
199
 
169
200
  if (property && value) {
201
+ let rules = Array.isArray(property) ? property.map((p, i) => ` ${p}: ${Array.isArray(value) ? value[i] : value};`) : [` ${property}: ${value};`];
202
+ return [{ breakpoint, variant, customSelector, rules }];
203
+ }
204
+ return null;
205
+ }
206
+
207
+ function processClass(fullCls) {
208
+ const groups = getRulesForClass(fullCls);
209
+ if (!groups) return;
210
+
211
+ // Merge groups with same selector (breakpoint + variant)
212
+ const merged = new Map();
213
+ groups.forEach(group => {
214
+ const key = `${group.breakpoint || ''}|${group.variant || ''}|${group.customSelector || ''}`;
215
+ if (!merged.has(key)) {
216
+ merged.set(key, { ...group, rules: [...group.rules] });
217
+ } else {
218
+ merged.get(key).rules.push(...group.rules);
219
+ }
220
+ });
221
+
222
+ merged.forEach(group => {
223
+ const { breakpoint, variant, customSelector, rules } = group;
170
224
  const escapedFull = fullCls.replace(/([:[\/])/g, '\\$1');
171
225
  let selector = customSelector || `.${escapedFull}`;
172
226
  if (variant) { if (variant === 'group-hover') selector = `.group:hover ${selector}`; else selector += `:${variant}`}
173
- let rules = Array.isArray(property) ? property.map((p, i) => ` ${p}: ${Array.isArray(value) ? value[i] : value};`).join('\n') : ` ${property}: ${value};`;
227
+
174
228
  const block = `${selector} {
175
- ${rules}
229
+ ${rules.join('\n')}
176
230
  }
177
231
  `;
178
232
  if (breakpoint) responsiveUtils[breakpoint].add(block); else utilities.add(block);
179
- }
233
+ });
180
234
  }
181
235
 
182
236
  rawClasses.forEach(processClass);
183
237
  let css = '/* Quantum CSS JIT Output */\n' + Array.from(utilities).join('\n');
184
238
  Object.entries(breakpoints).forEach(([name, width]) => {
185
- if (responsiveUtils[name].size > 0) {
239
+ if (responsiveUtils[name] && responsiveUtils[name].size > 0) {
186
240
  css += `\n@media (min-width: ${width}) {\n${Array.from(responsiveUtils[name]).map(u => ' ' + u.replace(/\n/g, '\n ')).join('\n').trimEnd()}\n}\n`;
187
241
  }
188
242
  });
243
+
244
+ if (responsiveUtils.dark && responsiveUtils.dark.size > 0) {
245
+ css += `\n@media (prefers-color-scheme: dark) {\n${Array.from(responsiveUtils.dark).map(u => ' ' + u.replace(/\n/g, '\n ')).join('\n').trimEnd()}\n}\n`;
246
+ }
247
+
189
248
  return css;
190
249
  }
191
250
 
@@ -0,0 +1,73 @@
1
+ /* Cosmic Animation Library */
2
+
3
+ @keyframes nebula-drift {
4
+ 0% { transform: translate(-5%, -5%) scale(1); opacity: 0.4; }
5
+ 50% { transform: translate(5%, 5%) scale(1.2); opacity: 0.7; }
6
+ 100% { transform: translate(-5%, -5%) scale(1); opacity: 0.4; }
7
+ }
8
+
9
+ @keyframes cosmic-pulse {
10
+ 0%, 100% { box-shadow: 0 0 20px rgba(0, 212, 255, 0.2), 0 0 40px rgba(0, 212, 255, 0.1); }
11
+ 50% { box-shadow: 0 0 40px rgba(0, 212, 255, 0.5), 0 0 80px rgba(0, 212, 255, 0.2); }
12
+ }
13
+
14
+ @keyframes star-twinkle {
15
+ 0%, 100% { opacity: 0.3; transform: scale(0.8); }
16
+ 50% { opacity: 1; transform: scale(1.2); }
17
+ }
18
+
19
+ @keyframes orbit {
20
+ from { transform: rotate(0deg) translateX(20px) rotate(0deg); }
21
+ to { transform: rotate(360deg) translateX(20px) rotate(-360deg); }
22
+ }
23
+
24
+ @keyframes svg-draw {
25
+ from { stroke-dashoffset: 1000; }
26
+ to { stroke-dashoffset: 0; }
27
+ }
28
+
29
+ @keyframes float-y {
30
+ 0%, 100% { transform: translateY(0); }
31
+ 50% { transform: translateY(-20px); }
32
+ }
33
+
34
+ /* Animation Classes */
35
+
36
+ .ani-nebula {
37
+ animation: nebula-drift 20s ease-in-out infinite;
38
+ will-change: transform, opacity;
39
+ }
40
+
41
+ .ani-cosmic-pulse {
42
+ animation: cosmic-pulse 4s ease-in-out infinite;
43
+ }
44
+
45
+ .ani-twinkle {
46
+ animation: star-twinkle var(--twinkle-duration, 3s) ease-in-out infinite;
47
+ }
48
+
49
+ .ani-orbit {
50
+ animation: orbit var(--orbit-duration, 10s) linear infinite;
51
+ }
52
+
53
+ .ani-svg-draw {
54
+ stroke-dasharray: 1000;
55
+ stroke-dashoffset: 1000;
56
+ animation: svg-draw 3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
57
+ }
58
+
59
+ .ani-float {
60
+ animation: float-y 6s ease-in-out infinite;
61
+ }
62
+
63
+ /* Staggered Animations */
64
+ .ani-stagger-1 { animation-delay: 0.1s; }
65
+ .ani-stagger-2 { animation-delay: 0.2s; }
66
+ .ani-stagger-3 { animation-delay: 0.3s; }
67
+ .ani-stagger-4 { animation-delay: 0.4s; }
68
+ .ani-stagger-5 { animation-delay: 0.5s; }
69
+
70
+ /* Speed Modifiers */
71
+ .ani-fast { animation-duration: 0.5s !important; }
72
+ .ani-slow { animation-duration: 8s !important; }
73
+ .ani-slower { animation-duration: 15s !important; }
@@ -102,14 +102,21 @@
102
102
  }
103
103
 
104
104
  .btn-secondary {
105
- background-color: var(--color-secondary);
105
+ background-color: rgba(255, 255, 255, 0.05);
106
106
  color: white;
107
- border-color: var(--color-secondary);
107
+ border: 1px solid rgba(255, 255, 255, 0.1);
108
+ backdrop-filter: blur(12px);
109
+ -webkit-backdrop-filter: blur(12px);
108
110
  }
109
111
 
110
112
  .btn-secondary:hover {
111
- background-color: var(--color-secondary-600);
112
- border-color: var(--color-secondary-600);
113
+ background-color: rgba(255, 255, 255, 0.1);
114
+ }
115
+
116
+ body.light-mode .btn-secondary {
117
+ background-color: rgba(0, 0, 0, 0.03);
118
+ color: #1e293b;
119
+ border-color: rgba(0, 0, 0, 0.1);
113
120
  }
114
121
 
115
122
  .btn-outline {
@@ -284,6 +291,37 @@ input[type="date"]::-webkit-calendar-picker-indicator {
284
291
  border-color: rgba(239, 68, 68, 0.3);
285
292
  }
286
293
 
294
+ /* Light Mode Overrides for Badges */
295
+ body.light-mode .badge-primary {
296
+ background-color: rgba(59, 130, 246, 0.1);
297
+ color: #2563eb;
298
+ border-color: rgba(59, 130, 246, 0.2);
299
+ }
300
+
301
+ body.light-mode .badge-secondary {
302
+ background-color: rgba(0, 0, 0, 0.05);
303
+ color: #475569;
304
+ border-color: rgba(0, 0, 0, 0.1);
305
+ }
306
+
307
+ body.light-mode .badge-success {
308
+ background-color: rgba(16, 185, 129, 0.1);
309
+ color: #059669;
310
+ border-color: rgba(16, 185, 129, 0.2);
311
+ }
312
+
313
+ body.light-mode .badge-warning {
314
+ background-color: rgba(245, 158, 11, 0.1);
315
+ color: #d97706;
316
+ border-color: rgba(245, 158, 11, 0.2);
317
+ }
318
+
319
+ body.light-mode .badge-error {
320
+ background-color: rgba(239, 68, 68, 0.1);
321
+ color: #dc2626;
322
+ border-color: rgba(239, 68, 68, 0.2);
323
+ }
324
+
287
325
  /* Alert Component */
288
326
  .alert {
289
327
  padding: var(--space-4);
@@ -111,6 +111,40 @@
111
111
  --transition-slow: 400ms ease-in-out;
112
112
  }
113
113
 
114
+ /* High Contrast (Accessibility) Mode Support */
115
+ @media (prefers-contrast: more) {
116
+ :root {
117
+ --color-starlight-blue: #0088cc; /* Darkened for better contrast on light */
118
+ --color-starlight-orange: #d14d33;
119
+ --glass-bg: rgba(255, 255, 255, 0.1);
120
+ --glass-border: rgba(255, 255, 255, 0.4);
121
+ --text-muted: rgba(241, 245, 249, 0.8);
122
+ }
123
+ }
124
+
125
+ @media (forced-colors: active) {
126
+ :root {
127
+ --radius-none: 0;
128
+ --radius-sm: 0;
129
+ --radius-md: 0;
130
+ --radius-lg: 0;
131
+ --radius-xl: 0;
132
+ --radius-2xl: 0;
133
+ --radius-3xl: 0;
134
+ --radius-full: 0;
135
+ }
136
+
137
+ .starlight-card, .glass, .q-card {
138
+ border: 2px solid CanvasText !important;
139
+ }
140
+
141
+ .btn-starlight, .q-btn-primary {
142
+ background: ButtonFace !important;
143
+ color: ButtonText !important;
144
+ border: 2px solid ButtonText !important;
145
+ }
146
+ }
147
+
114
148
  /* Dark Mode */
115
149
  @media (prefers-color-scheme: dark) {
116
150
  :root {
@@ -145,6 +179,8 @@ input, textarea, select, button {
145
179
  font-size: inherit;
146
180
  line-height: inherit;
147
181
  color: inherit;
182
+ border: none;
183
+ background: transparent;
148
184
  }
149
185
 
150
186
  textarea {
@@ -1057,21 +1093,39 @@ body.light-mode ::-webkit-scrollbar-thumb { border-color: var(--light-bg); }
1057
1093
 
1058
1094
  /* Starlight Focus Ring */
1059
1095
 
1060
- :focus-visible {
1096
+
1097
+
1098
+ :focus, :focus-visible {
1099
+
1100
+
1061
1101
 
1062
1102
  outline: none;
1063
1103
 
1104
+
1105
+
1064
1106
  box-shadow: 0 0 0 2px var(--color-starlight-deep), 0 0 0 4px var(--color-starlight-blue);
1065
1107
 
1108
+
1109
+
1066
1110
  transition: box-shadow var(--transition-fast);
1067
1111
 
1112
+
1113
+
1068
1114
  }
1069
1115
 
1070
1116
 
1071
1117
 
1072
- body.light-mode :focus-visible {
1073
1118
 
1074
- box-shadow: 0 0 0 2px var(--light-bg), 0 0 0 4px var(--color-starlight-blue);
1119
+
1120
+
1121
+
1122
+ body.light-mode :focus, body.light-mode :focus-visible {
1123
+
1124
+
1125
+
1126
+ box-shadow: 0 0 0 2px var(--light-bg), 0 0 0 4px rgba(59, 130, 246, 0.4);
1127
+
1128
+
1075
1129
 
1076
1130
  }
1077
1131
 
@@ -235,25 +235,46 @@ body.light-mode .dropdown-item:hover {
235
235
  color: #1e293b;
236
236
  }
237
237
 
238
- /* 7. Textarea Fixes */
239
- textarea.input-starlight, .textarea-starlight {
238
+ /* 7. Input & Glass Fixes */
239
+ .input-starlight, .textarea-starlight {
240
240
  height: auto;
241
- min-height: 120px;
242
241
  padding: 1rem;
243
242
  background-color: rgba(255, 255, 255, 0.04);
244
243
  border: 1px solid rgba(255, 255, 255, 0.15);
245
244
  border-radius: 0.75rem;
246
245
  color: inherit;
247
246
  width: 100%;
247
+ }
248
+
249
+ textarea.input-starlight, .textarea-starlight {
250
+ min-height: 120px;
248
251
  display: block;
249
252
  }
250
253
 
251
- body.light-mode textarea.input-starlight,
254
+ body.light-mode .input-starlight,
252
255
  body.light-mode .textarea-starlight {
253
256
  background-color: #ffffff;
254
257
  border-color: #cbd5e1;
255
258
  }
256
259
 
260
+ body.light-mode .glass {
261
+ background-color: rgba(0, 0, 0, 0.02);
262
+ border-color: rgba(0, 0, 0, 0.05);
263
+ }
264
+
265
+ /* 8. Input Focus States */
266
+ .input-starlight:focus, .textarea-starlight:focus {
267
+ outline: none;
268
+ border-color: var(--color-starlight-blue);
269
+ box-shadow: 0 0 0 4px rgba(0, 212, 255, 0.1);
270
+ }
271
+
272
+ body.light-mode .input-starlight:focus,
273
+ body.light-mode .textarea-starlight:focus {
274
+ border-color: #93c5fd;
275
+ box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.08);
276
+ }
277
+
257
278
  /* 9. Code Interface Window */
258
279
  .code-window {
259
280
  background: rgba(0, 0, 0, 0.4);
@@ -382,3 +403,25 @@ body.light-mode .accordion-starlight .accordion-content {
382
403
  background: #f1f5f9 !important;
383
404
  color: #475569 !important;
384
405
  }
406
+
407
+ @media (forced-colors: active) {
408
+ button:focus, input:focus, select:focus, textarea:focus {
409
+ outline: 2px solid SelectedItem !important;
410
+ outline-offset: 2px;
411
+ }
412
+
413
+ .starlight-card:hover, .accordion-starlight.accordion-item:hover {
414
+ border-color: SelectedItem !important;
415
+ }
416
+
417
+ .text-gradient-starlight, .bg-starlight {
418
+ background: none !important;
419
+ -webkit-text-fill-color: CanvasText !important;
420
+ color: CanvasText !important;
421
+ text-decoration: underline;
422
+ }
423
+
424
+ .btn-starlight {
425
+ border: 2px solid ButtonText !important;
426
+ }
427
+ }