@mrintel/villain-ui 0.8.1 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +52 -3
  2. package/dist/components/buttons/Button.svelte +1 -1
  3. package/dist/components/buttons/FloatingActionButton.svelte +1 -1
  4. package/dist/components/buttons/IconButton.svelte +2 -2
  5. package/dist/components/buttons/LinkButton.svelte +1 -1
  6. package/dist/components/buttons/buttonClasses.js +7 -7
  7. package/dist/components/cards/Card.svelte +43 -11
  8. package/dist/components/cards/Card.svelte.d.ts +11 -0
  9. package/dist/components/cards/Panel.svelte +44 -5
  10. package/dist/components/cards/Panel.svelte.d.ts +7 -0
  11. package/dist/components/cards/SectionHeader.svelte +2 -2
  12. package/dist/components/data/Avatar.svelte +1 -1
  13. package/dist/components/data/Badge.svelte +3 -3
  14. package/dist/components/data/CalendarGrid.svelte +6 -4
  15. package/dist/components/data/CalendarGrid.svelte.d.ts +3 -0
  16. package/dist/components/data/CodeBlock.svelte +1 -1
  17. package/dist/components/data/List.svelte +1 -1
  18. package/dist/components/data/Pagination.svelte +3 -3
  19. package/dist/components/data/Stat.svelte +3 -5
  20. package/dist/components/data/Table.svelte +12 -14
  21. package/dist/components/data/Tag.svelte +1 -1
  22. package/dist/components/data/WeekHeatmap.svelte +56 -28
  23. package/dist/components/forms/Checkbox.svelte +1 -1
  24. package/dist/components/forms/DatePicker.svelte +3 -1
  25. package/dist/components/forms/DateTimePicker.svelte +3 -1
  26. package/dist/components/forms/FileUpload.svelte +1 -1
  27. package/dist/components/forms/FormSection.svelte +21 -0
  28. package/dist/components/forms/FormSection.svelte.d.ts +14 -0
  29. package/dist/components/forms/InputGroup.svelte +1 -1
  30. package/dist/components/forms/RadioGroup.svelte +0 -1
  31. package/dist/components/forms/RangeSlider.svelte +2 -6
  32. package/dist/components/forms/SelectMenu.svelte +4 -3
  33. package/dist/components/forms/Switch.svelte +1 -1
  34. package/dist/components/forms/TimePicker.svelte +1 -1
  35. package/dist/components/forms/TimePickerGrid.svelte +0 -3
  36. package/dist/components/forms/formClasses.d.ts +2 -2
  37. package/dist/components/forms/formClasses.js +2 -2
  38. package/dist/components/forms/index.d.ts +1 -0
  39. package/dist/components/forms/index.js +1 -0
  40. package/dist/components/navigation/Breadcrumbs.svelte +3 -3
  41. package/dist/components/navigation/ContextMenu.svelte +2 -2
  42. package/dist/components/navigation/DropdownMenu.svelte +2 -2
  43. package/dist/components/navigation/Menu.svelte +2 -2
  44. package/dist/components/navigation/Navbar.svelte +10 -2
  45. package/dist/components/navigation/Sidebar.svelte +6 -9
  46. package/dist/components/navigation/Tabs.svelte +2 -2
  47. package/dist/components/overlays/Alert.svelte +1 -1
  48. package/dist/components/overlays/CommandPalette.svelte +2 -2
  49. package/dist/components/overlays/Drawer.svelte +5 -5
  50. package/dist/components/overlays/Dropdown.svelte +1 -1
  51. package/dist/components/overlays/Modal.svelte +2 -2
  52. package/dist/components/overlays/Popover.svelte +1 -1
  53. package/dist/components/overlays/ProgressBar.svelte +3 -3
  54. package/dist/components/overlays/SkeletonLoader.svelte +2 -2
  55. package/dist/components/overlays/Spinner.svelte +1 -1
  56. package/dist/components/overlays/Toast.svelte +2 -2
  57. package/dist/components/overlays/Tooltip.svelte +1 -1
  58. package/dist/components/typography/Code.svelte +1 -1
  59. package/dist/components/utilities/Carousel.svelte +4 -4
  60. package/dist/components/utilities/Collapse.svelte +1 -1
  61. package/dist/components/utilities/Hero.svelte +3 -2
  62. package/dist/components/utilities/SystemConsole.svelte +1 -1
  63. package/dist/components/utilities/SystemInterface.svelte +1 -2
  64. package/dist/components/wizard/StepForm.svelte +1 -1
  65. package/dist/index.d.ts +2 -2
  66. package/dist/index.js +2 -2
  67. package/dist/theme.css +207 -190
  68. package/package.json +2 -1
@@ -1,4 +1,4 @@
1
- <script lang="ts">import Tooltip from '../overlays/Tooltip.svelte';
1
+ <script lang="ts">import Portal from '../utilities/Portal.svelte';
2
2
  let { matrix, rowOrder, dayLabels, hourLabel = (h) => String(h), hourLabelEvery = 1, normalize = true, highlight, markNow = false, cellLabel, onCellSelect, selected = null, ariaLabel = 'Weekly traffic heatmap', class: className = '' } = $props();
3
3
  const BASE_DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
4
4
  const hours = Array.from({ length: 24 }, (_, h) => h);
@@ -20,6 +20,14 @@ function labelFor(cell, dayLabel, isNow) {
20
20
  return isNow ? `${text} (now)` : text;
21
21
  }
22
22
  const fill = (intensity) => `color-mix(in srgb, var(--color-accent) ${intensity}%, var(--color-base-2))`;
23
+ // Single branded tooltip, portaled to <body> so it escapes the overflow-x
24
+ // scroll wrapper (a per-cell absolute tooltip would expand/scroll the grid).
25
+ let tip = $state(null);
26
+ function showTip(el, label) {
27
+ const r = el.getBoundingClientRect();
28
+ tip = { label, x: r.left + r.width / 2, y: r.top };
29
+ }
30
+ const hideTip = () => (tip = null);
23
31
  </script>
24
32
 
25
33
  {#if matrix.length > 0}
@@ -46,34 +54,44 @@ const fill = (intensity) => `color-mix(in srgb, var(--color-accent) ${intensity}
46
54
  {@const isNow = markNow && row.day === nowDay && hour === nowHour}
47
55
  {@const isSel = !!selected && selected.day === row.day && selected.hour === hour}
48
56
  {@const label = labelFor(cell, row.label, isNow)}
49
- <!-- ponytail: one Tooltip per cell (168 total). Fine for a heatmap rendered
50
- once per page; swap for a single hovered-cell-driven tooltip if it ever bites. -->
51
- <Tooltip content={label}>
52
- {#if onCellSelect}
53
- <button
54
- type="button"
55
- class="heatmap-cell"
56
- class:is-highlight={hl}
57
- class:is-ring={isNow || isSel}
58
- style="background-color: {fill(cell.intensity)}"
59
- aria-label={label}
60
- aria-pressed={isSel}
61
- onclick={() => onCellSelect(cell)}
62
- ></button>
63
- {:else}
64
- <div
65
- class="heatmap-cell"
66
- class:is-highlight={hl}
67
- class:is-ring={isNow || isSel}
68
- style="background-color: {fill(cell.intensity)}"
69
- aria-hidden="true"
70
- ></div>
71
- {/if}
72
- </Tooltip>
57
+ {#if onCellSelect}
58
+ <button
59
+ type="button"
60
+ class="heatmap-cell"
61
+ class:is-highlight={hl}
62
+ class:is-ring={isNow || isSel}
63
+ style="background-color: {fill(cell.intensity)}"
64
+ aria-label={label}
65
+ aria-pressed={isSel}
66
+ onclick={() => onCellSelect(cell)}
67
+ onmouseenter={(e) => showTip(e.currentTarget, label)}
68
+ onmouseleave={hideTip}
69
+ onfocus={(e) => showTip(e.currentTarget, label)}
70
+ onblur={hideTip}
71
+ ></button>
72
+ {:else}
73
+ <div
74
+ class="heatmap-cell"
75
+ class:is-highlight={hl}
76
+ class:is-ring={isNow || isSel}
77
+ style="background-color: {fill(cell.intensity)}"
78
+ aria-hidden="true"
79
+ onmouseenter={(e) => showTip(e.currentTarget, label)}
80
+ onmouseleave={hideTip}
81
+ ></div>
82
+ {/if}
73
83
  {/each}
74
84
  {/each}
75
85
  </div>
76
86
  </div>
87
+
88
+ {#if tip}
89
+ <Portal>
90
+ <div class="heatmap-tip panel-floating" style="left: {tip.x}px; top: {tip.y}px;">
91
+ {tip.label}
92
+ </div>
93
+ </Portal>
94
+ {/if}
77
95
  {/if}
78
96
 
79
97
  <style>
@@ -102,15 +120,25 @@ const fill = (intensity) => `color-mix(in srgb, var(--color-accent) ${intensity}
102
120
  }
103
121
 
104
122
  .heatmap-cell {
105
- display: block;
106
- width: 100%;
107
123
  height: var(--heatmap-cell-size, 1.5rem);
108
124
  min-width: var(--heatmap-cell-size, 1.5rem);
109
- border-radius: 2px;
125
+ border-radius: 0;
110
126
  border: 0;
111
127
  padding: 0;
112
128
  }
113
129
 
130
+ .heatmap-tip {
131
+ position: fixed;
132
+ transform: translate(-50%, calc(-100% - 8px));
133
+ z-index: var(--z-50, 50);
134
+ pointer-events: none;
135
+ white-space: nowrap;
136
+ padding: 0.4rem 0.75rem;
137
+ border-radius: var(--radius-sm);
138
+ font-size: 0.8rem;
139
+ color: var(--color-text);
140
+ }
141
+
114
142
  button.heatmap-cell {
115
143
  cursor: pointer;
116
144
  }
@@ -12,7 +12,7 @@ const hasError = $derived(error || !!validationMessage);
12
12
  {disabled}
13
13
  bind:checked
14
14
  onchange={onchange}
15
- class="w-6 h-6 rounded-sm border-2 bg-transparent appearance-none transition-all duration-200 ease-luxe cursor-pointer checked:bg-accent checked:border-accent checked:accent-glow focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 focus:ring-offset-base-1 relative {hasError ? 'border-error' : 'border-border-strong'} {disabled ? 'cursor-not-allowed' : ''}"
15
+ class="w-6 h-6 rounded-sm border-2 bg-transparent appearance-none transition-all duration-200 ease-luxe cursor-pointer checked:bg-accent checked:border-accent focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 focus:ring-offset-base-1 relative {hasError ? 'border-error' : 'border-[var(--color-border-bright)]'} {disabled ? 'cursor-not-allowed' : ''}"
16
16
  />
17
17
  {#if iconBefore}
18
18
  <span class="inline-flex items-center justify-center text-text-soft">
@@ -246,7 +246,7 @@ const errorClasses = $derived(hasError ? 'error-state' : '');
246
246
  role="dialog"
247
247
  aria-modal="true"
248
248
  aria-label="Choose date"
249
- class="calendar-dropdown {variant === 'glass' ? 'glass-panel' : 'panel-floating'} rounded-lg overflow-hidden"
249
+ class="calendar-dropdown {variant === 'glass' ? 'glass-panel' : 'panel-floating'} rounded-[var(--radius-sm)] overflow-hidden"
250
250
  onclick={(e) => e.stopPropagation()}
251
251
  >
252
252
  <CalendarGrid
@@ -254,6 +254,8 @@ const errorClasses = $derived(hasError ? 'error-state' : '');
254
254
  selectedDate={selectedDate}
255
255
  onDateSelect={handleDateSelect}
256
256
  highlightToday={true}
257
+ minDate={minDate}
258
+ maxDate={maxDate}
257
259
  class="calendar-picker"
258
260
  />
259
261
  </div>
@@ -256,7 +256,7 @@ const errorClasses = $derived(hasError ? 'error-state' : '');
256
256
  role="dialog"
257
257
  aria-modal="true"
258
258
  aria-label="Choose date and time"
259
- class="datetime-picker-dropdown {variant === 'glass' ? 'glass-panel' : 'panel-floating'} rounded-lg overflow-hidden"
259
+ class="datetime-picker-dropdown {variant === 'glass' ? 'glass-panel' : 'panel-floating'} rounded-[var(--radius-sm)] overflow-hidden"
260
260
  onclick={(e) => e.stopPropagation()}
261
261
  >
262
262
  <!-- Tab navigation -->
@@ -308,6 +308,8 @@ const errorClasses = $derived(hasError ? 'error-state' : '');
308
308
  selectedDate={selectedDate}
309
309
  onDateSelect={handleDateSelect}
310
310
  highlightToday={true}
311
+ minDate={minDateStr ? new Date(minDateStr + 'T00:00:00') : undefined}
312
+ maxDate={maxDateStr ? new Date(maxDateStr + 'T00:00:00') : undefined}
311
313
  class="calendar-picker"
312
314
  />
313
315
  {:else}
@@ -67,7 +67,7 @@ const fileList = $derived(files ? Array.from(files) : []);
67
67
  ondrop={handleDrop}
68
68
  onclick={handleClick}
69
69
  onkeydown={(e) => e.key === 'Enter' && handleClick()}
70
- class="panel-raised rounded-[var(--radius-lg)] p-8 text-center cursor-pointer transition-all duration-300 ease-luxe {isDragging ? 'border-2 border-accent accent-glow bg-base-2' : ''} {hasError && !isDragging ? 'border-2 border-error' : ''} {disabled ? 'opacity-50 cursor-not-allowed pointer-events-none' : ''}"
70
+ class="panel-raised rounded-[var(--radius-sm)] p-8 text-center cursor-pointer transition-all duration-300 ease-luxe {isDragging ? 'border-2 border-accent accent-glow bg-base-2' : ''} {hasError && !isDragging ? 'border-2 border-error' : ''} {disabled ? 'opacity-50 cursor-not-allowed pointer-events-none' : ''}"
71
71
  >
72
72
  <div class="flex flex-col items-center gap-2">
73
73
  {#if icon}
@@ -0,0 +1,21 @@
1
+ <script lang="ts">let { legend, description, columns = 1, class: className = '', children, ...restProps } = $props();
2
+ export {};
3
+ </script>
4
+
5
+ <fieldset class="border-0 p-0 m-0 min-w-0 {className}" {...restProps}>
6
+ {#if legend}
7
+ <legend
8
+ class="font-[var(--font-mono)] text-xs uppercase tracking-[0.08em] text-[var(--color-text-soft)] p-0 mb-2 w-full border-b border-[var(--color-border-subtle)] pb-3"
9
+ >
10
+ {legend}
11
+ </legend>
12
+ {/if}
13
+ {#if description}
14
+ <p class="text-sm text-[var(--color-text-soft)] mt-3 mb-6">{description}</p>
15
+ {:else if legend}
16
+ <div class="mt-6"></div>
17
+ {/if}
18
+ <div class="grid gap-5 {columns === 2 ? 'md:grid-cols-2' : ''}">
19
+ {@render children?.()}
20
+ </div>
21
+ </fieldset>
@@ -0,0 +1,14 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ /** Mono uppercase section label (e.g. "01 / IDENTITY"). */
4
+ legend?: string;
5
+ /** Supporting copy under the legend. */
6
+ description?: string;
7
+ /** Field layout: single column (default) or two columns on md+. */
8
+ columns?: 1 | 2;
9
+ class?: string;
10
+ children?: Snippet;
11
+ }
12
+ declare const FormSection: import("svelte").Component<Props, {}, "">;
13
+ type FormSection = ReturnType<typeof FormSection>;
14
+ export default FormSection;
@@ -2,6 +2,6 @@
2
2
  // No props needed for standard slot usage
3
3
  </script>
4
4
 
5
- <div class="flex items-stretch rounded-[var(--radius-lg)] overflow-hidden panel-raised [&>*:not(:first-child)]:ml-[-1px] [&>*:not(:first-child):not(:last-child)]:rounded-none [&>*:first-child]:rounded-r-none [&>*:last-child]:rounded-l-none">
5
+ <div class="flex items-stretch rounded-[var(--radius-sm)] overflow-hidden panel-raised [&>*:not(:first-child)]:ml-[-1px] [&>*:not(:first-child):not(:last-child)]:rounded-none [&>*:first-child]:rounded-r-none [&>*:last-child]:rounded-l-none">
6
6
  <slot />
7
7
  </div>
@@ -83,5 +83,4 @@ export {};
83
83
  height: 0.75rem;
84
84
  border-radius: var(--radius-pill);
85
85
  background: var(--color-accent);
86
- box-shadow: var(--shadow-accent-glow);
87
86
  }</style>
@@ -47,7 +47,6 @@ const percentage = $derived(max === min ? 0 : ((value - min) / (max - min)) * 10
47
47
  height: 1.25rem;
48
48
  border-radius: var(--radius-pill);
49
49
  background: var(--color-accent);
50
- box-shadow: var(--shadow-accent-glow);
51
50
  cursor: pointer;
52
51
  transition: transform 0.2s var(--ease-luxe);
53
52
  }
@@ -62,7 +61,6 @@ const percentage = $derived(max === min ? 0 : ((value - min) / (max - min)) * 10
62
61
  height: 1.25rem;
63
62
  border-radius: var(--radius-pill);
64
63
  background: var(--color-accent);
65
- box-shadow: var(--shadow-accent-glow);
66
64
  cursor: pointer;
67
65
  transition: transform 0.2s var(--ease-luxe);
68
66
  border: none;
@@ -73,15 +71,13 @@ const percentage = $derived(max === min ? 0 : ((value - min) / (max - min)) * 10
73
71
  }
74
72
 
75
73
  input[type="range"]:focus::-webkit-slider-thumb {
76
- box-shadow:
77
- var(--shadow-accent-glow),
74
+ box-shadow:
78
75
  0 0 0 3px var(--color-base-1),
79
76
  0 0 0 5px var(--color-accent);
80
77
  }
81
78
 
82
79
  input[type="range"]:focus::-moz-range-thumb {
83
- box-shadow:
84
- var(--shadow-accent-glow),
80
+ box-shadow:
85
81
  0 0 0 3px var(--color-base-1),
86
82
  0 0 0 5px var(--color-accent);
87
83
  }
@@ -86,16 +86,17 @@ function handleListboxKeyDown(event) {
86
86
  break;
87
87
  }
88
88
  }
89
- function scrollOptionIntoView(index) {
89
+ function scrollOptionIntoView(index, block = 'nearest') {
90
90
  requestAnimationFrame(() => {
91
91
  const optionEl = listboxElement?.querySelector(`[data-index="${index}"]`);
92
- optionEl?.scrollIntoView({ block: 'nearest' });
92
+ optionEl?.scrollIntoView({ block });
93
93
  });
94
94
  }
95
- // Set highlighted index and focus listbox when dropdown opens
95
+ // Set highlighted index, scroll it into view, and focus listbox when dropdown opens
96
96
  $effect(() => {
97
97
  if (open) {
98
98
  highlightedIndex = selectedIndex >= 0 ? selectedIndex : 0;
99
+ scrollOptionIntoView(highlightedIndex, 'center');
99
100
  if (listboxElement) {
100
101
  requestAnimationFrame(() => listboxElement?.focus());
101
102
  }
@@ -14,7 +14,7 @@ const hasError = $derived(error || !!validationMessage);
14
14
  {disabled}
15
15
  bind:checked
16
16
  onchange={onchange}
17
- class="w-14 h-7 rounded-pill bg-base-3 border appearance-none transition-all duration-300 ease-luxe cursor-pointer checked:bg-accent checked:accent-glow focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 focus:ring-offset-base-1 relative {hasError ? 'border-error' : 'border-border'} {disabled ? 'cursor-not-allowed' : ''}"
17
+ class="w-14 h-7 rounded-pill bg-base-3 border appearance-none transition-all duration-300 ease-luxe cursor-pointer checked:bg-accent focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 focus:ring-offset-base-1 relative {hasError ? 'border-error' : 'border-border'} {disabled ? 'cursor-not-allowed' : ''}"
18
18
  />
19
19
  {#if iconBefore}
20
20
  <span class="inline-flex items-center justify-center text-text-soft">
@@ -169,7 +169,7 @@ const errorClasses = $derived(hasError ? 'error-state' : '');
169
169
  role="dialog"
170
170
  aria-modal="true"
171
171
  aria-label="Choose time"
172
- class="time-picker-dropdown {variant === 'glass' ? 'glass-panel' : 'panel-floating'} rounded-lg overflow-hidden"
172
+ class="time-picker-dropdown {variant === 'glass' ? 'glass-panel' : 'panel-floating'} rounded-[var(--radius-sm)] overflow-hidden"
173
173
  >
174
174
  <TimePickerGrid
175
175
  bind:hours={selectedHours}
@@ -445,7 +445,6 @@ $effect(() => {
445
445
  border-color: var(--color-accent);
446
446
  color: var(--color-base-0);
447
447
  font-weight: 700;
448
- box-shadow: 0 0 12px var(--color-accent-glow);
449
448
  }
450
449
 
451
450
  .time-option.disabled,
@@ -482,8 +481,6 @@ $effect(() => {
482
481
 
483
482
  .confirm-button:hover:not(:disabled) {
484
483
  background: var(--color-accent-hover);
485
- box-shadow: 0 0 20px var(--color-accent-glow);
486
- transform: translateY(-1px);
487
484
  }
488
485
 
489
486
  .confirm-button:active:not(:disabled) {
@@ -1,3 +1,3 @@
1
- export declare const baseInputClasses = "glass-panel rounded-[var(--radius-lg)] px-5 py-3.5 font-body text-text placeholder:text-text-muted transition-all duration-300 ease-luxe w-full";
2
- export declare const focusClasses = "focus:outline-none focus:border-accent focus:accent-glow";
1
+ export declare const baseInputClasses = "input-well rounded-[var(--radius-sm)] px-5 py-3.5 font-body text-text placeholder:text-text-muted transition-all duration-150 ease-luxe w-full";
2
+ export declare const focusClasses = "focus:outline-none focus:border-[var(--color-accent)]";
3
3
  export declare const disabledClasses = "opacity-50 cursor-not-allowed";
@@ -1,3 +1,3 @@
1
- export const baseInputClasses = 'glass-panel rounded-[var(--radius-lg)] px-5 py-3.5 font-body text-text placeholder:text-text-muted transition-all duration-300 ease-luxe w-full';
2
- export const focusClasses = 'focus:outline-none focus:border-accent focus:accent-glow';
1
+ export const baseInputClasses = 'input-well rounded-[var(--radius-sm)] px-5 py-3.5 font-body text-text placeholder:text-text-muted transition-all duration-150 ease-luxe w-full';
2
+ export const focusClasses = 'focus:outline-none focus:border-[var(--color-accent)]';
3
3
  export const disabledClasses = 'opacity-50 cursor-not-allowed';
@@ -11,3 +11,4 @@ export { default as DatePicker } from './DatePicker.svelte';
11
11
  export { default as TimePicker } from './TimePicker.svelte';
12
12
  export { default as DateTimePicker } from './DateTimePicker.svelte';
13
13
  export { default as SelectMenu } from './SelectMenu.svelte';
14
+ export { default as FormSection } from './FormSection.svelte';
@@ -11,3 +11,4 @@ export { default as DatePicker } from './DatePicker.svelte';
11
11
  export { default as TimePicker } from './TimePicker.svelte';
12
12
  export { default as DateTimePicker } from './DateTimePicker.svelte';
13
13
  export { default as SelectMenu } from './SelectMenu.svelte';
14
+ export { default as FormSection } from './FormSection.svelte';
@@ -9,7 +9,7 @@ export {};
9
9
  {#if item.href}
10
10
  <a
11
11
  href={item.href}
12
- class="flex items-center gap-2 text-sm font-[var(--font-body)] text-[var(--color-text-soft)] hover:text-[var(--color-accent)] hover:underline transition-colors duration-200 ease-[var(--ease-luxe)]"
12
+ class="flex items-center gap-2 text-xs font-[var(--font-mono)] uppercase tracking-[0.08em] text-[var(--color-text-soft)] hover:text-[var(--color-accent)] hover:underline transition-colors duration-200 ease-[var(--ease-luxe)]"
13
13
  >
14
14
  {#if item.icon}
15
15
  <span class="inline-flex items-center justify-center">
@@ -21,7 +21,7 @@ export {};
21
21
  {:else if item.onclick}
22
22
  <button
23
23
  onclick={item.onclick}
24
- class="flex items-center gap-2 text-sm font-[var(--font-body)] text-[var(--color-text-soft)] hover:text-[var(--color-accent)] hover:underline transition-colors duration-200 ease-[var(--ease-luxe)] cursor-pointer"
24
+ class="flex items-center gap-2 text-xs font-[var(--font-mono)] uppercase tracking-[0.08em] text-[var(--color-text-soft)] hover:text-[var(--color-accent)] hover:underline transition-colors duration-200 ease-[var(--ease-luxe)] cursor-pointer"
25
25
  >
26
26
  {#if item.icon}
27
27
  <span class="inline-flex items-center justify-center">
@@ -31,7 +31,7 @@ export {};
31
31
  {item.label}
32
32
  </button>
33
33
  {:else}
34
- <span class="flex items-center gap-2 text-sm font-[var(--font-body)] {index === items.length - 1 ? 'text-[var(--color-text)]' : 'text-[var(--color-text-soft)]'}">
34
+ <span class="flex items-center gap-2 text-xs font-[var(--font-mono)] uppercase tracking-[0.08em] {index === items.length - 1 ? 'text-[var(--color-text)]' : 'text-[var(--color-text-soft)]'}">
35
35
  {#if item.icon}
36
36
  <span class="inline-flex items-center justify-center">
37
37
  {@render item.icon()}
@@ -111,7 +111,7 @@ $effect(() => {
111
111
  role="menu"
112
112
  tabindex="-1"
113
113
  onkeydown={handleMenuKeyDown}
114
- class="fixed z-[var(--z-50)] glass-panel rounded-[var(--radius-lg)] shadow-[var(--shadow-deep)] min-w-[12rem] animate-[fade-in_0.15s_var(--ease-luxe)]"
114
+ class="fixed z-[var(--z-50)] glass-panel rounded-[var(--radius-sm)] shadow-[var(--shadow-deep)] min-w-[12rem] animate-[fade-in_0.15s_var(--ease-luxe)]"
115
115
  style="left: {x}px; top: {y}px;"
116
116
  >
117
117
  {#each items as item, index}
@@ -121,7 +121,7 @@ $effect(() => {
121
121
  tabindex={index === selectedIndex ? 0 : -1}
122
122
  onclick={() => handleItemClick(item)}
123
123
  disabled={item.disabled}
124
- class="w-full text-left px-4 py-2 text-sm text-text hover:bg-base-3 transition-colors duration-200 flex items-center gap-2 {item.disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}"
124
+ class="w-full text-left px-4 py-2 font-[var(--font-mono)] text-xs uppercase tracking-[0.08em] text-text hover:bg-base-3 transition-colors duration-200 flex items-center gap-2 {item.disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}"
125
125
  >
126
126
  {#if item.icon}
127
127
  {@render item.icon()}
@@ -117,7 +117,7 @@ $effect(() => {
117
117
  role="menu"
118
118
  tabindex="-1"
119
119
  onkeydown={handleMenuKeyDown}
120
- class="absolute {placementClasses[placement]} z-[var(--z-50)] glass-panel rounded-[var(--radius-lg)] shadow-[var(--shadow-deep)] min-w-[12rem] animate-[fade-up_0.2s_var(--ease-luxe)]"
120
+ class="absolute {placementClasses[placement]} z-[var(--z-50)] glass-panel rounded-[var(--radius-sm)] shadow-[var(--shadow-deep)] min-w-[12rem] animate-[scale-in_0.15s_var(--ease-sharp)]"
121
121
  >
122
122
  {#each items as item, index}
123
123
  <button
@@ -126,7 +126,7 @@ $effect(() => {
126
126
  tabindex={index === selectedIndex ? 0 : -1}
127
127
  onclick={() => handleItemClick(item)}
128
128
  disabled={item.disabled}
129
- class="w-full text-left px-4 py-2 text-sm text-text hover:bg-base-3 transition-colors duration-200 flex items-center gap-2 {item.disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}"
129
+ class="w-full text-left px-4 py-2 font-[var(--font-mono)] text-xs uppercase tracking-[0.08em] text-text hover:bg-base-3 transition-colors duration-200 flex items-center gap-2 {item.disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}"
130
130
  >
131
131
  {#if item.icon}
132
132
  {@render item.icon()}
@@ -49,7 +49,7 @@ $effect(() => {
49
49
  export {};
50
50
  </script>
51
51
 
52
- <div bind:this={menuContainer} role="menu" tabindex="-1" onkeydown={handleMenuKeyDown} class="glass-panel rounded-[var(--radius-lg)] p-3 shadow-[var(--shadow-deep)]">
52
+ <div bind:this={menuContainer} role="menu" tabindex="-1" onkeydown={handleMenuKeyDown} class="glass-panel rounded-[var(--radius-sm)] p-3 shadow-[var(--shadow-deep)]">
53
53
  {#if children}
54
54
  {@render children()}
55
55
  {:else if items}
@@ -60,7 +60,7 @@ export {};
60
60
  tabindex={index === selectedIndex ? 0 : -1}
61
61
  onclick={() => handleItemClick(item)}
62
62
  disabled={item.disabled}
63
- class="flex items-center gap-2 w-full px-4 py-3 rounded-[var(--radius-md)] text-[var(--color-text)] text-sm font-[var(--font-body)] hover:bg-[var(--color-base-3)] hover-lift transition-all duration-200 ease-[var(--ease-luxe)] {item.disabled ? 'opacity-50 pointer-events-none' : 'cursor-pointer'}"
63
+ class="flex items-center gap-2 w-full px-4 py-3 rounded-[var(--radius-sm)] text-[var(--color-text)] text-xs font-[var(--font-mono)] uppercase tracking-[0.08em] hover:bg-[var(--color-base-3)] hover-lift transition-all duration-200 ease-[var(--ease-luxe)] {item.disabled ? 'opacity-50 pointer-events-none' : 'cursor-pointer'}"
64
64
  >
65
65
  {#if item.icon}
66
66
  {@render item.icon()}
@@ -80,9 +80,18 @@ export {};
80
80
  </nav>
81
81
 
82
82
  <style>
83
+ /* System text: navigation is mono, uppercase, tracked */
84
+ nav :global(a),
85
+ nav :global(button) {
86
+ font-family: var(--font-mono);
87
+ font-size: var(--text-xs);
88
+ text-transform: uppercase;
89
+ letter-spacing: 0.08em;
90
+ }
91
+
83
92
  nav :global(.navbar-logo) {
84
93
  color: var(--color-accent);
85
- font-weight: 600;
94
+ font-weight: 700;
86
95
  font-size: var(--text-lg);
87
96
  }
88
97
 
@@ -102,7 +111,6 @@ export {};
102
111
  right: 0;
103
112
  height: 2px;
104
113
  background: var(--color-accent);
105
- box-shadow: var(--shadow-accent-glow);
106
114
  }
107
115
 
108
116
  nav :global(a:not(.active):hover),
@@ -171,7 +171,7 @@ export {};
171
171
  justify-content: center;
172
172
  width: 2.5rem;
173
173
  height: 2.5rem;
174
- border-radius: var(--radius-pill);
174
+ border-radius: var(--radius-sm);
175
175
  background: var(--color-accent-overlay-20);
176
176
  color: var(--color-accent);
177
177
  font-weight: 600;
@@ -204,11 +204,6 @@ export {};
204
204
  font-weight: 600;
205
205
  }
206
206
 
207
- aside :global(a.active),
208
- aside :global(button.active) {
209
- box-shadow: var(--shadow-accent-glow);
210
- }
211
-
212
207
  aside :global(a:not(.active):hover),
213
208
  aside :global(button:not(.active):hover) {
214
209
  background: var(--color-base-3);
@@ -222,7 +217,7 @@ export {};
222
217
  gap: 0.75rem;
223
218
  padding: 0.75rem 1rem;
224
219
  margin: 0.25rem 0.5rem;
225
- border-radius: var(--radius-md);
220
+ border-radius: 0;
226
221
  color: var(--color-text-soft);
227
222
  text-decoration: none;
228
223
  transition: all 0.2s var(--ease-luxe);
@@ -231,6 +226,8 @@ export {};
231
226
  background: transparent;
232
227
  width: calc(100% - 1rem);
233
228
  text-align: left;
234
- font-family: var(--font-body);
235
- font-size: 0.875rem;
229
+ font-family: var(--font-mono);
230
+ font-size: var(--text-xs);
231
+ text-transform: uppercase;
232
+ letter-spacing: 0.08em;
236
233
  }</style>
@@ -59,7 +59,7 @@ const orientationClasses = {
59
59
  horizontal: 'flex flex-row',
60
60
  vertical: 'flex flex-col'
61
61
  };
62
- const radiusClass = variant === 'pills' ? 'rounded-pill' : 'rounded-[var(--radius-md)]';
62
+ const radiusClass = variant === 'pills' ? 'rounded-pill' : 'rounded-none';
63
63
  export {};
64
64
  </script>
65
65
 
@@ -73,7 +73,7 @@ export {};
73
73
  onclick={() => handleTabClick(tab.id, tab.disabled)}
74
74
  onkeydown={(e) => handleKeyDown(e, tab.id)}
75
75
  disabled={tab.disabled}
76
- class="flex items-center gap-2 px-6 py-3 {radiusClass} font-body text-sm transition-all duration-300 ease-luxe {activeTab === tab.id ? 'bg-accent text-text accent-glow' : 'text-text-soft hover:bg-base-3'} {tab.disabled ? 'opacity-50 pointer-events-none' : 'cursor-pointer'}"
76
+ class="flex items-center gap-2 px-6 py-3 {radiusClass} font-mono text-xs uppercase tracking-[0.08em] transition-all duration-150 ease-luxe {activeTab === tab.id ? 'bg-accent text-text' : 'text-text-soft hover:bg-base-3'} {tab.disabled ? 'opacity-50 pointer-events-none' : 'cursor-pointer'}"
77
77
  >
78
78
  {#if tab.iconBefore}
79
79
  <span class="inline-flex items-center justify-center">
@@ -40,7 +40,7 @@ export {};
40
40
 
41
41
  {#if visible}
42
42
  <div
43
- class="glass-panel rounded-[var(--radius-lg)] p-4 flex gap-3 {variantClasses[variant]} {className} animate-[fade-in_0.2s_var(--ease-sharp)]"
43
+ class="glass-panel rounded-[var(--radius-sm)] p-4 flex gap-3 {variantClasses[variant]} {className} animate-[fade-in_0.2s_var(--ease-sharp)]"
44
44
  role={roleMap[variant]}
45
45
  aria-live={ariaLiveMap[variant]}
46
46
  >
@@ -103,12 +103,12 @@ $effect(() => {
103
103
 
104
104
  {#if open}
105
105
  <div
106
- class="fixed inset-0 z-[var(--z-50)] flex items-start justify-center pt-[20vh] p-4 bg-overlay backdrop-blur-md animate-[fade-in_0.2s_var(--ease-luxe)]"
106
+ class="fixed inset-0 z-[var(--z-50)] flex items-start justify-center pt-[20vh] p-4 bg-overlay animate-[fade-in_0.2s_var(--ease-luxe)]"
107
107
  onclick={handleBackdropClick}
108
108
  role="presentation"
109
109
  >
110
110
  <div
111
- class="panel-floating rounded-xl shadow-deep w-full max-w-[36rem] animate-[fade-up_0.3s_var(--ease-luxe)]"
111
+ class="panel-floating rounded-[var(--radius-md)] shadow-deep w-full max-w-[36rem] animate-[scale-in_0.2s_var(--ease-sharp)]"
112
112
  role="combobox"
113
113
  aria-expanded="true"
114
114
  aria-haspopup="listbox"
@@ -34,10 +34,10 @@ const positionClasses = {
34
34
  bottom: 'bottom-0 left-0 w-full'
35
35
  };
36
36
  const animationClasses = {
37
- left: 'animate-[slide-in-left_0.3s_var(--ease-luxe)]',
38
- right: 'animate-[slide-in-right_0.3s_var(--ease-luxe)]',
39
- top: 'animate-[slide-in-top_0.3s_var(--ease-luxe)]',
40
- bottom: 'animate-[slide-in-bottom_0.3s_var(--ease-luxe)]'
37
+ left: 'animate-[slide-in-left_0.2s_var(--ease-sharp)]',
38
+ right: 'animate-[slide-in-right_0.2s_var(--ease-sharp)]',
39
+ top: 'animate-[slide-in-top_0.2s_var(--ease-sharp)]',
40
+ bottom: 'animate-[slide-in-bottom_0.2s_var(--ease-sharp)]'
41
41
  };
42
42
  function handleClose() {
43
43
  open = false;
@@ -106,7 +106,7 @@ $effect(() => {
106
106
 
107
107
  {#if open}
108
108
  <div
109
- class="fixed inset-0 z-[var(--z-50)] bg-overlay backdrop-blur-md animate-[fade-in_0.2s_var(--ease-luxe)]"
109
+ class="fixed inset-0 z-[var(--z-50)] bg-overlay animate-[fade-in_0.2s_var(--ease-luxe)]"
110
110
  onclick={handleBackdropClick}
111
111
  role="presentation"
112
112
  >
@@ -76,7 +76,7 @@ $effect(() => {
76
76
  <div
77
77
  bind:this={dropdownElement}
78
78
  id={dropdownId}
79
- class="absolute {placementClasses[placement]} z-[var(--z-50)] panel-floating rounded-[var(--radius-lg)] shadow-[var(--shadow-deep)] min-w-[12rem] animate-[fade-up_0.2s_var(--ease-luxe)]"
79
+ class="absolute {placementClasses[placement]} z-[var(--z-50)] panel-floating rounded-[var(--radius-sm)] shadow-[var(--shadow-deep)] min-w-[12rem] animate-[scale-in_0.15s_var(--ease-sharp)]"
80
80
  style={fullWidth && triggerWidth > 0 ? `width: ${triggerWidth}px` : ''}
81
81
  role="menu"
82
82
  >
@@ -77,13 +77,13 @@ $effect(() => {
77
77
 
78
78
  {#if open}
79
79
  <div
80
- class="fixed inset-0 z-[var(--z-50)] flex items-center justify-center p-4 bg-overlay backdrop-blur-md animate-[fade-in_0.2s_var(--ease-luxe)]"
80
+ class="fixed inset-0 z-[var(--z-50)] flex items-center justify-center p-4 bg-overlay animate-[fade-in_0.2s_var(--ease-luxe)]"
81
81
  onclick={handleBackdropClick}
82
82
  role="presentation"
83
83
  >
84
84
  <div
85
85
  bind:this={modalElement}
86
- class="panel-floating rounded-[var(--radius-lg)] shadow-deep w-full {sizeClasses[size]} {className} animate-[fade-up_0.3s_var(--ease-luxe)] flex flex-col max-h-[90vh]"
86
+ class="panel-floating rounded-[var(--radius-md)] shadow-deep w-full {sizeClasses[size]} {className} animate-[scale-in_0.2s_var(--ease-sharp)] flex flex-col max-h-[90vh]"
87
87
  role="dialog"
88
88
  aria-modal="true"
89
89
  aria-labelledby={title ? titleId : undefined}
@@ -97,7 +97,7 @@ $effect(() => {
97
97
  <div
98
98
  bind:this={popoverElement}
99
99
  id={popoverId}
100
- class="absolute {placementClasses[actualPlacement]} z-[var(--z-50)] panel-floating rounded-[var(--radius-lg)] shadow-deep p-4 animate-[fade-up_0.2s_var(--ease-luxe)]"
100
+ class="absolute {placementClasses[actualPlacement]} z-[var(--z-50)] panel-floating rounded-[var(--radius-sm)] shadow-deep p-4 animate-[scale-in_0.15s_var(--ease-sharp)]"
101
101
  role="dialog"
102
102
  >
103
103
  {@render children?.()}