@webority/theme 0.3.0 → 0.4.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.
@@ -0,0 +1,410 @@
1
+ // Wizard stepper, pricing card, form repeater.
2
+ //
3
+ // Part of the wui-components layer — see _wui-components.scss for the whole
4
+ // picture and the load order. Order within the layer is LOAD-BEARING: these
5
+ // partials are a split of one stylesheet, not independent modules, so a rule
6
+ // here may be overridden by a later partial. Never reorder the imports.
7
+
8
+ // Wizard stepper. Two variants of one component:
9
+ // .stepper — compact: numbered circle above a label, line behind.
10
+ // .stepper-journey — wide: 40px dot beside a label + title, for a page masthead.
11
+ // ============================================================================
12
+ .stepper {
13
+ display: flex;
14
+ align-items: flex-start;
15
+ gap: 0;
16
+ padding: 0 8px;
17
+ }
18
+ .step {
19
+ position: relative;
20
+ flex: 1;
21
+ min-width: 0;
22
+ display: flex;
23
+ flex-direction: column;
24
+ align-items: center;
25
+ gap: 10px;
26
+ text-align: center;
27
+ }
28
+ // The connector belongs to the step on its RIGHT, drawn leftwards — that way the
29
+ // first step has none and no :last-child exception is needed.
30
+ .step + .step::before {
31
+ content: "";
32
+ position: absolute;
33
+ // 13px = half the 28px circle, minus 1 for the 2px line's own height.
34
+ top: 13px;
35
+ left: calc(-50% + 18px);
36
+ right: calc(50% + 18px);
37
+ height: 2px;
38
+ background: var(--color-border);
39
+ z-index: 0;
40
+ }
41
+ .step.done + .step::before {
42
+ background: var(--color-primary);
43
+ }
44
+ .step-circle {
45
+ position: relative;
46
+ z-index: 1;
47
+ flex: none;
48
+ width: 28px;
49
+ height: 28px;
50
+ display: inline-grid;
51
+ place-items: center;
52
+ border: 1.5px solid var(--color-border);
53
+ border-radius: 999px;
54
+ background: var(--color-surface);
55
+ font-size: 12px;
56
+ font-weight: 600;
57
+ color: var(--color-text-subtle);
58
+ }
59
+ .step.active .step-circle {
60
+ border-color: var(--color-primary);
61
+ background: var(--color-primary);
62
+ color: var(--color-on-scrim);
63
+ box-shadow: 0 0 0 4px var(--color-primary-soft);
64
+ }
65
+ .step.done .step-circle {
66
+ border-color: var(--color-primary);
67
+ background: var(--color-primary);
68
+ color: var(--color-on-scrim);
69
+ }
70
+ .step-label {
71
+ max-width: 130px;
72
+ font-size: 13px;
73
+ font-weight: 500;
74
+ line-height: 1.25;
75
+ color: var(--color-text-subtle);
76
+ overflow-wrap: break-word;
77
+ }
78
+ .step.active .step-label,
79
+ .step.done .step-label {
80
+ color: var(--color-text);
81
+ }
82
+ // A step that is an <a> is a deep link back to an earlier stage of the wizard.
83
+ a.step {
84
+ text-decoration: none;
85
+ cursor: pointer;
86
+ }
87
+ a.step:hover .step-circle {
88
+ box-shadow: 0 0 0 4px var(--color-primary-soft);
89
+ transform: scale(1.04);
90
+ }
91
+ a.step:hover .step-label {
92
+ color: var(--color-primary);
93
+ }
94
+ a.step:focus-visible {
95
+ outline: none;
96
+ }
97
+ // The ring moves onto the circle: an outline on the <a> would box the whole column.
98
+ a.step:focus-visible .step-circle {
99
+ box-shadow:
100
+ 0 0 0 4px var(--color-primary-soft),
101
+ 0 0 0 6px var(--color-primary);
102
+ }
103
+
104
+ .stepper-journey {
105
+ display: flex;
106
+ align-items: center;
107
+ gap: 0;
108
+ padding: 16px 24px;
109
+ border: 1px solid var(--color-border);
110
+ border-radius: var(--radius-xl);
111
+ corner-shape: squircle;
112
+ background: var(--color-surface);
113
+ }
114
+ .stepper-journey .step {
115
+ flex-direction: row;
116
+ align-items: center;
117
+ gap: 12px;
118
+ text-align: left;
119
+ }
120
+ // The compact variant's connector is absolute and 28px-calibrated; the journey
121
+ // variant uses a real flex sibling instead, so cancel it.
122
+ .stepper-journey .step + .step::before {
123
+ content: none;
124
+ }
125
+ .stepper-journey .step-circle {
126
+ width: 40px;
127
+ height: 40px;
128
+ border: none;
129
+ background: var(--color-bg-muted);
130
+ font-size: 13px;
131
+ font-weight: 700;
132
+ transition:
133
+ background 0.2s ease,
134
+ color 0.2s ease,
135
+ transform 0.2s ease;
136
+ }
137
+ .stepper-journey .step.done .step-circle {
138
+ background: var(--color-success-soft);
139
+ color: var(--color-success-strong);
140
+ }
141
+ .stepper-journey .step.active .step-circle {
142
+ background: var(--color-primary);
143
+ color: var(--color-on-scrim);
144
+ box-shadow: none;
145
+ transform: scale(1.06);
146
+ }
147
+ // A flex column, not a bare wrapper: the index and the label are inline spans, so
148
+ // without this they sit on one line ("STEP 1Add site") and the label's margin-top
149
+ // — a no-op on an inline element — cannot separate them.
150
+ .stepper-journey .step-text {
151
+ display: flex;
152
+ flex-direction: column;
153
+ min-width: 0;
154
+ }
155
+ .stepper-journey .step-index {
156
+ font-size: 12px;
157
+ font-weight: 500;
158
+ line-height: 1.2;
159
+ text-transform: uppercase;
160
+ letter-spacing: 0.06em;
161
+ color: var(--color-text-subtle);
162
+ }
163
+ .stepper-journey .step.done .step-index {
164
+ color: var(--color-success-strong);
165
+ }
166
+ .stepper-journey .step.active .step-index {
167
+ color: var(--color-primary);
168
+ }
169
+ .stepper-journey .step-label {
170
+ max-width: none;
171
+ margin-top: 8px;
172
+ font-size: 16px;
173
+ line-height: 1.3;
174
+ white-space: nowrap;
175
+ overflow: hidden;
176
+ text-overflow: ellipsis;
177
+ color: var(--color-text);
178
+ }
179
+ .stepper-journey .step-line {
180
+ flex: none;
181
+ width: 32px;
182
+ height: 2px;
183
+ margin: 0 6px;
184
+ background: var(--color-border-strong);
185
+ }
186
+ .stepper-journey .step-line.done {
187
+ background: var(--color-success);
188
+ }
189
+ // Below xl the four label+title pairs no longer fit side by side, so the bar
190
+ // becomes a vertical list and the connectors go away.
191
+ @include media-breakpoint-down(xl) {
192
+ .stepper-journey.stepper-journey {
193
+ flex-direction: column;
194
+ align-items: stretch;
195
+ gap: 10px;
196
+ padding: 14px;
197
+ }
198
+ .stepper-journey.stepper-journey .step-line {
199
+ display: none;
200
+ }
201
+ }
202
+ @media (prefers-reduced-motion: reduce) {
203
+ a.step:hover .step-circle,
204
+ .stepper-journey .step-circle {
205
+ transition: none;
206
+ transform: none;
207
+ }
208
+ }
209
+
210
+ // ============================================================================
211
+ // Pricing card. A tier's name, price, pitch, feature list and CTA.
212
+ //
213
+ // The "Most popular" flag is a real element, not a `content:` string on
214
+ // ::before — a label welded into CSS cannot be reworded, translated, or read by
215
+ // a screen reader.
216
+ // ============================================================================
217
+ .plan-grid {
218
+ display: grid;
219
+ gap: 20px;
220
+ grid-template-columns: 1fr;
221
+ // The featured card scales up by 2%; without room it clips against its neighbours.
222
+ padding: 12px 0;
223
+ align-items: stretch;
224
+ }
225
+ @include media-breakpoint-up(md) {
226
+ .plan-grid.plan-grid {
227
+ grid-template-columns: repeat(2, minmax(0, 1fr));
228
+ }
229
+ }
230
+ @include media-breakpoint-up(lg) {
231
+ .plan-grid.plan-grid {
232
+ grid-template-columns: repeat(3, minmax(0, 1fr));
233
+ }
234
+ }
235
+
236
+ .plan-card {
237
+ position: relative;
238
+ display: flex;
239
+ flex-direction: column;
240
+ padding: 32px 28px;
241
+ border: 1px solid var(--color-border);
242
+ border-radius: var(--radius-xl);
243
+ corner-shape: squircle;
244
+ background: var(--color-surface);
245
+ transition:
246
+ border-color 0.15s ease,
247
+ box-shadow 0.15s ease,
248
+ transform 0.15s ease;
249
+ }
250
+ .plan-card:hover {
251
+ border-color: var(--color-border-strong);
252
+ }
253
+ .plan-card.featured {
254
+ border-color: var(--color-primary);
255
+ transform: scale(1.02);
256
+ }
257
+ .plan-card.featured:hover {
258
+ border-color: var(--color-primary);
259
+ }
260
+ // The plan the customer is already on. A ring rather than a scale, so it reads as
261
+ // "yours" without competing with the recommended tier for visual weight.
262
+ .plan-card.current {
263
+ border-color: var(--color-primary);
264
+ box-shadow: 0 0 0 1px var(--color-primary);
265
+ }
266
+ .plan-badge {
267
+ position: absolute;
268
+ top: -12px;
269
+ left: 50%;
270
+ transform: translateX(-50%);
271
+ padding: 5px 14px;
272
+ border-radius: 999px;
273
+ background: var(--color-primary);
274
+ color: var(--color-on-scrim);
275
+ font-size: 11px;
276
+ font-weight: 600;
277
+ text-transform: uppercase;
278
+ letter-spacing: 0.04em;
279
+ white-space: nowrap;
280
+ }
281
+ .plan-name {
282
+ font-size: 17px;
283
+ font-weight: 600;
284
+ color: var(--color-text);
285
+ }
286
+ .plan-price {
287
+ margin-top: 14px;
288
+ font-size: 40px;
289
+ font-weight: 700;
290
+ letter-spacing: -0.03em;
291
+ line-height: 1;
292
+ color: var(--color-text);
293
+ }
294
+ .plan-price-unit {
295
+ font-size: 15px;
296
+ font-weight: 500;
297
+ color: var(--color-text-subtle);
298
+ }
299
+ // A min-height so a one-line and a two-line tagline still leave every card's
300
+ // price and CTA on the same baseline across the row.
301
+ .plan-tagline {
302
+ margin-top: 8px;
303
+ min-height: 36px;
304
+ font-size: 13px;
305
+ color: var(--color-text-muted);
306
+ }
307
+ .plan-features {
308
+ display: flex;
309
+ flex-direction: column;
310
+ gap: 10px;
311
+ margin: 24px 0;
312
+ padding: 0;
313
+ list-style: none;
314
+ }
315
+ .plan-features li {
316
+ display: flex;
317
+ align-items: flex-start;
318
+ gap: 10px;
319
+ font-size: 14px;
320
+ color: var(--color-text);
321
+ }
322
+ .plan-feature-icon {
323
+ flex: none;
324
+ margin-top: 2px;
325
+ color: var(--color-primary);
326
+ }
327
+ // A feature the tier does NOT include, kept in the list so every card has the
328
+ // same rows and the columns line up when read across.
329
+ .plan-features li.excluded {
330
+ color: var(--color-text-subtle);
331
+ }
332
+ .plan-features li.excluded .plan-feature-icon {
333
+ color: var(--color-text-subtle);
334
+ }
335
+ // Pushes the CTA to the bottom edge whatever the feature count, so the buttons
336
+ // across a row align.
337
+ .plan-cta {
338
+ margin-top: auto;
339
+ }
340
+ @media (prefers-reduced-motion: reduce) {
341
+ .plan-card {
342
+ transition: none;
343
+ }
344
+ .plan-card.featured {
345
+ transform: none;
346
+ }
347
+ .plan-badge {
348
+ // Cancelling the transform would move the badge off its own centre.
349
+ transform: translateX(-50%);
350
+ }
351
+ }
352
+
353
+ // ============================================================================
354
+ // Form repeater — a list of rows the user can add to and remove from
355
+ // (contacts, line items, zones).
356
+ // ============================================================================
357
+ .repeater {
358
+ display: flex;
359
+ flex-direction: column;
360
+ gap: 12px;
361
+ }
362
+ .repeater-row {
363
+ display: flex;
364
+ align-items: flex-start;
365
+ gap: 12px;
366
+ padding: 14px 16px;
367
+ border: 1px solid var(--color-border);
368
+ border-radius: var(--radius-lg);
369
+ corner-shape: squircle;
370
+ background: var(--color-surface);
371
+ }
372
+ .repeater-index {
373
+ flex: none;
374
+ width: 22px;
375
+ height: 22px;
376
+ // The index sits on the first line of the row's content, not centred on the
377
+ // whole row, which would drift as a row grows taller.
378
+ margin-top: 8px;
379
+ display: inline-grid;
380
+ place-items: center;
381
+ border-radius: var(--radius-sm);
382
+ background: var(--color-primary-soft);
383
+ color: var(--color-primary);
384
+ font-size: 12px;
385
+ font-weight: 700;
386
+ }
387
+ .repeater-body {
388
+ flex: 1;
389
+ min-width: 0;
390
+ }
391
+ .repeater-remove {
392
+ flex: none;
393
+ margin-top: 4px;
394
+ }
395
+ .repeater-add {
396
+ align-self: flex-start;
397
+ }
398
+ // Shown in place of the rows when there are none — a repeater with no rows and no
399
+ // message looks broken rather than empty.
400
+ .repeater-empty {
401
+ padding: 18px;
402
+ border: 1px dashed var(--color-border-strong);
403
+ border-radius: var(--radius-lg);
404
+ background: var(--color-bg-subtle);
405
+ font-size: 13px;
406
+ color: var(--color-text-muted);
407
+ text-align: center;
408
+ }
409
+
410
+ // ============================================================================