strata-css 1.0.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,2898 @@
1
+ /**
2
+ * Strata Registry — Optimised
3
+ * O(1) lookup via pre-computed Map for known classes
4
+ * Regex fallback only for arbitrary values
5
+ */
6
+
7
+ 'use strict'
8
+
9
+ const { wrapInMediaQuery } = require('./breakpoints')
10
+
11
+ // ─── Escape helpers ───────────────────────────────────────────────────
12
+
13
+ function escapeClass(cls) {
14
+ return cls
15
+ .replace(/!/g, '\\!')
16
+ .replace(/\[/g, '\\[')
17
+ .replace(/\]/g, '\\]')
18
+ .replace(/\//g, '\\/')
19
+ .replace(/:/g, '\\:')
20
+ .replace(/\./g, '\\.')
21
+ }
22
+
23
+ function parseArbitrary(value) {
24
+ const match = value.match(/^\[(.+)\]$/)
25
+ return match ? match[1] : null
26
+ }
27
+
28
+ // ─── Spacing scale ────────────────────────────────────────────────────
29
+
30
+ const SPACING_SCALE = {
31
+ '0': '0', '1': '0.25rem', '2': '0.5rem',
32
+ '3': '1rem', '4': '1.5rem', '5': '3rem', 'auto': 'auto',
33
+ }
34
+
35
+ const SPACING_PROPS = {
36
+ 'm': ['margin'],
37
+ 'mt': ['margin-top'], 'mb': ['margin-bottom'],
38
+ 'ms': ['margin-left'], 'me': ['margin-right'],
39
+ 'mx': ['margin-left', 'margin-right'],
40
+ 'my': ['margin-top', 'margin-bottom'],
41
+ 'p': ['padding'],
42
+ 'pt': ['padding-top'], 'pb': ['padding-bottom'],
43
+ 'ps': ['padding-left'], 'pe': ['padding-right'],
44
+ 'px': ['padding-left', 'padding-right'],
45
+ 'py': ['padding-top', 'padding-bottom'],
46
+ }
47
+
48
+ // ─── Color maps ───────────────────────────────────────────────────────
49
+
50
+ const TEXT_COLOR_MAP = {
51
+ primary: 'var(--st-primary)', secondary: 'var(--st-secondary)',
52
+ success: 'var(--st-success)', danger: 'var(--st-danger)',
53
+ warning: 'var(--st-warning)', info: 'var(--st-info)',
54
+ light: 'var(--st-light)', dark: 'var(--st-dark)',
55
+ white: '#ffffff', muted: 'var(--st-text-muted)', body: 'var(--st-text)',
56
+ }
57
+
58
+ const BG_COLOR_MAP = {
59
+ primary: 'var(--st-primary)', secondary: 'var(--st-secondary)',
60
+ success: 'var(--st-success)', danger: 'var(--st-danger)',
61
+ warning: 'var(--st-warning)', info: 'var(--st-info)',
62
+ light: 'var(--st-light)', dark: 'var(--st-dark)',
63
+ white: '#ffffff', transparent: 'transparent', body: 'var(--st-bg)',
64
+ }
65
+
66
+ const BORDER_COLOR_MAP = {
67
+ primary: 'var(--st-primary)', secondary: 'var(--st-secondary)',
68
+ success: 'var(--st-success)', danger: 'var(--st-danger)',
69
+ warning: 'var(--st-warning)', info: 'var(--st-info)',
70
+ light: 'var(--st-light)', dark: 'var(--st-dark)', white: '#ffffff',
71
+ }
72
+
73
+ const DISPLAY_MAP = {
74
+ none: 'none', inline: 'inline', 'inline-block': 'inline-block',
75
+ block: 'block', grid: 'grid', 'inline-grid': 'inline-grid',
76
+ flex: 'flex', 'inline-flex': 'inline-flex', table: 'table',
77
+ }
78
+
79
+ const JUSTIFY_MAP = {
80
+ start: 'flex-start', end: 'flex-end', center: 'center',
81
+ between: 'space-between', around: 'space-around', evenly: 'space-evenly',
82
+ }
83
+
84
+ const ALIGN_MAP = {
85
+ start: 'flex-start', end: 'flex-end', center: 'center',
86
+ baseline: 'baseline', stretch: 'stretch',
87
+ }
88
+
89
+ const OPACITY_SCALE = { '0':'0', '25':'.25', '50':'.5', '75':'.75', '100':'1' }
90
+ const SIZE_SCALE = { '25':'25%', '50':'50%', '75':'75%', '100':'100%', 'auto':'auto' }
91
+ const Z_SCALE = { '0':'0', '1':'1', '2':'2', '3':'3', 'auto':'auto' }
92
+ const BREAKPOINTS = ['xs','sm','md','lg','xl','xxl']
93
+ const CURSOR_VALUES = new Set(['auto','default','pointer','wait','text','move','not-allowed','grab'])
94
+ const OBJECT_FIT = new Set(['contain','cover','fill','none','scale-down'])
95
+ const EASE_MAP = {
96
+ 'in': 'cubic-bezier(0.4,0,1,1)', 'out': 'cubic-bezier(0,0,0.2,1)',
97
+ 'in-out': 'cubic-bezier(0.4,0,0.2,1)', 'linear': 'linear',
98
+ }
99
+
100
+ // ─── O(1) Pre-computed class Map ─────────────────────────────────────
101
+ // All known non-arbitrary classes pre-built at startup
102
+ // Lookup is instant — no regex needed
103
+
104
+ const EXACT_MAP = new Map()
105
+
106
+ // Actual breakpoint pixel values — CSS variables cannot be used in media queries
107
+ const BP_VALUES = {
108
+ sm: '576px',
109
+ md: '768px',
110
+ lg: '992px',
111
+ xl: '1200px',
112
+ xxl: '1400px',
113
+ }
114
+
115
+ function reg(cls, layer, css) {
116
+ EXACT_MAP.set(cls, { layer, css })
117
+ }
118
+
119
+ // Media query helper using actual pixel values
120
+ function mq(bp, css) {
121
+ return `@media (min-width: ${BP_VALUES[bp]}) { ${css} }`
122
+ }
123
+
124
+ // Display utilities
125
+ Object.keys(DISPLAY_MAP).forEach(v => {
126
+ reg(`d-${v}`, 'utilities', `.d-${v} { display: ${DISPLAY_MAP[v]}; }`)
127
+ BREAKPOINTS.forEach(bp => {
128
+ if (bp === 'xs') return
129
+ reg(`d-${bp}-${v}`, 'utilities',
130
+ mq(bp, `.d-${bp}-${v} { display: ${DISPLAY_MAP[v]}; }`))
131
+ })
132
+ })
133
+
134
+ // Spacing utilities
135
+ Object.keys(SPACING_PROPS).forEach(prop => {
136
+ const props = SPACING_PROPS[prop]
137
+ Object.keys(SPACING_SCALE).forEach(scale => {
138
+ const val = SPACING_SCALE[scale]
139
+ const decl = props.map(p => ` ${p}: ${val};`).join('\n')
140
+ reg(`${prop}-${scale}`, 'utilities', `.${prop}-${scale} {\n${decl}\n}`)
141
+
142
+ // Important variant
143
+ const declImp = props.map(p => ` ${p}: ${val} !important;`).join('\n')
144
+ reg(`!${prop}-${scale}`, 'utilities', `.\\!${prop}-${scale} {\n${declImp}\n}`)
145
+
146
+ // Breakpoint variants
147
+ BREAKPOINTS.forEach(bp => {
148
+ if (bp === 'xs') return
149
+ const bpDecl = props.map(p => ` ${p}: ${val};`).join('\n')
150
+ reg(`${prop}-${bp}-${scale}`, 'utilities',
151
+ mq(bp, `.${prop}-${bp}-${scale} {\n${bpDecl}\n}`))
152
+ })
153
+ })
154
+ })
155
+
156
+ // Text alignment
157
+ const TEXT_ALIGN = { start:'left', end:'right', center:'center', justify:'justify' }
158
+ Object.entries(TEXT_ALIGN).forEach(([k, v]) => {
159
+ reg(`text-${k}`, 'utilities', `.text-${k} { text-align: ${v}; }`)
160
+ reg(`!text-${k}`, 'utilities', `.\\!text-${k} { text-align: ${v} !important; }`)
161
+ BREAKPOINTS.forEach(bp => {
162
+ if (bp === 'xs') return
163
+ reg(`text-${bp}-${k}`, 'utilities',
164
+ mq(bp, `.text-${bp}-${k} { text-align: ${v}; }`))
165
+ })
166
+ })
167
+
168
+ // Text transform
169
+ ;['uppercase','lowercase','capitalize','none'].forEach(v => {
170
+ reg(`text-${v}`, 'utilities', `.text-${v} { text-transform: ${v}; }`)
171
+ reg(`!text-${v}`, 'utilities', `.\\!text-${v} { text-transform: ${v} !important; }`)
172
+ })
173
+
174
+ // Text colors
175
+ Object.entries(TEXT_COLOR_MAP).forEach(([k, v]) => {
176
+ reg(`text-${k}`, 'utilities', `.text-${k} { color: ${v}; }`)
177
+ reg(`!text-${k}`, 'utilities', `.\\!text-${k} { color: ${v} !important; }`)
178
+ })
179
+
180
+ // Background colors
181
+ Object.entries(BG_COLOR_MAP).forEach(([k, v]) => {
182
+ reg(`bg-${k}`, 'utilities', `.bg-${k} { background-color: ${v}; }`)
183
+ reg(`!bg-${k}`, 'utilities', `.\\!bg-${k} { background-color: ${v} !important; }`)
184
+ })
185
+
186
+ // Border colors
187
+ reg('border', 'utilities', '.border { border: 1px solid var(--st-border); }')
188
+ reg('border-0', 'utilities', '.border-0 { border: none; }')
189
+ Object.entries(BORDER_COLOR_MAP).forEach(([k, v]) => {
190
+ reg(`border-${k}`, 'utilities', `.border-${k} { border-color: ${v}; }`)
191
+ })
192
+
193
+ // Sizing
194
+ Object.entries(SIZE_SCALE).forEach(([k, v]) => {
195
+ reg(`w-${k}`, 'utilities', `.w-${k} { width: ${v}; }`)
196
+ reg(`h-${k}`, 'utilities', `.h-${k} { height: ${v}; }`)
197
+ reg(`!w-${k}`, 'utilities', `.\\!w-${k} { width: ${v} !important; }`)
198
+ reg(`!h-${k}`, 'utilities', `.\\!h-${k} { height: ${v} !important; }`)
199
+ })
200
+
201
+ // Flexbox
202
+ reg('flex-row', 'utilities', '.flex-row { flex-direction: row; }')
203
+ reg('flex-column', 'utilities', '.flex-column { flex-direction: column; }')
204
+ reg('flex-wrap', 'utilities', '.flex-wrap { flex-wrap: wrap; }')
205
+ reg('flex-nowrap', 'utilities', '.flex-nowrap { flex-wrap: nowrap; }')
206
+ reg('flex-fill', 'utilities', '.flex-fill { flex: 1 1 auto; }')
207
+ reg('flex-grow-0', 'utilities', '.flex-grow-0 { flex-grow: 0; }')
208
+ reg('flex-grow-1', 'utilities', '.flex-grow-1 { flex-grow: 1; }')
209
+ reg('flex-shrink-0','utilities', '.flex-shrink-0 { flex-shrink: 0; }')
210
+ reg('flex-shrink-1','utilities', '.flex-shrink-1 { flex-shrink: 1; }')
211
+
212
+ // Justify content
213
+ Object.entries(JUSTIFY_MAP).forEach(([k, v]) => {
214
+ reg(`justify-content-${k}`, 'utilities', `.justify-content-${k} { justify-content: ${v}; }`)
215
+ BREAKPOINTS.forEach(bp => {
216
+ if (bp === 'xs') return
217
+ reg(`justify-content-${bp}-${k}`, 'utilities',
218
+ mq(bp, `.justify-content-${bp}-${k} { justify-content: ${v}; }`))
219
+ })
220
+ })
221
+
222
+ // Align items
223
+ Object.entries(ALIGN_MAP).forEach(([k, v]) => {
224
+ reg(`align-items-${k}`, 'utilities', `.align-items-${k} { align-items: ${v}; }`)
225
+ BREAKPOINTS.forEach(bp => {
226
+ if (bp === 'xs') return
227
+ reg(`align-items-${bp}-${k}`, 'utilities',
228
+ mq(bp, `.align-items-${bp}-${k} { align-items: ${v}; }`))
229
+ })
230
+ })
231
+
232
+ // Position
233
+ ;['static','relative','absolute','fixed','sticky'].forEach(v => {
234
+ reg(`position-${v}`, 'utilities', `.position-${v} { position: ${v}; }`)
235
+ reg(`!position-${v}`, 'utilities', `.\\!position-${v} { position: ${v} !important; }`)
236
+ })
237
+
238
+ // Overflow
239
+ ;['auto','hidden','visible','scroll'].forEach(v => {
240
+ reg(`overflow-${v}`, 'utilities', `.overflow-${v} { overflow: ${v}; }`)
241
+ reg(`!overflow-${v}`, 'utilities', `.\\!overflow-${v} { overflow: ${v} !important; }`)
242
+ })
243
+
244
+ // Opacity
245
+ Object.entries(OPACITY_SCALE).forEach(([k, v]) => {
246
+ reg(`opacity-${k}`, 'utilities', `.opacity-${k} { opacity: ${v}; }`)
247
+ reg(`!opacity-${k}`, 'utilities', `.\\!opacity-${k} { opacity: ${v} !important; }`)
248
+ })
249
+
250
+ // Visibility
251
+ reg('visible', 'utilities', '.visible { visibility: visible; }')
252
+ reg('invisible', 'utilities', '.invisible { visibility: hidden; }')
253
+ reg('!visible', 'utilities', '.\\!visible { visibility: visible !important; }')
254
+ reg('!invisible', 'utilities', '.\\!invisible { visibility: hidden !important; }')
255
+
256
+ // Shadow
257
+ reg('shadow', 'utilities', '.shadow { box-shadow: var(--st-shadow); }')
258
+ reg('shadow-sm', 'utilities', '.shadow-sm { box-shadow: var(--st-shadow-sm); }')
259
+ reg('shadow-lg', 'utilities', '.shadow-lg { box-shadow: var(--st-shadow-lg); }')
260
+ reg('shadow-none', 'utilities', '.shadow-none { box-shadow: none; }')
261
+
262
+ // Z-index
263
+ Object.entries(Z_SCALE).forEach(([k, v]) => {
264
+ reg(`z-${k}`, 'utilities', `.z-${k} { z-index: ${v}; }`)
265
+ })
266
+
267
+ // Cursor
268
+ CURSOR_VALUES.forEach(v => {
269
+ reg(`cursor-${v}`, 'utilities', `.cursor-${v} { cursor: ${v}; }`)
270
+ })
271
+
272
+ // Object fit
273
+ OBJECT_FIT.forEach(v => {
274
+ reg(`object-fit-${v}`, 'utilities', `.object-fit-${v} { object-fit: ${v}; }`)
275
+ })
276
+
277
+ // Transitions
278
+ reg('transition', 'utilities', `.transition {
279
+ transition-property: color, background-color, border-color, box-shadow, opacity, transform, visibility;
280
+ transition-duration: var(--st-duration, 200ms);
281
+ transition-timing-function: var(--st-easing, cubic-bezier(0.4, 0, 0.2, 1));
282
+ }`)
283
+ reg('transition-fast', 'utilities', `.transition-fast {
284
+ transition-property: color, background-color, border-color, box-shadow, opacity, transform, visibility;
285
+ transition-duration: var(--st-duration-fast, 100ms);
286
+ transition-timing-function: var(--st-easing, cubic-bezier(0.4, 0, 0.2, 1));
287
+ }`)
288
+ reg('transition-slow', 'utilities', `.transition-slow {
289
+ transition-property: color, background-color, border-color, box-shadow, opacity, transform, visibility;
290
+ transition-duration: var(--st-duration-slow, 400ms);
291
+ transition-timing-function: var(--st-easing, cubic-bezier(0.4, 0, 0.2, 1));
292
+ }`)
293
+ reg('transition-none', 'utilities', '.transition-none { transition: none; }')
294
+
295
+ // Easing
296
+ Object.entries(EASE_MAP).forEach(([k, v]) => {
297
+ reg(`ease-${k}`, 'utilities', `.ease-${k} { transition-timing-function: ${v}; }`)
298
+ })
299
+
300
+ // ─── Grid — cols ─────────────────────────────────────────────────────
301
+ // 12 column system — matches Bootstrap exactly
302
+ // col-xs-* = col-* (xs is default, no media query needed)
303
+
304
+ for (let n = 1; n <= 12; n++) {
305
+ // 4 decimal places — browser-precise, ~30% fewer chars than 8dp
306
+ const pct = ((n / 12) * 100).toFixed(4).replace(/\.?0+$/, '') + '%'
307
+ // xs/base cols keep max-width:100% (same layer as .row>*, needs explicit override)
308
+ const colBase = `flex: 0 0 auto; width: ${pct}; max-width: 100%;`
309
+ // breakpoint cols drop max-width:100% — higher layer already beats .row>* without it
310
+ const colBp = `flex: 0 0 auto; width: ${pct};`
311
+
312
+ reg(`col-${n}`, 'components', `.col-${n} { ${colBase} }`)
313
+ reg(`col-xs-${n}`, 'components', `.col-xs-${n} { ${colBase} }`)
314
+
315
+ Object.keys(BP_VALUES).forEach(bp => {
316
+ reg(`col-${bp}-${n}`, 'components',
317
+ mq(bp, `.col-${bp}-${n} { ${colBp} }`))
318
+ })
319
+ }
320
+
321
+ // Auto width columns
322
+ reg('col', 'components', `.col { flex: 1 0 0%; }`)
323
+ reg('col-auto', 'components', `.col-auto { flex: 0 0 auto; width: auto; max-width: 100%; }`)
324
+
325
+ // Equal width at specific breakpoints
326
+ Object.keys(BP_VALUES).forEach(bp => {
327
+ reg(`col-${bp}`, 'components', mq(bp, `.col-${bp} { flex: 1 0 0%; }`))
328
+ reg(`col-${bp}-auto`, 'components', mq(bp, `.col-${bp}-auto { flex: 0 0 auto; width: auto; max-width: 100%; }`))
329
+ })
330
+
331
+ // ─── Grid — row ──────────────────────────────────────────────────────
332
+ // Full Bootstrap-equivalent row with gutter support
333
+
334
+ reg('row', 'components', `.row {
335
+ --st-gutter-x: 1.5rem;
336
+ --st-gutter-y: 0;
337
+ display: flex;
338
+ flex-wrap: wrap;
339
+ margin-top: calc(-1 * var(--st-gutter-y));
340
+ margin-right: calc(-0.5 * var(--st-gutter-x));
341
+ margin-left: calc(-0.5 * var(--st-gutter-x));
342
+ }
343
+
344
+ .row > * {
345
+ flex-shrink: 0;
346
+ width: 100%;
347
+ max-width: 100%;
348
+ padding-right: calc(var(--st-gutter-x) * 0.5);
349
+ padding-left: calc(var(--st-gutter-x) * 0.5);
350
+ margin-top: var(--st-gutter-y);
351
+ }`)
352
+
353
+ // Gutter utilities — g-0 through g-5
354
+ const GUTTER_SCALE = { '0':'0', '1':'0.25rem', '2':'0.5rem', '3':'1rem', '4':'1.5rem', '5':'3rem' }
355
+
356
+ Object.entries(GUTTER_SCALE).forEach(([k, v]) => {
357
+ // g-* — both axes
358
+ reg(`g-${k}`, 'utilities', `.g-${k} { --st-gutter-x: ${v}; --st-gutter-y: ${v}; }`)
359
+ // gx-* — horizontal only
360
+ reg(`gx-${k}`, 'utilities', `.gx-${k} { --st-gutter-x: ${v}; }`)
361
+ // gy-* — vertical only
362
+ reg(`gy-${k}`, 'utilities', `.gy-${k} { --st-gutter-y: ${v}; }`)
363
+
364
+ // Responsive gutter variants
365
+ Object.keys(BP_VALUES).forEach(bp => {
366
+ reg(`g-${bp}-${k}`, 'utilities', mq(bp, `.g-${bp}-${k} { --st-gutter-x: ${v}; --st-gutter-y: ${v}; }`))
367
+ reg(`gx-${bp}-${k}`, 'utilities', mq(bp, `.gx-${bp}-${k} { --st-gutter-x: ${v}; }`))
368
+ reg(`gy-${bp}-${k}`, 'utilities', mq(bp, `.gy-${bp}-${k} { --st-gutter-y: ${v}; }`))
369
+ })
370
+ })
371
+
372
+ // ─── Grid — container ────────────────────────────────────────────────
373
+ // Bootstrap-equivalent responsive containers with correct max-widths
374
+ // Bootstrap breakpoint max-widths:
375
+ // sm (≥576px) → 540px
376
+ // md (≥768px) → 720px
377
+ // lg (≥992px) → 960px
378
+ // xl (≥1200px) → 1140px
379
+ // xxl (≥1400px) → 1320px
380
+
381
+ const CONTAINER_MAX = {
382
+ sm: '540px',
383
+ md: '720px',
384
+ lg: '960px',
385
+ xl: '1140px',
386
+ xxl: '1320px',
387
+ }
388
+
389
+ const CONTAINER_BASE = `
390
+ width: 100%;
391
+ padding-right: calc(var(--st-gutter-x, 1.5rem) * 0.5);
392
+ padding-left: calc(var(--st-gutter-x, 1.5rem) * 0.5);
393
+ margin-right: auto;
394
+ margin-left: auto;`
395
+
396
+ // .container — responsive, grows with breakpoints
397
+ reg('container', 'components',
398
+ `.container {${CONTAINER_BASE}
399
+ }
400
+ @media (min-width: 576px) { .container { max-width: 540px; } }
401
+ @media (min-width: 768px) { .container { max-width: 720px; } }
402
+ @media (min-width: 992px) { .container { max-width: 960px; } }
403
+ @media (min-width: 1200px) { .container { max-width: 1140px; } }
404
+ @media (min-width: 1400px) { .container { max-width: 1320px; } }`)
405
+
406
+ // .container-fluid — always 100%
407
+ reg('container-fluid', 'components', `.container-fluid {${CONTAINER_BASE}
408
+ }`)
409
+
410
+ // .container-{bp} — 100% below breakpoint, max-width above
411
+ // e.g. container-md is full width on xs/sm, then 720px at md, 960px at lg etc.
412
+ const BP_ORDER = ['sm', 'md', 'lg', 'xl', 'xxl']
413
+
414
+ BP_ORDER.forEach((bp, i) => {
415
+ // Start with base styles
416
+ let css = `.container-${bp} {${CONTAINER_BASE}
417
+ }\n`
418
+ // Add max-widths from this breakpoint upward
419
+ BP_ORDER.slice(i).forEach(activeBp => {
420
+ css += `@media (min-width: ${BP_VALUES[activeBp]}) { .container-${bp} { max-width: ${CONTAINER_MAX[activeBp]}; } }\n`
421
+ })
422
+ reg(`container-${bp}`, 'components', css.trim())
423
+ })
424
+
425
+ // ─── Components — Card ───────────────────────────────────────────────
426
+
427
+ reg('card', 'components', `.card {
428
+ position: relative;
429
+ display: flex;
430
+ flex-direction: column;
431
+ min-width: 0;
432
+ word-wrap: break-word;
433
+ background: var(--st-bg);
434
+ border: 1px solid var(--st-border);
435
+ border-radius: var(--st-border-radius);
436
+ box-shadow: var(--st-shadow-sm);
437
+ }`)
438
+
439
+ reg('card-body', 'components', `.card-body {
440
+ flex: 1 1 auto;
441
+ padding: 1rem;
442
+ }`)
443
+
444
+ reg('card-header', 'components', `.card-header {
445
+ padding: 0.75rem 1rem;
446
+ background-color: var(--st-bg-secondary);
447
+ border-bottom: 1px solid var(--st-border);
448
+ border-radius: calc(var(--st-border-radius) - 1px) calc(var(--st-border-radius) - 1px) 0 0;
449
+ }`)
450
+
451
+ reg('card-footer', 'components', `.card-footer {
452
+ padding: 0.75rem 1rem;
453
+ background-color: var(--st-bg-secondary);
454
+ border-top: 1px solid var(--st-border);
455
+ border-radius: 0 0 calc(var(--st-border-radius) - 1px) calc(var(--st-border-radius) - 1px);
456
+ }`)
457
+
458
+ reg('card-title', 'components', `.card-title {
459
+ margin-bottom: 0.5rem;
460
+ font-size: 1.125rem;
461
+ font-weight: 600;
462
+ color: var(--st-text);
463
+ }`)
464
+
465
+ reg('card-subtitle', 'components', `.card-subtitle {
466
+ margin-top: -0.25rem;
467
+ margin-bottom: 0;
468
+ color: var(--st-text-muted);
469
+ font-size: 0.875rem;
470
+ }`)
471
+
472
+ reg('card-text', 'components', `.card-text {
473
+ font-size: 0.9375rem;
474
+ color: var(--st-text-muted);
475
+ line-height: 1.6;
476
+ margin-bottom: 0.75rem;
477
+ }
478
+
479
+ .card-text:last-child { margin-bottom: 0; }`)
480
+
481
+ reg('card-link', 'components', `.card-link {
482
+ color: var(--st-primary);
483
+ text-decoration: none;
484
+ }
485
+
486
+ .card-link:hover { color: var(--st-primary-hover); text-decoration: underline; }`)
487
+
488
+ reg('card-img-top', 'components', `.card-img-top {
489
+ width: 100%;
490
+ border-top-left-radius: calc(var(--st-border-radius) - 1px);
491
+ border-top-right-radius: calc(var(--st-border-radius) - 1px);
492
+ object-fit: cover;
493
+ display: block;
494
+ }`)
495
+
496
+ reg('card-img-bottom', 'components', `.card-img-bottom {
497
+ width: 100%;
498
+ border-bottom-left-radius: calc(var(--st-border-radius) - 1px);
499
+ border-bottom-right-radius: calc(var(--st-border-radius) - 1px);
500
+ object-fit: cover;
501
+ display: block;
502
+ }`)
503
+
504
+ reg('card-img', 'components', `.card-img {
505
+ width: 100%;
506
+ border-radius: calc(var(--st-border-radius) - 1px);
507
+ object-fit: cover;
508
+ display: block;
509
+ }`)
510
+
511
+ reg('card-img-overlay', 'components', `.card-img-overlay {
512
+ position: absolute;
513
+ inset: 0;
514
+ padding: 1rem;
515
+ border-radius: calc(var(--st-border-radius) - 1px);
516
+ background: rgba(0, 0, 0, 0.45);
517
+ color: #fff;
518
+ }`)
519
+
520
+ reg('card-group', 'components', `.card-group {
521
+ display: flex;
522
+ flex-flow: row wrap;
523
+ }
524
+
525
+ .card-group > .card {
526
+ flex: 1 0 0%;
527
+ margin-bottom: 0;
528
+ }
529
+
530
+ .card-group > .card + .card {
531
+ margin-left: 0;
532
+ border-left: 0;
533
+ border-top-left-radius: 0;
534
+ border-bottom-left-radius: 0;
535
+ }
536
+
537
+ .card-group > .card:not(:last-child) {
538
+ border-top-right-radius: 0;
539
+ border-bottom-right-radius: 0;
540
+ }`)
541
+
542
+ // ─── Components — Alert ──────────────────────────────────────────────
543
+
544
+ const ALERT_COLORS = {
545
+ primary: { bg: 'rgba(13,110,253,0.1)', border: 'rgba(13,110,253,0.3)', text: '#084298' },
546
+ secondary: { bg: 'rgba(108,117,125,0.1)', border: 'rgba(108,117,125,0.3)', text: '#41464b' },
547
+ success: { bg: 'rgba(25,135,84,0.1)', border: 'rgba(25,135,84,0.3)', text: '#0a3622' },
548
+ danger: { bg: 'rgba(220,53,69,0.1)', border: 'rgba(220,53,69,0.3)', text: '#842029' },
549
+ warning: { bg: 'rgba(255,193,7,0.1)', border: 'rgba(255,193,7,0.3)', text: '#664d03' },
550
+ info: { bg: 'rgba(13,202,240,0.1)', border: 'rgba(13,202,240,0.3)', text: '#055160' },
551
+ light: { bg: 'rgba(248,249,250,0.5)', border: 'rgba(248,249,250,0.8)', text: '#636464' },
552
+ dark: { bg: 'rgba(33,37,41,0.1)', border: 'rgba(33,37,41,0.3)', text: '#141619' },
553
+ }
554
+
555
+ reg('alert', 'components', `.alert {
556
+ position: relative;
557
+ padding: 1rem 1rem;
558
+ margin-bottom: 1rem;
559
+ border: 1px solid transparent;
560
+ border-radius: var(--st-border-radius);
561
+ font-size: 0.9375rem;
562
+ line-height: 1.5;
563
+ }`)
564
+
565
+ Object.entries(ALERT_COLORS).forEach(([color, { bg, border, text }]) => {
566
+ reg(`alert-${color}`, 'components', `.alert-${color} {
567
+ background-color: ${bg};
568
+ border-color: ${border};
569
+ color: ${text};
570
+ }`)
571
+ })
572
+
573
+ reg('alert-dismissible', 'components', `.alert-dismissible {
574
+ padding-right: 3rem;
575
+ }
576
+
577
+ .alert-dismissible .btn-close {
578
+ position: absolute;
579
+ top: 0;
580
+ right: 0;
581
+ z-index: 2;
582
+ padding: 1.25rem 1rem;
583
+ }`)
584
+
585
+ reg('alert-heading', 'components', `.alert-heading {
586
+ color: inherit;
587
+ font-weight: 600;
588
+ font-size: 1.1rem;
589
+ margin-top: 0;
590
+ }`)
591
+
592
+ reg('alert-link', 'components', `.alert-link {
593
+ font-weight: 700;
594
+ color: inherit;
595
+ }`)
596
+
597
+ // ─── Components — Badge ──────────────────────────────────────────────
598
+
599
+ reg('badge', 'components', `.badge {
600
+ display: inline-block;
601
+ padding: 0.35em 0.65em;
602
+ font-size: 0.75em;
603
+ font-weight: 700;
604
+ line-height: 1;
605
+ color: #fff;
606
+ text-align: center;
607
+ white-space: nowrap;
608
+ vertical-align: baseline;
609
+ border-radius: var(--st-border-radius);
610
+ background-color: var(--st-secondary);
611
+ }`)
612
+
613
+ const BADGE_COLORS = ['primary','secondary','success','danger','warning','info','light','dark']
614
+ BADGE_COLORS.forEach(color => {
615
+ const textColor = ['warning','info','light'].includes(color) ? 'var(--st-dark)' : '#fff'
616
+ reg(`badge-${color}`, 'components', `.badge-${color} {
617
+ background-color: var(--st-${color});
618
+ color: ${textColor};
619
+ }`)
620
+ })
621
+
622
+ reg('badge-pill', 'components', `.badge-pill { border-radius: 999px; }`)
623
+ reg('rounded-pill', 'components', `.rounded-pill { border-radius: 999px; }`)
624
+
625
+ // ─── Components — Buttons ────────────────────────────────────────────
626
+
627
+ const BTN_COLORS = ['primary','secondary','success','danger','warning','info','light','dark']
628
+
629
+ BTN_COLORS.forEach(color => {
630
+ const textColor = ['warning','info','light'].includes(color) ? 'var(--st-dark, #212529)' : '#fff'
631
+ reg(`btn-${color}`, 'components', `.btn-${color} {
632
+ display: inline-flex;
633
+ align-items: center;
634
+ justify-content: center;
635
+ padding: 0.375rem 0.75rem;
636
+ font-size: 1rem;
637
+ font-weight: 400;
638
+ line-height: 1.5;
639
+ color: ${textColor};
640
+ background-color: var(--st-${color});
641
+ border: 1px solid var(--st-${color});
642
+ border-radius: var(--st-border-radius);
643
+ cursor: pointer;
644
+ text-decoration: none;
645
+ white-space: nowrap;
646
+ vertical-align: middle;
647
+ user-select: none;
648
+ transition: color var(--st-duration) var(--st-easing),
649
+ background-color var(--st-duration) var(--st-easing),
650
+ border-color var(--st-duration) var(--st-easing),
651
+ box-shadow var(--st-duration) var(--st-easing);
652
+ }
653
+
654
+ .btn-${color}:hover {
655
+ background-color: var(--st-${color}-hover, color-mix(in srgb, var(--st-${color}) 85%, black));
656
+ border-color: var(--st-${color}-hover, color-mix(in srgb, var(--st-${color}) 85%, black));
657
+ color: ${textColor};
658
+ }
659
+
660
+ .btn-${color}:focus-visible {
661
+ box-shadow: var(--st-focus-ring);
662
+ outline: none;
663
+ }
664
+
665
+ .btn-${color}:active {
666
+ transform: scale(0.97);
667
+ }`)
668
+
669
+ reg(`btn-outline-${color}`, 'components', `.btn-outline-${color} {
670
+ display: inline-flex;
671
+ align-items: center;
672
+ justify-content: center;
673
+ padding: 0.375rem 0.75rem;
674
+ font-size: 1rem;
675
+ font-weight: 400;
676
+ line-height: 1.5;
677
+ color: var(--st-${color});
678
+ background-color: transparent;
679
+ border: 1px solid var(--st-${color});
680
+ border-radius: var(--st-border-radius);
681
+ cursor: pointer;
682
+ text-decoration: none;
683
+ white-space: nowrap;
684
+ user-select: none;
685
+ transition: color var(--st-duration) var(--st-easing),
686
+ background-color var(--st-duration) var(--st-easing),
687
+ box-shadow var(--st-duration) var(--st-easing);
688
+ }
689
+
690
+ .btn-outline-${color}:hover {
691
+ background-color: var(--st-${color});
692
+ color: ${textColor};
693
+ }
694
+
695
+ .btn-outline-${color}:focus-visible {
696
+ box-shadow: var(--st-focus-ring);
697
+ outline: none;
698
+ }`)
699
+ })
700
+
701
+ reg('btn-sm', 'components', `.btn-sm {
702
+ padding: 0.25rem 0.5rem;
703
+ font-size: 0.875rem;
704
+ border-radius: calc(var(--st-border-radius) * 0.75);
705
+ }`)
706
+
707
+ reg('btn-lg', 'components', `.btn-lg {
708
+ padding: 0.5rem 1rem;
709
+ font-size: 1.125rem;
710
+ border-radius: calc(var(--st-border-radius) * 1.5);
711
+ }`)
712
+
713
+ reg('btn-close', 'components', `.btn-close {
714
+ display: inline-flex;
715
+ align-items: center;
716
+ justify-content: center;
717
+ width: 1.25em;
718
+ height: 1.25em;
719
+ padding: 0.25em;
720
+ background: none;
721
+ border: none;
722
+ border-radius: var(--st-border-radius);
723
+ cursor: pointer;
724
+ opacity: 0.5;
725
+ font-size: 1rem;
726
+ color: var(--st-text);
727
+ transition: opacity var(--st-duration) var(--st-easing);
728
+ }
729
+
730
+ .btn-close::before {
731
+ content: '×';
732
+ font-size: 1.5em;
733
+ line-height: 1;
734
+ }
735
+
736
+ .btn-close:hover { opacity: 1; }`)
737
+
738
+ // ─── Components — Button Group ───────────────────────────────────────
739
+
740
+ reg('btn-group', 'components', `.btn-group {
741
+ position: relative;
742
+ display: inline-flex;
743
+ vertical-align: middle;
744
+ }
745
+
746
+ .btn-group > [class*="btn-"]:not(:first-child) {
747
+ border-top-left-radius: 0;
748
+ border-bottom-left-radius: 0;
749
+ margin-left: -1px;
750
+ }
751
+
752
+ .btn-group > [class*="btn-"]:not(:last-child) {
753
+ border-top-right-radius: 0;
754
+ border-bottom-right-radius: 0;
755
+ }`)
756
+
757
+ reg('btn-group-sm', 'components', `.btn-group-sm > [class*="btn-"] {
758
+ padding: 0.25rem 0.5rem;
759
+ font-size: 0.875rem;
760
+ border-radius: calc(var(--st-border-radius) * 0.75);
761
+ }`)
762
+
763
+ reg('btn-group-lg', 'components', `.btn-group-lg > [class*="btn-"] {
764
+ padding: 0.5rem 1rem;
765
+ font-size: 1.125rem;
766
+ border-radius: calc(var(--st-border-radius) * 1.5);
767
+ }`)
768
+
769
+ reg('btn-toolbar', 'components', `.btn-toolbar {
770
+ display: flex;
771
+ flex-wrap: wrap;
772
+ gap: 0.5rem;
773
+ align-items: center;
774
+ }`)
775
+
776
+ // ─── Components — Navbar ─────────────────────────────────────────────
777
+
778
+ reg('navbar', 'components', `.navbar {
779
+ position: relative;
780
+ display: flex;
781
+ flex-wrap: wrap;
782
+ align-items: center;
783
+ justify-content: space-between;
784
+ padding: 0.75rem 1rem;
785
+ background: var(--st-bg);
786
+ border-bottom: 1px solid var(--st-border);
787
+ }`)
788
+
789
+ reg('navbar-brand', 'components', `.navbar-brand {
790
+ display: inline-flex;
791
+ align-items: center;
792
+ padding: 0.25rem 0;
793
+ margin-right: 1rem;
794
+ font-size: 1.25rem;
795
+ font-weight: 700;
796
+ color: var(--st-text);
797
+ text-decoration: none;
798
+ white-space: nowrap;
799
+ }
800
+
801
+ .navbar-brand:hover { color: var(--st-primary); }`)
802
+
803
+ reg('navbar-nav', 'components', `.navbar-nav {
804
+ display: flex;
805
+ flex-direction: column;
806
+ padding-left: 0;
807
+ margin-bottom: 0;
808
+ list-style: none;
809
+ }
810
+
811
+ .navbar-nav .nav-link {
812
+ padding-right: 0;
813
+ padding-left: 0;
814
+ }`)
815
+
816
+ reg('navbar-toggler', 'components', `.navbar-toggler {
817
+ padding: 0.25rem 0.75rem;
818
+ font-size: 1.25rem;
819
+ line-height: 1;
820
+ background: transparent;
821
+ border: 1px solid var(--st-border);
822
+ border-radius: var(--st-border-radius);
823
+ cursor: pointer;
824
+ color: var(--st-text);
825
+ transition: box-shadow var(--st-duration) var(--st-easing);
826
+ }
827
+
828
+ .navbar-toggler:focus-visible { box-shadow: var(--st-focus-ring); }`)
829
+
830
+ reg('navbar-toggler-icon', 'components', `.navbar-toggler-icon {
831
+ display: inline-block;
832
+ width: 1.5em;
833
+ height: 1.5em;
834
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
835
+ background-repeat: no-repeat;
836
+ background-position: center;
837
+ background-size: 100%;
838
+ vertical-align: middle;
839
+ }`)
840
+
841
+ reg('navbar-collapse', 'components', `.navbar-collapse {
842
+ flex-basis: 100%;
843
+ flex-grow: 1;
844
+ align-items: center;
845
+ }`)
846
+
847
+ reg('navbar-text', 'components', `.navbar-text {
848
+ padding-top: 0.5rem;
849
+ padding-bottom: 0.5rem;
850
+ color: var(--st-text-muted);
851
+ }`)
852
+
853
+ // navbar-expand-* — at breakpoint navbar-nav becomes horizontal
854
+ Object.keys(BP_VALUES).forEach(bp => {
855
+ reg(`navbar-expand-${bp}`, 'components', mq(bp, `.navbar-expand-${bp} {
856
+ flex-wrap: nowrap;
857
+ justify-content: flex-start;
858
+ }
859
+
860
+ .navbar-expand-${bp} .navbar-nav {
861
+ flex-direction: row;
862
+ }
863
+
864
+ .navbar-expand-${bp} .navbar-nav .nav-link {
865
+ padding-right: 0.5rem;
866
+ padding-left: 0.5rem;
867
+ }
868
+
869
+ .navbar-expand-${bp} .navbar-collapse {
870
+ display: flex;
871
+ flex-basis: auto;
872
+ }
873
+
874
+ .navbar-expand-${bp} .navbar-toggler {
875
+ display: none;
876
+ }`))
877
+ })
878
+
879
+ // ─── Components — Nav ────────────────────────────────────────────────
880
+
881
+ reg('nav', 'components', `.nav {
882
+ display: flex;
883
+ flex-wrap: wrap;
884
+ padding: 0;
885
+ margin: 0;
886
+ list-style: none;
887
+ }`)
888
+
889
+ reg('nav-item', 'components', `.nav-item { flex-shrink: 0; }`)
890
+
891
+ reg('nav-link', 'components', `.nav-link {
892
+ display: block;
893
+ padding: 0.5rem 1rem;
894
+ font-size: 0.9375rem;
895
+ color: var(--st-primary);
896
+ text-decoration: none;
897
+ background: none;
898
+ border: 0;
899
+ cursor: pointer;
900
+ transition: color var(--st-duration) var(--st-easing),
901
+ background-color var(--st-duration) var(--st-easing);
902
+ }
903
+
904
+ .nav-link:hover { color: var(--st-primary-hover); }
905
+
906
+ .nav-link.active {
907
+ color: var(--st-text);
908
+ font-weight: 600;
909
+ }
910
+
911
+ .nav-link.disabled {
912
+ color: var(--st-text-muted);
913
+ pointer-events: none;
914
+ cursor: default;
915
+ }`)
916
+
917
+ reg('nav-tabs', 'components', `.nav-tabs {
918
+ border-bottom: 2px solid var(--st-border);
919
+ }
920
+
921
+ .nav-tabs .nav-link {
922
+ margin-bottom: -2px;
923
+ border: 2px solid transparent;
924
+ border-top-left-radius: var(--st-border-radius);
925
+ border-top-right-radius: var(--st-border-radius);
926
+ color: var(--st-text-muted);
927
+ }
928
+
929
+ .nav-tabs .nav-link:hover {
930
+ color: var(--st-text);
931
+ border-color: var(--st-border) var(--st-border) transparent;
932
+ }
933
+
934
+ .nav-tabs .nav-link.active {
935
+ color: var(--st-text);
936
+ background-color: var(--st-bg);
937
+ border-color: var(--st-border) var(--st-border) var(--st-bg);
938
+ }`)
939
+
940
+ reg('nav-pills', 'components', `.nav-pills .nav-link {
941
+ border-radius: var(--st-border-radius);
942
+ color: var(--st-text-muted);
943
+ }
944
+
945
+ .nav-pills .nav-link:hover {
946
+ background-color: var(--st-bg-secondary);
947
+ color: var(--st-text);
948
+ }
949
+
950
+ .nav-pills .nav-link.active {
951
+ background-color: var(--st-primary);
952
+ color: #fff;
953
+ }`)
954
+
955
+ reg('nav-fill', 'components', `.nav-fill .nav-item { flex: 1 1 auto; text-align: center; }`)
956
+ reg('nav-justified', 'components', `.nav-justified .nav-item { flex-basis: 0; flex-grow: 1; text-align: center; }`)
957
+
958
+ reg('tab-content', 'components', `.tab-content > .tab-pane { display: none; }
959
+ .tab-content > .tab-pane.active { display: block; }`)
960
+
961
+ // ─── Components — Table ──────────────────────────────────────────────
962
+
963
+ reg('table', 'components', `.table {
964
+ width: 100%;
965
+ margin-bottom: 1rem;
966
+ color: var(--st-text);
967
+ vertical-align: top;
968
+ border-color: var(--st-border);
969
+ border-collapse: collapse;
970
+ }
971
+
972
+ .table > thead {
973
+ vertical-align: bottom;
974
+ }
975
+
976
+ .table > :not(caption) > * > * {
977
+ padding: 0.75rem;
978
+ border-bottom: 1px solid var(--st-border);
979
+ }
980
+
981
+ .table > thead > tr > th {
982
+ font-weight: 600;
983
+ font-size: 0.8125rem;
984
+ text-transform: uppercase;
985
+ letter-spacing: 0.04em;
986
+ color: var(--st-text-muted);
987
+ background-color: var(--st-bg-secondary);
988
+ border-bottom: 2px solid var(--st-border);
989
+ }
990
+
991
+ .table > tbody > tr:last-child > * { border-bottom: none; }`)
992
+
993
+ reg('table-striped', 'components', `.table-striped > tbody > tr:nth-child(odd) > * {
994
+ background-color: var(--st-bg-secondary);
995
+ }`)
996
+
997
+ reg('table-hover', 'components', `.table-hover > tbody > tr:hover > * {
998
+ background-color: color-mix(in srgb, var(--st-primary) 5%, var(--st-bg));
999
+ }`)
1000
+
1001
+ reg('table-bordered', 'components', `.table-bordered > :not(caption) > * {
1002
+ border-width: 1px 0;
1003
+ }
1004
+
1005
+ .table-bordered > :not(caption) > * > * {
1006
+ border-width: 0 1px;
1007
+ border-style: solid;
1008
+ border-color: var(--st-border);
1009
+ }`)
1010
+
1011
+ reg('table-borderless', 'components', `.table-borderless > :not(caption) > * > * { border-bottom: none; }`)
1012
+
1013
+ reg('table-sm', 'components', `.table-sm > :not(caption) > * > * { padding: 0.35rem 0.5rem; }`)
1014
+
1015
+ reg('table-responsive', 'components', `.table-responsive {
1016
+ overflow-x: auto;
1017
+ -webkit-overflow-scrolling: touch;
1018
+ }`)
1019
+
1020
+ reg('caption-top', 'components', `.caption-top { caption-side: top; }`)
1021
+
1022
+ // ─── Components — Form ───────────────────────────────────────────────
1023
+
1024
+ reg('form-label', 'components', `.form-label {
1025
+ display: block;
1026
+ margin-bottom: 0.375rem;
1027
+ font-size: 0.9375rem;
1028
+ font-weight: 500;
1029
+ color: var(--st-text);
1030
+ }`)
1031
+
1032
+ reg('form-control', 'components', `.form-control {
1033
+ display: block;
1034
+ width: 100%;
1035
+ padding: 0.375rem 0.75rem;
1036
+ font-size: 1rem;
1037
+ font-weight: 400;
1038
+ line-height: 1.5;
1039
+ color: var(--st-text);
1040
+ background-color: var(--st-bg);
1041
+ border: 1px solid var(--st-border);
1042
+ border-radius: var(--st-border-radius);
1043
+ outline: none;
1044
+ transition: border-color var(--st-duration) var(--st-easing),
1045
+ box-shadow var(--st-duration) var(--st-easing);
1046
+ appearance: none;
1047
+ }
1048
+
1049
+ .form-control::placeholder { color: var(--st-text-muted); opacity: 0.7; }
1050
+
1051
+ .form-control:focus {
1052
+ border-color: var(--st-primary);
1053
+ box-shadow: var(--st-focus-ring);
1054
+ }
1055
+
1056
+ .form-control:disabled,
1057
+ .form-control[readonly] {
1058
+ background-color: var(--st-bg-secondary);
1059
+ opacity: 0.7;
1060
+ }`)
1061
+
1062
+ reg('form-control-sm', 'components', `.form-control-sm {
1063
+ min-height: calc(1.5em + 0.5rem + 2px);
1064
+ padding: 0.25rem 0.5rem;
1065
+ font-size: 0.875rem;
1066
+ border-radius: calc(var(--st-border-radius) * 0.75);
1067
+ }`)
1068
+
1069
+ reg('form-control-lg', 'components', `.form-control-lg {
1070
+ min-height: calc(1.5em + 1rem + 2px);
1071
+ padding: 0.5rem 1rem;
1072
+ font-size: 1.125rem;
1073
+ border-radius: calc(var(--st-border-radius) * 1.5);
1074
+ }`)
1075
+
1076
+ reg('form-select', 'components', `.form-select {
1077
+ display: block;
1078
+ width: 100%;
1079
+ padding: 0.375rem 2.25rem 0.375rem 0.75rem;
1080
+ font-size: 1rem;
1081
+ font-weight: 400;
1082
+ line-height: 1.5;
1083
+ color: var(--st-text);
1084
+ background-color: var(--st-bg);
1085
+ border: 1px solid var(--st-border);
1086
+ border-radius: var(--st-border-radius);
1087
+ outline: none;
1088
+ cursor: pointer;
1089
+ appearance: none;
1090
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
1091
+ background-repeat: no-repeat;
1092
+ background-position: right 0.75rem center;
1093
+ background-size: 16px 12px;
1094
+ transition: border-color var(--st-duration) var(--st-easing),
1095
+ box-shadow var(--st-duration) var(--st-easing);
1096
+ }
1097
+
1098
+ .form-select:focus {
1099
+ border-color: var(--st-primary);
1100
+ box-shadow: var(--st-focus-ring);
1101
+ }
1102
+
1103
+ .form-select:disabled {
1104
+ background-color: var(--st-bg-secondary);
1105
+ opacity: 0.7;
1106
+ }`)
1107
+
1108
+ reg('form-check', 'components', `.form-check {
1109
+ display: block;
1110
+ min-height: 1.5rem;
1111
+ padding-left: 1.75rem;
1112
+ margin-bottom: 0.25rem;
1113
+ }`)
1114
+
1115
+ reg('form-check-input', 'components', `.form-check-input {
1116
+ float: left;
1117
+ margin-left: -1.75rem;
1118
+ width: 1.125rem;
1119
+ height: 1.125rem;
1120
+ margin-top: 0.1875rem;
1121
+ background-color: var(--st-bg);
1122
+ border: 1px solid var(--st-border);
1123
+ border-radius: 0.25rem;
1124
+ appearance: none;
1125
+ cursor: pointer;
1126
+ transition: background-color var(--st-duration) var(--st-easing),
1127
+ border-color var(--st-duration) var(--st-easing),
1128
+ box-shadow var(--st-duration) var(--st-easing);
1129
+ }
1130
+
1131
+ .form-check-input[type="radio"] { border-radius: 50%; }
1132
+
1133
+ .form-check-input:checked {
1134
+ background-color: var(--st-primary);
1135
+ border-color: var(--st-primary);
1136
+ }
1137
+
1138
+ .form-check-input:focus { box-shadow: var(--st-focus-ring); }`)
1139
+
1140
+ reg('form-check-label', 'components', `.form-check-label {
1141
+ font-size: 0.9375rem;
1142
+ color: var(--st-text);
1143
+ cursor: pointer;
1144
+ }`)
1145
+
1146
+ reg('form-check-inline', 'components', `.form-check-inline {
1147
+ display: inline-flex;
1148
+ align-items: center;
1149
+ margin-right: 1rem;
1150
+ }`)
1151
+
1152
+ reg('form-switch', 'components', `.form-switch {
1153
+ padding-left: 2.5rem;
1154
+ }
1155
+
1156
+ .form-switch .form-check-input {
1157
+ width: 2rem;
1158
+ border-radius: 999px;
1159
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280,0,0,.25%29'/%3e%3c/svg%3e");
1160
+ background-repeat: no-repeat;
1161
+ background-position: left center;
1162
+ transition: background-position var(--st-duration) var(--st-easing);
1163
+ }
1164
+
1165
+ .form-switch .form-check-input:checked {
1166
+ background-position: right center;
1167
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");
1168
+ }`)
1169
+
1170
+ reg('form-range', 'components', `.form-range {
1171
+ width: 100%;
1172
+ height: 1.5rem;
1173
+ padding: 0;
1174
+ cursor: pointer;
1175
+ appearance: none;
1176
+ background: transparent;
1177
+ }
1178
+
1179
+ .form-range::-webkit-slider-runnable-track {
1180
+ height: 0.5rem;
1181
+ border-radius: 999px;
1182
+ background: var(--st-border);
1183
+ }
1184
+
1185
+ .form-range::-webkit-slider-thumb {
1186
+ width: 1rem;
1187
+ height: 1rem;
1188
+ margin-top: -0.25rem;
1189
+ background: var(--st-primary);
1190
+ border: none;
1191
+ border-radius: 50%;
1192
+ appearance: none;
1193
+ }`)
1194
+
1195
+ reg('form-text', 'components', `.form-text {
1196
+ display: block;
1197
+ margin-top: 0.25rem;
1198
+ font-size: 0.875rem;
1199
+ color: var(--st-text-muted);
1200
+ }`)
1201
+
1202
+ reg('form-floating', 'components', `.form-floating {
1203
+ position: relative;
1204
+ }
1205
+
1206
+ .form-floating > .form-control,
1207
+ .form-floating > .form-select {
1208
+ height: calc(3.5rem + 2px);
1209
+ line-height: 1.25;
1210
+ padding: 1rem 0.75rem;
1211
+ }
1212
+
1213
+ .form-floating > label {
1214
+ position: absolute;
1215
+ top: 0;
1216
+ left: 0;
1217
+ width: 100%;
1218
+ height: 100%;
1219
+ padding: 1rem 0.75rem;
1220
+ pointer-events: none;
1221
+ color: var(--st-text-muted);
1222
+ transition: opacity var(--st-duration) var(--st-easing),
1223
+ transform var(--st-duration) var(--st-easing);
1224
+ transform-origin: 0 0;
1225
+ }
1226
+
1227
+ .form-floating > .form-control:focus ~ label,
1228
+ .form-floating > .form-control:not(:placeholder-shown) ~ label {
1229
+ opacity: 0.65;
1230
+ transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);
1231
+ }`)
1232
+
1233
+ reg('input-group', 'components', `.input-group {
1234
+ position: relative;
1235
+ display: flex;
1236
+ flex-wrap: wrap;
1237
+ align-items: stretch;
1238
+ width: 100%;
1239
+ }
1240
+
1241
+ .input-group > .form-control,
1242
+ .input-group > .form-select {
1243
+ position: relative;
1244
+ flex: 1 1 auto;
1245
+ width: 1%;
1246
+ min-width: 0;
1247
+ }
1248
+
1249
+ .input-group > .form-control:not(:last-child),
1250
+ .input-group > .form-select:not(:last-child) {
1251
+ border-top-right-radius: 0;
1252
+ border-bottom-right-radius: 0;
1253
+ }
1254
+
1255
+ .input-group > .form-control:not(:first-child),
1256
+ .input-group > .form-select:not(:first-child) {
1257
+ border-top-left-radius: 0;
1258
+ border-bottom-left-radius: 0;
1259
+ margin-left: -1px;
1260
+ }`)
1261
+
1262
+ reg('input-group-text', 'components', `.input-group-text {
1263
+ display: flex;
1264
+ align-items: center;
1265
+ padding: 0.375rem 0.75rem;
1266
+ font-size: 1rem;
1267
+ font-weight: 400;
1268
+ line-height: 1.5;
1269
+ color: var(--st-text-muted);
1270
+ text-align: center;
1271
+ white-space: nowrap;
1272
+ background-color: var(--st-bg-secondary);
1273
+ border: 1px solid var(--st-border);
1274
+ border-radius: var(--st-border-radius);
1275
+ }`)
1276
+
1277
+ reg('input-group-sm', 'components', `.input-group-sm > .form-control,
1278
+ .input-group-sm > .form-select,
1279
+ .input-group-sm > .input-group-text,
1280
+ .input-group-sm > [class*="btn-"] {
1281
+ padding: 0.25rem 0.5rem;
1282
+ font-size: 0.875rem;
1283
+ border-radius: calc(var(--st-border-radius) * 0.75);
1284
+ }`)
1285
+
1286
+ reg('input-group-lg', 'components', `.input-group-lg > .form-control,
1287
+ .input-group-lg > .form-select,
1288
+ .input-group-lg > .input-group-text,
1289
+ .input-group-lg > [class*="btn-"] {
1290
+ padding: 0.5rem 1rem;
1291
+ font-size: 1.125rem;
1292
+ border-radius: calc(var(--st-border-radius) * 1.5);
1293
+ }`)
1294
+
1295
+ reg('valid-feedback', 'components', `.valid-feedback { display: none; font-size: 0.875rem; color: var(--st-success); margin-top: 0.25rem; }`)
1296
+ reg('invalid-feedback', 'components', `.invalid-feedback { display: none; font-size: 0.875rem; color: var(--st-danger); margin-top: 0.25rem; }`)
1297
+ reg('was-validated', 'components', `.was-validated .form-control:valid { border-color: var(--st-success); }
1298
+ .was-validated .form-control:invalid { border-color: var(--st-danger); }
1299
+ .was-validated .form-control:valid ~ .valid-feedback { display: block; }
1300
+ .was-validated .form-control:invalid ~ .invalid-feedback { display: block; }`)
1301
+
1302
+ // ─── Components — List Group ─────────────────────────────────────────
1303
+
1304
+ reg('list-group', 'components', `.list-group {
1305
+ display: flex;
1306
+ flex-direction: column;
1307
+ padding-left: 0;
1308
+ margin-bottom: 0;
1309
+ border-radius: var(--st-border-radius);
1310
+ overflow: hidden;
1311
+ }`)
1312
+
1313
+ reg('list-group-item', 'components', `.list-group-item {
1314
+ position: relative;
1315
+ display: block;
1316
+ padding: 0.75rem 1rem;
1317
+ color: var(--st-text);
1318
+ background-color: var(--st-bg);
1319
+ border: 1px solid var(--st-border);
1320
+ margin-bottom: -1px;
1321
+ }
1322
+
1323
+ .list-group-item:first-child {
1324
+ border-top-left-radius: inherit;
1325
+ border-top-right-radius: inherit;
1326
+ }
1327
+
1328
+ .list-group-item:last-child {
1329
+ border-bottom-left-radius: inherit;
1330
+ border-bottom-right-radius: inherit;
1331
+ margin-bottom: 0;
1332
+ }
1333
+
1334
+ .list-group-item.active {
1335
+ z-index: 2;
1336
+ background-color: var(--st-primary);
1337
+ border-color: var(--st-primary);
1338
+ color: #fff;
1339
+ }
1340
+
1341
+ .list-group-item.disabled {
1342
+ color: var(--st-text-muted);
1343
+ pointer-events: none;
1344
+ background-color: var(--st-bg-secondary);
1345
+ }`)
1346
+
1347
+ reg('list-group-item-action', 'components', `.list-group-item-action {
1348
+ width: 100%;
1349
+ color: var(--st-text);
1350
+ text-align: inherit;
1351
+ text-decoration: none;
1352
+ cursor: pointer;
1353
+ transition: background-color var(--st-duration) var(--st-easing),
1354
+ color var(--st-duration) var(--st-easing);
1355
+ }
1356
+
1357
+ .list-group-item-action:hover,
1358
+ .list-group-item-action:focus {
1359
+ background-color: var(--st-bg-secondary);
1360
+ color: var(--st-text);
1361
+ z-index: 1;
1362
+ }`)
1363
+
1364
+ reg('list-group-flush', 'components', `.list-group-flush {
1365
+ border-radius: 0;
1366
+ }
1367
+
1368
+ .list-group-flush .list-group-item {
1369
+ border-right: 0;
1370
+ border-left: 0;
1371
+ border-radius: 0;
1372
+ }
1373
+
1374
+ .list-group-flush > .list-group-item:first-child { border-top: 0; }
1375
+ .list-group-flush > .list-group-item:last-child { border-bottom: 0; }`)
1376
+
1377
+ reg('list-group-horizontal', 'components', `.list-group-horizontal {
1378
+ flex-direction: row;
1379
+ }
1380
+
1381
+ .list-group-horizontal .list-group-item {
1382
+ border-bottom: 1px solid var(--st-border);
1383
+ margin-bottom: 0;
1384
+ margin-right: -1px;
1385
+ }
1386
+
1387
+ .list-group-horizontal .list-group-item:first-child {
1388
+ border-top-left-radius: var(--st-border-radius);
1389
+ border-bottom-left-radius: var(--st-border-radius);
1390
+ border-top-right-radius: 0;
1391
+ }
1392
+
1393
+ .list-group-horizontal .list-group-item:last-child {
1394
+ border-top-right-radius: var(--st-border-radius);
1395
+ border-bottom-right-radius: var(--st-border-radius);
1396
+ border-bottom-left-radius: 0;
1397
+ margin-right: 0;
1398
+ }`)
1399
+
1400
+ // ─── Components — Progress ───────────────────────────────────────────
1401
+
1402
+ reg('progress', 'components', `.progress {
1403
+ display: flex;
1404
+ height: 0.75rem;
1405
+ overflow: hidden;
1406
+ background-color: var(--st-bg-secondary);
1407
+ border: 1px solid var(--st-border);
1408
+ border-radius: 999px;
1409
+ }`)
1410
+
1411
+ reg('progress-bar', 'components', `.progress-bar {
1412
+ display: flex;
1413
+ flex-direction: column;
1414
+ justify-content: center;
1415
+ overflow: hidden;
1416
+ color: #fff;
1417
+ text-align: center;
1418
+ white-space: nowrap;
1419
+ background-color: var(--st-primary);
1420
+ transition: width var(--st-duration-slow) var(--st-easing);
1421
+ font-size: 0.7rem;
1422
+ }
1423
+
1424
+ .progress-bar-striped {
1425
+ background-image: linear-gradient(
1426
+ 45deg,
1427
+ rgba(255,255,255,0.15) 25%,
1428
+ transparent 25%,
1429
+ transparent 50%,
1430
+ rgba(255,255,255,0.15) 50%,
1431
+ rgba(255,255,255,0.15) 75%,
1432
+ transparent 75%,
1433
+ transparent
1434
+ );
1435
+ background-size: 1rem 1rem;
1436
+ }`)
1437
+
1438
+ // ─── Components — Spinner ────────────────────────────────────────────
1439
+
1440
+ reg('spinner-border', 'components', `.spinner-border {
1441
+ display: inline-block;
1442
+ width: 2rem;
1443
+ height: 2rem;
1444
+ vertical-align: text-bottom;
1445
+ border: 0.25em solid var(--st-primary);
1446
+ border-right-color: transparent;
1447
+ border-radius: 50%;
1448
+ animation: spinner-border 0.75s linear infinite;
1449
+ }
1450
+
1451
+ .spinner-border-sm {
1452
+ width: 1rem;
1453
+ height: 1rem;
1454
+ border-width: 0.2em;
1455
+ }
1456
+
1457
+ @keyframes spinner-border {
1458
+ to { transform: rotate(360deg); }
1459
+ }`)
1460
+
1461
+ reg('spinner-grow', 'components', `.spinner-grow {
1462
+ display: inline-block;
1463
+ width: 2rem;
1464
+ height: 2rem;
1465
+ vertical-align: text-bottom;
1466
+ background-color: var(--st-primary);
1467
+ border-radius: 50%;
1468
+ opacity: 0;
1469
+ animation: spinner-grow 0.75s linear infinite;
1470
+ }
1471
+
1472
+ .spinner-grow-sm {
1473
+ width: 1rem;
1474
+ height: 1rem;
1475
+ }
1476
+
1477
+ @keyframes spinner-grow {
1478
+ 0% { transform: scale(0); }
1479
+ 50% { opacity: 0.5; }
1480
+ 100% { opacity: 0; transform: scale(1); }
1481
+ }`)
1482
+
1483
+ // ─── Components — Breadcrumb ─────────────────────────────────────────
1484
+
1485
+ reg('breadcrumb', 'components', `.breadcrumb {
1486
+ display: flex;
1487
+ flex-wrap: wrap;
1488
+ padding: 0;
1489
+ margin: 0;
1490
+ list-style: none;
1491
+ font-size: 0.875rem;
1492
+ }`)
1493
+
1494
+ reg('breadcrumb-item', 'components', `.breadcrumb-item + .breadcrumb-item {
1495
+ padding-left: 0.5rem;
1496
+ }
1497
+
1498
+ .breadcrumb-item + .breadcrumb-item::before {
1499
+ float: left;
1500
+ padding-right: 0.5rem;
1501
+ color: var(--st-text-muted);
1502
+ content: "/";
1503
+ }
1504
+
1505
+ .breadcrumb-item.active { color: var(--st-text-muted); }
1506
+
1507
+ .breadcrumb-item a {
1508
+ color: var(--st-primary);
1509
+ text-decoration: none;
1510
+ }
1511
+
1512
+ .breadcrumb-item a:hover { text-decoration: underline; }`)
1513
+
1514
+ // ─── Components — Pagination ─────────────────────────────────────────
1515
+
1516
+ reg('pagination', 'components', `.pagination {
1517
+ display: flex;
1518
+ padding: 0;
1519
+ margin: 0;
1520
+ list-style: none;
1521
+ flex-wrap: wrap;
1522
+ gap: 0.25rem;
1523
+ }`)
1524
+
1525
+ reg('page-item', 'components', `.page-item.disabled .page-link {
1526
+ color: var(--st-text-muted);
1527
+ pointer-events: none;
1528
+ background: var(--st-bg-secondary);
1529
+ border-color: var(--st-border);
1530
+ }
1531
+
1532
+ .page-item.active .page-link {
1533
+ background-color: var(--st-primary);
1534
+ border-color: var(--st-primary);
1535
+ color: #fff;
1536
+ }`)
1537
+
1538
+ reg('page-link', 'components', `.page-link {
1539
+ display: flex;
1540
+ align-items: center;
1541
+ justify-content: center;
1542
+ padding: 0.375rem 0.75rem;
1543
+ min-width: 2.25rem;
1544
+ font-size: 0.9375rem;
1545
+ color: var(--st-primary);
1546
+ background-color: var(--st-bg);
1547
+ border: 1px solid var(--st-border);
1548
+ border-radius: var(--st-border-radius);
1549
+ text-decoration: none;
1550
+ cursor: pointer;
1551
+ transition: color var(--st-duration) var(--st-easing),
1552
+ background-color var(--st-duration) var(--st-easing);
1553
+ }
1554
+
1555
+ .page-link:hover {
1556
+ background-color: var(--st-bg-secondary);
1557
+ color: var(--st-primary-hover);
1558
+ }
1559
+
1560
+ .page-link:focus-visible { box-shadow: var(--st-focus-ring); outline: none; }`)
1561
+
1562
+ // ─── Components — Modal ──────────────────────────────────────────────
1563
+ // Modal uses data-st-visible for show/hide — no JavaScript plugin needed
1564
+ // Toggle: element.setAttribute('data-st-visible', 'true')
1565
+
1566
+ reg('modal', 'components', `.modal {
1567
+ position: fixed;
1568
+ inset: 0;
1569
+ z-index: var(--st-z-modal, 1050);
1570
+ overflow-x: hidden;
1571
+ overflow-y: auto;
1572
+ outline: 0;
1573
+ visibility: hidden;
1574
+ opacity: 0;
1575
+ overscroll-behavior: contain;
1576
+ transition: opacity var(--st-duration) var(--st-easing),
1577
+ visibility var(--st-duration) var(--st-easing);
1578
+ }
1579
+
1580
+ .modal[data-st-visible="true"] {
1581
+ visibility: visible;
1582
+ opacity: 1;
1583
+ }
1584
+
1585
+ @keyframes st-modal-shake {
1586
+ 0%, 100% { transform: translateX(0); }
1587
+ 25% { transform: translateX(-5px); }
1588
+ 75% { transform: translateX(5px); }
1589
+ }
1590
+
1591
+ .modal.modal-static .modal-dialog {
1592
+ animation: st-modal-shake 0.25s var(--st-easing);
1593
+ }
1594
+
1595
+ body.modal-open {
1596
+ overflow: hidden;
1597
+ padding-right: var(--st-scrollbar-width, 0);
1598
+ }`)
1599
+
1600
+ reg('modal-backdrop', 'components', `.modal-backdrop {
1601
+ position: fixed;
1602
+ inset: 0;
1603
+ z-index: var(--st-z-modal-backdrop, 1040);
1604
+ background: rgba(0, 0, 0, 0.5);
1605
+ visibility: hidden;
1606
+ opacity: 0;
1607
+ transition: opacity var(--st-duration) var(--st-easing),
1608
+ visibility var(--st-duration) var(--st-easing);
1609
+ }
1610
+
1611
+ .modal-backdrop[data-st-visible="true"] {
1612
+ visibility: visible;
1613
+ opacity: 1;
1614
+ }`)
1615
+
1616
+ reg('modal-dialog', 'components', `.modal-dialog {
1617
+ position: relative;
1618
+ width: auto;
1619
+ max-width: 500px;
1620
+ margin: 1.75rem auto;
1621
+ pointer-events: none;
1622
+ transform: translateY(-1.5rem);
1623
+ transition: transform var(--st-duration) var(--st-easing);
1624
+ }
1625
+
1626
+ @media (max-width: 575.98px) {
1627
+ .modal-dialog { margin: 0.5rem; }
1628
+ }
1629
+
1630
+ .modal[data-st-visible="true"] .modal-dialog {
1631
+ transform: translateY(0);
1632
+ }`)
1633
+
1634
+ reg('modal-dialog-centered', 'components', `.modal-dialog-centered {
1635
+ display: flex;
1636
+ align-items: center;
1637
+ min-height: calc(100% - 3.5rem);
1638
+ }`)
1639
+
1640
+ reg('modal-dialog-scrollable', 'components', `.modal-dialog-scrollable {
1641
+ height: calc(100% - 3.5rem);
1642
+ }
1643
+
1644
+ .modal-dialog-scrollable .modal-content {
1645
+ max-height: 100%;
1646
+ overflow: hidden;
1647
+ }
1648
+
1649
+ .modal-dialog-scrollable .modal-body {
1650
+ overflow-y: auto;
1651
+ }`)
1652
+
1653
+ reg('modal-content', 'components', `.modal-content {
1654
+ position: relative;
1655
+ display: flex;
1656
+ flex-direction: column;
1657
+ width: 100%;
1658
+ pointer-events: auto;
1659
+ background-color: var(--st-bg);
1660
+ border: 1px solid var(--st-border);
1661
+ border-radius: calc(var(--st-border-radius) * 1.5);
1662
+ box-shadow: var(--st-shadow);
1663
+ outline: 0;
1664
+ }`)
1665
+
1666
+ reg('modal-header', 'components', `.modal-header {
1667
+ display: flex;
1668
+ flex-shrink: 0;
1669
+ align-items: center;
1670
+ justify-content: space-between;
1671
+ padding: 1rem 1.25rem;
1672
+ border-bottom: 1px solid var(--st-border);
1673
+ border-radius: calc(var(--st-border-radius) * 1.5) calc(var(--st-border-radius) * 1.5) 0 0;
1674
+ }`)
1675
+
1676
+ reg('modal-title', 'components', `.modal-title {
1677
+ margin: 0;
1678
+ font-size: 1.125rem;
1679
+ font-weight: 600;
1680
+ color: var(--st-text);
1681
+ line-height: 1.5;
1682
+ }`)
1683
+
1684
+ reg('modal-body', 'components', `.modal-body {
1685
+ position: relative;
1686
+ flex: 1 1 auto;
1687
+ padding: 1.25rem;
1688
+ color: var(--st-text);
1689
+ }`)
1690
+
1691
+ reg('modal-footer', 'components', `.modal-footer {
1692
+ display: flex;
1693
+ flex-shrink: 0;
1694
+ flex-wrap: wrap;
1695
+ align-items: center;
1696
+ justify-content: flex-end;
1697
+ padding: 0.75rem 1.25rem;
1698
+ gap: 0.5rem;
1699
+ border-top: 1px solid var(--st-border);
1700
+ border-radius: 0 0 calc(var(--st-border-radius) * 1.5) calc(var(--st-border-radius) * 1.5);
1701
+ }`)
1702
+
1703
+ // Modal size variants — class goes on .modal element: <div class="modal modal-lg">
1704
+ reg('modal-sm', 'components', `.modal-sm .modal-dialog { max-width: 300px; }`)
1705
+ reg('modal-lg', 'components', `.modal-lg .modal-dialog { max-width: 800px; }`)
1706
+ reg('modal-xl', 'components', `.modal-xl .modal-dialog { max-width: 1140px; }`)
1707
+ reg('modal-fullscreen', 'components', `.modal-fullscreen .modal-dialog {
1708
+ width: 100vw;
1709
+ max-width: none;
1710
+ height: 100%;
1711
+ margin: 0;
1712
+ }
1713
+
1714
+ .modal-fullscreen .modal-content {
1715
+ height: 100%;
1716
+ border: 0;
1717
+ border-radius: 0;
1718
+ }`)
1719
+
1720
+ // ─── Components — Toast ──────────────────────────────────────────────
1721
+
1722
+ reg('toast-container', 'components', `.toast-container {
1723
+ position: fixed;
1724
+ z-index: var(--st-z-toast, 1070);
1725
+ display: flex;
1726
+ flex-direction: column;
1727
+ gap: 0.5rem;
1728
+ pointer-events: none;
1729
+ }
1730
+
1731
+ .toast-container .toast { pointer-events: auto; }`)
1732
+
1733
+ reg('toast', 'components', `.toast {
1734
+ width: 350px;
1735
+ max-width: 100%;
1736
+ background-color: var(--st-bg);
1737
+ border: 1px solid var(--st-border);
1738
+ border-radius: var(--st-border-radius);
1739
+ box-shadow: var(--st-shadow);
1740
+ font-size: 0.875rem;
1741
+ opacity: 0;
1742
+ visibility: hidden;
1743
+ transition: opacity var(--st-duration) var(--st-easing),
1744
+ visibility var(--st-duration) var(--st-easing);
1745
+ }
1746
+
1747
+ .toast[data-st-visible="true"] {
1748
+ opacity: 1;
1749
+ visibility: visible;
1750
+ }`)
1751
+
1752
+ reg('toast-header', 'components', `.toast-header {
1753
+ display: flex;
1754
+ align-items: center;
1755
+ padding: 0.5rem 0.75rem;
1756
+ background-color: var(--st-bg-secondary);
1757
+ border-bottom: 1px solid var(--st-border);
1758
+ border-radius: calc(var(--st-border-radius) - 1px) calc(var(--st-border-radius) - 1px) 0 0;
1759
+ color: var(--st-text-muted);
1760
+ font-weight: 600;
1761
+ font-size: 0.8125rem;
1762
+ }`)
1763
+
1764
+ reg('toast-body', 'components', `.toast-body {
1765
+ padding: 0.75rem;
1766
+ word-break: break-word;
1767
+ color: var(--st-text);
1768
+ }`)
1769
+
1770
+ // ─── Components — Accordion ──────────────────────────────────────────
1771
+
1772
+ reg('accordion', 'components', `.accordion {
1773
+ border-radius: var(--st-border-radius);
1774
+ border: 1px solid var(--st-border);
1775
+ overflow: hidden;
1776
+ }`)
1777
+
1778
+ reg('accordion-item', 'components', `.accordion-item {
1779
+ background-color: var(--st-bg);
1780
+ border-bottom: 1px solid var(--st-border);
1781
+ }
1782
+
1783
+ .accordion-item:last-child { border-bottom: none; }`)
1784
+
1785
+ reg('accordion-header', 'components', `.accordion-header {
1786
+ margin: 0;
1787
+ }`)
1788
+
1789
+ reg('accordion-button', 'components', `.accordion-button {
1790
+ position: relative;
1791
+ display: flex;
1792
+ align-items: center;
1793
+ width: 100%;
1794
+ padding: 1rem 1.25rem;
1795
+ font-size: 1rem;
1796
+ font-weight: 500;
1797
+ color: var(--st-text);
1798
+ text-align: left;
1799
+ background-color: var(--st-bg);
1800
+ border: 0;
1801
+ cursor: pointer;
1802
+ overflow-anchor: none;
1803
+ transition: color var(--st-duration) var(--st-easing),
1804
+ background-color var(--st-duration) var(--st-easing);
1805
+ }
1806
+
1807
+ .accordion-button::after {
1808
+ flex-shrink: 0;
1809
+ width: 1.25rem;
1810
+ height: 1.25rem;
1811
+ margin-left: auto;
1812
+ content: "";
1813
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
1814
+ background-repeat: no-repeat;
1815
+ background-size: 1.25rem;
1816
+ transition: transform var(--st-duration) var(--st-easing);
1817
+ }
1818
+
1819
+ .accordion-button:not(.collapsed) {
1820
+ color: var(--st-primary);
1821
+ background-color: var(--st-bg-secondary);
1822
+ }
1823
+
1824
+ .accordion-button:not(.collapsed)::after {
1825
+ transform: rotate(-180deg);
1826
+ }
1827
+
1828
+ .accordion-button:focus-visible {
1829
+ box-shadow: var(--st-focus-ring);
1830
+ outline: none;
1831
+ }`)
1832
+
1833
+ reg('accordion-collapse', 'components', `.accordion-collapse {
1834
+ overflow: hidden;
1835
+ transition: max-height var(--st-duration-slow) var(--st-easing);
1836
+ }
1837
+
1838
+ .accordion-collapse[data-st-collapsed="true"] {
1839
+ max-height: 0;
1840
+ }
1841
+
1842
+ .accordion-collapse[data-st-collapsed="false"] {
1843
+ max-height: 9999px;
1844
+ }`)
1845
+
1846
+ reg('accordion-body', 'components', `.accordion-body {
1847
+ padding: 1rem 1.25rem;
1848
+ color: var(--st-text);
1849
+ }`)
1850
+
1851
+ reg('accordion-flush', 'components', `.accordion-flush {
1852
+ border: none;
1853
+ }
1854
+
1855
+ .accordion-flush .accordion-item {
1856
+ border-left: 0;
1857
+ border-right: 0;
1858
+ border-radius: 0;
1859
+ }
1860
+
1861
+ .accordion-flush .accordion-item:first-child { border-top: 0; }`)
1862
+
1863
+ // ─── Components — Dropdowns ──────────────────────────────────────────
1864
+
1865
+ reg('dropdown', 'components', `.dropdown {
1866
+ position: relative;
1867
+ display: inline-block;
1868
+ }`)
1869
+
1870
+ reg('dropdown-toggle', 'components', `.dropdown-toggle::after {
1871
+ display: inline-block;
1872
+ margin-left: 0.255em;
1873
+ vertical-align: 0.255em;
1874
+ content: "";
1875
+ border-top: 0.3em solid;
1876
+ border-right: 0.3em solid transparent;
1877
+ border-bottom: 0;
1878
+ border-left: 0.3em solid transparent;
1879
+ }`)
1880
+
1881
+ reg('dropdown-menu', 'components', `.dropdown-menu {
1882
+ position: absolute;
1883
+ top: 100%;
1884
+ left: 0;
1885
+ z-index: var(--st-z-dropdown, 1000);
1886
+ display: none;
1887
+ min-width: 10rem;
1888
+ padding: 0.5rem 0;
1889
+ margin: 0;
1890
+ font-size: 0.9375rem;
1891
+ color: var(--st-text);
1892
+ background-color: var(--st-bg);
1893
+ border: 1px solid var(--st-border);
1894
+ border-radius: var(--st-border-radius);
1895
+ box-shadow: var(--st-shadow);
1896
+ list-style: none;
1897
+ }
1898
+
1899
+ .dropdown-menu.show { display: block; }
1900
+
1901
+ .dropdown-menu[data-st-visible="true"] { display: block; }`)
1902
+
1903
+ reg('dropdown-item', 'components', `.dropdown-item {
1904
+ display: block;
1905
+ width: 100%;
1906
+ padding: 0.5rem 1rem;
1907
+ clear: both;
1908
+ font-weight: 400;
1909
+ color: var(--st-text);
1910
+ text-align: inherit;
1911
+ text-decoration: none;
1912
+ white-space: nowrap;
1913
+ background-color: transparent;
1914
+ border: 0;
1915
+ cursor: pointer;
1916
+ transition: background-color var(--st-duration) var(--st-easing),
1917
+ color var(--st-duration) var(--st-easing);
1918
+ }
1919
+
1920
+ .dropdown-item:hover,
1921
+ .dropdown-item:focus {
1922
+ background-color: var(--st-bg-secondary);
1923
+ color: var(--st-text);
1924
+ }
1925
+
1926
+ .dropdown-item.active,
1927
+ .dropdown-item:active {
1928
+ background-color: var(--st-primary);
1929
+ color: #fff;
1930
+ }
1931
+
1932
+ .dropdown-item.disabled {
1933
+ color: var(--st-text-muted);
1934
+ pointer-events: none;
1935
+ }`)
1936
+
1937
+ reg('dropdown-divider', 'components', `.dropdown-divider {
1938
+ height: 0;
1939
+ margin: 0.5rem 0;
1940
+ overflow: hidden;
1941
+ border-top: 1px solid var(--st-border);
1942
+ }`)
1943
+
1944
+ reg('dropdown-header', 'components', `.dropdown-header {
1945
+ display: block;
1946
+ padding: 0.25rem 1rem;
1947
+ margin: 0;
1948
+ font-size: 0.8rem;
1949
+ font-weight: 600;
1950
+ color: var(--st-text-muted);
1951
+ text-transform: uppercase;
1952
+ letter-spacing: 0.05em;
1953
+ white-space: nowrap;
1954
+ }`)
1955
+
1956
+ reg('dropdown-menu-end', 'components', `.dropdown-menu-end { left: auto; right: 0; }`)
1957
+
1958
+ reg('dropup', 'components', `.dropup .dropdown-menu {
1959
+ top: auto;
1960
+ bottom: 100%;
1961
+ margin-bottom: 0.125rem;
1962
+ }`)
1963
+
1964
+ // ─── Components — Close button ───────────────────────────────────────
1965
+
1966
+ reg('close', 'components', `.close {
1967
+ float: right;
1968
+ font-size: 1.25rem;
1969
+ font-weight: 700;
1970
+ line-height: 1;
1971
+ color: var(--st-text);
1972
+ opacity: 0.5;
1973
+ background: none;
1974
+ border: none;
1975
+ cursor: pointer;
1976
+ padding: 0;
1977
+ }
1978
+
1979
+ .close:hover { opacity: 1; }`)
1980
+
1981
+ // ─── Components — Offcanvas ──────────────────────────────────────────
1982
+
1983
+ reg('offcanvas', 'components', `.offcanvas {
1984
+ position: fixed;
1985
+ bottom: 0;
1986
+ z-index: var(--st-z-offcanvas, 1045);
1987
+ display: flex;
1988
+ flex-direction: column;
1989
+ max-width: 100%;
1990
+ background-color: var(--st-bg);
1991
+ border: 1px solid var(--st-border);
1992
+ outline: 0;
1993
+ visibility: hidden;
1994
+ transition: transform var(--st-duration) var(--st-easing),
1995
+ visibility var(--st-duration) var(--st-easing);
1996
+ }
1997
+
1998
+ .offcanvas[data-st-visible="true"] {
1999
+ visibility: visible;
2000
+ transform: none;
2001
+ }`)
2002
+
2003
+ reg('offcanvas-start', 'components', `.offcanvas-start {
2004
+ top: 0;
2005
+ left: 0;
2006
+ width: 300px;
2007
+ transform: translateX(-100%);
2008
+ }`)
2009
+
2010
+ reg('offcanvas-end', 'components', `.offcanvas-end {
2011
+ top: 0;
2012
+ right: 0;
2013
+ width: 300px;
2014
+ transform: translateX(100%);
2015
+ }`)
2016
+
2017
+ reg('offcanvas-top', 'components', `.offcanvas-top {
2018
+ top: 0;
2019
+ right: 0;
2020
+ left: 0;
2021
+ height: 30vh;
2022
+ transform: translateY(-100%);
2023
+ }`)
2024
+
2025
+ reg('offcanvas-bottom', 'components', `.offcanvas-bottom {
2026
+ right: 0;
2027
+ left: 0;
2028
+ height: 30vh;
2029
+ transform: translateY(100%);
2030
+ }`)
2031
+
2032
+ reg('offcanvas-header', 'components', `.offcanvas-header {
2033
+ display: flex;
2034
+ align-items: center;
2035
+ justify-content: space-between;
2036
+ padding: 1rem 1.25rem;
2037
+ border-bottom: 1px solid var(--st-border);
2038
+ }`)
2039
+
2040
+ reg('offcanvas-title', 'components', `.offcanvas-title {
2041
+ margin: 0;
2042
+ font-size: 1.125rem;
2043
+ font-weight: 600;
2044
+ color: var(--st-text);
2045
+ }`)
2046
+
2047
+ reg('offcanvas-body', 'components', `.offcanvas-body {
2048
+ flex-grow: 1;
2049
+ padding: 1.25rem;
2050
+ overflow-y: auto;
2051
+ color: var(--st-text);
2052
+ }`)
2053
+
2054
+ // ─── Row cols ────────────────────────────────────────────────────────
2055
+ for (let n = 1; n <= 6; n++) {
2056
+ const pct = (100/n).toFixed(4).replace(/\.?0+$/, '') + '%'
2057
+ reg(`row-cols-${n}`, 'components', `.row-cols-${n} > * { flex: 0 0 auto; width: ${pct}; }`)
2058
+ Object.keys(BP_VALUES).forEach(bp => {
2059
+ reg(`row-cols-${bp}-${n}`, 'components',
2060
+ mq(bp, `.row-cols-${bp}-${n} > * { flex: 0 0 auto; width: ${pct}; }`))
2061
+ })
2062
+ }
2063
+ reg('row-cols-auto', 'components', `.row-cols-auto > * { flex: 0 0 auto; width: auto; }`)
2064
+
2065
+ // ─── Column offsets ───────────────────────────────────────────────────
2066
+ for (let n = 0; n <= 11; n++) {
2067
+ const ml = n === 0 ? '0' : `${((n / 12) * 100).toFixed(4).replace(/\.?0+$/, '')}%`
2068
+ reg(`offset-${n}`, 'components', `.offset-${n} { margin-left: ${ml}; }`)
2069
+ Object.keys(BP_VALUES).forEach(bp => {
2070
+ reg(`offset-${bp}-${n}`, 'components',
2071
+ mq(bp, `.offset-${bp}-${n} { margin-left: ${ml}; }`))
2072
+ })
2073
+ }
2074
+
2075
+ // ─── Images & Figures ────────────────────────────────────────────────
2076
+ reg('img-fluid', 'utilities', `.img-fluid { max-width: 100%; height: auto; }`)
2077
+ reg('img-thumbnail', 'utilities', `.img-thumbnail {
2078
+ padding: 0.25rem;
2079
+ background-color: var(--st-bg);
2080
+ border: 1px solid var(--st-border);
2081
+ border-radius: var(--st-border-radius);
2082
+ max-width: 100%;
2083
+ height: auto;
2084
+ }`)
2085
+ reg('figure', 'utilities', `.figure { display: inline-block; }`)
2086
+ reg('figure-img', 'utilities', `.figure-img { margin-bottom: 0.5rem; line-height: 1; }`)
2087
+ reg('figure-caption', 'utilities', `.figure-caption { font-size: 0.875rem; color: var(--st-text-muted); }`)
2088
+
2089
+ // ─── Table colour variants ────────────────────────────────────────────
2090
+ const TABLE_COLORS = {
2091
+ primary: 'rgba(13,110,253,0.1)',
2092
+ secondary: 'rgba(108,117,125,0.1)',
2093
+ success: 'rgba(25,135,84,0.1)',
2094
+ danger: 'rgba(220,53,69,0.1)',
2095
+ warning: 'rgba(255,193,7,0.1)',
2096
+ info: 'rgba(13,202,240,0.1)',
2097
+ light: 'rgba(248,249,250,0.5)',
2098
+ active: 'rgba(0,0,0,0.05)',
2099
+ }
2100
+ Object.entries(TABLE_COLORS).forEach(([color, bg]) => {
2101
+ reg(`table-${color}`, 'components', `.table-${color} { --st-table-bg: ${bg}; }
2102
+ .table-${color} > :not(caption) > * > * { background-color: var(--st-table-bg); }`)
2103
+ })
2104
+ reg('table-dark', 'components', `.table-dark {
2105
+ --st-table-bg: #212529;
2106
+ color: #dee2e6;
2107
+ border-color: #373b3e;
2108
+ }
2109
+ .table-dark > :not(caption) > * > * { background-color: var(--st-table-bg); color: #dee2e6; }
2110
+ .table-dark > thead > tr > th { background-color: #1a1d20; color: #adb5bd; }`)
2111
+ reg('table-striped-columns', 'components', `.table-striped-columns > :not(caption) > tr > :nth-child(even) {
2112
+ background-color: var(--st-bg-secondary);
2113
+ }`)
2114
+ reg('table-group-divider', 'components', `.table-group-divider { border-top: 2px solid var(--st-border); }`)
2115
+
2116
+ // ─── Button extra variants ────────────────────────────────────────────
2117
+ reg('btn-link', 'components', `.btn-link {
2118
+ display: inline-flex;
2119
+ align-items: center;
2120
+ padding: 0.375rem 0.75rem;
2121
+ font-size: 1rem;
2122
+ color: var(--st-primary);
2123
+ background: none;
2124
+ border: none;
2125
+ cursor: pointer;
2126
+ text-decoration: underline;
2127
+ text-underline-offset: 2px;
2128
+ transition: color var(--st-duration) var(--st-easing);
2129
+ }
2130
+ .btn-link:hover { color: var(--st-primary-hover); }`)
2131
+ reg('btn-close-white', 'components', `.btn-close-white { filter: invert(1) grayscale(100%) brightness(200%); }`)
2132
+ reg('btn-group-vertical', 'components', `.btn-group-vertical {
2133
+ flex-direction: column;
2134
+ align-items: flex-start;
2135
+ justify-content: center;
2136
+ }
2137
+ .btn-group-vertical > [class*="btn-"] {
2138
+ width: 100%;
2139
+ }
2140
+ .btn-group-vertical > [class*="btn-"]:not(:first-child) {
2141
+ border-top-left-radius: 0;
2142
+ border-top-right-radius: 0;
2143
+ margin-top: -1px;
2144
+ }
2145
+ .btn-group-vertical > [class*="btn-"]:not(:last-child) {
2146
+ border-bottom-left-radius: 0;
2147
+ border-bottom-right-radius: 0;
2148
+ }`)
2149
+
2150
+ // ─── Collapse ────────────────────────────────────────────────────────
2151
+ reg('collapse', 'components', `.collapse:not(.show) { display: none; }`)
2152
+ reg('collapsing', 'components', `.collapsing {
2153
+ height: 0;
2154
+ overflow: hidden;
2155
+ transition: height var(--st-duration-slow) var(--st-easing);
2156
+ }`)
2157
+
2158
+ // ─── Carousel ────────────────────────────────────────────────────────
2159
+ reg('carousel', 'components', `.carousel { position: relative; }`)
2160
+ reg('carousel-inner', 'components', `.carousel-inner { position: relative; width: 100%; overflow: hidden; }`)
2161
+ reg('carousel-item', 'components', `.carousel-item {
2162
+ position: relative;
2163
+ display: none;
2164
+ float: left;
2165
+ width: 100%;
2166
+ backface-visibility: hidden;
2167
+ transition: transform var(--st-duration-slow) var(--st-easing);
2168
+ }
2169
+ .carousel-item.active,
2170
+ .carousel-item-next,
2171
+ .carousel-item-prev { display: block; }`)
2172
+ reg('carousel-control-prev', 'components', `.carousel-control-prev {
2173
+ position: absolute; top: 0; bottom: 0; left: 0;
2174
+ display: flex; align-items: center; justify-content: center;
2175
+ width: 15%; padding: 0; color: #fff; text-align: center;
2176
+ background: rgba(0,0,0,0.2); border: 0; opacity: 0.5;
2177
+ cursor: pointer; transition: opacity var(--st-duration) var(--st-easing);
2178
+ }
2179
+ .carousel-control-prev:hover { opacity: 0.9; }`)
2180
+ reg('carousel-control-next', 'components', `.carousel-control-next {
2181
+ position: absolute; top: 0; right: 0; bottom: 0;
2182
+ display: flex; align-items: center; justify-content: center;
2183
+ width: 15%; padding: 0; color: #fff; text-align: center;
2184
+ background: rgba(0,0,0,0.2); border: 0; opacity: 0.5;
2185
+ cursor: pointer; transition: opacity var(--st-duration) var(--st-easing);
2186
+ }
2187
+ .carousel-control-next:hover { opacity: 0.9; }`)
2188
+ reg('carousel-indicators', 'components', `.carousel-indicators {
2189
+ position: absolute; right: 0; bottom: 0; left: 0;
2190
+ display: flex; justify-content: center;
2191
+ padding: 0; margin: 0 15%; list-style: none;
2192
+ }
2193
+ .carousel-indicators [data-bs-target],
2194
+ .carousel-indicators button {
2195
+ width: 30px; height: 3px; margin: 0 3px;
2196
+ background-color: #fff; border: none; cursor: pointer;
2197
+ opacity: 0.5; transition: opacity var(--st-duration) var(--st-easing);
2198
+ }
2199
+ .carousel-indicators .active { opacity: 1; }`)
2200
+ reg('carousel-caption', 'components', `.carousel-caption {
2201
+ position: absolute; right: 15%; bottom: 1.25rem; left: 15%;
2202
+ padding: 1.25rem; color: #fff; text-align: center;
2203
+ }`)
2204
+ reg('carousel-fade', 'components', `.carousel-fade .carousel-item { opacity: 0; transition: opacity var(--st-duration-slow) var(--st-easing); transform: none; }
2205
+ .carousel-fade .carousel-item.active { opacity: 1; }`)
2206
+ reg('carousel-dark', 'components', `.carousel-dark .carousel-control-prev,
2207
+ .carousel-dark .carousel-control-next { filter: invert(1); }
2208
+ .carousel-dark .carousel-indicators button { background-color: #000; }
2209
+ .carousel-dark .carousel-caption { color: #000; }`)
2210
+ reg('slide', 'components', `.slide .carousel-item { transition: transform var(--st-duration-slow) var(--st-easing); }`)
2211
+
2212
+ // ─── Dropdown responsive ──────────────────────────────────────────────
2213
+ Object.keys(BP_VALUES).forEach(bp => {
2214
+ reg(`dropdown-menu-${bp}-start`, 'components', mq(bp, `.dropdown-menu-${bp}-start { left: 0; right: auto; }`))
2215
+ reg(`dropdown-menu-${bp}-end`, 'components', mq(bp, `.dropdown-menu-${bp}-end { left: auto; right: 0; }`))
2216
+ })
2217
+ reg('dropend', 'components', `.dropend .dropdown-menu { top: 0; right: auto; left: 100%; margin-left: 0.125rem; }`)
2218
+ reg('dropstart', 'components', `.dropstart .dropdown-menu { top: 0; right: 100%; left: auto; margin-right: 0.125rem; }`)
2219
+ reg('dropdown-center', 'components', `.dropdown-center .dropdown-menu { left: 50%; transform: translateX(-50%); }`)
2220
+ reg('dropup-center', 'components', `.dropup-center .dropdown-menu { left: 50%; transform: translateX(-50%); bottom: 100%; top: auto; }`)
2221
+
2222
+ // ─── List group extras ────────────────────────────────────────────────
2223
+ Object.keys(BP_VALUES).forEach(bp => {
2224
+ reg(`list-group-horizontal-${bp}`, 'components', mq(bp, `.list-group-horizontal-${bp} {
2225
+ flex-direction: row;
2226
+ }
2227
+ .list-group-horizontal-${bp} .list-group-item {
2228
+ border-bottom: 1px solid var(--st-border);
2229
+ margin-bottom: 0;
2230
+ margin-right: -1px;
2231
+ }
2232
+ .list-group-horizontal-${bp} .list-group-item:first-child {
2233
+ border-top-left-radius: var(--st-border-radius);
2234
+ border-bottom-left-radius: var(--st-border-radius);
2235
+ border-top-right-radius: 0;
2236
+ }
2237
+ .list-group-horizontal-${bp} .list-group-item:last-child {
2238
+ border-top-right-radius: var(--st-border-radius);
2239
+ border-bottom-right-radius: var(--st-border-radius);
2240
+ border-bottom-left-radius: 0;
2241
+ margin-right: 0;
2242
+ }`))
2243
+ })
2244
+
2245
+ const LG_COLORS = { primary:'#084298', success:'#0a3622', danger:'#842029', warning:'#664d03' }
2246
+ Object.entries(LG_COLORS).forEach(([color, text]) => {
2247
+ reg(`list-group-item-${color}`, 'components', `.list-group-item-${color} {
2248
+ background-color: color-mix(in srgb, var(--st-${color}) 15%, var(--st-bg));
2249
+ color: ${text};
2250
+ }`)
2251
+ })
2252
+ reg('list-group-numbered', 'components', `.list-group-numbered { list-style-type: none; counter-reset: section; }
2253
+ .list-group-numbered > .list-group-item::before {
2254
+ content: counters(section, ".") ". ";
2255
+ counter-increment: section;
2256
+ font-weight: 600;
2257
+ }`)
2258
+
2259
+ // ─── Modal fullscreen responsive ─────────────────────────────────────
2260
+ const MODAL_DOWN = { sm:'576px', md:'768px', lg:'992px', xl:'1200px', xxl:'1400px' }
2261
+ Object.entries(MODAL_DOWN).forEach(([bp, px]) => {
2262
+ reg(`modal-fullscreen-${bp}-down`, 'components',
2263
+ `@media (max-width: ${parseFloat(px)-0.02}px) {
2264
+ .modal-fullscreen-${bp}-down .modal-dialog {
2265
+ width: 100vw; max-width: none; height: 100%; margin: 0;
2266
+ }
2267
+ .modal-fullscreen-${bp}-down .modal-content {
2268
+ height: 100%; border: 0; border-radius: 0;
2269
+ }
2270
+ }`)
2271
+ })
2272
+
2273
+ // ─── Nav extras ───────────────────────────────────────────────────────
2274
+ reg('nav-underline', 'components', `.nav-underline .nav-link {
2275
+ border-bottom: 2px solid transparent;
2276
+ border-radius: 0;
2277
+ padding-bottom: calc(0.5rem - 2px);
2278
+ }
2279
+ .nav-underline .nav-link:hover { border-color: var(--st-border); }
2280
+ .nav-underline .nav-link.active { border-color: var(--st-primary); color: var(--st-primary); font-weight: 600; }`)
2281
+ reg('tab-pane', 'components', `.tab-pane { display: none; }
2282
+ .tab-pane.active { display: block; }
2283
+ .tab-pane.fade { opacity: 0; transition: opacity var(--st-duration) var(--st-easing); }
2284
+ .tab-pane.fade.show { opacity: 1; }`)
2285
+ reg('active', 'utilities', `.active { /* active state — set by component context */ }`)
2286
+ reg('fade', 'utilities', `.fade { opacity: 0; transition: opacity var(--st-duration) var(--st-easing); }
2287
+ .fade.show { opacity: 1; }`)
2288
+ reg('show', 'utilities', `.show { display: block; }`)
2289
+
2290
+ // ─── Navbar extras ────────────────────────────────────────────────────
2291
+ reg('navbar-expand', 'components', `.navbar-expand {
2292
+ flex-wrap: nowrap;
2293
+ justify-content: flex-start;
2294
+ }
2295
+ .navbar-expand .navbar-nav { flex-direction: row; }
2296
+ .navbar-expand .navbar-collapse { display: flex; flex-basis: auto; }
2297
+ .navbar-expand .navbar-toggler { display: none; }`)
2298
+ reg('navbar-dark', 'components', `.navbar-dark {
2299
+ --st-navbar-color: rgba(255,255,255,0.75);
2300
+ background-color: var(--st-dark);
2301
+ border-color: transparent;
2302
+ }
2303
+ .navbar-dark .navbar-brand,
2304
+ .navbar-dark .nav-link { color: rgba(255,255,255,0.9); }
2305
+ .navbar-dark .navbar-toggler { border-color: rgba(255,255,255,0.1); color: rgba(255,255,255,0.75); }`)
2306
+ reg('navbar-light', 'components', `.navbar-light {
2307
+ background-color: var(--st-bg);
2308
+ border-color: var(--st-border);
2309
+ }`)
2310
+
2311
+ // ─── Offcanvas backdrop ───────────────────────────────────────────────
2312
+ reg('offcanvas-backdrop', 'components', `.offcanvas-backdrop {
2313
+ position: fixed; inset: 0;
2314
+ z-index: var(--st-z-offcanvas, 1045);
2315
+ background: rgba(0,0,0,0.5);
2316
+ opacity: 0; visibility: hidden;
2317
+ transition: opacity var(--st-duration) var(--st-easing), visibility var(--st-duration) var(--st-easing);
2318
+ }
2319
+ .offcanvas-backdrop.show { opacity: 1; visibility: visible; }`)
2320
+
2321
+ // ─── Pagination size variants ─────────────────────────────────────────
2322
+ reg('pagination-sm', 'components', `.pagination-sm .page-link {
2323
+ padding: 0.25rem 0.5rem;
2324
+ font-size: 0.875rem;
2325
+ border-radius: calc(var(--st-border-radius) * 0.75);
2326
+ }`)
2327
+ reg('pagination-lg', 'components', `.pagination-lg .page-link {
2328
+ padding: 0.75rem 1.5rem;
2329
+ font-size: 1.125rem;
2330
+ border-radius: calc(var(--st-border-radius) * 1.5);
2331
+ }`)
2332
+
2333
+ // ─── Placeholders ────────────────────────────────────────────────────
2334
+ reg('placeholder', 'components', `.placeholder {
2335
+ display: inline-block;
2336
+ min-height: 1em;
2337
+ vertical-align: middle;
2338
+ cursor: wait;
2339
+ background-color: var(--st-skeleton-base);
2340
+ border-radius: var(--st-skeleton-radius, 4px);
2341
+ opacity: 0.5;
2342
+ }
2343
+ .col-1 .placeholder, .col-2 .placeholder,
2344
+ .col-3 .placeholder, .col-4 .placeholder { width: 100%; }`)
2345
+ reg('placeholder-glow', 'components', `.placeholder-glow .placeholder {
2346
+ animation: placeholder-glow 2s ease-in-out infinite;
2347
+ }
2348
+ @keyframes placeholder-glow {
2349
+ 50% { opacity: 0.2; }
2350
+ }`)
2351
+ reg('placeholder-wave', 'components', `.placeholder-wave .placeholder {
2352
+ -webkit-mask-image: linear-gradient(130deg, #000 55%, rgba(0,0,0,0.8) 75%, #000 95%);
2353
+ mask-image: linear-gradient(130deg, #000 55%, rgba(0,0,0,0.8) 75%, #000 95%);
2354
+ -webkit-mask-size: 200% 100%;
2355
+ mask-size: 200% 100%;
2356
+ animation: placeholder-wave 2s linear infinite;
2357
+ }
2358
+ @keyframes placeholder-wave { 100% { -webkit-mask-position: -200% 0%; mask-position: -200% 0%; } }`)
2359
+ reg('placeholder-xs', 'components', `.placeholder-xs { min-height: 0.6em; }`)
2360
+ reg('placeholder-sm', 'components', `.placeholder-sm { min-height: 0.8em; }`)
2361
+ reg('placeholder-lg', 'components', `.placeholder-lg { min-height: 1.2em; }`)
2362
+
2363
+ // ─── Progress extras ──────────────────────────────────────────────────
2364
+ reg('progress-stacked', 'components', `.progress-stacked { display: flex; }
2365
+ .progress-stacked .progress { border-radius: 0; }
2366
+ .progress-stacked .progress:first-child { border-top-left-radius: 999px; border-bottom-left-radius: 999px; }
2367
+ .progress-stacked .progress:last-child { border-top-right-radius: 999px; border-bottom-right-radius: 999px; }`)
2368
+ reg('progress-bar-animated', 'components', `.progress-bar-animated {
2369
+ animation: progress-bar-stripes 1s linear infinite;
2370
+ }
2371
+ @keyframes progress-bar-stripes {
2372
+ 0% { background-position-x: 1rem; }
2373
+ }`)
2374
+
2375
+ // ─── Spinner size variants ────────────────────────────────────────────
2376
+ reg('spinner-border-sm', 'components', `.spinner-border-sm { width: 1rem; height: 1rem; border-width: 0.2em; }`)
2377
+ reg('spinner-grow-sm', 'components', `.spinner-grow-sm { width: 1rem; height: 1rem; }`)
2378
+
2379
+ // ─── Form extras ──────────────────────────────────────────────────────
2380
+ reg('form-control-color', 'components', `.form-control-color {
2381
+ width: 3rem;
2382
+ height: calc(1.5em + 0.75rem + 2px);
2383
+ padding: 0.375rem;
2384
+ cursor: pointer;
2385
+ }
2386
+ .form-control-color::-webkit-color-swatch { border-radius: calc(var(--st-border-radius) * 0.75); }`)
2387
+ reg('form-control-plaintext', 'components', `.form-control-plaintext {
2388
+ display: block;
2389
+ width: 100%;
2390
+ padding: 0.375rem 0;
2391
+ background: transparent;
2392
+ border: solid transparent;
2393
+ border-width: 1px 0;
2394
+ color: var(--st-text);
2395
+ font-size: 1rem;
2396
+ line-height: 1.5;
2397
+ }`)
2398
+ reg('form-select-sm', 'components', `.form-select-sm {
2399
+ padding: 0.25rem 2.25rem 0.25rem 0.5rem;
2400
+ font-size: 0.875rem;
2401
+ border-radius: calc(var(--st-border-radius) * 0.75);
2402
+ }`)
2403
+ reg('form-select-lg', 'components', `.form-select-lg {
2404
+ padding: 0.5rem 2.25rem 0.5rem 1rem;
2405
+ font-size: 1.125rem;
2406
+ border-radius: calc(var(--st-border-radius) * 1.5);
2407
+ }`)
2408
+ reg('valid-tooltip', 'components', `.valid-tooltip { display: none; position: absolute; top: 100%; padding: 0.25rem 0.5rem; margin-top: 0.1rem; font-size: 0.875rem; color: #fff; background-color: var(--st-success); border-radius: var(--st-border-radius); }`)
2409
+ reg('invalid-tooltip', 'components', `.invalid-tooltip { display: none; position: absolute; top: 100%; padding: 0.25rem 0.5rem; margin-top: 0.1rem; font-size: 0.875rem; color: #fff; background-color: var(--st-danger); border-radius: var(--st-border-radius); }`)
2410
+ reg('is-valid', 'components', `.is-valid.form-control,
2411
+ .is-valid.form-select { border-color: var(--st-success); }
2412
+ .is-valid ~ .valid-feedback,
2413
+ .is-valid ~ .valid-tooltip { display: block; }`)
2414
+ reg('is-invalid', 'components', `.is-invalid.form-control,
2415
+ .is-invalid.form-select { border-color: var(--st-danger); }
2416
+ .is-invalid ~ .invalid-feedback,
2417
+ .is-invalid ~ .invalid-tooltip { display: block; }`)
2418
+ reg('col-form-label', 'components', `.col-form-label { padding-top: calc(0.375rem + 1px); padding-bottom: calc(0.375rem + 1px); margin-bottom: 0; font-size: inherit; line-height: 1.5; font-weight: 500; }`)
2419
+ reg('col-form-label-sm', 'components', `.col-form-label-sm { padding-top: calc(0.25rem + 1px); padding-bottom: calc(0.25rem + 1px); font-size: 0.875rem; }`)
2420
+ reg('col-form-label-lg', 'components', `.col-form-label-lg { padding-top: calc(0.5rem + 1px); padding-bottom: calc(0.5rem + 1px); font-size: 1.125rem; }`)
2421
+
2422
+ // ─── Tooltip / Popover CSS shells ─────────────────────────────────────
2423
+ reg('tooltip', 'components', `.tooltip {
2424
+ position: absolute; z-index: var(--st-z-tooltip, 1060);
2425
+ display: block; margin: 0; padding: 4px 0;
2426
+ font-size: 0.875rem; opacity: 0; pointer-events: none;
2427
+ }
2428
+ .tooltip.show { opacity: 0.9; }`)
2429
+ reg('tooltip-inner', 'components', `.tooltip-inner {
2430
+ max-width: 200px; padding: 0.25rem 0.5rem;
2431
+ color: #fff; text-align: center;
2432
+ background-color: #000; border-radius: var(--st-border-radius);
2433
+ }`)
2434
+ reg('bs-tooltip-top', 'components', `.bs-tooltip-top { padding: 4px 0; }
2435
+ .bs-tooltip-top .tooltip-arrow::before {
2436
+ top: -1px; border-width: 4px 4px 0;
2437
+ border-top-color: #000;
2438
+ }`)
2439
+ reg('popover', 'components', `.popover {
2440
+ position: absolute; z-index: var(--st-z-popover, 1070);
2441
+ display: block; max-width: 276px; padding: 0;
2442
+ font-size: 0.875rem; background-color: var(--st-bg);
2443
+ border: 1px solid var(--st-border); border-radius: var(--st-border-radius);
2444
+ box-shadow: var(--st-shadow);
2445
+ }`)
2446
+ reg('popover-header', 'components', `.popover-header {
2447
+ padding: 0.5rem 1rem; margin-bottom: 0;
2448
+ font-size: 1rem; font-weight: 600;
2449
+ background-color: var(--st-bg-secondary);
2450
+ border-bottom: 1px solid var(--st-border);
2451
+ border-radius: calc(var(--st-border-radius) - 1px) calc(var(--st-border-radius) - 1px) 0 0;
2452
+ }`)
2453
+ reg('popover-body', 'components', `.popover-body { padding: 1rem; color: var(--st-text); }`)
2454
+ reg('bs-popover-top', 'components', `.bs-popover-top { margin-bottom: 0.5rem; }`)
2455
+
2456
+ // ─── Helpers ─────────────────────────────────────────────────────────
2457
+ reg('clearfix', 'utilities', `.clearfix::after { display: block; clear: both; content: ""; }`)
2458
+ reg('color-body', 'utilities', `.color-body { color: var(--st-text); }`)
2459
+
2460
+ reg('ratio', 'utilities', `.ratio { position: relative; width: 100%; }
2461
+ .ratio::before { display: block; content: ""; padding-top: var(--st-aspect-ratio); }
2462
+ .ratio > * { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }`)
2463
+ reg('ratio-1x1', 'utilities', `.ratio-1x1 { --st-aspect-ratio: 100%; }`)
2464
+ reg('ratio-4x3', 'utilities', `.ratio-4x3 { --st-aspect-ratio: 75%; }`)
2465
+ reg('ratio-16x9', 'utilities', `.ratio-16x9 { --st-aspect-ratio: 56.25%; }`)
2466
+ reg('ratio-21x9', 'utilities', `.ratio-21x9 { --st-aspect-ratio: 42.8571%; }`)
2467
+
2468
+ reg('fixed-top', 'utilities', `.fixed-top { position: fixed; top: 0; right: 0; left: 0; z-index: var(--st-z-fixed, 1030); }`)
2469
+ reg('fixed-bottom', 'utilities', `.fixed-bottom { position: fixed; bottom: 0; right: 0; left: 0; z-index: var(--st-z-fixed, 1030); }`)
2470
+ reg('sticky-top', 'utilities', `.sticky-top { position: sticky; top: 0; z-index: var(--st-z-sticky, 1020); }`)
2471
+ reg('sticky-bottom', 'utilities', `.sticky-bottom { position: sticky; bottom: 0; z-index: var(--st-z-sticky, 1020); }`)
2472
+ Object.keys(BP_VALUES).forEach(bp => {
2473
+ reg(`sticky-${bp}-top`, 'utilities', mq(bp, `.sticky-${bp}-top { position: sticky; top: 0; z-index: var(--st-z-sticky, 1020); }`))
2474
+ reg(`sticky-${bp}-bottom`, 'utilities', mq(bp, `.sticky-${bp}-bottom { position: sticky; bottom: 0; z-index: var(--st-z-sticky, 1020); }`))
2475
+ })
2476
+
2477
+ reg('hstack', 'utilities', `.hstack { display: flex; flex-direction: row; align-items: center; align-self: stretch; }`)
2478
+ reg('vstack', 'utilities', `.vstack { display: flex; flex-direction: column; flex: 1 1 auto; }`)
2479
+
2480
+ const GAP_SCALE = { '0':'0', '1':'0.25rem', '2':'0.5rem', '3':'1rem', '4':'1.5rem', '5':'3rem' }
2481
+ Object.entries(GAP_SCALE).forEach(([k, v]) => {
2482
+ reg(`gap-${k}`, 'utilities', `.gap-${k} { gap: ${v}; }`)
2483
+ reg(`row-gap-${k}`, 'utilities', `.row-gap-${k} { row-gap: ${v}; }`)
2484
+ reg(`col-gap-${k}`, 'utilities', `.col-gap-${k} { column-gap: ${v}; }`)
2485
+ Object.keys(BP_VALUES).forEach(bp => {
2486
+ reg(`gap-${bp}-${k}`, 'utilities', mq(bp, `.gap-${bp}-${k} { gap: ${v}; }`))
2487
+ reg(`row-gap-${bp}-${k}`, 'utilities', mq(bp, `.row-gap-${bp}-${k} { row-gap: ${v}; }`))
2488
+ reg(`col-gap-${bp}-${k}`, 'utilities', mq(bp, `.col-gap-${bp}-${k} { column-gap: ${v}; }`))
2489
+ })
2490
+ })
2491
+
2492
+ reg('stretched-link', 'utilities', `.stretched-link::after {
2493
+ position: absolute; inset: 0; z-index: 1; content: "";
2494
+ }`)
2495
+ reg('text-truncate', 'utilities', `.text-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }`)
2496
+ reg('text-break', 'utilities', `.text-break { word-wrap: break-word; word-break: break-word; }`)
2497
+ reg('visually-hidden', 'utilities', `.visually-hidden {
2498
+ position: absolute; width: 1px; height: 1px;
2499
+ padding: 0; margin: -1px; overflow: hidden;
2500
+ clip: rect(0,0,0,0); white-space: nowrap; border: 0;
2501
+ }`)
2502
+ reg('visually-hidden-focusable', 'utilities', `.visually-hidden-focusable:not(:focus):not(:focus-within) {
2503
+ position: absolute; width: 1px; height: 1px;
2504
+ padding: 0; margin: -1px; overflow: hidden;
2505
+ clip: rect(0,0,0,0); white-space: nowrap; border: 0;
2506
+ }`)
2507
+ reg('icon-link', 'utilities', `.icon-link {
2508
+ display: inline-flex; align-items: center; gap: 0.375rem;
2509
+ color: var(--st-primary); text-decoration: underline;
2510
+ text-underline-offset: 2px;
2511
+ transition: color var(--st-duration) var(--st-easing);
2512
+ }
2513
+ .icon-link:hover { color: var(--st-primary-hover); }`)
2514
+ reg('icon-link-hover', 'utilities', `.icon-link-hover { gap: 0.5rem; }`)
2515
+
2516
+ // ─── Background extras ────────────────────────────────────────────────
2517
+ reg('bg-body', 'utilities', `.bg-body { background-color: var(--st-bg); }`)
2518
+ reg('bg-body-secondary', 'utilities', `.bg-body-secondary { background-color: var(--st-bg-secondary); }`)
2519
+ reg('bg-body-tertiary', 'utilities', `.bg-body-tertiary { background-color: color-mix(in srgb, var(--st-bg-secondary) 50%, var(--st-bg)); }`)
2520
+ reg('bg-black', 'utilities', `.bg-black { background-color: #000; }`)
2521
+ reg('bg-gradient', 'utilities', `.bg-gradient { background-image: linear-gradient(180deg, rgba(255,255,255,0.15), rgba(255,255,255,0)); }`)
2522
+ ;[10,25,50,75,100].forEach(n => {
2523
+ reg(`bg-opacity-${n}`, 'utilities', `.bg-opacity-${n} { --st-bg-opacity: ${n/100}; }`)
2524
+ })
2525
+
2526
+ // ─── Border extras ────────────────────────────────────────────────────
2527
+ reg('border-top', 'utilities', `.border-top { border-top: 1px solid var(--st-border); }`)
2528
+ reg('border-end', 'utilities', `.border-end { border-right: 1px solid var(--st-border); }`)
2529
+ reg('border-bottom', 'utilities', `.border-bottom { border-bottom: 1px solid var(--st-border); }`)
2530
+ reg('border-start', 'utilities', `.border-start { border-left: 1px solid var(--st-border); }`)
2531
+ reg('border-top-0', 'utilities', `.border-top-0 { border-top: 0; }`)
2532
+ reg('border-end-0', 'utilities', `.border-end-0 { border-right: 0; }`)
2533
+ reg('border-bottom-0', 'utilities', `.border-bottom-0 { border-bottom: 0; }`)
2534
+ reg('border-start-0', 'utilities', `.border-start-0 { border-left: 0; }`)
2535
+ reg('border-black', 'utilities', `.border-black { border-color: #000; }`)
2536
+ reg('border-white', 'utilities', `.border-white { border-color: #fff; }`)
2537
+ ;[1,2,3,4,5].forEach(n => {
2538
+ reg(`border-${n}`, 'utilities', `.border-${n} { border-width: ${n}px; }`)
2539
+ })
2540
+ ;[10,25,50,75].forEach(n => {
2541
+ reg(`border-opacity-${n}`, 'utilities', `.border-opacity-${n} { --st-border-opacity: ${n/100}; }`)
2542
+ })
2543
+
2544
+ // ─── Text color extras ────────────────────────────────────────────────
2545
+ reg('text-body-secondary', 'utilities', `.text-body-secondary { color: var(--st-text-muted); }`)
2546
+ reg('text-body-tertiary', 'utilities', `.text-body-tertiary { color: color-mix(in srgb, var(--st-text-muted) 65%, transparent); }`)
2547
+ reg('text-body-emphasis', 'utilities', `.text-body-emphasis { color: var(--st-text); font-weight: 600; }`)
2548
+ reg('text-black', 'utilities', `.text-black { color: #000; }`)
2549
+ reg('text-white', 'utilities', `.text-white { color: #fff; }`)
2550
+ reg('text-black-50', 'utilities', `.text-black-50 { color: rgba(0,0,0,0.5); }`)
2551
+ reg('text-white-50', 'utilities', `.text-white-50 { color: rgba(255,255,255,0.5); }`)
2552
+ ;['primary','secondary','success','danger','warning','info'].forEach(c => {
2553
+ reg(`text-${c}-emphasis`, 'utilities',
2554
+ `.text-${c}-emphasis { color: color-mix(in srgb, var(--st-${c}) 70%, black); }`)
2555
+ })
2556
+
2557
+ // ─── Link utilities ───────────────────────────────────────────────────
2558
+ ;['primary','secondary','success','danger','warning','info','light','dark'].forEach(c => {
2559
+ const hover = ['light'].includes(c) ? '#000' : `color-mix(in srgb, var(--st-${c}) 80%, black)`
2560
+ reg(`link-${c}`, 'utilities', `.link-${c} { color: var(--st-${c}); text-decoration-color: var(--st-${c}); }
2561
+ .link-${c}:hover { color: ${hover}; }`)
2562
+ })
2563
+ reg('link-body-emphasis', 'utilities', `.link-body-emphasis { color: var(--st-text); }
2564
+ .link-body-emphasis:hover { color: var(--st-text-muted); }`)
2565
+ ;[1,2,3].forEach(n => {
2566
+ reg(`link-offset-${n}`, 'utilities', `.link-offset-${n} { text-underline-offset: ${n*2}px; }`)
2567
+ })
2568
+ reg('link-underline', 'utilities', `.link-underline { text-decoration: underline; }`)
2569
+ ;['primary','secondary','success','danger','warning','info','light','dark'].forEach(c => {
2570
+ reg(`link-underline-${c}`, 'utilities', `.link-underline-${c} { text-decoration-color: var(--st-${c}); }`)
2571
+ })
2572
+
2573
+ // ─── Display extras ───────────────────────────────────────────────────
2574
+ reg('d-table-cell', 'utilities', `.d-table-cell { display: table-cell; }`)
2575
+ reg('d-table-row', 'utilities', `.d-table-row { display: table-row; }`)
2576
+ reg('d-print-none', 'utilities', `@media print { .d-print-none { display: none; } }`)
2577
+ reg('d-print-block', 'utilities', `@media print { .d-print-block { display: block; } }`)
2578
+ reg('d-print-flex', 'utilities', `@media print { .d-print-flex { display: flex; } }`)
2579
+
2580
+ // ─── Flex extras ──────────────────────────────────────────────────────
2581
+ reg('flex-row-reverse', 'utilities', `.flex-row-reverse { flex-direction: row-reverse; }`)
2582
+ reg('flex-column-reverse', 'utilities', `.flex-column-reverse { flex-direction: column-reverse; }`)
2583
+ reg('flex-wrap-reverse', 'utilities', `.flex-wrap-reverse { flex-wrap: wrap-reverse; }`)
2584
+
2585
+ ;['start','center','end','between','around','stretch'].forEach(v => {
2586
+ const val = v === 'start' ? 'flex-start' : v === 'end' ? 'flex-end' : v
2587
+ reg(`align-content-${v}`, 'utilities', `.align-content-${v} { align-content: ${val}; }`)
2588
+ })
2589
+
2590
+ ;['auto','start','center','end','baseline','stretch'].forEach(v => {
2591
+ const val = v === 'start' ? 'flex-start' : v === 'end' ? 'flex-end' : v
2592
+ reg(`align-self-${v}`, 'utilities', `.align-self-${v} { align-self: ${val}; }`)
2593
+ Object.keys(BP_VALUES).forEach(bp => {
2594
+ reg(`align-self-${bp}-${v}`, 'utilities', mq(bp, `.align-self-${bp}-${v} { align-self: ${val}; }`))
2595
+ })
2596
+ })
2597
+
2598
+ ;[0,1,2,3,4,5].forEach(n => {
2599
+ reg(`order-${n}`, 'utilities', `.order-${n} { order: ${n}; }`)
2600
+ Object.keys(BP_VALUES).forEach(bp => {
2601
+ reg(`order-${bp}-${n}`, 'utilities', mq(bp, `.order-${bp}-${n} { order: ${n}; }`))
2602
+ })
2603
+ })
2604
+ reg('order-first', 'utilities', `.order-first { order: -1; }`)
2605
+ reg('order-last', 'utilities', `.order-last { order: 6; }`)
2606
+ Object.keys(BP_VALUES).forEach(bp => {
2607
+ reg(`order-${bp}-first`, 'utilities', mq(bp, `.order-${bp}-first { order: -1; }`))
2608
+ reg(`order-${bp}-last`, 'utilities', mq(bp, `.order-${bp}-last { order: 6; }`))
2609
+ })
2610
+
2611
+ // ─── Float ────────────────────────────────────────────────────────────
2612
+ reg('float-start', 'utilities', `.float-start { float: left; }`)
2613
+ reg('float-end', 'utilities', `.float-end { float: right; }`)
2614
+ reg('float-none', 'utilities', `.float-none { float: none; }`)
2615
+ Object.keys(BP_VALUES).forEach(bp => {
2616
+ reg(`float-${bp}-start`, 'utilities', mq(bp, `.float-${bp}-start { float: left; }`))
2617
+ reg(`float-${bp}-end`, 'utilities', mq(bp, `.float-${bp}-end { float: right; }`))
2618
+ reg(`float-${bp}-none`, 'utilities', mq(bp, `.float-${bp}-none { float: none; }`))
2619
+ })
2620
+
2621
+ // ─── Interactions ─────────────────────────────────────────────────────
2622
+ reg('user-select-all', 'utilities', `.user-select-all { user-select: all; }`)
2623
+ reg('user-select-auto', 'utilities', `.user-select-auto { user-select: auto; }`)
2624
+ reg('user-select-none', 'utilities', `.user-select-none { user-select: none; }`)
2625
+ reg('pe-none', 'utilities', `.pe-none { pointer-events: none; }`)
2626
+ reg('pe-auto', 'utilities', `.pe-auto { pointer-events: auto; }`)
2627
+
2628
+ // ─── Object fit responsive ────────────────────────────────────────────
2629
+ ;['contain','cover','fill','none','scale-down'].forEach(v => {
2630
+ Object.keys(BP_VALUES).forEach(bp => {
2631
+ reg(`object-fit-${bp}-${v}`, 'utilities',
2632
+ mq(bp, `.object-fit-${bp}-${v} { object-fit: ${v}; }`))
2633
+ })
2634
+ })
2635
+
2636
+ // ─── Overflow extras ──────────────────────────────────────────────────
2637
+ ;['auto','hidden','visible','scroll'].forEach(v => {
2638
+ reg(`overflow-x-${v}`, 'utilities', `.overflow-x-${v} { overflow-x: ${v}; }`)
2639
+ reg(`overflow-y-${v}`, 'utilities', `.overflow-y-${v} { overflow-y: ${v}; }`)
2640
+ })
2641
+
2642
+ // ─── Position offset utilities ────────────────────────────────────────
2643
+ ;['top','bottom','start','end'].forEach(side => {
2644
+ const prop = side === 'start' ? 'left' : side === 'end' ? 'right' : side
2645
+ ;[['0','0'],['50','50%'],['100','100%']].forEach(([k, v]) => {
2646
+ reg(`${side}-${k}`, 'utilities', `.${side}-${k} { ${prop}: ${v}; }`)
2647
+ })
2648
+ })
2649
+ reg('translate-middle', 'utilities', `.translate-middle { transform: translate(-50%, -50%); }`)
2650
+ reg('translate-middle-x', 'utilities', `.translate-middle-x { transform: translateX(-50%); }`)
2651
+ reg('translate-middle-y', 'utilities', `.translate-middle-y { transform: translateY(-50%); }`)
2652
+
2653
+ // ─── Sizing extras ────────────────────────────────────────────────────
2654
+ reg('mw-100', 'utilities', `.mw-100 { max-width: 100%; }`)
2655
+ reg('mh-100', 'utilities', `.mh-100 { max-height: 100%; }`)
2656
+ reg('vw-100', 'utilities', `.vw-100 { width: 100vw; }`)
2657
+ reg('vh-100', 'utilities', `.vh-100 { height: 100vh; }`)
2658
+ reg('min-vw-100','utilities', `.min-vw-100{ min-width: 100vw; }`)
2659
+ reg('min-vh-100','utilities', `.min-vh-100{ min-height: 100vh; }`)
2660
+ reg('w-auto', 'utilities', `.w-auto { width: auto; }`)
2661
+ reg('h-auto', 'utilities', `.h-auto { height: auto; }`)
2662
+
2663
+ // ─── Text extras ──────────────────────────────────────────────────────
2664
+ reg('text-wrap', 'utilities', `.text-wrap { white-space: normal; }`)
2665
+ reg('text-nowrap', 'utilities', `.text-nowrap { white-space: nowrap; }`)
2666
+ reg('text-reset', 'utilities', `.text-reset { color: inherit; text-decoration: none; }`)
2667
+
2668
+ ;[
2669
+ ['fw-light', '300'],
2670
+ ['fw-lighter', 'lighter'],
2671
+ ['fw-normal', '400'],
2672
+ ['fw-medium', '500'],
2673
+ ['fw-semibold', '600'],
2674
+ ['fw-bold', '700'],
2675
+ ['fw-bolder', 'bolder'],
2676
+ ].forEach(([cls, val]) => reg(cls, 'utilities', `.${cls} { font-weight: ${val}; }`))
2677
+
2678
+ reg('fst-italic', 'utilities', `.fst-italic { font-style: italic; }`)
2679
+ reg('fst-normal', 'utilities', `.fst-normal { font-style: normal; }`)
2680
+
2681
+ ;[
2682
+ ['lh-1', '1'],
2683
+ ['lh-sm', '1.25'],
2684
+ ['lh-base', '1.5'],
2685
+ ['lh-lg', '2'],
2686
+ ].forEach(([cls, val]) => reg(cls, 'utilities', `.${cls} { line-height: ${val}; }`))
2687
+
2688
+ reg('font-monospace', 'utilities', `.font-monospace { font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; }`)
2689
+
2690
+ reg('text-decoration-none', 'utilities', `.text-decoration-none { text-decoration: none; }`)
2691
+ reg('text-decoration-underline', 'utilities', `.text-decoration-underline { text-decoration: underline; }`)
2692
+ reg('text-decoration-line-through', 'utilities', `.text-decoration-line-through { text-decoration: line-through; }`)
2693
+
2694
+ // ─── Vertical alignment ───────────────────────────────────────────────
2695
+ ;['baseline','top','middle','bottom','text-top','text-bottom'].forEach(v => {
2696
+ reg(`align-${v}`, 'utilities', `.align-${v} { vertical-align: ${v}; }`)
2697
+ })
2698
+
2699
+ // ─── Z-index extras ───────────────────────────────────────────────────
2700
+ reg('z-n1', 'utilities', `.z-n1 { z-index: -1; }`)
2701
+
2702
+ // ─── Rounded utilities ────────────────────────────────────────────────
2703
+ reg('rounded', 'utilities', `.rounded { border-radius: var(--st-border-radius); }`)
2704
+ reg('rounded-0', 'utilities', `.rounded-0 { border-radius: 0; }`)
2705
+ reg('rounded-1', 'utilities', `.rounded-1 { border-radius: 0.25rem; }`)
2706
+ reg('rounded-2', 'utilities', `.rounded-2 { border-radius: 0.375rem; }`)
2707
+ reg('rounded-3', 'utilities', `.rounded-3 { border-radius: 0.5rem; }`)
2708
+ reg('rounded-4', 'utilities', `.rounded-4 { border-radius: 0.75rem; }`)
2709
+ reg('rounded-5', 'utilities', `.rounded-5 { border-radius: 1rem; }`)
2710
+ reg('rounded-circle', 'utilities', `.rounded-circle { border-radius: 50%; }`)
2711
+ reg('rounded-top', 'utilities', `.rounded-top { border-top-left-radius: var(--st-border-radius); border-top-right-radius: var(--st-border-radius); }`)
2712
+ reg('rounded-end', 'utilities', `.rounded-end { border-top-right-radius: var(--st-border-radius); border-bottom-right-radius: var(--st-border-radius); }`)
2713
+ reg('rounded-bottom', 'utilities', `.rounded-bottom { border-bottom-left-radius: var(--st-border-radius); border-bottom-right-radius: var(--st-border-radius); }`)
2714
+ reg('rounded-start', 'utilities', `.rounded-start { border-top-left-radius: var(--st-border-radius); border-bottom-left-radius: var(--st-border-radius); }`)
2715
+
2716
+ // ─── Text opacity ────────────────────────────────────────────────────
2717
+ ;[10,25,50,75,100].forEach(n => {
2718
+ reg(`text-opacity-${n}`, 'utilities', `.text-opacity-${n} { --st-text-opacity: ${n/100}; }`)
2719
+ })
2720
+
2721
+ // ─── Print display utilities (complete set) ───────────────────────────
2722
+ ;['inline','inline-block','inline-flex','grid','table','table-row','table-cell'].forEach(v => {
2723
+ reg(`d-print-${v}`, 'utilities', `@media print { .d-print-${v} { display: ${v}; } }`)
2724
+ })
2725
+
2726
+ // ─── Bootstrap 5.3 subtle colour variants ────────────────────────────
2727
+ const SUBTLE_COLORS = {
2728
+ primary: { bg: 'rgba(13,110,253,0.1)', border: 'rgba(13,110,253,0.3)' },
2729
+ secondary: { bg: 'rgba(108,117,125,0.1)', border: 'rgba(108,117,125,0.3)' },
2730
+ success: { bg: 'rgba(25,135,84,0.1)', border: 'rgba(25,135,84,0.3)' },
2731
+ danger: { bg: 'rgba(220,53,69,0.1)', border: 'rgba(220,53,69,0.3)' },
2732
+ warning: { bg: 'rgba(255,193,7,0.1)', border: 'rgba(255,193,7,0.3)' },
2733
+ info: { bg: 'rgba(13,202,240,0.1)', border: 'rgba(13,202,240,0.3)' },
2734
+ light: { bg: 'rgba(248,249,250,0.5)', border: 'rgba(248,249,250,0.8)' },
2735
+ dark: { bg: 'rgba(33,37,41,0.1)', border: 'rgba(33,37,41,0.3)' },
2736
+ }
2737
+ Object.entries(SUBTLE_COLORS).forEach(([c, { bg, border }]) => {
2738
+ reg(`bg-${c}-subtle`, 'utilities', `.bg-${c}-subtle { background-color: ${bg}; }`)
2739
+ reg(`border-${c}-subtle`, 'utilities', `.border-${c}-subtle { border-color: ${border}; }`)
2740
+ })
2741
+
2742
+ // ─── needs-validation ─────────────────────────────────────────────────
2743
+ reg('needs-validation', 'components', `.needs-validation .form-control:invalid,
2744
+ .needs-validation .form-select:invalid {
2745
+ border-color: var(--st-danger);
2746
+ }
2747
+
2748
+ .needs-validation .form-control:valid,
2749
+ .needs-validation .form-select:valid {
2750
+ border-color: var(--st-success);
2751
+ }
2752
+
2753
+ .needs-validation .form-control:invalid ~ .invalid-feedback,
2754
+ .needs-validation .form-select:invalid ~ .invalid-feedback {
2755
+ display: block;
2756
+ }
2757
+
2758
+ .needs-validation .form-control:valid ~ .valid-feedback,
2759
+ .needs-validation .form-select:valid ~ .valid-feedback {
2760
+ display: block;
2761
+ }`)
2762
+
2763
+ // ─── Tooltip directional variants ────────────────────────────────────
2764
+ const TOOLTIP_ARROW = `
2765
+ .tooltip-arrow {
2766
+ position: absolute;
2767
+ display: block;
2768
+ width: 0.8rem;
2769
+ height: 0.4rem;
2770
+ }
2771
+ .tooltip-arrow::before {
2772
+ position: absolute;
2773
+ content: "";
2774
+ border-color: transparent;
2775
+ border-style: solid;
2776
+ }`
2777
+
2778
+ reg('tooltip-arrow', 'components', TOOLTIP_ARROW)
2779
+
2780
+ ;[
2781
+ ['bs-tooltip-bottom', 'bottom', 'top', '0.4rem', '0', 'border-bottom-color: rgba(0,0,0,0.7); border-width: 0 0.4rem 0.4rem; top: 0;'],
2782
+ ['bs-tooltip-start', 'left', 'right', '0', '0.4rem', 'border-left-color: rgba(0,0,0,0.7); border-width: 0.4rem 0 0.4rem 0.4rem; right: 0; top: 50%; transform: translateY(-50%);'],
2783
+ ['bs-tooltip-end', 'right', 'left', '0', '0.4rem', 'border-right-color: rgba(0,0,0,0.7); border-width: 0.4rem 0.4rem 0.4rem 0; left: 0; top: 50%; transform: translateY(-50%);'],
2784
+ ].forEach(([cls, placement, arrowSide, arrowTop, arrowBottom, arrowStyle]) => {
2785
+ reg(cls, 'components', `.${cls} { padding: ${arrowTop === '0' ? '0 0.4rem' : '0.4rem 0'}; }
2786
+ .${cls} .tooltip-arrow { ${arrowSide}: 0; top: ${arrowTop}; bottom: ${arrowBottom}; }
2787
+ .${cls} .tooltip-arrow::before { ${arrowStyle} }`)
2788
+ })
2789
+
2790
+ // ─── Popover directional variants ────────────────────────────────────
2791
+ ;[
2792
+ ['bs-popover-bottom', 'top', '0.5rem', 'border-bottom-color: var(--st-border); border-width: 0 0.5rem 0.5rem;'],
2793
+ ['bs-popover-start', 'right', '0.5rem', 'border-left-color: var(--st-border); border-width: 0.5rem 0 0.5rem 0.5rem;'],
2794
+ ['bs-popover-end', 'left', '0.5rem', 'border-right-color: var(--st-border); border-width: 0.5rem 0.5rem 0.5rem 0;'],
2795
+ ].forEach(([cls, arrowSide, arrowSize, arrowStyle]) => {
2796
+ reg(cls, 'components', `.${cls} { margin-${arrowSide}: ${arrowSize}; }
2797
+ .${cls} > .popover-arrow::before { ${arrowStyle} }`)
2798
+ })
2799
+
2800
+ // ─── Arbitrary value patterns — regex fallback ────────────────────────
2801
+ // Only used when no exact match found in EXACT_MAP
2802
+
2803
+ const ARBITRARY_PATTERNS = [
2804
+ // Spacing arbitrary: mt-[12px], !px-[2rem]
2805
+ { re: /^(!?)(m[trblxye]?|p[trblxye]?)-\[(.+)\]$/, fn: (m) => {
2806
+ const [, imp, prop, val] = m
2807
+ const props = SPACING_PROPS[prop]
2808
+ if (!props) return null
2809
+ const i = imp ? ' !important' : ''
2810
+ const decl = props.map(p => ` ${p}: ${val}${i};`).join('\n')
2811
+ return { layer: 'utilities', css: `.${escapeClass(m[0])} {\n${decl}\n}` }
2812
+ }},
2813
+ // Text color arbitrary: text-[#ff0000]
2814
+ { re: /^(!?)text-\[(.+)\]$/, fn: (m) => {
2815
+ const i = m[1] ? ' !important' : ''
2816
+ return { layer: 'utilities', css: `.${escapeClass(m[0])} { color: ${m[2]}${i}; }` }
2817
+ }},
2818
+ // BG arbitrary: bg-[#ff0000]
2819
+ { re: /^(!?)bg-\[(.+)\]$/, fn: (m) => {
2820
+ const i = m[1] ? ' !important' : ''
2821
+ return { layer: 'utilities', css: `.${escapeClass(m[0])} { background-color: ${m[2]}${i}; }` }
2822
+ }},
2823
+ // Border arbitrary: border-[2px_solid_red]
2824
+ { re: /^(!?)border-\[(.+)\]$/, fn: (m) => {
2825
+ const i = m[1] ? ' !important' : ''
2826
+ return { layer: 'utilities', css: `.${escapeClass(m[0])} { border: ${m[2].replace(/_/g,' ')}${i}; }` }
2827
+ }},
2828
+ // Width arbitrary: w-[200px]
2829
+ { re: /^(!?)w-\[(.+)\]$/, fn: (m) => {
2830
+ const i = m[1] ? ' !important' : ''
2831
+ return { layer: 'utilities', css: `.${escapeClass(m[0])} { width: ${m[2]}${i}; }` }
2832
+ }},
2833
+ // Height arbitrary: h-[100px]
2834
+ { re: /^(!?)h-\[(.+)\]$/, fn: (m) => {
2835
+ const i = m[1] ? ' !important' : ''
2836
+ return { layer: 'utilities', css: `.${escapeClass(m[0])} { height: ${m[2]}${i}; }` }
2837
+ }},
2838
+ // Opacity arbitrary: opacity-[0.3]
2839
+ { re: /^(!?)opacity-\[(.+)\]$/, fn: (m) => {
2840
+ const i = m[1] ? ' !important' : ''
2841
+ return { layer: 'utilities', css: `.${escapeClass(m[0])} { opacity: ${m[2]}${i}; }` }
2842
+ }},
2843
+ // Shadow arbitrary: shadow-[0_4px_6px_rgba(0,0,0,0.1)]
2844
+ { re: /^(!?)shadow-\[(.+)\]$/, fn: (m) => {
2845
+ const i = m[1] ? ' !important' : ''
2846
+ return { layer: 'utilities', css: `.${escapeClass(m[0])} { box-shadow: ${m[2].replace(/_/g,' ')}${i}; }` }
2847
+ }},
2848
+ // Z-index arbitrary: z-[100]
2849
+ { re: /^(!?)z-\[(.+)\]$/, fn: (m) => {
2850
+ const i = m[1] ? ' !important' : ''
2851
+ return { layer: 'utilities', css: `.${escapeClass(m[0])} { z-index: ${m[2]}${i}; }` }
2852
+ }},
2853
+ // Transition arbitrary: transition-[background-color_0.3s_ease]
2854
+ { re: /^transition-\[(.+)\]$/, fn: (m) => {
2855
+ return { layer: 'utilities', css: `.${escapeClass(m[0])} { transition: ${m[1].replace(/_/g,' ')}; }` }
2856
+ }},
2857
+ // Duration arbitrary: duration-[400ms]
2858
+ { re: /^duration-\[(.+)\]$/, fn: (m) => {
2859
+ return { layer: 'utilities', css: `.${escapeClass(m[0])} { transition-duration: ${m[1]}; }` }
2860
+ }},
2861
+ // Cursor arbitrary: cursor-[crosshair]
2862
+ { re: /^cursor-\[(.+)\]$/, fn: (m) => {
2863
+ return { layer: 'utilities', css: `.${escapeClass(m[0])} { cursor: ${m[1]}; }` }
2864
+ }},
2865
+ ]
2866
+
2867
+ // ─── Lookup — O(1) first, regex fallback ─────────────────────────────
2868
+
2869
+ const resultCache = new Map()
2870
+
2871
+ function lookup(className) {
2872
+ // L1 cache — fastest
2873
+ if (resultCache.has(className)) return resultCache.get(className)
2874
+
2875
+ // L2 — exact map — O(1)
2876
+ if (EXACT_MAP.has(className)) {
2877
+ const result = EXACT_MAP.get(className)
2878
+ resultCache.set(className, result)
2879
+ return result
2880
+ }
2881
+
2882
+ // L3 — arbitrary value patterns — O(patterns)
2883
+ for (let i = 0; i < ARBITRARY_PATTERNS.length; i++) {
2884
+ const m = className.match(ARBITRARY_PATTERNS[i].re)
2885
+ if (m) {
2886
+ const result = ARBITRARY_PATTERNS[i].fn(m)
2887
+ if (result) {
2888
+ resultCache.set(className, result)
2889
+ return result
2890
+ }
2891
+ }
2892
+ }
2893
+
2894
+ resultCache.set(className, null)
2895
+ return null
2896
+ }
2897
+
2898
+ module.exports = { lookup, EXACT_MAP, ARBITRARY_PATTERNS, escapeClass, parseArbitrary }