@keenmate/pure-admin-core 2.7.1 → 2.8.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keenmate/pure-admin-core",
3
- "version": "2.7.1",
3
+ "version": "2.8.0",
4
4
  "description": "Lightweight, data-focused HTML/CSS admin framework built with PureCSS foundation and comprehensive component system",
5
5
  "style": "dist/css/main.css",
6
6
  "exports": {
@@ -14,6 +14,12 @@
14
14
  // --ms-accent-color: var(--base-accent-color, #3b82f6);
15
15
  // ============================================================================
16
16
 
17
+ // Make variables resolvable when this file is loaded as a @use module
18
+ // (e.g. from main.scss). Legacy @import callers (themes) are unaffected —
19
+ // Sass places @use'd members into the importing file's global scope, where
20
+ // theme-set overrides already live.
21
+ @use 'variables/index' as *;
22
+
17
23
  @mixin output-base-css-variables {
18
24
  // === Accent Colors ===
19
25
  --base-accent-color: #{$base-accent-color};
@@ -1,22 +1,32 @@
1
1
  /* ========================================
2
2
  KPI · Bento layout
3
3
  Magazine-style asymmetric tile sizing with sparklines as soft background
4
- fills behind the values. 6 tiles arranged on a 6-col × 3-row grid
4
+ fills behind the values. Default 6-tile layout on a 6-col × 3-row grid
5
5
  (hero left-half × 2 rows, two stacked right-half × 2 rows, three equal
6
- tiles bottom row). Tile placement by source order via :nth-child.
6
+ tiles bottom row). Tile placement is by source order via :nth-child, so
7
+ markup stays identical across layout modifiers.
8
+
9
+ Layout modifiers (below) swap `grid-template-areas` to provide
10
+ alternative compositions:
11
+ · default (no modifier) — 6 tiles, hero on the left
12
+ · `--hero-right` — 6 tiles, mirror of default (hero on right)
13
+ · `--5-tile` — 5 tiles, hero + 4 supporting
14
+ Row height is a CSS variable (`--pa-kpi-bento-row-height`, default
15
+ `12rem`) so authors can dial tile height per instance.
7
16
  ======================================== */
8
17
  @use '../variables' as *;
9
18
 
10
19
  .pa-kpi-bento {
11
20
  container-type: inline-size;
21
+ --pa-kpi-bento-row-height: 12rem;
12
22
  }
13
23
  .pa-kpi-bento__body { padding: 1.6rem; }
14
24
 
15
- /* ----- Bento grid ------------------------------------------------------- */
25
+ /* ----- Bento grid (default: 6 tiles, hero on left) ---------------------- */
16
26
  .pa-kpi-bento__grid {
17
27
  display: grid;
18
28
  grid-template-columns: repeat(6, 1fr);
19
- grid-template-rows: 12rem 12rem 12rem;
29
+ grid-template-rows: repeat(3, var(--pa-kpi-bento-row-height));
20
30
  grid-template-areas:
21
31
  "hero hero hero a a a"
22
32
  "hero hero hero b b b"
@@ -31,8 +41,33 @@
31
41
  > :nth-child(6) { grid-area: e; }
32
42
  }
33
43
 
34
- /* Narrow card stack everything single column. Hero modifier still
35
- bumps the value font-size so it reads as the headline. */
44
+ /* Modifier: --hero-right mirror of default. Hero on the right half,
45
+ two stacked tiles on the left of rows 1-2, three equal tiles below.
46
+ Same 6-tile contract as default; only the template flips. Source
47
+ order stays unchanged (1st = hero, 2nd = a, …). */
48
+ .pa-kpi-bento__grid--hero-right {
49
+ grid-template-areas:
50
+ "a a a hero hero hero"
51
+ "b b b hero hero hero"
52
+ "c c d d e e";
53
+ }
54
+
55
+ /* Modifier: --5-tile — hero + 4 supporting. Hero spans the left half ×
56
+ 2 rows, two stacked tiles on the right (rows 1-2), two equal halves on
57
+ the bottom row. Requires exactly 5 tiles — a 6th tile (if present)
58
+ would be auto-placed in a new row and break the layout. */
59
+ .pa-kpi-bento__grid--5-tile {
60
+ grid-template-areas:
61
+ "hero hero hero a a a"
62
+ "hero hero hero b b b"
63
+ "c c c d d d";
64
+ }
65
+
66
+ /* Narrow card → stack everything single column. Resets `grid-area: auto`
67
+ on every tile so the same markup works regardless of which layout
68
+ modifier the grid carries (the @container override sets
69
+ `grid-template-areas: none` and the `grid-area: auto` on nth-child
70
+ together neutralise any modifier's template). */
36
71
  @container (max-width: 700px) {
37
72
  .pa-kpi-bento__grid {
38
73
  grid-template-columns: 1fr;
@@ -3,55 +3,92 @@
3
3
  Goal-oriented progress bars. Each KPI shows label · value, a bar with a
4
4
  target tick, and a 0 · tgt scale below. Bar fill = value/target * 100%,
5
5
  capped visually so overshoots are signalled by colour, not overflow.
6
+
7
+ Layout is a cell-min-driven `auto-fit` grid — cells stay at least
8
+ `--pa-kpi-gauge-cell-min` wide, the grid fits as many columns as the
9
+ container allows, and the responsive cascade is intrinsic to the grid
10
+ template (no `@container` queries). `--max-N` cap modifiers cap the
11
+ column count at N while still collapsing below the cell-min × N
12
+ threshold. Same pattern as `_kpi-editorial-minimal.scss`.
6
13
  ======================================== */
7
14
  @use '../variables' as *;
8
15
 
9
- .pa-kpi-gauge-list {
10
- container-type: inline-size;
11
- }
12
16
  .pa-kpi-gauge-list__body { padding: 0; }
13
17
 
14
- /* 2-column internal grid by default; collapses to 1-col when card narrows. */
18
+ /* ----- Grid + hairline rules -------------------------------------------
19
+ Cell-min-driven responsive layout. Override `--pa-kpi-gauge-cell-min`
20
+ per instance to change density (smaller min → more columns at the same
21
+ container width).
22
+
23
+ `gap: 1px` over `background: var(--pa-border-color)` with each tile
24
+ painting `background: var(--pa-card-bg)` on top — only the gap shows
25
+ through, giving single-pixel hairlines on every interior boundary
26
+ regardless of column count. The card's outer border supplies the
27
+ perimeter. Replaces the previous per-tile `border-right` +
28
+ `border-bottom` + nth-child suppression machinery, which only worked
29
+ for the hardcoded 2-col layout. */
15
30
  .pa-kpi-gauge-list__grid {
16
31
  display: grid;
17
- grid-template-columns: repeat(2, 1fr);
32
+ grid-template-columns: repeat(auto-fit, minmax(var(--pa-kpi-gauge-cell-min, 20rem), 1fr));
33
+ gap: 1px;
34
+ background: var(--pa-border-color);
18
35
  }
19
- @container (max-width: 600px) {
20
- .pa-kpi-gauge-list__grid {
21
- grid-template-columns: 1fr;
22
- }
36
+
37
+ /* Modifier: force exactly 2 columns regardless of cell-min or container
38
+ width. For placements wanting a deterministic 2×N layout. */
39
+ .pa-kpi-gauge-list__grid--2col { grid-template-columns: repeat(2, 1fr); }
40
+
41
+ /* Cap-at-N modifiers: combine auto-fit with a ceiling on the column
42
+ count. Cells stay at least --pa-kpi-gauge-cell-min wide, but the grid
43
+ never exceeds N columns even when the container is wide enough to fit
44
+ more. Below the cell-min × N threshold the grid still collapses
45
+ responsively — these modifiers cap the maximum, not the minimum.
46
+
47
+ How the calc reads: each track's effective min is
48
+ max(cell-min, (container − total-gap) / N)
49
+ On a wide container the calc wins (so tracks grow, you stay at N).
50
+ On a narrow container cell-min wins (so the grid collapses).
51
+
52
+ Pick the cap so your item count divides into clean rows. */
53
+ .pa-kpi-gauge-list__grid--max-2 {
54
+ grid-template-columns:
55
+ repeat(auto-fit, minmax(max(var(--pa-kpi-gauge-cell-min, 20rem), calc((100% - 1px) / 2)), 1fr));
56
+ }
57
+ .pa-kpi-gauge-list__grid--max-3 {
58
+ grid-template-columns:
59
+ repeat(auto-fit, minmax(max(var(--pa-kpi-gauge-cell-min, 20rem), calc((100% - 1px * 2) / 3)), 1fr));
60
+ }
61
+ .pa-kpi-gauge-list__grid--max-4 {
62
+ grid-template-columns:
63
+ repeat(auto-fit, minmax(max(var(--pa-kpi-gauge-cell-min, 20rem), calc((100% - 1px * 3) / 4)), 1fr));
64
+ }
65
+ .pa-kpi-gauge-list__grid--max-5 {
66
+ grid-template-columns:
67
+ repeat(auto-fit, minmax(max(var(--pa-kpi-gauge-cell-min, 20rem), calc((100% - 1px * 4) / 5)), 1fr));
68
+ }
69
+ .pa-kpi-gauge-list__grid--max-6 {
70
+ grid-template-columns:
71
+ repeat(auto-fit, minmax(max(var(--pa-kpi-gauge-cell-min, 20rem), calc((100% - 1px * 5) / 6)), 1fr));
23
72
  }
24
73
 
25
74
  /* ----- Gauge tile (label/value head · bar · 0/tgt scale) ---------------- */
26
75
  .pa-kpi-gauge {
27
76
  position: relative;
77
+ background: var(--pa-card-bg);
28
78
  padding: 1.6rem 2rem;
29
- border-bottom: 1px solid var(--pa-border-color);
30
- border-right: 1px solid var(--pa-border-color);
79
+ min-width: 0;
31
80
 
32
81
  /* Per-tile bar colour cascade — modifiers below set the var, the fill
33
82
  reads it. Cleaner than per-modifier-per-element rules; host apps can
34
83
  override at the tile level via inline style="--pa-kpi-bar-color: …". */
35
84
  --pa-kpi-bar-color: var(--pa-positive);
36
85
 
37
- &:nth-child(2n) { border-right: 0; }
38
- &:nth-last-child(-n+2) { border-bottom: 0; }
39
-
40
86
  &--positive { --pa-kpi-bar-color: var(--pa-positive); }
41
87
  &--warning { --pa-kpi-bar-color: var(--pa-warning); }
42
88
  &--negative { --pa-kpi-bar-color: var(--pa-negative); }
43
89
  &--neutral { --pa-kpi-bar-color: color-mix(in srgb, var(--pa-text-color-1) 70%, transparent); }
44
90
  }
45
91
 
46
- @container (max-width: 600px) {
47
- .pa-kpi-gauge {
48
- border-right: 0;
49
-
50
- &:nth-last-child(-n+2) { border-bottom: 1px solid var(--pa-border-color); }
51
- &:last-child { border-bottom: 0; }
52
- }
53
- }
54
-
55
92
  /* ----- Head: label left, value right (baseline-aligned) ----------------- */
56
93
  .pa-kpi-gauge__head {
57
94
  display: flex;
@@ -1,6 +1,6 @@
1
1
  /* ========================================
2
2
  KPI · Editorial minimal
3
- Six KPIs in a 2×3 / 3×2 grid with hairline rules between cells,
3
+ KPI tiles in a responsive grid with hairline rules between cells,
4
4
  generous space, and an extra-light-weight number as the focal point per
5
5
  tile. No charts, no pills — the design's whole identity is the thin
6
6
  numeral. Renders as a table card (zero card-body padding) so hairlines
@@ -11,32 +11,73 @@
11
11
  .pa-kpi-edit__body { padding: 0; }
12
12
 
13
13
  /* ----- Grid + hairline rules -------------------------------------------
14
+ Cell-min-driven responsive layout: cells stay at least
15
+ --pa-kpi-edit-cell-min wide; the grid fits as many columns as the
16
+ container allows. No @container queries — `auto-fit + minmax` handles
17
+ the cascade intrinsically. Override the min per instance to change
18
+ density (smaller min → more columns at the same container width).
19
+
14
20
  `gap: 1px` over `background: var(--pa-border-color)` with each tile
15
- painting `background: var(--pa-card-bg)` on top. Only the gap shows
16
- through, giving perfect single-pixel hairlines on every interior
17
- boundary (vertical and horizontal) for free, regardless of column
18
- count. The card's outer border supplies the perimeter. */
21
+ painting `background: var(--pa-card-bg)` on top only the gap shows
22
+ through, giving single-pixel hairlines on every interior boundary
23
+ regardless of column count. The card's outer border supplies the
24
+ perimeter. */
19
25
  .pa-kpi-edit__grid {
20
26
  display: grid;
21
- grid-template-columns: repeat(3, 1fr);
27
+ grid-template-columns: repeat(auto-fit, minmax(var(--pa-kpi-edit-cell-min, 14rem), 1fr));
22
28
  gap: 1px;
23
29
  background: var(--pa-border-color);
24
- container-type: inline-size;
25
- }
26
- @container (max-width: 640px) {
27
- .pa-kpi-edit__grid { grid-template-columns: repeat(2, 1fr); }
28
- }
29
- @container (max-width: 360px) {
30
- .pa-kpi-edit__grid { grid-template-columns: 1fr; }
31
30
  }
32
31
 
33
- /* Modifier: force 2-col regardless of card width. For placements wanting
34
- a deterministic 2×N layout (e.g. 4 tiles as clean 2×2) instead of
35
- relying on the container query to land in the right bucket. */
32
+ /* Modifier: force exactly 2 columns regardless of cell-min or container
33
+ width. For placements wanting a deterministic 2×N layout (e.g. 4 tiles
34
+ as a clean 2×2 even when the container is wide enough for more). */
36
35
  .pa-kpi-edit__grid--2col { grid-template-columns: repeat(2, 1fr); }
37
36
 
38
- /* ----- Tile (editorial spacing leans on the 2.4rem padding) ------------- */
37
+ /* Cap-at-N modifiers: combine auto-fit with a ceiling on the column
38
+ count. Cells stay at least --pa-kpi-edit-cell-min wide, but the grid
39
+ never exceeds N columns even when the container is wide enough to fit
40
+ more. Below the cell-min × N threshold the grid still collapses
41
+ responsively — these modifiers cap the maximum, not the minimum.
42
+
43
+ How the calc reads: each track's effective min is
44
+ max(cell-min, (container − total-gap) / N)
45
+ On a wide container the calc wins (so tracks grow, you stay at N).
46
+ On a narrow container cell-min wins (so the grid collapses).
47
+
48
+ Why this matters for the gray-void edge case: `auto-fit` keeps as many
49
+ tracks as fit the container, then only collapses tracks empty across
50
+ the *whole* grid. With 6 items at 4-col auto-fit, tracks 1–4 all have
51
+ row-1 items, so row 2's tracks 3–4 stay (and show the gap background).
52
+ Capping at 3 makes 6 items pack 3×2 cleanly. Pick the cap so your item
53
+ count divides into clean rows. */
54
+ .pa-kpi-edit__grid--max-2 {
55
+ grid-template-columns:
56
+ repeat(auto-fit, minmax(max(var(--pa-kpi-edit-cell-min, 14rem), calc((100% - 1px) / 2)), 1fr));
57
+ }
58
+ .pa-kpi-edit__grid--max-3 {
59
+ grid-template-columns:
60
+ repeat(auto-fit, minmax(max(var(--pa-kpi-edit-cell-min, 14rem), calc((100% - 1px * 2) / 3)), 1fr));
61
+ }
62
+ .pa-kpi-edit__grid--max-4 {
63
+ grid-template-columns:
64
+ repeat(auto-fit, minmax(max(var(--pa-kpi-edit-cell-min, 14rem), calc((100% - 1px * 3) / 4)), 1fr));
65
+ }
66
+ .pa-kpi-edit__grid--max-5 {
67
+ grid-template-columns:
68
+ repeat(auto-fit, minmax(max(var(--pa-kpi-edit-cell-min, 14rem), calc((100% - 1px * 4) / 5)), 1fr));
69
+ }
70
+ .pa-kpi-edit__grid--max-6 {
71
+ grid-template-columns:
72
+ repeat(auto-fit, minmax(max(var(--pa-kpi-edit-cell-min, 14rem), calc((100% - 1px * 5) / 6)), 1fr));
73
+ }
74
+
75
+ /* ----- Tile (editorial spacing leans on the 2.4rem padding) -------------
76
+ Per-cell container: the value's `cqi`-based font-size scales with
77
+ *this* tile's width, not the whole grid's. Keeps typography legible as
78
+ the grid packs more cells into the same row. */
39
79
  .pa-kpi-edit__tile {
80
+ container-type: inline-size;
40
81
  background: var(--pa-card-bg);
41
82
  padding: 2.4rem 2rem;
42
83
  display: flex;
@@ -69,11 +110,13 @@
69
110
  "monospace at low contrast".
70
111
  - font-weight: 200 (extra-light). 300 was tested first but didn't read
71
112
  distinctly enough as "light" against the body's 400 default.
72
- - clamp() lets the number shrink in narrow 25% page-grid cells without
73
- manual breakpoints. */
113
+ - clamp() lets the number shrink in narrow cells without manual
114
+ breakpoints. `cqi` measures per-cell (tile is a container) so the
115
+ value tracks each cell's actual width as the grid packs more
116
+ columns into a wider container. */
74
117
  .pa-kpi-edit__value {
75
118
  font-family: var(--base-font-family);
76
- font-size: clamp(3.2rem, 18cqi, 5.6rem);
119
+ font-size: clamp(3.2rem, 22cqi, 5.6rem);
77
120
  font-weight: 200;
78
121
  letter-spacing: -0.02em;
79
122
  line-height: 1;
@@ -12,12 +12,26 @@
12
12
  }
13
13
  .pa-kpi-hero-list__body { padding: 1.6rem; }
14
14
 
15
- /* ----- Layout: hero left, rail right ------------------------------------ */
15
+ /* ----- Layout: hero left, rail right ------------------------------------
16
+ Default split is 1:1 (50% hero / 50% rail). Modifiers below shift the
17
+ weight to the hero, which is the more common "executive dashboard"
18
+ shape — one big headline, supporting tiles compressed into a narrower
19
+ rail. Pick a modifier per instance; the markup stays unchanged. */
16
20
  .pa-kpi-hero-list__layout {
17
21
  display: grid;
18
22
  grid-template-columns: 1fr 1fr;
19
23
  gap: 1.4rem;
20
24
  }
25
+ /* Hero 2/3, rail 1/3. */
26
+ .pa-kpi-hero-list__layout--hero-2-3 {
27
+ grid-template-columns: 2fr 1fr;
28
+ }
29
+ /* Hero 3/4, rail 1/4 — hero-dominant; rail is a thin sidebar. */
30
+ .pa-kpi-hero-list__layout--hero-3-4 {
31
+ grid-template-columns: 3fr 1fr;
32
+ }
33
+ /* Narrow card → stack to single column. Overrides any --hero-N-M modifier
34
+ that lives on the same element. */
21
35
  @container (max-width: 700px) {
22
36
  .pa-kpi-hero-list__layout {
23
37
  grid-template-columns: 1fr;
@@ -1,13 +1,27 @@
1
1
  /* ========================================
2
2
  KPI · Numeric strip (densest)
3
- Tabular "spreadsheet-style" table card with metric/now/prev/Δ%/vs target
4
- columns — most data per pixel, no chart chrome. Each row is its own
5
- grid sharing the same column template so cells align across rows
3
+ Tabular "spreadsheet-style" table card with metric / now / prev / Δ% /
4
+ target columns — most data per pixel, no chart chrome. Each row is its
5
+ own grid sharing the same column template so cells align across rows
6
6
  without needing subgrid. Wide-only by design — no responsive collapse;
7
7
  narrow placements should use Comparison gauges instead.
8
+
9
+ Column contract: `metric` + `now` are always present; `prev`, `delta`,
10
+ and `target` are independently optional via toggle modifiers on the
11
+ strip (`--no-prev`, `--no-delta`, `--no-target`). Modifiers compose, so
12
+ the visible column count ranges from 2 (all three optional columns
13
+ dropped) to 5 (none dropped). Each combination has its own template
14
+ selector below so the grid tracks always match the visible cell count.
8
15
  ======================================== */
9
16
  @use '../variables' as *;
10
17
 
18
+ /* Per-column track widths — referenced from every template selector. */
19
+ $strip-col-metric: minmax(0, 2fr);
20
+ $strip-col-now: minmax(0, 1.1fr);
21
+ $strip-col-prev: minmax(0, 1fr);
22
+ $strip-col-delta: minmax(0, 1fr);
23
+ $strip-col-target: minmax(0, 1.6fr);
24
+
11
25
  .pa-kpi-strip__body { padding: 0; }
12
26
 
13
27
  /* ----- Table layout ----------------------------------------------------- */
@@ -15,11 +29,11 @@
15
29
  .pa-kpi-strip__head-row {
16
30
  display: grid;
17
31
  grid-template-columns:
18
- minmax(0, 2fr) /* metric */
19
- minmax(0, 1.1fr) /* now */
20
- minmax(0, 1fr) /* prev */
21
- minmax(0, 1fr) /* delta */
22
- minmax(0, 1.6fr); /* target (bar + pct stacked) */
32
+ $strip-col-metric
33
+ $strip-col-now
34
+ $strip-col-prev
35
+ $strip-col-delta
36
+ $strip-col-target;
23
37
  column-gap: 1.6rem;
24
38
  align-items: center;
25
39
  position: relative;
@@ -142,13 +156,58 @@
142
156
  line-height: 1;
143
157
  }
144
158
 
145
- /* ----- --no-prev: 4-col layout (metric / now / Δ% / target) -------------
146
- Useful for narrow placements; markup omits the prev cells. */
159
+ /* ----- Toggle modifiers ------------------------------------------------
160
+ `--no-prev`, `--no-delta`, `--no-target` are independently composable.
161
+ Each hides its column's cells (data + header) and the matching template
162
+ selector below adjusts `grid-template-columns` to the visible cell
163
+ count, so source order (metric, now, prev, delta, target) is preserved
164
+ and the remaining cells slot into the right tracks.
165
+
166
+ `metric` and `now` are mandatory — no toggle drops them. If a strip
167
+ doesn't need a focal value, it's a different design (use comparison
168
+ gauges or editorial-minimal). */
169
+ .pa-kpi-strip--no-prev .pa-kpi-strip__prev,
170
+ .pa-kpi-strip--no-prev .pa-kpi-strip__head--prev { display: none; }
171
+ .pa-kpi-strip--no-delta .pa-kpi-strip__delta,
172
+ .pa-kpi-strip--no-delta .pa-kpi-strip__head--delta { display: none; }
173
+ .pa-kpi-strip--no-target .pa-kpi-strip__target,
174
+ .pa-kpi-strip--no-target .pa-kpi-strip__head--target { display: none; }
175
+
176
+ /* ----- Grid templates per visible-column combination -------------------
177
+ 8 combos (1 default + 3 single-drops + 3 double-drops + 1 triple-drop).
178
+ Each lists the visible columns in source order with their declared
179
+ `$strip-col-*` width. */
180
+
181
+ /* 4-col: one optional column dropped */
147
182
  .pa-kpi-strip--no-prev .pa-kpi-strip__row,
148
183
  .pa-kpi-strip--no-prev .pa-kpi-strip__head-row {
149
- grid-template-columns:
150
- minmax(0, 2fr)
151
- minmax(0, 1.1fr)
152
- minmax(0, 1fr)
153
- minmax(0, 1.6fr);
184
+ grid-template-columns: $strip-col-metric $strip-col-now $strip-col-delta $strip-col-target;
185
+ }
186
+ .pa-kpi-strip--no-delta .pa-kpi-strip__row,
187
+ .pa-kpi-strip--no-delta .pa-kpi-strip__head-row {
188
+ grid-template-columns: $strip-col-metric $strip-col-now $strip-col-prev $strip-col-target;
189
+ }
190
+ .pa-kpi-strip--no-target .pa-kpi-strip__row,
191
+ .pa-kpi-strip--no-target .pa-kpi-strip__head-row {
192
+ grid-template-columns: $strip-col-metric $strip-col-now $strip-col-prev $strip-col-delta;
193
+ }
194
+
195
+ /* 3-col: two optional columns dropped */
196
+ .pa-kpi-strip--no-prev.pa-kpi-strip--no-delta .pa-kpi-strip__row,
197
+ .pa-kpi-strip--no-prev.pa-kpi-strip--no-delta .pa-kpi-strip__head-row {
198
+ grid-template-columns: $strip-col-metric $strip-col-now $strip-col-target;
199
+ }
200
+ .pa-kpi-strip--no-prev.pa-kpi-strip--no-target .pa-kpi-strip__row,
201
+ .pa-kpi-strip--no-prev.pa-kpi-strip--no-target .pa-kpi-strip__head-row {
202
+ grid-template-columns: $strip-col-metric $strip-col-now $strip-col-delta;
203
+ }
204
+ .pa-kpi-strip--no-delta.pa-kpi-strip--no-target .pa-kpi-strip__row,
205
+ .pa-kpi-strip--no-delta.pa-kpi-strip--no-target .pa-kpi-strip__head-row {
206
+ grid-template-columns: $strip-col-metric $strip-col-now $strip-col-prev;
207
+ }
208
+
209
+ /* 2-col: all three optional columns dropped (metric + now only) */
210
+ .pa-kpi-strip--no-prev.pa-kpi-strip--no-delta.pa-kpi-strip--no-target .pa-kpi-strip__row,
211
+ .pa-kpi-strip--no-prev.pa-kpi-strip--no-delta.pa-kpi-strip--no-target .pa-kpi-strip__head-row {
212
+ grid-template-columns: $strip-col-metric $strip-col-now;
154
213
  }
@@ -4,9 +4,23 @@
4
4
  scanning rather than per-tile depth — no view-mode toggle, no status
5
5
  pills. Container queries collapse 4-col → 2-row → 3-row as the card
6
6
  narrows.
7
+
8
+ Row template uses local SCSS variables for the four column widths
9
+ (`$spark-col-label`, `$spark-col-chart`, `$spark-col-value`,
10
+ `$spark-col-delta`) so all responsive overrides reference the same
11
+ widths — change one and every breakpoint follows. The `--no-delta`
12
+ modifier drops the rightmost column; the other three are load-bearing
13
+ (a sparkline list without label, chart, or value is a different
14
+ design).
7
15
  ======================================== */
8
16
  @use '../variables' as *;
9
17
 
18
+ /* Per-column track widths — referenced from every template selector. */
19
+ $spark-col-label: minmax(14rem, 28%);
20
+ $spark-col-chart: minmax(10rem, 1fr);
21
+ $spark-col-value: minmax(8rem, 18%);
22
+ $spark-col-delta: minmax(7rem, 12%);
23
+
10
24
  /* Card is a query container so rows react to *card* width, not viewport. */
11
25
  .pa-kpi-spark-list {
12
26
  container-type: inline-size;
@@ -17,10 +31,10 @@
17
31
  .pa-kpi-spark-row {
18
32
  display: grid;
19
33
  grid-template-columns:
20
- minmax(14rem, 28%)
21
- minmax(10rem, 1fr)
22
- minmax(8rem, 18%)
23
- minmax(7rem, 12%);
34
+ $spark-col-label
35
+ $spark-col-chart
36
+ $spark-col-value
37
+ $spark-col-delta;
24
38
  align-items: center;
25
39
  gap: 1.6rem;
26
40
  padding: 1.4rem 2rem;
@@ -29,6 +43,18 @@
29
43
  &:last-child { border-bottom: 0; }
30
44
  }
31
45
 
46
+ /* Modifier: --no-delta — drops the rightmost column. label · chart ·
47
+ value only. Useful when the chart's slope already conveys direction
48
+ and the delta would be redundant. Hides the delta element and shrinks
49
+ the row template to 3 columns. */
50
+ .pa-kpi-spark-list--no-delta .pa-kpi-spark-row {
51
+ grid-template-columns:
52
+ $spark-col-label
53
+ $spark-col-chart
54
+ $spark-col-value;
55
+ }
56
+ .pa-kpi-spark-list--no-delta .pa-kpi-spark-row__delta { display: none; }
57
+
32
58
  /* Mid-narrow card (1×3 page-grid + 45% column): stack to 2 rows.
33
59
  Label/value/delta on top, full-width chart below. Default order is
34
60
  value-above-chart; use .pa-kpi-spark-list--chart-first to flip. */
@@ -66,6 +92,23 @@
66
92
  .pa-kpi-spark-list--chart-first .pa-kpi-spark-row__label { align-self: start; }
67
93
  .pa-kpi-spark-list--chart-first .pa-kpi-spark-row__value { text-align: start; align-self: baseline; }
68
94
  .pa-kpi-spark-list--chart-first .pa-kpi-spark-row__delta { align-self: baseline; }
95
+
96
+ /* --no-delta at mid-narrow: 2-row layout, top row is label+value only. */
97
+ .pa-kpi-spark-list--no-delta .pa-kpi-spark-row {
98
+ grid-template-columns: minmax(0, 1fr) auto;
99
+ grid-template-areas:
100
+ "label value"
101
+ "chart chart";
102
+ }
103
+
104
+ /* --no-delta + --chart-first: 3-row single-column stack. */
105
+ .pa-kpi-spark-list--no-delta.pa-kpi-spark-list--chart-first .pa-kpi-spark-row {
106
+ grid-template-columns: 1fr;
107
+ grid-template-areas:
108
+ "label"
109
+ "chart"
110
+ "value";
111
+ }
69
112
  }
70
113
 
71
114
  /* Very narrow card (~280–360px, 25% page-grid stress test): force the
@@ -86,6 +129,15 @@
86
129
  .pa-kpi-spark-row__chart { grid-area: chart; }
87
130
  .pa-kpi-spark-row__value { grid-area: value; text-align: start; align-self: baseline; }
88
131
  .pa-kpi-spark-row__delta { grid-area: delta; align-self: baseline; }
132
+
133
+ /* --no-delta at very-narrow: 3-row single-column stack (no delta cell). */
134
+ .pa-kpi-spark-list--no-delta .pa-kpi-spark-row {
135
+ grid-template-columns: 1fr;
136
+ grid-template-areas:
137
+ "label"
138
+ "chart"
139
+ "value";
140
+ }
89
141
  }
90
142
 
91
143
  /* ----- Cell typography -------------------------------------------------- */
@@ -1,25 +1,31 @@
1
1
  /* ========================================
2
2
  KPI · Terminal grid
3
3
  Bloomberg-style dense panel: mono numbers, status pills, inline SVG
4
- sparklines, ▲▼ deltas, segmented view-mode toggle (VALUE/Δ%/TREND).
4
+ sparklines, ▲▼ deltas, optional segmented tab strip for swapping
5
+ the body content (different tile sets / grids per tab).
5
6
  Shared chrome (header, live, footer, detail popover, spark-dot) is in
6
7
  _kpi-base.scss.
7
8
  ======================================== */
8
9
  @use '../variables' as *;
9
10
 
10
- /* ----- View-mode toggle (segmented button group) ------------------------ */
11
+ /* ----- Tab strip (segmented button group) ------------------------------
12
+ Generalised tabs — each `.pa-kpi-terminal__tab` carries a `data-tab`
13
+ slug that matches a `.pa-kpi-terminal__pane[data-tab="…"]` in the body.
14
+ JS toggles `.is-active` on the clicked tab + matching pane; tabs and
15
+ panes are otherwise independent so each pane can hold a completely
16
+ different grid (different tile count, different grid modifier). */
11
17
  .pa-kpi-terminal__controls {
12
18
  display: inline-flex;
13
19
  align-items: center;
14
20
  gap: 1.6rem;
15
21
  }
16
- .pa-kpi-terminal__viewtoggle {
22
+ .pa-kpi-terminal__tabs {
17
23
  display: inline-flex;
18
24
  border: 1px solid var(--pa-border-color);
19
25
  border-radius: 0.4rem;
20
26
  overflow: hidden;
21
27
  }
22
- .pa-kpi-terminal__viewbtn {
28
+ .pa-kpi-terminal__tab {
23
29
  background: transparent;
24
30
  border: 0;
25
31
  color: var(--pa-text-color-2);
@@ -41,6 +47,13 @@
41
47
  }
42
48
  }
43
49
 
50
+ /* ----- Pane visibility ------------------------------------------------- */
51
+ .pa-kpi-terminal__pane {
52
+ display: none;
53
+
54
+ &.is-active { display: block; }
55
+ }
56
+
44
57
  /* ----- Grid + tile borders ----------------------------------------------
45
58
  Hairline 1px borders between tiles, no gap. Last-row/last-column
46
59
  borders suppressed via :nth-last-child / :nth-child selectors. */
@@ -132,16 +145,15 @@
132
145
  margin-bottom: 0.8rem;
133
146
  }
134
147
 
135
- /* ----- Big value with 3-mode swap --------------------------------------
136
- Author renders three .pa-kpi-tile__value siblings (data-mode="value" /
137
- "delta" / "trend"); the active mode is selected via the
138
- .pa-kpi-terminal[data-view="X"] attribute. JS toggles that attribute
139
- when the segmented control is clicked. */
148
+ /* ----- Big value -------------------------------------------------------
149
+ One value per tile sentiment colour set via `--very-positive` /
150
+ `--positive` / `--neutral` / `--negative` / `--very-negative` modifier
151
+ on the value element. */
140
152
  .pa-kpi-tile__values {
141
153
  margin-bottom: 0.4rem;
142
154
  }
143
155
  .pa-kpi-tile__value {
144
- display: none;
156
+ display: inline-flex;
145
157
  align-items: baseline;
146
158
  gap: 0.3rem;
147
159
  font-family: var(--base-font-family-mono);
@@ -153,11 +165,6 @@
153
165
  &--negative .pa-kpi-tile__num { color: var(--pa-negative); }
154
166
  &--very-negative .pa-kpi-tile__num { color: var(--pa-very-negative); }
155
167
  }
156
- .pa-kpi-terminal[data-view="value"] .pa-kpi-tile__value[data-mode="value"],
157
- .pa-kpi-terminal[data-view="delta"] .pa-kpi-tile__value[data-mode="delta"],
158
- .pa-kpi-terminal[data-view="trend"] .pa-kpi-tile__value[data-mode="trend"] {
159
- display: inline-flex;
160
- }
161
168
 
162
169
  .pa-kpi-tile__num {
163
170
  font-size: 3.8rem;
@@ -5,3 +5,14 @@
5
5
 
6
6
  // Core framework (includes variables and all core-components)
7
7
  @use 'core' as *;
8
+
9
+ // CSS variable defaults at :root. Themes don't go through main.scss — they
10
+ // @import core + base-css-variables and emit their own values — so this only
11
+ // affects the unthemed bundle (dist/css/main.css) and the FOUC window before
12
+ // a theme stylesheet resolves.
13
+ @use 'base-css-variables' as bcv;
14
+ :root {
15
+ @include bcv.output-base-css-variables;
16
+ @include bcv.output-pa-css-variables;
17
+ @include bcv.output-pa-alert-variables-light;
18
+ }