juxscript 1.0.21 → 1.0.22

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/bin/cli.js CHANGED
@@ -49,6 +49,7 @@ console.log(` Output: ${PATHS.frontendDist}`);
49
49
  console.log(` Lib: ${PATHS.juxLib}\n`);
50
50
 
51
51
  const command = process.argv[2];
52
+ const subCommand = process.argv[3]; // For serve <pagename>
52
53
  const watchMode = process.argv.includes('--watch');
53
54
  const bundleMode = process.argv.includes('--bundle');
54
55
 
@@ -233,41 +234,34 @@ async function buildProject(isServe = false) {
233
234
  // Create structure
234
235
  fs.mkdirSync(juxDir, { recursive: true });
235
236
 
236
- // Create index.jux with proper imports
237
- const indexContent = `// Welcome to JUX!
238
- import { jux } from 'juxscript';
239
-
240
- // Apply a layout preset (optional)
241
- // import 'juxscript/presets/notion.js';
242
-
243
- // Create your app structure
244
- jux.container('app-container')
245
- .direction('column')
246
- .gap(20)
247
- .style('padding: 40px;')
248
- .render('body');
249
-
250
- jux.hero('welcome-hero', {
251
- title: 'Welcome to JUX',
252
- subtitle: 'A JavaScript UX authorship platform'
253
- }).render('#app-container');
254
-
255
- jux.divider({}).render('#app-container');
256
-
257
- jux.write(\`
258
- <h2>Getting Started</h2>
259
- <p>Edit <code>jux/index.jux</code> to build your app.</p>
260
- <ul>
261
- <li>Run <code>npx jux build</code> to compile</li>
262
- <li>Run <code>npx jux serve</code> for dev mode</li>
263
- <li>Serve <code>jux-dist/</code> from your backend</li>
264
- </ul>
265
- \`).render('#app-container');
266
- `;
237
+ // Copy hey.jux from presets as starter template
238
+ const heyTemplatePath = path.join(PATHS.packageRoot, 'presets', 'hey.jux');
239
+ const heyTargetPath = path.join(juxDir, 'hey.jux');
240
+
241
+ if (fs.existsSync(heyTemplatePath)) {
242
+ fs.copyFileSync(heyTemplatePath, heyTargetPath);
243
+ console.log('+ Created jux/hey.jux (starter template)');
244
+ } else {
245
+ console.warn('⚠️ hey.jux template not found in presets/');
246
+ }
267
247
 
268
- const targetPath = path.join(juxDir, 'index.jux');
269
- fs.writeFileSync(targetPath, indexContent);
270
- console.log('✅ Created jux/index.jux');
248
+ // Copy over presets/*.css as styles/ to jux/
249
+ const presetsSrc = path.join(PATHS.packageRoot, 'presets');
250
+ const stylesDest = path.join(juxDir, 'styles');
251
+
252
+ if (fs.existsSync(presetsSrc)) {
253
+ fs.mkdirSync(stylesDest, { recursive: true });
254
+
255
+ const presetFiles = fs.readdirSync(presetsSrc);
256
+ presetFiles.forEach(file => {
257
+ if (file.endsWith('.css')) {
258
+ const srcFile = path.join(presetsSrc, file);
259
+ const destFile = path.join(stylesDest, file);
260
+ fs.copyFileSync(srcFile, destFile);
261
+ console.log(`+ Copied preset style: styles/${file}`);
262
+ }
263
+ });
264
+ }
271
265
 
272
266
  // Create package.json if it doesn't exist
273
267
  const pkgPath = path.join(PATHS.projectRoot, 'package.json');
@@ -297,15 +291,13 @@ node_modules/
297
291
 
298
292
  if (!fs.existsSync(gitignorePath)) {
299
293
  fs.writeFileSync(gitignorePath, gitignoreContent);
300
- console.log(' Created .gitignore');
294
+ console.log('+ Created .gitignore');
301
295
  }
302
296
 
303
- console.log(' Created jux/ directory\n');
297
+ console.log('+ Created jux/ directory\n');
304
298
  console.log('Next steps:');
305
- console.log(' 1. npm install # Install juxscript');
306
- console.log(' 2. Edit jux/index.jux # Build your app');
307
- console.log(' 3. npx jux build # Compile to jux-dist/');
308
- console.log(' 4. Serve jux-dist/ from your backend\n');
299
+ console.log(' npx jux serve hey # Start dev server for hey.jux\n');
300
+ console.log('Check out the docs: https://juxscript.com/docs\n');
309
301
 
310
302
  } else if (command === 'build') {
311
303
  // ✅ Always builds router bundle
@@ -313,28 +305,37 @@ node_modules/
313
305
  console.log(`✅ Build complete: ${PATHS.frontendDist}`);
314
306
 
315
307
  } else if (command === 'serve') {
316
- // ✅ Always serves router bundle
308
+ // ✅ Serve with optional page parameter
309
+ const pageName = subCommand; // e.g., "hey" from "npx jux serve hey"
310
+
311
+ if (pageName) {
312
+ console.log(`🎯 Serving specific page: ${pageName}\n`);
313
+ }
314
+
317
315
  await buildProject(true);
318
316
 
319
- const port = parseInt(process.argv[3]) || 3000;
320
- await start(port);
317
+ const port = pageName ? 3000 : (parseInt(subCommand) || 3000);
318
+
319
+ // Start server
320
+ await start(port, pageName);
321
321
 
322
322
  } else {
323
323
  console.log(`
324
324
  JUX CLI - A JavaScript UX authorship platform
325
325
 
326
326
  Usage:
327
- npx jux init Initialize a new JUX project
328
- npx jux build Build router bundle to ./jux-dist/
329
- npx jux serve [port] Start dev server with hot reload (default: 3000)
327
+ npx jux init Initialize a new JUX project
328
+ npx jux build Build router bundle to ./jux-dist/
329
+ npx jux serve [page] Start dev server (optionally for specific page)
330
+ npx jux serve [port] Start dev server on custom port
330
331
 
331
332
  Project Structure:
332
333
  my-project/
333
334
  ├── jux/ # Your .jux source files
334
- │ ├── index.jux # Entry point
335
- │ └── pages/ # Additional pages
336
- ├── jux-dist/ # Build output (git-ignore this)
337
- ├── server/ # Your backend
335
+ │ ├── hey.jux # Starter page (created by init)
336
+ │ └── pages/ # Additional pages
337
+ ├── jux-dist/ # Build output (git-ignore this)
338
+ ├── server/ # Your backend
338
339
  └── package.json
339
340
 
340
341
  Import Style:
@@ -343,15 +344,17 @@ Import Style:
343
344
  import 'juxscript/presets/notion.js';
344
345
 
345
346
  Getting Started:
346
- 1. npx jux init # Create project structure
347
- 2. npm install # Install dependencies
348
- 3. npx jux serve # Dev server with hot reload
349
- 4. Serve jux-dist/ from your backend
347
+ 1. npx jux init # Create project structure
348
+ 2. npm install # Install dependencies
349
+ 3. npx jux serve hey # Dev server for hey.jux at localhost:3000/hey
350
+ 4. npx jux serve # Dev server for all pages
351
+ 5. Serve jux-dist/ from your backend
350
352
 
351
353
  Examples:
352
- npx jux build Build production bundle
353
- npx jux serve Start dev server on port 3000
354
- npx jux serve 8080 Start dev server on port 8080
354
+ npx jux build Build production bundle
355
+ npx jux serve Start dev server on port 3000 (all pages)
356
+ npx jux serve hey Start dev server at localhost:3000/hey
357
+ npx jux serve 8080 Start dev server on port 8080
355
358
  `);
356
359
  }
357
360
  })();
@@ -87,9 +87,12 @@ async function serve(port = 3000, distDir = './jux-dist') {
87
87
 
88
88
  // Start HTTP server
89
89
  server.listen(port, () => {
90
- console.log(`🚀 JUX dev server running at http://localhost:${port}`);
91
- console.log(` Serving: ${absoluteDistDir}`);
92
- console.log(` Press Ctrl+C to stop\n`);
90
+ console.log(`\n🚀 JUX dev server running`);
91
+ console.log(` Local: http://localhost:${port}${pageName ? '/' + pageName : ''}`);
92
+ if (pageName) {
93
+ console.log(` (Root redirects to /${pageName})`);
94
+ }
95
+ console.log('');
93
96
  });
94
97
 
95
98
  // Start file watcher
@@ -113,6 +116,27 @@ async function serve(port = 3000, distDir = './jux-dist') {
113
116
  return { server };
114
117
  }
115
118
 
116
- export async function start(port = 3000) {
117
- return serve(port, './jux-dist');
119
+ export async function start(port = 3000, pageName = null) {
120
+ const app = express();
121
+
122
+ // ...existing code for static files...
123
+
124
+ // If pageName specified, redirect root to that page
125
+ if (pageName) {
126
+ app.get('/', (req, res) => {
127
+ res.redirect(`/${pageName}`);
128
+ });
129
+ console.log(`🎯 Root (/) redirects to /${pageName}`);
130
+ }
131
+
132
+ // ...existing code...
133
+
134
+ app.listen(port, () => {
135
+ console.log(`\n🚀 JUX dev server running`);
136
+ console.log(` Local: http://localhost:${port}${pageName ? '/' + pageName : ''}`);
137
+ if (pageName) {
138
+ console.log(` (Root redirects to /${pageName})`);
139
+ }
140
+ console.log('');
141
+ });
118
142
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "lib/jux.js",
@@ -0,0 +1,46 @@
1
+ import { jux } from 'juxscript';
2
+
3
+ // Welcome page - edit this to build your app
4
+ jux.hero('welcome-hero', {
5
+ title: 'Welcome to JUX',
6
+ subtitle: 'A JavaScript UX authorship platform'
7
+ }).render('#app');
8
+
9
+ jux.divider().render('#app');
10
+
11
+ jux.container('getting-started')
12
+ .style('max-width: 800px; margin: 2rem auto; padding: 2rem;')
13
+ .render('#app');
14
+
15
+ jux.heading('gs-title')
16
+ .level(2)
17
+ .text('Getting Started')
18
+ .render('#getting-started');
19
+
20
+ jux.paragraph('gs-intro')
21
+ .text('Edit jux/hey.jux to start building your application.')
22
+ .style('margin: 1rem 0;')
23
+ .render('#getting-started');
24
+
25
+ jux.list('gs-steps', {
26
+ items: [
27
+ 'Run npx jux build to compile',
28
+ 'Run npx jux serve to dev with hot reload',
29
+ 'Run npx jux serve hey to serve just this page',
30
+ 'Serve jux-dist/ from your backend in production'
31
+ ]
32
+ }).render('#getting-started');
33
+
34
+ jux.divider().render('#app');
35
+
36
+ jux.button('demo-button')
37
+ .label('Click Me!')
38
+ .variant('primary')
39
+ .bind('click', () => {
40
+ jux.alert('demo-alert')
41
+ .type('success')
42
+ .message('Button clicked! You are ready to build with JUX.')
43
+ .render('#app');
44
+ })
45
+ .style('margin: 2rem auto; display: block;')
46
+ .render('#app');
@@ -45,8 +45,6 @@ body {
45
45
  align-items: center;
46
46
  padding: 0 var(--space-lg);
47
47
  height: 48px;
48
- position: sticky;
49
- top: 0;
50
48
  z-index: 100;
51
49
  transition: background-color var(--transition-base), border-color var(--transition-base);
52
50
  }
@@ -92,7 +90,8 @@ body {
92
90
  /* ============================================
93
91
  SIDEBAR - Left Navigation
94
92
  ============================================ */
95
- #appsidebar {
93
+ .jux-sidebar,
94
+ #appaside {
96
95
  grid-area: sidebar;
97
96
  width: 260px;
98
97
  background: var(--color-surface-base);
@@ -100,47 +99,95 @@ body {
100
99
  display: flex;
101
100
  flex-direction: column;
102
101
  overflow: hidden;
103
- transition: width var(--transition-base), background-color var(--transition-base);
102
+ transition: width var(--transition-base);
103
+ position: relative;
104
104
  }
105
105
 
106
- #appsidebar-content {
106
+ .jux-sidebar-collapsed,
107
+ #appaside.jux-sidebar-collapsed {
108
+ width: 60px;
109
+ }
110
+
111
+ .jux-sidebar-expand-on-hover.jux-sidebar-collapsed.jux-sidebar-hover-expanded,
112
+ #appaside.jux-sidebar-expand-on-hover.jux-sidebar-collapsed.jux-sidebar-hover-expanded {
113
+ width: 260px;
114
+ z-index: 1000;
115
+ /* box-shadow: var(--shadow-lg); */
116
+ }
117
+
118
+ .jux-sidebar-menu {
107
119
  flex: 1;
108
120
  overflow-y: auto;
109
121
  overflow-x: hidden;
110
- padding: var(--space-lg);
122
+ padding: var(--space-sm);
111
123
  }
112
124
 
113
- /* Custom scrollbar for sidebar */
114
- #appsidebar-content::-webkit-scrollbar {
125
+ .jux-sidebar-menu::-webkit-scrollbar {
115
126
  width: 6px;
116
127
  }
117
128
 
118
- #appsidebar-content::-webkit-scrollbar-track {
119
- background: transparent;
120
- }
121
-
122
- #appsidebar-content::-webkit-scrollbar-thumb {
129
+ .jux-sidebar-menu::-webkit-scrollbar-thumb {
123
130
  background: var(--color-border);
124
131
  border-radius: var(--radius-full);
125
132
  }
126
133
 
127
- #appsidebar-content::-webkit-scrollbar-thumb:hover {
128
- background: var(--color-border-hover);
134
+ .jux-sidebar-toggle {
135
+ margin-top: auto;
136
+ order: 999;
137
+ width: 32px;
138
+ height: 32px;
139
+ margin-left: auto;
140
+ margin-right: var(--space-lg);
141
+ margin-bottom: var(--space-lg);
142
+ border: none;
143
+ background: var(--color-surface-elevated);
144
+ color: var(--color-text-secondary);
145
+ border-radius: var(--radius-md);
146
+ cursor: pointer;
147
+ display: flex;
148
+ align-items: center;
149
+ justify-content: center;
150
+ transition: all var(--transition-base);
151
+ border: var(--border-width) solid var(--color-border);
129
152
  }
130
153
 
131
- #appsidebar-menu {
154
+ .jux-sidebar-toggle:hover {
155
+ background: var(--color-surface-hover);
156
+ color: var(--color-text-primary);
157
+ }
158
+
159
+ .jux-sidebar-collapsed .jux-sidebar-toggle,
160
+ #appaside.jux-sidebar-collapsed .jux-sidebar-toggle {
161
+ margin-left: 14px;
162
+ margin-right: 14px;
163
+ }
164
+
165
+ .jux-sidebar-hover-expanded .jux-sidebar-toggle,
166
+ #appaside.jux-sidebar-hover-expanded .jux-sidebar-toggle {
167
+ margin-left: auto;
168
+ margin-right: var(--space-lg);
169
+ }
170
+
171
+ .jux-sidebar-toggle-icon {
172
+ display: flex;
173
+ align-items: center;
174
+ justify-content: center;
175
+ font-size: var(--font-size-lg);
176
+ }
177
+
178
+ /* Menu inside sidebar */
179
+ .jux-sidebar .jux-menu {
132
180
  display: flex;
133
181
  flex-direction: column;
134
182
  gap: var(--space-xs);
135
183
  }
136
184
 
137
- /* Menu items */
138
- #appsidebar-menu .jux-menu-item {
185
+ .jux-sidebar .jux-menu-item {
139
186
  list-style: none;
140
187
  }
141
188
 
142
- #appsidebar-menu .jux-menu-button,
143
- #appsidebar-menu a {
189
+ .jux-sidebar .jux-menu-link,
190
+ .jux-sidebar .jux-menu-button {
144
191
  display: flex;
145
192
  align-items: center;
146
193
  gap: var(--space-md);
@@ -156,29 +203,83 @@ body {
156
203
  background: transparent;
157
204
  width: 100%;
158
205
  text-align: left;
206
+ white-space: nowrap;
207
+ position: relative;
159
208
  }
160
209
 
161
- #appsidebar-menu .jux-menu-button:hover,
162
- #appsidebar-menu a:hover {
210
+ .jux-sidebar .jux-menu-link:hover,
211
+ .jux-sidebar .jux-menu-button:hover {
163
212
  background: var(--color-surface-hover);
164
213
  color: var(--color-text-primary);
165
214
  }
166
215
 
167
- #appsidebar-menu .jux-menu-button:active,
168
- #appsidebar-menu a:active {
169
- background: var(--color-surface-active);
170
- }
171
-
172
- /* Active menu item */
173
- #appsidebar-menu .jux-menu-item.active .jux-menu-button,
174
- #appsidebar-menu .jux-menu-item.active a {
216
+ .jux-sidebar .jux-menu-item-active > .jux-menu-link,
217
+ .jux-sidebar .jux-menu-item-active > .jux-menu-button,
218
+ .jux-sidebar .jux-menu-link-active {
175
219
  background: var(--color-brand-subtle);
176
220
  color: var(--color-brand);
177
221
  font-weight: var(--font-weight-semibold);
178
222
  }
179
223
 
180
- /* Submenu */
181
- #appsidebar-menu .jux-menu-submenu {
224
+ .jux-sidebar .jux-menu-icon {
225
+ flex-shrink: 0;
226
+ width: 20px;
227
+ display: flex;
228
+ align-items: center;
229
+ justify-content: center;
230
+ }
231
+
232
+ .jux-sidebar .jux-menu-label {
233
+ flex: 1;
234
+ overflow: hidden;
235
+ text-overflow: ellipsis;
236
+ }
237
+
238
+ .jux-sidebar .jux-menu-chevron {
239
+ flex-shrink: 0;
240
+ transition: transform var(--transition-fast);
241
+ font-size: var(--font-size-sm);
242
+ color: var(--color-text-tertiary);
243
+ }
244
+
245
+ .jux-sidebar .jux-menu-item-open > .jux-menu-button .jux-menu-chevron {
246
+ transform: rotate(90deg);
247
+ }
248
+
249
+ /* Collapsed state - hide labels and chevrons */
250
+ .jux-sidebar-collapsed .jux-menu-label,
251
+ .jux-sidebar-collapsed .jux-menu-chevron,
252
+ #appaside.jux-sidebar-collapsed .jux-menu-label,
253
+ #appaside.jux-sidebar-collapsed .jux-menu-chevron {
254
+ display: none;
255
+ }
256
+
257
+ .jux-sidebar-collapsed .jux-menu-link,
258
+ .jux-sidebar-collapsed .jux-menu-button,
259
+ #appaside.jux-sidebar-collapsed .jux-menu-link,
260
+ #appaside.jux-sidebar-collapsed .jux-menu-button {
261
+ justify-content: center;
262
+ padding: var(--space-sm);
263
+ }
264
+
265
+ /* Hover-expanded state - show labels */
266
+ .jux-sidebar-hover-expanded .jux-menu-label,
267
+ .jux-sidebar-hover-expanded .jux-menu-chevron,
268
+ #appaside.jux-sidebar-hover-expanded .jux-menu-label,
269
+ #appaside.jux-sidebar-hover-expanded .jux-menu-chevron {
270
+ display: inline;
271
+ }
272
+
273
+ .jux-sidebar-hover-expanded .jux-menu-link,
274
+ .jux-sidebar-hover-expanded .jux-menu-button,
275
+ #appaside.jux-sidebar-hover-expanded .jux-menu-link,
276
+ #appaside.jux-sidebar-hover-expanded .jux-menu-button {
277
+ justify-content: flex-start;
278
+ padding: var(--space-sm) var(--space-md);
279
+ }
280
+
281
+ /* Submenus */
282
+ .jux-sidebar .jux-menu-submenu {
182
283
  display: none;
183
284
  flex-direction: column;
184
285
  gap: var(--space-xs);
@@ -188,21 +289,26 @@ body {
188
289
  margin-left: var(--space-lg);
189
290
  }
190
291
 
191
- #appsidebar-menu .jux-menu-submenu.open {
292
+ .jux-sidebar .jux-menu-item-open > .jux-menu-submenu {
192
293
  display: flex;
193
294
  }
194
295
 
195
- #appsidebar-menu .jux-menu-submenu .jux-menu-button,
196
- #appsidebar-menu .jux-menu-submenu a {
296
+ .jux-sidebar .jux-menu-submenu .jux-menu-link,
297
+ .jux-sidebar .jux-menu-submenu .jux-menu-button {
197
298
  font-size: var(--font-size-xs);
198
299
  padding: var(--space-xs) var(--space-md);
199
300
  }
200
301
 
201
- #appsidebar-footer {
202
- padding: var(--space-lg);
203
- border-top: var(--border-width) solid var(--color-border);
204
- background: var(--color-surface-elevated);
205
- transition: border-color var(--transition-base);
302
+ /* Hide submenus when collapsed */
303
+ .jux-sidebar-collapsed .jux-menu-submenu,
304
+ #appaside.jux-sidebar-collapsed .jux-menu-submenu {
305
+ display: none !important;
306
+ }
307
+
308
+ /* Show submenus when hover-expanded and open */
309
+ .jux-sidebar-hover-expanded .jux-menu-item-open > .jux-menu-submenu,
310
+ #appaside.jux-sidebar-hover-expanded .jux-menu-item-open > .jux-menu-submenu {
311
+ display: flex !important;
206
312
  }
207
313
 
208
314
  /* ============================================
@@ -245,29 +351,29 @@ body {
245
351
  }
246
352
 
247
353
  /* ============================================
248
- RIGHT SIDEBAR (Aside) - Optional Panel
354
+ RIGHT SIDEBAR - Optional Panel
249
355
  ============================================ */
250
- #appaside {
356
+ #appsidebar {
251
357
  grid-area: aside;
252
358
  width: 280px;
253
359
  background: var(--color-surface-base);
254
360
  border-left: var(--border-width) solid var(--color-border);
255
- display: none; /* Hidden by default */
361
+ display: none;
256
362
  flex-direction: column;
257
363
  overflow: hidden;
258
364
  transition: width var(--transition-base), background-color var(--transition-base);
259
365
  }
260
366
 
261
- #appaside.show {
367
+ #appsidebar.show {
262
368
  display: flex;
263
369
  }
264
370
 
265
- #appaside.collapsed {
371
+ #appsidebar.collapsed {
266
372
  width: 0;
267
373
  border: none;
268
374
  }
269
375
 
270
- #appaside-header {
376
+ #appsidebar-header {
271
377
  padding: var(--space-lg);
272
378
  border-bottom: var(--border-width) solid var(--color-border);
273
379
  font-weight: var(--font-weight-semibold);
@@ -277,31 +383,31 @@ body {
277
383
  letter-spacing: 0.05em;
278
384
  }
279
385
 
280
- #appaside-content {
386
+ #appsidebar-content {
281
387
  flex: 1;
282
388
  overflow-y: auto;
283
389
  overflow-x: hidden;
284
390
  padding: var(--space-lg);
285
391
  }
286
392
 
287
- #appaside-content::-webkit-scrollbar {
393
+ #appsidebar-content::-webkit-scrollbar {
288
394
  width: 6px;
289
395
  }
290
396
 
291
- #appaside-content::-webkit-scrollbar-track {
397
+ #appsidebar-content::-webkit-scrollbar-track {
292
398
  background: transparent;
293
399
  }
294
400
 
295
- #appaside-content::-webkit-scrollbar-thumb {
401
+ #appsidebar-content::-webkit-scrollbar-thumb {
296
402
  background: var(--color-border);
297
403
  border-radius: var(--radius-full);
298
404
  }
299
405
 
300
- #appaside-content::-webkit-scrollbar-thumb:hover {
406
+ #appsidebar-content::-webkit-scrollbar-thumb:hover {
301
407
  background: var(--color-border-hover);
302
408
  }
303
409
 
304
- #appaside-footer {
410
+ #appsidebar-footer {
305
411
  padding: var(--space-lg);
306
412
  border-top: var(--border-width) solid var(--color-border);
307
413
  font-size: var(--font-size-xs);
@@ -380,6 +486,24 @@ body {
380
486
  transform: scale(0.95);
381
487
  }
382
488
 
489
+ /* Mobile Sidebar Backdrop */
490
+ .jux-sidebar-backdrop {
491
+ display: none;
492
+ position: fixed;
493
+ top: 0;
494
+ left: 0;
495
+ right: 0;
496
+ bottom: 0;
497
+ background: rgba(0, 0, 0, 0.5);
498
+ z-index: 999;
499
+ backdrop-filter: blur(4px);
500
+ animation: fadeIn 0.2s ease-out;
501
+ }
502
+
503
+ .jux-sidebar-backdrop.show {
504
+ display: block;
505
+ }
506
+
383
507
  /* ============================================
384
508
  RESPONSIVE DESIGN
385
509
  ============================================ */
@@ -391,24 +515,40 @@ body {
391
515
  #app {
392
516
  grid-template-columns: auto 1fr;
393
517
  grid-template-areas:
394
- "header header"
518
+ "sidebar header"
395
519
  "sidebar main"
396
- "footer footer";
520
+ "sidebar footer";
397
521
  }
398
522
  }
399
523
 
400
524
  @media (max-width: 768px) {
401
- #appsidebar {
525
+ /* Move sidebar toggle to header on mobile */
526
+ .jux-sidebar-toggle,
527
+ #appaside .jux-sidebar-toggle {
528
+ position: fixed;
529
+ top: 8px;
530
+ left: var(--space-md);
531
+ z-index: 1001;
532
+ margin: 0;
533
+ }
534
+
535
+ .jux-sidebar,
536
+ #appaside {
537
+ display: none;
402
538
  position: fixed;
403
539
  left: 0;
404
- top: 48px;
540
+ top: 0;
405
541
  bottom: 0;
406
- z-index: 90;
542
+ z-index: 1000;
543
+ width: 260px;
544
+ box-shadow: var(--shadow-xl);
407
545
  transform: translateX(-100%);
408
546
  transition: transform var(--transition-base);
409
547
  }
410
548
 
411
- #appsidebar.open {
549
+ .jux-sidebar.open,
550
+ #appaside.open {
551
+ display: flex;
412
552
  transform: translateX(0);
413
553
  }
414
554
 
@@ -425,7 +565,7 @@ body {
425
565
  }
426
566
 
427
567
  #appheader {
428
- padding: 0 var(--space-md);
568
+ padding: 0 var(--space-md) 0 calc(var(--space-md) + 48px);
429
569
  }
430
570
  }
431
571
 
@@ -466,6 +606,23 @@ body {
466
606
  JUX COMPONENT STYLES
467
607
  ============================================ */
468
608
 
609
+ /* Icon Component */
610
+ .jux-icon {
611
+ display: inline-flex;
612
+ align-items: center;
613
+ justify-content: center;
614
+ vertical-align: middle;
615
+ line-height: 1;
616
+ }
617
+
618
+ .jux-icon img,
619
+ .jux-icon svg {
620
+ display: block;
621
+ width: 100%;
622
+ height: 100%;
623
+ object-fit: contain;
624
+ }
625
+
469
626
  /* Hero Component */
470
627
  .jux-hero {
471
628
  padding: var(--space-3xl) 0;
@@ -519,6 +676,7 @@ body {
519
676
  background: linear-gradient(90deg, var(--color-brand), var(--color-info));
520
677
  transform: translateY(-100%);
521
678
  transition: transform var(--transition-base);
679
+ z-index: 1;
522
680
  }
523
681
 
524
682
  .jux-card:hover {
@@ -537,6 +695,9 @@ body {
537
695
  color: var(--color-text-primary);
538
696
  margin-bottom: var(--space-lg);
539
697
  letter-spacing: -0.01em;
698
+ position: relative;
699
+ z-index: 2;
700
+ padding-right: calc(var(--font-size-2xl) + var(--space-lg));
540
701
  }
541
702
 
542
703
  .jux-card-icon {
@@ -547,6 +708,7 @@ body {
547
708
  line-height: 1;
548
709
  opacity: 0.8;
549
710
  transition: opacity var(--transition-fast);
711
+ z-index: 2;
550
712
  }
551
713
 
552
714
  .jux-card:hover .jux-card-icon {
@@ -557,6 +719,8 @@ body {
557
719
  font-size: var(--font-size-sm);
558
720
  color: var(--color-text-secondary);
559
721
  line-height: var(--line-height-relaxed);
722
+ position: relative;
723
+ z-index: 2;
560
724
  }
561
725
 
562
726
  .jux-card-body .jux-card-title {
@@ -579,51 +743,119 @@ body {
579
743
  font-weight: var(--font-weight-normal);
580
744
  }
581
745
 
746
+ .jux-card-footer {
747
+ margin-top: var(--space-xl);
748
+ padding-top: var(--space-lg);
749
+ border-top: var(--border-width) solid var(--color-border);
750
+ }
751
+
752
+ .jux-card-actions {
753
+ display: flex;
754
+ gap: var(--space-md);
755
+ justify-content: flex-start;
756
+ }
757
+
582
758
  /* List Component */
583
759
  .jux-list {
584
760
  display: flex;
585
761
  flex-direction: column;
586
- gap: var(--space-sm);
762
+ gap: var(--space-md);
587
763
  list-style: none;
588
764
  padding: 0;
589
- margin: 0;
765
+ margin: var(--space-lg);
590
766
  }
591
767
 
592
768
  .jux-list-wrapper {
593
- /* No extra styling - list renders inside cards */
769
+ display: flex;
770
+ flex-direction: column;
771
+ gap: var(--space-md);
594
772
  }
595
773
 
596
774
  .jux-list-header {
597
- font-size: var(--font-size-lg);
775
+ font-size: var(--font-size-sm);
598
776
  font-weight: var(--font-weight-semibold);
599
- color: var(--color-text-primary);
777
+ color: var(--color-text-secondary);
600
778
  margin-bottom: var(--space-lg);
601
- letter-spacing: -0.01em;
779
+ padding: 0 var(--space-lg);
780
+ text-transform: uppercase;
781
+ letter-spacing: 0.05em;
602
782
  }
603
783
 
604
784
  .jux-list-item {
605
785
  display: flex;
606
786
  align-items: center;
607
- gap: var(--space-md);
787
+ gap: var(--space-lg);
608
788
  padding: var(--space-lg);
609
- background: var(--color-surface-base);
789
+ background: var(--color-surface-elevated);
610
790
  border: var(--border-width) solid var(--color-border);
611
- border-radius: var(--radius-md);
612
- transition: all var(--transition-fast);
791
+ border-radius: var(--radius-lg);
792
+ transition: all var(--transition-base);
613
793
  cursor: pointer;
614
794
  }
615
795
 
616
796
  .jux-list-item:hover {
617
797
  background: var(--color-surface-hover);
618
798
  border-color: var(--color-border-hover);
619
- transform: translateX(4px);
620
799
  box-shadow: var(--shadow-sm);
621
800
  }
622
801
 
623
- .jux-list-item::before {
624
- content: attr(data-icon);
625
- font-size: var(--font-size-xl);
802
+ .jux-list-item.jux-list-item-selected {
803
+ border-color: var(--color-brand);
804
+ box-shadow: 0 0 0 3px var(--color-brand-subtle);
805
+ }
806
+
807
+ .jux-list-item-icon {
808
+ display: flex;
809
+ align-items: center;
810
+ justify-content: center;
626
811
  flex-shrink: 0;
812
+ width: 24px;
813
+ height: 24px;
814
+ color: var(--color-text-primary);
815
+ }
816
+
817
+ .jux-list-item-content {
818
+ flex: 1;
819
+ display: flex;
820
+ flex-direction: column;
821
+ gap: var(--space-xs);
822
+ }
823
+
824
+ .jux-list-item-title {
825
+ font-size: var(--font-size-base);
826
+ font-weight: var(--font-weight-semibold);
827
+ color: var(--color-text-primary);
828
+ line-height: var(--line-height-tight);
829
+ }
830
+
831
+ .jux-list-item-body {
832
+ font-size: var(--font-size-sm);
833
+ color: var(--color-text-secondary);
834
+ line-height: var(--line-height-normal);
835
+ }
836
+
837
+ .jux-list-item-metadata {
838
+ font-size: var(--font-size-xs);
839
+ color: var(--color-text-tertiary);
840
+ margin-left: auto;
841
+ padding-left: var(--space-lg);
842
+ white-space: nowrap;
843
+ }
844
+
845
+ .jux-list-item-success {
846
+ border-left: 3px solid var(--color-success);
847
+ }
848
+
849
+ .jux-list-item-warning {
850
+ border-left: 3px solid var(--color-warning);
851
+ }
852
+
853
+ .jux-list-item-error {
854
+ border-left: 3px solid var(--color-danger);
855
+ }
856
+
857
+ .jux-list-item-info {
858
+ border-left: 3px solid var(--color-info);
627
859
  }
628
860
 
629
861
  /* Container Component */
@@ -841,103 +1073,122 @@ body {
841
1073
 
842
1074
  /* File Upload Component */
843
1075
  .jux-fileupload {
844
- margin-bottom: var(--space-lg);
845
- position: relative;
1076
+ margin: var(--space-xl);
1077
+ display: flex;
1078
+ flex-direction: column;
1079
+ gap: var(--space-sm);
1080
+ min-width: 200px;
1081
+ flex: 1;
1082
+ align-items: center;
1083
+ }
1084
+
1085
+ .jux-fileupload-label {
1086
+ display: block;
1087
+ font-size: var(--font-size-xs);
1088
+ font-weight: var(--font-weight-semibold);
1089
+ color: var(--color-text-tertiary);
1090
+ text-transform: uppercase;
1091
+ letter-spacing: 0.05em;
1092
+ margin-bottom: 2px;
1093
+ width: fit-content;
1094
+ text-align: center;
1095
+ white-space: nowrap;
846
1096
  }
847
1097
 
848
1098
  .jux-fileupload-input {
849
1099
  display: none;
850
1100
  }
851
1101
 
852
- .jux-fileupload-dropzone {
853
- border: 2px dashed var(--color-border);
854
- border-radius: var(--radius-lg);
855
- padding: var(--space-3xl) var(--space-2xl);
856
- text-align: center;
1102
+ .jux-fileupload-button {
1103
+ display: flex;
1104
+ flex-direction: column;
1105
+ align-items: center;
1106
+ justify-content: center;
1107
+ gap: var(--space-lg);
1108
+ padding: var(--space-2xl) var(--space-lg);
857
1109
  background: var(--color-surface-elevated);
858
- transition: all var(--transition-fast);
1110
+ border: 2px dashed var(--color-border);
1111
+ border-radius: var(--radius-xl);
1112
+ color: var(--color-text-secondary);
1113
+ font-family: inherit;
1114
+ font-size: var(--font-size-sm);
859
1115
  cursor: pointer;
1116
+ transition: all var(--transition-base);
860
1117
  position: relative;
1118
+ overflow: wrap;
1119
+ width: 100%;
861
1120
  }
862
1121
 
863
- .jux-fileupload-dropzone:hover {
864
- border-color: var(--color-brand);
865
- background: var(--color-surface-hover);
1122
+ .jux-fileupload-icon {
1123
+ font-size: 2rem;
1124
+ line-height: 1;
1125
+ opacity: 0.6;
1126
+ transition: opacity var(--transition-fast), transform var(--transition-bounce);
1127
+ }
1128
+
1129
+ .jux-fileupload-button:hover .jux-fileupload-icon {
1130
+ opacity: 1;
1131
+ transform: translateY(-4px);
866
1132
  }
867
1133
 
868
- .jux-fileupload-dropzone-active {
1134
+ .jux-fileupload-text {
1135
+ font-weight: var(--font-weight-medium);
1136
+ margin-top: var(--space-xs);
1137
+ }
1138
+
1139
+ .jux-fileupload-button:hover:not(:disabled) {
1140
+ background: var(--color-surface-hover);
869
1141
  border-color: var(--color-brand);
870
- background: var(--color-brand-subtle);
871
- transform: scale(1.02);
1142
+ color: var(--color-brand);
1143
+ box-shadow: var(--shadow-md);
872
1144
  }
873
1145
 
874
- .jux-fileupload-icon {
875
- font-size: var(--font-size-3xl);
876
- margin-bottom: var(--space-md);
877
- opacity: 0.5;
1146
+ .jux-fileupload-button:active:not(:disabled) {
1147
+ transform: scale(0.98);
878
1148
  }
879
1149
 
880
- .jux-fileupload-text {
881
- color: var(--color-text-secondary);
882
- font-size: var(--font-size-sm);
1150
+ .jux-fileupload-button:disabled {
1151
+ opacity: 0.5;
1152
+ cursor: not-allowed;
1153
+ background: var(--color-surface-base);
883
1154
  }
884
1155
 
885
1156
  .jux-fileupload-list {
886
- margin-top: var(--space-lg);
887
1157
  display: flex;
888
1158
  flex-direction: column;
889
- gap: var(--space-sm);
1159
+ gap: var(--space-xs);
1160
+ margin-top: var(--space-xs);
1161
+ color: var(--color-text-tertiary);
1162
+ font-style: italic;
1163
+ font-size: var(--font-size-xs);
890
1164
  }
891
1165
 
892
- .jux-fileupload-file {
1166
+ .jux-fileupload-item {
893
1167
  display: flex;
894
1168
  align-items: center;
895
1169
  gap: var(--space-md);
896
- padding: var(--space-md) var(--space-lg);
897
- background: var(--color-surface-elevated);
1170
+ padding: var(--space-sm) var(--space-md);
1171
+ background: var(--color-surface-base);
898
1172
  border: var(--border-width) solid var(--color-border);
899
1173
  border-radius: var(--radius-md);
1174
+ color: var(--color-text-primary);
1175
+ font-size: var(--font-size-sm);
1176
+ font-style: normal;
1177
+ animation: slideIn var(--transition-base) ease-out;
900
1178
  transition: all var(--transition-fast);
901
1179
  }
902
1180
 
903
- .jux-fileupload-file:hover {
1181
+ .jux-fileupload-item:hover {
904
1182
  background: var(--color-surface-hover);
905
1183
  border-color: var(--color-border-hover);
1184
+ transform: translateX(2px);
1185
+ box-shadow: var(--shadow-xs);
906
1186
  }
907
1187
 
908
- .jux-fileupload-file-name {
909
- flex: 1;
910
- font-size: var(--font-size-sm);
911
- font-weight: var(--font-weight-medium);
912
- color: var(--color-text-primary);
913
- white-space: nowrap;
914
- overflow: hidden;
915
- text-overflow: ellipsis;
916
- }
917
-
918
- .jux-fileupload-file-size {
919
- font-size: var(--font-size-xs);
920
- color: var(--color-text-tertiary);
921
- }
922
-
923
- .jux-fileupload-file-remove {
924
- padding: var(--space-xs) var(--space-sm);
925
- border: none;
926
- background: transparent;
927
- color: var(--color-text-tertiary);
928
- cursor: pointer;
929
- border-radius: var(--radius-sm);
930
- transition: all var(--transition-fast);
931
- font-size: var(--font-size-xl);
932
- line-height: 1;
933
- display: flex;
934
- align-items: center;
935
- justify-content: center;
936
- }
937
-
938
- .jux-fileupload-file-remove:hover {
939
- background: var(--color-surface-active);
940
- color: var(--color-danger);
1188
+ .jux-fileupload-item::before {
1189
+ content: '📄';
1190
+ font-size: 1.1rem;
1191
+ filter: saturate(0.5);
941
1192
  }
942
1193
 
943
1194
  /* Progress Component */
@@ -1031,30 +1282,56 @@ body {
1031
1282
  align-items: flex-start;
1032
1283
  gap: var(--space-md);
1033
1284
  padding: var(--space-lg);
1034
- border-radius: var(--radius-md);
1285
+ border-radius: var(--radius-lg);
1035
1286
  border: var(--border-width) solid;
1036
- margin-bottom: var(--space-lg);
1037
1287
  font-size: var(--font-size-sm);
1038
1288
  line-height: var(--line-height-normal);
1039
- position: relative;
1040
- animation: slideIn var(--transition-base) ease-out;
1289
+ animation: alertSlideIn var(--transition-base) ease-out;
1041
1290
  transition: all var(--transition-fast);
1042
- box-shadow: var(--shadow-sm);
1291
+ box-shadow: var(--shadow-lg);
1292
+ width: 100%;
1293
+ position: relative;
1294
+ z-index: 1000;
1295
+ transform-origin: top center;
1296
+ margin-bottom: 0;
1043
1297
  }
1044
1298
 
1045
- .jux-alert-icon {
1046
- flex-shrink: 0;
1047
- font-size: var(--font-size-xl);
1048
- line-height: 1;
1299
+ /* Alert Stack Effect (Scoped to .jux-alert-stack container) */
1300
+ .jux-alert-stack {
1301
+ position: relative;
1302
+ min-height: 80px;
1049
1303
  display: flex;
1050
- align-items: center;
1051
- justify-content: center;
1304
+ flex-direction: column-reverse; /* Keep newest at bottom of DOM but appearing correctly */
1052
1305
  }
1053
1306
 
1054
- .jux-alert-content {
1055
- flex: 1;
1056
- color: inherit;
1057
- font-weight: var(--font-weight-medium);
1307
+ /* Base stack state: Hidden for all */
1308
+ .jux-alert-stack .jux-alert {
1309
+ display: none !important;
1310
+ transition: all var(--transition-bounce), opacity var(--transition-base);
1311
+ margin-bottom: 0;
1312
+ transform-origin: top center;
1313
+ }
1314
+
1315
+ /* Top Visible Alert: The one with no UN-HIDDEN siblings after it in the DOM */
1316
+ .jux-alert-stack .jux-alert:not([style*="display: none"]):not(:has(~ .jux-alert:not([style*="display: none"]))) {
1317
+ display: flex !important;
1318
+ position: relative;
1319
+ z-index: 20;
1320
+ opacity: 1;
1321
+ transform: translateY(0) scale(1);
1322
+ }
1323
+
1324
+ /* Second Visible Alert: The one with exactly one UN-HIDDEN sibling after it */
1325
+ .jux-alert-stack .jux-alert:not([style*="display: none"]):has(~ .jux-alert:not([style*="display: none"])):not(:has(~ .jux-alert:not([style*="display: none"]) ~ .jux-alert:not([style*="display: none"]))) {
1326
+ display: flex !important;
1327
+ position: absolute;
1328
+ top: 0;
1329
+ left: 0;
1330
+ right: 0;
1331
+ z-index: 10;
1332
+ opacity: 0.5;
1333
+ transform: translateY(var(--space-sm)) scale(0.98);
1334
+ pointer-events: none;
1058
1335
  }
1059
1336
 
1060
1337
  .jux-alert-close {
@@ -1076,91 +1353,28 @@ body {
1076
1353
  opacity: 0.6;
1077
1354
  }
1078
1355
 
1079
- .jux-alert-close:hover {
1080
- opacity: 1;
1081
- background: rgba(0, 0, 0, 0.1);
1082
- transform: scale(1.1);
1083
- }
1084
-
1085
- .jux-alert-close:active {
1086
- transform: scale(0.95);
1087
- }
1088
1356
 
1089
1357
  /* Alert Type Variants */
1090
1358
  .jux-alert-info {
1091
- background: rgba(59, 130, 246, 0.1);
1092
- border-color: rgba(59, 130, 246, 0.3);
1093
- color: #3b82f6;
1359
+ background: var(--color-surface-elevated);
1360
+ border-color: currentColor;
1361
+ color: var(--color-info);
1094
1362
  }
1095
1363
 
1096
1364
  .jux-alert-success {
1097
- background: rgba(34, 197, 94, 0.1);
1098
- border-color: rgba(34, 197, 94, 0.3);
1099
- color: #22c55e;
1365
+ background: var(--color-surface-elevated);
1366
+ border-color: currentColor;
1367
+ color: var(--color-success);
1100
1368
  }
1101
1369
 
1102
1370
  .jux-alert-warning {
1103
- background: rgba(251, 191, 36, 0.1);
1104
- border-color: rgba(251, 191, 36, 0.3);
1105
- color: #f59e0b;
1371
+ background: var(--color-surface-elevated);
1372
+ border-color: currentColor;
1373
+ color: var(--color-warning);
1106
1374
  }
1107
1375
 
1108
1376
  .jux-alert-error {
1109
- background: rgba(239, 68, 68, 0.1);
1110
- border-color: rgba(239, 68, 68, 0.3);
1111
- color: #ef4444;
1112
- }
1113
-
1114
- /* Dark mode adjustments for alerts */
1115
- @media (prefers-color-scheme: dark) {
1116
- .jux-alert-info {
1117
- background: rgba(59, 130, 246, 0.15);
1118
- border-color: rgba(59, 130, 246, 0.4);
1119
- color: #60a5fa;
1120
- }
1121
-
1122
- .jux-alert-success {
1123
- background: rgba(34, 197, 94, 0.15);
1124
- border-color: rgba(34, 197, 94, 0.4);
1125
- color: #4ade80;
1126
- }
1127
-
1128
- .jux-alert-warning {
1129
- background: rgba(251, 191, 36, 0.15);
1130
- border-color: rgba(251, 191, 36, 0.4);
1131
- color: #fbbf24;
1132
- }
1133
-
1134
- .jux-alert-error {
1135
- background: rgba(239, 68, 68, 0.15);
1136
- border-color: rgba(239, 68, 68, 0.4);
1137
- color: #f87171;
1138
- }
1139
- }
1140
-
1141
- /* Alert animations */
1142
- @keyframes alertSlideIn {
1143
- from {
1144
- opacity: 0;
1145
- transform: translateY(-10px);
1146
- }
1147
- to {
1148
- opacity: 1;
1149
- transform: translateY(0);
1150
- }
1151
- }
1152
-
1153
- @keyframes alertSlideOut {
1154
- from {
1155
- opacity: 1;
1156
- transform: translateY(0);
1157
- }
1158
- to {
1159
- opacity: 0;
1160
- transform: translateY(-10px);
1161
- }
1162
- }
1163
-
1164
- .jux-alert[style*="display: none"] {
1165
- animation: alertSlideOut var(--transition-fast) ease-out forwards;
1377
+ background: var(--color-surface-elevated);
1378
+ border-color: currentColor;
1379
+ color: var(--color-danger);
1166
1380
  }
File without changes