@keenmate/pure-admin-core 2.4.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +16 -9
  2. package/dist/css/main.css +177 -266
  3. package/package.json +1 -1
  4. package/snippets/AUDIT.md +94 -0
  5. package/snippets/alerts.html +264 -89
  6. package/snippets/badges.html +193 -61
  7. package/snippets/buttons.html +178 -0
  8. package/snippets/callouts.html +210 -129
  9. package/snippets/cards.html +383 -200
  10. package/snippets/checkbox-lists.html +199 -65
  11. package/snippets/code.html +55 -11
  12. package/snippets/command-palette.html +401 -111
  13. package/snippets/comparison.html +144 -93
  14. package/snippets/customization.html +311 -104
  15. package/snippets/data-display.html +584 -0
  16. package/snippets/detail-panel.html +470 -138
  17. package/snippets/filter-card.html +246 -0
  18. package/snippets/forms.html +408 -308
  19. package/snippets/grid.html +253 -141
  20. package/snippets/layout.html +379 -480
  21. package/snippets/lists.html +144 -47
  22. package/snippets/loaders.html +64 -39
  23. package/snippets/manifest.json +330 -280
  24. package/snippets/modal-dialogs.html +137 -64
  25. package/snippets/modals.html +221 -151
  26. package/snippets/notifications.html +285 -0
  27. package/snippets/popconfirm.html +213 -19
  28. package/snippets/profile.html +290 -330
  29. package/snippets/statistics.html +247 -0
  30. package/snippets/tables.html +359 -150
  31. package/snippets/tabs.html +129 -45
  32. package/snippets/timeline.html +123 -56
  33. package/snippets/toasts.html +179 -31
  34. package/snippets/tooltips.html +199 -81
  35. package/snippets/typography.html +183 -58
  36. package/snippets/utilities.html +511 -415
  37. package/snippets/virtual-scroll.html +201 -75
  38. package/snippets/web-daterangepicker.html +369 -189
  39. package/snippets/web-multiselect.html +360 -124
  40. package/src/scss/_base-css-variables.scss +123 -16
  41. package/src/scss/core-components/_alerts.scss +51 -12
  42. package/src/scss/core-components/_data-viz.scss +2 -2
  43. package/src/scss/core-components/_pagers.scss +1 -1
  44. package/src/scss/core-components/_popconfirm.scss +35 -13
  45. package/src/scss/core-components/_statistics.scss +15 -18
  46. package/src/scss/core-components/_tables.scss +2 -134
  47. package/src/scss/variables/_base.scss +15 -3
  48. package/src/scss/variables/_components.scss +40 -14
@@ -207,6 +207,42 @@
207
207
  --pa-alert-info-border: color-mix(in srgb, var(--pa-info-bg) #{$alert-border-opacity-dark}%, transparent);
208
208
  }
209
209
 
210
+ // ============================================================================
211
+ // DERIVED TOKENS — TEXT CONTRAST TIERS + SURFACE TINTS
212
+ // ============================================================================
213
+ // These tokens use `color-mix(... var(--pa-text-color-1) ...)` to derive
214
+ // theme-aware contrast levels. They MUST be emitted at every selector that
215
+ // could carry an override of --pa-text-color-1, because CSS custom property
216
+ // substitution bakes nested var() references at the *defining element*, not
217
+ // the using element. If we only defined these at `:root, .pa-mode-light`,
218
+ // the light-mode text colour would be frozen into the value, and elements
219
+ // inside `.pa-mode-dark` (which overrides --pa-text-color-1 at body) would
220
+ // inherit the light-mode-baked tier values → low contrast on dark surfaces.
221
+ //
222
+ // Emitting at all three selectors causes each scope to recompute the tiers
223
+ // against its own --pa-text-color-1. The selector list `:root, .pa-mode-light,
224
+ // .pa-mode-dark` covers html, body in light mode, and body in dark mode —
225
+ // regardless of which class layout each theme uses for mode switching.
226
+ //
227
+ // This is a TOP-LEVEL rule (not inside the mixin) because themes only call
228
+ // the mixin from their light-mode block; they don't call it again in the
229
+ // dark-mode block. Emitting at framework level means themes get correct
230
+ // dark-mode tiers without changing any theme code.
231
+ //
232
+ // Override these per-theme by redefining them in the same scope you
233
+ // override --pa-text-color-1.
234
+ // ============================================================================
235
+
236
+ :root,
237
+ .pa-mode-light,
238
+ .pa-mode-dark {
239
+ --pa-text-strong: color-mix(in srgb, var(--pa-text-color-1) 85%, transparent);
240
+ --pa-text-secondary: color-mix(in srgb, var(--pa-text-color-1) 70%, transparent);
241
+ --pa-text-tertiary: color-mix(in srgb, var(--pa-text-color-1) 55%, transparent);
242
+ --pa-surface-hover: color-mix(in srgb, var(--pa-text-color-1) 4%, transparent);
243
+ --pa-surface-track: color-mix(in srgb, var(--pa-text-color-1) 12%, transparent);
244
+ }
245
+
210
246
  // ============================================================================
211
247
  // PURE ADMIN THEME CSS VARIABLES OUTPUT MIXIN
212
248
  // ============================================================================
@@ -244,6 +280,77 @@
244
280
  // Border
245
281
  --pa-border-color: #{$border-color};
246
282
 
283
+ // =========================================================================
284
+ // ROLE COLORS — single source of truth for the four semantic roles.
285
+ // Button surfaces (--pa-btn-{success,warning,danger,info}-bg) and
286
+ // contextual surfaces (--pa-{success,warning,danger,info}-bg) below
287
+ // both reference these via var() so a theme can override one place
288
+ // and the change cascades everywhere.
289
+ //
290
+ // --pa-warning here is also the "off-target / approaching-limit" axis
291
+ // colour used by KPI components — orange-500, deliberately distinct
292
+ // from the previous Bootstrap yellow.
293
+ // =========================================================================
294
+
295
+ --pa-success: #{$base-success-color};
296
+ --pa-warning: #{$base-warning-color};
297
+ --pa-danger: #{$base-danger-color};
298
+ --pa-info: #{$base-info-color};
299
+
300
+ // =========================================================================
301
+ // SENTIMENT SCALE — 5-step direction-of-change.
302
+ // Used by KPI / data-display components for indicating directional
303
+ // movement (delta colours, sparkline trend colours, etc.).
304
+ // The middle three values alias the role colours so a theme that
305
+ // retunes --pa-success / --pa-danger automatically retunes the
306
+ // matching sentiment positions; the outliers (very-positive /
307
+ // very-negative) are explicit darker shades.
308
+ //
309
+ // Sentiment is on a *different axis* from role colours — direction
310
+ // (ordinal: -2 to +2) vs. urgency (categorical: success / warning /
311
+ // danger / info). They coexist intentionally.
312
+ // =========================================================================
313
+
314
+ --pa-very-positive: #{$base-very-positive};
315
+ --pa-positive: var(--pa-success);
316
+ --pa-neutral: #{$base-neutral};
317
+ --pa-negative: var(--pa-danger);
318
+ --pa-very-negative: #{$base-very-negative};
319
+
320
+ // (Text contrast tiers and surface tints are emitted as a top-level rule
321
+ // OUTSIDE this mixin — see below — because their values use
322
+ // `var(--pa-text-color-1)` and CSS custom property substitution bakes
323
+ // nested var() at the *defining element*, not the using element. That
324
+ // means tier vars defined here at `:root, .pa-mode-light` would freeze
325
+ // the light-mode text colour into them; under `.pa-mode-dark` (which
326
+ // overrides --pa-text-color-1 at body) the tiers would still resolve
327
+ // against the light value. Emitting them at all three selectors
328
+ // simultaneously fixes the substitution scope.)
329
+
330
+ // =========================================================================
331
+ // CHART / SPARKLINE
332
+ // =========================================================================
333
+ // Single height token used by every KPI showcase that renders a sparkline
334
+ // / trendline (terminal grid, sparkline list, hero+supporting, bento).
335
+ // Centralised so the line proportions stay consistent across designs;
336
+ // override globally for taller or shorter trendlines, or per-component
337
+ // if a particular layout (e.g. a "hero" pattern) wants a different size.
338
+
339
+ --pa-chart-trendline-height: 3rem;
340
+ --pa-chart-trendline-stroke: 2.1; // SVG user-space units (viewBox-relative)
341
+
342
+ // =========================================================================
343
+ // DETAIL POPOVER — Bloomberg-dark by default, regardless of host
344
+ // theme, for the terminal/data-dashboard aesthetic. Override the
345
+ // five tokens to adopt a theme-aware popover instead.
346
+ // =========================================================================
347
+
348
+ --pa-detail-bg: rgba(15, 17, 21, 0.97);
349
+ --pa-detail-text: #ffffff;
350
+ --pa-detail-row-label: rgba(255, 255, 255, 0.75);
351
+ --pa-detail-title: rgba(255, 255, 255, 0.55);
352
+ --pa-detail-shadow: 0 1.4rem 3.6rem rgba(0, 0, 0, 0.55);
353
+
247
354
  // =========================================================================
248
355
  // LAYOUT COLORS
249
356
  // =========================================================================
@@ -283,23 +390,23 @@
283
390
  --pa-btn-secondary-text: #{$btn-secondary-text};
284
391
  --pa-btn-secondary-outline-color: #{$btn-secondary-text};
285
392
 
286
- // Success
287
- --pa-btn-success-bg: #{$btn-success-bg};
393
+ // Success — bg references role colour for runtime cascade
394
+ --pa-btn-success-bg: var(--pa-success);
288
395
  --pa-btn-success-bg-hover: #{$btn-success-bg-hover};
289
396
  --pa-btn-success-text: #{$btn-success-text};
290
397
 
291
- // Danger
292
- --pa-btn-danger-bg: #{$btn-danger-bg};
398
+ // Danger — bg references role colour for runtime cascade
399
+ --pa-btn-danger-bg: var(--pa-danger);
293
400
  --pa-btn-danger-bg-hover: #{$btn-danger-bg-hover};
294
401
  --pa-btn-danger-text: #{$btn-danger-text};
295
402
 
296
- // Warning
297
- --pa-btn-warning-bg: #{$btn-warning-bg};
403
+ // Warning — bg references role colour for runtime cascade
404
+ --pa-btn-warning-bg: var(--pa-warning);
298
405
  --pa-btn-warning-bg-hover: #{$btn-warning-bg-hover};
299
406
  --pa-btn-warning-text: #{$btn-warning-text};
300
407
 
301
- // Info
302
- --pa-btn-info-bg: #{$btn-info-bg};
408
+ // Info — bg references role colour for runtime cascade
409
+ --pa-btn-info-bg: var(--pa-info);
303
410
  --pa-btn-info-bg-hover: #{$btn-info-bg-hover};
304
411
  --pa-btn-info-text: #{$btn-info-text};
305
412
 
@@ -317,8 +424,8 @@
317
424
  // CONTEXTUAL/SEMANTIC COLORS
318
425
  // =========================================================================
319
426
 
320
- // Success
321
- --pa-success-bg: #{$success-bg};
427
+ // Success — bg references role colour for runtime cascade
428
+ --pa-success-bg: var(--pa-success);
322
429
  --pa-success-bg-hover: #{$success-bg-hover};
323
430
  --pa-success-bg-light: #{$success-bg-light};
324
431
  --pa-success-bg-subtle: #{$success-bg-subtle};
@@ -326,8 +433,8 @@
326
433
  --pa-success-text: #{$success-text};
327
434
  --pa-success-text-light: #{$success-text-light};
328
435
 
329
- // Danger
330
- --pa-danger-bg: #{$danger-bg};
436
+ // Danger — bg references role colour for runtime cascade
437
+ --pa-danger-bg: var(--pa-danger);
331
438
  --pa-danger-bg-hover: #{$danger-bg-hover};
332
439
  --pa-danger-bg-light: #{$danger-bg-light};
333
440
  --pa-danger-bg-subtle: #{$danger-bg-subtle};
@@ -335,8 +442,8 @@
335
442
  --pa-danger-text: #{$danger-text};
336
443
  --pa-danger-text-light: #{$danger-text-light};
337
444
 
338
- // Warning
339
- --pa-warning-bg: #{$warning-bg};
445
+ // Warning — bg references role colour for runtime cascade
446
+ --pa-warning-bg: var(--pa-warning);
340
447
  --pa-warning-bg-hover: #{$warning-bg-hover};
341
448
  --pa-warning-bg-light: #{$warning-bg-light};
342
449
  --pa-warning-bg-subtle: #{$warning-bg-subtle};
@@ -344,8 +451,8 @@
344
451
  --pa-warning-text: #{$warning-text};
345
452
  --pa-warning-text-light: #{$warning-text-light};
346
453
 
347
- // Info
348
- --pa-info-bg: #{$info-bg};
454
+ // Info — bg references role colour for runtime cascade
455
+ --pa-info-bg: var(--pa-info);
349
456
  --pa-info-bg-hover: #{$info-bg-hover};
350
457
  --pa-info-bg-light: #{$info-bg-light};
351
458
  --pa-info-bg-subtle: #{$info-bg-subtle};
@@ -7,14 +7,18 @@
7
7
  // Alerts
8
8
  .pa-alert {
9
9
  position: relative;
10
- padding: $card-footer-padding-v $card-footer-padding-h;
10
+ padding: $alert-padding-v $alert-padding-h;
11
11
  margin-bottom: $spacing-base;
12
12
  border: $border-width-base solid transparent;
13
13
  border-radius: var(--pa-border-radius);
14
14
  font-size: $font-size-sm;
15
15
  display: flex;
16
16
  flex-wrap: wrap;
17
- align-items: flex-start;
17
+ // Centre by default — keeps icon + single-line content vertically
18
+ // aligned with the text glyph (the most common case). Opt out with
19
+ // .pa-alert--multiline when you have icon + multi-line __content
20
+ // and need the icon to stick to the top with the heading.
21
+ align-items: center;
18
22
 
19
23
  // Remove margins when first/last child in card body
20
24
  .pa-card__body &:first-child {
@@ -121,15 +125,17 @@
121
125
  }
122
126
  }
123
127
 
124
- // Alert sizes (size modifiers only change font-size, padding controlled by theme)
128
+ // Alert sizes both padding AND font-size step at each size.
129
+ // See $alert-padding-{sm,lg}-{v,h} and $alert-font-size-{sm,lg} in
130
+ // variables/_components.scss for theme-overridable scales.
125
131
  &--sm {
126
- padding: $alert-padding-v $alert-padding-h;
127
- font-size: $font-size-sm;
132
+ padding: $alert-padding-sm-v $alert-padding-sm-h;
133
+ font-size: $alert-font-size-sm;
128
134
  }
129
135
 
130
136
  &--lg {
131
- padding: $alert-padding-v $alert-padding-h;
132
- font-size: $font-size-base;
137
+ padding: $alert-padding-lg-v $alert-padding-lg-h;
138
+ font-size: $alert-font-size-lg;
133
139
  }
134
140
 
135
141
  // Dismissible alerts
@@ -137,6 +143,17 @@
137
143
  padding-inline-end: $alert-dismissible-padding-right; // RTL: flips to padding-left
138
144
  }
139
145
 
146
+ // Multiline content modifier — opt out of the default `align-items: center`
147
+ // so that an icon next to a multi-line .pa-alert__content (heading +
148
+ // paragraph + actions, etc.) stays top-aligned with the heading instead
149
+ // of floating in the vertical middle of the stack.
150
+ // Pure structural stacks without an icon don't need this — their children
151
+ // get `flex-basis: 100%` (one per row) so the alert's `align-items` is
152
+ // visually irrelevant for them.
153
+ &--multiline {
154
+ align-items: flex-start;
155
+ }
156
+
140
157
  // Alert components
141
158
  &__icon {
142
159
  flex-shrink: 0;
@@ -146,24 +163,46 @@
146
163
 
147
164
  &__content {
148
165
  flex: 1;
166
+ min-width: 0; // allow long content to shrink + wrap rather than overflow
167
+ }
168
+
169
+ // Force structural sub-elements + top-level <p> / <hr> onto their own row
170
+ // in the alert's flex-wrap layout. Without this, they sit at content width
171
+ // beside each other (heading next to body, actions next to list, …).
172
+ &__heading,
173
+ &__list,
174
+ &__actions,
175
+ > p,
176
+ > hr {
177
+ flex-basis: 100%;
149
178
  }
150
179
 
151
180
  &__heading {
152
- margin: 0 0 $spacing-sm 0;
181
+ margin: 0; // alert flex-gap supplies the spacing — don't double up
153
182
  color: inherit;
154
- font-size: $font-size-lg;
155
183
  font-weight: $font-weight-semibold;
184
+ // No explicit font-size — inherits the alert's body font-size ($font-size-sm)
185
+ // so heading + body read at the same scale (compact look). Add --lg to opt
186
+ // into the bigger, "punchy" presentation when the alert needs deliberate
187
+ // attention.
188
+
189
+ &--lg {
190
+ font-size: $font-size-lg;
191
+ }
156
192
  }
157
193
 
158
194
  &__list {
159
- margin: $spacing-sm 0;
195
+ margin: 0; // alert flex-gap supplies the spacing
160
196
  padding-inline-start: $alert-list-padding-left; // RTL: flips to padding-right
161
197
  }
162
198
 
163
199
  &__actions {
164
- margin-top: $alert-list-item-margin-top;
200
+ margin-top: 0; // alert flex-gap supplies the spacing above the divider
201
+ padding-top: $spacing-base;
202
+ border-top: $border-width-base solid rgba(0, 0, 0, $opacity-light);
165
203
  display: flex;
166
204
  gap: $spacing-sm;
205
+ flex-wrap: wrap;
167
206
  }
168
207
 
169
208
  &__close {
@@ -171,7 +210,7 @@
171
210
  top: 0;
172
211
  inset-inline-end: 0; // RTL: flips to left
173
212
  z-index: $focus-outline-width;
174
- padding: $card-footer-padding-v $card-footer-padding-h;
213
+ padding: $alert-padding-v $alert-padding-h; // matches the alert body's padding
175
214
  background: none;
176
215
  border: none;
177
216
  font-size: $font-size-xl;
@@ -185,7 +185,7 @@
185
185
  width: 70%;
186
186
  height: 70%;
187
187
  border-radius: 50%;
188
- background: $card-bg;
188
+ background: var(--pa-card-bg);
189
189
  display: flex;
190
190
  flex-direction: column;
191
191
  align-items: center;
@@ -288,7 +288,7 @@
288
288
  right: 15%;
289
289
  height: 70%;
290
290
  border-radius: #{$gauge-size} #{$gauge-size} 0 0;
291
- background: $card-bg;
291
+ background: var(--pa-card-bg);
292
292
  display: flex;
293
293
  flex-direction: column;
294
294
  align-items: center;
@@ -124,7 +124,7 @@
124
124
  width: $spinner-size;
125
125
  height: $spinner-size;
126
126
  border: $spinner-border-width solid var(--pa-border-color);
127
- border-top: $spinner-border-width solid $accent-color;
127
+ border-top: $spinner-border-width solid var(--pa-accent);
128
128
  border-radius: 50%;
129
129
  }
130
130
 
@@ -98,59 +98,81 @@
98
98
  }
99
99
 
100
100
  // ====================
101
- // Position Variants
101
+ // Position Variants (logical — mirror in RTL)
102
102
  // ====================
103
103
 
104
- // Bottom (default) - arrow on top
104
+ // Bottom popconfirm below trigger, arrow on top (block-axis; no RTL flip)
105
105
  .pa-popconfirm--bottom {
106
106
  margin-top: $spacing-sm;
107
107
 
108
108
  .pa-popconfirm__arrow {
109
109
  top: -0.64rem;
110
- left: 50%;
110
+ inset-inline-start: 50%;
111
111
  transform: translateX(-50%) rotate(45deg);
112
112
  border-right: none;
113
113
  border-bottom: none;
114
114
  }
115
+
116
+ // RTL: translateX needs to invert because inset-inline-start flipped sides
117
+ [dir="rtl"] & .pa-popconfirm__arrow {
118
+ transform: translateX(50%) rotate(45deg);
119
+ }
115
120
  }
116
121
 
117
- // Top - arrow on bottom
122
+ // Top popconfirm above trigger, arrow on bottom (block-axis; no RTL flip)
118
123
  .pa-popconfirm--top {
119
124
  margin-bottom: $spacing-sm;
120
125
 
121
126
  .pa-popconfirm__arrow {
122
127
  bottom: -0.64rem;
123
- left: 50%;
128
+ inset-inline-start: 50%;
124
129
  transform: translateX(-50%) rotate(45deg);
125
130
  border-left: none;
126
131
  border-top: none;
127
132
  }
133
+
134
+ [dir="rtl"] & .pa-popconfirm__arrow {
135
+ transform: translateX(50%) rotate(45deg);
136
+ }
128
137
  }
129
138
 
130
- // Right - arrow on left
131
- .pa-popconfirm--right {
132
- margin-left: $spacing-sm;
139
+ // End popconfirm on the inline-end side of trigger (right in LTR, left in RTL)
140
+ // Arrow sits on the inline-start side of the popconfirm, pointing at the trigger.
141
+ .pa-popconfirm--end {
142
+ margin-inline-start: $spacing-sm;
133
143
 
134
144
  .pa-popconfirm__arrow {
135
- left: -0.64rem;
145
+ inset-inline-start: -0.64rem;
136
146
  top: 50%;
137
147
  transform: translateY(-50%) rotate(45deg);
138
148
  border-top: none;
139
149
  border-right: none;
140
150
  }
151
+
152
+ // RTL: mirror the rotated arrow via scaleX(-1) so the hidden borders still
153
+ // mask the side facing the popconfirm body, and the triangle points the
154
+ // right way (toward the trigger on the visually-right).
155
+ [dir="rtl"] & .pa-popconfirm__arrow {
156
+ transform: translateY(-50%) rotate(45deg) scaleX(-1);
157
+ }
141
158
  }
142
159
 
143
- // Left - arrow on right
144
- .pa-popconfirm--left {
145
- margin-right: $spacing-sm;
160
+ // Start popconfirm on the inline-start side of trigger (left in LTR, right in RTL)
161
+ // Arrow sits on the inline-end side of the popconfirm, pointing at the trigger.
162
+ .pa-popconfirm--start {
163
+ margin-inline-end: $spacing-sm;
146
164
 
147
165
  .pa-popconfirm__arrow {
148
- right: -0.64rem;
166
+ inset-inline-end: -0.64rem;
149
167
  top: 50%;
150
168
  transform: translateY(-50%) rotate(45deg);
151
169
  border-bottom: none;
152
170
  border-left: none;
153
171
  }
172
+
173
+ [dir="rtl"] & .pa-popconfirm__arrow {
174
+ transform: translateY(-50%) rotate(45deg) scaleX(-1);
175
+ }
154
176
  }
155
177
 
156
178
  // ====================
@@ -98,11 +98,17 @@
98
98
  }
99
99
  }
100
100
 
101
- // Square variant - compact square with big number and shadowed symbol
101
+ // Square variant - compact square with a big number and a smaller, inline unit symbol.
102
+ // Layout: number + symbol on one row (baseline-aligned) at the top, label pinned to the
103
+ // bottom. Markup order drives visual order — `<symbol><number>` works for prefix
104
+ // currencies ($1,234), `<number><symbol>` for suffix units (87% / 23°C).
102
105
  &--square {
106
+ container-type: inline-size; // enables cqi units in __number / __symbol clamps
103
107
  display: flex;
104
- align-items: center;
105
- justify-content: space-between;
108
+ flex-wrap: wrap;
109
+ align-content: space-between; // number/symbol row sticks to the top, label to the bottom
110
+ align-items: baseline; // baseline-align number and symbol (clean even when symbol is multi-char like "°C")
111
+ column-gap: $stat-square-symbol-gap;
106
112
  padding: $spacing-lg;
107
113
  min-height: $stat-square-min-size;
108
114
  min-width: $stat-square-min-size;
@@ -120,10 +126,8 @@
120
126
  .pa-stat__number {
121
127
  font-size: clamp($stat-square-number-min, $stat-square-number-scale, $stat-square-number-max);
122
128
  font-weight: $font-weight-bold;
123
- line-height: 1.1;
129
+ line-height: 1;
124
130
  color: inherit;
125
- z-index: $focus-outline-width;
126
- position: relative;
127
131
  text-shadow: 0 $stat-text-shadow-1-y $stat-text-shadow-1-blur rgba(0, 0, 0, $btn-focus-ring-opacity),
128
132
  0 $stat-text-shadow-2-y $stat-text-shadow-2-blur rgba(0, 0, 0, $opacity-shadow-md);
129
133
  filter: drop-shadow(0 $stat-drop-shadow-y $stat-drop-shadow-blur rgba(0, 0, 0, $opacity-light));
@@ -134,28 +138,21 @@
134
138
  .pa-stat__symbol {
135
139
  font-size: clamp($stat-square-symbol-min, $stat-square-symbol-scale, $stat-square-symbol-max);
136
140
  font-weight: $font-weight-bold;
137
- line-height: 1.1;
138
- opacity: $stat-symbol-opacity;
141
+ line-height: 1;
139
142
  color: inherit;
140
- position: absolute;
141
- right: $spacing-lg;
142
- top: 50%;
143
- transform: translateY(-50%);
144
- z-index: $z-index-base;
143
+ opacity: $stat-symbol-opacity;
144
+ word-break: keep-all;
145
+ white-space: nowrap;
145
146
  }
146
147
 
147
148
  .pa-stat__label {
148
- position: absolute;
149
- bottom: $spacing-base;
150
- left: $spacing-lg;
149
+ flex-basis: 100%; // forces label onto its own row
151
150
  font-size: $font-size-xs;
152
151
  text-transform: uppercase;
153
152
  letter-spacing: $stat-label-letter-spacing;
154
153
  font-weight: $font-weight-medium;
155
154
  color: inherit;
156
155
  opacity: 0.8;
157
- z-index: 2;
158
- max-width: calc(100% - #{$spacing-lg * 2});
159
156
  overflow: hidden;
160
157
  text-overflow: ellipsis;
161
158
  white-space: nowrap;
@@ -471,140 +471,8 @@
471
471
  }
472
472
  }
473
473
 
474
- // Pager
475
- .pa-pager {
476
- display: flex;
477
- margin: $pager-button-margin 0;
478
-
479
- // Remove margins when first/last child in card body
480
- .pa-card__body &:first-child {
481
- margin-top: 0;
482
- }
483
- .pa-card__body &:last-child {
484
- margin-bottom: 0;
485
- }
486
-
487
- // Default center alignment
488
- justify-content: center;
489
-
490
- // Positioning modifiers
491
- &--start {
492
- justify-content: flex-start;
493
- }
494
-
495
- &--center {
496
- justify-content: center;
497
- }
498
-
499
- &--end {
500
- justify-content: flex-end;
501
- }
502
-
503
- &__container {
504
- display: flex;
505
- align-items: center;
506
- gap: $spacing-sm;
507
- white-space: nowrap;
508
- }
509
-
510
- &__controls {
511
- display: flex;
512
- gap: $pager-controls-gap;
513
- }
514
-
515
- &__info {
516
- display: flex;
517
- align-items: center;
518
- gap: $spacing-sm;
519
- }
520
-
521
- &__input {
522
- width: $pager-input-width !important;
523
- text-align: center;
524
- }
525
-
526
- &__text {
527
- color: var(--pa-text-color-2);
528
- font-size: $font-size-sm;
529
- }
530
- }
531
-
532
- // Load More
533
- .pa-load-more {
534
- display: flex;
535
- margin: $spacing-base 0;
536
-
537
- // Remove margins when first/last child in card body
538
- .pa-card__body &:first-child {
539
- margin-top: 0;
540
- }
541
- .pa-card__body &:last-child {
542
- margin-bottom: 0;
543
- }
544
-
545
- // Default center alignment
546
- justify-content: center;
547
-
548
- // Positioning modifiers
549
- &--start {
550
- justify-content: flex-start;
551
- }
552
-
553
- &--center {
554
- justify-content: center;
555
- }
556
-
557
- &--end {
558
- justify-content: flex-end;
559
- }
560
-
561
- &__button {
562
- display: flex;
563
- align-items: center;
564
- gap: $spacing-sm;
565
- padding: $btn-padding-v $btn-padding-h;
566
- background-color: transparent;
567
- border: $border-width-base solid var(--pa-border-color);
568
- border-radius: var(--pa-border-radius);
569
- color: var(--pa-text-color-1);
570
- font-size: $font-size-sm;
571
- cursor: pointer;
572
- transition: all $transition-fast $easing-snappy;
573
-
574
- &:hover {
575
- border-color: var(--pa-accent);
576
- color: var(--pa-accent);
577
- background-color: var(--pa-accent-light);
578
- }
579
-
580
- &--loading {
581
- pointer-events: none;
582
- opacity: 0.7;
583
-
584
- .pa-load-more__spinner {
585
- animation: pa-spin 1s linear infinite;
586
- }
587
- }
588
- }
589
-
590
- &__spinner {
591
- width: $spinner-size;
592
- height: $spinner-size;
593
- border: $spinner-border-width solid var(--pa-border-color);
594
- border-top: $spinner-border-width solid var(--pa-accent);
595
- border-radius: 50%;
596
- }
597
-
598
- &__text {
599
- color: inherit;
600
- }
601
-
602
- &__count {
603
- color: var(--pa-text-color-2);
604
- font-size: $font-size-xs;
605
- margin-inline-start: $spacing-xs; // RTL: flips to margin-right
606
- }
607
- }
474
+ // .pa-pager and .pa-load-more live in _pagers.scss — this file used to
475
+ // duplicate them. Removed so there's a single source of truth.
608
476
 
609
477
  // Virtual Table Styles
610
478
  .pa-virtual-table {