@webority/theme 0.7.3 → 0.7.5
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.
- package/README.md +108 -6
- package/dist/webority-theme.css +1 -1
- package/package.json +1 -1
- package/scss/_components.scss +158 -547
- package/scss/_marketing.scss +337 -0
- package/scss/_tokens.scss +119 -108
- package/scss/_variables.scss +126 -132
- package/scss/_wui-aliases.scss +103 -102
- package/scss/wui/_choice.scss +4 -4
- package/scss/wui/_controls.scss +167 -251
- package/scss/wui/_data.scss +39 -0
- package/scss/wui/_display.scss +88 -0
- package/scss/wui/_navigation.scss +27 -0
- package/scss/wui/_overlays.scss +2 -2
- package/scss/wui/_scrollbar.scss +13 -11
- package/scss/wui/_shell.scss +3 -3
- package/scss/wui/_utilities.scss +139 -1
package/scss/_components.scss
CHANGED
|
@@ -1,70 +1,8 @@
|
|
|
1
1
|
// =====================================================================
|
|
2
|
-
// Webority UI — shared
|
|
3
|
-
//
|
|
4
|
-
// on the Razor marketing site AND the React customer portal lives here.
|
|
5
|
-
// Per-project styling (page layouts, route-specific tweaks) stays in
|
|
6
|
-
// the consuming project, NOT in this shared theme.
|
|
2
|
+
// Webority UI — shared portal / control CSS (AFTER Bootstrap).
|
|
3
|
+
// Marketing section/hero/motion helpers: scss/_marketing.scss (opt-in).
|
|
7
4
|
// =====================================================================
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
// ---- Section helpers ------------------------------------------------
|
|
11
|
-
// Marketing + portal both use vertically-padded sections with the
|
|
12
|
-
// brand's spacing rhythm. Bootstrap's py-* gets you part-way; these
|
|
13
|
-
// are the named variants the design system reaches for.
|
|
14
|
-
.section-y-sm { padding-block: 3rem; }
|
|
15
|
-
.section-y { padding-block: 5rem; }
|
|
16
|
-
.section-y-lg { padding-block: 7rem; }
|
|
17
|
-
@media (min-width: 768px) {
|
|
18
|
-
.section-y-sm { padding-block: 4rem; }
|
|
19
|
-
.section-y { padding-block: 6rem; }
|
|
20
|
-
.section-y-lg { padding-block: 9rem; }
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// ---- Eyebrow (accent-dot + label) -----------------------------------
|
|
25
|
-
.eyebrow {
|
|
26
|
-
display: inline-flex;
|
|
27
|
-
align-items: center;
|
|
28
|
-
gap: 0.5rem;
|
|
29
|
-
font-size: 14px;
|
|
30
|
-
font-weight: 700;
|
|
31
|
-
line-height: 1;
|
|
32
|
-
color: var(--wui-primary);
|
|
33
|
-
}
|
|
34
|
-
.eyebrow::before {
|
|
35
|
-
content: "";
|
|
36
|
-
width: 0.5rem;
|
|
37
|
-
height: 0.5rem;
|
|
38
|
-
border-radius: 9999px;
|
|
39
|
-
background: var(--wui-primary);
|
|
40
|
-
}
|
|
41
|
-
.eyebrow--on-dark {
|
|
42
|
-
color: rgba(255, 255, 255, 0.85);
|
|
43
|
-
}
|
|
44
|
-
.eyebrow--on-dark::before {
|
|
45
|
-
background: #FFFFFF;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// ---- Display heading -------------------------------------------------
|
|
50
|
-
// Bootstrap's .display-1..6 ladder skews bigger than our locked design.
|
|
51
|
-
// .h-display drops in where the marketing hero needs the locked
|
|
52
|
-
// font-weight 500 + tight letter-spacing. font-family reads --wui-font-display
|
|
53
|
-
// so a product can swap in a serif/brand display face by overriding just that
|
|
54
|
-
// token (default: the same body stack .font-display already uses).
|
|
55
|
-
.h-display {
|
|
56
|
-
font-family: var(--wui-font-display, var(--bs-body-font-family));
|
|
57
|
-
font-weight: 500;
|
|
58
|
-
letter-spacing: -0.03em;
|
|
59
|
-
line-height: 1.05;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// ---- Gradient backgrounds -------------------------------------------
|
|
64
|
-
.bg-hero-gradient { background: var(--wui-hero-gradient); }
|
|
65
|
-
.bg-cta-gradient { background: var(--wui-cta-gradient); }
|
|
66
|
-
|
|
67
|
-
|
|
68
6
|
// ---- Button polish (used by both Razor + React) ---------------------
|
|
69
7
|
// Flat buttons — no drop shadow. Active-press feedback (1px nudge) is
|
|
70
8
|
// retained because it's a tactile signal, not a depth cue.
|
|
@@ -88,18 +26,18 @@
|
|
|
88
26
|
.btn-primary {
|
|
89
27
|
// Pin hover/active to the pressed-primary token so a rebrand tracks
|
|
90
28
|
// automatically instead of Bootstrap auto-shading $primary off-spec.
|
|
91
|
-
--bs-btn-hover-bg:
|
|
92
|
-
--bs-btn-hover-border-color:
|
|
93
|
-
--bs-btn-active-bg:
|
|
29
|
+
--bs-btn-hover-bg: var(--wui-primary-pressed);
|
|
30
|
+
--bs-btn-hover-border-color: var(--wui-primary-pressed);
|
|
31
|
+
--bs-btn-active-bg: var(--wui-primary-pressed);
|
|
94
32
|
--bs-btn-active-border-color: var(--wui-primary-pressed);
|
|
95
33
|
// Bootstrap computes this label colour at SCSS compile time from the library's
|
|
96
34
|
// default $primary, so it can never react to a product overriding --wui-primary as a
|
|
97
35
|
// runtime CSS var. Routing it through the token makes a light-primary brand's
|
|
98
36
|
// --wui-primary-text override reach the button.
|
|
99
|
-
--bs-btn-color:
|
|
100
|
-
--bs-btn-hover-color:
|
|
101
|
-
--bs-btn-active-color:
|
|
102
|
-
--bs-btn-disabled-color:
|
|
37
|
+
--bs-btn-color: var(--wui-primary-text);
|
|
38
|
+
--bs-btn-hover-color: var(--wui-primary-text);
|
|
39
|
+
--bs-btn-active-color: var(--wui-primary-text);
|
|
40
|
+
--bs-btn-disabled-color: var(--wui-primary-text);
|
|
103
41
|
}
|
|
104
42
|
|
|
105
43
|
.btn-danger {
|
|
@@ -107,17 +45,17 @@
|
|
|
107
45
|
// brighter danger fills (#F8285A → 3.86:1, #E82646 → 4.38:1), so the button uses the
|
|
108
46
|
// deepened fill (5.80:1) that carries white cleanly. Token-driven so a rebrand tracks it;
|
|
109
47
|
// the brighter --wui-danger stays for badges/dots that never carry white text.
|
|
110
|
-
--bs-btn-color:
|
|
111
|
-
--bs-btn-bg:
|
|
112
|
-
--bs-btn-border-color:
|
|
113
|
-
--bs-btn-hover-color:
|
|
114
|
-
--bs-btn-hover-bg:
|
|
115
|
-
--bs-btn-hover-border-color:
|
|
116
|
-
--bs-btn-active-color:
|
|
117
|
-
--bs-btn-active-bg:
|
|
118
|
-
--bs-btn-active-border-color:
|
|
119
|
-
--bs-btn-disabled-color:
|
|
120
|
-
--bs-btn-disabled-bg:
|
|
48
|
+
--bs-btn-color: #fff;
|
|
49
|
+
--bs-btn-bg: var(--wui-danger-strong);
|
|
50
|
+
--bs-btn-border-color: var(--wui-danger-strong);
|
|
51
|
+
--bs-btn-hover-color: #fff;
|
|
52
|
+
--bs-btn-hover-bg: var(--wui-danger-strong-hover);
|
|
53
|
+
--bs-btn-hover-border-color: var(--wui-danger-strong-hover);
|
|
54
|
+
--bs-btn-active-color: #fff;
|
|
55
|
+
--bs-btn-active-bg: var(--wui-danger-strong-hover);
|
|
56
|
+
--bs-btn-active-border-color: var(--wui-danger-strong-hover);
|
|
57
|
+
--bs-btn-disabled-color: #fff;
|
|
58
|
+
--bs-btn-disabled-bg: var(--wui-danger-strong);
|
|
121
59
|
--bs-btn-disabled-border-color: var(--wui-danger-strong);
|
|
122
60
|
}
|
|
123
61
|
|
|
@@ -126,34 +64,65 @@
|
|
|
126
64
|
// on a transparent fill; hover fills with the brand and flips the label to the on-brand text
|
|
127
65
|
// token; active deepens to the pressed fill.
|
|
128
66
|
.btn-outline-primary {
|
|
129
|
-
--bs-btn-color:
|
|
130
|
-
--bs-btn-border-color:
|
|
131
|
-
--bs-btn-hover-color:
|
|
132
|
-
--bs-btn-hover-bg:
|
|
133
|
-
--bs-btn-hover-border-color:
|
|
134
|
-
--bs-btn-active-color:
|
|
135
|
-
--bs-btn-active-bg:
|
|
136
|
-
--bs-btn-active-border-color:
|
|
137
|
-
--bs-btn-disabled-color:
|
|
67
|
+
--bs-btn-color: var(--color-primary);
|
|
68
|
+
--bs-btn-border-color: var(--color-primary);
|
|
69
|
+
--bs-btn-hover-color: var(--wui-primary-text);
|
|
70
|
+
--bs-btn-hover-bg: var(--color-primary);
|
|
71
|
+
--bs-btn-hover-border-color: var(--color-primary);
|
|
72
|
+
--bs-btn-active-color: var(--wui-primary-text);
|
|
73
|
+
--bs-btn-active-bg: var(--wui-primary-pressed);
|
|
74
|
+
--bs-btn-active-border-color: var(--wui-primary-pressed);
|
|
75
|
+
--bs-btn-disabled-color: var(--color-primary);
|
|
138
76
|
--bs-btn-disabled-border-color: var(--color-primary);
|
|
139
77
|
}
|
|
140
78
|
|
|
141
79
|
// Outline danger uses the deepened --wui-danger-strong (5.80:1, carries white) that solid
|
|
142
80
|
// .btn-danger uses, so an outline-danger hover reads identically to solid danger.
|
|
143
81
|
.btn-outline-danger {
|
|
144
|
-
--bs-btn-color:
|
|
145
|
-
--bs-btn-border-color:
|
|
146
|
-
--bs-btn-hover-color:
|
|
147
|
-
--bs-btn-hover-bg:
|
|
148
|
-
--bs-btn-hover-border-color:
|
|
149
|
-
--bs-btn-active-color:
|
|
150
|
-
--bs-btn-active-bg:
|
|
151
|
-
--bs-btn-active-border-color:
|
|
152
|
-
--bs-btn-disabled-color:
|
|
82
|
+
--bs-btn-color: var(--wui-danger-strong);
|
|
83
|
+
--bs-btn-border-color: var(--wui-danger-strong);
|
|
84
|
+
--bs-btn-hover-color: #fff;
|
|
85
|
+
--bs-btn-hover-bg: var(--wui-danger-strong);
|
|
86
|
+
--bs-btn-hover-border-color: var(--wui-danger-strong);
|
|
87
|
+
--bs-btn-active-color: #fff;
|
|
88
|
+
--bs-btn-active-bg: var(--wui-danger-strong-hover);
|
|
89
|
+
--bs-btn-active-border-color: var(--wui-danger-strong-hover);
|
|
90
|
+
--bs-btn-disabled-color: var(--wui-danger-strong);
|
|
153
91
|
--bs-btn-disabled-border-color: var(--wui-danger-strong);
|
|
154
92
|
}
|
|
155
93
|
|
|
156
|
-
//
|
|
94
|
+
// Soft neutral tinted button — a quieter alternative to .btn-light for a secondary
|
|
95
|
+
// action that shouldn't compete with a primary/danger CTA in the same row.
|
|
96
|
+
.btn-subtle {
|
|
97
|
+
--bs-btn-bg: var(--color-bg-muted);
|
|
98
|
+
--bs-btn-color: var(--color-text);
|
|
99
|
+
--bs-btn-border-color: transparent;
|
|
100
|
+
--bs-btn-hover-bg: var(--color-border);
|
|
101
|
+
--bs-btn-hover-color: var(--color-text);
|
|
102
|
+
--bs-btn-hover-border-color: transparent;
|
|
103
|
+
--bs-btn-active-bg: var(--color-border-strong);
|
|
104
|
+
--bs-btn-active-color: var(--color-text);
|
|
105
|
+
--bs-btn-active-border-color: transparent;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Light outline for a dark banner/hero CTA, where .btn-outline-primary's brand-coloured
|
|
109
|
+
// border/text would disappear against a dark background. White is used directly (not a
|
|
110
|
+
// token) because it's the one colour that reads on any dark surface, brand-independent.
|
|
111
|
+
.btn-outline-light {
|
|
112
|
+
--bs-btn-color: #fff;
|
|
113
|
+
--bs-btn-bg: transparent;
|
|
114
|
+
--bs-btn-border-color: rgba(255, 255, 255, 0.6);
|
|
115
|
+
--bs-btn-hover-color: #fff;
|
|
116
|
+
--bs-btn-hover-bg: rgba(255, 255, 255, 0.15);
|
|
117
|
+
--bs-btn-hover-border-color: #fff;
|
|
118
|
+
--bs-btn-active-color: #fff;
|
|
119
|
+
--bs-btn-active-bg: rgba(255, 255, 255, 0.25);
|
|
120
|
+
--bs-btn-active-border-color: #fff;
|
|
121
|
+
--bs-btn-disabled-color: rgba(255, 255, 255, 0.5);
|
|
122
|
+
--bs-btn-disabled-border-color: rgba(255, 255, 255, 0.3);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Themed disabled state (default). Bootstrap's own disabled look is just the
|
|
157
126
|
// variant's colour at --bs-btn-disabled-opacity (.65) — barely different from the active
|
|
158
127
|
// button on a tinted brand palette. Give the ordinary case a real desaturated/muted look
|
|
159
128
|
// instead, reusing the same tokens .input:disabled already uses so a disabled button and
|
|
@@ -171,7 +140,6 @@
|
|
|
171
140
|
cursor: not-allowed;
|
|
172
141
|
}
|
|
173
142
|
|
|
174
|
-
|
|
175
143
|
// ---- Component radii from tokens ------------------------------------
|
|
176
144
|
// .btn/.card/.input capture Bootstrap's compiled $*-border-radius (the library
|
|
177
145
|
// ladder) before a product loads, so a product's --wui-radius* override never
|
|
@@ -180,9 +148,8 @@
|
|
|
180
148
|
// sm/lg are re-asserted so they keep their own rung instead of the base value.
|
|
181
149
|
.btn {
|
|
182
150
|
--bs-btn-border-radius: var(--wui-radius);
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
// fall back to the plain border-radius arc — no breakage.
|
|
151
|
+
// Progressive enhancement only (Chrome/Safari recent). Unsupported engines
|
|
152
|
+
// keep a plain border-radius arc — not a cross-browser design contract.
|
|
186
153
|
corner-shape: squircle;
|
|
187
154
|
}
|
|
188
155
|
.btn-sm,
|
|
@@ -194,7 +161,6 @@
|
|
|
194
161
|
--bs-btn-border-radius: var(--wui-radius-lg);
|
|
195
162
|
}
|
|
196
163
|
|
|
197
|
-
|
|
198
164
|
// ---- Dropdown menu (AppDropdown / <app-dropdown>) -------------------
|
|
199
165
|
// Bootstrap ships a bare .dropdown-item; the design system gives the menu real
|
|
200
166
|
// padding, an icon–label gap, a rounded hover highlight tinted with the brand,
|
|
@@ -203,19 +169,19 @@
|
|
|
203
169
|
// Elevated surface (like .card), not Bootstrap's default page --bs-body-bg: otherwise the
|
|
204
170
|
// menu blends into a tinted page and, in dark mode, resolves to the page colour instead of
|
|
205
171
|
// the elevated surface.
|
|
206
|
-
--bs-dropdown-bg:
|
|
207
|
-
--bs-dropdown-min-width:
|
|
208
|
-
--bs-dropdown-padding-x:
|
|
209
|
-
--bs-dropdown-padding-y:
|
|
210
|
-
--bs-dropdown-item-padding-y:
|
|
211
|
-
--bs-dropdown-item-padding-x:
|
|
212
|
-
--bs-dropdown-border-color:
|
|
213
|
-
--bs-dropdown-border-radius:
|
|
214
|
-
--bs-dropdown-link-color:
|
|
215
|
-
--bs-dropdown-link-hover-color:
|
|
216
|
-
--bs-dropdown-link-hover-bg:
|
|
172
|
+
--bs-dropdown-bg: var(--color-surface);
|
|
173
|
+
--bs-dropdown-min-width: 12rem;
|
|
174
|
+
--bs-dropdown-padding-x: 0.375rem;
|
|
175
|
+
--bs-dropdown-padding-y: 0.375rem;
|
|
176
|
+
--bs-dropdown-item-padding-y: 0.5rem;
|
|
177
|
+
--bs-dropdown-item-padding-x: 0.625rem;
|
|
178
|
+
--bs-dropdown-border-color: var(--wui-border-light);
|
|
179
|
+
--bs-dropdown-border-radius: var(--wui-radius-lg);
|
|
180
|
+
--bs-dropdown-link-color: var(--wui-text-1);
|
|
181
|
+
--bs-dropdown-link-hover-color: var(--wui-primary-pressed);
|
|
182
|
+
--bs-dropdown-link-hover-bg: var(--wui-primary-5);
|
|
217
183
|
--bs-dropdown-link-active-color: var(--wui-primary-pressed);
|
|
218
|
-
--bs-dropdown-link-active-bg:
|
|
184
|
+
--bs-dropdown-link-active-bg: var(--wui-primary-10);
|
|
219
185
|
box-shadow: var(--wui-shadow-overlay);
|
|
220
186
|
}
|
|
221
187
|
|
|
@@ -253,87 +219,15 @@
|
|
|
253
219
|
|
|
254
220
|
// Destructive row action — red label, red glyph, red-tinted hover.
|
|
255
221
|
&.danger {
|
|
256
|
-
--bs-dropdown-link-color:
|
|
222
|
+
--bs-dropdown-link-color: var(--wui-danger-text);
|
|
257
223
|
--bs-dropdown-link-hover-color: var(--wui-danger-text);
|
|
258
|
-
--bs-dropdown-link-hover-bg:
|
|
224
|
+
--bs-dropdown-link-hover-bg: var(--wui-danger-10);
|
|
259
225
|
.bi, i, svg { color: var(--wui-danger); }
|
|
260
226
|
&:hover .bi, &:hover i, &:hover svg,
|
|
261
227
|
&:focus .bi, &:focus i, &:focus svg { color: var(--wui-danger); }
|
|
262
228
|
}
|
|
263
229
|
}
|
|
264
230
|
|
|
265
|
-
|
|
266
|
-
// ---- Marketing pill buttons (locked design parity) -------------------
|
|
267
|
-
// Pixel-locked to the standalone reference's `btn-squircle inline-flex
|
|
268
|
-
// h-12 items-center text-sm font-medium` pattern. Each marketing CTA
|
|
269
|
-
// layers .btn-pill on a Bootstrap variant — these classes only override
|
|
270
|
-
// Bootstrap's btn CSS vars (height / padding / radius / font), so
|
|
271
|
-
// every variant inherits the right size without redefining colors.
|
|
272
|
-
//
|
|
273
|
-
// Reference values:
|
|
274
|
-
// - height : 48px (h-12 = 3rem)
|
|
275
|
-
// - radius : 16px (--wui-radius-xl)
|
|
276
|
-
// - font : 14px / 500 (text-sm font-medium)
|
|
277
|
-
// - padding-x : 20px (px-5) default — Watch demo / outline-dark
|
|
278
|
-
// Primary CTA overrides to 24px (px-6) inline
|
|
279
|
-
// - corner-shape : squircle (iOS-style smooth curve)
|
|
280
|
-
//
|
|
281
|
-
// Example:
|
|
282
|
-
// <a class="btn btn-primary btn-pill" style="--bs-btn-padding-x:1.5rem;">…</a>
|
|
283
|
-
// <a class="btn btn-light btn-pill-sm">Learn more</a>
|
|
284
|
-
.btn-pill {
|
|
285
|
-
--bs-btn-padding-y: 0; // height driven by min-height + flex centering
|
|
286
|
-
--bs-btn-padding-x: 1.25rem; // 20px (px-5)
|
|
287
|
-
--bs-btn-font-size: 0.875rem; // 14px
|
|
288
|
-
--bs-btn-font-weight: 500;
|
|
289
|
-
--bs-btn-line-height: 1.25;
|
|
290
|
-
--bs-btn-border-radius: var(--wui-radius-xl); // 16px
|
|
291
|
-
min-height: 3rem; // 48px (h-12)
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
display: inline-flex;
|
|
295
|
-
align-items: center;
|
|
296
|
-
gap: 0.375rem; // 6px — matches primary arrow's ml-1.5
|
|
297
|
-
}
|
|
298
|
-
.btn-pill-sm {
|
|
299
|
-
--bs-btn-padding-y: 0;
|
|
300
|
-
--bs-btn-padding-x: 0.875rem; // 14px (px-3.5)
|
|
301
|
-
--bs-btn-font-size: 0.875rem; // 14px (text-sm)
|
|
302
|
-
--bs-btn-font-weight: 500;
|
|
303
|
-
--bs-btn-line-height: 1.25;
|
|
304
|
-
--bs-btn-border-radius: 12px;
|
|
305
|
-
min-height: 2.5rem; // 40px (h-10) — reference "Learn more" CTA
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
display: inline-flex;
|
|
309
|
-
align-items: center;
|
|
310
|
-
gap: 0.375rem;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
// ---- Ghost-dark button (frosted glass on light surface) -------------
|
|
315
|
-
// Pixel-locked to the reference's `WatchDemoButton` "outline-dark" variant
|
|
316
|
-
// (src/components/marketing/brand/watch-demo-button.tsx):
|
|
317
|
-
// - border : rgba(13, 24, 43, 0.15) (foreground/15 in marketing-root)
|
|
318
|
-
// - bg : rgba(255, 255, 255, 0.6) (surface/60)
|
|
319
|
-
// - text : --wui-text-heading (text-foreground in marketing-root)
|
|
320
|
-
// - blur : 4px (backdrop-blur-sm)
|
|
321
|
-
// - hover bg: #ffffff (hover:bg-surface = full opacity)
|
|
322
|
-
.btn-ghost-dark {
|
|
323
|
-
--bs-btn-color: var(--wui-text-heading);
|
|
324
|
-
--bs-btn-bg: rgba(255, 255, 255, 0.60);
|
|
325
|
-
--bs-btn-border-color: rgba(13, 24, 43, 0.15);
|
|
326
|
-
--bs-btn-hover-color: var(--wui-text-heading);
|
|
327
|
-
--bs-btn-hover-bg: #ffffff;
|
|
328
|
-
--bs-btn-hover-border-color: rgba(13, 24, 43, 0.20);
|
|
329
|
-
--bs-btn-active-color: var(--wui-text-heading);
|
|
330
|
-
--bs-btn-active-bg: #ffffff;
|
|
331
|
-
--bs-btn-active-border-color: rgba(13, 24, 43, 0.20);
|
|
332
|
-
backdrop-filter: blur(4px);
|
|
333
|
-
-webkit-backdrop-filter: blur(4px);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
|
|
337
231
|
// ---- Ghost button (transparent, brand-tinted hover) ------------------
|
|
338
232
|
// A real ghost button: transparent at rest (no fill, no border), current text colour;
|
|
339
233
|
// only the background lights up — a soft brand tint, the same recipe as a
|
|
@@ -346,23 +240,21 @@
|
|
|
346
240
|
// is an ordinary .btn variant, so it sizes the same way .btn-primary/.btn-danger do —
|
|
347
241
|
// Bootstrap's own .btn-sm/.btn-lg.
|
|
348
242
|
.btn-ghost {
|
|
349
|
-
--bs-btn-color:
|
|
350
|
-
--bs-btn-bg:
|
|
351
|
-
--bs-btn-border-color:
|
|
352
|
-
--bs-btn-hover-color:
|
|
353
|
-
--bs-btn-hover-bg:
|
|
354
|
-
--bs-btn-hover-border-color:
|
|
355
|
-
--bs-btn-active-color:
|
|
356
|
-
--bs-btn-active-bg:
|
|
243
|
+
--bs-btn-color: var(--color-text);
|
|
244
|
+
--bs-btn-bg: transparent;
|
|
245
|
+
--bs-btn-border-color: transparent;
|
|
246
|
+
--bs-btn-hover-color: var(--color-text);
|
|
247
|
+
--bs-btn-hover-bg: var(--wui-primary-5);
|
|
248
|
+
--bs-btn-hover-border-color: transparent;
|
|
249
|
+
--bs-btn-active-color: var(--color-text);
|
|
250
|
+
--bs-btn-active-bg: var(--wui-primary-10);
|
|
357
251
|
--bs-btn-active-border-color: transparent;
|
|
358
252
|
}
|
|
359
253
|
|
|
360
|
-
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
//
|
|
364
|
-
// expresses elevation only via the 1px hover translate (kept as a
|
|
365
|
-
// tactile signal, not a depth cue). No box-shadow, here or on hover.
|
|
254
|
+
// ---- Card hover-lift (name is historical) -----------------------------
|
|
255
|
+
// `.card-lifted` does NOT cast a shadow — in-page cards stay flat. It only
|
|
256
|
+
// nudges 1px on hover (tactile signal). API prop stays `lifted` / class
|
|
257
|
+
// `card-lifted` so call sites don't break.
|
|
366
258
|
.card-lifted {
|
|
367
259
|
transition: transform 0.2s var(--wui-ease-out-quart);
|
|
368
260
|
&:hover {
|
|
@@ -370,7 +262,6 @@
|
|
|
370
262
|
}
|
|
371
263
|
}
|
|
372
264
|
|
|
373
|
-
|
|
374
265
|
// ---- Card surface + radius (token-driven) ---------------------------
|
|
375
266
|
// Radius follows the token ladder (see component-radii note) instead of the
|
|
376
267
|
// compiled $card-border-radius. Background is an ELEVATED surface, not the page
|
|
@@ -396,53 +287,10 @@
|
|
|
396
287
|
margin-left: auto;
|
|
397
288
|
}
|
|
398
289
|
|
|
399
|
-
|
|
400
290
|
// ---- Inputs ---------------------------------------------------------
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
// hover-border-darken cue and the leading/trailing-slot wrapper.
|
|
405
|
-
//
|
|
406
|
-
// The error state is driven by `aria-invalid="true"` (NOT by Bootstrap's
|
|
407
|
-
// .is-invalid class) so accessibility tooling and visual cue stay in
|
|
408
|
-
// sync without extra markup.
|
|
409
|
-
|
|
410
|
-
.form-control,
|
|
411
|
-
.form-select {
|
|
412
|
-
// Radius follows the token ladder (see component-radii note) rather than the
|
|
413
|
-
// compiled $input-border-radius, so a product's --wui-radius override reaches
|
|
414
|
-
// the raw Bootstrap inputs the way it already reaches the .input primitive.
|
|
415
|
-
border-radius: var(--wui-radius);
|
|
416
|
-
transition: border-color 0.15s ease;
|
|
417
|
-
}
|
|
418
|
-
.form-control:hover:not(:focus):not(:disabled):not([readonly]):not([aria-invalid="true"]),
|
|
419
|
-
.form-select:hover:not(:focus):not(:disabled):not([aria-invalid="true"]) {
|
|
420
|
-
border-color: var(--color-border-strong); // --wui-text-3 @ 60%
|
|
421
|
-
}
|
|
422
|
-
.form-control[aria-invalid="true"],
|
|
423
|
-
.form-select[aria-invalid="true"] {
|
|
424
|
-
border-color: var(--bs-danger);
|
|
425
|
-
}
|
|
426
|
-
.form-control[aria-invalid="true"]:focus,
|
|
427
|
-
.form-select[aria-invalid="true"]:focus {
|
|
428
|
-
border-color: var(--bs-danger);
|
|
429
|
-
box-shadow: none;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// Optional helper class for forms that prefer the explicit Bootstrap
|
|
433
|
-
// validation pattern. Mirrors aria-invalid styling.
|
|
434
|
-
.form-control.is-invalid,
|
|
435
|
-
.form-select.is-invalid {
|
|
436
|
-
border-color: var(--bs-danger);
|
|
437
|
-
background-image: none; // kill Bootstrap's exclamation-mark SVG
|
|
438
|
-
padding-right: 0.875rem;
|
|
439
|
-
}
|
|
440
|
-
.form-control.is-invalid:focus,
|
|
441
|
-
.form-select.is-invalid:focus {
|
|
442
|
-
border-color: var(--bs-danger);
|
|
443
|
-
box-shadow: none;
|
|
444
|
-
}
|
|
445
|
-
|
|
291
|
+
// Field chrome (height, hover, focus, invalid) lives in wui/_controls.scss on
|
|
292
|
+
// `.form-control` / `.form-select` with `.input` as an alias — one skin, not two
|
|
293
|
+
// This partial only keeps the marketing-side input-group shell.
|
|
446
294
|
|
|
447
295
|
// ---- Input group (leading / trailing slots) ------------------------
|
|
448
296
|
// Single outlined box that contains leading icon + input + trailing
|
|
@@ -450,47 +298,46 @@
|
|
|
450
298
|
// / error states are visible regardless of which child has focus.
|
|
451
299
|
//
|
|
452
300
|
// Usage:
|
|
453
|
-
//
|
|
454
|
-
//
|
|
455
|
-
//
|
|
456
|
-
//
|
|
301
|
+
// <div class="wui-input-group">
|
|
302
|
+
// <span class="wui-input-leading"><i class="bi bi-currency-dollar"></i></span>
|
|
303
|
+
// <input type="text" class="form-control" placeholder="0.00" />
|
|
304
|
+
// </div>
|
|
457
305
|
//
|
|
458
|
-
//
|
|
459
|
-
//
|
|
460
|
-
//
|
|
461
|
-
//
|
|
306
|
+
// <div class="wui-input-group">
|
|
307
|
+
// <input type="password" class="form-control" placeholder="Password" />
|
|
308
|
+
// <button type="button" class="wui-input-trailing"><i class="bi bi-eye"></i></button>
|
|
309
|
+
// </div>
|
|
462
310
|
.wui-input-group {
|
|
463
311
|
display: flex;
|
|
464
312
|
align-items: center;
|
|
465
313
|
gap: 0.5rem;
|
|
466
314
|
padding-inline: 0.875rem;
|
|
467
|
-
height:
|
|
468
|
-
background: var(--
|
|
469
|
-
border: 1px solid var(--
|
|
470
|
-
border-radius: var(--radius);
|
|
315
|
+
height: var(--wui-control-height);
|
|
316
|
+
background: var(--color-surface);
|
|
317
|
+
border: 1px solid var(--color-border);
|
|
318
|
+
border-radius: var(--radius);
|
|
471
319
|
transition: border-color 0.15s ease;
|
|
472
|
-
|
|
473
|
-
|
|
474
320
|
}
|
|
475
321
|
.wui-input-group:hover:not(:focus-within):not(.is-disabled) {
|
|
476
|
-
border-color:
|
|
322
|
+
border-color: var(--color-border-strong);
|
|
477
323
|
}
|
|
478
324
|
.wui-input-group:focus-within {
|
|
479
|
-
border-color: var(--
|
|
325
|
+
border-color: var(--color-primary);
|
|
480
326
|
}
|
|
481
327
|
.wui-input-group.is-disabled,
|
|
482
328
|
.wui-input-group:has(input:disabled) {
|
|
483
|
-
background: var(--
|
|
329
|
+
background: var(--color-bg-muted);
|
|
484
330
|
cursor: not-allowed;
|
|
485
|
-
border-color: var(--
|
|
331
|
+
border-color: var(--color-border);
|
|
486
332
|
}
|
|
487
333
|
.wui-input-group.has-error,
|
|
488
334
|
.wui-input-group:has(input[aria-invalid="true"]) {
|
|
489
|
-
border-color: var(--
|
|
335
|
+
border-color: color-mix(in srgb, var(--color-danger) 45%, var(--color-surface));
|
|
490
336
|
}
|
|
491
337
|
|
|
492
338
|
// Strip the bare input's own chrome — the wrapper carries it all.
|
|
493
|
-
.wui-input-group > .form-control
|
|
339
|
+
.wui-input-group > .form-control,
|
|
340
|
+
.wui-input-group > .input {
|
|
494
341
|
flex: 1 1 auto;
|
|
495
342
|
min-width: 0;
|
|
496
343
|
height: 100%;
|
|
@@ -502,7 +349,9 @@
|
|
|
502
349
|
border-radius: 0;
|
|
503
350
|
}
|
|
504
351
|
.wui-input-group > .form-control:focus,
|
|
505
|
-
.wui-input-group > .form-control:hover
|
|
352
|
+
.wui-input-group > .form-control:hover,
|
|
353
|
+
.wui-input-group > .input:focus,
|
|
354
|
+
.wui-input-group > .input:hover {
|
|
506
355
|
border: 0;
|
|
507
356
|
box-shadow: none !important;
|
|
508
357
|
background: transparent;
|
|
@@ -519,7 +368,7 @@
|
|
|
519
368
|
flex-shrink: 0;
|
|
520
369
|
font-size: 1rem;
|
|
521
370
|
line-height: 1;
|
|
522
|
-
pointer-events: none;
|
|
371
|
+
pointer-events: none; // icons are visual only by default
|
|
523
372
|
}
|
|
524
373
|
|
|
525
374
|
// When the trailing slot is a button (clear, toggle, etc.), reset
|
|
@@ -544,120 +393,24 @@ button.wui-input-leading:focus-visible {
|
|
|
544
393
|
border-radius: 9999px;
|
|
545
394
|
}
|
|
546
395
|
|
|
547
|
-
|
|
548
|
-
// ---- Link with the marketing wipe underline (matches locked design) -
|
|
549
|
-
// Uses background-image (not a pseudo-element with inline-block) so the link
|
|
550
|
-
// stays inline. inline-block + overflow:hidden used to give the link its own
|
|
551
|
-
// baseline, which dropped it below surrounding prose whenever the parent had
|
|
552
|
-
// a non-default font-size (see Pricing footnote "Talk to sales" regression).
|
|
553
|
-
|
|
554
|
-
.link-wipe {
|
|
555
|
-
background-image: linear-gradient(currentColor, currentColor);
|
|
556
|
-
background-repeat: no-repeat;
|
|
557
|
-
background-position-x: 100%;
|
|
558
|
-
background-position-y: 100%;
|
|
559
|
-
background-size: 0 1px;
|
|
560
|
-
transition: background-size 500ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
561
|
-
}
|
|
562
|
-
@media (hover: hover) {
|
|
563
|
-
.link-wipe:hover {
|
|
564
|
-
background-position-x: 0%;
|
|
565
|
-
background-size: 100% 1px;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
.link-wipe--accent {
|
|
569
|
-
background-image: linear-gradient(var(--wui-primary), var(--wui-primary));
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
|
|
573
396
|
// ---- Tabular-numerals helper for KPIs / counters --------------------
|
|
574
397
|
.tabular-nums { font-variant-numeric: tabular-nums; }
|
|
575
398
|
|
|
576
|
-
|
|
577
|
-
// ---- Container with the marketing 1512px canvas ---------------------
|
|
578
|
-
// Pair with --wui-container-pad (responsive). Distinct from Bootstrap's
|
|
579
|
-
// .container (max-width steps at 540/720/960/1140/1320). Use this when a
|
|
580
|
-
// section needs to breathe up to 1512px on wide monitors.
|
|
581
|
-
.container-cloves {
|
|
582
|
-
max-width: var(--wui-container-max, 1512px);
|
|
583
|
-
margin-inline: auto;
|
|
584
|
-
padding-inline: var(--wui-container-pad, 24px);
|
|
585
|
-
width: 100%;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
|
|
589
399
|
// ---- Radius step utilities ------------------------------------------
|
|
590
400
|
// Every .btn is already a squircle (see base .btn above), so a button that
|
|
591
401
|
// wants a larger corner only needs a bigger radius — hence .btn-radius-xl,
|
|
592
402
|
// named for what it does (an xl radius step) rather than the corner shape.
|
|
593
403
|
.btn-radius-xl {
|
|
594
|
-
--bs-btn-border-radius: var(--wui-radius-xl);
|
|
404
|
+
--bs-btn-border-radius: var(--wui-radius-xl); // 16px
|
|
595
405
|
}
|
|
596
406
|
|
|
597
|
-
// Generic (non-button) squircle rounding.
|
|
598
|
-
|
|
599
|
-
// otherwise the name would lie and it'd render a plain 32px arc. Older
|
|
600
|
-
// browsers fall back to that arc (Chrome 138+ / Safari 16+ for the squircle).
|
|
407
|
+
// Generic (non-button) squircle rounding. Progressive enhancement only
|
|
408
|
+
//: unsupported engines keep a plain 32px arc — not a contract.
|
|
601
409
|
.rounded-squircle {
|
|
602
410
|
border-radius: 32px;
|
|
603
411
|
corner-shape: squircle;
|
|
604
412
|
}
|
|
605
413
|
|
|
606
|
-
|
|
607
|
-
// ---- Hero radial wash ------------------------------------------------
|
|
608
|
-
// Soft tinted glow over an elevated surface — sits behind hero copy.
|
|
609
|
-
.hero-wash {
|
|
610
|
-
background:
|
|
611
|
-
radial-gradient(80% 50% at 50% 0%,
|
|
612
|
-
color-mix(in srgb, var(--wui-primary) 8%, transparent), transparent 70%),
|
|
613
|
-
var(--wui-bg-elevated);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
// ---- Page entry fade -------------------------------------------------
|
|
618
|
-
// Apply to <main> in _Layout so every page entry has a polite fade-up.
|
|
619
|
-
.page-fade-in {
|
|
620
|
-
animation: pageFadeIn 0.25s var(--wui-ease-ui) backwards;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
// ---- Single-element fade-in ------------------------------------------
|
|
625
|
-
.fade-in {
|
|
626
|
-
animation: fadeIn 220ms var(--wui-ease-out-quart);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
// ---- Stagger groups --------------------------------------------------
|
|
631
|
-
// Wrap a container in `.stagger-children` and its direct children
|
|
632
|
-
// cascade in 40ms apart, capped at 12 children. For bigger groups,
|
|
633
|
-
// use the IntersectionObserver-driven `[data-reveal]` pattern below.
|
|
634
|
-
.stagger-children > * {
|
|
635
|
-
animation: staggerRise 360ms var(--wui-ease-out-quart) backwards;
|
|
636
|
-
}
|
|
637
|
-
.stagger-children > *:nth-child(1) { animation-delay: 0ms; }
|
|
638
|
-
.stagger-children > *:nth-child(2) { animation-delay: 40ms; }
|
|
639
|
-
.stagger-children > *:nth-child(3) { animation-delay: 80ms; }
|
|
640
|
-
.stagger-children > *:nth-child(4) { animation-delay: 120ms; }
|
|
641
|
-
.stagger-children > *:nth-child(5) { animation-delay: 160ms; }
|
|
642
|
-
.stagger-children > *:nth-child(6) { animation-delay: 200ms; }
|
|
643
|
-
.stagger-children > *:nth-child(7) { animation-delay: 240ms; }
|
|
644
|
-
.stagger-children > *:nth-child(8) { animation-delay: 280ms; }
|
|
645
|
-
.stagger-children > *:nth-child(9) { animation-delay: 320ms; }
|
|
646
|
-
.stagger-children > *:nth-child(10) { animation-delay: 360ms; }
|
|
647
|
-
.stagger-children > *:nth-child(11) { animation-delay: 400ms; }
|
|
648
|
-
.stagger-children > *:nth-child(12) { animation-delay: 440ms; }
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
// ---- Opt-in single-element stagger -----------------------------------
|
|
652
|
-
// Use when you need granular control: set `--stagger: 240ms` inline on
|
|
653
|
-
// individual items inside a non-stagger-children container.
|
|
654
|
-
.stagger-item {
|
|
655
|
-
opacity: 0;
|
|
656
|
-
animation: rise 320ms var(--wui-ease-out-quart) forwards;
|
|
657
|
-
animation-delay: var(--stagger, 0ms);
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
|
|
661
414
|
// ---- Press feedback --------------------------------------------------
|
|
662
415
|
// Lightweight tactile alternative to a Material ripple. Add to any
|
|
663
416
|
// pressable element (button, card link, chip). 80ms ease-out.
|
|
@@ -668,7 +421,6 @@ button.wui-input-leading:focus-visible {
|
|
|
668
421
|
transform: scale(0.96);
|
|
669
422
|
}
|
|
670
423
|
|
|
671
|
-
|
|
672
424
|
// ---- Subtle hover lift -----------------------------------------------
|
|
673
425
|
.lift-on-hover {
|
|
674
426
|
transition: transform 0.2s var(--wui-ease-out-quart),
|
|
@@ -678,7 +430,6 @@ button.wui-input-leading:focus-visible {
|
|
|
678
430
|
transform: translateY(-1px);
|
|
679
431
|
}
|
|
680
432
|
|
|
681
|
-
|
|
682
433
|
// ---- Loading shimmer skeleton ----------------------------------------
|
|
683
434
|
.shimmer {
|
|
684
435
|
position: relative;
|
|
@@ -697,124 +448,38 @@ button.wui-input-leading:focus-visible {
|
|
|
697
448
|
animation: shimmer 1.4s infinite;
|
|
698
449
|
}
|
|
699
450
|
|
|
700
|
-
|
|
701
|
-
// ---- Marquee (customer-logo carousel etc.) ---------------------------
|
|
702
|
-
// Pair with a flex wrapper that duplicates its children so the
|
|
703
|
-
// translateX(-50%) loops seamlessly. Example:
|
|
704
|
-
// <div class="overflow-hidden">
|
|
705
|
-
// <div class="d-flex animate-marquee" style="width: max-content;">
|
|
706
|
-
// <ul class="d-flex gap-5 mb-0 list-unstyled">...</ul>
|
|
707
|
-
// <ul class="d-flex gap-5 mb-0 list-unstyled" aria-hidden="true">...</ul>
|
|
708
|
-
// </div>
|
|
709
|
-
// </div>
|
|
710
|
-
.animate-marquee {
|
|
711
|
-
animation: marketing-marquee 32s linear infinite;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
// ---- Scroll-triggered reveals (paired with wwwroot/js/reveal.js) -----
|
|
716
|
-
// Author opts in by adding `data-reveal` to a section. The JS module
|
|
717
|
-
// adds `.is-visible` when the element scrolls into view; CSS transitions
|
|
718
|
-
// fade + lift on the transition. Stagger descendants via
|
|
719
|
-
// `[data-reveal-stagger] > *` with `--reveal-step` inline (default 40ms).
|
|
720
|
-
[data-reveal] {
|
|
721
|
-
opacity: 0;
|
|
722
|
-
transform: translateY(8px);
|
|
723
|
-
transition:
|
|
724
|
-
opacity 540ms var(--wui-ease-out-quart),
|
|
725
|
-
transform 540ms var(--wui-ease-out-quart);
|
|
726
|
-
will-change: opacity, transform;
|
|
727
|
-
}
|
|
728
|
-
[data-reveal].is-visible {
|
|
729
|
-
opacity: 1;
|
|
730
|
-
transform: none;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
[data-reveal-stagger] > * {
|
|
734
|
-
opacity: 0;
|
|
735
|
-
transform: translateY(8px);
|
|
736
|
-
transition:
|
|
737
|
-
opacity 540ms var(--wui-ease-out-quart),
|
|
738
|
-
transform 540ms var(--wui-ease-out-quart);
|
|
739
|
-
}
|
|
740
|
-
[data-reveal-stagger].is-visible > * {
|
|
741
|
-
opacity: 1;
|
|
742
|
-
transform: none;
|
|
743
|
-
}
|
|
744
|
-
[data-reveal-stagger].is-visible > *:nth-child(1) { transition-delay: calc(var(--reveal-step, 40ms) * 0); }
|
|
745
|
-
[data-reveal-stagger].is-visible > *:nth-child(2) { transition-delay: calc(var(--reveal-step, 40ms) * 1); }
|
|
746
|
-
[data-reveal-stagger].is-visible > *:nth-child(3) { transition-delay: calc(var(--reveal-step, 40ms) * 2); }
|
|
747
|
-
[data-reveal-stagger].is-visible > *:nth-child(4) { transition-delay: calc(var(--reveal-step, 40ms) * 3); }
|
|
748
|
-
[data-reveal-stagger].is-visible > *:nth-child(5) { transition-delay: calc(var(--reveal-step, 40ms) * 4); }
|
|
749
|
-
[data-reveal-stagger].is-visible > *:nth-child(6) { transition-delay: calc(var(--reveal-step, 40ms) * 5); }
|
|
750
|
-
[data-reveal-stagger].is-visible > *:nth-child(7) { transition-delay: calc(var(--reveal-step, 40ms) * 6); }
|
|
751
|
-
[data-reveal-stagger].is-visible > *:nth-child(8) { transition-delay: calc(var(--reveal-step, 40ms) * 7); }
|
|
752
|
-
[data-reveal-stagger].is-visible > *:nth-child(9) { transition-delay: calc(var(--reveal-step, 40ms) * 8); }
|
|
753
|
-
[data-reveal-stagger].is-visible > *:nth-child(10) { transition-delay: calc(var(--reveal-step, 40ms) * 9); }
|
|
754
|
-
[data-reveal-stagger].is-visible > *:nth-child(11) { transition-delay: calc(var(--reveal-step, 40ms) * 10); }
|
|
755
|
-
[data-reveal-stagger].is-visible > *:nth-child(12) { transition-delay: calc(var(--reveal-step, 40ms) * 11); }
|
|
756
|
-
|
|
757
|
-
|
|
758
451
|
// ---- Keyframes -------------------------------------------------------
|
|
759
|
-
|
|
760
|
-
from { opacity: 0; transform: translateY(4px); }
|
|
761
|
-
to { opacity: 1; transform: translateY(0); }
|
|
762
|
-
}
|
|
763
|
-
@keyframes pageFadeIn {
|
|
764
|
-
from { opacity: 0; transform: translateY(6px); }
|
|
765
|
-
to { opacity: 1; transform: translateY(0); }
|
|
766
|
-
}
|
|
767
|
-
@keyframes rise {
|
|
768
|
-
from { opacity: 0; transform: translateY(8px); }
|
|
769
|
-
to { opacity: 1; transform: translateY(0); }
|
|
770
|
-
}
|
|
771
|
-
@keyframes staggerRise {
|
|
772
|
-
from { opacity: 0; transform: translateY(8px); }
|
|
773
|
-
to { opacity: 1; transform: translateY(0); }
|
|
774
|
-
}
|
|
452
|
+
|
|
775
453
|
@keyframes shimmer {
|
|
776
|
-
0%
|
|
454
|
+
0% { transform: translateX(-100%); }
|
|
777
455
|
100% { transform: translateX(100%); }
|
|
778
456
|
}
|
|
779
|
-
@keyframes marketing-marquee {
|
|
780
|
-
from { transform: translateX(0); }
|
|
781
|
-
to { transform: translateX(-50%); }
|
|
782
|
-
}
|
|
783
|
-
|
|
784
457
|
|
|
785
|
-
// ----
|
|
786
|
-
//
|
|
458
|
+
// ---- Native scrollbars (document + opt-in) --------------------------
|
|
459
|
+
// Scoped to html/body and `.wui-scroll` — never `*`. Global
|
|
460
|
+
// `*` restyled every nested scroller (modals, tiny panels) and fought
|
|
461
|
+
// component-local rails. AppScrollbar uses `.wui-scroll` (see wui/_scrollbar).
|
|
787
462
|
//
|
|
788
|
-
//
|
|
789
|
-
//
|
|
790
|
-
//
|
|
791
|
-
// - Pill thumb with a 2px transparent border (visual cushion)
|
|
792
|
-
// - `scrollbar-gutter: stable` on <html>/<body> so the page never
|
|
793
|
-
// shifts when scrollable content grows/shrinks. Internal containers
|
|
794
|
-
// (avatars, badges, rounded buttons) intentionally skip the gutter
|
|
795
|
-
// reservation — applying it everywhere would push centered glyphs
|
|
796
|
-
// off-center inside rounded boxes.
|
|
797
|
-
//
|
|
798
|
-
// `wwwroot/js/scroll-state.js` toggles `html.is-scrolling` for ~800ms
|
|
799
|
-
// after each scroll event; the thumb gets an extra opacity bump while
|
|
800
|
-
// the class is on, giving subtle "I'm scrolling now" feedback without
|
|
801
|
-
// sacrificing the always-visible affordance.
|
|
463
|
+
// `scrollbar-gutter: stable` only on the document so internal boxes do not
|
|
464
|
+
// push centered glyphs off-center. `html.is-scrolling` (scroll-state.js)
|
|
465
|
+
// brightens the document thumb while the page is scrolling.
|
|
802
466
|
html,
|
|
803
467
|
body {
|
|
804
468
|
scrollbar-gutter: stable;
|
|
805
|
-
}
|
|
806
|
-
* {
|
|
807
469
|
scrollbar-width: thin;
|
|
808
470
|
scrollbar-color: var(--wui-scrollbar) transparent;
|
|
809
471
|
}
|
|
810
|
-
|
|
472
|
+
html::-webkit-scrollbar,
|
|
473
|
+
body::-webkit-scrollbar {
|
|
811
474
|
width: 8px;
|
|
812
475
|
height: 8px;
|
|
813
476
|
}
|
|
814
|
-
|
|
477
|
+
html::-webkit-scrollbar-track,
|
|
478
|
+
body::-webkit-scrollbar-track {
|
|
815
479
|
background: transparent;
|
|
816
480
|
}
|
|
817
|
-
|
|
481
|
+
html::-webkit-scrollbar-thumb,
|
|
482
|
+
body::-webkit-scrollbar-thumb {
|
|
818
483
|
background-color: var(--wui-scrollbar);
|
|
819
484
|
border-radius: 9999px;
|
|
820
485
|
border: 2px solid transparent;
|
|
@@ -823,29 +488,31 @@ body {
|
|
|
823
488
|
min-width: 36px;
|
|
824
489
|
transition: background-color 220ms ease;
|
|
825
490
|
}
|
|
826
|
-
|
|
491
|
+
html::-webkit-scrollbar-thumb:hover,
|
|
492
|
+
body::-webkit-scrollbar-thumb:hover {
|
|
827
493
|
background-color: var(--wui-scrollbar-hover);
|
|
828
494
|
background-clip: padding-box;
|
|
829
495
|
}
|
|
830
|
-
|
|
496
|
+
html::-webkit-scrollbar-thumb:active,
|
|
497
|
+
body::-webkit-scrollbar-thumb:active {
|
|
831
498
|
background-color: var(--wui-scrollbar-active);
|
|
832
499
|
background-clip: padding-box;
|
|
833
500
|
}
|
|
834
|
-
|
|
501
|
+
html::-webkit-scrollbar-corner,
|
|
502
|
+
body::-webkit-scrollbar-corner {
|
|
835
503
|
background: transparent;
|
|
836
504
|
}
|
|
837
505
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
html.is-scrolling * {
|
|
506
|
+
html.is-scrolling,
|
|
507
|
+
html.is-scrolling body {
|
|
841
508
|
scrollbar-color: var(--wui-scrollbar-hover) transparent;
|
|
842
509
|
}
|
|
843
|
-
html.is-scrolling
|
|
510
|
+
html.is-scrolling::-webkit-scrollbar-thumb,
|
|
511
|
+
html.is-scrolling body::-webkit-scrollbar-thumb {
|
|
844
512
|
background-color: var(--wui-scrollbar-hover);
|
|
845
513
|
}
|
|
846
514
|
|
|
847
|
-
//
|
|
848
|
-
// carousels where the rail itself is visually intrusive).
|
|
515
|
+
// Hide a scrollbar (marquee / horizontal carousels).
|
|
849
516
|
.scrollbar-hide {
|
|
850
517
|
scrollbar-width: none;
|
|
851
518
|
}
|
|
@@ -853,86 +520,30 @@ html.is-scrolling *::-webkit-scrollbar-thumb {
|
|
|
853
520
|
display: none;
|
|
854
521
|
}
|
|
855
522
|
|
|
856
|
-
|
|
857
|
-
// ---- Render perf primitives ----------------------------------------
|
|
858
|
-
//
|
|
859
|
-
// `content-visibility: auto` lets the browser skip layout + paint for
|
|
860
|
-
// offscreen sections — a 1500px page with 8 sections paints in ~⅓ the
|
|
861
|
-
// time after this change. Sections opt in via [data-section] (already
|
|
862
|
-
// used on most homepage sections) or .section-y / .section-y-lg /
|
|
863
|
-
// .section-y-sm wrappers. `contain-intrinsic-size` reserves a sane
|
|
864
|
-
// height so the scrollbar doesn't snap as sections paint in.
|
|
865
|
-
//
|
|
866
|
-
// Notes:
|
|
867
|
-
// - Don't apply to the inner hero or any element that hosts
|
|
868
|
-
// position: sticky descendants — `content-visibility` creates a
|
|
869
|
-
// containing block which breaks sticky positioning in those areas.
|
|
870
|
-
// - First section is excluded — it's above the fold on every page,
|
|
871
|
-
// no benefit and risks delaying LCP.
|
|
872
|
-
[data-section]:not(:first-of-type):not([data-section="inner-hero"]):not([data-section="home-hero"]):not([data-section-skip-cv]),
|
|
873
|
-
.section-y:not(:first-of-type):not([data-section-skip-cv]),
|
|
874
|
-
.section-y-sm:not(:first-of-type):not([data-section-skip-cv]),
|
|
875
|
-
.section-y-lg:not(:first-of-type):not([data-section-skip-cv]) {
|
|
876
|
-
content-visibility: auto;
|
|
877
|
-
contain-intrinsic-size: auto 720px;
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
// GPU-promote frequently-animated elements at rest so the compositor
|
|
881
|
-
// can flip them on hover without a paint hit. We deliberately do NOT
|
|
882
|
-
// add will-change globally — that costs memory on every element. Only
|
|
883
|
-
// elements with motion-on-interact get it.
|
|
523
|
+
// GPU-promote portal interactive surfaces (marketing selectors → marketing.scss).
|
|
884
524
|
.card-lifted,
|
|
885
525
|
.lift-on-hover,
|
|
886
|
-
.press-feedback
|
|
887
|
-
|
|
888
|
-
.btn-pill,
|
|
889
|
-
.btn-pill-sm,
|
|
890
|
-
[data-reveal],
|
|
891
|
-
[data-reveal-stagger] > * {
|
|
892
|
-
will-change: auto; // baseline — opt out of promotion
|
|
526
|
+
.press-feedback {
|
|
527
|
+
will-change: auto;
|
|
893
528
|
}
|
|
894
529
|
.card-lifted:hover,
|
|
895
530
|
.lift-on-hover:hover,
|
|
896
|
-
.press-feedback:active
|
|
897
|
-
[data-reveal]:not(.is-visible),
|
|
898
|
-
[data-reveal-stagger]:not(.is-visible) > * {
|
|
531
|
+
.press-feedback:active {
|
|
899
532
|
will-change: transform, opacity;
|
|
900
533
|
}
|
|
901
534
|
|
|
902
|
-
// Reduce paint on form-field focus rings — box-shadow repaints are
|
|
903
|
-
// cheaper when the painted layer is isolated.
|
|
904
|
-
.contact-field,
|
|
905
|
-
.custom-select-toggle,
|
|
906
|
-
.contact-submit {
|
|
907
|
-
backface-visibility: hidden;
|
|
908
|
-
transform: translateZ(0);
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
|
|
912
535
|
// ---- Reduced motion -------------------------------------------------
|
|
913
536
|
@media (prefers-reduced-motion: reduce) {
|
|
914
537
|
.card-lifted,
|
|
915
538
|
.lift-on-hover,
|
|
916
|
-
.link-wipe,
|
|
917
539
|
.press-feedback {
|
|
918
540
|
transition: none;
|
|
919
541
|
}
|
|
920
|
-
.
|
|
921
|
-
.fade-in,
|
|
922
|
-
.stagger-children > *,
|
|
923
|
-
.stagger-item,
|
|
924
|
-
.animate-marquee,
|
|
925
|
-
.shimmer::after,
|
|
926
|
-
[data-reveal],
|
|
927
|
-
[data-reveal-stagger] > * {
|
|
542
|
+
.shimmer::after {
|
|
928
543
|
animation: none;
|
|
929
|
-
opacity: 1;
|
|
930
|
-
transform: none;
|
|
931
|
-
transition: none;
|
|
932
544
|
}
|
|
933
545
|
}
|
|
934
546
|
|
|
935
|
-
|
|
936
547
|
// ---- Theme-flipped typography utilities ------------------------------------
|
|
937
548
|
// Bootstrap builds `.text-body` as `rgba(var(--bs-body-color-rgb), …)` — an RGB
|
|
938
549
|
// TRIPLET baked in at compile time (37, 47, 74). Bridging `--bs-body-color`
|