Files
server-26/drb-frontend/app/globals.css
T
Logan CusanoandClaude Opus 5.5 c1a2721fbe Map: pin 511NY camera feeds as tiles on the map (#183)
Clicking a camera's snapshot pins a 192x108 tile anchored at the camera --
the live HLS stream when the camera has one (hls.js; Safari native), else the
snapshot refreshed every 60s, falling back to the snapshot if the stream dies.
Up to 6 pins, oldest dropped. Players exist only while the DOT Cameras overlay
is on, so hiding it stops every stream.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 14:43:40 -04:00

235 lines
11 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.
@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 <html> (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;
}
/* Pinned 511 camera tiles (MapView DotCameraLayer): strip Leaflet's tooltip
* padding and let the tile's own theme colours show, so the tile is no bigger
* than the video it holds. */
.leaflet-tooltip.drb-cam-pin {
padding: 0;
border-radius: 6px;
background: var(--surface);
border: 1px solid var(--line-strong);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);
white-space: normal;
}
/* ── 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;
}