@webority/theme 0.7.4 → 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 +102 -474
- package/scss/_marketing.scss +337 -0
- package/scss/_tokens.scss +102 -97
- package/scss/_variables.scss +92 -87
- package/scss/_wui-aliases.scss +102 -102
- package/scss/wui/_controls.scss +8 -8
- package/scss/wui/_navigation.scss +27 -0
- package/scss/wui/_utilities.scss +112 -0
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,65 +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
|
|
157
95
|
// action that shouldn't compete with a primary/danger CTA in the same row.
|
|
158
96
|
.btn-subtle {
|
|
159
|
-
--bs-btn-bg:
|
|
160
|
-
--bs-btn-color:
|
|
161
|
-
--bs-btn-border-color:
|
|
162
|
-
--bs-btn-hover-bg:
|
|
163
|
-
--bs-btn-hover-color:
|
|
164
|
-
--bs-btn-hover-border-color:
|
|
165
|
-
--bs-btn-active-bg:
|
|
166
|
-
--bs-btn-active-color:
|
|
167
|
-
--bs-btn-active-border-color:
|
|
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;
|
|
168
106
|
}
|
|
169
107
|
|
|
170
108
|
// Light outline for a dark banner/hero CTA, where .btn-outline-primary's brand-coloured
|
|
171
109
|
// border/text would disappear against a dark background. White is used directly (not a
|
|
172
110
|
// token) because it's the one colour that reads on any dark surface, brand-independent.
|
|
173
111
|
.btn-outline-light {
|
|
174
|
-
--bs-btn-color:
|
|
175
|
-
--bs-btn-bg:
|
|
176
|
-
--bs-btn-border-color:
|
|
177
|
-
--bs-btn-hover-color:
|
|
178
|
-
--bs-btn-hover-bg:
|
|
179
|
-
--bs-btn-hover-border-color:
|
|
180
|
-
--bs-btn-active-color:
|
|
181
|
-
--bs-btn-active-bg:
|
|
182
|
-
--bs-btn-active-border-color:
|
|
183
|
-
--bs-btn-disabled-color:
|
|
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);
|
|
184
122
|
--bs-btn-disabled-border-color: rgba(255, 255, 255, 0.3);
|
|
185
123
|
}
|
|
186
124
|
|
|
187
|
-
// Themed disabled state (
|
|
125
|
+
// Themed disabled state (default). Bootstrap's own disabled look is just the
|
|
188
126
|
// variant's colour at --bs-btn-disabled-opacity (.65) — barely different from the active
|
|
189
127
|
// button on a tinted brand palette. Give the ordinary case a real desaturated/muted look
|
|
190
128
|
// instead, reusing the same tokens .input:disabled already uses so a disabled button and
|
|
@@ -202,7 +140,6 @@
|
|
|
202
140
|
cursor: not-allowed;
|
|
203
141
|
}
|
|
204
142
|
|
|
205
|
-
|
|
206
143
|
// ---- Component radii from tokens ------------------------------------
|
|
207
144
|
// .btn/.card/.input capture Bootstrap's compiled $*-border-radius (the library
|
|
208
145
|
// ladder) before a product loads, so a product's --wui-radius* override never
|
|
@@ -224,7 +161,6 @@
|
|
|
224
161
|
--bs-btn-border-radius: var(--wui-radius-lg);
|
|
225
162
|
}
|
|
226
163
|
|
|
227
|
-
|
|
228
164
|
// ---- Dropdown menu (AppDropdown / <app-dropdown>) -------------------
|
|
229
165
|
// Bootstrap ships a bare .dropdown-item; the design system gives the menu real
|
|
230
166
|
// padding, an icon–label gap, a rounded hover highlight tinted with the brand,
|
|
@@ -233,19 +169,19 @@
|
|
|
233
169
|
// Elevated surface (like .card), not Bootstrap's default page --bs-body-bg: otherwise the
|
|
234
170
|
// menu blends into a tinted page and, in dark mode, resolves to the page colour instead of
|
|
235
171
|
// the elevated surface.
|
|
236
|
-
--bs-dropdown-bg:
|
|
237
|
-
--bs-dropdown-min-width:
|
|
238
|
-
--bs-dropdown-padding-x:
|
|
239
|
-
--bs-dropdown-padding-y:
|
|
240
|
-
--bs-dropdown-item-padding-y:
|
|
241
|
-
--bs-dropdown-item-padding-x:
|
|
242
|
-
--bs-dropdown-border-color:
|
|
243
|
-
--bs-dropdown-border-radius:
|
|
244
|
-
--bs-dropdown-link-color:
|
|
245
|
-
--bs-dropdown-link-hover-color:
|
|
246
|
-
--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);
|
|
247
183
|
--bs-dropdown-link-active-color: var(--wui-primary-pressed);
|
|
248
|
-
--bs-dropdown-link-active-bg:
|
|
184
|
+
--bs-dropdown-link-active-bg: var(--wui-primary-10);
|
|
249
185
|
box-shadow: var(--wui-shadow-overlay);
|
|
250
186
|
}
|
|
251
187
|
|
|
@@ -283,87 +219,15 @@
|
|
|
283
219
|
|
|
284
220
|
// Destructive row action — red label, red glyph, red-tinted hover.
|
|
285
221
|
&.danger {
|
|
286
|
-
--bs-dropdown-link-color:
|
|
222
|
+
--bs-dropdown-link-color: var(--wui-danger-text);
|
|
287
223
|
--bs-dropdown-link-hover-color: var(--wui-danger-text);
|
|
288
|
-
--bs-dropdown-link-hover-bg:
|
|
224
|
+
--bs-dropdown-link-hover-bg: var(--wui-danger-10);
|
|
289
225
|
.bi, i, svg { color: var(--wui-danger); }
|
|
290
226
|
&:hover .bi, &:hover i, &:hover svg,
|
|
291
227
|
&:focus .bi, &:focus i, &:focus svg { color: var(--wui-danger); }
|
|
292
228
|
}
|
|
293
229
|
}
|
|
294
230
|
|
|
295
|
-
|
|
296
|
-
// ---- Marketing pill buttons (locked design parity) -------------------
|
|
297
|
-
// Pixel-locked to the standalone reference's `btn-squircle inline-flex
|
|
298
|
-
// h-12 items-center text-sm font-medium` pattern. Each marketing CTA
|
|
299
|
-
// layers .btn-pill on a Bootstrap variant — these classes only override
|
|
300
|
-
// Bootstrap's btn CSS vars (height / padding / radius / font), so
|
|
301
|
-
// every variant inherits the right size without redefining colors.
|
|
302
|
-
//
|
|
303
|
-
// Reference values:
|
|
304
|
-
// - height : 48px (h-12 = 3rem)
|
|
305
|
-
// - radius : 16px (--wui-radius-xl)
|
|
306
|
-
// - font : 14px / 500 (text-sm font-medium)
|
|
307
|
-
// - padding-x : 20px (px-5) default — Watch demo / outline-dark
|
|
308
|
-
// Primary CTA overrides to 24px (px-6) inline
|
|
309
|
-
// - corner-shape : squircle (iOS-style smooth curve)
|
|
310
|
-
//
|
|
311
|
-
// Example:
|
|
312
|
-
// <a class="btn btn-primary btn-pill" style="--bs-btn-padding-x:1.5rem;">…</a>
|
|
313
|
-
// <a class="btn btn-light btn-pill-sm">Learn more</a>
|
|
314
|
-
.btn-pill {
|
|
315
|
-
--bs-btn-padding-y: 0; // height driven by min-height + flex centering
|
|
316
|
-
--bs-btn-padding-x: 1.25rem; // 20px (px-5)
|
|
317
|
-
--bs-btn-font-size: 0.875rem; // 14px
|
|
318
|
-
--bs-btn-font-weight: 500;
|
|
319
|
-
--bs-btn-line-height: 1.25;
|
|
320
|
-
--bs-btn-border-radius: var(--wui-radius-xl); // 16px
|
|
321
|
-
min-height: 3rem; // 48px (h-12)
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
display: inline-flex;
|
|
325
|
-
align-items: center;
|
|
326
|
-
gap: 0.375rem; // 6px — matches primary arrow's ml-1.5
|
|
327
|
-
}
|
|
328
|
-
.btn-pill-sm {
|
|
329
|
-
--bs-btn-padding-y: 0;
|
|
330
|
-
--bs-btn-padding-x: 0.875rem; // 14px (px-3.5)
|
|
331
|
-
--bs-btn-font-size: 0.875rem; // 14px (text-sm)
|
|
332
|
-
--bs-btn-font-weight: 500;
|
|
333
|
-
--bs-btn-line-height: 1.25;
|
|
334
|
-
--bs-btn-border-radius: 12px;
|
|
335
|
-
min-height: 2.5rem; // 40px (h-10) — reference "Learn more" CTA
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
display: inline-flex;
|
|
339
|
-
align-items: center;
|
|
340
|
-
gap: 0.375rem;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
// ---- Ghost-dark button (frosted glass on light surface) -------------
|
|
345
|
-
// Pixel-locked to the reference's `WatchDemoButton` "outline-dark" variant
|
|
346
|
-
// (src/components/marketing/brand/watch-demo-button.tsx):
|
|
347
|
-
// - border : rgba(13, 24, 43, 0.15) (foreground/15 in marketing-root)
|
|
348
|
-
// - bg : rgba(255, 255, 255, 0.6) (surface/60)
|
|
349
|
-
// - text : --wui-text-heading (text-foreground in marketing-root)
|
|
350
|
-
// - blur : 4px (backdrop-blur-sm)
|
|
351
|
-
// - hover bg: #ffffff (hover:bg-surface = full opacity)
|
|
352
|
-
.btn-ghost-dark {
|
|
353
|
-
--bs-btn-color: var(--wui-text-heading);
|
|
354
|
-
--bs-btn-bg: rgba(255, 255, 255, 0.60);
|
|
355
|
-
--bs-btn-border-color: rgba(13, 24, 43, 0.15);
|
|
356
|
-
--bs-btn-hover-color: var(--wui-text-heading);
|
|
357
|
-
--bs-btn-hover-bg: #ffffff;
|
|
358
|
-
--bs-btn-hover-border-color: rgba(13, 24, 43, 0.20);
|
|
359
|
-
--bs-btn-active-color: var(--wui-text-heading);
|
|
360
|
-
--bs-btn-active-bg: #ffffff;
|
|
361
|
-
--bs-btn-active-border-color: rgba(13, 24, 43, 0.20);
|
|
362
|
-
backdrop-filter: blur(4px);
|
|
363
|
-
-webkit-backdrop-filter: blur(4px);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
|
|
367
231
|
// ---- Ghost button (transparent, brand-tinted hover) ------------------
|
|
368
232
|
// A real ghost button: transparent at rest (no fill, no border), current text colour;
|
|
369
233
|
// only the background lights up — a soft brand tint, the same recipe as a
|
|
@@ -376,22 +240,21 @@
|
|
|
376
240
|
// is an ordinary .btn variant, so it sizes the same way .btn-primary/.btn-danger do —
|
|
377
241
|
// Bootstrap's own .btn-sm/.btn-lg.
|
|
378
242
|
.btn-ghost {
|
|
379
|
-
--bs-btn-color:
|
|
380
|
-
--bs-btn-bg:
|
|
381
|
-
--bs-btn-border-color:
|
|
382
|
-
--bs-btn-hover-color:
|
|
383
|
-
--bs-btn-hover-bg:
|
|
384
|
-
--bs-btn-hover-border-color:
|
|
385
|
-
--bs-btn-active-color:
|
|
386
|
-
--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);
|
|
387
251
|
--bs-btn-active-border-color: transparent;
|
|
388
252
|
}
|
|
389
253
|
|
|
390
|
-
|
|
391
254
|
// ---- Card hover-lift (name is historical) -----------------------------
|
|
392
255
|
// `.card-lifted` does NOT cast a shadow — in-page cards stay flat. It only
|
|
393
256
|
// nudges 1px on hover (tactile signal). API prop stays `lifted` / class
|
|
394
|
-
// `card-lifted` so call sites don't break
|
|
257
|
+
// `card-lifted` so call sites don't break.
|
|
395
258
|
.card-lifted {
|
|
396
259
|
transition: transform 0.2s var(--wui-ease-out-quart);
|
|
397
260
|
&:hover {
|
|
@@ -399,7 +262,6 @@
|
|
|
399
262
|
}
|
|
400
263
|
}
|
|
401
264
|
|
|
402
|
-
|
|
403
265
|
// ---- Card surface + radius (token-driven) ---------------------------
|
|
404
266
|
// Radius follows the token ladder (see component-radii note) instead of the
|
|
405
267
|
// compiled $card-border-radius. Background is an ELEVATED surface, not the page
|
|
@@ -425,12 +287,10 @@
|
|
|
425
287
|
margin-left: auto;
|
|
426
288
|
}
|
|
427
289
|
|
|
428
|
-
|
|
429
290
|
// ---- Inputs ---------------------------------------------------------
|
|
430
291
|
// Field chrome (height, hover, focus, invalid) lives in wui/_controls.scss on
|
|
431
292
|
// `.form-control` / `.form-select` with `.input` as an alias — one skin, not two
|
|
432
|
-
//
|
|
433
|
-
|
|
293
|
+
// This partial only keeps the marketing-side input-group shell.
|
|
434
294
|
|
|
435
295
|
// ---- Input group (leading / trailing slots) ------------------------
|
|
436
296
|
// Single outlined box that contains leading icon + input + trailing
|
|
@@ -438,15 +298,15 @@
|
|
|
438
298
|
// / error states are visible regardless of which child has focus.
|
|
439
299
|
//
|
|
440
300
|
// Usage:
|
|
441
|
-
//
|
|
442
|
-
//
|
|
443
|
-
//
|
|
444
|
-
//
|
|
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>
|
|
445
305
|
//
|
|
446
|
-
//
|
|
447
|
-
//
|
|
448
|
-
//
|
|
449
|
-
//
|
|
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>
|
|
450
310
|
.wui-input-group {
|
|
451
311
|
display: flex;
|
|
452
312
|
align-items: center;
|
|
@@ -508,7 +368,7 @@
|
|
|
508
368
|
flex-shrink: 0;
|
|
509
369
|
font-size: 1rem;
|
|
510
370
|
line-height: 1;
|
|
511
|
-
pointer-events: none;
|
|
371
|
+
pointer-events: none; // icons are visual only by default
|
|
512
372
|
}
|
|
513
373
|
|
|
514
374
|
// When the trailing slot is a button (clear, toggle, etc.), reset
|
|
@@ -533,120 +393,24 @@ button.wui-input-leading:focus-visible {
|
|
|
533
393
|
border-radius: 9999px;
|
|
534
394
|
}
|
|
535
395
|
|
|
536
|
-
|
|
537
|
-
// ---- Link with the marketing wipe underline (matches locked design) -
|
|
538
|
-
// Uses background-image (not a pseudo-element with inline-block) so the link
|
|
539
|
-
// stays inline. inline-block + overflow:hidden used to give the link its own
|
|
540
|
-
// baseline, which dropped it below surrounding prose whenever the parent had
|
|
541
|
-
// a non-default font-size (see Pricing footnote "Talk to sales" regression).
|
|
542
|
-
|
|
543
|
-
.link-wipe {
|
|
544
|
-
background-image: linear-gradient(currentColor, currentColor);
|
|
545
|
-
background-repeat: no-repeat;
|
|
546
|
-
background-position-x: 100%;
|
|
547
|
-
background-position-y: 100%;
|
|
548
|
-
background-size: 0 1px;
|
|
549
|
-
transition: background-size 500ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
550
|
-
}
|
|
551
|
-
@media (hover: hover) {
|
|
552
|
-
.link-wipe:hover {
|
|
553
|
-
background-position-x: 0%;
|
|
554
|
-
background-size: 100% 1px;
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
.link-wipe--accent {
|
|
558
|
-
background-image: linear-gradient(var(--wui-primary), var(--wui-primary));
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
|
|
562
396
|
// ---- Tabular-numerals helper for KPIs / counters --------------------
|
|
563
397
|
.tabular-nums { font-variant-numeric: tabular-nums; }
|
|
564
398
|
|
|
565
|
-
|
|
566
|
-
// ---- Container with the marketing 1512px canvas ---------------------
|
|
567
|
-
// Pair with --wui-container-pad (responsive). Distinct from Bootstrap's
|
|
568
|
-
// .container (max-width steps at 540/720/960/1140/1320). Use this when a
|
|
569
|
-
// section needs to breathe up to 1512px on wide monitors.
|
|
570
|
-
.container-cloves {
|
|
571
|
-
max-width: var(--wui-container-max, 1512px);
|
|
572
|
-
margin-inline: auto;
|
|
573
|
-
padding-inline: var(--wui-container-pad, 24px);
|
|
574
|
-
width: 100%;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
|
|
578
399
|
// ---- Radius step utilities ------------------------------------------
|
|
579
400
|
// Every .btn is already a squircle (see base .btn above), so a button that
|
|
580
401
|
// wants a larger corner only needs a bigger radius — hence .btn-radius-xl,
|
|
581
402
|
// named for what it does (an xl radius step) rather than the corner shape.
|
|
582
403
|
.btn-radius-xl {
|
|
583
|
-
--bs-btn-border-radius: var(--wui-radius-xl);
|
|
404
|
+
--bs-btn-border-radius: var(--wui-radius-xl); // 16px
|
|
584
405
|
}
|
|
585
406
|
|
|
586
|
-
// Generic (non-button) squircle rounding.
|
|
587
|
-
|
|
588
|
-
// otherwise the name would lie and it'd render a plain 32px arc. Older
|
|
589
|
-
// 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.
|
|
590
409
|
.rounded-squircle {
|
|
591
410
|
border-radius: 32px;
|
|
592
411
|
corner-shape: squircle;
|
|
593
412
|
}
|
|
594
413
|
|
|
595
|
-
|
|
596
|
-
// ---- Hero radial wash ------------------------------------------------
|
|
597
|
-
// Soft tinted glow over an elevated surface — sits behind hero copy.
|
|
598
|
-
.hero-wash {
|
|
599
|
-
background:
|
|
600
|
-
radial-gradient(80% 50% at 50% 0%,
|
|
601
|
-
color-mix(in srgb, var(--wui-primary) 8%, transparent), transparent 70%),
|
|
602
|
-
var(--wui-bg-elevated);
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
// ---- Page entry fade -------------------------------------------------
|
|
607
|
-
// Apply to <main> in _Layout so every page entry has a polite fade-up.
|
|
608
|
-
.page-fade-in {
|
|
609
|
-
animation: pageFadeIn 0.25s var(--wui-ease-ui) backwards;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
// ---- Single-element fade-in ------------------------------------------
|
|
614
|
-
.fade-in {
|
|
615
|
-
animation: fadeIn 220ms var(--wui-ease-out-quart);
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
// ---- Stagger groups --------------------------------------------------
|
|
620
|
-
// Wrap a container in `.stagger-children` and its direct children
|
|
621
|
-
// cascade in 40ms apart, capped at 12 children. For bigger groups,
|
|
622
|
-
// use the IntersectionObserver-driven `[data-reveal]` pattern below.
|
|
623
|
-
.stagger-children > * {
|
|
624
|
-
animation: staggerRise 360ms var(--wui-ease-out-quart) backwards;
|
|
625
|
-
}
|
|
626
|
-
.stagger-children > *:nth-child(1) { animation-delay: 0ms; }
|
|
627
|
-
.stagger-children > *:nth-child(2) { animation-delay: 40ms; }
|
|
628
|
-
.stagger-children > *:nth-child(3) { animation-delay: 80ms; }
|
|
629
|
-
.stagger-children > *:nth-child(4) { animation-delay: 120ms; }
|
|
630
|
-
.stagger-children > *:nth-child(5) { animation-delay: 160ms; }
|
|
631
|
-
.stagger-children > *:nth-child(6) { animation-delay: 200ms; }
|
|
632
|
-
.stagger-children > *:nth-child(7) { animation-delay: 240ms; }
|
|
633
|
-
.stagger-children > *:nth-child(8) { animation-delay: 280ms; }
|
|
634
|
-
.stagger-children > *:nth-child(9) { animation-delay: 320ms; }
|
|
635
|
-
.stagger-children > *:nth-child(10) { animation-delay: 360ms; }
|
|
636
|
-
.stagger-children > *:nth-child(11) { animation-delay: 400ms; }
|
|
637
|
-
.stagger-children > *:nth-child(12) { animation-delay: 440ms; }
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
// ---- Opt-in single-element stagger -----------------------------------
|
|
641
|
-
// Use when you need granular control: set `--stagger: 240ms` inline on
|
|
642
|
-
// individual items inside a non-stagger-children container.
|
|
643
|
-
.stagger-item {
|
|
644
|
-
opacity: 0;
|
|
645
|
-
animation: rise 320ms var(--wui-ease-out-quart) forwards;
|
|
646
|
-
animation-delay: var(--stagger, 0ms);
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
|
|
650
414
|
// ---- Press feedback --------------------------------------------------
|
|
651
415
|
// Lightweight tactile alternative to a Material ripple. Add to any
|
|
652
416
|
// pressable element (button, card link, chip). 80ms ease-out.
|
|
@@ -657,7 +421,6 @@ button.wui-input-leading:focus-visible {
|
|
|
657
421
|
transform: scale(0.96);
|
|
658
422
|
}
|
|
659
423
|
|
|
660
|
-
|
|
661
424
|
// ---- Subtle hover lift -----------------------------------------------
|
|
662
425
|
.lift-on-hover {
|
|
663
426
|
transition: transform 0.2s var(--wui-ease-out-quart),
|
|
@@ -667,7 +430,6 @@ button.wui-input-leading:focus-visible {
|
|
|
667
430
|
transform: translateY(-1px);
|
|
668
431
|
}
|
|
669
432
|
|
|
670
|
-
|
|
671
433
|
// ---- Loading shimmer skeleton ----------------------------------------
|
|
672
434
|
.shimmer {
|
|
673
435
|
position: relative;
|
|
@@ -686,93 +448,15 @@ button.wui-input-leading:focus-visible {
|
|
|
686
448
|
animation: shimmer 1.4s infinite;
|
|
687
449
|
}
|
|
688
450
|
|
|
689
|
-
|
|
690
|
-
// ---- Marquee (customer-logo carousel etc.) ---------------------------
|
|
691
|
-
// Pair with a flex wrapper that duplicates its children so the
|
|
692
|
-
// translateX(-50%) loops seamlessly. Example:
|
|
693
|
-
// <div class="overflow-hidden">
|
|
694
|
-
// <div class="d-flex animate-marquee" style="width: max-content;">
|
|
695
|
-
// <ul class="d-flex gap-5 mb-0 list-unstyled">...</ul>
|
|
696
|
-
// <ul class="d-flex gap-5 mb-0 list-unstyled" aria-hidden="true">...</ul>
|
|
697
|
-
// </div>
|
|
698
|
-
// </div>
|
|
699
|
-
.animate-marquee {
|
|
700
|
-
animation: marketing-marquee 32s linear infinite;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
// ---- Scroll-triggered reveals (paired with wwwroot/js/reveal.js) -----
|
|
705
|
-
// Author opts in by adding `data-reveal` to a section. The JS module
|
|
706
|
-
// adds `.is-visible` when the element scrolls into view; CSS transitions
|
|
707
|
-
// fade + lift on the transition. Stagger descendants via
|
|
708
|
-
// `[data-reveal-stagger] > *` with `--reveal-step` inline (default 40ms).
|
|
709
|
-
[data-reveal] {
|
|
710
|
-
opacity: 0;
|
|
711
|
-
transform: translateY(8px);
|
|
712
|
-
transition:
|
|
713
|
-
opacity 540ms var(--wui-ease-out-quart),
|
|
714
|
-
transform 540ms var(--wui-ease-out-quart);
|
|
715
|
-
will-change: opacity, transform;
|
|
716
|
-
}
|
|
717
|
-
[data-reveal].is-visible {
|
|
718
|
-
opacity: 1;
|
|
719
|
-
transform: none;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
[data-reveal-stagger] > * {
|
|
723
|
-
opacity: 0;
|
|
724
|
-
transform: translateY(8px);
|
|
725
|
-
transition:
|
|
726
|
-
opacity 540ms var(--wui-ease-out-quart),
|
|
727
|
-
transform 540ms var(--wui-ease-out-quart);
|
|
728
|
-
}
|
|
729
|
-
[data-reveal-stagger].is-visible > * {
|
|
730
|
-
opacity: 1;
|
|
731
|
-
transform: none;
|
|
732
|
-
}
|
|
733
|
-
[data-reveal-stagger].is-visible > *:nth-child(1) { transition-delay: calc(var(--reveal-step, 40ms) * 0); }
|
|
734
|
-
[data-reveal-stagger].is-visible > *:nth-child(2) { transition-delay: calc(var(--reveal-step, 40ms) * 1); }
|
|
735
|
-
[data-reveal-stagger].is-visible > *:nth-child(3) { transition-delay: calc(var(--reveal-step, 40ms) * 2); }
|
|
736
|
-
[data-reveal-stagger].is-visible > *:nth-child(4) { transition-delay: calc(var(--reveal-step, 40ms) * 3); }
|
|
737
|
-
[data-reveal-stagger].is-visible > *:nth-child(5) { transition-delay: calc(var(--reveal-step, 40ms) * 4); }
|
|
738
|
-
[data-reveal-stagger].is-visible > *:nth-child(6) { transition-delay: calc(var(--reveal-step, 40ms) * 5); }
|
|
739
|
-
[data-reveal-stagger].is-visible > *:nth-child(7) { transition-delay: calc(var(--reveal-step, 40ms) * 6); }
|
|
740
|
-
[data-reveal-stagger].is-visible > *:nth-child(8) { transition-delay: calc(var(--reveal-step, 40ms) * 7); }
|
|
741
|
-
[data-reveal-stagger].is-visible > *:nth-child(9) { transition-delay: calc(var(--reveal-step, 40ms) * 8); }
|
|
742
|
-
[data-reveal-stagger].is-visible > *:nth-child(10) { transition-delay: calc(var(--reveal-step, 40ms) * 9); }
|
|
743
|
-
[data-reveal-stagger].is-visible > *:nth-child(11) { transition-delay: calc(var(--reveal-step, 40ms) * 10); }
|
|
744
|
-
[data-reveal-stagger].is-visible > *:nth-child(12) { transition-delay: calc(var(--reveal-step, 40ms) * 11); }
|
|
745
|
-
|
|
746
|
-
|
|
747
451
|
// ---- Keyframes -------------------------------------------------------
|
|
748
|
-
|
|
749
|
-
from { opacity: 0; transform: translateY(4px); }
|
|
750
|
-
to { opacity: 1; transform: translateY(0); }
|
|
751
|
-
}
|
|
752
|
-
@keyframes pageFadeIn {
|
|
753
|
-
from { opacity: 0; transform: translateY(6px); }
|
|
754
|
-
to { opacity: 1; transform: translateY(0); }
|
|
755
|
-
}
|
|
756
|
-
@keyframes rise {
|
|
757
|
-
from { opacity: 0; transform: translateY(8px); }
|
|
758
|
-
to { opacity: 1; transform: translateY(0); }
|
|
759
|
-
}
|
|
760
|
-
@keyframes staggerRise {
|
|
761
|
-
from { opacity: 0; transform: translateY(8px); }
|
|
762
|
-
to { opacity: 1; transform: translateY(0); }
|
|
763
|
-
}
|
|
452
|
+
|
|
764
453
|
@keyframes shimmer {
|
|
765
|
-
0%
|
|
454
|
+
0% { transform: translateX(-100%); }
|
|
766
455
|
100% { transform: translateX(100%); }
|
|
767
456
|
}
|
|
768
|
-
@keyframes marketing-marquee {
|
|
769
|
-
from { transform: translateX(0); }
|
|
770
|
-
to { transform: translateX(-50%); }
|
|
771
|
-
}
|
|
772
|
-
|
|
773
457
|
|
|
774
458
|
// ---- Native scrollbars (document + opt-in) --------------------------
|
|
775
|
-
// Scoped to html/body and `.wui-scroll` — never
|
|
459
|
+
// Scoped to html/body and `.wui-scroll` — never `*`. Global
|
|
776
460
|
// `*` restyled every nested scroller (modals, tiny panels) and fought
|
|
777
461
|
// component-local rails. AppScrollbar uses `.wui-scroll` (see wui/_scrollbar).
|
|
778
462
|
//
|
|
@@ -836,86 +520,30 @@ html.is-scrolling body::-webkit-scrollbar-thumb {
|
|
|
836
520
|
display: none;
|
|
837
521
|
}
|
|
838
522
|
|
|
839
|
-
|
|
840
|
-
// ---- Render perf primitives ----------------------------------------
|
|
841
|
-
//
|
|
842
|
-
// `content-visibility: auto` lets the browser skip layout + paint for
|
|
843
|
-
// offscreen sections — a 1500px page with 8 sections paints in ~⅓ the
|
|
844
|
-
// time after this change. Sections opt in via [data-section] (already
|
|
845
|
-
// used on most homepage sections) or .section-y / .section-y-lg /
|
|
846
|
-
// .section-y-sm wrappers. `contain-intrinsic-size` reserves a sane
|
|
847
|
-
// height so the scrollbar doesn't snap as sections paint in.
|
|
848
|
-
//
|
|
849
|
-
// Notes:
|
|
850
|
-
// - Don't apply to the inner hero or any element that hosts
|
|
851
|
-
// position: sticky descendants — `content-visibility` creates a
|
|
852
|
-
// containing block which breaks sticky positioning in those areas.
|
|
853
|
-
// - First section is excluded — it's above the fold on every page,
|
|
854
|
-
// no benefit and risks delaying LCP.
|
|
855
|
-
[data-section]:not(:first-of-type):not([data-section="inner-hero"]):not([data-section="home-hero"]):not([data-section-skip-cv]),
|
|
856
|
-
.section-y:not(:first-of-type):not([data-section-skip-cv]),
|
|
857
|
-
.section-y-sm:not(:first-of-type):not([data-section-skip-cv]),
|
|
858
|
-
.section-y-lg:not(:first-of-type):not([data-section-skip-cv]) {
|
|
859
|
-
content-visibility: auto;
|
|
860
|
-
contain-intrinsic-size: auto 720px;
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
// GPU-promote frequently-animated elements at rest so the compositor
|
|
864
|
-
// can flip them on hover without a paint hit. We deliberately do NOT
|
|
865
|
-
// add will-change globally — that costs memory on every element. Only
|
|
866
|
-
// elements with motion-on-interact get it.
|
|
523
|
+
// GPU-promote portal interactive surfaces (marketing selectors → marketing.scss).
|
|
867
524
|
.card-lifted,
|
|
868
525
|
.lift-on-hover,
|
|
869
|
-
.press-feedback
|
|
870
|
-
|
|
871
|
-
.btn-pill,
|
|
872
|
-
.btn-pill-sm,
|
|
873
|
-
[data-reveal],
|
|
874
|
-
[data-reveal-stagger] > * {
|
|
875
|
-
will-change: auto; // baseline — opt out of promotion
|
|
526
|
+
.press-feedback {
|
|
527
|
+
will-change: auto;
|
|
876
528
|
}
|
|
877
529
|
.card-lifted:hover,
|
|
878
530
|
.lift-on-hover:hover,
|
|
879
|
-
.press-feedback:active
|
|
880
|
-
[data-reveal]:not(.is-visible),
|
|
881
|
-
[data-reveal-stagger]:not(.is-visible) > * {
|
|
531
|
+
.press-feedback:active {
|
|
882
532
|
will-change: transform, opacity;
|
|
883
533
|
}
|
|
884
534
|
|
|
885
|
-
// Reduce paint on form-field focus rings — box-shadow repaints are
|
|
886
|
-
// cheaper when the painted layer is isolated.
|
|
887
|
-
.contact-field,
|
|
888
|
-
.custom-select-toggle,
|
|
889
|
-
.contact-submit {
|
|
890
|
-
backface-visibility: hidden;
|
|
891
|
-
transform: translateZ(0);
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
|
|
895
535
|
// ---- Reduced motion -------------------------------------------------
|
|
896
536
|
@media (prefers-reduced-motion: reduce) {
|
|
897
537
|
.card-lifted,
|
|
898
538
|
.lift-on-hover,
|
|
899
|
-
.link-wipe,
|
|
900
539
|
.press-feedback {
|
|
901
540
|
transition: none;
|
|
902
541
|
}
|
|
903
|
-
.
|
|
904
|
-
.fade-in,
|
|
905
|
-
.stagger-children > *,
|
|
906
|
-
.stagger-item,
|
|
907
|
-
.animate-marquee,
|
|
908
|
-
.shimmer::after,
|
|
909
|
-
[data-reveal],
|
|
910
|
-
[data-reveal-stagger] > * {
|
|
542
|
+
.shimmer::after {
|
|
911
543
|
animation: none;
|
|
912
|
-
opacity: 1;
|
|
913
|
-
transform: none;
|
|
914
|
-
transition: none;
|
|
915
544
|
}
|
|
916
545
|
}
|
|
917
546
|
|
|
918
|
-
|
|
919
547
|
// ---- Theme-flipped typography utilities ------------------------------------
|
|
920
548
|
// Bootstrap builds `.text-body` as `rgba(var(--bs-body-color-rgb), …)` — an RGB
|
|
921
549
|
// TRIPLET baked in at compile time (37, 47, 74). Bridging `--bs-body-color`
|