@vanduo-oss/framework 1.5.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +34 -2
  2. package/README.md +25 -154
  3. package/SKILL.md +58 -0
  4. package/css/components/accordion.css +80 -0
  5. package/css/components/alerts.css +65 -0
  6. package/css/components/avatar.css +2 -1
  7. package/css/components/badges.css +2 -1
  8. package/css/components/buttons.css +44 -0
  9. package/css/components/cards.css +26 -0
  10. package/css/components/code-snippet.css +23 -0
  11. package/css/components/collections.css +21 -1
  12. package/css/components/flow.css +20 -0
  13. package/css/components/forms.css +12 -1
  14. package/css/components/modals.css +69 -0
  15. package/css/components/navbar.css +11 -0
  16. package/css/components/popover.css +97 -0
  17. package/css/components/preloader.css +31 -0
  18. package/css/components/progress.css +46 -0
  19. package/css/components/separator.css +50 -0
  20. package/css/components/skeleton.css +42 -0
  21. package/css/components/slider.css +92 -0
  22. package/css/components/tabs.css +46 -0
  23. package/css/components/theme-customizer.css +53 -0
  24. package/css/core/colors-fib-base.css +524 -0
  25. package/css/core/colors-palette.css +786 -0
  26. package/css/core/colors.css +54 -46
  27. package/css/core/helpers.css +17 -0
  28. package/css/core/tokens.css +19 -19
  29. package/css/utilities/table.css +9 -0
  30. package/css/vanduo.css +11 -1
  31. package/dist/build-info.json +3 -3
  32. package/dist/vanduo-core.css +2147 -93
  33. package/dist/vanduo-core.css.map +1 -1
  34. package/dist/vanduo-core.min.css +2 -2
  35. package/dist/vanduo-core.min.css.map +1 -1
  36. package/dist/vanduo.cjs.js +387 -6
  37. package/dist/vanduo.cjs.js.map +3 -3
  38. package/dist/vanduo.cjs.min.js +16 -10
  39. package/dist/vanduo.cjs.min.js.map +4 -4
  40. package/dist/vanduo.css +2147 -93
  41. package/dist/vanduo.css.map +1 -1
  42. package/dist/vanduo.esm.js +387 -6
  43. package/dist/vanduo.esm.js.map +3 -3
  44. package/dist/vanduo.esm.min.js +16 -10
  45. package/dist/vanduo.esm.min.js.map +4 -4
  46. package/dist/vanduo.js +387 -6
  47. package/dist/vanduo.js.map +3 -3
  48. package/dist/vanduo.min.css +2 -2
  49. package/dist/vanduo.min.css.map +1 -1
  50. package/dist/vanduo.min.js +16 -10
  51. package/dist/vanduo.min.js.map +4 -4
  52. package/js/components/doc-search.js +9 -4
  53. package/js/components/popover.js +295 -0
  54. package/js/components/search.js +108 -0
  55. package/js/components/theme-customizer.js +54 -1
  56. package/js/index.js +2 -0
  57. package/js/utils/helpers.js +12 -4
  58. package/package.json +8 -4
@@ -197,9 +197,11 @@ textarea.textarea-lg {
197
197
  border-radius: var(--vd-input-border-radius-lg);
198
198
  }
199
199
 
200
- /* Input States - Error */
200
+ /* Input States - Error (`.vd-input-danger` is the canonical alias, added 1.7.0) */
201
201
  .vd-input-error,
202
202
  input.vd-input-error,
203
+ .vd-input-danger,
204
+ input.vd-input-danger,
203
205
  .vd-input.is-error,
204
206
  input.is-error {
205
207
  border-color: var(--vd-color-error);
@@ -212,6 +214,8 @@ input.vd-input-invalid {
212
214
 
213
215
  .vd-input-error:focus,
214
216
  input.vd-input-error:focus,
217
+ .vd-input-danger:focus,
218
+ input.vd-input-danger:focus,
215
219
  .vd-input.is-error:focus,
216
220
  input.is-error:focus {
217
221
  border-color: var(--vd-color-error);
@@ -283,6 +287,7 @@ textarea.is-success:focus {
283
287
 
284
288
  /* Labels */
285
289
  .label,
290
+ .vd-form-label,
286
291
  label {
287
292
  display: inline-block;
288
293
  margin-bottom: 0.5rem;
@@ -298,6 +303,12 @@ label.required::after {
298
303
  color: var(--vd-color-error);
299
304
  }
300
305
 
306
+ /* Icon inside a radio/check label (added 1.7.0; used by @vanduo-oss/vue) */
307
+ .vd-form-radio-icon {
308
+ margin-right: 0.375rem;
309
+ vertical-align: -0.125em;
310
+ }
311
+
301
312
  .label-sm,
302
313
  label.label-sm {
303
314
  font-size: var(--vd-font-size-xs);
@@ -300,3 +300,72 @@
300
300
  overflow: hidden;
301
301
  padding-right: 0;
302
302
  }
303
+
304
+ /* ==========================================================================
305
+ @vanduo-oss/vue panel-based modal markup (additive; added in 1.6.1)
306
+ VdModal renders `.vd-modal.vd-modal-open > .vd-modal-backdrop +
307
+ .vd-modal-panel.vd-modal-panel-{sm|md|lg}` with header/body/footer directly
308
+ inside the panel (no .vd-modal-dialog / .vd-modal-content split, and the
309
+ backdrop carries no .is-visible state). These aliases style that DOM without
310
+ touching the legacy .is-open / .vd-modal-dialog / .vd-modal-content rules.
311
+ ========================================================================== */
312
+
313
+ /* Container open state (mirror of .vd-modal.is-open) */
314
+ .vd-modal.vd-modal-open {
315
+ display: flex;
316
+ align-items: center;
317
+ justify-content: center;
318
+ opacity: 1;
319
+ }
320
+
321
+ /* Backdrop becomes visible when the container is open (mirror of .is-visible) */
322
+ .vd-modal-open .vd-modal-backdrop {
323
+ opacity: 1;
324
+ pointer-events: auto;
325
+ }
326
+
327
+ /* Panel merges .vd-modal-dialog (sizing/positioning) + .vd-modal-content (box) */
328
+ .vd-modal-panel {
329
+ position: relative;
330
+ display: flex;
331
+ flex-direction: column;
332
+ width: min(calc(100% - 3.5rem), var(--vd-modal-dialog-max-width));
333
+ max-width: var(--vd-modal-dialog-max-width);
334
+ margin: 1.75rem auto;
335
+ pointer-events: auto;
336
+ background-color: var(--vd-modal-bg);
337
+ background-clip: padding-box;
338
+ border: 1px solid var(--vd-modal-border-color);
339
+ border-radius: var(--vd-btn-border-radius-lg);
340
+ box-shadow: var(--vd-shadow-2xl);
341
+ outline: 0;
342
+ transform: scale(0.95);
343
+ transition: var(--vd-modal-dialog-transition);
344
+ z-index: 1060;
345
+ }
346
+
347
+ .vd-modal.vd-modal-open .vd-modal-panel {
348
+ transform: scale(1);
349
+ }
350
+
351
+ /* Panel sizes (md = default width) */
352
+ .vd-modal-panel-sm { --vd-modal-dialog-max-width: var(--vd-modal-width-sm); }
353
+ .vd-modal-panel-md { --vd-modal-dialog-max-width: var(--vd-modal-width); }
354
+ .vd-modal-panel-lg { --vd-modal-dialog-max-width: var(--vd-modal-width-lg); }
355
+
356
+ /* Glass variant parity with .vd-modal-glass .vd-modal-content */
357
+ .vd-modal-glass .vd-modal-panel {
358
+ background: var(--vd-glass-bg-light);
359
+ border-color: var(--vd-glass-border-light);
360
+ box-shadow: var(--vd-glass-shadow);
361
+ backdrop-filter: blur(var(--vd-glass-blur)) saturate(var(--vd-glass-saturate));
362
+ -webkit-backdrop-filter: blur(var(--vd-glass-blur)) saturate(var(--vd-glass-saturate));
363
+ }
364
+
365
+ @media (max-width: 575.98px) {
366
+ .vd-modal-panel {
367
+ margin: 0.5rem;
368
+ width: calc(100% - 1rem);
369
+ max-width: calc(100% - 1rem);
370
+ }
371
+ }
@@ -82,6 +82,17 @@
82
82
  -webkit-backdrop-filter: none;
83
83
  }
84
84
 
85
+ /* Strong frosted surface once scroll activates glass (beyond vd-glass-xl) */
86
+ .vd-navbar-glass.vd-navbar-scrolled {
87
+ --vd-glass-blur: 80px;
88
+ --vd-glass-saturate: 2.6;
89
+ }
90
+
91
+ .vd-navbar-fixed.vd-navbar-glass.vd-navbar-scrolled,
92
+ .vd-navbar-fixed-top.vd-navbar-glass.vd-navbar-scrolled {
93
+ box-shadow: var(--vd-glass-shadow);
94
+ }
95
+
85
96
  /* Navbar Container - matches .vd-container-responsive breakpoints for alignment with page content */
86
97
  .vd-navbar-container {
87
98
  display: flex;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Vanduo Framework - Popover Component
3
+ * Multi-trigger (click | hover | focus) popover with auto-placement flip.
4
+ * Sibling to .vd-bubble (which is click-only rich HTML); this is the general
5
+ * primitive documented in docs/sections/components/popover.html.
6
+ */
7
+
8
+ :root {
9
+ --vd-popover-bg: var(--vd-card-bg, #fff);
10
+ --vd-popover-text: var(--vd-text-primary, #212529);
11
+ --vd-popover-border-color: var(--vd-border-color, #dee2e6);
12
+ --vd-popover-shadow: 0 0.5rem 1.3125rem rgba(0, 0, 0, 0.12);
13
+ --vd-popover-padding: 0.8125rem;
14
+ --vd-popover-radius: var(--vd-border-radius, 0.5rem);
15
+ --vd-popover-max-width: 21rem;
16
+ --vd-popover-min-width: 8rem;
17
+ --vd-popover-z-index: 1060;
18
+ --vd-popover-arrow-size: 8px;
19
+ --vd-popover-font-size: var(--vd-font-size-sm, 0.875rem);
20
+ }
21
+
22
+ [data-theme="dark"] {
23
+ --vd-popover-bg: var(--vd-card-bg, #2d2d44);
24
+ --vd-popover-border-color: var(--vd-border-color, #3d3d5c);
25
+ --vd-popover-shadow: 0 0.5rem 1.3125rem rgba(0, 0, 0, 0.3);
26
+ }
27
+
28
+ @media (prefers-color-scheme: dark) {
29
+ :root:not([data-theme]) {
30
+ --vd-popover-bg: var(--vd-card-bg, #2d2d44);
31
+ --vd-popover-border-color: var(--vd-border-color, #3d3d5c);
32
+ --vd-popover-shadow: 0 0.5rem 1.3125rem rgba(0, 0, 0, 0.3);
33
+ }
34
+ }
35
+
36
+ .vd-popover-panel {
37
+ position: absolute;
38
+ z-index: var(--vd-popover-z-index);
39
+ min-width: var(--vd-popover-min-width);
40
+ max-width: var(--vd-popover-max-width);
41
+ padding: var(--vd-popover-padding);
42
+ background: var(--vd-popover-bg);
43
+ color: var(--vd-popover-text);
44
+ border: 1px solid var(--vd-popover-border-color);
45
+ border-radius: var(--vd-popover-radius);
46
+ box-shadow: var(--vd-popover-shadow);
47
+ font-size: var(--vd-popover-font-size);
48
+ animation: vd-popover-enter 0.18s ease-out;
49
+ }
50
+
51
+ .vd-popover-panel[hidden] {
52
+ display: none !important;
53
+ }
54
+
55
+ .vd-popover-panel::before {
56
+ content: '';
57
+ position: absolute;
58
+ width: 0;
59
+ height: 0;
60
+ border: var(--vd-popover-arrow-size) solid transparent;
61
+ }
62
+
63
+ .vd-popover-panel[data-placement="bottom"]::before {
64
+ bottom: 100%;
65
+ left: 50%;
66
+ transform: translateX(-50%);
67
+ border-bottom-color: var(--vd-popover-border-color);
68
+ }
69
+
70
+ .vd-popover-panel[data-placement="top"]::before {
71
+ top: 100%;
72
+ left: 50%;
73
+ transform: translateX(-50%);
74
+ border-top-color: var(--vd-popover-border-color);
75
+ }
76
+
77
+ .vd-popover-panel[data-placement="left"]::before {
78
+ left: 100%;
79
+ top: 50%;
80
+ transform: translateY(-50%);
81
+ border-left-color: var(--vd-popover-border-color);
82
+ }
83
+
84
+ .vd-popover-panel[data-placement="right"]::before {
85
+ right: 100%;
86
+ top: 50%;
87
+ transform: translateY(-50%);
88
+ border-right-color: var(--vd-popover-border-color);
89
+ }
90
+
91
+ .vd-popover-sm { --vd-popover-padding: 0.5rem; --vd-popover-font-size: var(--vd-font-size-xs, 0.75rem); }
92
+ .vd-popover-lg { --vd-popover-padding: 1.3125rem; --vd-popover-max-width: 34rem; }
93
+
94
+ @keyframes vd-popover-enter {
95
+ from { opacity: 0; transform: scale(0.96); }
96
+ to { opacity: 1; transform: scale(1); }
97
+ }
@@ -54,3 +54,34 @@
54
54
  }
55
55
  }
56
56
 
57
+ /* ==========================================================================
58
+ @vanduo-oss/vue VdPreloader nested spinner (additive; added in 1.6.1)
59
+ VdPreloader nests `.vd-preloader > .vd-preloader-spinner`; when the inner
60
+ element is present the spinner visual moves to it (legacy bare .vd-preloader
61
+ keeps spinning on its own). Size/color vars set on .vd-preloader inherit down.
62
+ ========================================================================== */
63
+ .vd-preloader:has(.vd-preloader-spinner) {
64
+ width: auto;
65
+ height: auto;
66
+ border: none;
67
+ border-radius: 0;
68
+ animation: none;
69
+ display: inline-flex;
70
+ }
71
+
72
+ .vd-preloader-spinner {
73
+ display: inline-block;
74
+ width: var(--vd-spinner-size, 2rem);
75
+ height: var(--vd-spinner-size, 2rem);
76
+ border: var(--vd-spinner-border-width, 3px) solid var(--vd-border-color, #e9ecef);
77
+ border-top-color: var(--vd-spinner-color, var(--vd-color-primary));
78
+ border-radius: 50%;
79
+ animation: spinner-rotate var(--vd-spinner-speed, 0.65s) linear infinite;
80
+ }
81
+
82
+ @media (prefers-reduced-motion: reduce) {
83
+ .vd-preloader-spinner {
84
+ animation-duration: 1.5s;
85
+ }
86
+ }
87
+
@@ -105,3 +105,49 @@
105
105
  --vd-progress-bg: var(--vd-bg-tertiary, #2d3748);
106
106
  }
107
107
  }
108
+
109
+ /* ==========================================================================
110
+ @vanduo-oss/vue VdProgress markup (additive; added in 1.6.1)
111
+ VdProgress renders `.vd-progress > .vd-progress-track > .vd-progress-fill`
112
+ plus an optional `.vd-progress-label`, and uses `.is-indeterminate` (vs the
113
+ legacy `.vd-progress-bar` / `.vd-progress-indeterminate`). Scoped with :has()
114
+ so the legacy single-bar progress is untouched.
115
+ ========================================================================== */
116
+ .vd-progress:has(.vd-progress-track) {
117
+ display: block;
118
+ height: auto;
119
+ overflow: visible;
120
+ background-color: transparent;
121
+ }
122
+
123
+ .vd-progress-track {
124
+ display: flex;
125
+ height: var(--vd-progress-height);
126
+ overflow: hidden;
127
+ background-color: var(--vd-progress-bg);
128
+ border-radius: var(--vd-progress-border-radius);
129
+ }
130
+
131
+ .vd-progress-fill {
132
+ display: block;
133
+ height: 100%;
134
+ background-color: var(--vd-progress-bar-bg);
135
+ border-radius: var(--vd-progress-border-radius);
136
+ transition: width 0.4s ease;
137
+ }
138
+
139
+ .vd-progress.is-indeterminate .vd-progress-fill {
140
+ width: 30%;
141
+ animation: progress-indeterminate 1.5s ease-in-out infinite;
142
+ }
143
+
144
+ .vd-progress-label {
145
+ display: block;
146
+ margin-top: 0.375rem;
147
+ }
148
+
149
+ @media (prefers-reduced-motion: reduce) {
150
+ .vd-progress.is-indeterminate .vd-progress-fill {
151
+ animation: none;
152
+ }
153
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Vanduo Framework - Separator / Divider
3
+ * Added in 1.7.0 to back @vanduo-oss/vue's VdSeparator.
4
+ */
5
+
6
+ :root {
7
+ --vd-separator-color: var(--vd-border-color);
8
+ --vd-separator-spacing: 1.3125rem; /* 21px - fib */
9
+ }
10
+
11
+ .vd-separator {
12
+ width: 100%;
13
+ height: 0;
14
+ margin: var(--vd-separator-spacing) 0;
15
+ border: none;
16
+ border-top: 1px solid var(--vd-separator-color);
17
+ }
18
+
19
+ .vd-separator-vertical {
20
+ width: 0;
21
+ height: auto;
22
+ min-height: 1em;
23
+ margin: 0 var(--vd-separator-spacing);
24
+ border-top: none;
25
+ border-left: 1px solid var(--vd-separator-color);
26
+ align-self: stretch;
27
+ }
28
+
29
+ /* Labeled separator: a horizontal rule with centered text. */
30
+ .vd-separator-labeled {
31
+ display: flex;
32
+ align-items: center;
33
+ gap: 0.75rem;
34
+ width: 100%;
35
+ margin: var(--vd-separator-spacing) 0;
36
+ border: none;
37
+ color: var(--vd-text-muted);
38
+ font-size: var(--vd-font-size-sm);
39
+ }
40
+
41
+ .vd-separator-labeled::before,
42
+ .vd-separator-labeled::after {
43
+ content: '';
44
+ flex: 1 1 auto;
45
+ border-top: 1px solid var(--vd-separator-color);
46
+ }
47
+
48
+ .vd-separator-label {
49
+ flex-shrink: 0;
50
+ }
@@ -240,3 +240,45 @@
240
240
  animation: none;
241
241
  }
242
242
  }
243
+
244
+ /* ==========================================================================
245
+ @vanduo-oss/vue VdSkeleton additions (additive; added in 1.6.1)
246
+ VdSkeleton renders multi-line (`.vd-skeleton-lines`) and card shapes
247
+ (`.vd-skeleton-card.vd-skeleton-card-{size} > .vd-skeleton-card-header +
248
+ .vd-skeleton-card-body`), plus a `.vd-skeleton-text-short` last line.
249
+ ========================================================================== */
250
+ .vd-skeleton-lines {
251
+ display: flex;
252
+ flex-direction: column;
253
+ gap: 0.5rem;
254
+ }
255
+
256
+ .vd-skeleton-lines .vd-skeleton-text {
257
+ margin-bottom: 0;
258
+ }
259
+
260
+ .vd-skeleton-text-short {
261
+ width: 60%;
262
+ }
263
+
264
+ .vd-skeleton-card-header {
265
+ height: 8rem;
266
+ width: 100%;
267
+ margin-bottom: 0.8125rem;
268
+ }
269
+
270
+ .vd-skeleton-card-body {
271
+ display: flex;
272
+ flex-direction: column;
273
+ gap: 0.5rem;
274
+ }
275
+
276
+ .vd-skeleton-card-body .vd-skeleton-text {
277
+ margin-bottom: 0;
278
+ }
279
+
280
+ /* Card size variants scale the header placeholder height */
281
+ .vd-skeleton-card-sm .vd-skeleton-card-header { height: 5rem; }
282
+ .vd-skeleton-card-md .vd-skeleton-card-header { height: 8rem; }
283
+ .vd-skeleton-card-lg .vd-skeleton-card-header { height: 12rem; }
284
+ .vd-skeleton-card-xl .vd-skeleton-card-header { height: 16rem; }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Vanduo Framework - Slider (range input)
3
+ * Added in 1.7.0 to back @vanduo-oss/vue's VdSlider.
4
+ */
5
+
6
+ :root {
7
+ --vd-slider-track-height: 0.375rem; /* 6px */
8
+ --vd-slider-track-bg: var(--vd-bg-tertiary, #e9ecef);
9
+ --vd-slider-thumb-size: 1.125rem; /* 18px */
10
+ --vd-slider-thumb-bg: var(--vd-color-primary);
11
+ }
12
+
13
+ .vd-slider-field {
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: 0.375rem;
17
+ width: 100%;
18
+ }
19
+
20
+ .vd-slider-row {
21
+ display: flex;
22
+ align-items: center;
23
+ gap: 0.75rem;
24
+ }
25
+
26
+ .vd-slider-value {
27
+ flex-shrink: 0;
28
+ min-width: 3ch;
29
+ text-align: right;
30
+ font-size: var(--vd-font-size-sm);
31
+ font-variant-numeric: tabular-nums;
32
+ color: var(--vd-text-secondary);
33
+ }
34
+
35
+ .vd-slider {
36
+ -webkit-appearance: none;
37
+ appearance: none;
38
+ width: 100%;
39
+ height: var(--vd-slider-track-height);
40
+ border-radius: calc(var(--vd-slider-track-height) / 2);
41
+ background-color: var(--vd-slider-track-bg);
42
+ cursor: pointer;
43
+ outline-offset: 4px;
44
+ }
45
+
46
+ .vd-slider:disabled {
47
+ opacity: 0.6;
48
+ cursor: not-allowed;
49
+ }
50
+
51
+ .vd-slider:focus-visible {
52
+ outline: 2px solid var(--vd-color-primary);
53
+ }
54
+
55
+ /* WebKit / Blink thumb */
56
+ .vd-slider::-webkit-slider-thumb {
57
+ -webkit-appearance: none;
58
+ appearance: none;
59
+ width: var(--vd-slider-thumb-size);
60
+ height: var(--vd-slider-thumb-size);
61
+ border: 2px solid var(--vd-color-white);
62
+ border-radius: 50%;
63
+ background-color: var(--vd-slider-thumb-bg);
64
+ box-shadow: var(--vd-shadow-sm);
65
+ transition: transform 0.1s ease;
66
+ }
67
+
68
+ .vd-slider::-webkit-slider-thumb:active {
69
+ transform: scale(1.15);
70
+ }
71
+
72
+ /* Firefox thumb + track */
73
+ .vd-slider::-moz-range-thumb {
74
+ width: var(--vd-slider-thumb-size);
75
+ height: var(--vd-slider-thumb-size);
76
+ border: 2px solid var(--vd-color-white);
77
+ border-radius: 50%;
78
+ background-color: var(--vd-slider-thumb-bg);
79
+ box-shadow: var(--vd-shadow-sm);
80
+ }
81
+
82
+ .vd-slider::-moz-range-track {
83
+ height: var(--vd-slider-track-height);
84
+ border-radius: calc(var(--vd-slider-track-height) / 2);
85
+ background-color: var(--vd-slider-track-bg);
86
+ }
87
+
88
+ @media (prefers-reduced-motion: reduce) {
89
+ .vd-slider::-webkit-slider-thumb {
90
+ transition: none;
91
+ }
92
+ }
@@ -327,3 +327,49 @@
327
327
  transition: none;
328
328
  }
329
329
  }
330
+
331
+ /* ==========================================================================
332
+ @vanduo-oss/vue VdTabs markup (additive; added in 1.6.1)
333
+ VdTabs renders `.vd-tabs > .vd-tab-list > button.vd-tab.is-active` and a
334
+ `.vd-tab-panels` wrapper (vs the legacy .vd-tab-link / .vd-tab-content).
335
+ ========================================================================== */
336
+ .vd-tab {
337
+ display: inline-flex;
338
+ align-items: center;
339
+ gap: 0.5rem;
340
+ padding: var(--vd-tab-padding-y) var(--vd-tab-padding-x);
341
+ font-size: var(--vd-font-size-base);
342
+ font-weight: var(--vd-font-weight-medium);
343
+ color: var(--vd-tab-text);
344
+ background: transparent;
345
+ border: none;
346
+ border-bottom: var(--vd-tab-border-width) solid transparent;
347
+ cursor: pointer;
348
+ transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
349
+ white-space: nowrap;
350
+ }
351
+
352
+ .vd-tab:hover {
353
+ color: var(--vd-tab-text-hover);
354
+ background-color: var(--vd-tab-bg-hover);
355
+ }
356
+
357
+ .vd-tab:focus-visible {
358
+ outline: 2px solid var(--vd-color-primary);
359
+ outline-offset: -2px;
360
+ }
361
+
362
+ .vd-tab.is-active {
363
+ color: var(--vd-tab-text-active);
364
+ border-bottom-color: var(--vd-tab-border-active);
365
+ }
366
+
367
+ .vd-tab-panels {
368
+ padding-top: 1rem;
369
+ }
370
+
371
+ @media (prefers-reduced-motion: reduce) {
372
+ .vd-tab {
373
+ transition: none;
374
+ }
375
+ }
@@ -223,6 +223,57 @@ body > .vd-theme-customizer-panel.is-open {
223
223
  z-index: 1;
224
224
  }
225
225
 
226
+ /* Palette toggle (Fibonacci default / Open Color optional) */
227
+ .tc-palette-group {
228
+ display: flex;
229
+ gap: 0;
230
+ }
231
+
232
+ .tc-palette-btn {
233
+ flex: 1;
234
+ display: flex;
235
+ align-items: center;
236
+ justify-content: center;
237
+ padding: 0.5rem 0.5rem;
238
+ background: transparent;
239
+ border: 1px solid var(--vd-border-color);
240
+ color: var(--vd-text-primary);
241
+ font-size: var(--vd-font-size-xs);
242
+ font-weight: var(--vd-font-weight-medium);
243
+ cursor: pointer;
244
+ transition: all 0.15s ease;
245
+ }
246
+
247
+ .tc-palette-btn:first-child {
248
+ border-radius: calc(var(--vd-radius-scale, 0.25) * 0.5rem) 0 0 calc(var(--vd-radius-scale, 0.25) * 0.5rem);
249
+ }
250
+
251
+ .tc-palette-btn:last-child {
252
+ border-radius: 0 calc(var(--vd-radius-scale, 0.25) * 0.5rem) calc(var(--vd-radius-scale, 0.25) * 0.5rem) 0;
253
+ }
254
+
255
+ .tc-palette-btn:not(:first-child) {
256
+ margin-left: -1px;
257
+ }
258
+
259
+ .tc-palette-btn:hover {
260
+ background-color: var(--vd-bg-secondary);
261
+ z-index: 1;
262
+ }
263
+
264
+ .tc-palette-btn:focus-visible {
265
+ outline: 2px solid var(--vd-color-primary);
266
+ outline-offset: 1px;
267
+ z-index: 2;
268
+ }
269
+
270
+ .tc-palette-btn.is-active {
271
+ background-color: var(--vd-bg-dark);
272
+ border-color: var(--vd-bg-dark);
273
+ color: var(--vd-text-inverse);
274
+ z-index: 1;
275
+ }
276
+
226
277
  /* Font Select */
227
278
  .tc-font-select {
228
279
  width: 100%;
@@ -647,6 +698,7 @@ body > .vd-theme-customizer-panel.is-open {
647
698
  [data-theme="dark"] .customizer-radius-btn.is-active,
648
699
  [data-theme="dark"] .customizer-mode-btn.is-active,
649
700
  [data-theme="dark"] .tc-radius-btn.is-active,
701
+ [data-theme="dark"] .tc-palette-btn.is-active,
650
702
  [data-theme="dark"] .tc-mode-btn.is-active {
651
703
  background-color: var(--vd-gray-2);
652
704
  border-color: var(--vd-gray-2);
@@ -667,6 +719,7 @@ body > .vd-theme-customizer-panel.is-open {
667
719
  :root:not([data-theme]) .customizer-radius-btn.is-active,
668
720
  :root:not([data-theme]) .customizer-mode-btn.is-active,
669
721
  :root:not([data-theme]) .tc-radius-btn.is-active,
722
+ :root:not([data-theme]) .tc-palette-btn.is-active,
670
723
  :root:not([data-theme]) .tc-mode-btn.is-active {
671
724
  background-color: var(--vd-gray-2);
672
725
  border-color: var(--vd-gray-2);