@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
package/README.md CHANGED
@@ -3,16 +3,17 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@mrintel/villain-ui.svg)](https://www.npmjs.com/package/@mrintel/villain-ui)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- A luxury Svelte 5 component library featuring a **Modern Villain Luxury** aesthetic. Built on Onyx Black backgrounds with Royal Purple accents, glass morphism, and neon edges for modern web applications that demand commanding elegance and exceptional user experience.
6
+ An opinionated Svelte 5 design system built around **Modern Villain** design: square geometry, crisp 1px borders, heavy editorial typography, and disciplined use of accent color. Onyx Black surfaces with a Royal Purple accent. Engineered, not decorative.
7
7
 
8
8
  ## ✨ Features
9
9
 
10
10
  - **🚀 Svelte 5 with Runes** - Built on the latest Svelte 5 reactivity system with full TypeScript support
11
- - **🎨 Modern Villain Luxury** - Onyx Black base with Royal Purple accents, glass morphism, and commanding neon edges
11
+ - **⬛ Modern Villain** - Square geometry (0-8px radii), border-driven hierarchy, minimal shadow, glow reserved for primary CTAs and active states
12
+ - **🔤 Editorial Typography** - Archivo display (weight 900 headlines) with IBM Plex Mono for all system text: buttons, navigation, labels, metadata
12
13
  - **🎭 Tailwind CSS v4** - Powered by the latest Tailwind with CSS variable theming
13
14
  - **🌳 Tree-Shakeable** - Import only what you need for optimal bundle size
14
15
  - **🎯 Fully Typed** - Strict TypeScript mode with complete type definitions
15
- - **🎬 Premium Motion** - Smooth animations with custom luxury easing curves
16
+ - **🎬 Mechanical Motion** - Fade, precision slide, and 0.98→1 scale; no bounce, no float
16
17
  - **🔧 Highly Customizable** - Theme via CSS variables without touching component code
17
18
  - **♿ Accessible** - ARIA-compliant components following WAI-ARIA best practices
18
19
 
@@ -56,6 +57,54 @@ This explicit import strategy gives you full control over styling and allows you
56
57
  - Conditionally load themes
57
58
  - Override theme variables before or after the default theme loads
58
59
 
60
+ ### Load Fonts
61
+
62
+ The library references fonts by name but does not bundle them - your app loads them. Add to your `app.html` / `index.html`:
63
+
64
+ ```html
65
+ <link rel="preconnect" href="https://fonts.googleapis.com">
66
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
67
+ <link href="https://fonts.googleapis.com/css2?family=Archivo:wght@400..900&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
68
+ ```
69
+
70
+ Required fonts:
71
+
72
+ | Font | Role |
73
+ |------|------|
74
+ | **Archivo** (400-900) | Display headlines and body copy |
75
+ | **IBM Plex Mono** (400-600) | System text: buttons, navigation, labels, metadata, tables |
76
+
77
+ If the fonts are not loaded, the library falls back to the browser's default `sans-serif` / `monospace`.
78
+
79
+ ### Customizing the Accent Color
80
+
81
+ Royal Purple is the default accent. Every accent-derived color in the library (overlays, borders, focus rings, scrollbars, glows) is computed from three CSS variables via `color-mix`, so rebranding is a single override after the theme import:
82
+
83
+ ```css
84
+ /* your app css, after importing theme.css */
85
+ :root {
86
+ --color-accent: #c8102e; /* primary accent */
87
+ --color-accent-soft: #e4304c; /* hover / lighter variant */
88
+ --color-accent-dark: #8f0b21; /* pressed / darker variant */
89
+ }
90
+ ```
91
+
92
+ No component changes needed - the entire system re-tints.
93
+
94
+ ### Display Themes
95
+
96
+ Archivo is the default display font. Two alternate display modes are available by setting `data-display` on any ancestor element (typically `<html>` or `<body>`):
97
+
98
+ ```html
99
+ <!-- Luxury editorial (italic accent words welcome) -->
100
+ <html data-display="bodoni">
101
+
102
+ <!-- Condensed performance aesthetic -->
103
+ <html data-display="oswald">
104
+ ```
105
+
106
+ Load the matching font when using a display mode: `Bodoni Moda` (400-900, incl. italic) or `Oswald` (400-700).
107
+
59
108
  ## 🚀 Quick Start
60
109
 
61
110
  ### SvelteKit Setup
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">import { variantClasses, sizeClasses, disabledClasses } from './buttonClasses';
2
2
  let { variant = 'primary', size = 'md', disabled = false, type = 'button', onclick, iconBefore, iconAfter, class: className = '', children, ...restProps } = $props();
3
- const baseClasses = 'inline-flex items-center justify-center rounded-[var(--radius-lg)] font-[var(--font-body)] transition-all duration-300 ease-[var(--ease-luxe)] cursor-pointer gap-2';
3
+ const baseClasses = 'inline-flex items-center justify-center rounded-[var(--radius-sm)] font-[var(--font-body)] transition-all duration-300 ease-[var(--ease-luxe)] cursor-pointer gap-2';
4
4
  const classes = $derived(`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${disabled ? disabledClasses : ''} ${className}`);
5
5
  </script>
6
6
 
@@ -6,7 +6,7 @@ const positionClasses = {
6
6
  'top-right': 'top-6 right-6',
7
7
  'top-left': 'top-6 left-6'
8
8
  };
9
- const baseClasses = 'fixed z-[var(--z-50)] flex items-center justify-center rounded-[var(--radius-pill)] bg-[var(--color-accent)] text-[var(--color-text)] accent-glow hover-lift shadow-[var(--shadow-deep)] transition-all duration-300 ease-[var(--ease-luxe)] cursor-pointer animate-[glow-pulse_3s_ease-in-out_infinite] hover:scale-110';
9
+ const baseClasses = 'fixed z-[var(--z-50)] flex items-center justify-center rounded-[var(--radius-sm)] bg-[var(--color-accent)] text-[var(--color-text)] accent-glow hover-lift shadow-[var(--shadow-deep)] transition-all duration-300 ease-[var(--ease-luxe)] cursor-pointer';
10
10
  </script>
11
11
 
12
12
  <button
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">import { variantClasses, disabledClasses } from './buttonClasses';
2
- let { variant = 'ghost', size = 'md', shape = 'circle', disabled = false, type = 'button', ariaLabel, onclick, children, class: className = '' } = $props();
2
+ let { variant = 'ghost', size = 'md', shape = 'square', disabled = false, type = 'button', ariaLabel, onclick, children, class: className = '' } = $props();
3
3
  const iconSizeClasses = {
4
4
  sm: 'w-10 h-10 text-sm',
5
5
  md: 'w-12 h-12 text-base',
@@ -7,7 +7,7 @@ const iconSizeClasses = {
7
7
  };
8
8
  const shapeClasses = {
9
9
  circle: 'rounded-[var(--radius-pill)]',
10
- square: 'rounded-[var(--radius-lg)]'
10
+ square: 'rounded-[var(--radius-sm)]'
11
11
  };
12
12
  const baseClasses = 'inline-flex items-center justify-center font-[var(--font-body)] transition-all duration-300 ease-[var(--ease-luxe)] cursor-pointer hover-lift';
13
13
  </script>
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">import { variantClasses, sizeClasses, disabledClasses } from './buttonClasses';
2
2
  let { href, variant = 'primary', size = 'md', disabled = false, target, rel, children, iconBefore, iconAfter, class: className = '' } = $props();
3
- const baseClasses = 'inline-flex items-center justify-center rounded-[var(--radius-lg)] font-[var(--font-body)] transition-all duration-300 ease-[var(--ease-luxe)] no-underline gap-2';
3
+ const baseClasses = 'inline-flex items-center justify-center rounded-[var(--radius-sm)] font-[var(--font-body)] transition-all duration-300 ease-[var(--ease-luxe)] no-underline gap-2';
4
4
  const computedRel = $derived(target === '_blank' ? (rel || 'noopener noreferrer') : rel);
5
5
  </script>
6
6
 
@@ -1,13 +1,13 @@
1
1
  export const variantClasses = {
2
- primary: 'bg-[var(--color-accent)] text-[var(--color-text)] accent-glow hover-lift',
3
- secondary: 'bg-transparent text-[var(--color-text)] border border-[var(--color-border-strong)] hover:border-[var(--color-accent)] hover-lift',
4
- ghost: 'bg-transparent text-[var(--color-text)] hover:bg-[var(--color-base-2)] hover-lift',
5
- danger: 'bg-[var(--color-error)] text-[var(--color-text)] shadow-[0_0_20px_var(--color-error-overlay-30)] hover-lift'
2
+ primary: 'bg-[var(--color-accent)] text-[var(--color-text)] border border-transparent machined hover:bg-[var(--color-accent-soft)] hover-lift active:translate-y-px',
3
+ secondary: 'bg-transparent text-[var(--color-text)] border border-[var(--color-border-strong)] hover:border-[var(--color-accent)] hover-lift active:translate-y-px',
4
+ ghost: 'bg-transparent text-[var(--color-text)] border border-transparent hover:bg-[var(--color-base-2)] hover-lift active:translate-y-px',
5
+ danger: 'bg-[var(--color-error)] text-[var(--color-text)] border border-transparent machined hover-lift active:translate-y-px'
6
6
  };
7
7
  export const sizeClasses = {
8
- sm: 'px-3 py-1.5 text-sm font-medium',
9
- md: 'px-4 py-2 text-base font-medium',
10
- lg: 'px-5 py-2.5 text-lg font-semibold'
8
+ sm: 'px-3.5 py-2 text-xs font-mono font-medium uppercase tracking-[0.08em]',
9
+ md: 'px-5 py-2.5 text-sm font-mono font-medium uppercase tracking-[0.08em]',
10
+ lg: 'px-6 py-3 text-base font-mono font-semibold uppercase tracking-[0.08em]'
11
11
  };
12
12
  export const disabledClasses = 'opacity-50 cursor-not-allowed pointer-events-none';
13
13
  export const fabSizeClasses = {
@@ -1,29 +1,43 @@
1
- <script lang="ts">let { padding = 'md', href, target = '_self', rel, onclick, selected = false, disabled = false, class: className = '', children, header, footer, iconBefore, ...restProps } = $props();
1
+ <script lang="ts">let { padding = 'md', variant = 'default', eyebrow, actions, href, target = '_self', rel, onclick, selected = false, disabled = false, class: className = '', children, header, footer, iconBefore, ...restProps } = $props();
2
2
  // Compute rel attribute: use explicit rel if provided, otherwise default to 'noopener noreferrer' for target='_blank'
3
3
  const relValue = $derived(rel ?? (target === '_blank' ? 'noopener noreferrer' : undefined));
4
4
  const paddingClasses = {
5
5
  none: 'p-0',
6
- sm: 'p-4',
7
- md: 'p-6',
8
- lg: 'p-8'
6
+ sm: 'p-5',
7
+ md: 'p-8',
8
+ lg: 'p-10'
9
9
  };
10
10
  const isInteractive = $derived(!!href || !!onclick);
11
- const baseClasses = $derived(`panel-raised rounded-[var(--radius-lg)] transition-all duration-300 ease-[var(--ease-luxe)]` +
11
+ const hasCommandBar = $derived(variant === 'command' && (!!eyebrow || !!actions));
12
+ const surfaceClasses = $derived(variant === 'metric' ? 'panel-spectral' : 'panel-raised');
13
+ // Command bar is full-bleed: root loses padding, content area gets it back
14
+ const rootPadding = $derived(hasCommandBar ? 'p-0' : paddingClasses[padding]);
15
+ const baseClasses = $derived(`${surfaceClasses} rounded-[var(--radius-sm)] transition-all duration-300 ease-[var(--ease-luxe)]` +
16
+ `${hasCommandBar ? ' overflow-hidden' : ''}` +
12
17
  `${href ? ' no-underline' : ''}` +
13
18
  `${isInteractive ? ' cursor-pointer hover-lift' : ''}` +
14
19
  `${selected ? ' ring-2 ring-accent ring-offset-2 ring-offset-base-0' : ''}` +
15
20
  `${disabled ? ' opacity-50 pointer-events-none' : ''}`);
21
+ const eyebrowClasses = 'font-[var(--font-mono)] text-xs uppercase tracking-[0.08em] text-[var(--color-text-soft)]';
16
22
  export {};
17
23
  </script>
18
24
 
19
- {#snippet cardContent()}
25
+ {#snippet cardBody()}
20
26
  {#if iconBefore}
21
27
  <div class="card-icon">
22
28
  {@render iconBefore()}
23
29
  </div>
24
30
  {/if}
31
+ {#if !hasCommandBar && eyebrow}
32
+ <div class="{eyebrowClasses} mb-3 flex items-center justify-between gap-4">
33
+ {eyebrow}
34
+ {#if actions}
35
+ <span class="flex items-center gap-2 normal-case tracking-normal">{@render actions()}</span>
36
+ {/if}
37
+ </div>
38
+ {/if}
25
39
  {#if header}
26
- <div class="pb-3 mb-3 border-b border-border-strong">
40
+ <div class="mb-5">
27
41
  {@render header()}
28
42
  </div>
29
43
  {/if}
@@ -33,22 +47,40 @@ export {};
33
47
  </div>
34
48
 
35
49
  {#if footer}
36
- <div class="pt-3 mt-3 border-t border-border-strong">
50
+ <div class="mt-5">
37
51
  {@render footer()}
38
52
  </div>
39
53
  {/if}
40
54
  {/snippet}
41
55
 
56
+ {#snippet cardContent()}
57
+ {#if hasCommandBar}
58
+ <div class="flex items-center justify-between gap-4 px-5 py-3 border-b border-[var(--color-border)] bg-[var(--color-neutral-overlay-2)]">
59
+ <span class={eyebrowClasses}>{eyebrow}</span>
60
+ {#if actions}
61
+ <div class="flex items-center gap-2">
62
+ {@render actions()}
63
+ </div>
64
+ {/if}
65
+ </div>
66
+ <div class={paddingClasses[padding]}>
67
+ {@render cardBody()}
68
+ </div>
69
+ {:else}
70
+ {@render cardBody()}
71
+ {/if}
72
+ {/snippet}
73
+
42
74
  {#if href}
43
- <a {href} {target} rel={relValue} class="{baseClasses} {paddingClasses[padding]} {className}" aria-disabled={disabled || undefined} {...restProps}>
75
+ <a {href} {target} rel={relValue} class="{baseClasses} {rootPadding} {className}" aria-disabled={disabled || undefined} {...restProps}>
44
76
  {@render cardContent()}
45
77
  </a>
46
78
  {:else if onclick}
47
- <button type="button" {onclick} {disabled} aria-pressed={selected} class="{baseClasses} {paddingClasses[padding]} {className} text-left w-full" {...restProps}>
79
+ <button type="button" {onclick} {disabled} aria-pressed={selected} class="{baseClasses} {rootPadding} {className} text-left w-full" {...restProps}>
48
80
  {@render cardContent()}
49
81
  </button>
50
82
  {:else}
51
- <div class="{baseClasses} {paddingClasses[padding]} {className}" {...restProps}>
83
+ <div class="{baseClasses} {rootPadding} {className}" {...restProps}>
52
84
  {@render cardContent()}
53
85
  </div>
54
86
  {/if}
@@ -1,6 +1,17 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  export interface Props {
3
3
  padding?: 'none' | 'sm' | 'md' | 'lg';
4
+ /**
5
+ * Pattern variant:
6
+ * - default: raised content card
7
+ * - command: card with an integrated command bar (eyebrow + actions strip)
8
+ * - metric: recessed instrument surface for numbers/readouts
9
+ */
10
+ variant?: 'default' | 'command' | 'metric';
11
+ /** Mono uppercase system label. In command variant it lives in the command bar. */
12
+ eyebrow?: string;
13
+ /** Actions rendered in the command bar (command variant) or top-right of content. */
14
+ actions?: Snippet;
4
15
  href?: string;
5
16
  target?: '_blank' | '_self' | '_parent' | '_top';
6
17
  rel?: string;
@@ -1,18 +1,57 @@
1
- <script lang="ts">let { padding = 'md', rounded = true, variant = 'default', class: className = '', children, ...restProps } = $props();
1
+ <script lang="ts">let { padding = 'md', rounded = true, variant = 'default', title, status, statusVariant = 'neutral', actions, class: className = '', children, ...restProps } = $props();
2
2
  const paddingClasses = {
3
3
  none: 'p-0',
4
4
  sm: 'p-5',
5
5
  md: 'p-8',
6
6
  lg: 'p-10'
7
7
  };
8
+ const statusColors = {
9
+ success: 'var(--color-success)',
10
+ warning: 'var(--color-warning)',
11
+ error: 'var(--color-error)',
12
+ info: 'var(--color-info)',
13
+ neutral: 'var(--color-text-muted)'
14
+ };
8
15
  const glassClasses = $derived(variant === 'glass'
9
16
  ? 'glass-panel bg-[var(--color-accent-overlay-5)] border-[var(--color-border-glow)]'
10
17
  : 'panel-raised');
11
- const roundedClasses = $derived(rounded ? 'rounded-[var(--radius-lg)]' : '');
18
+ const hasCommandBar = $derived(!!title || !!status || !!actions);
19
+ const roundedClasses = $derived(rounded ? 'rounded-[var(--radius-sm)]' : '');
12
20
  const baseClasses = 'transition-all duration-300 ease-[var(--ease-luxe)]';
13
21
  export {};
14
22
  </script>
15
23
 
16
- <div class="{baseClasses} {glassClasses} {roundedClasses} {paddingClasses[padding]} {className}" {...restProps}>
17
- {@render children?.()}
18
- </div>
24
+ {#if hasCommandBar}
25
+ <div class="{baseClasses} {glassClasses} {roundedClasses} overflow-hidden {className}" {...restProps}>
26
+ <div class="flex items-center justify-between gap-4 px-5 py-3 border-b border-[var(--color-border)] bg-[var(--color-neutral-overlay-2)]">
27
+ {#if title}
28
+ <span class="font-[var(--font-mono)] text-xs uppercase tracking-[0.08em] text-[var(--color-text)]">{title}</span>
29
+ {:else}
30
+ <span></span>
31
+ {/if}
32
+ <div class="flex items-center gap-4">
33
+ {#if status}
34
+ <span
35
+ class="inline-flex items-center gap-2 font-[var(--font-mono)] text-xs uppercase tracking-[0.08em]"
36
+ style="color: {statusColors[statusVariant]};"
37
+ >
38
+ <span class="w-2 h-2 inline-block" style="background: {statusColors[statusVariant]};"></span>
39
+ {status}
40
+ </span>
41
+ {/if}
42
+ {#if actions}
43
+ <div class="flex items-center gap-2">
44
+ {@render actions()}
45
+ </div>
46
+ {/if}
47
+ </div>
48
+ </div>
49
+ <div class={paddingClasses[padding]}>
50
+ {@render children?.()}
51
+ </div>
52
+ </div>
53
+ {:else}
54
+ <div class="{baseClasses} {glassClasses} {roundedClasses} {paddingClasses[padding]} {className}" {...restProps}>
55
+ {@render children?.()}
56
+ </div>
57
+ {/if}
@@ -3,6 +3,13 @@ interface Props {
3
3
  padding?: 'none' | 'sm' | 'md' | 'lg';
4
4
  rounded?: boolean;
5
5
  variant?: 'default' | 'glass';
6
+ /** Mono uppercase title rendered in an integrated command bar. */
7
+ title?: string;
8
+ /** System status shown in the command bar (with a colored indicator). */
9
+ status?: string;
10
+ statusVariant?: 'success' | 'warning' | 'error' | 'info' | 'neutral';
11
+ /** Actions rendered on the right side of the command bar. */
12
+ actions?: Snippet;
6
13
  class?: string;
7
14
  children?: Snippet;
8
15
  }
@@ -1,4 +1,4 @@
1
- <script lang="ts">let { variant = 'default', title, level = 2, glow = true, subtitle, align = 'left', children } = $props();
1
+ <script lang="ts">let { variant = 'default', title, level = 2, glow = false, subtitle, align = 'left', children } = $props();
2
2
  const variantClasses = {
3
3
  default: '',
4
4
  accent: 'text-accent',
@@ -12,7 +12,7 @@ const subtitleClasses = $derived(`mt-2 font-[var(--font-body)] text-[length:var(
12
12
  export {};
13
13
  </script>
14
14
 
15
- <div class="mb-8">
15
+ <div class="mb-12">
16
16
  <svelte:element this={headingTag} class={headingClasses}>
17
17
  {#if title}
18
18
  {title}
@@ -14,7 +14,7 @@ const fontSizes = {
14
14
  lg: '1.25rem',
15
15
  xl: '1.5rem'
16
16
  };
17
- const classes = $derived(`${sizeClasses[size]} rounded-full overflow-hidden inline-flex items-center justify-center transition-all duration-300 hover:scale-105 ${glow ? 'border-2 border-[var(--color-accent)] accent-glow' : ''}`);
17
+ const classes = $derived(`${sizeClasses[size]} rounded-[var(--radius-sm)] overflow-hidden inline-flex items-center justify-center transition-all duration-300 ${glow ? 'border-2 border-[var(--color-accent)] accent-glow' : ''}`);
18
18
  const fallbackText = $derived(fallback || alt.charAt(0).toUpperCase());
19
19
  function handleImageError() {
20
20
  imageError = true;
@@ -17,10 +17,10 @@ const hoverClasses = {
17
17
  feature: 'hover:bg-[var(--color-accent-overlay-30)] hover:shadow-[var(--shadow-accent-glow)]'
18
18
  };
19
19
  const sizeClasses = {
20
- sm: 'px-2 py-0.5 text-[var(--text-xs)] gap-1',
21
- md: 'px-3 py-1 text-[0.75rem] gap-1.5'
20
+ sm: 'px-2.5 py-1 text-[var(--text-xs)] gap-1',
21
+ md: 'px-3.5 py-1.5 text-[0.75rem] gap-1.5'
22
22
  };
23
- const classes = $derived(`inline-flex items-center justify-center rounded-[var(--radius-pill)] border font-[var(--font-body)] font-semibold transition-all duration-[var(--duration-300)] ease-[var(--ease-sharp)] ${variantClasses[variant]} ${sizeClasses[size]} ${hover ? hoverClasses[variant] : ''} ${glow ? 'accent-glow' : ''} ${uppercase ? 'uppercase tracking-wider' : ''} ${className}`.trim());
23
+ const classes = $derived(`inline-flex items-center justify-center rounded-[var(--radius-sm)] border-[1.5px] font-[var(--font-mono)] font-medium transition-all duration-[var(--duration-300)] ease-[var(--ease-sharp)] ${variantClasses[variant]} ${sizeClasses[size]} ${hover ? hoverClasses[variant] : ''} ${glow ? 'accent-glow' : ''} ${uppercase ? 'uppercase tracking-wider' : ''} ${className}`.trim());
24
24
  </script>
25
25
 
26
26
  <span class={classes} {...restProps}>
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">import SelectMenu from '../forms/SelectMenu.svelte';
2
- let { month = $bindable(new Date()), events = [], selectedDate = $bindable(undefined), onDateSelect, onMonthChange, renderCell, weekStartsOn = 0, showWeekNumbers = false, highlightToday = true, class: className = '' } = $props();
2
+ let { month = $bindable(new Date()), events = [], selectedDate = $bindable(undefined), onDateSelect, onMonthChange, renderCell, weekStartsOn = 0, showWeekNumbers = false, highlightToday = true, minDate, maxDate, class: className = '' } = $props();
3
3
  const today = new Date();
4
4
  today.setHours(0, 0, 0, 0);
5
5
  // Get calendar days for the current month
@@ -156,11 +156,14 @@ const monthOptions = monthNames.map((name, index) => ({
156
156
  value: String(index),
157
157
  label: name
158
158
  }));
159
- // Generate year options (current year ± 50 years)
159
+ // Generate year options: 100 years back (birth dates), 50 forward,
160
+ // clamped to minDate/maxDate when provided.
160
161
  const yearOptions = $derived.by(() => {
161
162
  const current = new Date().getFullYear();
163
+ const start = minDate ? minDate.getFullYear() : current - 100;
164
+ const end = maxDate ? maxDate.getFullYear() : current + 50;
162
165
  const options = [];
163
- for (let y = current - 50; y <= current + 50; y++) {
166
+ for (let y = start; y <= end; y++) {
164
167
  options.push({ value: String(y), label: String(y) });
165
168
  }
166
169
  return options;
@@ -438,7 +441,6 @@ const variantColors = {
438
441
  background: var(--color-accent);
439
442
  border-color: var(--color-accent);
440
443
  color: var(--color-text);
441
- box-shadow: 0 0 20px var(--color-accent-glow);
442
444
  }
443
445
 
444
446
  .calendar-cell.is-selected .day-number {
@@ -18,6 +18,9 @@ interface Props {
18
18
  weekStartsOn?: 0 | 1;
19
19
  showWeekNumbers?: boolean;
20
20
  highlightToday?: boolean;
21
+ /** Clamp the year selector (e.g. from a DatePicker's min/max). */
22
+ minDate?: Date;
23
+ maxDate?: Date;
21
24
  class?: string;
22
25
  }
23
26
  declare const CalendarGrid: import("svelte").Component<Props, {}, "month" | "selectedDate">;
@@ -17,7 +17,7 @@ async function copyCode() {
17
17
  }
18
18
  </script>
19
19
 
20
- <div class="panel-raised rounded-[var(--radius-lg)] overflow-hidden relative">
20
+ <div class="panel-raised rounded-[var(--radius-sm)] overflow-hidden relative">
21
21
  {#if filename || showCopy}
22
22
  <div
23
23
  class="px-4 py-2 border-b border-border flex items-center justify-between"
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">let { variant = 'plain', hoverable = false, items, children } = $props();
2
2
  const containerClasses = $derived(variant === 'bordered'
3
- ? 'panel-raised rounded-[var(--radius-lg)] border border-[var(--color-border)]'
3
+ ? 'panel-raised rounded-[var(--radius-sm)] border border-[var(--color-border)]'
4
4
  : '');
5
5
  const listClasses = $derived(`list-none ${variant} ${hoverable ? 'hoverable' : ''}`);
6
6
  export {};
@@ -52,7 +52,7 @@ function goToPage(page) {
52
52
  <nav role="navigation" aria-label="Pagination" class="flex items-center gap-2">
53
53
  <!-- Previous Button -->
54
54
  <button
55
- class="flex items-center gap-2 px-3 py-2 rounded-[var(--radius-md)] border border-[var(--color-border)] bg-transparent transition-all duration-300 font-[var(--font-body)] hover:bg-[var(--color-secondary-overlay-10)] hover:border-[var(--color-accent)] disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent disabled:hover:border-[var(--color-border)]"
55
+ class="flex items-center gap-2 px-3 py-2 rounded-none border border-[var(--color-border)] bg-transparent transition-all duration-300 font-[var(--font-mono)] hover:bg-[var(--color-secondary-overlay-10)] hover:border-[var(--color-accent)] disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent disabled:hover:border-[var(--color-border)]"
56
56
  onclick={() => goToPage(currentPage - 1)}
57
57
  disabled={currentPage === 1}
58
58
  aria-disabled={currentPage === 1}
@@ -71,7 +71,7 @@ function goToPage(page) {
71
71
  <span class="px-3 py-2 text-[var(--color-text-muted)]">...</span>
72
72
  {:else}
73
73
  <button
74
- class="px-3 py-2 rounded-[var(--radius-md)] transition-all duration-300 font-[var(--font-body)]"
74
+ class="px-3 py-2 rounded-none transition-all duration-300 font-[var(--font-mono)]"
75
75
  class:active={page === currentPage}
76
76
  class:inactive={page !== currentPage}
77
77
  onclick={() => goToPage(page)}
@@ -84,7 +84,7 @@ function goToPage(page) {
84
84
 
85
85
  <!-- Next Button -->
86
86
  <button
87
- class="flex items-center gap-2 px-3 py-2 rounded-[var(--radius-md)] border border-[var(--color-border)] bg-transparent transition-all duration-300 font-[var(--font-body)] hover:bg-[var(--color-secondary-overlay-10)] hover:border-[var(--color-accent)] disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent disabled:hover:border-[var(--color-border)]"
87
+ class="flex items-center gap-2 px-3 py-2 rounded-none border border-[var(--color-border)] bg-transparent transition-all duration-300 font-[var(--font-mono)] hover:bg-[var(--color-secondary-overlay-10)] hover:border-[var(--color-accent)] disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent disabled:hover:border-[var(--color-border)]"
88
88
  onclick={() => goToPage(currentPage + 1)}
89
89
  disabled={currentPage === totalPages}
90
90
  aria-disabled={currentPage === totalPages}
@@ -6,7 +6,6 @@ const trendColors = {
6
6
  neutral: 'var(--color-text-muted)'
7
7
  };
8
8
  const trendColor = $derived(trend ? trendColors[trend] : 'var(--color-text-muted)');
9
- const showGlow = $derived(trend === 'up');
10
9
  // Count-up: animate toward the current numeric value; re-runs when value changes.
11
10
  let animatedValue = $state(0);
12
11
  $effect(() => {
@@ -37,13 +36,13 @@ const displayValue = $derived(countUp && typeof value === 'number'
37
36
  : value);
38
37
  </script>
39
38
 
40
- <div class="panel-spectral rounded-[var(--radius-xl)] p-6">
39
+ <div class="panel-spectral rounded-[var(--radius-sm)] p-6">
41
40
  <!-- Top Row: Icon & Label -->
42
41
  <div class="flex items-start justify-between mb-4">
43
42
  <div class="flex-1">
44
43
  <div
45
44
  class="uppercase tracking-wider"
46
- style="color: var(--color-text-soft); font-family: var(--font-body); font-size: 0.875rem; font-weight: 500;"
45
+ style="color: var(--color-text-soft); font-family: var(--font-mono); font-size: 0.75rem; font-weight: 500;"
47
46
  >
48
47
  {label}
49
48
  </div>
@@ -59,8 +58,7 @@ const displayValue = $derived(countUp && typeof value === 'number'
59
58
  <!-- Value Display -->
60
59
  <div
61
60
  class="mb-2"
62
- class:text-glow={showGlow}
63
- style="color: var(--color-text); font-family: var(--font-heading); font-size: 2.5rem; font-weight: 700; line-height: 1.2;"
61
+ style="color: var(--color-text); font-family: var(--font-heading); font-size: 2.5rem; font-weight: 800; line-height: 1.2;"
64
62
  >
65
63
  {displayValue}
66
64
  </div>
@@ -87,7 +87,7 @@ function notifySelectionChange() {
87
87
  }
88
88
  </script>
89
89
 
90
- <div class="panel-raised rounded-[var(--radius-xl)] overflow-hidden {className}" {...restProps}>
90
+ <div class="panel-raised rounded-none overflow-hidden {className}" {...restProps}>
91
91
  <table class="mrdv-table w-full" class:striped class:hoverable class:compact class:sticky-header={stickyHeader}>
92
92
  {#if isDynamicMode}
93
93
  <!-- Dynamic mode: render from columns and data -->
@@ -265,15 +265,21 @@ function notifySelectionChange() {
265
265
 
266
266
  .mrdv-table :global(thead th) {
267
267
  color: var(--color-text);
268
- font-weight: 600;
268
+ font-family: var(--font-mono);
269
+ font-weight: 500;
269
270
  text-transform: uppercase;
270
271
  letter-spacing: 0.05em;
271
272
  text-align: left;
272
273
  }
273
274
 
275
+ /* Dense mode: information density over spaciousness */
274
276
  .mrdv-table.compact :global(th),
275
277
  .mrdv-table.compact :global(td) {
276
- padding: 0.5rem 1rem;
278
+ padding: 0.375rem 0.75rem;
279
+ }
280
+
281
+ .mrdv-table.compact :global(td) {
282
+ font-size: var(--text-sm);
277
283
  }
278
284
 
279
285
  .mrdv-table:not(.compact) :global(th),
@@ -292,11 +298,7 @@ function notifySelectionChange() {
292
298
  }
293
299
 
294
300
  .mrdv-table.hoverable :global(tbody tr:hover) {
295
- background: var(--color-accent-overlay-5);
296
- transform: translateY(-1px);
297
- box-shadow:
298
- 0 2px 8px var(--color-shadow-overlay-20),
299
- var(--shadow-accent-glow);
301
+ background: var(--color-accent-overlay-10);
300
302
  }
301
303
 
302
304
  .mrdv-table.striped :global(tbody tr:nth-child(even)) {
@@ -304,11 +306,7 @@ function notifySelectionChange() {
304
306
  }
305
307
 
306
308
  .mrdv-table.striped.hoverable :global(tbody tr:nth-child(even):hover) {
307
- background: var(--color-accent-overlay-5);
308
- transform: translateY(-1px);
309
- box-shadow:
310
- 0 2px 8px var(--color-shadow-overlay-20),
311
- var(--shadow-accent-glow);
309
+ background: var(--color-accent-overlay-10);
312
310
  }
313
311
 
314
312
  .mrdv-table :global(th),
@@ -324,7 +322,7 @@ function notifySelectionChange() {
324
322
  }
325
323
 
326
324
  .mrdv-table th.sortable:hover {
327
- background: var(--color-accent-overlay-5);
325
+ background: var(--color-accent-overlay-10);
328
326
  color: var(--color-accent);
329
327
  }
330
328
 
@@ -11,7 +11,7 @@ const sizeClasses = {
11
11
  md: 'px-3 py-1.5 text-sm gap-2',
12
12
  lg: 'px-4 py-2 text-base gap-2'
13
13
  };
14
- const classes = $derived(`inline-flex items-center rounded-[var(--radius-pill)] border font-[var(--font-body)] font-medium transition-all duration-300 hover:scale-[1.02] ${sizeClasses[size]} ${variantClasses[variant]}`);
14
+ const classes = $derived(`inline-flex items-center rounded-[var(--radius-sm)] border font-[var(--font-mono)] font-medium transition-all duration-150 ${sizeClasses[size]} ${variantClasses[variant]}`);
15
15
  function handleDismiss() {
16
16
  onDismissCallback?.();
17
17
  }