diff --git a/frontend/src/components/StatsPanel.tsx b/frontend/src/components/StatsPanel.tsx index 959f740..ab3529d 100644 --- a/frontend/src/components/StatsPanel.tsx +++ b/frontend/src/components/StatsPanel.tsx @@ -52,11 +52,14 @@ const nf = (n: number) => n.toLocaleString('en-US'); // ── Shell ──────────────────────────────────────────────────────────────────── -function Card({ title, sub, children, className }: { title: string; sub?: string; children: React.ReactNode; className?: string }) { +function Card({ title, sub, children, className, accent = 'var(--chart-1)' }: { title: string; sub?: string; children: React.ReactNode; className?: string; accent?: string }) { return ( -
+
-

{title}

+

+ + {title} +

{sub &&

{sub}

}
{children} @@ -64,14 +67,22 @@ function Card({ title, sub, children, className }: { title: string; sub?: string ); } -// A headline number IS the chart — a one-bar bar chart would be noise. -function StatTile({ label, value, sub }: { label: string; value: string; sub?: string }) { +// A headline number IS the chart — a one-bar bar chart would be noise. Each tile +// carries an accent (a categorical chart hue or a semantic token): a soft tint +// wash + a left accent bar give it a colourful identity, while the number itself +// stays in high-contrast foreground ink so it reads on every theme. +function StatTile({ label, value, sub, accent = 'var(--chart-1)' }: { label: string; value: string; sub?: string; accent?: string }) { return ( -
-

{label}

- {/* Proportional figures: tabular-nums makes a big standalone number look loose. */} -

{value}

- {sub &&

{sub}

} +
+
+
+
+
+

{label}

+ {/* Proportional figures: tabular-nums makes a big standalone number look loose. */} +

{value}

+ {sub &&

{sub}

} +
); } @@ -80,7 +91,7 @@ function StatTile({ label, value, sub }: { label: string; value: string; sub?: s // One series → one hue. The value is direct-labelled, so no reader ever depends // on a tooltip to get a number. -function HBars({ data, max, empty, share, labelWidth = 'w-20' }: { data: Bucket[]; max?: number; empty: string; share?: boolean; labelWidth?: string }) { +function HBars({ data, max, empty, share, labelWidth = 'w-20', colorful, color = 'var(--chart-1)' }: { data: Bucket[]; max?: number; empty: string; share?: boolean; labelWidth?: string; colorful?: boolean; color?: string }) { // max is a display cap for long tails (top entities). Where EVERY row matters — // the operators of a multi-op — it is deliberately not set: a capped chart would // silently drop the 9th operator, and "who worked what" is the whole question. @@ -90,13 +101,13 @@ function HBars({ data, max, empty, share, labelWidth = 'w-20' }: { data: Bucket[ if (top.length === 0) return

{empty}

; return (
- {top.map((d) => ( + {top.map((d, i) => (
{d.key}
{nf(d.count)} @@ -629,12 +640,12 @@ export function StatsPanel() { {/* Headline figures: stat tiles, not a grouped bar chart. */}
- - - - + + + + + sub={`${nf(stats.confirmed_any)} / ${nf(stats.total)}`} accent="var(--success)" />
{/* Period / contest block — ONLY when a window is selected. "12 QSO/h" across @@ -723,43 +734,43 @@ export function StatsPanel() {
) : (
- + - - + + - + {/* EVERY operator, never a top-N: on a multi-op contest the point is who worked what, and a cap would quietly delete the 9th operator. Scrolls instead of truncating. */} - +
- +
- + - - + +
- +
- +
- +