An unselected satellite was a four-pixel dot with a one-pixel white edge, and the basemap decides whether that is visible at all: a pale marker vanishes into pale terrain, a grey one into a dark ocean, and the operator can switch between the two in one session. So: a dark halo under a white ring around a larger dot. Two rings, because no single colour reads on both. The ones above the horizon now carry their name. Not all of them — a dozen labels is a map nobody can read — but the two or three an operator is choosing between right now, which is what saves hovering each grey dot in turn to find them. Hovering said "name · elevation · altitude", none of which decides anything. What decides whether to reach for the radio is how long is left, how high it will get and where to point, so the tooltip now carries the pass: elevation and azimuth with its compass point, distance with an arrow for closing or receding, rise and set with a countdown and a direction, and the peak. A bird already in view shows its SET countdown instead of its rise — that is the number that matters at that moment. No pass in the window says so, because a blank reads as a fault. It costs no extra prediction: the pass list on screen is indexed by name, and the first entry for a name is its next pass. The tooltip also had to stop closing itself. The layer is rebuilt every five seconds as the birds move, and a rebuilt marker is a new marker, so the detail being read disappeared mid-sentence. The map now tracks the pointer and reopens the tooltip of the dot it is still on — that one only, so nothing hangs open once the mouse has moved away. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
1285 lines
44 KiB
CSS
1285 lines
44 KiB
CSS
/* 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);
|
||
/* The "worked with this callsign" dot. Declared ONCE, like --mx-cur: it is
|
||
drawn over every one of the five cell colours, so it follows the theme's own
|
||
foreground/background pair rather than a per-theme colour of its own. */
|
||
--mx-mark-work: var(--foreground);
|
||
--mx-mark-conf: var(--foreground);
|
||
|
||
--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: #a78bfa;
|
||
--mx-dx-work: #5b4a9e;
|
||
--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;
|
||
}
|
||
|
||
/* ---- Theme 13: DXHunter — its own slate and blue -----------------------
|
||
Ported so an operator running both side by side does not switch between two
|
||
colour worlds every time they look up. It is Tailwind's slate scale, which
|
||
is what DXHunter is built on: page at slate-900, panels at slate-800, rules
|
||
at slate-700, and blue-500 for the accent — counted across its sources, not
|
||
guessed from one panel: blue is 132 uses to violet's 25, and the violet is
|
||
the PSK Reporter panel alone. Active tab, focus border, primary button: all
|
||
blue-500. The status colours are DXHunter's too — emerald for good, amber
|
||
for attention, cyan for information, red for trouble — so a green number
|
||
means the same thing in both windows. */
|
||
[data-theme="dxhunter"] {
|
||
--background: #0f172a; /* slate-900 — the page */
|
||
--foreground: #e2e8f0; /* slate-200 */
|
||
--card: #1e293b; /* slate-800 — panels lift off the page */
|
||
--card-foreground: #e2e8f0;
|
||
--popover: #1e293b;
|
||
--popover-foreground: #e2e8f0;
|
||
--primary: #3b82f6; /* blue-500 — active tabs, focus, buttons */
|
||
--primary-foreground: #f8fafc;
|
||
--secondary: #273449;
|
||
--secondary-foreground: #e2e8f0;
|
||
--muted: #1c2941; /* toolbars / table headers */
|
||
--muted-foreground: #94a3b8; /* slate-400 — DXHunter's muted text */
|
||
--accent: #2c3b54; /* hover / selection tint */
|
||
--accent-foreground: #cbd5e1;
|
||
--destructive: #ef4444;
|
||
--destructive-foreground: #fef2f2;
|
||
--destructive-muted: #3a1518;
|
||
--destructive-muted-foreground: #fca5a5;
|
||
--border: #334155; /* slate-700 — every rule in DXHunter */
|
||
--input: #334155;
|
||
--ring: #60a5fa; /* blue-400 — its focus border */
|
||
|
||
--success: #34d399; /* emerald-400 — "online", confirmed */
|
||
--success-foreground: #04211a;
|
||
--success-muted: #0e3029;
|
||
--success-muted-foreground: #6ee7b7;
|
||
--success-border: #17564a;
|
||
|
||
--warning: #fbbf24; /* amber-400 */
|
||
--warning-foreground: #211803;
|
||
--warning-muted: #33280f;
|
||
--warning-muted-foreground: #fcd34d;
|
||
--warning-border: #4f3e15;
|
||
|
||
--caution: #facc15;
|
||
--caution-foreground: #211e04;
|
||
--caution-muted: #322d0e;
|
||
--caution-muted-foreground: #fde047;
|
||
--caution-border: #4b4315;
|
||
|
||
--danger: #f87171; /* red-400 — rose is barely used there */
|
||
--danger-foreground: #250912;
|
||
--danger-muted: #3a1621;
|
||
--danger-muted-foreground: #fda4af;
|
||
--danger-border: #5a2735;
|
||
|
||
--info: #22d3ee; /* cyan-400 — "heard near you" */
|
||
--info-foreground: #04212a;
|
||
--info-muted: #0c2f3b;
|
||
--info-muted-foreground: #67e8f9;
|
||
--info-border: #155e6e;
|
||
|
||
/* Blue is the primary, so the entity ramp goes VIOLET rather than reading
|
||
as a button — and violet is where DXHunter puts its own second accent. */
|
||
--mx-call-conf: #22c55e;
|
||
--mx-call-work: #2c7a52;
|
||
--mx-dx-conf: #a78bfa;
|
||
--mx-dx-work: #5b4a9e;
|
||
--mx-none: #334155;
|
||
|
||
--scrollbar-thumb: #334155; /* DXHunter's own scrollbar */
|
||
--scrollbar-thumb-hover: #475569;
|
||
--card-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(226, 232, 240, 0.05);
|
||
color-scheme: dark;
|
||
}
|
||
|
||
/* ---- Theme 14: DXHunter orange — the same slate, OpsLog's own accent --
|
||
DXHunter's slate, kept exactly — page, panels, rules, muted text, and its
|
||
status colours — with OpsLog's orange in place of its blue. For an operator
|
||
who wants the two windows to sit together without OpsLog losing the accent
|
||
it is recognised by. The entity ramp goes VIOLET here for the same reason as
|
||
in the blue version: it must not read as the accent. */
|
||
[data-theme="dxhunter-orange"] {
|
||
--background: #0f172a; /* slate-900 — the page */
|
||
--foreground: #e2e8f0; /* slate-200 */
|
||
--card: #1e293b; /* slate-800 — panels lift off the page */
|
||
--card-foreground: #e2e8f0;
|
||
--popover: #1e293b;
|
||
--popover-foreground: #e2e8f0;
|
||
--primary: #f97316; /* orange-500 — OpsLog's own accent */
|
||
--primary-foreground: #1c0a02;
|
||
--secondary: #273449;
|
||
--secondary-foreground: #e2e8f0;
|
||
--muted: #1c2941; /* toolbars / table headers */
|
||
--muted-foreground: #94a3b8; /* slate-400 — DXHunter's muted text */
|
||
--accent: #2c3b54; /* hover / selection tint */
|
||
--accent-foreground: #cbd5e1;
|
||
--destructive: #ef4444;
|
||
--destructive-foreground: #fef2f2;
|
||
--destructive-muted: #3a1518;
|
||
--destructive-muted-foreground: #fca5a5;
|
||
--border: #334155; /* slate-700 — every rule in DXHunter */
|
||
--input: #334155;
|
||
--ring: #fdba74; /* orange-300 focus ring */
|
||
|
||
--success: #34d399; /* emerald-400 — "online", confirmed */
|
||
--success-foreground: #04211a;
|
||
--success-muted: #0e3029;
|
||
--success-muted-foreground: #6ee7b7;
|
||
--success-border: #17564a;
|
||
|
||
--warning: #fbbf24; /* amber-400 */
|
||
--warning-foreground: #211803;
|
||
--warning-muted: #33280f;
|
||
--warning-muted-foreground: #fcd34d;
|
||
--warning-border: #4f3e15;
|
||
|
||
--caution: #facc15;
|
||
--caution-foreground: #211e04;
|
||
--caution-muted: #322d0e;
|
||
--caution-muted-foreground: #fde047;
|
||
--caution-border: #4b4315;
|
||
|
||
--danger: #f87171; /* red-400 — rose is barely used there */
|
||
--danger-foreground: #250912;
|
||
--danger-muted: #3a1621;
|
||
--danger-muted-foreground: #fda4af;
|
||
--danger-border: #5a2735;
|
||
|
||
--info: #22d3ee; /* cyan-400 — "heard near you" */
|
||
--info-foreground: #04212a;
|
||
--info-muted: #0c2f3b;
|
||
--info-muted-foreground: #67e8f9;
|
||
--info-border: #155e6e;
|
||
|
||
/* Blue is the primary, so the entity ramp goes VIOLET rather than reading
|
||
as a button — and violet is where DXHunter puts its own second accent. */
|
||
--mx-call-conf: #22c55e;
|
||
--mx-call-work: #2c7a52;
|
||
--mx-dx-conf: #a78bfa;
|
||
--mx-dx-work: #5b4a9e;
|
||
--mx-none: #334155;
|
||
|
||
--scrollbar-thumb: #334155; /* DXHunter's own scrollbar */
|
||
--scrollbar-thumb-hover: #475569;
|
||
--card-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(226, 232, 240, 0.05);
|
||
color-scheme: dark;
|
||
}
|
||
|
||
/* ── 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"],
|
||
[data-theme="dxhunter"],
|
||
[data-theme="dxhunter-orange"] {
|
||
--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);
|
||
--color-mx-mark-work: var(--mx-mark-work);
|
||
--color-mx-mark-conf: var(--mx-mark-conf);
|
||
|
||
--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;
|
||
}
|
||
|
||
/* The ground beside the planet, on every map: Leaflet paints its container a
|
||
hard-coded light grey (#ddd), which reads as a broken tile against any
|
||
theme. The surround follows the theme's own card surface instead. */
|
||
.leaflet-container {
|
||
background: var(--card) !important;
|
||
}
|
||
|
||
/* Satellite map tooltips. Leaflet's own are a white box with a grey border —
|
||
fine on a street map, a bright rectangle on a dark one, and always the wrong
|
||
colours for whichever theme the operator chose. These follow the theme, and
|
||
are wide enough for a pass: AOS, LOS, elevation and range each on their own
|
||
line. */
|
||
.leaflet-tooltip.sat-tip {
|
||
background: var(--popover);
|
||
color: var(--popover-foreground);
|
||
border: 1px solid var(--border);
|
||
border-radius: 0.5rem;
|
||
box-shadow: 0 4px 16px rgb(0 0 0 / 0.35);
|
||
padding: 0.4rem 0.55rem;
|
||
font-size: 11px;
|
||
line-height: 1.45;
|
||
white-space: nowrap;
|
||
}
|
||
.leaflet-tooltip.sat-tip::before { border-top-color: var(--border); }
|
||
.sat-tip-name { font-weight: 600; font-size: 12px; margin-bottom: 0.2rem; }
|
||
.sat-tip-row { display: flex; justify-content: space-between; gap: 1.25rem; }
|
||
.sat-tip-row > span:first-child { color: var(--muted-foreground); }
|
||
.sat-tip-note { color: var(--muted-foreground); font-style: italic; }
|
||
|
||
/* The name beside a satellite that is up right now. A plain div marker and
|
||
not a Leaflet tooltip, because Leaflet keeps one tooltip per layer and the
|
||
hover detail is the one worth keeping. */
|
||
.sat-name-label {
|
||
pointer-events: none;
|
||
white-space: nowrap;
|
||
font-size: 10px;
|
||
font-weight: 600;
|
||
/* Painted twice — a dark halo under a light glyph — because the label sits on
|
||
satellite imagery, on a street map and on a dark ocean in the same session,
|
||
and no single colour is readable on all three. */
|
||
color: #fff;
|
||
text-shadow: 0 0 3px #000, 0 0 3px #000, 0 1px 2px #000;
|
||
}
|