@vanduo-oss/framework 1.2.6 → 1.2.7

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 (52) hide show
  1. package/README.md +31 -5
  2. package/css/components/affix.css +53 -0
  3. package/css/components/bubble.css +165 -0
  4. package/css/components/datepicker.css +216 -0
  5. package/css/components/fab.css +225 -0
  6. package/css/components/flow.css +265 -0
  7. package/css/components/rating.css +112 -0
  8. package/css/components/ripple.css +63 -0
  9. package/css/components/sidenav.css +70 -0
  10. package/css/components/spotlight.css +119 -0
  11. package/css/components/stepper.css +176 -0
  12. package/css/components/suggest.css +119 -0
  13. package/css/components/timeline.css +201 -0
  14. package/css/components/timepicker.css +80 -0
  15. package/css/components/transfer.css +165 -0
  16. package/css/components/tree.css +173 -0
  17. package/css/components/waypoint.css +59 -0
  18. package/css/vanduo.css +17 -0
  19. package/dist/build-info.json +3 -3
  20. package/dist/vanduo.cjs.js +2152 -4
  21. package/dist/vanduo.cjs.js.map +4 -4
  22. package/dist/vanduo.cjs.min.js +5 -5
  23. package/dist/vanduo.cjs.min.js.map +4 -4
  24. package/dist/vanduo.css +1943 -1
  25. package/dist/vanduo.css.map +1 -1
  26. package/dist/vanduo.esm.js +2152 -4
  27. package/dist/vanduo.esm.js.map +4 -4
  28. package/dist/vanduo.esm.min.js +5 -5
  29. package/dist/vanduo.esm.min.js.map +4 -4
  30. package/dist/vanduo.js +2152 -4
  31. package/dist/vanduo.js.map +4 -4
  32. package/dist/vanduo.min.css +2 -2
  33. package/dist/vanduo.min.css.map +1 -1
  34. package/dist/vanduo.min.js +5 -5
  35. package/dist/vanduo.min.js.map +4 -4
  36. package/js/components/affix.js +129 -0
  37. package/js/components/bubble.js +203 -0
  38. package/js/components/datepicker.js +287 -0
  39. package/js/components/flow.js +264 -0
  40. package/js/components/rating.js +160 -0
  41. package/js/components/ripple.js +74 -0
  42. package/js/components/sidenav.js +9 -2
  43. package/js/components/spotlight.js +295 -0
  44. package/js/components/stepper.js +97 -0
  45. package/js/components/suggest.js +219 -0
  46. package/js/components/timepicker.js +142 -0
  47. package/js/components/transfer.js +206 -0
  48. package/js/components/tree.js +191 -0
  49. package/js/components/validate.js +185 -0
  50. package/js/components/waypoint.js +120 -0
  51. package/js/index.js +16 -0
  52. package/package.json +1 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Vanduo Framework v1.2.6
1
+ # Vanduo Framework v1.2.7
2
2
 
3
3
  <p align="center">
4
4
  <img src="vanduo-banner.svg" alt="Vanduo Framework Banner" width="100%">
@@ -19,7 +19,7 @@
19
19
 
20
20
  ## Overview
21
21
 
22
- A lightweight, pure HTML/CSS/JS framework for designing beautiful interfaces. Zero runtime dependencies, no mandatory build tools, just clean and simple code.
22
+ A lightweight, pure HTML/CSS/JS framework with **45+ components** for designing beautiful interfaces. Zero runtime dependencies, no mandatory build tools, just clean and simple code.
23
23
 
24
24
  [**Browse Full Documentation →**](https://vanduo.dev/#docs)
25
25
 
@@ -37,6 +37,32 @@ A lightweight, pure HTML/CSS/JS framework for designing beautiful interfaces. Ze
37
37
 
38
38
  ---
39
39
 
40
+ ## What's New in v1.2.7
41
+
42
+ v1.2.7 adds **17 new components**, bringing the total to **45+**. Each component ships with dedicated CSS, JavaScript (where applicable), full Playwright test coverage, and comprehensive documentation.
43
+
44
+ | Component | Vanduo Name | Type |
45
+ |---|---|---|
46
+ | Carousel | Flow | CSS + JS |
47
+ | Popover | Bubble | CSS + JS |
48
+ | Scrollspy | Waypoint | CSS + JS |
49
+ | Offcanvas | — (enhanced Sidenav) | CSS + JS |
50
+ | Ripple / Waves | Ripple | CSS + JS |
51
+ | Floating Action Button | FAB | CSS-only |
52
+ | Sticky | Affix | CSS + JS |
53
+ | Autocomplete | Suggest | CSS + JS |
54
+ | Form Validation | Validate | JS |
55
+ | Date Picker | Datepicker | CSS + JS |
56
+ | Time Picker | Timepicker | CSS + JS |
57
+ | Stepper | Stepper | CSS + JS |
58
+ | Timeline | Timeline | CSS-only |
59
+ | Rating | Rating | CSS + JS |
60
+ | Transfer / Multi-select | Transfer | CSS + JS |
61
+ | Tree View | Tree | CSS + JS |
62
+ | Spotlight / Feature Discovery | Spotlight | CSS + JS |
63
+
64
+ ---
65
+
40
66
  ## Quick Start
41
67
 
42
68
  ### Option 1: CDN (Recommended)
@@ -54,8 +80,8 @@ The quickest way to get started — no install, no build step. Add two lines to
54
80
 
55
81
  **Pin to a specific version** for production:
56
82
  ```html
57
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/vanduo-oss/framework@v1.2.5/dist/vanduo.min.css">
58
- <script src="https://cdn.jsdelivr.net/gh/vanduo-oss/framework@v1.2.5/dist/vanduo.min.js"></script>
83
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/vanduo-oss/framework@v1.2.7/dist/vanduo.min.css">
84
+ <script src="https://cdn.jsdelivr.net/gh/vanduo-oss/framework@v1.2.7/dist/vanduo.min.js"></script>
59
85
  <script>Vanduo.init();</script>
60
86
  ```
61
87
 
@@ -118,7 +144,7 @@ This project includes an [`llms.txt`](llms.txt) file — a structured markdown s
118
144
  Use the hardened upload script to attach only approved bundle artifacts from `dist/`:
119
145
 
120
146
  ```bash
121
- pnpm run release:assets -- v1.2.5
147
+ pnpm run release:assets -- v1.2.7
122
148
  ```
123
149
 
124
150
  Notes:
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Vanduo Framework - Affix (Sticky)
3
+ * Scroll-aware sticky positioning with is-stuck state
4
+ * Primary: .vd-affix | Alias: .vd-sticky
5
+ */
6
+
7
+ :root {
8
+ --affix-top-offset: 0;
9
+ --affix-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
10
+ --affix-transition: box-shadow 0.2s ease, background-color 0.2s ease;
11
+ --affix-z-index: 1020;
12
+ }
13
+
14
+ [data-theme="dark"] {
15
+ --affix-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
16
+ }
17
+
18
+ @media (prefers-color-scheme: dark) {
19
+ :root:not([data-theme]) {
20
+ --affix-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
21
+ }
22
+ }
23
+
24
+ /* ========== Base ========== */
25
+
26
+ .vd-affix,
27
+ .vd-sticky,
28
+ [data-vd-affix] {
29
+ position: sticky;
30
+ top: var(--affix-top-offset);
31
+ transition: var(--affix-transition);
32
+ z-index: calc(var(--affix-z-index) - 1);
33
+ }
34
+
35
+ /* ========== Stuck State ========== */
36
+
37
+ .vd-affix.is-stuck,
38
+ .vd-sticky.is-stuck,
39
+ [data-vd-affix].is-stuck {
40
+ z-index: var(--affix-z-index);
41
+ box-shadow: var(--affix-shadow);
42
+ }
43
+
44
+ /* ========== Variants ========== */
45
+
46
+ .vd-affix-no-shadow.is-stuck {
47
+ box-shadow: none;
48
+ }
49
+
50
+ .vd-affix-bordered.is-stuck {
51
+ box-shadow: none;
52
+ border-bottom: 1px solid var(--border-color, #dee2e6);
53
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Vanduo Framework - Bubble (Popover)
3
+ * Click-triggered rich content popover
4
+ * Primary: .vd-bubble | Alias: .vd-popover
5
+ */
6
+
7
+ :root {
8
+ /* Bubble Colors */
9
+ --bubble-bg: var(--card-bg, #fff);
10
+ --bubble-text: var(--text-primary, #212529);
11
+ --bubble-border-color: var(--border-color, #dee2e6);
12
+ --bubble-shadow: 0 0.5rem 1.3125rem rgba(0, 0, 0, 0.12); /* 8px 21px - fib */
13
+
14
+ /* Bubble Spacing (Fibonacci) */
15
+ --bubble-padding: 0.8125rem; /* 13px - fib */
16
+ --bubble-header-padding: 0.5rem 0.8125rem; /* 8px 13px */
17
+ --bubble-arrow-size: 8px; /* fib */
18
+
19
+ /* Bubble Sizing */
20
+ --bubble-max-width: 21rem; /* 336px - fib×16 */
21
+ --bubble-min-width: 8rem;
22
+ --bubble-border-radius: var(--border-radius, 0.5rem);
23
+
24
+ /* Bubble Z-index */
25
+ --bubble-z-index: 1060;
26
+
27
+ /* Bubble Font */
28
+ --bubble-font-size: var(--font-size-sm, 0.875rem);
29
+ }
30
+
31
+ /* Dark Theme */
32
+ [data-theme="dark"] {
33
+ --bubble-bg: var(--card-bg, #2d2d44);
34
+ --bubble-border-color: var(--border-color, #3d3d5c);
35
+ --bubble-shadow: 0 0.5rem 1.3125rem rgba(0, 0, 0, 0.3);
36
+ }
37
+
38
+ @media (prefers-color-scheme: dark) {
39
+ :root:not([data-theme]) {
40
+ --bubble-bg: var(--card-bg, #2d2d44);
41
+ --bubble-border-color: var(--border-color, #3d3d5c);
42
+ --bubble-shadow: 0 0.5rem 1.3125rem rgba(0, 0, 0, 0.3);
43
+ }
44
+ }
45
+
46
+ /* ========== Base ========== */
47
+
48
+ .vd-bubble-content,
49
+ .vd-popover-content {
50
+ position: absolute;
51
+ z-index: var(--bubble-z-index);
52
+ max-width: var(--bubble-max-width);
53
+ min-width: var(--bubble-min-width);
54
+ background: var(--bubble-bg);
55
+ color: var(--bubble-text);
56
+ border: 1px solid var(--bubble-border-color);
57
+ border-radius: var(--bubble-border-radius);
58
+ box-shadow: var(--bubble-shadow);
59
+ font-size: var(--bubble-font-size);
60
+ opacity: 0;
61
+ visibility: hidden;
62
+ transition: opacity 0.2s ease, visibility 0.2s ease, transform 0.2s ease;
63
+ transform: scale(0.95);
64
+ pointer-events: none;
65
+ }
66
+
67
+ .vd-bubble-content.is-visible,
68
+ .vd-popover-content.is-visible {
69
+ opacity: 1;
70
+ visibility: visible;
71
+ transform: scale(1);
72
+ pointer-events: auto;
73
+ }
74
+
75
+ /* ========== Header ========== */
76
+
77
+ .vd-bubble-header {
78
+ display: flex;
79
+ align-items: center;
80
+ justify-content: space-between;
81
+ padding: var(--bubble-header-padding);
82
+ border-bottom: 1px solid var(--bubble-border-color);
83
+ font-weight: 600;
84
+ }
85
+
86
+ /* ========== Body ========== */
87
+
88
+ .vd-bubble-body {
89
+ padding: var(--bubble-padding);
90
+ }
91
+
92
+ /* ========== Close Button ========== */
93
+
94
+ .vd-bubble-close {
95
+ display: flex;
96
+ align-items: center;
97
+ justify-content: center;
98
+ width: 1.5rem;
99
+ height: 1.5rem;
100
+ padding: 0;
101
+ margin-left: 0.5rem;
102
+ border: none;
103
+ border-radius: 50%;
104
+ background: transparent;
105
+ color: var(--bubble-text);
106
+ font-size: 1.125rem;
107
+ line-height: 1;
108
+ cursor: pointer;
109
+ opacity: 0.5;
110
+ transition: opacity 0.15s ease;
111
+ }
112
+
113
+ .vd-bubble-close:hover { opacity: 1; }
114
+
115
+ /* ========== Arrow ========== */
116
+
117
+ .vd-bubble-content::before,
118
+ .vd-popover-content::before {
119
+ content: '';
120
+ position: absolute;
121
+ width: 0;
122
+ height: 0;
123
+ border: var(--bubble-arrow-size) solid transparent;
124
+ }
125
+
126
+ /* Arrow: top placement (popover below trigger) */
127
+ .vd-bubble-content[data-placement="bottom"]::before {
128
+ bottom: 100%;
129
+ left: 50%;
130
+ transform: translateX(-50%);
131
+ border-bottom-color: var(--bubble-border-color);
132
+ }
133
+
134
+ .vd-bubble-content[data-placement="top"]::before {
135
+ top: 100%;
136
+ left: 50%;
137
+ transform: translateX(-50%);
138
+ border-top-color: var(--bubble-border-color);
139
+ }
140
+
141
+ .vd-bubble-content[data-placement="left"]::before {
142
+ left: 100%;
143
+ top: 50%;
144
+ transform: translateY(-50%);
145
+ border-left-color: var(--bubble-border-color);
146
+ }
147
+
148
+ .vd-bubble-content[data-placement="right"]::before {
149
+ right: 100%;
150
+ top: 50%;
151
+ transform: translateY(-50%);
152
+ border-right-color: var(--bubble-border-color);
153
+ }
154
+
155
+ /* ========== Size Variants ========== */
156
+
157
+ .vd-bubble-sm .vd-bubble-body {
158
+ --bubble-padding: 0.5rem; /* 8px - fib */
159
+ --bubble-font-size: var(--font-size-xs, 0.75rem);
160
+ }
161
+
162
+ .vd-bubble-lg .vd-bubble-body {
163
+ --bubble-padding: 1.3125rem; /* 21px - fib */
164
+ --bubble-max-width: 34rem;
165
+ }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Vanduo Framework - Datepicker
3
+ * Calendar popup for date selection
4
+ */
5
+
6
+ :root {
7
+ /* Datepicker Colors */
8
+ --dp-bg: var(--card-bg, #fff);
9
+ --dp-border-color: var(--border-color, #dee2e6);
10
+ --dp-shadow: 0 0.25rem 0.8125rem rgba(0, 0, 0, 0.12); /* 4px 13px fib */
11
+ --dp-header-bg: var(--color-primary, #0d6efd);
12
+ --dp-header-color: #fff;
13
+ --dp-day-hover-bg: var(--bg-secondary, #f8f9fa);
14
+ --dp-day-selected-bg: var(--color-primary, #0d6efd);
15
+ --dp-day-selected-color: #fff;
16
+ --dp-day-today-color: var(--color-primary, #0d6efd);
17
+ --dp-day-outside-color: var(--text-muted, #6c757d);
18
+ --dp-day-disabled-color: var(--text-disabled, #adb5bd);
19
+
20
+ /* Datepicker Spacing (Fibonacci) */
21
+ --dp-padding: 0.5rem; /* 8px - fib */
22
+ --dp-cell-size: 2.25rem; /* 36px */
23
+ --dp-gap: 2px;
24
+ --dp-border-radius: var(--border-radius, 0.5rem);
25
+
26
+ /* Datepicker Z-index */
27
+ --dp-z-index: 1055;
28
+ }
29
+
30
+ [data-theme="dark"] {
31
+ --dp-bg: var(--card-bg, #2d2d44);
32
+ --dp-border-color: var(--border-color, #3d3d5c);
33
+ --dp-day-hover-bg: var(--bg-primary, #1a1a2e);
34
+ --dp-shadow: 0 0.25rem 0.8125rem rgba(0, 0, 0, 0.3);
35
+ }
36
+
37
+ @media (prefers-color-scheme: dark) {
38
+ :root:not([data-theme]) {
39
+ --dp-bg: var(--card-bg, #2d2d44);
40
+ --dp-border-color: var(--border-color, #3d3d5c);
41
+ --dp-day-hover-bg: var(--bg-primary, #1a1a2e);
42
+ --dp-shadow: 0 0.25rem 0.8125rem rgba(0, 0, 0, 0.3);
43
+ }
44
+ }
45
+
46
+ /* ========== Calendar Popup ========== */
47
+
48
+ .vd-datepicker-popup {
49
+ position: absolute;
50
+ z-index: var(--dp-z-index);
51
+ background: var(--dp-bg);
52
+ border: 1px solid var(--dp-border-color);
53
+ border-radius: var(--dp-border-radius);
54
+ box-shadow: var(--dp-shadow);
55
+ padding: var(--dp-padding);
56
+ display: none;
57
+ min-width: 17rem;
58
+ }
59
+
60
+ .vd-datepicker-popup.is-open {
61
+ display: block;
62
+ }
63
+
64
+ /* ========== Header ========== */
65
+
66
+ .vd-datepicker-header {
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: space-between;
70
+ padding: 0.3125rem; /* 5px - fib */
71
+ margin-bottom: 0.3125rem;
72
+ }
73
+
74
+ .vd-datepicker-title {
75
+ font-weight: 600;
76
+ font-size: var(--font-size-base, 1rem);
77
+ cursor: pointer;
78
+ }
79
+
80
+ .vd-datepicker-title:hover {
81
+ color: var(--dp-day-today-color);
82
+ }
83
+
84
+ .vd-datepicker-prev,
85
+ .vd-datepicker-next {
86
+ display: flex;
87
+ align-items: center;
88
+ justify-content: center;
89
+ width: 1.75rem;
90
+ height: 1.75rem;
91
+ padding: 0;
92
+ border: none;
93
+ border-radius: 50%;
94
+ background: transparent;
95
+ cursor: pointer;
96
+ font-size: 1rem;
97
+ color: var(--text-primary, #212529);
98
+ transition: background 0.15s ease;
99
+ }
100
+
101
+ .vd-datepicker-prev:hover,
102
+ .vd-datepicker-next:hover {
103
+ background: var(--dp-day-hover-bg);
104
+ }
105
+
106
+ /* ========== Weekday Headers ========== */
107
+
108
+ .vd-datepicker-weekdays {
109
+ display: grid;
110
+ grid-template-columns: repeat(7, 1fr);
111
+ gap: var(--dp-gap);
112
+ text-align: center;
113
+ font-size: 0.75rem;
114
+ font-weight: 600;
115
+ color: var(--text-muted, #6c757d);
116
+ padding-bottom: 0.25rem;
117
+ }
118
+
119
+ /* ========== Days Grid ========== */
120
+
121
+ .vd-datepicker-days {
122
+ display: grid;
123
+ grid-template-columns: repeat(7, 1fr);
124
+ gap: var(--dp-gap);
125
+ }
126
+
127
+ .vd-datepicker-day {
128
+ display: flex;
129
+ align-items: center;
130
+ justify-content: center;
131
+ width: var(--dp-cell-size);
132
+ height: var(--dp-cell-size);
133
+ border: none;
134
+ border-radius: 50%;
135
+ background: transparent;
136
+ cursor: pointer;
137
+ font-size: 0.875rem;
138
+ transition: background 0.15s ease, color 0.15s ease;
139
+ padding: 0;
140
+ }
141
+
142
+ .vd-datepicker-day:hover {
143
+ background: var(--dp-day-hover-bg);
144
+ }
145
+
146
+ .vd-datepicker-day.is-today {
147
+ color: var(--dp-day-today-color);
148
+ font-weight: 700;
149
+ }
150
+
151
+ .vd-datepicker-day.is-selected {
152
+ background: var(--dp-day-selected-bg);
153
+ color: var(--dp-day-selected-color);
154
+ font-weight: 600;
155
+ }
156
+
157
+ .vd-datepicker-day.is-outside {
158
+ color: var(--dp-day-outside-color);
159
+ }
160
+
161
+ .vd-datepicker-day.is-disabled {
162
+ color: var(--dp-day-disabled-color);
163
+ cursor: not-allowed;
164
+ pointer-events: none;
165
+ }
166
+
167
+ /* ========== Range ========== */
168
+
169
+ .vd-datepicker-day.is-range-start {
170
+ border-radius: 50% 0 0 50%;
171
+ background: var(--dp-day-selected-bg);
172
+ color: var(--dp-day-selected-color);
173
+ }
174
+
175
+ .vd-datepicker-day.is-range-end {
176
+ border-radius: 0 50% 50% 0;
177
+ background: var(--dp-day-selected-bg);
178
+ color: var(--dp-day-selected-color);
179
+ }
180
+
181
+ .vd-datepicker-day.is-in-range {
182
+ background: rgba(13, 110, 253, 0.1);
183
+ border-radius: 0;
184
+ }
185
+
186
+ /* ========== Month/Year Picker ========== */
187
+
188
+ .vd-datepicker-months,
189
+ .vd-datepicker-years {
190
+ display: grid;
191
+ grid-template-columns: repeat(3, 1fr);
192
+ gap: 0.25rem;
193
+ padding: 0.25rem;
194
+ }
195
+
196
+ .vd-datepicker-month-btn,
197
+ .vd-datepicker-year-btn {
198
+ padding: 0.5rem;
199
+ border: none;
200
+ border-radius: var(--dp-border-radius);
201
+ background: transparent;
202
+ cursor: pointer;
203
+ text-align: center;
204
+ transition: background 0.15s ease;
205
+ }
206
+
207
+ .vd-datepicker-month-btn:hover,
208
+ .vd-datepicker-year-btn:hover {
209
+ background: var(--dp-day-hover-bg);
210
+ }
211
+
212
+ .vd-datepicker-month-btn.is-selected,
213
+ .vd-datepicker-year-btn.is-selected {
214
+ background: var(--dp-day-selected-bg);
215
+ color: var(--dp-day-selected-color);
216
+ }
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Vanduo Framework - FAB (Floating Action Button)
3
+ * Elevated circular action button with optional fixed positioning and speed-dial menu
4
+ */
5
+
6
+ :root {
7
+ /* FAB Dimensions (Fibonacci) */
8
+ --fab-size: 3.5rem; /* 56px */
9
+ --fab-size-sm: 2.5rem; /* 40px */
10
+ --fab-size-lg: 4.5rem; /* 72px */
11
+ --fab-icon-size: 1.5rem;
12
+
13
+ /* FAB Colors */
14
+ --fab-bg: var(--color-primary, #0d6efd);
15
+ --fab-color: #fff;
16
+ --fab-hover-bg: var(--color-primary-dark, #0b5ed7);
17
+ --fab-shadow: 0 3px 8px rgba(0, 0, 0, 0.25), 0 1px 3px rgba(0, 0, 0, 0.15);
18
+ --fab-hover-shadow: 0 5px 13px rgba(0, 0, 0, 0.3), 0 2px 5px rgba(0, 0, 0, 0.2);
19
+
20
+ /* FAB Position */
21
+ --fab-offset: 1.3125rem; /* 21px - fib */
22
+ --fab-z-index: 1030;
23
+
24
+ /* FAB Menu */
25
+ --fab-menu-gap: 0.8125rem; /* 13px - fib */
26
+ }
27
+
28
+ [data-theme="dark"] {
29
+ --fab-shadow: 0 3px 8px rgba(0, 0, 0, 0.5), 0 1px 3px rgba(0, 0, 0, 0.3);
30
+ --fab-hover-shadow: 0 5px 13px rgba(0, 0, 0, 0.6), 0 2px 5px rgba(0, 0, 0, 0.35);
31
+ }
32
+
33
+ @media (prefers-color-scheme: dark) {
34
+ :root:not([data-theme]) {
35
+ --fab-shadow: 0 3px 8px rgba(0, 0, 0, 0.5), 0 1px 3px rgba(0, 0, 0, 0.3);
36
+ --fab-hover-shadow: 0 5px 13px rgba(0, 0, 0, 0.6), 0 2px 5px rgba(0, 0, 0, 0.35);
37
+ }
38
+ }
39
+
40
+ /* ========== Base ========== */
41
+
42
+ .vd-fab {
43
+ display: inline-flex;
44
+ align-items: center;
45
+ justify-content: center;
46
+ width: var(--fab-size);
47
+ height: var(--fab-size);
48
+ padding: 0;
49
+ border: none;
50
+ border-radius: 50%;
51
+ background: var(--fab-bg);
52
+ color: var(--fab-color);
53
+ font-size: var(--fab-icon-size);
54
+ box-shadow: var(--fab-shadow);
55
+ cursor: pointer;
56
+ transition: background 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
57
+ }
58
+
59
+ .vd-fab:hover {
60
+ background: var(--fab-hover-bg);
61
+ box-shadow: var(--fab-hover-shadow);
62
+ transform: scale(1.05);
63
+ }
64
+
65
+ .vd-fab:active {
66
+ transform: scale(0.95);
67
+ }
68
+
69
+ .vd-fab:focus-visible {
70
+ outline: 2px solid var(--fab-bg);
71
+ outline-offset: 3px;
72
+ }
73
+
74
+ /* ========== Sizes ========== */
75
+
76
+ .vd-fab-sm {
77
+ width: var(--fab-size-sm);
78
+ height: var(--fab-size-sm);
79
+ font-size: 1.125rem;
80
+ }
81
+
82
+ .vd-fab-lg {
83
+ width: var(--fab-size-lg);
84
+ height: var(--fab-size-lg);
85
+ font-size: 2rem;
86
+ }
87
+
88
+ /* ========== Extended (with label) ========== */
89
+
90
+ .vd-fab-extended {
91
+ border-radius: 1.75rem;
92
+ width: auto;
93
+ padding: 0 1.3125rem; /* 21px - fib */
94
+ gap: 0.5rem;
95
+ }
96
+
97
+ /* ========== Fixed Positioning ========== */
98
+
99
+ .vd-fab-fixed {
100
+ position: fixed;
101
+ bottom: var(--fab-offset);
102
+ right: var(--fab-offset);
103
+ z-index: var(--fab-z-index);
104
+ }
105
+
106
+ /* ========== Position Variants (all include position: fixed) ========== */
107
+
108
+ .vd-fab-bottom-left {
109
+ position: fixed;
110
+ z-index: var(--fab-z-index);
111
+ bottom: var(--fab-offset);
112
+ right: auto;
113
+ left: var(--fab-offset);
114
+ }
115
+
116
+ .vd-fab-top-right {
117
+ position: fixed;
118
+ z-index: var(--fab-z-index);
119
+ bottom: auto;
120
+ right: var(--fab-offset);
121
+ top: var(--fab-offset);
122
+ }
123
+
124
+ .vd-fab-top-left {
125
+ position: fixed;
126
+ z-index: var(--fab-z-index);
127
+ bottom: auto;
128
+ right: auto;
129
+ top: var(--fab-offset);
130
+ left: var(--fab-offset);
131
+ }
132
+
133
+ .vd-fab-center {
134
+ position: fixed;
135
+ z-index: var(--fab-z-index);
136
+ bottom: var(--fab-offset);
137
+ right: 50%;
138
+ transform: translateX(50%);
139
+ }
140
+
141
+ .vd-fab-center:hover {
142
+ transform: translateX(50%) scale(1.05);
143
+ }
144
+
145
+ /* ========== Color Variants ========== */
146
+
147
+ .vd-fab-secondary {
148
+ --fab-bg: var(--color-gray-600, #6c757d);
149
+ --fab-hover-bg: var(--color-gray-700, #5c636a);
150
+ }
151
+
152
+ .vd-fab-success {
153
+ --fab-bg: var(--color-success, #198754);
154
+ --fab-hover-bg: #157347;
155
+ }
156
+
157
+ .vd-fab-danger {
158
+ --fab-bg: var(--color-danger, #dc3545);
159
+ --fab-hover-bg: #bb2d3b;
160
+ }
161
+
162
+ /* ========== Speed Dial Menu ========== */
163
+
164
+ .vd-fab-menu {
165
+ position: relative;
166
+ display: inline-flex;
167
+ flex-direction: column-reverse;
168
+ align-items: center;
169
+ gap: var(--fab-menu-gap);
170
+ }
171
+
172
+ .vd-fab-menu .vd-fab-actions {
173
+ display: flex;
174
+ flex-direction: column;
175
+ align-items: center;
176
+ gap: var(--fab-menu-gap);
177
+ opacity: 0;
178
+ visibility: hidden;
179
+ transform: translateY(0.5rem);
180
+ transition: opacity 0.2s ease, visibility 0.2s ease, transform 0.2s ease;
181
+ }
182
+
183
+ .vd-fab-menu.is-open .vd-fab-actions {
184
+ opacity: 1;
185
+ visibility: visible;
186
+ transform: translateY(0);
187
+ }
188
+
189
+ .vd-fab-menu.vd-fab-fixed {
190
+ position: fixed;
191
+ bottom: var(--fab-offset);
192
+ right: var(--fab-offset);
193
+ z-index: var(--fab-z-index);
194
+ }
195
+
196
+ .vd-fab-menu .vd-fab-action {
197
+ display: inline-flex;
198
+ align-items: center;
199
+ justify-content: center;
200
+ width: var(--fab-size-sm);
201
+ height: var(--fab-size-sm);
202
+ border: none;
203
+ border-radius: 50%;
204
+ background: var(--fab-bg);
205
+ color: var(--fab-color);
206
+ font-size: 1rem;
207
+ box-shadow: var(--fab-shadow);
208
+ cursor: pointer;
209
+ transition: background 0.15s ease, transform 0.15s ease;
210
+ }
211
+
212
+ .vd-fab-menu .vd-fab-action:hover {
213
+ transform: scale(1.1);
214
+ background: var(--fab-hover-bg);
215
+ }
216
+
217
+ /* Staggered entrance for action buttons */
218
+ .vd-fab-menu.is-open .vd-fab-actions .vd-fab:nth-child(1),
219
+ .vd-fab-menu.is-open .vd-fab-action:nth-child(1) { transition-delay: 0s; }
220
+ .vd-fab-menu.is-open .vd-fab-actions .vd-fab:nth-child(2),
221
+ .vd-fab-menu.is-open .vd-fab-action:nth-child(2) { transition-delay: 0.05s; }
222
+ .vd-fab-menu.is-open .vd-fab-actions .vd-fab:nth-child(3),
223
+ .vd-fab-menu.is-open .vd-fab-action:nth-child(3) { transition-delay: 0.1s; }
224
+ .vd-fab-menu.is-open .vd-fab-actions .vd-fab:nth-child(4),
225
+ .vd-fab-menu.is-open .vd-fab-action:nth-child(4) { transition-delay: 0.15s; }