@birdapi/velinstyle 0.6.1 → 0.8.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 (64) hide show
  1. package/README.de.md +337 -316
  2. package/README.md +33 -12
  3. package/cli/blueprint.js +8 -0
  4. package/cli/blueprints/bottom-nav-mobile.html +17 -0
  5. package/cli/blueprints/cookie-consent.html +9 -0
  6. package/cli/blueprints/empty-state.html +5 -0
  7. package/cli/blueprints/filter-bar.html +15 -0
  8. package/cli/blueprints/notification-center.html +13 -0
  9. package/cli/blueprints/onboarding.html +23 -0
  10. package/cli/blueprints/pricing-table.html +20 -0
  11. package/cli/blueprints/settings-panel.html +20 -0
  12. package/cli/index.js +119 -3
  13. package/cli/layout-audit.js +325 -0
  14. package/cli/scaffold-recipes.json +70 -0
  15. package/cli/scaffold.js +155 -0
  16. package/cli/scanner.js +114 -0
  17. package/components/focus-manager.js +106 -80
  18. package/components/index.js +20 -1
  19. package/components/sanitize.js +29 -3
  20. package/components/shadow-a11y-styles.js +18 -0
  21. package/components/velin-accordion.js +112 -98
  22. package/components/velin-announcer.js +35 -0
  23. package/components/velin-bottom-nav.js +89 -0
  24. package/components/velin-carousel.js +40 -5
  25. package/components/velin-collapse.js +95 -65
  26. package/components/velin-combobox.js +149 -0
  27. package/components/velin-command.js +127 -0
  28. package/components/velin-counter.js +152 -0
  29. package/components/velin-drawer.js +6 -3
  30. package/components/velin-dropdown.js +33 -2
  31. package/components/velin-flip.js +220 -0
  32. package/components/velin-icon.js +43 -9
  33. package/components/velin-live-dot.js +85 -0
  34. package/components/velin-menubar.js +83 -0
  35. package/components/velin-modal.js +3 -1
  36. package/components/velin-popover.js +61 -21
  37. package/components/velin-rating.js +91 -0
  38. package/components/velin-reveal.js +80 -0
  39. package/components/velin-segmented-control.js +108 -0
  40. package/components/velin-sheet.js +107 -0
  41. package/components/velin-sparkline.js +207 -0
  42. package/components/velin-theme-toggle.js +277 -60
  43. package/components/velin-tooltip-wc.js +26 -2
  44. package/dist/velinstyle-components.iife.js +1849 -120
  45. package/dist/velinstyle-components.js +1871 -120
  46. package/dist/velinstyle-components.min.js +434 -91
  47. package/dist/velinstyle.css +640 -44
  48. package/dist/velinstyle.min.css +1 -1
  49. package/package.json +7 -3
  50. package/src/a11y/focus-not-obscured.css +21 -0
  51. package/src/a11y/forced-colors.css +86 -38
  52. package/src/a11y/high-contrast-aaa.css +37 -0
  53. package/src/a11y/preferences.css +106 -85
  54. package/src/a11y/security.css +119 -104
  55. package/src/a11y/target-size.css +32 -0
  56. package/src/base/reset.css +12 -1
  57. package/src/base/root.css +4 -4
  58. package/src/components/nav.css +152 -151
  59. package/src/tokens/motion.css +7 -0
  60. package/src/utilities/animation.css +97 -1
  61. package/src/utilities/chart-animation.css +101 -0
  62. package/src/utilities/filter-effects.css +103 -0
  63. package/src/utilities/safe-area.css +39 -0
  64. package/src/velinstyle.css +9 -1
package/src/base/root.css CHANGED
@@ -1,4 +1,4 @@
1
- /* VelinStyle -- Layer order declaration
2
- This MUST be the first file imported.
3
- Later layers always override earlier layers -- no specificity wars. */
4
- @layer tokens, reset, base, a11y, layout, components, utilities, themes;
1
+ /* VelinStyle -- Layer order declaration
2
+ This MUST be the first file imported.
3
+ Later layers always override earlier layers -- no specificity wars. */
4
+ @layer tokens, reset, base, a11y, layout, components, utilities, security, themes;
@@ -1,151 +1,152 @@
1
- @layer components {
2
- .velin-nav {
3
- display: flex;
4
- align-items: center;
5
- gap: var(--velin-space-2);
6
- padding-block: var(--velin-space-3);
7
- padding-inline: var(--velin-space-4);
8
- background-color: var(--velin-color-surface-bright);
9
- border-block-end: 1px solid var(--velin-color-border);
10
- }
11
-
12
- .velin-nav__brand {
13
- font-size: var(--velin-text-lg);
14
- font-weight: var(--velin-weight-bold);
15
- color: var(--velin-color-text);
16
- text-decoration: none;
17
- margin-inline-end: auto;
18
- }
19
-
20
- .velin-nav__list {
21
- display: flex;
22
- align-items: center;
23
- gap: var(--velin-space-1);
24
- list-style: none;
25
- }
26
-
27
- .velin-nav__link {
28
- display: inline-flex;
29
- align-items: center;
30
- gap: var(--velin-space-1);
31
- min-block-size: 2.75rem; /* 44px touch target */
32
- padding-block: var(--velin-space-2);
33
- padding-inline: var(--velin-space-3);
34
- font-size: var(--velin-text-base);
35
- font-weight: var(--velin-weight-medium);
36
- color: var(--velin-color-text-muted);
37
- text-decoration: none;
38
- border-radius: var(--velin-radius-md);
39
- transition: var(--velin-transition-colors);
40
- }
41
-
42
- .velin-nav__link:hover {
43
- color: var(--velin-color-text);
44
- background-color: var(--velin-color-surface-dim);
45
- }
46
-
47
- .velin-nav__link[aria-current="page"],
48
- .velin-nav__link--active {
49
- color: var(--velin-color-primary);
50
- font-weight: var(--velin-weight-semibold);
51
- }
52
-
53
- /* Toggle button for mobile */
54
- .velin-nav__toggle {
55
- display: none;
56
- min-block-size: 2.75rem;
57
- min-inline-size: 2.75rem;
58
- align-items: center;
59
- justify-content: center;
60
- background: none;
61
- border: none;
62
- color: var(--velin-color-text);
63
- cursor: pointer;
64
- border-radius: var(--velin-radius-md);
65
- }
66
-
67
- .velin-nav__toggle:hover {
68
- background-color: var(--velin-color-surface-dim);
69
- }
70
-
71
- /* Mobile: stack nav vertically */
72
- @media (max-width: 47.999rem) {
73
- .velin-nav {
74
- flex-wrap: wrap;
75
- }
76
-
77
- .velin-nav__toggle {
78
- display: inline-flex;
79
- }
80
-
81
- .velin-nav__list {
82
- flex-direction: column;
83
- align-items: stretch;
84
- inline-size: 100%;
85
- display: none;
86
- }
87
-
88
- .velin-nav__list[data-velin-open] {
89
- display: flex;
90
- }
91
-
92
- .velin-nav__link {
93
- inline-size: 100%;
94
- }
95
- }
96
-
97
- /* Vertical nav variant */
98
- .velin-nav--vertical {
99
- flex-direction: column;
100
- align-items: stretch;
101
- border-block-end: none;
102
- border-inline-end: 1px solid var(--velin-color-border);
103
- }
104
-
105
- .velin-nav--vertical .velin-nav__brand {
106
- margin-inline-end: 0;
107
- margin-block-end: var(--velin-space-4);
108
- }
109
-
110
- .velin-nav--vertical .velin-nav__list {
111
- flex-direction: column;
112
- align-items: stretch;
113
- }
114
-
115
- /* Mega menu */
116
- .velin-nav__mega {
117
- position: absolute;
118
- inset-inline-start: 0;
119
- inset-inline-end: 0;
120
- inset-block-start: 100%;
121
- z-index: var(--velin-z-dropdown);
122
- background: var(--velin-color-surface-bright);
123
- border: 1px solid var(--velin-color-border);
124
- border-radius: var(--velin-radius-lg);
125
- box-shadow: var(--velin-shadow-lg);
126
- padding: var(--velin-space-6);
127
- display: none;
128
- }
129
-
130
- .velin-nav__item:hover > .velin-nav__mega,
131
- .velin-nav__item:focus-within > .velin-nav__mega {
132
- display: grid;
133
- grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
134
- gap: var(--velin-space-6);
135
- }
136
-
137
- .velin-nav__mega-section {
138
- display: flex;
139
- flex-direction: column;
140
- gap: var(--velin-space-2);
141
- }
142
-
143
- .velin-nav__mega-title {
144
- font-size: var(--velin-text-xs);
145
- font-weight: var(--velin-weight-bold);
146
- text-transform: uppercase;
147
- letter-spacing: 0.06em;
148
- color: var(--velin-color-text-muted);
149
- margin-block-end: var(--velin-space-2);
150
- }
151
- }
1
+ @layer components {
2
+ .velin-nav {
3
+ display: flex;
4
+ align-items: center;
5
+ gap: var(--velin-space-2);
6
+ padding-block: var(--velin-space-3);
7
+ padding-inline: var(--velin-space-4);
8
+ background-color: var(--velin-color-surface-bright);
9
+ border-block-end: 1px solid var(--velin-color-border);
10
+ }
11
+
12
+ .velin-nav__brand {
13
+ font-size: var(--velin-text-lg);
14
+ font-weight: var(--velin-weight-bold);
15
+ color: var(--velin-color-text);
16
+ text-decoration: none;
17
+ margin-inline-end: auto;
18
+ }
19
+
20
+ .velin-nav__list {
21
+ display: flex;
22
+ align-items: center;
23
+ gap: var(--velin-space-1);
24
+ list-style: none;
25
+ }
26
+
27
+ .velin-nav__link {
28
+ display: inline-flex;
29
+ align-items: center;
30
+ gap: var(--velin-space-1);
31
+ min-block-size: 2.75rem; /* 44px touch target */
32
+ padding-block: var(--velin-space-2);
33
+ padding-inline: var(--velin-space-3);
34
+ font-size: var(--velin-text-base);
35
+ font-weight: var(--velin-weight-medium);
36
+ color: var(--velin-color-text-muted);
37
+ text-decoration: none;
38
+ border-radius: var(--velin-radius-md);
39
+ transition: var(--velin-transition-colors);
40
+ }
41
+
42
+ .velin-nav__link:hover {
43
+ color: var(--velin-color-text);
44
+ background-color: var(--velin-color-surface-dim);
45
+ }
46
+
47
+ .velin-nav__link[aria-current="page"],
48
+ .velin-nav__link--active {
49
+ color: var(--velin-color-primary);
50
+ font-weight: var(--velin-weight-semibold);
51
+ }
52
+
53
+ /* Toggle button for mobile */
54
+ .velin-nav__toggle {
55
+ display: none;
56
+ min-block-size: 2.75rem;
57
+ min-inline-size: 2.75rem;
58
+ align-items: center;
59
+ justify-content: center;
60
+ background: none;
61
+ border: none;
62
+ color: var(--velin-color-text);
63
+ cursor: pointer;
64
+ border-radius: var(--velin-radius-md);
65
+ }
66
+
67
+ .velin-nav__toggle:hover {
68
+ background-color: var(--velin-color-surface-dim);
69
+ }
70
+
71
+ /* Mobile: stack nav vertically */
72
+ /* Aligns with --velin-bp-md (48rem) used in safe-area utilities */
73
+ @media (max-width: 47.999rem) {
74
+ .velin-nav {
75
+ flex-wrap: wrap;
76
+ }
77
+
78
+ .velin-nav__toggle {
79
+ display: inline-flex;
80
+ }
81
+
82
+ .velin-nav__list {
83
+ flex-direction: column;
84
+ align-items: stretch;
85
+ inline-size: 100%;
86
+ display: none;
87
+ }
88
+
89
+ .velin-nav__list[data-velin-open] {
90
+ display: flex;
91
+ }
92
+
93
+ .velin-nav__link {
94
+ inline-size: 100%;
95
+ }
96
+ }
97
+
98
+ /* Vertical nav variant */
99
+ .velin-nav--vertical {
100
+ flex-direction: column;
101
+ align-items: stretch;
102
+ border-block-end: none;
103
+ border-inline-end: 1px solid var(--velin-color-border);
104
+ }
105
+
106
+ .velin-nav--vertical .velin-nav__brand {
107
+ margin-inline-end: 0;
108
+ margin-block-end: var(--velin-space-4);
109
+ }
110
+
111
+ .velin-nav--vertical .velin-nav__list {
112
+ flex-direction: column;
113
+ align-items: stretch;
114
+ }
115
+
116
+ /* Mega menu */
117
+ .velin-nav__mega {
118
+ position: absolute;
119
+ inset-inline-start: 0;
120
+ inset-inline-end: 0;
121
+ inset-block-start: 100%;
122
+ z-index: var(--velin-z-dropdown);
123
+ background: var(--velin-color-surface-bright);
124
+ border: 1px solid var(--velin-color-border);
125
+ border-radius: var(--velin-radius-lg);
126
+ box-shadow: var(--velin-shadow-lg);
127
+ padding: var(--velin-space-6);
128
+ display: none;
129
+ }
130
+
131
+ .velin-nav__item:hover > .velin-nav__mega,
132
+ .velin-nav__item:focus-within > .velin-nav__mega {
133
+ display: grid;
134
+ grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
135
+ gap: var(--velin-space-6);
136
+ }
137
+
138
+ .velin-nav__mega-section {
139
+ display: flex;
140
+ flex-direction: column;
141
+ gap: var(--velin-space-2);
142
+ }
143
+
144
+ .velin-nav__mega-title {
145
+ font-size: var(--velin-text-xs);
146
+ font-weight: var(--velin-weight-bold);
147
+ text-transform: uppercase;
148
+ letter-spacing: 0.06em;
149
+ color: var(--velin-color-text-muted);
150
+ margin-block-end: var(--velin-space-2);
151
+ }
152
+ }
@@ -6,6 +6,8 @@
6
6
  --velin-duration-normal: 200ms;
7
7
  --velin-duration-slow: 300ms;
8
8
  --velin-duration-slower: 500ms;
9
+ --velin-duration-slowest: 800ms;
10
+ --velin-duration-cinematic: 1200ms;
9
11
 
10
12
  /* Easing curves */
11
13
  --velin-ease-default: cubic-bezier(0.4, 0, 0.2, 1);
@@ -13,6 +15,11 @@
13
15
  --velin-ease-out: cubic-bezier(0, 0, 0.2, 1);
14
16
  --velin-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
15
17
  --velin-ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
18
+ --velin-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1.4);
19
+ --velin-ease-elastic: cubic-bezier(0.68, -0.6, 0.32, 1.6);
20
+ --velin-ease-expo-out: cubic-bezier(0.16, 1, 0.3, 1);
21
+ --velin-ease-back-out: cubic-bezier(0.34, 1.4, 0.64, 1);
22
+ --velin-ease-snappy: cubic-bezier(0.2, 0.8, 0.2, 1);
16
23
 
17
24
  /* Common transitions */
18
25
  --velin-transition-colors: color var(--velin-duration-normal) var(--velin-ease-default),
@@ -259,5 +259,101 @@
259
259
 
260
260
  /* === Hover trigger === */
261
261
  .velin-animate-hover { animation-play-state: paused; }
262
- .velin-animate-hover:hover { animation-play-state: running; }
262
+ @media (hover: hover) and (pointer: fine) {
263
+ .velin-animate-hover:hover { animation-play-state: running; }
264
+ }
265
+
266
+ /* === 0.7.5 Animations === */
267
+ .velin-animate-blur-in {
268
+ animation: velin-blur-in var(--velin-duration-normal) var(--velin-ease-out) both;
269
+ }
270
+ .velin-animate-blur-out {
271
+ animation: velin-blur-out var(--velin-duration-normal) var(--velin-ease-in) both;
272
+ }
273
+ .velin-animate-rotate-in {
274
+ animation: velin-rotate-in var(--velin-duration-normal) var(--velin-ease-out) both;
275
+ }
276
+ .velin-animate-float {
277
+ animation: velin-float 3s var(--velin-ease-in-out) infinite;
278
+ }
279
+ .velin-animate-flash {
280
+ animation: velin-flash 0.6s var(--velin-ease-default) both;
281
+ }
282
+ .velin-animate-reveal-up {
283
+ animation: velin-reveal-up var(--velin-duration-normal) var(--velin-ease-out) both;
284
+ }
285
+ .velin-animate-reveal-down {
286
+ animation: velin-reveal-down var(--velin-duration-normal) var(--velin-ease-out) both;
287
+ }
288
+ .velin-animate-glow {
289
+ animation: velin-glow 2s var(--velin-ease-in-out) infinite;
290
+ }
291
+ .velin-animate-slide-fade-left {
292
+ animation: velin-slide-fade-left var(--velin-duration-normal) var(--velin-ease-out) both;
293
+ }
294
+ .velin-animate-slide-fade-right {
295
+ animation: velin-slide-fade-right var(--velin-duration-normal) var(--velin-ease-out) both;
296
+ }
297
+
298
+ @keyframes velin-blur-in {
299
+ from { opacity: 0; filter: blur(8px); }
300
+ to { opacity: 1; filter: blur(0); }
301
+ }
302
+ @keyframes velin-blur-out {
303
+ from { opacity: 1; filter: blur(0); }
304
+ to { opacity: 0; filter: blur(8px); }
305
+ }
306
+ @keyframes velin-rotate-in {
307
+ from { opacity: 0; transform: rotate(-8deg) scale(0.95); }
308
+ to { opacity: 1; transform: rotate(0) scale(1); }
309
+ }
310
+ @keyframes velin-float {
311
+ 0%, 100% { transform: translateY(0); }
312
+ 50% { transform: translateY(-0.35rem); }
313
+ }
314
+ @keyframes velin-flash {
315
+ 0%, 50%, 100% { opacity: 1; }
316
+ 25%, 75% { opacity: 0.35; }
317
+ }
318
+ @keyframes velin-reveal-up {
319
+ from { clip-path: inset(100% 0 0 0); opacity: 0; }
320
+ to { clip-path: inset(0 0 0 0); opacity: 1; }
321
+ }
322
+ @keyframes velin-reveal-down {
323
+ from { clip-path: inset(0 0 100% 0); opacity: 0; }
324
+ to { clip-path: inset(0 0 0 0); opacity: 1; }
325
+ }
326
+ @keyframes velin-glow {
327
+ 0%, 100% { box-shadow: 0 0 0 0 transparent; }
328
+ 50% { box-shadow: 0 0 1rem 0 color-mix(in oklch, var(--velin-color-primary, #2563eb) 35%, transparent); }
329
+ }
330
+ @keyframes velin-slide-fade-left {
331
+ from { opacity: 0; transform: translateX(1rem); }
332
+ to { opacity: 1; transform: translateX(0); }
333
+ }
334
+ @keyframes velin-slide-fade-right {
335
+ from { opacity: 0; transform: translateX(-1rem); }
336
+ to { opacity: 1; transform: translateX(0); }
337
+ }
338
+
339
+ @media (prefers-reduced-motion: reduce) {
340
+ .velin-animate-blur-in,
341
+ .velin-animate-blur-out,
342
+ .velin-animate-rotate-in,
343
+ .velin-animate-reveal-up,
344
+ .velin-animate-reveal-down,
345
+ .velin-animate-slide-fade-left,
346
+ .velin-animate-slide-fade-right {
347
+ animation: none;
348
+ opacity: 1;
349
+ filter: none;
350
+ transform: none;
351
+ clip-path: none;
352
+ }
353
+ .velin-animate-float,
354
+ .velin-animate-glow,
355
+ .velin-animate-flash {
356
+ animation: none;
357
+ }
358
+ }
263
359
  }
@@ -0,0 +1,101 @@
1
+ /*
2
+ * Chart animation utilities — keyframes and helpers for sparkline draw-in,
3
+ * value bumps, live pulses, and count-up fades. Consumed by the
4
+ * <velin-sparkline>, <velin-counter>, <velin-live-dot> Web Components plus
5
+ * any plain SVG/HTML that wants a "live" feel without a chart library.
6
+ *
7
+ * Each animated class is gated by `prefers-reduced-motion: reduce` so the
8
+ * baseline experience stays calm. CSS custom properties exposed so authors
9
+ * can re-skin without forking selectors.
10
+ */
11
+
12
+ @layer utilities {
13
+ /* === Keyframes === */
14
+ @keyframes velin-stroke-draw {
15
+ from { stroke-dashoffset: var(--velin-chart-len, 1000); }
16
+ to { stroke-dashoffset: 0; }
17
+ }
18
+
19
+ @keyframes velin-sparkline-glow {
20
+ 0%, 100% {
21
+ filter: drop-shadow(0 0 0 transparent);
22
+ }
23
+ 50% {
24
+ filter: drop-shadow(0 0 0.5rem color-mix(in oklch, var(--velin-chart-glow-color, var(--velin-color-primary, #2563eb)) 50%, transparent));
25
+ }
26
+ }
27
+
28
+ @keyframes velin-value-bump {
29
+ 0% { transform: scale(1); }
30
+ 35% { transform: scale(1.06); color: var(--velin-chart-bump-color, var(--velin-color-primary, #2563eb)); }
31
+ 100% { transform: scale(1); }
32
+ }
33
+
34
+ @keyframes velin-live-pulse {
35
+ 0% {
36
+ box-shadow: 0 0 0 0 color-mix(in oklch, var(--velin-live-color, var(--velin-color-success, oklch(60% 0.16 145))) 65%, transparent);
37
+ }
38
+ 70% {
39
+ box-shadow: 0 0 0 0.65rem color-mix(in oklch, var(--velin-live-color, var(--velin-color-success, oklch(60% 0.16 145))) 0%, transparent);
40
+ }
41
+ 100% {
42
+ box-shadow: 0 0 0 0 transparent;
43
+ }
44
+ }
45
+
46
+ @keyframes velin-count-fade {
47
+ from { opacity: 0; transform: translateY(0.4rem); }
48
+ to { opacity: 1; transform: translateY(0); }
49
+ }
50
+
51
+ /* === Utility classes === */
52
+ .velin-chart-line {
53
+ fill: none;
54
+ stroke: currentColor;
55
+ stroke-width: 2;
56
+ stroke-linecap: round;
57
+ stroke-linejoin: round;
58
+ stroke-dasharray: var(--velin-chart-len, 1000);
59
+ stroke-dashoffset: 0;
60
+ animation: velin-stroke-draw var(--velin-duration-cinematic, 1200ms) var(--velin-ease-expo-out, ease-out) both;
61
+ }
62
+
63
+ .velin-chart-area {
64
+ fill: var(--velin-chart-area-fill, currentColor);
65
+ opacity: 0;
66
+ animation: velin-count-fade var(--velin-duration-slowest, 800ms) var(--velin-ease-expo-out, ease-out) 200ms forwards;
67
+ }
68
+
69
+ .velin-chart-glow {
70
+ animation: velin-sparkline-glow 2.4s var(--velin-ease-in-out, ease-in-out) infinite;
71
+ }
72
+
73
+ .velin-spark-tick {
74
+ animation: velin-value-bump var(--velin-duration-slow, 300ms) var(--velin-ease-spring, ease-out) both;
75
+ }
76
+
77
+ .velin-live-pulse {
78
+ animation: velin-live-pulse 1.8s var(--velin-ease-out, ease-out) infinite;
79
+ border-radius: 50%;
80
+ }
81
+
82
+ .velin-count-fade {
83
+ animation: velin-count-fade var(--velin-duration-slow, 300ms) var(--velin-ease-expo-out, ease-out) both;
84
+ }
85
+
86
+ @media (prefers-reduced-motion: reduce) {
87
+ .velin-chart-line,
88
+ .velin-chart-area,
89
+ .velin-chart-glow,
90
+ .velin-spark-tick,
91
+ .velin-live-pulse,
92
+ .velin-count-fade {
93
+ animation: none;
94
+ stroke-dashoffset: 0;
95
+ opacity: 1;
96
+ transform: none;
97
+ filter: none;
98
+ box-shadow: none;
99
+ }
100
+ }
101
+ }
@@ -0,0 +1,103 @@
1
+ /*
2
+ * Filter-Effect utilities — saturate, hue-rotate, contrast, invert, sepia,
3
+ * drop-shadow plus extended backdrop and glass composites.
4
+ *
5
+ * Layered under `utilities`; pair with the existing blur/brightness/grayscale
6
+ * helpers from src/utilities/position.css. The reduced-motion block at the
7
+ * bottom strips saturation/hue rotation because those distort color contrast
8
+ * for users who request reduced motion as a proxy for visual calm.
9
+ */
10
+
11
+ @layer utilities {
12
+ /* === Saturate === */
13
+ .velin-saturate-0 { filter: saturate(0); }
14
+ .velin-saturate-50 { filter: saturate(0.5); }
15
+ .velin-saturate-100 { filter: saturate(1); }
16
+ .velin-saturate-150 { filter: saturate(1.5); }
17
+ .velin-saturate-200 { filter: saturate(2); }
18
+
19
+ /* === Hue rotate === */
20
+ .velin-hue-rotate-15 { filter: hue-rotate(15deg); }
21
+ .velin-hue-rotate-30 { filter: hue-rotate(30deg); }
22
+ .velin-hue-rotate-60 { filter: hue-rotate(60deg); }
23
+ .velin-hue-rotate-90 { filter: hue-rotate(90deg); }
24
+ .velin-hue-rotate-180 { filter: hue-rotate(180deg); }
25
+
26
+ /* === Contrast === */
27
+ .velin-contrast-50 { filter: contrast(0.5); }
28
+ .velin-contrast-75 { filter: contrast(0.75); }
29
+ .velin-contrast-100 { filter: contrast(1); }
30
+ .velin-contrast-125 { filter: contrast(1.25); }
31
+ .velin-contrast-150 { filter: contrast(1.5); }
32
+
33
+ /* === Invert === */
34
+ .velin-invert { filter: invert(1); }
35
+ .velin-invert-0 { filter: invert(0); }
36
+
37
+ /* === Sepia === */
38
+ .velin-sepia { filter: sepia(1); }
39
+ .velin-sepia-50 { filter: sepia(0.5); }
40
+ .velin-sepia-0 { filter: sepia(0); }
41
+
42
+ /* === Drop shadow (paints under transparent SVG/PNG content; differs from box-shadow) === */
43
+ .velin-drop-shadow-sm {
44
+ filter: drop-shadow(0 1px 1px oklch(0% 0 0 / 0.18));
45
+ }
46
+ .velin-drop-shadow-md {
47
+ filter: drop-shadow(0 4px 6px oklch(0% 0 0 / 0.18));
48
+ }
49
+ .velin-drop-shadow-lg {
50
+ filter: drop-shadow(0 12px 24px oklch(0% 0 0 / 0.22));
51
+ }
52
+ .velin-drop-shadow-glow {
53
+ filter: drop-shadow(0 0 1.5rem color-mix(in oklch, var(--velin-color-primary, #2563eb) 60%, transparent));
54
+ }
55
+
56
+ /* === Extended backdrop helpers === */
57
+ .velin-backdrop-saturate {
58
+ backdrop-filter: saturate(180%);
59
+ -webkit-backdrop-filter: saturate(180%);
60
+ }
61
+ .velin-backdrop-brightness-90 {
62
+ backdrop-filter: brightness(0.9);
63
+ -webkit-backdrop-filter: brightness(0.9);
64
+ }
65
+ .velin-backdrop-brightness-110 {
66
+ backdrop-filter: brightness(1.1);
67
+ -webkit-backdrop-filter: brightness(1.1);
68
+ }
69
+ .velin-backdrop-grayscale {
70
+ backdrop-filter: grayscale(1);
71
+ -webkit-backdrop-filter: grayscale(1);
72
+ }
73
+
74
+ /* === Glass composites — frosted surfaces for sticky bars/cards === */
75
+ .velin-glass {
76
+ background: color-mix(in oklch, var(--velin-color-surface-bright, #fff) 70%, transparent);
77
+ backdrop-filter: blur(12px) saturate(160%);
78
+ -webkit-backdrop-filter: blur(12px) saturate(160%);
79
+ border: 1px solid color-mix(in oklch, var(--velin-color-border, #ddd) 60%, transparent);
80
+ }
81
+ .velin-glass-strong {
82
+ background: color-mix(in oklch, var(--velin-color-surface-bright, #fff) 55%, transparent);
83
+ backdrop-filter: blur(20px) saturate(180%);
84
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
85
+ border: 1px solid color-mix(in oklch, var(--velin-color-border, #ddd) 70%, transparent);
86
+ }
87
+
88
+ /* Reduced-motion / reduced-transparency: strip color distortion. */
89
+ @media (prefers-reduced-motion: reduce) {
90
+ .velin-saturate-0,
91
+ .velin-saturate-50,
92
+ .velin-saturate-100,
93
+ .velin-saturate-150,
94
+ .velin-saturate-200,
95
+ .velin-hue-rotate-15,
96
+ .velin-hue-rotate-30,
97
+ .velin-hue-rotate-60,
98
+ .velin-hue-rotate-90,
99
+ .velin-hue-rotate-180 {
100
+ filter: none;
101
+ }
102
+ }
103
+ }
@@ -0,0 +1,39 @@
1
+ @layer utilities {
2
+ .velin-pt-safe {
3
+ padding-block-start: max(var(--velin-space-4, 1rem), env(safe-area-inset-top, 0px));
4
+ }
5
+
6
+ .velin-pb-safe {
7
+ padding-block-end: max(var(--velin-space-4, 1rem), env(safe-area-inset-bottom, 0px));
8
+ }
9
+
10
+ .velin-px-safe {
11
+ padding-inline: max(var(--velin-space-4, 1rem), env(safe-area-inset-left, 0px))
12
+ max(var(--velin-space-4, 1rem), env(safe-area-inset-right, 0px));
13
+ }
14
+
15
+ .velin-p-safe {
16
+ padding-block: max(var(--velin-space-4, 1rem), env(safe-area-inset-top, 0px))
17
+ max(var(--velin-space-4, 1rem), env(safe-area-inset-bottom, 0px));
18
+ padding-inline: max(var(--velin-space-4, 1rem), env(safe-area-inset-left, 0px))
19
+ max(var(--velin-space-4, 1rem), env(safe-area-inset-right, 0px));
20
+ }
21
+
22
+ .velin-mobile-only {
23
+ display: block;
24
+ }
25
+
26
+ .velin-desktop-only {
27
+ display: none;
28
+ }
29
+
30
+ @media (min-width: 48rem) {
31
+ .velin-mobile-only {
32
+ display: none;
33
+ }
34
+
35
+ .velin-desktop-only {
36
+ display: block;
37
+ }
38
+ }
39
+ }