webspresso 0.0.63 → 0.0.64

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.
@@ -84,7 +84,7 @@ const Breadcrumb = {
84
84
  m('ol.flex.items-center.space-x-2.text-sm', [
85
85
  // Home link
86
86
  m('li', [
87
- m('a.text-gray-500.hover:text-gray-700', {
87
+ m('a.text-gray-500 dark:text-slate-400.hover:text-gray-700 dark:hover:text-slate-200 dark:hover:text-slate-200', {
88
88
  href: '/',
89
89
  onclick: (e) => {
90
90
  e.preventDefault();
@@ -99,12 +99,12 @@ const Breadcrumb = {
99
99
  // Dynamic items
100
100
  ...items.map((item, idx) => [
101
101
  m('li.flex.items-center', [
102
- m('svg.w-4.h-4.text-gray-400.mx-1', { fill: 'currentColor', viewBox: '0 0 20 20' }, [
102
+ m('svg.w-4.h-4.text-gray-400 dark:text-slate-500.mx-1', { fill: 'currentColor', viewBox: '0 0 20 20' }, [
103
103
  m('path', { 'fill-rule': 'evenodd', d: 'M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z', 'clip-rule': 'evenodd' }),
104
104
  ]),
105
105
  idx === items.length - 1
106
- ? m('span.text-gray-700.font-medium', item.label)
107
- : m('a.text-gray-500.hover:text-gray-700', {
106
+ ? m('span.text-gray-700 dark:text-slate-300.font-medium', item.label)
107
+ : m('a.text-gray-500 dark:text-slate-400.hover:text-gray-700 dark:hover:text-slate-200 dark:hover:text-slate-200', {
108
108
  href: item.href,
109
109
  onclick: (e) => {
110
110
  e.preventDefault();
@@ -211,12 +211,12 @@ const ActiveFiltersBar = {
211
211
  if (filterEntries.length === 0) return null;
212
212
 
213
213
  return m('.flex.items-center.gap-2.py-2.flex-wrap', [
214
- m('span.text-xs.font-medium.text-gray-500.uppercase.tracking-wide', 'Active filters:'),
214
+ m('span.text-xs.font-medium.text-gray-500 dark:text-slate-400.uppercase.tracking-wide', 'Active filters:'),
215
215
  ...filterEntries.map(([colName, filter]) => {
216
216
  const col = modelMeta?.columns?.find(c => c.name === colName);
217
217
  return m(FilterBadge, { colName, filter, colMeta: col, onRemove });
218
218
  }),
219
- filterEntries.length > 1 ? m('button.text-xs.text-gray-500.hover:text-gray-700.underline.ml-2', {
219
+ filterEntries.length > 1 ? m('button.text-xs.text-gray-500 dark:text-slate-400.hover:text-gray-700 dark:hover:text-slate-200 dark:hover:text-slate-200.underline.ml-2', {
220
220
  onclick: onClearAll,
221
221
  type: 'button',
222
222
  }, 'Clear all') : null,
@@ -235,13 +235,13 @@ const QuickFilterInput = {
235
235
  m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z' }),
236
236
  ]),
237
237
  ]),
238
- m('input.block.w-full.pl-9.pr-8.py-2.text-sm.border.border-gray-300.rounded-lg.bg-white.placeholder-gray-400.focus:outline-none.focus:ring-2.focus:ring-indigo-500.focus:border-transparent', {
238
+ m('input.block.w-full.pl-9.pr-8.py-2.text-sm.border.border-gray-300 dark:border-slate-600.rounded-lg.bg-white dark:bg-slate-800.placeholder-gray-400 dark:placeholder-slate-500.focus:outline-none.focus:ring-2.focus:ring-indigo-500.focus:border-transparent', {
239
239
  type: 'text',
240
240
  placeholder: placeholder || 'Quick search...',
241
241
  value: value || '',
242
242
  oninput: (e) => onChange(e.target.value),
243
243
  }),
244
- value ? m('button.absolute.inset-y-0.right-0.pr-3.flex.items-center.text-gray-400.hover:text-gray-600', {
244
+ value ? m('button.absolute.inset-y-0.right-0.pr-3.flex.items-center.text-gray-400 dark:text-slate-500.hover:text-gray-600 dark:hover:text-slate-300 dark:hover:text-slate-300', {
245
245
  onclick: onClear,
246
246
  type: 'button',
247
247
  }, [
@@ -271,7 +271,7 @@ const QuickFiltersBar = {
271
271
  const quickSearchCol = searchableColumns[0];
272
272
  const quickSearchFilter = quickSearchCol ? filters[quickSearchCol.name] : null;
273
273
 
274
- return m('.bg-white.border.border-gray-200.rounded-lg.p-3.mb-4.shadow-sm', [
274
+ return m('.bg-white dark:bg-slate-800.border.border-gray-200 dark:border-slate-600.rounded-lg.p-3.mb-4.shadow-sm', [
275
275
  m('.flex.items-center.gap-3.flex-wrap', [
276
276
  quickSearchCol ? m(QuickFilterInput, {
277
277
  placeholder: 'Search by ' + (quickSearchCol.ui?.label || formatColumnLabel(quickSearchCol.name)).toLowerCase() + '...',
@@ -297,14 +297,14 @@ const QuickFiltersBar = {
297
297
  m('button.px-2.py-1.text-xs.rounded-md.transition-colors', {
298
298
  class: !currentValue
299
299
  ? 'bg-gray-200 text-gray-800'
300
- : 'bg-gray-100 text-gray-600 hover:bg-gray-200',
300
+ : 'bg-gray-100 text-gray-600 dark:text-slate-400 hover:bg-gray-200 dark:hover:bg-slate-600 dark:hover:bg-slate-600',
301
301
  onclick: () => onFilterChange(col.name, null),
302
302
  }, 'All'),
303
303
  ...col.enumValues.map(val =>
304
304
  m('button.px-2.py-1.text-xs.rounded-md.transition-colors', {
305
305
  class: currentValue === val
306
306
  ? 'bg-indigo-600 text-white'
307
- : 'bg-gray-100 text-gray-600 hover:bg-gray-200',
307
+ : 'bg-gray-100 text-gray-600 dark:text-slate-400 hover:bg-gray-200 dark:hover:bg-slate-600 dark:hover:bg-slate-600',
308
308
  onclick: () => onFilterChange(col.name, { op: 'equals', value: val }),
309
309
  }, val)
310
310
  ),
@@ -314,7 +314,7 @@ const QuickFiltersBar = {
314
314
 
315
315
  m('.flex-1'),
316
316
 
317
- m('button.inline-flex.items-center.gap-2.px-3.py-2.text-sm.font-medium.text-gray-700.bg-white.border.border-gray-300.rounded-lg.hover:bg-gray-50.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
317
+ m('button.inline-flex.items-center.gap-2.px-3.py-2.text-sm.font-medium.text-gray-700 dark:text-slate-300.bg-white dark:bg-slate-800.border.border-gray-300 dark:border-slate-600.rounded-lg.hover:bg-gray-50 dark:hover:bg-slate-800/50 dark:hover:bg-slate-800/50.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
318
318
  onclick: onOpenDrawer,
319
319
  type: 'button',
320
320
  }, [
@@ -343,7 +343,7 @@ const FilterField = {
343
343
  ['true', 'false', ''].map((val, idx) => {
344
344
  const labels = ['Yes', 'No', 'Any'];
345
345
  return m('label.inline-flex.items-center.cursor-pointer', [
346
- m('input.w-4.h-4.text-indigo-600.border-gray-300.focus:ring-indigo-500', {
346
+ m('input.w-4.h-4.text-indigo-600.border-gray-300 dark:border-slate-600.focus:ring-indigo-500', {
347
347
  type: 'radio',
348
348
  name: 'filter_bool_' + col.name,
349
349
  checked: (currentFilter.value || '') === val,
@@ -370,7 +370,7 @@ const FilterField = {
370
370
  type: 'button',
371
371
  class: isSelected
372
372
  ? 'bg-indigo-100 border-indigo-300 text-indigo-700'
373
- : 'bg-white border-gray-300 text-gray-700 hover:bg-gray-50',
373
+ : 'bg-white border-gray-300 dark:border-slate-600 text-gray-700 dark:text-slate-300 hover:bg-gray-50 dark:hover:bg-slate-800/50 dark:hover:bg-slate-800/50',
374
374
  onclick: () => {
375
375
  const newSelected = isSelected
376
376
  ? selectedValues.filter(v => v !== val)
@@ -390,7 +390,7 @@ const FilterField = {
390
390
  return m('.space-y-2', [
391
391
  m('label.block.text-sm.font-medium.text-gray-700', label),
392
392
  m('.flex.items-start.gap-2.flex-wrap', [
393
- m('select.px-3.py-2.text-sm.border.border-gray-300.rounded-lg.bg-white.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
393
+ m('select.px-3.py-2.text-sm.border.border-gray-300 dark:border-slate-600.rounded-lg.bg-white dark:bg-slate-800.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
394
394
  value: currentFilter.op || 'eq',
395
395
  onchange: (e) => {
396
396
  const op = e.target.value;
@@ -402,18 +402,18 @@ const FilterField = {
402
402
  },
403
403
  }, ops.map(o => m('option', { value: o.value }, o.label))),
404
404
  currentFilter.op === 'between' ? [
405
- m('input.flex-1.min-w-32.px-3.py-2.text-sm.border.border-gray-300.rounded-lg.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
405
+ m('input.flex-1.min-w-32.px-3.py-2.text-sm.border.border-gray-300 dark:border-slate-600.rounded-lg.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
406
406
  type: inputType,
407
407
  value: currentFilter.from || '',
408
408
  oninput: (e) => onChange({ op: 'between', from: e.target.value, to: currentFilter.to || '' }),
409
409
  }),
410
- m('span.text-gray-400.self-center', 'to'),
411
- m('input.flex-1.min-w-32.px-3.py-2.text-sm.border.border-gray-300.rounded-lg.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
410
+ m('span.text-gray-400 dark:text-slate-500.self-center', 'to'),
411
+ m('input.flex-1.min-w-32.px-3.py-2.text-sm.border.border-gray-300 dark:border-slate-600.rounded-lg.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
412
412
  type: inputType,
413
413
  value: currentFilter.to || '',
414
414
  oninput: (e) => onChange({ op: 'between', from: currentFilter.from || '', to: e.target.value }),
415
415
  }),
416
- ] : m('input.flex-1.px-3.py-2.text-sm.border.border-gray-300.rounded-lg.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
416
+ ] : m('input.flex-1.px-3.py-2.text-sm.border.border-gray-300 dark:border-slate-600.rounded-lg.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
417
417
  type: inputType,
418
418
  value: currentFilter.value || '',
419
419
  oninput: (e) => onChange({ op: currentFilter.op || 'eq', value: e.target.value }),
@@ -428,7 +428,7 @@ const FilterField = {
428
428
  return m('.space-y-2', [
429
429
  m('label.block.text-sm.font-medium.text-gray-700', label),
430
430
  m('.flex.items-start.gap-2', [
431
- m('select.px-3.py-2.text-sm.border.border-gray-300.rounded-lg.bg-white.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
431
+ m('select.px-3.py-2.text-sm.border.border-gray-300 dark:border-slate-600.rounded-lg.bg-white dark:bg-slate-800.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
432
432
  value: currentFilter.op || 'eq',
433
433
  onchange: (e) => {
434
434
  const op = e.target.value;
@@ -440,20 +440,20 @@ const FilterField = {
440
440
  },
441
441
  }, ops.map(o => m('option', { value: o.value }, o.label))),
442
442
  currentFilter.op === 'between' ? [
443
- m('input.w-24.px-3.py-2.text-sm.border.border-gray-300.rounded-lg.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
443
+ m('input.w-24.px-3.py-2.text-sm.border.border-gray-300 dark:border-slate-600.rounded-lg.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
444
444
  type: 'number',
445
445
  value: currentFilter.from || '',
446
446
  placeholder: 'Min',
447
447
  oninput: (e) => onChange({ op: 'between', from: e.target.value, to: currentFilter.to || '' }),
448
448
  }),
449
- m('span.text-gray-400.self-center', 'to'),
450
- m('input.w-24.px-3.py-2.text-sm.border.border-gray-300.rounded-lg.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
449
+ m('span.text-gray-400 dark:text-slate-500.self-center', 'to'),
450
+ m('input.w-24.px-3.py-2.text-sm.border.border-gray-300 dark:border-slate-600.rounded-lg.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
451
451
  type: 'number',
452
452
  value: currentFilter.to || '',
453
453
  placeholder: 'Max',
454
454
  oninput: (e) => onChange({ op: 'between', from: currentFilter.from || '', to: e.target.value }),
455
455
  }),
456
- ] : m('input.flex-1.px-3.py-2.text-sm.border.border-gray-300.rounded-lg.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
456
+ ] : m('input.flex-1.px-3.py-2.text-sm.border.border-gray-300 dark:border-slate-600.rounded-lg.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
457
457
  type: 'number',
458
458
  value: currentFilter.value || '',
459
459
  placeholder: 'Enter value',
@@ -469,11 +469,11 @@ const FilterField = {
469
469
  return m('.space-y-2', [
470
470
  m('label.block.text-sm.font-medium.text-gray-700', label),
471
471
  m('.flex.items-center.gap-2', [
472
- m('select.px-3.py-2.text-sm.border.border-gray-300.rounded-lg.bg-white.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
472
+ m('select.px-3.py-2.text-sm.border.border-gray-300 dark:border-slate-600.rounded-lg.bg-white dark:bg-slate-800.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
473
473
  value: currentFilter.op || 'contains',
474
474
  onchange: (e) => onChange({ op: e.target.value, value: currentFilter.value || '' }),
475
475
  }, ops.map(o => m('option', { value: o.value }, o.label))),
476
- m('input.flex-1.px-3.py-2.text-sm.border.border-gray-300.rounded-lg.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
476
+ m('input.flex-1.px-3.py-2.text-sm.border.border-gray-300 dark:border-slate-600.rounded-lg.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
477
477
  type: 'text',
478
478
  value: currentFilter.value || '',
479
479
  placeholder: 'Enter search term',
@@ -506,7 +506,7 @@ const FilterDrawer = {
506
506
  const renderGroup = (title, columns) => {
507
507
  if (columns.length === 0) return null;
508
508
  return m('.mb-6', [
509
- m('h4.text-xs.font-semibold.text-gray-400.uppercase.tracking-wider.mb-3', title),
509
+ m('h4.text-xs.font-semibold.text-gray-400 dark:text-slate-500.uppercase.tracking-wider.mb-3', title),
510
510
  m('.space-y-4', columns.map(col =>
511
511
  m(FilterField, {
512
512
  col,
@@ -519,7 +519,7 @@ const FilterDrawer = {
519
519
 
520
520
  return [
521
521
  m('.fixed.inset-0.bg-black.bg-opacity-25.z-40.transition-opacity', { onclick: onClose }),
522
- m('.fixed.inset-y-0.right-0.w-full.max-w-md.bg-white.shadow-xl.z-50.flex.flex-col', {
522
+ m('.fixed.inset-y-0.right-0.w-full.max-w-md.bg-white dark:bg-slate-800.shadow-xl.z-50.flex.flex-col', {
523
523
  style: 'animation: filterDrawerSlideIn 0.2s ease-out;',
524
524
  }, [
525
525
  m('.flex.items-center.justify-between.px-6.py-4.border-b.border-gray-200', [
@@ -527,7 +527,7 @@ const FilterDrawer = {
527
527
  m('h3.text-lg.font-semibold.text-gray-900', 'Advanced Filters'),
528
528
  m('p.text-sm.text-gray-500', 'Filter records by multiple criteria'),
529
529
  ]),
530
- m('button.p-2.text-gray-400.hover:text-gray-600.rounded-lg.hover:bg-gray-100', {
530
+ m('button.p-2.text-gray-400 dark:text-slate-500.hover:text-gray-600 dark:hover:text-slate-300 dark:hover:text-slate-300.rounded-lg.hover:bg-gray-100 dark:hover:bg-slate-700 dark:hover:bg-slate-700', {
531
531
  onclick: onClose,
532
532
  type: 'button',
533
533
  }, [
@@ -542,13 +542,13 @@ const FilterDrawer = {
542
542
  renderGroup('Numbers', numericColumns),
543
543
  renderGroup('Dates', dateColumns),
544
544
  ]),
545
- m('.flex.items-center.justify-between.gap-3.px-6.py-4.border-t.border-gray-200.bg-gray-50', [
546
- m('button.px-4.py-2.text-sm.font-medium.text-gray-700.hover:text-gray-900', {
545
+ m('.flex.items-center.justify-between.gap-3.px-6.py-4.border-t.border-gray-200 dark:border-slate-600.bg-gray-50', [
546
+ m('button.px-4.py-2.text-sm.font-medium.text-gray-700 dark:text-slate-300.hover:text-gray-900 dark:hover:text-slate-100 dark:hover:text-slate-100', {
547
547
  onclick: onClear,
548
548
  type: 'button',
549
549
  }, 'Clear all'),
550
550
  m('.flex.gap-3', [
551
- m('button.px-4.py-2.text-sm.font-medium.text-gray-700.bg-white.border.border-gray-300.rounded-lg.hover:bg-gray-50', {
551
+ m('button.px-4.py-2.text-sm.font-medium.text-gray-700 dark:text-slate-300.bg-white dark:bg-slate-800.border.border-gray-300 dark:border-slate-600.rounded-lg.hover:bg-gray-50 dark:hover:bg-slate-800/50 dark:hover:bg-slate-800/50', {
552
552
  onclick: onClose,
553
553
  type: 'button',
554
554
  }, 'Cancel'),
@@ -592,7 +592,7 @@ const Pagination = {
592
592
  pages.push(i);
593
593
  }
594
594
 
595
- return m('.flex.items-center.justify-between.px-4.py-3.bg-white.border-t', [
595
+ return m('.flex.items-center.justify-between.px-4.py-3.bg-white dark:bg-slate-800.border-t', [
596
596
  m('.text-sm.text-gray-700', [
597
597
  'Showing ',
598
598
  m('span.font-medium', ((page - 1) * perPage) + 1),
@@ -606,13 +606,13 @@ const Pagination = {
606
606
  // Previous button
607
607
  m('button.px-3.py-1.rounded.border.text-sm', {
608
608
  disabled: page <= 1,
609
- class: page <= 1 ? 'text-gray-300 cursor-not-allowed' : 'text-gray-700 hover:bg-gray-100',
609
+ class: page <= 1 ? 'text-gray-300 cursor-not-allowed' : 'text-gray-700 hover:bg-gray-100 dark:hover:bg-slate-700 dark:hover:bg-slate-700',
610
610
  onclick: () => page > 1 && onPageChange(page - 1),
611
611
  }, '← Prev'),
612
612
 
613
613
  // Page numbers
614
614
  start > 1 ? [
615
- m('button.px-3.py-1.rounded.text-sm.text-gray-700.hover:bg-gray-100', {
615
+ m('button.px-3.py-1.rounded.text-sm.text-gray-700 dark:text-slate-300.hover:bg-gray-100 dark:hover:bg-slate-700 dark:hover:bg-slate-700', {
616
616
  onclick: () => onPageChange(1),
617
617
  }, '1'),
618
618
  start > 2 ? m('span.px-2.text-gray-400', '...') : null,
@@ -622,14 +622,14 @@ const Pagination = {
622
622
  m('button.px-3.py-1.rounded.text-sm', {
623
623
  class: p === page
624
624
  ? 'bg-blue-600 text-white'
625
- : 'text-gray-700 hover:bg-gray-100',
625
+ : 'text-gray-700 hover:bg-gray-100 dark:hover:bg-slate-700 dark:hover:bg-slate-700',
626
626
  onclick: () => onPageChange(p),
627
627
  }, p)
628
628
  ),
629
629
 
630
630
  end < totalPages ? [
631
631
  end < totalPages - 1 ? m('span.px-2.text-gray-400', '...') : null,
632
- m('button.px-3.py-1.rounded.text-sm.text-gray-700.hover:bg-gray-100', {
632
+ m('button.px-3.py-1.rounded.text-sm.text-gray-700 dark:text-slate-300.hover:bg-gray-100 dark:hover:bg-slate-700 dark:hover:bg-slate-700', {
633
633
  onclick: () => onPageChange(totalPages),
634
634
  }, totalPages),
635
635
  ] : null,
@@ -637,7 +637,7 @@ const Pagination = {
637
637
  // Next button
638
638
  m('button.px-3.py-1.rounded.border.text-sm', {
639
639
  disabled: page >= totalPages,
640
- class: page >= totalPages ? 'text-gray-300 cursor-not-allowed' : 'text-gray-700 hover:bg-gray-100',
640
+ class: page >= totalPages ? 'text-gray-300 cursor-not-allowed' : 'text-gray-700 hover:bg-gray-100 dark:hover:bg-slate-700 dark:hover:bg-slate-700',
641
641
  onclick: () => page < totalPages && onPageChange(page + 1),
642
642
  }, 'Next →'),
643
643
  ]),
@@ -657,8 +657,8 @@ const FieldRenderers = {
657
657
  const hint = ui.hint || '';
658
658
 
659
659
  return m('.mb-4', [
660
- m('label.block.text-sm.font-medium.text-gray-700.mb-1', { for: col.name }, label),
661
- m('input.w-full.px-3.py-2.border.border-gray-300.rounded.focus:outline-none.focus:ring-2.focus:ring-blue-500', {
660
+ m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: col.name }, label),
661
+ m('input.w-full.px-3.py-2.border.border-gray-300 dark:border-slate-600.rounded.focus:outline-none.focus:ring-2.focus:ring-blue-500', {
662
662
  id: col.name,
663
663
  name: col.name,
664
664
  type: inputType,
@@ -673,7 +673,7 @@ const FieldRenderers = {
673
673
  class: readonly ? 'bg-gray-100 cursor-not-allowed' : '',
674
674
  oninput: (e) => onChange(e.target.value),
675
675
  }),
676
- hint ? m('p.text-xs.text-gray-500.mt-1', hint) : null,
676
+ hint ? m('p.text-xs.text-gray-500 dark:text-slate-400.mt-1', hint) : null,
677
677
  ]);
678
678
  },
679
679
 
@@ -687,8 +687,8 @@ const FieldRenderers = {
687
687
  const rows = ui.rows || 4;
688
688
 
689
689
  return m('.mb-4', [
690
- m('label.block.text-sm.font-medium.text-gray-700.mb-1', { for: col.name }, label),
691
- m('textarea.w-full.px-3.py-2.border.border-gray-300.rounded.focus:outline-none.focus:ring-2.focus:ring-blue-500', {
690
+ m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: col.name }, label),
691
+ m('textarea.w-full.px-3.py-2.border.border-gray-300 dark:border-slate-600.rounded.focus:outline-none.focus:ring-2.focus:ring-blue-500', {
692
692
  id: col.name,
693
693
  name: col.name,
694
694
  rows: rows,
@@ -701,7 +701,7 @@ const FieldRenderers = {
701
701
  class: readonly ? 'bg-gray-100 cursor-not-allowed' : '',
702
702
  oninput: (e) => onChange(e.target.value),
703
703
  }, value || ''),
704
- hint ? m('p.text-xs.text-gray-500.mt-1', hint) : null,
704
+ hint ? m('p.text-xs.text-gray-500 dark:text-slate-400.mt-1', hint) : null,
705
705
  ]);
706
706
  },
707
707
 
@@ -714,8 +714,8 @@ const FieldRenderers = {
714
714
  const hint = ui.hint || '';
715
715
 
716
716
  return m('.mb-4', [
717
- m('label.block.text-sm.font-medium.text-gray-700.mb-1', { for: col.name }, label),
718
- m('input.w-full.px-3.py-2.border.border-gray-300.rounded.focus:outline-none.focus:ring-2.focus:ring-blue-500', {
717
+ m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: col.name }, label),
718
+ m('input.w-full.px-3.py-2.border.border-gray-300 dark:border-slate-600.rounded.focus:outline-none.focus:ring-2.focus:ring-blue-500', {
719
719
  id: col.name,
720
720
  name: col.name,
721
721
  type: 'number',
@@ -730,7 +730,7 @@ const FieldRenderers = {
730
730
  class: readonly ? 'bg-gray-100 cursor-not-allowed' : '',
731
731
  oninput: (e) => onChange(e.target.value === '' ? null : parseInt(e.target.value, 10)),
732
732
  }),
733
- hint ? m('p.text-xs.text-gray-500.mt-1', hint) : null,
733
+ hint ? m('p.text-xs.text-gray-500 dark:text-slate-400.mt-1', hint) : null,
734
734
  ]);
735
735
  },
736
736
 
@@ -743,8 +743,8 @@ const FieldRenderers = {
743
743
  const hint = ui.hint || '';
744
744
 
745
745
  return m('.mb-4', [
746
- m('label.block.text-sm.font-medium.text-gray-700.mb-1', { for: col.name }, label),
747
- m('input.w-full.px-3.py-2.border.border-gray-300.rounded.focus:outline-none.focus:ring-2.focus:ring-blue-500', {
746
+ m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: col.name }, label),
747
+ m('input.w-full.px-3.py-2.border.border-gray-300 dark:border-slate-600.rounded.focus:outline-none.focus:ring-2.focus:ring-blue-500', {
748
748
  id: col.name,
749
749
  name: col.name,
750
750
  type: 'number',
@@ -759,7 +759,7 @@ const FieldRenderers = {
759
759
  class: readonly ? 'bg-gray-100 cursor-not-allowed' : '',
760
760
  oninput: (e) => onChange(e.target.value === '' ? null : parseFloat(e.target.value)),
761
761
  }),
762
- hint ? m('p.text-xs.text-gray-500.mt-1', hint) : null,
762
+ hint ? m('p.text-xs.text-gray-500 dark:text-slate-400.mt-1', hint) : null,
763
763
  ]);
764
764
  },
765
765
 
@@ -780,7 +780,7 @@ const FieldRenderers = {
780
780
  }),
781
781
  m('span.text-sm.font-medium.text-gray-700', label),
782
782
  ]),
783
- hint ? m('p.text-xs.text-gray-500.mt-1', hint) : null,
783
+ hint ? m('p.text-xs.text-gray-500 dark:text-slate-400.mt-1', hint) : null,
784
784
  ]);
785
785
  },
786
786
 
@@ -794,8 +794,8 @@ const FieldRenderers = {
794
794
  const dateValue = value ? new Date(value).toISOString().split('T')[0] : '';
795
795
 
796
796
  return m('.mb-4', [
797
- m('label.block.text-sm.font-medium.text-gray-700.mb-1', { for: col.name }, label),
798
- m('input.w-full.px-3.py-2.border.border-gray-300.rounded.focus:outline-none.focus:ring-2.focus:ring-blue-500', {
797
+ m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: col.name }, label),
798
+ m('input.w-full.px-3.py-2.border.border-gray-300 dark:border-slate-600.rounded.focus:outline-none.focus:ring-2.focus:ring-blue-500', {
799
799
  id: col.name,
800
800
  name: col.name,
801
801
  type: 'date',
@@ -809,7 +809,7 @@ const FieldRenderers = {
809
809
  class: readonly ? 'bg-gray-100 cursor-not-allowed' : '',
810
810
  oninput: (e) => onChange(e.target.value),
811
811
  }),
812
- hint ? m('p.text-xs.text-gray-500.mt-1', hint) : null,
812
+ hint ? m('p.text-xs.text-gray-500 dark:text-slate-400.mt-1', hint) : null,
813
813
  ]);
814
814
  },
815
815
 
@@ -823,8 +823,8 @@ const FieldRenderers = {
823
823
  const dateTimeValue = value ? new Date(value).toISOString().slice(0, 16) : '';
824
824
 
825
825
  return m('.mb-4', [
826
- m('label.block.text-sm.font-medium.text-gray-700.mb-1', { for: col.name }, label),
827
- m('input.w-full.px-3.py-2.border.border-gray-300.rounded.focus:outline-none.focus:ring-2.focus:ring-blue-500', {
826
+ m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: col.name }, label),
827
+ m('input.w-full.px-3.py-2.border.border-gray-300 dark:border-slate-600.rounded.focus:outline-none.focus:ring-2.focus:ring-blue-500', {
828
828
  id: col.name,
829
829
  name: col.name,
830
830
  type: 'datetime-local',
@@ -838,7 +838,7 @@ const FieldRenderers = {
838
838
  class: readonly ? 'bg-gray-100 cursor-not-allowed' : '',
839
839
  oninput: (e) => onChange(e.target.value),
840
840
  }),
841
- hint ? m('p.text-xs.text-gray-500.mt-1', hint) : null,
841
+ hint ? m('p.text-xs.text-gray-500 dark:text-slate-400.mt-1', hint) : null,
842
842
  ]);
843
843
  },
844
844
 
@@ -850,8 +850,8 @@ const FieldRenderers = {
850
850
  const options = col.enumValues || [];
851
851
 
852
852
  return m('.mb-4', [
853
- m('label.block.text-sm.font-medium.text-gray-700.mb-1', { for: col.name }, label),
854
- m('select.w-full.px-3.py-2.border.border-gray-300.rounded.focus:outline-none.focus:ring-2.focus:ring-blue-500', {
853
+ m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: col.name }, label),
854
+ m('select.w-full.px-3.py-2.border.border-gray-300 dark:border-slate-600.rounded.focus:outline-none.focus:ring-2.focus:ring-blue-500', {
855
855
  id: col.name,
856
856
  name: col.name,
857
857
  value: value || '',
@@ -863,7 +863,7 @@ const FieldRenderers = {
863
863
  col.nullable ? m('option', { value: '' }, '-- Select --') : null,
864
864
  ...options.map(opt => m('option', { value: opt, selected: value === opt }, opt)),
865
865
  ]),
866
- hint ? m('p.text-xs.text-gray-500.mt-1', hint) : null,
866
+ hint ? m('p.text-xs.text-gray-500 dark:text-slate-400.mt-1', hint) : null,
867
867
  ]);
868
868
  },
869
869
 
@@ -877,8 +877,8 @@ const FieldRenderers = {
877
877
  const jsonString = value ? (typeof value === 'string' ? value : JSON.stringify(value, null, 2)) : '';
878
878
 
879
879
  return m('.mb-4', [
880
- m('label.block.text-sm.font-medium.text-gray-700.mb-1', { for: col.name }, label),
881
- m('textarea.w-full.px-3.py-2.border.border-gray-300.rounded.font-mono.text-sm.focus:outline-none.focus:ring-2.focus:ring-blue-500', {
880
+ m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: col.name }, label),
881
+ m('textarea.w-full.px-3.py-2.border.border-gray-300 dark:border-slate-600.rounded.font-mono.text-sm.focus:outline-none.focus:ring-2.focus:ring-blue-500', {
882
882
  id: col.name,
883
883
  name: col.name,
884
884
  rows: rows,
@@ -896,7 +896,7 @@ const FieldRenderers = {
896
896
  }
897
897
  },
898
898
  }, jsonString),
899
- hint ? m('p.text-xs.text-gray-500.mt-1', hint) : null,
899
+ hint ? m('p.text-xs.text-gray-500 dark:text-slate-400.mt-1', hint) : null,
900
900
  ]);
901
901
  },
902
902
 
@@ -910,8 +910,8 @@ const FieldRenderers = {
910
910
  const arrayValue = Array.isArray(value) ? value.join(', ') : (value || '');
911
911
 
912
912
  return m('.mb-4', [
913
- m('label.block.text-sm.font-medium.text-gray-700.mb-1', { for: col.name }, label),
914
- m('input.w-full.px-3.py-2.border.border-gray-300.rounded.focus:outline-none.focus:ring-2.focus:ring-blue-500', {
913
+ m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: col.name }, label),
914
+ m('input.w-full.px-3.py-2.border.border-gray-300 dark:border-slate-600.rounded.focus:outline-none.focus:ring-2.focus:ring-blue-500', {
915
915
  id: col.name,
916
916
  name: col.name,
917
917
  type: 'text',
@@ -928,7 +928,7 @@ const FieldRenderers = {
928
928
  onChange(arr);
929
929
  },
930
930
  }),
931
- hint ? m('p.text-xs.text-gray-500.mt-1', hint) : null,
931
+ hint ? m('p.text-xs.text-gray-500 dark:text-slate-400.mt-1', hint) : null,
932
932
  ]);
933
933
  },
934
934
  };
@@ -1030,11 +1030,11 @@ const RichTextField = {
1030
1030
  const required = !col.nullable && !readonly;
1031
1031
 
1032
1032
  return m('.mb-4', [
1033
- m('label.block.text-sm.font-medium.text-gray-700.mb-1', { for: name },
1033
+ m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: name },
1034
1034
  label,
1035
1035
  required ? m('span.text-red-500', ' *') : null
1036
1036
  ),
1037
- m('div.border.border-gray-300.rounded', {
1037
+ m('div.border.border-gray-300 dark:border-slate-600.rounded', {
1038
1038
  id: editorId,
1039
1039
  class: readonly ? 'bg-gray-100 opacity-50' : '',
1040
1040
  style: 'min-height: 200px;'
@@ -1044,7 +1044,7 @@ const RichTextField = {
1044
1044
  id: name + '-value',
1045
1045
  value: value || '',
1046
1046
  }),
1047
- hint ? m('p.text-xs.text-gray-500.mt-1', hint) : null,
1047
+ hint ? m('p.text-xs.text-gray-500 dark:text-slate-400.mt-1', hint) : null,
1048
1048
  ]);
1049
1049
  }
1050
1050
  };
@@ -1112,10 +1112,10 @@ function isRichTextEmpty(value) {
1112
1112
  const LoginForm = {
1113
1113
  view: () => m('.min-h-screen.flex.items-center.justify-center.p-4.sm:p-6.bg-gradient-to-br.from-blue-600.via-indigo-600.to-purple-700', [
1114
1114
  m('.w-full.max-w-md', [
1115
- m('.bg-white.rounded-2xl.shadow-2xl.p-6.sm:p-8', [
1115
+ m('.bg-white dark:bg-slate-800.rounded-2xl.shadow-2xl.p-6.sm:p-8', [
1116
1116
  m('div.text-center.mb-6', [
1117
1117
  m('h1.text-2xl.sm:text-3xl.font-bold.text-gray-900', 'Admin Login'),
1118
- m('p.text-gray-500.text-sm.mt-1', 'Sign in to your account'),
1118
+ m('p.text-gray-500 dark:text-slate-400.text-sm.mt-1', 'Sign in to your account'),
1119
1119
  ]),
1120
1120
  m('form', {
1121
1121
  onsubmit: async (e) => {
@@ -1139,8 +1139,8 @@ const LoginForm = {
1139
1139
  }, [
1140
1140
  state.error ? m('.bg-red-50.border.border-red-200.text-red-700.px-4.py-3.rounded-lg.mb-4.text-sm', state.error) : null,
1141
1141
  m('.mb-4', [
1142
- m('label.block.text-sm.font-medium.text-gray-700.mb-2', { for: 'email' }, 'Email'),
1143
- m('input#email.w-full.px-3.py-2.5.border.border-gray-300.rounded-lg.focus:ring-2.focus:ring-blue-500.focus:border-blue-500.transition-colors', {
1142
+ m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-2', { for: 'email' }, 'Email'),
1143
+ m('input#email.w-full.px-3.py-2.5.border.border-gray-300 dark:border-slate-600.rounded-lg.focus:ring-2.focus:ring-blue-500.focus:border-blue-500.transition-colors', {
1144
1144
  type: 'email',
1145
1145
  name: 'email',
1146
1146
  required: true,
@@ -1148,8 +1148,8 @@ const LoginForm = {
1148
1148
  }),
1149
1149
  ]),
1150
1150
  m('.mb-6', [
1151
- m('label.block.text-sm.font-medium.text-gray-700.mb-2', { for: 'password' }, 'Password'),
1152
- m('input#password.w-full.px-3.py-2.5.border.border-gray-300.rounded-lg.focus:ring-2.focus:ring-blue-500.focus:border-blue-500.transition-colors', {
1151
+ m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-2', { for: 'password' }, 'Password'),
1152
+ m('input#password.w-full.px-3.py-2.5.border.border-gray-300 dark:border-slate-600.rounded-lg.focus:ring-2.focus:ring-blue-500.focus:border-blue-500.transition-colors', {
1153
1153
  type: 'password',
1154
1154
  name: 'password',
1155
1155
  required: true,
@@ -1169,10 +1169,10 @@ const LoginForm = {
1169
1169
  const SetupForm = {
1170
1170
  view: () => m('.min-h-screen.flex.items-center.justify-center.p-4.sm:p-6.bg-gradient-to-br.from-blue-600.via-indigo-600.to-purple-700', [
1171
1171
  m('.w-full.max-w-md', [
1172
- m('.bg-white.rounded-2xl.shadow-2xl.p-6.sm:p-8', [
1172
+ m('.bg-white dark:bg-slate-800.rounded-2xl.shadow-2xl.p-6.sm:p-8', [
1173
1173
  m('div.text-center.mb-6', [
1174
1174
  m('h1.text-2xl.sm:text-3xl.font-bold.text-gray-900', 'Setup Admin Account'),
1175
- m('p.text-gray-500.text-sm.mt-1', 'Create the first admin user account.'),
1175
+ m('p.text-gray-500 dark:text-slate-400.text-sm.mt-1', 'Create the first admin user account.'),
1176
1176
  ]),
1177
1177
  m('form', {
1178
1178
  onsubmit: async (e) => {
@@ -1198,16 +1198,16 @@ const SetupForm = {
1198
1198
  }, [
1199
1199
  state.error ? m('.bg-red-50.border.border-red-200.text-red-700.px-4.py-3.rounded-lg.mb-4.text-sm', state.error) : null,
1200
1200
  m('.mb-4', [
1201
- m('label.block.text-sm.font-medium.text-gray-700.mb-2', { for: 'name' }, 'Name'),
1202
- m('input#name.w-full.px-3.py-2.5.border.border-gray-300.rounded-lg.focus:ring-2.focus:ring-blue-500.focus:border-blue-500.transition-colors', {
1201
+ m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-2', { for: 'name' }, 'Name'),
1202
+ m('input#name.w-full.px-3.py-2.5.border.border-gray-300 dark:border-slate-600.rounded-lg.focus:ring-2.focus:ring-blue-500.focus:border-blue-500.transition-colors', {
1203
1203
  type: 'text',
1204
1204
  name: 'name',
1205
1205
  required: true,
1206
1206
  }),
1207
1207
  ]),
1208
1208
  m('.mb-4', [
1209
- m('label.block.text-sm.font-medium.text-gray-700.mb-2', { for: 'email' }, 'Email'),
1210
- m('input#email.w-full.px-3.py-2.5.border.border-gray-300.rounded-lg.focus:ring-2.focus:ring-blue-500.focus:border-blue-500.transition-colors', {
1209
+ m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-2', { for: 'email' }, 'Email'),
1210
+ m('input#email.w-full.px-3.py-2.5.border.border-gray-300 dark:border-slate-600.rounded-lg.focus:ring-2.focus:ring-blue-500.focus:border-blue-500.transition-colors', {
1211
1211
  type: 'email',
1212
1212
  name: 'email',
1213
1213
  required: true,
@@ -1215,8 +1215,8 @@ const SetupForm = {
1215
1215
  }),
1216
1216
  ]),
1217
1217
  m('.mb-6', [
1218
- m('label.block.text-sm.font-medium.text-gray-700.mb-2', { for: 'password' }, 'Password'),
1219
- m('input#password.w-full.px-3.py-2.5.border.border-gray-300.rounded-lg.focus:ring-2.focus:ring-blue-500.focus:border-blue-500.transition-colors', {
1218
+ m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-2', { for: 'password' }, 'Password'),
1219
+ m('input#password.w-full.px-3.py-2.5.border.border-gray-300 dark:border-slate-600.rounded-lg.focus:ring-2.focus:ring-blue-500.focus:border-blue-500.transition-colors', {
1220
1220
  type: 'password',
1221
1221
  name: 'password',
1222
1222
  required: true,
@@ -1260,7 +1260,7 @@ const ModelList = {
1260
1260
  ? m('p.text-gray-600', 'No models enabled in admin panel. Make sure your models have admin: { enabled: true }')
1261
1261
  : m('.grid.grid-cols-1.md:grid-cols-2.lg:grid-cols-3.gap-4',
1262
1262
  state.models.map(model =>
1263
- m('a.bg-white.p-6.rounded.shadow.hover:shadow-lg.transition', {
1263
+ m('a.bg-white dark:bg-slate-800.p-6.rounded.shadow.hover:shadow-lg.transition', {
1264
1264
  href: '/models/' + model.name,
1265
1265
  onclick: (e) => {
1266
1266
  e.preventDefault();
@@ -1269,7 +1269,7 @@ const ModelList = {
1269
1269
  }, [
1270
1270
  model.icon ? m('span.text-2xl.mb-2.block', model.icon) : null,
1271
1271
  m('h3.font-semibold.text-lg', model.label || model.name),
1272
- m('p.text-sm.text-gray-600.mt-2', model.table),
1272
+ m('p.text-sm.text-gray-600 dark:text-slate-400.mt-2', model.table),
1273
1273
  ])
1274
1274
  )
1275
1275
  ),
@@ -1279,14 +1279,14 @@ const ModelList = {
1279
1279
  // Format cell value based on column type
1280
1280
  function formatCellValue(value, col) {
1281
1281
  if (value === null || value === undefined) {
1282
- return m('span.text-gray-400.italic', 'null');
1282
+ return m('span.text-gray-400 dark:text-slate-500.italic', 'null');
1283
1283
  }
1284
1284
 
1285
1285
  switch (col?.type) {
1286
1286
  case 'boolean':
1287
1287
  return value
1288
1288
  ? m('span.inline-flex.items-center.px-2.py-1.rounded-full.text-xs.font-medium.bg-green-100.text-green-800', '✓ Yes')
1289
- : m('span.inline-flex.items-center.px-2.py-1.rounded-full.text-xs.font-medium.bg-gray-100.text-gray-600', '✗ No');
1289
+ : m('span.inline-flex.items-center.px-2.py-1.rounded-full.text-xs.font-medium.bg-gray-100 dark:bg-slate-800.text-gray-600', '✗ No');
1290
1290
 
1291
1291
  case 'datetime':
1292
1292
  case 'timestamp':
@@ -1305,11 +1305,11 @@ function formatCellValue(value, col) {
1305
1305
  case 'array':
1306
1306
  if (Array.isArray(value)) {
1307
1307
  return value.length > 0
1308
- ? m('span.text-xs.bg-gray-100.px-2.py-1.rounded', value.slice(0, 3).join(', ') + (value.length > 3 ? '...' : ''))
1308
+ ? m('span.text-xs.bg-gray-100 dark:bg-slate-800.px-2.py-1.rounded', value.slice(0, 3).join(', ') + (value.length > 3 ? '...' : ''))
1309
1309
  : m('span.text-gray-400', '[]');
1310
1310
  }
1311
1311
  if (typeof value === 'object') {
1312
- return m('span.text-xs.bg-gray-100.px-2.py-1.rounded.font-mono', '{...}');
1312
+ return m('span.text-xs.bg-gray-100 dark:bg-slate-800.px-2.py-1.rounded.font-mono', '{...}');
1313
1313
  }
1314
1314
  return String(value);
1315
1315
 
@@ -1373,7 +1373,7 @@ const BulkFieldUpdateDropdown = {
1373
1373
 
1374
1374
  return m('.relative.inline-block', [
1375
1375
  // Dropdown trigger
1376
- m('button.inline-flex.items-center.gap-1.px-3.py-1.5.text-sm.font-medium.text-purple-600.bg-white.border.border-purple-200.rounded.hover:bg-purple-50.transition-colors', {
1376
+ m('button.inline-flex.items-center.gap-1.px-3.py-1.5.text-sm.font-medium.text-purple-600.bg-white dark:bg-slate-800.border.border-purple-200.rounded.hover:bg-purple-50.transition-colors', {
1377
1377
  disabled: state.bulkActionInProgress,
1378
1378
  onclick: (e) => {
1379
1379
  e.stopPropagation();
@@ -1392,7 +1392,7 @@ const BulkFieldUpdateDropdown = {
1392
1392
  ]),
1393
1393
 
1394
1394
  // Dropdown menu
1395
- state.bulkFieldDropdownOpen && m('.absolute.z-50.mt-1.w-64.bg-white.rounded-lg.shadow-lg.border.border-gray-200.overflow-hidden', {
1395
+ state.bulkFieldDropdownOpen && m('.absolute.z-50.mt-1.w-64.bg-white dark:bg-slate-800.rounded-lg.shadow-lg.border.border-gray-200 dark:border-slate-600.overflow-hidden', {
1396
1396
  style: 'left: 0; top: 100%;',
1397
1397
  onclick: (e) => e.stopPropagation(),
1398
1398
  }, [
@@ -1408,8 +1408,8 @@ const BulkFieldUpdateDropdown = {
1408
1408
  // Dropdown content
1409
1409
  m('.relative.z-50.bg-white', [
1410
1410
  // Header
1411
- m('.px-3.py-2.bg-gray-50.border-b.border-gray-200', [
1412
- m('span.text-xs.font-medium.text-gray-500.uppercase.tracking-wider',
1411
+ m('.px-3.py-2.bg-gray-50 dark:bg-slate-900.border-b.border-gray-200', [
1412
+ m('span.text-xs.font-medium.text-gray-500 dark:text-slate-400.uppercase.tracking-wider',
1413
1413
  state.selectedBulkField ? 'Select Value' : 'Select Field'
1414
1414
  ),
1415
1415
  ]),
@@ -1441,7 +1441,7 @@ const BulkFieldUpdateDropdown = {
1441
1441
  state.selectedBulkField.type === 'boolean' && m('span.ml-2',
1442
1442
  option.value === true
1443
1443
  ? m('span.inline-flex.items-center.px-2.py-0.5.rounded-full.text-xs.font-medium.bg-green-100.text-green-800', '✓')
1444
- : m('span.inline-flex.items-center.px-2.py-0.5.rounded-full.text-xs.font-medium.bg-gray-100.text-gray-600', '✗')
1444
+ : m('span.inline-flex.items-center.px-2.py-0.5.rounded-full.text-xs.font-medium.bg-gray-100 dark:bg-slate-800.text-gray-600', '✗')
1445
1445
  ),
1446
1446
  ])
1447
1447
  )
@@ -1455,7 +1455,7 @@ const BulkFieldUpdateDropdown = {
1455
1455
  }, [
1456
1456
  m('.flex.items-center.gap-2', [
1457
1457
  m('span.text-gray-700', formatColumnLabel(field.label || field.name)),
1458
- m('span.text-xs.text-gray-400.uppercase', field.type),
1458
+ m('span.text-xs.text-gray-400 dark:text-slate-500.uppercase', field.type),
1459
1459
  ]),
1460
1460
  m('svg.w-4.h-4.text-gray-400', { fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor' },
1461
1461
  m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M9 5l7 7-7 7' })
@@ -1464,7 +1464,7 @@ const BulkFieldUpdateDropdown = {
1464
1464
  ),
1465
1465
 
1466
1466
  // Back button when viewing values
1467
- state.selectedBulkField && m('button.w-full.px-3.py-2.text-left.text-sm.text-gray-500.hover:bg-gray-50.border-t.border-gray-100.flex.items-center.gap-1', {
1467
+ state.selectedBulkField && m('button.w-full.px-3.py-2.text-left.text-sm.text-gray-500 dark:text-slate-400.hover:bg-gray-50 dark:hover:bg-slate-800/50 dark:hover:bg-slate-800/50.border-t.border-gray-100 dark:border-slate-700.flex.items-center.gap-1', {
1468
1468
  onclick: () => {
1469
1469
  state.selectedBulkField = null;
1470
1470
  m.redraw();
@@ -1736,16 +1736,16 @@ const RecordList = {
1736
1736
  m('.flex.items-center.justify-between.mb-4', [
1737
1737
  m('.flex.items-center.gap-3', [
1738
1738
  m('h2.text-2xl.font-bold', modelMeta?.label || modelName),
1739
- modelMeta?.softDelete ? m('.flex.rounded-lg.border.border-gray-200.p-0.5', [
1739
+ modelMeta?.softDelete ? m('.flex.rounded-lg.border.border-gray-200 dark:border-slate-600.p-0.5', [
1740
1740
  m('button.px-3.py-1.5.text-sm.font-medium.rounded-md.transition-colors', {
1741
- class: !state.trashedView ? 'bg-indigo-600.text-white' : 'text-gray-600.hover:text-gray-900',
1741
+ class: !state.trashedView ? 'bg-indigo-600.text-white' : 'text-gray-600.hover:text-gray-900 dark:hover:text-slate-100 dark:hover:text-slate-100',
1742
1742
  onclick: () => {
1743
1743
  state.trashedView = false;
1744
1744
  loadRecords(modelName, 1);
1745
1745
  },
1746
1746
  }, 'Active'),
1747
1747
  m('button.px-3.py-1.5.text-sm.font-medium.rounded-md.transition-colors', {
1748
- class: state.trashedView ? 'bg-indigo-600.text-white' : 'text-gray-600.hover:text-gray-900',
1748
+ class: state.trashedView ? 'bg-indigo-600.text-white' : 'text-gray-600.hover:text-gray-900 dark:hover:text-slate-100 dark:hover:text-slate-100',
1749
1749
  onclick: () => {
1750
1750
  state.trashedView = true;
1751
1751
  loadRecords(modelName, 1);
@@ -1820,14 +1820,14 @@ const RecordList = {
1820
1820
  m('.animate-spin.rounded-full.h-8.w-8.border-b-2.border-indigo-600'),
1821
1821
  ])
1822
1822
  : state.records.length === 0
1823
- ? m('.bg-white.rounded-lg.shadow-sm.border.border-gray-200.p-12.text-center', [
1824
- m('svg.w-12.h-12.mx-auto.text-gray-400.mb-4', { fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor' }, [
1823
+ ? m('.bg-white dark:bg-slate-800.rounded-lg.shadow-sm.border.border-gray-200 dark:border-slate-600.p-12.text-center', [
1824
+ m('svg.w-12.h-12.mx-auto.text-gray-400 dark:text-slate-500.mb-4', { fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor' }, [
1825
1825
  m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '1.5', d: 'M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4' }),
1826
1826
  ]),
1827
- m('h3.text-lg.font-medium.text-gray-900.mb-1', 'No records found'),
1827
+ m('h3.text-lg.font-medium.text-gray-900 dark:text-slate-100.mb-1', 'No records found'),
1828
1828
  m('p.text-gray-500', activeFilterCount > 0 ? 'Try adjusting your filters' : 'Get started by creating your first record'),
1829
1829
  ])
1830
- : m('.bg-white.rounded-lg.shadow-sm.border.border-gray-200.overflow-hidden', [
1830
+ : m('.bg-white dark:bg-slate-800.rounded-lg.shadow-sm.border.border-gray-200 dark:border-slate-600.overflow-hidden', [
1831
1831
  // Bulk Actions Toolbar (shown when items selected)
1832
1832
  (state.selectedRecords && state.selectedRecords.size > 0) || state.selectAllMode ? m('.bg-indigo-50.border-b.border-indigo-100.px-4.py-3.flex.items-center.justify-between', [
1833
1833
  m('.flex.items-center.gap-3', [
@@ -1853,7 +1853,7 @@ const RecordList = {
1853
1853
  ]),
1854
1854
  m('.flex.items-center.gap-2', [
1855
1855
  state.trashedView && modelMeta?.softDelete
1856
- ? m('button.inline-flex.items-center.gap-1.px-3.py-1.5.text-sm.font-medium.text-green-600.bg-white.border.border-green-200.rounded.hover:bg-green-50.transition-colors', {
1856
+ ? m('button.inline-flex.items-center.gap-1.px-3.py-1.5.text-sm.font-medium.text-green-600.bg-white dark:bg-slate-800.border.border-green-200.rounded.hover:bg-green-50.transition-colors', {
1857
1857
  disabled: state.bulkActionInProgress,
1858
1858
  onclick: async () => {
1859
1859
  if (!confirm('Restore the selected records?')) return;
@@ -1880,7 +1880,7 @@ const RecordList = {
1880
1880
  ),
1881
1881
  'Restore',
1882
1882
  ])
1883
- : m('button.inline-flex.items-center.gap-1.px-3.py-1.5.text-sm.font-medium.text-red-600.bg-white.border.border-red-200.rounded.hover:bg-red-50.transition-colors', {
1883
+ : m('button.inline-flex.items-center.gap-1.px-3.py-1.5.text-sm.font-medium.text-red-600.bg-white dark:bg-slate-800.border.border-red-200.rounded.hover:bg-red-50.transition-colors', {
1884
1884
  disabled: state.bulkActionInProgress,
1885
1885
  onclick: async () => {
1886
1886
  const count = state.selectAllMode ? state.pagination.total : state.selectedRecords.size;
@@ -1908,7 +1908,7 @@ const RecordList = {
1908
1908
  ),
1909
1909
  'Delete',
1910
1910
  ]),
1911
- !state.trashedView ? m('button.inline-flex.items-center.gap-1.px-3.py-1.5.text-sm.font-medium.text-blue-600.bg-white.border.border-blue-200.rounded.hover:bg-blue-50.transition-colors', {
1911
+ !state.trashedView ? m('button.inline-flex.items-center.gap-1.px-3.py-1.5.text-sm.font-medium.text-blue-600.bg-white dark:bg-slate-800.border.border-blue-200.rounded.hover:bg-blue-50.transition-colors', {
1912
1912
  disabled: state.bulkActionInProgress,
1913
1913
  onclick: async () => {
1914
1914
  state.bulkActionInProgress = true;
@@ -1939,7 +1939,7 @@ const RecordList = {
1939
1939
  ),
1940
1940
  'Export JSON',
1941
1941
  ]) : null,
1942
- !state.trashedView ? m('button.inline-flex.items-center.gap-1.px-3.py-1.5.text-sm.font-medium.text-green-600.bg-white.border.border-green-200.rounded.hover:bg-green-50.transition-colors', {
1942
+ !state.trashedView ? m('button.inline-flex.items-center.gap-1.px-3.py-1.5.text-sm.font-medium.text-green-600.bg-white dark:bg-slate-800.border.border-green-200.rounded.hover:bg-green-50.transition-colors', {
1943
1943
  disabled: state.bulkActionInProgress,
1944
1944
  onclick: async () => {
1945
1945
  state.bulkActionInProgress = true;
@@ -1981,7 +1981,7 @@ const RecordList = {
1981
1981
  loadRecords(modelName, state.pagination.page);
1982
1982
  },
1983
1983
  }) : null,
1984
- m('button.px-3.py-1.5.text-sm.text-gray-500.hover:text-gray-700', {
1984
+ m('button.px-3.py-1.5.text-sm.text-gray-500 dark:text-slate-400.hover:text-gray-700 dark:hover:text-slate-200 dark:hover:text-slate-200', {
1985
1985
  onclick: () => {
1986
1986
  state.selectedRecords = new Set();
1987
1987
  state.selectAllMode = false;
@@ -1996,11 +1996,11 @@ const RecordList = {
1996
1996
  m('.overflow-auto.max-h-[calc(100vh-380px)]', { style: 'position: relative;' }, [
1997
1997
  m('table.w-full.border-collapse', { style: 'min-width: 100%;' }, [
1998
1998
  // Sticky header
1999
- m('thead.bg-gray-50', { style: 'position: sticky; top: 0; z-index: 20; background: #f9fafb;' }, [
1999
+ m('thead.bg-gray-50.dark:bg-slate-900', { style: 'position: sticky; top: 0; z-index: 20;' }, [
2000
2000
  m('tr', [
2001
2001
  // Checkbox column header (sticky left, box-shadow on right)
2002
- m('th.px-4.py-3.text-left.bg-gray-50.border-b.border-gray-200', { style: 'width: 40px; position: sticky; left: 0; z-index: 15; box-shadow: 4px 0 8px -4px rgba(0,0,0,0.08);' }, [
2003
- m('input[type=checkbox].rounded.border-gray-300.text-indigo-600.focus:ring-indigo-500', {
2002
+ m('th.px-4.py-3.text-left.bg-gray-50 dark:bg-slate-900.border-b.border-gray-200', { style: 'width: 40px; position: sticky; left: 0; z-index: 15; box-shadow: 4px 0 8px -4px rgba(0,0,0,0.08);' }, [
2003
+ m('input[type=checkbox].rounded.border-gray-300 dark:border-slate-600.text-indigo-600.focus:ring-indigo-500', {
2004
2004
  checked: state.records.length > 0 && state.selectedRecords && state.selectedRecords.size === state.records.length,
2005
2005
  indeterminate: state.selectedRecords && state.selectedRecords.size > 0 && state.selectedRecords.size < state.records.length,
2006
2006
  onchange: (e) => {
@@ -2015,26 +2015,26 @@ const RecordList = {
2015
2015
  ]),
2016
2016
  // Dynamic column headers (first column sticky left with box-shadow)
2017
2017
  ...displayColumns.map((col, i) =>
2018
- m('th.px-4.py-3.text-left.text-xs.font-medium.text-gray-500.uppercase.tracking-wider.whitespace-nowrap.bg-gray-50.border-b.border-gray-200',
2018
+ m('th.px-4.py-3.text-left.text-xs.font-medium.text-gray-500 dark:text-slate-400.uppercase.tracking-wider.whitespace-nowrap.bg-gray-50 dark:bg-slate-900.border-b.border-gray-200',
2019
2019
  i === 0 ? { style: 'position: sticky; left: 40px; z-index: 15; box-shadow: 4px 0 8px -4px rgba(0,0,0,0.08);' } : {},
2020
2020
  formatColumnLabel(col.name)
2021
2021
  )
2022
2022
  ),
2023
2023
  // Sticky actions header (sticky right, box-shadow on left)
2024
- m('th.px-4.py-3.text-right.text-xs.font-medium.text-gray-500.uppercase.tracking-wider.bg-gray-50.border-b.border-gray-200', {
2024
+ m('th.px-4.py-3.text-right.text-xs.font-medium.text-gray-500 dark:text-slate-400.uppercase.tracking-wider.bg-gray-50 dark:bg-slate-900.border-b.border-gray-200', {
2025
2025
  style: 'position: sticky; right: 0; min-width: 120px; z-index: 15; box-shadow: -4px 0 8px -4px rgba(0,0,0,0.08);',
2026
2026
  }, 'Actions'),
2027
2027
  ]),
2028
2028
  ]),
2029
2029
  m('tbody.divide-y.divide-gray-100', state.records.map(record =>
2030
- m('tr.hover:bg-gray-50.transition-colors', {
2030
+ m('tr.hover:bg-gray-50 dark:hover:bg-slate-800/50 dark:hover:bg-slate-800/50.transition-colors', {
2031
2031
  class: state.selectedRecords && state.selectedRecords.has(record[primaryKey]) ? 'bg-indigo-50' : '',
2032
2032
  }, [
2033
2033
  // Checkbox cell (sticky left, box-shadow on right)
2034
2034
  m('td.px-4.py-3.bg-white', {
2035
2035
  style: 'position: sticky; left: 0; z-index: 5; box-shadow: 4px 0 8px -4px rgba(0,0,0,0.08);',
2036
2036
  }, [
2037
- m('input[type=checkbox].rounded.border-gray-300.text-indigo-600.focus:ring-indigo-500', {
2037
+ m('input[type=checkbox].rounded.border-gray-300 dark:border-slate-600.text-indigo-600.focus:ring-indigo-500', {
2038
2038
  checked: state.selectedRecords && state.selectedRecords.has(record[primaryKey]),
2039
2039
  onchange: (e) => {
2040
2040
  if (!state.selectedRecords) state.selectedRecords = new Set();
@@ -2049,7 +2049,7 @@ const RecordList = {
2049
2049
  ]),
2050
2050
  // Dynamic cell values (first column sticky left with box-shadow)
2051
2051
  ...displayColumns.map((col, i) =>
2052
- m('td.px-4.py-3.text-sm.whitespace-nowrap.text-gray-700.bg-white',
2052
+ m('td.px-4.py-3.text-sm.whitespace-nowrap.text-gray-700 dark:text-slate-300.bg-white',
2053
2053
  i === 0 ? { style: 'position: sticky; left: 40px; z-index: 5; box-shadow: 4px 0 8px -4px rgba(0,0,0,0.08);' } : {},
2054
2054
  formatCellValue(record[col.name], col)
2055
2055
  )
@@ -2172,7 +2172,7 @@ const RecordForm = {
2172
2172
  state.loading ? m('p.text-gray-600', 'Loading...') :
2173
2173
  state.error && !modelMeta ? m('.bg-red-100.border.border-red-400.text-red-700.px-4.py-3.rounded', state.error) :
2174
2174
 
2175
- m('form.bg-white.rounded.shadow.flex.flex-col', {
2175
+ m('form.bg-white dark:bg-slate-800.rounded.shadow.flex.flex-col', {
2176
2176
  style: 'min-height: calc(100vh - 280px);',
2177
2177
  onsubmit: async (e) => {
2178
2178
  e.preventDefault();
@@ -2261,16 +2261,16 @@ const RecordForm = {
2261
2261
  };
2262
2262
 
2263
2263
  return renderer(col, value, onChange, isReadonly);
2264
- }) : m('p.text-gray-600.mb-4', 'Loading form fields...'),
2264
+ }) : m('p.text-gray-600 dark:text-slate-400.mb-4', 'Loading form fields...'),
2265
2265
  ]),
2266
2266
 
2267
2267
  // Sticky footer buttons
2268
- m('.flex.gap-4.p-4.border-t.bg-gray-50.sticky.bottom-0', [
2268
+ m('.flex.gap-4.p-4.border-t.bg-gray-50 dark:bg-slate-900.sticky.bottom-0', [
2269
2269
  m('button.bg-blue-600.text-white.px-6.py-2.rounded.hover:bg-blue-700.disabled:opacity-50', {
2270
2270
  type: 'submit',
2271
2271
  disabled: state.loading,
2272
2272
  }, state.loading ? 'Saving...' : 'Save'),
2273
- m('button.bg-gray-200.text-gray-800.px-6.py-2.rounded.hover:bg-gray-300[type=button]', {
2273
+ m('button.bg-gray-200 dark:bg-slate-700.text-gray-800 dark:text-slate-200.px-6.py-2.rounded.hover:bg-gray-300 dark:hover:bg-slate-600 dark:hover:bg-slate-600[type=button]', {
2274
2274
  onclick: () => m.route.set('/models/' + modelName),
2275
2275
  }, 'Cancel'),
2276
2276
  ]),