@keenmate/pure-admin-core 2.7.0 → 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/README.md +15 -25
- package/dist/css/main.css +2057 -0
- package/package.json +1 -1
- package/src/scss/_base-css-variables.scss +6 -0
- package/src/scss/_core.scss +11 -0
- package/src/scss/core-components/_kpi-base.scss +169 -0
- package/src/scss/core-components/_kpi-bento.scss +217 -0
- package/src/scss/core-components/_kpi-comparison-gauges.scss +169 -0
- package/src/scss/core-components/_kpi-editorial-minimal.scss +171 -0
- package/src/scss/core-components/_kpi-hero-supporting.scss +224 -0
- package/src/scss/core-components/_kpi-numeric-strip.scss +213 -0
- package/src/scss/core-components/_kpi-sparkline-list.scss +223 -0
- package/src/scss/core-components/_kpi-terminal.scss +236 -0
- package/src/scss/main.scss +11 -0
package/package.json
CHANGED
|
@@ -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};
|
package/src/scss/_core.scss
CHANGED
|
@@ -117,5 +117,16 @@
|
|
|
117
117
|
// Data Visualization (progress bars, rings, gauges, heatmaps, sparklines)
|
|
118
118
|
@use 'core-components/data-viz' as *;
|
|
119
119
|
|
|
120
|
+
// KPI showcase components — shared chrome + 7 distinct designs.
|
|
121
|
+
// Base must come first; design files use the shared classes from base.
|
|
122
|
+
@use 'core-components/kpi-base' as *;
|
|
123
|
+
@use 'core-components/kpi-terminal' as *;
|
|
124
|
+
@use 'core-components/kpi-sparkline-list' as *;
|
|
125
|
+
@use 'core-components/kpi-comparison-gauges' as *;
|
|
126
|
+
@use 'core-components/kpi-hero-supporting' as *;
|
|
127
|
+
@use 'core-components/kpi-bento' as *;
|
|
128
|
+
@use 'core-components/kpi-numeric-strip' as *;
|
|
129
|
+
@use 'core-components/kpi-editorial-minimal' as *;
|
|
130
|
+
|
|
120
131
|
// Utility classes and helpers
|
|
121
132
|
@use 'core-components/utilities' as *;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/* ========================================
|
|
2
|
+
KPI · shared base
|
|
3
|
+
Cross-cutting building blocks used by every pa-kpi-* showcase.
|
|
4
|
+
Tokens (--pa-positive, --pa-detail-bg, --pa-chart-trendline-*) live in
|
|
5
|
+
_base-css-variables.scss; this file only defines the shared class
|
|
6
|
+
surface that consumes them.
|
|
7
|
+
======================================== */
|
|
8
|
+
@use '../variables' as *;
|
|
9
|
+
|
|
10
|
+
/* ----- LIVE indicator (title-bar pulse) ---------------------------------
|
|
11
|
+
Used in every KPI card header — a small mono "LIVE" caption with a
|
|
12
|
+
pulsing green dot. Sits next to the card title. */
|
|
13
|
+
.pa-kpi-live {
|
|
14
|
+
display: inline-flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
gap: 0.6rem;
|
|
17
|
+
font-family: var(--base-font-family-mono);
|
|
18
|
+
font-size: 1.2rem;
|
|
19
|
+
font-weight: 600;
|
|
20
|
+
letter-spacing: 0.06em;
|
|
21
|
+
color: var(--pa-text-secondary);
|
|
22
|
+
|
|
23
|
+
&__dot {
|
|
24
|
+
width: 0.8rem;
|
|
25
|
+
height: 0.8rem;
|
|
26
|
+
border-radius: 50%;
|
|
27
|
+
background: var(--pa-positive);
|
|
28
|
+
box-shadow: 0 0 6px var(--pa-positive);
|
|
29
|
+
animation: pa-kpi-pulse 1.6s ease-in-out infinite;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@keyframes pa-kpi-pulse {
|
|
34
|
+
50% { opacity: 0.35; }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* ----- Card header row --------------------------------------------------
|
|
38
|
+
Title (h2/h3 by author choice) on one side, controls + LIVE on the other.
|
|
39
|
+
Wraps onto multiple rows on narrow cards. Every showcase uses this shape. */
|
|
40
|
+
.pa-kpi-header {
|
|
41
|
+
display: flex;
|
|
42
|
+
justify-content: space-between;
|
|
43
|
+
align-items: center;
|
|
44
|
+
gap: 1.6rem;
|
|
45
|
+
flex-wrap: wrap;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* ----- Card footer caption ---------------------------------------------
|
|
49
|
+
Two-line mono caption row at the bottom of the card (left: data source,
|
|
50
|
+
right: timestamp). Strong text inside picks up the focal color so things
|
|
51
|
+
like a metric name or count stand out. */
|
|
52
|
+
.pa-kpi-footer {
|
|
53
|
+
display: flex;
|
|
54
|
+
justify-content: space-between;
|
|
55
|
+
align-items: center;
|
|
56
|
+
flex-wrap: wrap;
|
|
57
|
+
gap: 1rem;
|
|
58
|
+
font-family: var(--base-font-family-mono);
|
|
59
|
+
font-size: 1.2rem;
|
|
60
|
+
color: var(--pa-text-secondary);
|
|
61
|
+
|
|
62
|
+
strong {
|
|
63
|
+
color: var(--pa-text-color-1);
|
|
64
|
+
font-weight: 700;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* ----- Sparkline endpoint dot ------------------------------------------
|
|
69
|
+
The SVG circle that marks the end of a sparkline is converted to a CSS
|
|
70
|
+
span at init time — the SVG uses preserveAspectRatio="none" so an
|
|
71
|
+
embedded <circle> renders as an oval at non-square aspect ratios.
|
|
72
|
+
See kpi-showcases.js for the conversion; here we only style the result. */
|
|
73
|
+
.pa-kpi-spark-dot {
|
|
74
|
+
position: absolute;
|
|
75
|
+
width: 6px;
|
|
76
|
+
height: 6px;
|
|
77
|
+
margin: -3px 0 0 -3px; /* centre on the (left, top) anchor */
|
|
78
|
+
border-radius: 50%;
|
|
79
|
+
background: currentColor; /* inherits sentiment color from chart wrapper */
|
|
80
|
+
pointer-events: none;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Wrapper inserted around an SVG sparkline at init time when its parent
|
|
84
|
+
isn't already a positioned anchor — so the .pa-kpi-spark-dot can be
|
|
85
|
+
absolutely positioned relative to the chart. */
|
|
86
|
+
.pa-kpi-spark-wrap {
|
|
87
|
+
display: block;
|
|
88
|
+
position: relative;
|
|
89
|
+
width: 100%;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* ----- Hover detail popover --------------------------------------------
|
|
93
|
+
Bloomberg-dark by default regardless of host theme (terminal/data-dashboard
|
|
94
|
+
aesthetic). Override the --pa-detail-* tokens at :root or .pa-kpi-detail
|
|
95
|
+
level for a light/theme-aware variant.
|
|
96
|
+
|
|
97
|
+
Positioned by Floating UI anchored to a virtual element at the cursor —
|
|
98
|
+
the JS appends each popover to <body> at init so ancestor overflow:hidden
|
|
99
|
+
doesn't clip it. position: fixed because cursor coords are viewport-
|
|
100
|
+
relative; pointer-events:none so the cursor passes through cleanly and
|
|
101
|
+
tile mouseleave fires reliably. */
|
|
102
|
+
.pa-kpi-detail {
|
|
103
|
+
position: fixed;
|
|
104
|
+
top: 0;
|
|
105
|
+
left: 0;
|
|
106
|
+
visibility: hidden;
|
|
107
|
+
pointer-events: none;
|
|
108
|
+
z-index: 9000;
|
|
109
|
+
min-width: 26rem;
|
|
110
|
+
max-width: 32rem;
|
|
111
|
+
background: var(--pa-detail-bg);
|
|
112
|
+
color: var(--pa-detail-text);
|
|
113
|
+
padding: 1.2rem 1.5rem;
|
|
114
|
+
font-family: var(--base-font-family-mono);
|
|
115
|
+
font-size: 1.25rem;
|
|
116
|
+
line-height: 1.5;
|
|
117
|
+
border-radius: 0.4rem;
|
|
118
|
+
box-shadow: var(--pa-detail-shadow);
|
|
119
|
+
|
|
120
|
+
&[data-show] {
|
|
121
|
+
visibility: visible;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
&__title {
|
|
125
|
+
text-transform: uppercase;
|
|
126
|
+
letter-spacing: 0.08em;
|
|
127
|
+
font-size: 1.05rem;
|
|
128
|
+
font-weight: 700;
|
|
129
|
+
color: var(--pa-detail-title);
|
|
130
|
+
margin-bottom: 0.6rem;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
dl {
|
|
134
|
+
display: grid;
|
|
135
|
+
grid-template-columns: 1fr auto;
|
|
136
|
+
gap: 0.35rem 1.6rem;
|
|
137
|
+
margin: 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
dt {
|
|
141
|
+
margin: 0;
|
|
142
|
+
color: var(--pa-detail-row-label);
|
|
143
|
+
font-weight: 400;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
dd {
|
|
147
|
+
margin: 0;
|
|
148
|
+
text-align: end;
|
|
149
|
+
font-weight: 600;
|
|
150
|
+
font-variant-numeric: tabular-nums;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* Inline sentiment hints for dd content (e.g. <dd>+12% <span class="pos">…</span></dd>) */
|
|
154
|
+
.pos { color: var(--pa-positive); }
|
|
155
|
+
.neg { color: var(--pa-negative); }
|
|
156
|
+
.warn { color: var(--pa-warning); }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* ----- Section heading strip used outside card chrome -------------------
|
|
160
|
+
The 7 showcases each render a "stress-test" section that places tiles
|
|
161
|
+
directly inside .pa-col-* page-grid cells (no card wrapper). The strip
|
|
162
|
+
above those layouts hosts a label or LIVE indicator at the row's end. */
|
|
163
|
+
.pa-kpi-sectionhead {
|
|
164
|
+
display: flex;
|
|
165
|
+
justify-content: flex-end;
|
|
166
|
+
align-items: center;
|
|
167
|
+
gap: 1.6rem;
|
|
168
|
+
margin-bottom: 1.2rem;
|
|
169
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/* ========================================
|
|
2
|
+
KPI · Bento layout
|
|
3
|
+
Magazine-style asymmetric tile sizing with sparklines as soft background
|
|
4
|
+
fills behind the values. Default 6-tile layout on a 6-col × 3-row grid
|
|
5
|
+
(hero left-half × 2 rows, two stacked right-half × 2 rows, three equal
|
|
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.
|
|
16
|
+
======================================== */
|
|
17
|
+
@use '../variables' as *;
|
|
18
|
+
|
|
19
|
+
.pa-kpi-bento {
|
|
20
|
+
container-type: inline-size;
|
|
21
|
+
--pa-kpi-bento-row-height: 12rem;
|
|
22
|
+
}
|
|
23
|
+
.pa-kpi-bento__body { padding: 1.6rem; }
|
|
24
|
+
|
|
25
|
+
/* ----- Bento grid (default: 6 tiles, hero on left) ---------------------- */
|
|
26
|
+
.pa-kpi-bento__grid {
|
|
27
|
+
display: grid;
|
|
28
|
+
grid-template-columns: repeat(6, 1fr);
|
|
29
|
+
grid-template-rows: repeat(3, var(--pa-kpi-bento-row-height));
|
|
30
|
+
grid-template-areas:
|
|
31
|
+
"hero hero hero a a a"
|
|
32
|
+
"hero hero hero b b b"
|
|
33
|
+
"c c d d e e";
|
|
34
|
+
gap: 1rem;
|
|
35
|
+
|
|
36
|
+
> :nth-child(1) { grid-area: hero; }
|
|
37
|
+
> :nth-child(2) { grid-area: a; }
|
|
38
|
+
> :nth-child(3) { grid-area: b; }
|
|
39
|
+
> :nth-child(4) { grid-area: c; }
|
|
40
|
+
> :nth-child(5) { grid-area: d; }
|
|
41
|
+
> :nth-child(6) { grid-area: e; }
|
|
42
|
+
}
|
|
43
|
+
|
|
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). */
|
|
71
|
+
@container (max-width: 700px) {
|
|
72
|
+
.pa-kpi-bento__grid {
|
|
73
|
+
grid-template-columns: 1fr;
|
|
74
|
+
grid-template-rows: auto;
|
|
75
|
+
grid-template-areas: none;
|
|
76
|
+
gap: 0.8rem;
|
|
77
|
+
|
|
78
|
+
> :nth-child(n) { grid-area: auto; }
|
|
79
|
+
}
|
|
80
|
+
.pa-kpi-bento-tile { min-height: 10rem; }
|
|
81
|
+
.pa-kpi-bento-tile--hero { min-height: 14rem; }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* ----- Tile (per-KPI bento cell) ---------------------------------------- */
|
|
85
|
+
.pa-kpi-bento-tile {
|
|
86
|
+
/* Per-tile inline-size container so the value font-size scales with
|
|
87
|
+
*this* tile's width via cqi (not the outer card's width). The hero
|
|
88
|
+
and the smaller bottom tiles each scale to their own column. */
|
|
89
|
+
container-type: inline-size;
|
|
90
|
+
position: relative;
|
|
91
|
+
overflow: hidden;
|
|
92
|
+
padding: 1.3rem 1.5rem;
|
|
93
|
+
border: 1px solid var(--pa-border-color);
|
|
94
|
+
background: var(--pa-card-bg);
|
|
95
|
+
display: grid;
|
|
96
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
97
|
+
grid-template-rows: auto 1fr auto;
|
|
98
|
+
grid-template-areas:
|
|
99
|
+
"label delta"
|
|
100
|
+
". . "
|
|
101
|
+
"value value";
|
|
102
|
+
gap: 0.4rem 1rem;
|
|
103
|
+
|
|
104
|
+
/* Sparkline + delta sentiment cascade via currentColor. */
|
|
105
|
+
--pa-kpi-accent: var(--pa-positive);
|
|
106
|
+
|
|
107
|
+
&--positive { --pa-kpi-accent: var(--pa-positive); }
|
|
108
|
+
&--negative { --pa-kpi-accent: var(--pa-negative); }
|
|
109
|
+
&--neutral { --pa-kpi-accent: var(--pa-neutral); }
|
|
110
|
+
&--up-strong { --pa-kpi-accent: var(--pa-very-positive); }
|
|
111
|
+
&--down-strong { --pa-kpi-accent: var(--pa-very-negative); }
|
|
112
|
+
|
|
113
|
+
&--hero {
|
|
114
|
+
padding: 1.6rem 1.8rem;
|
|
115
|
+
|
|
116
|
+
.pa-kpi-bento-tile__num { font-size: clamp(3.6rem, 22cqi, 7rem); }
|
|
117
|
+
.pa-kpi-bento-tile__unit { font-size: clamp(1.6rem, 9cqi, 2.6rem); }
|
|
118
|
+
.pa-kpi-bento-tile__label,
|
|
119
|
+
.pa-kpi-bento-tile__delta { font-size: 1.4rem; }
|
|
120
|
+
.pa-kpi-bento-tile__chart { height: 70%; }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* ----- Top row: label + delta, both layered over the chart -------------- */
|
|
125
|
+
.pa-kpi-bento-tile__label {
|
|
126
|
+
grid-area: label;
|
|
127
|
+
font-family: var(--base-font-family-mono);
|
|
128
|
+
font-size: 1.3rem;
|
|
129
|
+
font-weight: 700;
|
|
130
|
+
letter-spacing: 0.1em;
|
|
131
|
+
text-transform: uppercase;
|
|
132
|
+
color: color-mix(in srgb, var(--pa-text-color-1) 60%, transparent);
|
|
133
|
+
z-index: 1;
|
|
134
|
+
line-height: 1.2;
|
|
135
|
+
}
|
|
136
|
+
.pa-kpi-bento-tile__delta {
|
|
137
|
+
grid-area: delta;
|
|
138
|
+
font-family: var(--base-font-family-mono);
|
|
139
|
+
font-size: 1.3rem;
|
|
140
|
+
font-weight: 700;
|
|
141
|
+
color: var(--pa-kpi-accent);
|
|
142
|
+
z-index: 1;
|
|
143
|
+
align-self: start;
|
|
144
|
+
line-height: 1.2;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* ----- Value (bottom of grid, layered over chart) ----------------------- */
|
|
148
|
+
.pa-kpi-bento-tile__value {
|
|
149
|
+
grid-area: value;
|
|
150
|
+
align-self: end;
|
|
151
|
+
position: relative;
|
|
152
|
+
z-index: 1;
|
|
153
|
+
font-family: var(--base-font-family-mono);
|
|
154
|
+
display: inline-flex;
|
|
155
|
+
align-items: baseline;
|
|
156
|
+
line-height: 1;
|
|
157
|
+
white-space: nowrap;
|
|
158
|
+
}
|
|
159
|
+
.pa-kpi-bento-tile__num {
|
|
160
|
+
font-size: clamp(2rem, 14cqi, 3.2rem);
|
|
161
|
+
font-weight: 700;
|
|
162
|
+
letter-spacing: -0.02em;
|
|
163
|
+
color: var(--pa-text-color-1);
|
|
164
|
+
}
|
|
165
|
+
.pa-kpi-bento-tile__unit {
|
|
166
|
+
font-size: clamp(1.2rem, 7cqi, 1.6rem);
|
|
167
|
+
font-weight: 500;
|
|
168
|
+
color: color-mix(in srgb, var(--pa-text-color-1) 50%, transparent);
|
|
169
|
+
margin: 0 0.3rem;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* ----- Sparkline background ---------------------------------------------
|
|
173
|
+
Absolutely positioned, fills the bottom 65% of the tile. The value sits
|
|
174
|
+
over it (z-index: 1 above). The line/area read through behind the
|
|
175
|
+
digits at lower opacity so the value remains the focal point. SVG is
|
|
176
|
+
nested in a fixed-height wrapper so taller tiles don't distort the line
|
|
177
|
+
shape (preserveAspectRatio="none" would otherwise stretch the Y axis). */
|
|
178
|
+
.pa-kpi-bento-tile__chart {
|
|
179
|
+
position: absolute;
|
|
180
|
+
left: 0;
|
|
181
|
+
right: 0;
|
|
182
|
+
bottom: 0;
|
|
183
|
+
height: 65%;
|
|
184
|
+
pointer-events: none;
|
|
185
|
+
color: var(--pa-kpi-accent);
|
|
186
|
+
z-index: 0;
|
|
187
|
+
display: flex;
|
|
188
|
+
align-items: flex-end;
|
|
189
|
+
|
|
190
|
+
svg {
|
|
191
|
+
display: block;
|
|
192
|
+
width: 100%;
|
|
193
|
+
height: 100%;
|
|
194
|
+
overflow: visible;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
polyline {
|
|
198
|
+
fill: none;
|
|
199
|
+
stroke: currentColor;
|
|
200
|
+
stroke-opacity: 0.55;
|
|
201
|
+
stroke-width: var(--pa-chart-trendline-stroke);
|
|
202
|
+
stroke-linecap: round;
|
|
203
|
+
stroke-linejoin: round;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
polygon {
|
|
207
|
+
fill: currentColor;
|
|
208
|
+
fill-opacity: 0.10;
|
|
209
|
+
stroke: none;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
.pa-kpi-bento-tile__chart-svg {
|
|
213
|
+
position: relative;
|
|
214
|
+
display: block;
|
|
215
|
+
width: 100%;
|
|
216
|
+
height: var(--pa-chart-trendline-height);
|
|
217
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/* ========================================
|
|
2
|
+
KPI · Comparison gauges
|
|
3
|
+
Goal-oriented progress bars. Each KPI shows label · value, a bar with a
|
|
4
|
+
target tick, and a 0 · tgt scale below. Bar fill = value/target * 100%,
|
|
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`.
|
|
13
|
+
======================================== */
|
|
14
|
+
@use '../variables' as *;
|
|
15
|
+
|
|
16
|
+
.pa-kpi-gauge-list__body { padding: 0; }
|
|
17
|
+
|
|
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. */
|
|
30
|
+
.pa-kpi-gauge-list__grid {
|
|
31
|
+
display: grid;
|
|
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);
|
|
35
|
+
}
|
|
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));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* ----- Gauge tile (label/value head · bar · 0/tgt scale) ---------------- */
|
|
75
|
+
.pa-kpi-gauge {
|
|
76
|
+
position: relative;
|
|
77
|
+
background: var(--pa-card-bg);
|
|
78
|
+
padding: 1.6rem 2rem;
|
|
79
|
+
min-width: 0;
|
|
80
|
+
|
|
81
|
+
/* Per-tile bar colour cascade — modifiers below set the var, the fill
|
|
82
|
+
reads it. Cleaner than per-modifier-per-element rules; host apps can
|
|
83
|
+
override at the tile level via inline style="--pa-kpi-bar-color: …". */
|
|
84
|
+
--pa-kpi-bar-color: var(--pa-positive);
|
|
85
|
+
|
|
86
|
+
&--positive { --pa-kpi-bar-color: var(--pa-positive); }
|
|
87
|
+
&--warning { --pa-kpi-bar-color: var(--pa-warning); }
|
|
88
|
+
&--negative { --pa-kpi-bar-color: var(--pa-negative); }
|
|
89
|
+
&--neutral { --pa-kpi-bar-color: color-mix(in srgb, var(--pa-text-color-1) 70%, transparent); }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* ----- Head: label left, value right (baseline-aligned) ----------------- */
|
|
93
|
+
.pa-kpi-gauge__head {
|
|
94
|
+
display: flex;
|
|
95
|
+
justify-content: space-between;
|
|
96
|
+
align-items: baseline;
|
|
97
|
+
gap: 1.2rem;
|
|
98
|
+
margin-bottom: 0.8rem;
|
|
99
|
+
}
|
|
100
|
+
.pa-kpi-gauge__label {
|
|
101
|
+
font-family: var(--base-font-family-mono);
|
|
102
|
+
font-size: 1.4rem;
|
|
103
|
+
font-weight: 700;
|
|
104
|
+
letter-spacing: 0.1em;
|
|
105
|
+
text-transform: uppercase;
|
|
106
|
+
color: color-mix(in srgb, var(--pa-text-color-1) 60%, transparent);
|
|
107
|
+
}
|
|
108
|
+
.pa-kpi-gauge__value {
|
|
109
|
+
font-family: var(--base-font-family-mono);
|
|
110
|
+
line-height: 1;
|
|
111
|
+
}
|
|
112
|
+
.pa-kpi-gauge__num {
|
|
113
|
+
font-size: 2.6rem;
|
|
114
|
+
font-weight: 700;
|
|
115
|
+
letter-spacing: -0.02em;
|
|
116
|
+
color: var(--pa-text-color-1);
|
|
117
|
+
}
|
|
118
|
+
.pa-kpi-gauge__unit {
|
|
119
|
+
font-size: 1.3rem;
|
|
120
|
+
font-weight: 500;
|
|
121
|
+
color: var(--pa-text-secondary);
|
|
122
|
+
margin-left: 0.2rem;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* ----- Bar (track + sentiment-coloured fill + target tick) -------------- */
|
|
126
|
+
.pa-kpi-gauge__bar {
|
|
127
|
+
/* Author-controlled tick position (default 100% — the target sits at the
|
|
128
|
+
right edge of the bar's "0 → target" scale). Override per-tile via
|
|
129
|
+
style="--pa-kpi-gauge-tick-pos: 80%" if the bar represents a wider
|
|
130
|
+
scale where the target sits inside the bar. */
|
|
131
|
+
--pa-kpi-gauge-tick-pos: 100%;
|
|
132
|
+
--pa-kpi-gauge-tick-color: var(--pa-text-color-1);
|
|
133
|
+
|
|
134
|
+
position: relative;
|
|
135
|
+
height: 0.7rem;
|
|
136
|
+
background: var(--pa-surface-track);
|
|
137
|
+
margin-bottom: 0.5rem;
|
|
138
|
+
overflow: visible; /* tick can sit just outside the track */
|
|
139
|
+
|
|
140
|
+
/* Target tick — small bar slightly taller than the track. Centred on
|
|
141
|
+
--pa-kpi-gauge-tick-pos via the negative margin-left (matches half
|
|
142
|
+
the tick width). Full opacity so it stays readable on dark themes
|
|
143
|
+
against bright orange/green fills. */
|
|
144
|
+
&::after {
|
|
145
|
+
content: '';
|
|
146
|
+
position: absolute;
|
|
147
|
+
left: var(--pa-kpi-gauge-tick-pos);
|
|
148
|
+
margin-left: -0.15rem;
|
|
149
|
+
top: -0.2rem;
|
|
150
|
+
bottom: -0.2rem;
|
|
151
|
+
width: 0.3rem;
|
|
152
|
+
background: var(--pa-kpi-gauge-tick-color);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
.pa-kpi-gauge__fill {
|
|
156
|
+
height: 100%;
|
|
157
|
+
background: var(--pa-kpi-bar-color);
|
|
158
|
+
/* Width set inline per tile via style="width: X%". */
|
|
159
|
+
transition: width 0.3s ease;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* ----- Scale row (0 left · tgt XYZ right) ------------------------------- */
|
|
163
|
+
.pa-kpi-gauge__scale {
|
|
164
|
+
display: flex;
|
|
165
|
+
justify-content: space-between;
|
|
166
|
+
font-family: var(--base-font-family-mono);
|
|
167
|
+
font-size: 1.2rem;
|
|
168
|
+
color: var(--pa-text-tertiary);
|
|
169
|
+
}
|