Files
OpsLog/frontend/src/style.css
T
rouggy f50806fbd9 feat: panadapter spots that read, a CI-V link that survives, no auto-call
Panadapter spots are now worth reading. The comment carries the spotter, the
entity and the status in DXHunter's own shape — "CQ up 2 [F4BPO] [Franz Josef
Land] [New Slot]" — which needed two things nothing documents: SmartSDR splits
its command line on SPACES, so the words ran together until every space became
non-breaking; and it truncates past ~60 characters, so the cluster's own words
are trimmed first and the three brackets always survive. RBN column padding is
collapsed on the way in, or a preserved run of spaces opened a gap wide enough
to push the rest off screen.

"Already worked" means the CALLSIGN is in the log, not the entity: saying it of
a station never contacted was simply wrong. Each status can also be kept off the
panadapter entirely, and the WSJT-X decode spots obey the same switches — the
palette governs the panadapter, not one of the two things that feed it.

And the radio is no longer hammered: a spot whose frequency, colour and comment
are unchanged is not removed and redrawn. A busy skimmer feed re-spots the same
station every few seconds; one two-minute session sent 2128 adds, 88 of them for
a single callsign, and the display did not move a pixel for any of them.

CI-V, from an IC-7850 that kept killing JTDX: a reply the rig sent to another
controller on the same bus is no longer taken for ours, and a set_ptt, set_freq
or set_mode whose acknowledgement goes missing is verified by reading the rig
back instead of being reported as a failure. WSJT-X and JTDX answer a failed
command with a Rig Control Error and drop the link mid-over — 98 keyings, 6 lost
acknowledgements, 2 dropped connections in one session. The check waits 700 ms,
not the poll's 150: the rig has just failed to answer twice because it was
retuning, and a short probe would fail for the same reason.

Auto-call is withdrawn — it duplicated DXHunter, which already answers decodes,
and two programs deciding that from one shack key over each other. The library
is kept whole and dormant; a guard in App.tsx makes sure a stored preference
cannot key a transmitter whose switch no longer exists.

Also:
  - the log rotates while running, not only at startup: the CI-V trace left on
    wrote 416 MB and nothing would have stopped it before the disk did. Closing
    it now releases the crash file too — the runtime keeps its own duplicate.
  - the interface zoom announces itself, with a badge, a click back to 100% and
    a View menu; Ctrl+wheel and Ctrl+0 always worked and nothing said so.
  - no more elastic bounce, and no swipe-to-navigate out of the app.
  - Edit QSO: your own TX power and the contacted station's extended locator
    were saved and written back with no box to set them.
  - FT decodes: continents are a multiple choice; a compound MSHV message that
    answers two stations in one line is recognised as addressed to you.
2026-08-23 01:16:26 +02:00

1082 lines
36 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* External @imports must come first per CSS spec. */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap');
@import "tailwindcss";
@import "tw-animate-css";
/* ============================================================================
THEME SYSTEM
----------------------------------------------------------------------------
Every color is a semantic CSS variable declared per theme below. The
`@theme inline` block points Tailwind's color utilities (bg-*, text-*,
border-*, ring-*, …) directly at those variables, so switching the
`data-theme` attribute on <html> reskins the whole UI at runtime with no
rebuild. Four themes ship: two light-neutral surfaces and status families
for OK / warning / caution / danger / info that adapt to each surface.
Semantic status vocabulary (use these, NOT raw palette colors):
success — worked / confirmed / data / OK (was emerald)
warning — new-band / attention (was amber)
caution — new-mode / new-slot / lighter alert (was yellow)
danger — new / split / negative (was rose)
info — informational (was sky/blue)
destructive — hard errors / live REC / delete (was red)
Each family has: base (dots/bars/solid), -foreground (text on base),
-muted (soft pill fill), -muted-foreground (text on pill), -border.
========================================================================= */
/* ---- Theme 1: Warm light (default) — warm taupe & burnt orange ---------- */
:root,
[data-theme="light-warm"] {
--background: #e8dfc9; /* warm taupe — deeper than cream */
--foreground: #1c1917; /* stone-900 */
--card: #faf6ea; /* lifted off-white cream */
--card-foreground: #1c1917;
--popover: #faf6ea;
--popover-foreground: #1c1917;
--primary: #b8410c; /* burnt orange, slightly deeper */
--primary-foreground: #fff7ed;
--secondary: #ddd2b8;
--secondary-foreground: #1c1917;
--muted: #ddd2b8; /* toolbars / table headers */
--muted-foreground: #44403c; /* stone-700 — readable on muted */
--accent: #f8d6a9; /* warm amber-cream */
--accent-foreground: #7c2d12; /* orange-900 */
--destructive: #a51c1c;
--destructive-foreground: #ffffff;
--destructive-muted: #fef2f2;
--destructive-muted-foreground: #b91c1c;
--border: #c8b994; /* deeper warm beige border */
--input: #c8b994;
--ring: #d97706; /* amber-600 focus ring */
--success: #16a34a;
--success-foreground: #ffffff;
--success-muted: #dcfce7;
--success-muted-foreground: #166534;
--success-border: #86efac;
--warning: #d97706;
--warning-foreground: #ffffff;
--warning-muted: #fef3c7;
--warning-muted-foreground: #92400e;
--warning-border: #fcd34d;
--caution: #ca8a04;
--caution-foreground: #1c1917;
--caution-muted: #fef9c3;
--caution-muted-foreground: #854d0e;
--caution-border: #fde047;
--danger: #e11d48;
--danger-foreground: #ffffff;
--danger-muted: #ffe4e6;
--danger-muted-foreground: #9f1239;
--danger-border: #fda4af;
--info: #0284c7;
--info-foreground: #ffffff;
--info-muted: #e0f2fe;
--info-muted-foreground: #075985;
--info-border: #7dd3fc;
/* Band/mode matrix (BandSlotGrid) — original emerald/indigo/stone ramp. */
--mx-call-conf: #15803d; /* call confirmed */
--mx-call-work: #6ee7b7; /* call worked */
--mx-dx-conf: #3730a3; /* entity confirmed*/
--mx-dx-work: #a5b4fc; /* entity worked */
--mx-none: #e7e5e4; /* never worked */
/* Ring on the cell the operator is entering. Declared ONCE, on :root, and
deliberately not repeated per theme: it follows --warning, which every theme
already tunes to its own background. The token exists so the matrix ring can
be recoloured on its own without dragging every other warning in the app
with it (Appearance → matrix colours). */
--mx-cur: var(--warning);
--scrollbar-thumb: #b8a880;
--scrollbar-thumb-hover: #968455;
--card-shadow: 0 1px 2px rgba(28, 25, 23, 0.05), 0 0 0 1px rgba(28, 25, 23, 0.02);
color-scheme: light;
}
/* ---- Theme 1b: Cool light (slate/blue) — crisp daylight, neutral cool --- */
[data-theme="light-cool"] {
--background: #f4f6f8; /* cool blue-white */
--foreground: #0f172a; /* slate-900 */
--card: #ffffff;
--card-foreground: #0f172a;
--popover: #ffffff;
--popover-foreground: #0f172a;
--primary: #2563eb; /* blue-600 */
--primary-foreground: #ffffff;
--secondary: #e2e8f0; /* slate-200 */
--secondary-foreground: #0f172a;
--muted: #e6ebf1; /* toolbars / table headers */
--muted-foreground: #475569; /* slate-600 — readable on muted */
--accent: #dbeafe; /* blue-100 */
--accent-foreground: #1e3a8a; /* blue-900 */
--destructive: #dc2626;
--destructive-foreground: #ffffff;
--destructive-muted: #fef2f2;
--destructive-muted-foreground: #b91c1c;
--border: #d3dae2; /* cool slate border */
--input: #d3dae2;
--ring: #3b82f6; /* blue-500 focus ring */
--success: #16a34a;
--success-foreground: #ffffff;
--success-muted: #dcfce7;
--success-muted-foreground: #166534;
--success-border: #86efac;
--warning: #d97706;
--warning-foreground: #ffffff;
--warning-muted: #fef3c7;
--warning-muted-foreground: #92400e;
--warning-border: #fcd34d;
--caution: #ca8a04;
--caution-foreground: #1c1917;
--caution-muted: #fef9c3;
--caution-muted-foreground: #854d0e;
--caution-border: #fde047;
--danger: #e11d48;
--danger-foreground: #ffffff;
--danger-muted: #ffe4e6;
--danger-muted-foreground: #9f1239;
--danger-border: #fda4af;
--info: #0284c7;
--info-foreground: #ffffff;
--info-muted: #e0f2fe;
--info-muted-foreground: #075985;
--info-border: #7dd3fc;
/* Band/mode matrix (BandSlotGrid) — emerald/indigo ramp, cool neutral base. */
--mx-call-conf: #15803d;
--mx-call-work: #6ee7b7;
--mx-dx-conf: #3730a3;
--mx-dx-work: #a5b4fc;
--mx-none: #e2e8f0;
--scrollbar-thumb: #b6c2d1;
--scrollbar-thumb-hover: #8fa1b5;
--card-shadow: 0 1px 2px rgba(15, 23, 42, 0.06), 0 0 0 1px rgba(15, 23, 42, 0.02);
color-scheme: light;
}
/* ---- Theme 1c: Sage light (Nordic) — soft green-grey, low-fatigue ------- */
[data-theme="light-sage"] {
--background: #eef1ec; /* pale sage */
--foreground: #1a2420; /* deep pine-ink */
--card: #f8faf6; /* lifted off-white with a green cast */
--card-foreground: #1a2420;
--popover: #f8faf6;
--popover-foreground: #1a2420;
--primary: #2f855a; /* deep sage green */
--primary-foreground: #f0fdf4;
--secondary: #dde5db;
--secondary-foreground: #1a2420;
--muted: #dde5db; /* toolbars / table headers */
--muted-foreground: #445048; /* muted pine — readable on muted */
--accent: #d7e8d5; /* soft green */
--accent-foreground: #1e4634; /* forest-900 */
--destructive: #b91c1c;
--destructive-foreground: #ffffff;
--destructive-muted: #fef2f2;
--destructive-muted-foreground: #b91c1c;
--border: #cdd8ce; /* soft sage border */
--input: #cdd8ce;
--ring: #38a169; /* green focus ring */
--success: #16a34a;
--success-foreground: #ffffff;
--success-muted: #dcfce7;
--success-muted-foreground: #166534;
--success-border: #86efac;
--warning: #d97706;
--warning-foreground: #ffffff;
--warning-muted: #fef3c7;
--warning-muted-foreground: #92400e;
--warning-border: #fcd34d;
--caution: #ca8a04;
--caution-foreground: #1c1917;
--caution-muted: #fef9c3;
--caution-muted-foreground: #854d0e;
--caution-border: #fde047;
--danger: #e11d48;
--danger-foreground: #ffffff;
--danger-muted: #ffe4e6;
--danger-muted-foreground: #9f1239;
--danger-border: #fda4af;
--info: #0284c7;
--info-foreground: #ffffff;
--info-muted: #e0f2fe;
--info-muted-foreground: #075985;
--info-border: #7dd3fc;
/* Band/mode matrix (BandSlotGrid) — emerald/indigo ramp, sage neutral base. */
--mx-call-conf: #15803d;
--mx-call-work: #6ee7b7;
--mx-dx-conf: #3730a3;
--mx-dx-work: #a5b4fc;
--mx-none: #dde5db;
--scrollbar-thumb: #b3c2b3;
--scrollbar-thumb-hover: #91a591;
--card-shadow: 0 1px 2px rgba(26, 36, 32, 0.06), 0 0 0 1px rgba(26, 36, 32, 0.02);
color-scheme: light;
}
/* ---- Theme 1d: Dim slate — mid-tone twilight, between light and dark ---- */
[data-theme="dim-slate"] {
--background: #343b47; /* medium slate — light text, not deep dark */
--foreground: #eceef2;
--card: #3d4552; /* lifted panel */
--card-foreground: #eceef2;
--popover: #3d4552;
--popover-foreground: #eceef2;
--primary: #fb923c; /* orange-400 — brand warmth, pops on slate */
--primary-foreground: #241206;
--secondary: #454e5c;
--secondary-foreground: #eceef2;
--muted: #414a58; /* toolbars / table headers */
--muted-foreground: #b4bcc9; /* readable on muted */
--accent: #495468; /* hover / selection tint */
--accent-foreground: #dce3ef;
--destructive: #ef4444;
--destructive-foreground: #1a0808;
--destructive-muted: #3f1f1f;
--destructive-muted-foreground: #fca5a5;
--border: #4e5867;
--input: #4e5867;
--ring: #fdba74; /* orange-300 focus ring */
--success: #34d399;
--success-foreground: #06231a;
--success-muted: #1c3d34;
--success-muted-foreground: #6ee7b7;
--success-border: #2a5f4c;
--warning: #fbbf24;
--warning-foreground: #241a06;
--warning-muted: #41371a;
--warning-muted-foreground: #fcd34d;
--warning-border: #5e4d22;
--caution: #facc15;
--caution-foreground: #242006;
--caution-muted: #403b1a;
--caution-muted-foreground: #fde047;
--caution-border: #5b5222;
--danger: #fb7185;
--danger-foreground: #260a11;
--danger-muted: #421f28;
--danger-muted-foreground: #fda4af;
--danger-border: #63303c;
--info: #38bdf8;
--info-foreground: #061f2b;
--info-muted: #16384a;
--info-muted-foreground: #7dd3fc;
--info-border: #1d5670;
/* Matrix: bright confirmed, medium worked, slate not-worked (sits on dim). */
--mx-call-conf: #22c55e;
--mx-call-work: #2f7d55;
--mx-dx-conf: #6366f1;
--mx-dx-work: #3f3f8a;
--mx-none: #4a5262;
--scrollbar-thumb: #565f70;
--scrollbar-thumb-hover: #6b7588;
--card-shadow: 0 1px 2px rgba(0, 0, 0, 0.35), 0 0 0 1px rgba(236, 238, 242, 0.05);
color-scheme: dark;
}
/* ---- Theme 2: Warm dark (espresso) — same soul, night mode ------------- */
[data-theme="dark-warm"] {
--background: #221d18;
--foreground: #ede6d8;
--card: #2e2820;
--card-foreground: #ede6d8;
--popover: #2e2820;
--popover-foreground: #ede6d8;
--primary: #e07a2e; /* brighter burnt orange for dark */
--primary-foreground: #1a1512;
--secondary: #3a3229;
--secondary-foreground: #ede6d8;
--muted: #352e26;
--muted-foreground: #bcae9a;
--accent: #4a3a28;
--accent-foreground: #f8d6a9;
--destructive: #e5484d;
--destructive-foreground: #1a1512;
--destructive-muted: #3a1414;
--destructive-muted-foreground: #fca5a5;
--border: #473e33;
--input: #473e33;
--ring: #e8853a;
--success: #34d399;
--success-foreground: #0e2a20;
--success-muted: #123024;
--success-muted-foreground: #6ee7b7;
--success-border: #1e5540;
--warning: #fbbf24;
--warning-foreground: #2a1f08;
--warning-muted: #3a2e10;
--warning-muted-foreground: #fcd34d;
--warning-border: #5c4a18;
--caution: #facc15;
--caution-foreground: #2a2607;
--caution-muted: #38330f;
--caution-muted-foreground: #fde047;
--caution-border: #574f18;
--danger: #fb7185;
--danger-foreground: #2a0d13;
--danger-muted: #3a1720;
--danger-muted-foreground: #fda4af;
--danger-border: #5c2530;
--info: #38bdf8;
--info-foreground: #08222e;
--info-muted: #0c2a3a;
--info-muted-foreground: #7dd3fc;
--info-border: #164a5c;
/* Matrix: bright confirmed, medium-solid worked (clearly visible on dark),
warm-gray not-worked lifted off the card. */
--mx-call-conf: #22c55e;
--mx-call-work: #2f7d55;
--mx-dx-conf: #6366f1;
--mx-dx-work: #3f3f8a;
--mx-none: #453d33;
--scrollbar-thumb: #5c5044;
--scrollbar-thumb-hover: #7a6a58;
--card-shadow: 0 1px 2px rgba(0, 0, 0, 0.35), 0 0 0 1px rgba(255, 240, 220, 0.04);
color-scheme: dark;
}
/* ---- Theme 3: Graphite dark — cool slate, technical look --------------- */
[data-theme="dark-graphite"] {
--background: #16181d;
--foreground: #e6e8ec;
--card: #1f232b;
--card-foreground: #e6e8ec;
--popover: #1f232b;
--popover-foreground: #e6e8ec;
--primary: #f97316; /* orange-500 pops on slate */
--primary-foreground: #0a0c10;
--secondary: #272c35;
--secondary-foreground: #e6e8ec;
--muted: #232830;
--muted-foreground: #9aa2b1;
--accent: #2a3240;
--accent-foreground: #cbd5e5;
--destructive: #ef4444;
--destructive-foreground: #0a0c10;
--destructive-muted: #331414;
--destructive-muted-foreground: #fca5a5;
--border: #2e343f;
--input: #2e343f;
--ring: #fb8c3c;
--success: #34d399;
--success-foreground: #06231a;
--success-muted: #0f2e26;
--success-muted-foreground: #6ee7b7;
--success-border: #17513f;
--warning: #fbbf24;
--warning-foreground: #241a06;
--warning-muted: #33290f;
--warning-muted-foreground: #fcd34d;
--warning-border: #4f3f16;
--caution: #facc15;
--caution-foreground: #242006;
--caution-muted: #322e0e;
--caution-muted-foreground: #fde047;
--caution-border: #4c4416;
--danger: #fb7185;
--danger-foreground: #260a11;
--danger-muted: #34141d;
--danger-muted-foreground: #fda4af;
--danger-border: #532430;
--info: #38bdf8;
--info-foreground: #061f2b;
--info-muted: #0b2938;
--info-muted-foreground: #7dd3fc;
--info-border: #124659;
/* Matrix: bright confirmed, medium-solid worked, cool-gray not-worked. */
--mx-call-conf: #22c55e;
--mx-call-work: #2f7d55;
--mx-dx-conf: #6366f1;
--mx-dx-work: #3f3f8a;
--mx-none: #333a45;
--scrollbar-thumb: #3a4150;
--scrollbar-thumb-hover: #4e5768;
--card-shadow: 0 1px 2px rgba(0, 0, 0, 0.45), 0 0 0 1px rgba(230, 232, 236, 0.04);
color-scheme: dark;
}
/* ---- Theme 4: High contrast — pure black / white, max legibility ------- */
[data-theme="high-contrast"] {
--background: #000000;
--foreground: #ffffff;
--card: #0d0d0d;
--card-foreground: #ffffff;
--popover: #0d0d0d;
--popover-foreground: #ffffff;
--primary: #ff7a1a;
--primary-foreground: #000000;
--secondary: #1a1a1a;
--secondary-foreground: #ffffff;
--muted: #141414;
--muted-foreground: #d4d4d4;
--accent: #262626;
--accent-foreground: #ffffff;
--destructive: #ff4d4d;
--destructive-foreground: #000000;
--destructive-muted: #2a0808;
--destructive-muted-foreground: #ff9999;
--border: #3f3f3f;
--input: #3f3f3f;
--ring: #ffab5e;
--success: #22e37a;
--success-foreground: #002313;
--success-muted: #06231a;
--success-muted-foreground: #6affb0;
--success-border: #0f6b4a;
--warning: #ffcc33;
--warning-foreground: #201900;
--warning-muted: #2a2205;
--warning-muted-foreground: #ffe08a;
--warning-border: #6b5510;
--caution: #ffe14d;
--caution-foreground: #201d00;
--caution-muted: #2a2705;
--caution-muted-foreground: #fff08a;
--caution-border: #6b6210;
--danger: #ff6b7d;
--danger-foreground: #23000a;
--danger-muted: #2a0a12;
--danger-muted-foreground: #ffb3bd;
--danger-border: #6b1f2c;
--info: #4dc4ff;
--info-foreground: #001a26;
--info-muted: #052330;
--info-muted-foreground: #a3e0ff;
--info-border: #0f5a6b;
/* Matrix: max-contrast ramp on pure black. */
--mx-call-conf: #2ee87f;
--mx-call-work: #17a85a;
--mx-dx-conf: #7d97ff;
--mx-dx-work: #3646b0;
--mx-none: #3a3a3a;
--scrollbar-thumb: #4a4a4a;
--scrollbar-thumb-hover: #666666;
--card-shadow: 0 0 0 1px rgba(255, 255, 255, 0.12);
color-scheme: dark;
}
/* ---- Theme 5: Indigo — deep blue-violet, electric accent ----------------
The first seven themes are all neutral (beige, slate, graphite, black) with an
orange accent, so "another dark theme" only ever meant another grey. These
four carry a real hue in the SURFACES, not just in the accent, and each takes
a different primary — violet, cyan, pink, indigo — so they are told apart at a
glance in the picker.
Every one keeps the same contract: the semantic colours (success / warning /
caution / danger / info) must stay recognisable as themselves. A logger is
read for hours and "red = a problem" cannot become a decorative choice, so the
hue budget is spent on the surfaces and the primary, never on the meanings. */
[data-theme="dark-indigo"] {
--background: #0e0f1f;
--foreground: #e4e5f2;
--card: #181a30;
--card-foreground: #e4e5f2;
--popover: #181a30;
--popover-foreground: #e4e5f2;
--primary: #7c6cff; /* electric violet */
--primary-foreground: #0b0a1a;
--secondary: #222542;
--secondary-foreground: #e4e5f2;
--muted: #1e2038;
--muted-foreground: #9d9fbe;
--accent: #2a2d51;
--accent-foreground: #c7c9ee;
--destructive: #f2555a;
--destructive-foreground: #12030a;
--destructive-muted: #34131c;
--destructive-muted-foreground: #fca5a5;
--border: #2a2d4c;
--input: #2a2d4c;
--ring: #9b8dff;
--success: #34d399;
--success-foreground: #052018;
--success-muted: #0e2f27;
--success-muted-foreground: #6ee7b7;
--success-border: #165241;
--warning: #fbbf24;
--warning-foreground: #221803;
--warning-muted: #33280f;
--warning-muted-foreground: #fcd34d;
--warning-border: #4f3e15;
--caution: #facc15;
--caution-foreground: #221e04;
--caution-muted: #322d0e;
--caution-muted-foreground: #fde047;
--caution-border: #4b4315;
--danger: #fb7185;
--danger-foreground: #250912;
--danger-muted: #37151f;
--danger-muted-foreground: #fda4af;
--danger-border: #562533;
--info: #38bdf8;
--info-foreground: #051d29;
--info-muted: #0b2a3a;
--info-muted-foreground: #7dd3fc;
--info-border: #12475c;
/* The entity ramp moves to CYAN here: violet is the primary, and a violet
"entity confirmed" cell would read as a button. */
--mx-call-conf: #22c55e;
--mx-call-work: #2c7a52;
--mx-dx-conf: #22d3ee;
--mx-dx-work: #1b6b7c;
--mx-none: #2c2f4d;
--scrollbar-thumb: #383c63;
--scrollbar-thumb-hover: #4c5182;
--card-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(228, 229, 242, 0.05);
color-scheme: dark;
}
/* ---- Theme 6: Ocean — deep teal, cyan accent --------------------------- */
[data-theme="dark-teal"] {
--background: #061c21;
--foreground: #dcecee;
--card: #0d2c33;
--card-foreground: #dcecee;
--popover: #0d2c33;
--popover-foreground: #dcecee;
--primary: #22d3ee; /* cyan */
--primary-foreground: #041d24;
--secondary: #143b44;
--secondary-foreground: #dcecee;
--muted: #10333b;
--muted-foreground: #92b4ba;
--accent: #15424c;
--accent-foreground: #a5d8e0;
--destructive: #f2555a;
--destructive-foreground: #12030a;
--destructive-muted: #35161a;
--destructive-muted-foreground: #fca5a5;
--border: #1a4652;
--input: #1a4652;
--ring: #5fe3f5;
--success: #4ade80;
--success-foreground: #04220f;
--success-muted: #0e3020;
--success-muted-foreground: #86efac;
--success-border: #17593a;
--warning: #fbbf24;
--warning-foreground: #221803;
--warning-muted: #33280f;
--warning-muted-foreground: #fcd34d;
--warning-border: #4f3e15;
--caution: #facc15;
--caution-foreground: #221e04;
--caution-muted: #312d0e;
--caution-muted-foreground: #fde047;
--caution-border: #4a4315;
--danger: #fb7185;
--danger-foreground: #250912;
--danger-muted: #38161f;
--danger-muted-foreground: #fda4af;
--danger-border: #572634;
/* Info moves to a BLUE: a cyan "info" would be the primary colour again. */
--info: #7aa2f7;
--info-foreground: #04122e;
--info-muted: #16233f;
--info-muted-foreground: #a9c2fb;
--info-border: #2b3d66;
--mx-call-conf: #4ade80;
--mx-call-work: #2b7a4d;
--mx-dx-conf: #a78bfa;
--mx-dx-work: #5b4a9c;
--mx-none: #1c4550;
--scrollbar-thumb: #235a68;
--scrollbar-thumb-hover: #31788a;
--card-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(220, 236, 238, 0.05);
color-scheme: dark;
}
/* ---- Theme 7: Plum — aubergine, magenta accent ------------------------- */
[data-theme="dark-plum"] {
--background: #180f1e;
--foreground: #eee0f0;
--card: #251830;
--card-foreground: #eee0f0;
--popover: #251830;
--popover-foreground: #eee0f0;
--primary: #f472b6; /* magenta-pink */
--primary-foreground: #250518;
--secondary: #33203f;
--secondary-foreground: #eee0f0;
--muted: #2b1b36;
--muted-foreground: #b39dbd;
--accent: #3b2648;
--accent-foreground: #dcc2e6;
--destructive: #f2555a;
--destructive-foreground: #12030a;
--destructive-muted: #3a1620;
--destructive-muted-foreground: #fca5a5;
--border: #3a2547;
--input: #3a2547;
--ring: #f9a8d4;
--success: #34d399;
--success-foreground: #052018;
--success-muted: #10302a;
--success-muted-foreground: #6ee7b7;
--success-border: #1a5745;
--warning: #fbbf24;
--warning-foreground: #221803;
--warning-muted: #352a12;
--warning-muted-foreground: #fcd34d;
--warning-border: #524019;
--caution: #facc15;
--caution-foreground: #221e04;
--caution-muted: #342e12;
--caution-muted-foreground: #fde047;
--caution-border: #4e4519;
/* Danger goes RED here, not rose: a rose "danger" beside a pink primary is
indistinguishable, and this is the one colour that must never be missed. */
--danger: #f43f3f;
--danger-foreground: #2a0606;
--danger-muted: #3d1418;
--danger-muted-foreground: #fca5a5;
--danger-border: #5c2128;
--info: #38bdf8;
--info-foreground: #051d29;
--info-muted: #0f2b3a;
--info-muted-foreground: #7dd3fc;
--info-border: #16485d;
--mx-call-conf: #22c55e;
--mx-call-work: #2f7d55;
--mx-dx-conf: #60a5fa;
--mx-dx-work: #35538c;
--mx-none: #3a2647;
--scrollbar-thumb: #4a3159;
--scrollbar-thumb-hover: #644176;
--card-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(238, 224, 240, 0.05);
color-scheme: dark;
}
/* ---- Theme 8: Nordic light — crisp cool white, indigo accent ----------- */
[data-theme="light-nordic"] {
--background: #eef1f7;
--foreground: #1b2130;
--card: #ffffff;
--card-foreground: #1b2130;
--popover: #ffffff;
--popover-foreground: #1b2130;
--primary: #4f46e5; /* indigo */
--primary-foreground: #ffffff;
--secondary: #dfe4ee;
--secondary-foreground: #1b2130;
--muted: #e2e7f0;
--muted-foreground: #4a5468;
--accent: #dcdcfb;
--accent-foreground: #312a91;
--destructive: #b91c1c;
--destructive-foreground: #ffffff;
--destructive-muted: #fef2f2;
--destructive-muted-foreground: #b91c1c;
--border: #c9d1e0;
--input: #c9d1e0;
--ring: #6366f1;
--success: #15803d;
--success-foreground: #ffffff;
--success-muted: #dcfce7;
--success-muted-foreground: #14532d;
--success-border: #86efac;
--warning: #b45309;
--warning-foreground: #ffffff;
--warning-muted: #fef3c7;
--warning-muted-foreground: #92400e;
--warning-border: #fcd34d;
--caution: #a16207;
--caution-foreground: #ffffff;
--caution-muted: #fef9c3;
--caution-muted-foreground: #854d0e;
--caution-border: #fde047;
--danger: #dc2626;
--danger-foreground: #ffffff;
--danger-muted: #fee2e2;
--danger-muted-foreground: #991b1b;
--danger-border: #fca5a5;
--info: #0369a1;
--info-foreground: #ffffff;
--info-muted: #e0f2fe;
--info-muted-foreground: #075985;
--info-border: #7dd3fc;
/* Entity ramp shifts to TEAL: indigo is the primary here. */
--mx-call-conf: #15803d;
--mx-call-work: #86efac;
--mx-dx-conf: #0f766e;
--mx-dx-work: #99f6e4;
--mx-none: #dde3ec;
--scrollbar-thumb: #b6c0d2;
--scrollbar-thumb-hover: #93a0b8;
--card-shadow: 0 1px 2px rgba(27, 33, 48, 0.06), 0 0 0 1px rgba(27, 33, 48, 0.03);
color-scheme: light;
}
/* ---- Theme 1e: Sahara — Warm light, taken a step deeper --------------------
Warm light's cream is kept for panels and the entry strip (#faf6eb, the one
surface an operator stares at while typing a call), but the page behind them
and every toolbar, tab bar and table header go one shade further into the
sand. That was the whole request: same family, just a little darker, so the
cream panels lift clearly instead of blending into the page.
Ink is a deep warm brown rather than black: pure black on sand glares.
The accent moves from Warm light's burnt orange to ochre to keep the two
themes apart at a glance in the theme picker. ---------------------------- */
[data-theme="sahara"] {
--background: #e6ded0; /* parchment — the page */
--foreground: #3a3227; /* warm brown-ink */
--card: #f3eee2; /* panels, entry strip, log rows */
--card-foreground: #3a3227;
--popover: #f3eee2;
--popover-foreground: #3a3227;
--primary: #c4501e; /* terracotta — buttons and the frequency */
--primary-foreground: #fdf8f0;
--secondary: #ded5c4;
--secondary-foreground: #3a3227;
--muted: #ded5c4; /* toolbars / tab bar / table headers */
--muted-foreground: #6a5f4e; /* readable on both parchments */
--accent: #ecd9c0; /* warm highlight */
--accent-foreground: #7a3410;
--destructive: #b3401b;
--destructive-foreground: #ffffff;
--destructive-muted: #f7e4da;
--destructive-muted-foreground: #8f3315;
--border: #cdc2ae; /* one step down, so edges read without lines */
--input: #cdc2ae;
--ring: #c4501e; /* the accent, so focus matches the buttons */
--success: #15803d;
--success-foreground: #ffffff;
--success-muted: #dcf3e1;
--success-muted-foreground: #14532d;
--success-border: #86d99f;
--warning: #b45309;
--warning-foreground: #ffffff;
--warning-muted: #fbeacd;
--warning-muted-foreground: #7c3d05;
--warning-border: #e8b96a;
--caution: #a16207;
--caution-foreground: #1c1917;
--caution-muted: #f7edcb;
--caution-muted-foreground: #713f12;
--caution-border: #e0c069;
--danger: #be123c;
--danger-foreground: #ffffff;
--danger-muted: #fbe3e8;
--danger-muted-foreground: #9f1239;
--danger-border: #ee9aac;
--info: #0369a1;
--info-foreground: #ffffff;
--info-muted: #e0eef6;
--info-muted-foreground: #075985;
--info-border: #86c5e4;
/* Band/mode matrix — the app's blues kept, on a sand neutral so an empty slot
recedes into the grid instead of punching a hole in it. */
--mx-call-conf: #15803d;
--mx-call-work: #7fd6a4;
--mx-dx-conf: #3730a3;
--mx-dx-work: #a5b4fc;
--mx-none: #ded5c4;
--scrollbar-thumb: #c2b6a0;
--scrollbar-thumb-hover: #a5977e;
--card-shadow: 0 1px 2px rgba(58, 50, 39, 0.06), 0 0 0 1px rgba(58, 50, 39, 0.03);
color-scheme: light;
}
/* ── Data-viz palette (Statistics dashboard) ────────────────────────────────
A VALIDATED categorical palette, not hand-picked: the slot ORDER is what makes
it colour-blind-safe (worst adjacent ΔE 24.2 light / 10.3 dark), so never
reorder, recycle, or generate a 9th hue — fold the tail into "Other" instead.
The dark column is the same eight hues re-stepped for a dark surface, NOT an
automatic inversion.
--chart-1 is the DEFAULT single-series hue: a chart with one series uses it for
every bar. Colour is for identity, never to re-encode a length the bar already
shows.
--chart-seq-* is a ONE-HUE ramp for magnitude (the hour×band heatmap). On light
themes it runs light→dark; on dark themes dark→light, so "near zero" always
recedes toward the surface instead of glowing. */
:root,
[data-theme="light-warm"],
[data-theme="light-cool"],
[data-theme="light-sage"],
[data-theme="light-nordic"],
[data-theme="sahara"] {
--chart-1: #2a78d6; /* blue — default single-series hue */
--chart-2: #1baf7a; /* aqua */
--chart-3: #eda100; /* yellow */
--chart-4: #008300; /* green */
--chart-5: #4a3aa7; /* violet */
--chart-6: #e34948; /* red */
--chart-7: #e87ba4; /* magenta */
--chart-8: #eb6834; /* orange */
--chart-seq-1: #cde2fb;
--chart-seq-2: #9ec5f4;
--chart-seq-3: #6da7ec;
--chart-seq-4: #3987e5;
--chart-seq-5: #256abf;
--chart-seq-6: #104281;
}
[data-theme="dim-slate"],
[data-theme="dark-warm"],
[data-theme="dark-graphite"],
[data-theme="high-contrast"],
[data-theme="dark-indigo"],
[data-theme="dark-teal"],
[data-theme="dark-plum"] {
--chart-1: #3987e5;
--chart-2: #199e70;
--chart-3: #c98500;
--chart-4: #008300;
--chart-5: #9085e9;
--chart-6: #e66767;
--chart-7: #d55181;
--chart-8: #d95926;
/* Reversed: the lowest step must sink toward the dark surface. */
--chart-seq-1: #0d366b;
--chart-seq-2: #184f95;
--chart-seq-3: #256abf;
--chart-seq-4: #3987e5;
--chart-seq-5: #6da7ec;
--chart-seq-6: #9ec5f4;
}
/* Map Tailwind's color utilities onto the semantic vars above. `inline` makes
the generated utilities reference var(--…) directly, so overriding a var in
a [data-theme] block re-skins every utility at runtime. */
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-destructive-muted: var(--destructive-muted);
--color-destructive-muted-foreground: var(--destructive-muted-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-success: var(--success);
--color-success-foreground: var(--success-foreground);
--color-success-muted: var(--success-muted);
--color-success-muted-foreground: var(--success-muted-foreground);
--color-success-border: var(--success-border);
--color-warning: var(--warning);
--color-warning-foreground: var(--warning-foreground);
--color-warning-muted: var(--warning-muted);
--color-warning-muted-foreground: var(--warning-muted-foreground);
--color-warning-border: var(--warning-border);
--color-caution: var(--caution);
--color-caution-foreground: var(--caution-foreground);
--color-caution-muted: var(--caution-muted);
--color-caution-muted-foreground: var(--caution-muted-foreground);
--color-caution-border: var(--caution-border);
--color-danger: var(--danger);
--color-danger-foreground: var(--danger-foreground);
--color-danger-muted: var(--danger-muted);
--color-danger-muted-foreground: var(--danger-muted-foreground);
--color-danger-border: var(--danger-border);
--color-info: var(--info);
--color-info-foreground: var(--info-foreground);
--color-info-muted: var(--info-muted);
--color-info-muted-foreground: var(--info-muted-foreground);
--color-info-border: var(--info-border);
--color-mx-call-conf: var(--mx-call-conf);
--color-mx-call-work: var(--mx-call-work);
--color-mx-dx-conf: var(--mx-dx-conf);
--color-mx-dx-work: var(--mx-dx-work);
--color-mx-none: var(--mx-none);
--color-mx-cur: var(--mx-cur);
--radius: 0.5rem;
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
--font-mono: 'JetBrains Mono', 'Cascadia Mono', Consolas, 'Courier New', monospace;
}
@layer base {
* { @apply border-border; }
html, body, #root { height: 100%; }
/* No rubber-band, and no swipe-to-navigate.
Chromium lets a scroll that has reached the end pull the content a few
pixels and spring back, and a two-finger horizontal swipe means "go back a
page". Both are browser behaviours that have no meaning in a logbook: the
first makes a table wobble at the bottom of a list, and the second can
navigate the WebView away from the app entirely — a blank window and a
restart. Neither shows up with a mouse, which is why they went unnoticed;
they arrive with a precision touchpad or a touchscreen.
A no-op wherever the effects don't occur. */
html, body { overscroll-behavior: none; }
/* Root rem base. Tailwind sizing (text/heights/padding/gaps) is in rem, so
this is the single global density knob — shrinks the whole UI uniformly
while Leaflet maps (px-based) stay correct. Default browser base is 16px. */
html { font-size: 13px; }
body {
@apply bg-background text-foreground;
font-family: var(--font-sans);
font-size: 0.95rem;
line-height: 1.4;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
}
/* Subtle elevation on every Card-styled surface so cards visibly sit on
top of the background — paper-on-paper feel, tuned per theme. */
.bg-card {
box-shadow: var(--card-shadow);
}
/* Scrollbar tuned to each theme's surface */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb);
border-radius: 5px;
border: 2px solid var(--background);
}
::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover); }
/* ── Frameless window: drag region ──────────────────────────────────────────
The OS title bar is gone (main.go, Frameless), so the app header IS the title
bar and must be draggable. Everything interactive inside it opts OUT: a drag
region swallows clicks, and without this the menus and toolbar buttons would
move the window instead of doing their job. Opting out by ROLE rather than
listing each child keeps a future button working without anyone remembering
this rule. */
.app-dragregion {
--wails-draggable: drag;
}
.app-dragregion button,
.app-dragregion a,
.app-dragregion input,
.app-dragregion select,
.app-dragregion textarea,
.app-dragregion nav,
.app-dragregion [role='button'],
.app-dragregion [role='menu'],
.app-dragregion [data-no-drag] {
--wails-draggable: no-drag;
}
/* Native date inputs: the WebView draws its calendar glyph in near-black, which
disappears on the dark themes. Invert it there — the control itself is worth
keeping (OS calendar, locale date order, keyboard entry), only its icon needs
help. */
Every temporal input carries the same glyph, so the rule names them all:
datetime-local was left out at first and its icon stayed invisible. */
:is([data-theme='dim-slate'], [data-theme='dark-warm'], [data-theme='dark-graphite'], [data-theme='high-contrast'],
[data-theme='dark-indigo'], [data-theme='dark-teal'], [data-theme='dark-plum'])
:is(input[type='date'], input[type='datetime-local'], input[type='time'], input[type='month'], input[type='week'])::-webkit-calendar-picker-indicator {
filter: invert(1) brightness(1.8);
opacity: 0.9;
}
/* Every scrollable pane keeps its overscroll to itself.
"contain" (not "none") on the inside: the pane still scrolls normally, it
simply stops passing the leftover motion up to its parent — which is how
scrolling a grid to its end used to tug the panel behind it. */
.overflow-auto,
.overflow-y-auto,
.overflow-x-auto,
.overflow-scroll {
overscroll-behavior: contain;
}