@tailwind base; @tailwind components; @tailwind utilities; @import 'leaflet/dist/leaflet.css'; /* ── Design tokens ──────────────────────────────────────────────────────────── * Single source of truth for surface, ink and encoding colour. `:root` is the * LIGHT theme; `.dark` on (set by ThemeProvider, darkMode: ["class"]) * swaps the same names to their dark values. Tailwind reads these through * theme.extend.colors, so components say `bg-surface` / `text-ink-muted` * instead of `bg-gray-900` / `text-gray-400`. * * Colour encoding is validated for colour-blindness — see UI_REDESIGN.md §2.3. * Severity is the ONLY hue channel. Incident type is shape. Node state is value. * Do not add a hue here for a category; add a glyph. */ :root { --page: #F4F6F9; --surface: #FFFFFF; --raised: #F7F9FB; --line: rgba(11,17,27,.11); --line-strong: rgba(11,17,27,.22); --ink: #101620; --ink-2: #46536A; --ink-muted: #6B788C; --accent: #2A78D6; --sev-moderate: #B07800; --sev-major: #C0281F; --map-bg: #E8ECF1; --map-block: #DFE4EB; --map-road: #FFFFFF; --map-water: #D3E2EE; } .dark { --page: #0B0E13; --surface: #141922; --raised: #1B2230; --line: rgba(255,255,255,.09); --line-strong: rgba(255,255,255,.17); --ink: #E8EBF0; --ink-2: #A5B0C2; --ink-muted: #77839A; --accent: #3987E5; --sev-moderate: #C98500; --sev-major: #D03B3B; --map-bg: #0E131B; --map-block: #161C26; --map-road: #232C3A; --map-water: #12202E; } /* ── Base ─────────────────────────────────────────────────────────────────── */ html, body { @apply bg-page text-ink; font-family: var(--font-sans), system-ui, sans-serif; } /* Mono is for machine identifiers only — timestamps, talkgroups, unit * callsigns, node ids, frequencies, incident ids, number columns. */ .font-mono, code, kbd, pre, samp { font-family: var(--font-mono), ui-monospace, monospace; } /* ── Light mode overrides ─────────────────────────────────────────────────── */ /* * The app's components use hardcoded dark-palette Tailwind classes (bg-gray-9xx, * text-gray-xxx). Rather than adding dark: prefixes everywhere, we remap those * classes here when the html element doesn't carry the .dark class. */ /* Structural backgrounds */ html:not(.dark), html:not(.dark) body { background-color: #f1f5f9; color: #0f172a; } html:not(.dark) .bg-gray-950 { background-color: #f1f5f9 !important; } html:not(.dark) .bg-gray-950\/95 { background-color: rgba(241,245,249,0.95) !important; } html:not(.dark) .bg-gray-900 { background-color: #ffffff !important; } html:not(.dark) .bg-gray-900\/60 { background-color: rgba(255,255,255,0.85) !important; } html:not(.dark) .bg-gray-900\/50 { background-color: rgba(255,255,255,0.75) !important; } html:not(.dark) .bg-gray-900\/30 { background-color: rgba(255,255,255,0.50) !important; } html:not(.dark) .bg-gray-800 { background-color: #f1f5f9 !important; } html:not(.dark) .bg-gray-800\/40 { background-color: rgba(241,245,249,0.60) !important; } html:not(.dark) .bg-gray-800\/30 { background-color: rgba(241,245,249,0.50) !important; } html:not(.dark) .bg-gray-700 { background-color: #e2e8f0 !important; } /* Borders */ html:not(.dark) .border-gray-800 { border-color: #e2e8f0 !important; } html:not(.dark) .border-gray-700 { border-color: #cbd5e1 !important; } html:not(.dark) .divide-gray-800 > * + * { border-color: #e2e8f0 !important; } /* Text */ html:not(.dark) .text-white { color: #0f172a !important; } html:not(.dark) .text-gray-100 { color: #1e293b !important; } html:not(.dark) .text-gray-300 { color: #334155 !important; } html:not(.dark) .text-gray-400 { color: #475569 !important; } html:not(.dark) .text-gray-500 { color: #64748b !important; } html:not(.dark) .text-gray-600 { color: #94a3b8 !important; } /* Hover states */ html:not(.dark) .hover\:bg-gray-900:hover { background-color: #f8fafc !important; } html:not(.dark) .hover\:bg-gray-900\/50:hover { background-color: rgba(255,255,255,0.75) !important; } html:not(.dark) .hover\:bg-gray-800:hover { background-color: #f1f5f9 !important; } html:not(.dark) .hover\:bg-gray-700:hover { background-color: #e2e8f0 !important; } html:not(.dark) .active\:bg-gray-800:active { background-color: #f1f5f9 !important; } /* Hover text */ html:not(.dark) .hover\:text-gray-300:hover { color: #334155 !important; } html:not(.dark) .hover\:text-gray-200:hover { color: #1e293b !important; } /* ── Accent badge palette (dark → light) ─────────────────────────────────── */ /* Fire / Error */ html:not(.dark) .bg-red-900 { background-color: #fef2f2 !important; } html:not(.dark) .bg-red-950 { background-color: #fff1f2 !important; } html:not(.dark) .text-red-300 { color: #b91c1c !important; } html:not(.dark) .text-red-400 { color: #dc2626 !important; } html:not(.dark) .border-red-800 { border-color: #fca5a5 !important; } /* Police */ html:not(.dark) .bg-blue-900 { background-color: #eff6ff !important; } html:not(.dark) .bg-blue-950 { background-color: #eff6ff !important; } html:not(.dark) .text-blue-300 { color: #1d4ed8 !important; } html:not(.dark) .border-blue-800 { border-color: #93c5fd !important; } /* EMS */ html:not(.dark) .bg-yellow-900 { background-color: #fefce8 !important; } html:not(.dark) .bg-yellow-950 { background-color: #fefce8 !important; } html:not(.dark) .text-yellow-300 { color: #a16207 !important; } html:not(.dark) .text-yellow-400 { color: #ca8a04 !important; } /* Accident / Recording */ html:not(.dark) .bg-orange-900 { background-color: #fff7ed !important; } html:not(.dark) .bg-orange-950 { background-color: #fff7ed !important; } html:not(.dark) .text-orange-300 { color: #c2410c !important; } html:not(.dark) .text-orange-400 { color: #ea580c !important; } html:not(.dark) .border-orange-800 { border-color: #fdba74 !important; } /* Active / Online */ html:not(.dark) .bg-green-900 { background-color: #f0fdf4 !important; } html:not(.dark) .bg-green-950 { background-color: #f0fdf4 !important; } html:not(.dark) .text-green-300 { color: #15803d !important; } html:not(.dark) .text-green-400 { color: #16a34a !important; } html:not(.dark) .border-green-800 { border-color: #86efac !important; } /* Unconfigured / Info */ html:not(.dark) .bg-indigo-950 { background-color: #eef2ff !important; } html:not(.dark) .bg-indigo-900 { background-color: #eef2ff !important; } html:not(.dark) .text-indigo-300 { color: #4338ca !important; } html:not(.dark) .text-indigo-400 { color: #6366f1 !important; } html:not(.dark) .border-indigo-800 { border-color: #a5b4fc !important; } /* ── Pulsing ring for recording nodes ────────────────────────────────────── */ @keyframes pulse-ring { 0% { transform: scale(1); opacity: 0.85; } 100% { transform: scale(2.4); opacity: 0; } } .node-pulse-ring { animation: pulse-ring 1.8s ease-out infinite; } /* ── Leaflet stacking fix ───────────────────────────────────────────────────── * Leaflet's internal panes (z-index 200–700) and its zoom / layers controls * (z-index 1000) otherwise paint above the sticky app Nav (z-40) and any modal * overlay — on Live this put the account dropdown *behind* the map. Pinning the * map container to its own low stacking context keeps Leaflet's internal layer * order intact while dropping the whole map (tiles + controls) below the app * chrome. The map's own overlay UI (legend, incident rail, clock, fit-all) sits * outside .leaflet-container, so it is unaffected and still renders on top. */ .leaflet-container { position: relative; z-index: 0; } /* ── Form inputs ─────────────────────────────────────────────────────────── */ html:not(.dark) input:not([type="submit"]):not([type="button"]):not([type="reset"]), html:not(.dark) select, html:not(.dark) textarea { color: #0f172a; } html:not(.dark) input::placeholder, html:not(.dark) textarea::placeholder { color: #94a3b8; } /* ── Marketing/product surface additions (2026-08 overhaul) ───────────────── * Same pattern as above: components use hardcoded dark-palette Tailwind * classes, remapped here for light mode instead of dark: prefixes. * Only new classes introduced by the marketing pages / settings shell live * below — everything else reuses the palette already mapped above. */ /* Tinted accent surfaces (plan highlight cards, "included" checks, danger zones) */ html:not(.dark) .bg-indigo-600\/10 { background-color: rgba(79,70,229,0.08) !important; } html:not(.dark) .border-indigo-600\/40 { border-color: rgba(79,70,229,0.35) !important; } html:not(.dark) .bg-green-600\/10 { background-color: rgba(22,163,74,0.08) !important; } html:not(.dark) .bg-red-600\/10 { background-color: rgba(220,38,38,0.08) !important; } html:not(.dark) .border-red-600\/40 { border-color: rgba(220,38,38,0.35) !important; } html:not(.dark) .bg-yellow-600\/10 { background-color: rgba(202,138,4,0.08) !important; } html:not(.dark) .border-yellow-600\/40 { border-color: rgba(202,138,4,0.35) !important; } /* Marketing hero background — subtle radial glow, brand-neutral in both themes */ .marketing-hero-bg { background-image: radial-gradient(ellipse 80% 50% at 50% -10%, rgba(99,102,241,0.25), transparent 60%); } html:not(.dark) .marketing-hero-bg { background-image: radial-gradient(ellipse 80% 50% at 50% -10%, rgba(99,102,241,0.12), transparent 60%); } /* Skeleton loading shimmer */ @keyframes skeleton-pulse { 0%, 100% { opacity: 0.5; } 50% { opacity: 1; } } .skeleton { animation: skeleton-pulse 1.6s ease-in-out infinite; }