Frontend redesign chunk 1: design tokens and type
Replace hardcoded dark-palette Tailwind classes with semantic CSS custom properties (page/surface/raised/line/ink/accent/sev-moderate/sev-major/ map-*) defined on :root (light) and .dark (dark), wired through tailwind.config.ts theme.extend.colors. Add IBM Plex Sans/Mono via next/font/google: sans for everything a person reads, mono reserved for machine identifiers only. Drop font-mono from body and Button's base classes. Existing !important light-mode overrides kept temporarily so nothing goes unreadable mid-migration (removed in chunk 4). Per UI_REDESIGN.md chunk 1.
This commit is contained in:
@@ -4,9 +4,67 @@
|
|||||||
|
|
||||||
@import 'leaflet/dist/leaflet.css';
|
@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 ─────────────────────────────────────────────────────────────────── */
|
/* ── Base ─────────────────────────────────────────────────────────────────── */
|
||||||
html, body {
|
html, body {
|
||||||
@apply bg-gray-950 text-gray-100 font-mono;
|
@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 ─────────────────────────────────────────────────── */
|
/* ── Light mode overrides ─────────────────────────────────────────────────── */
|
||||||
|
|||||||
@@ -1,9 +1,26 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
import { IBM_Plex_Sans, IBM_Plex_Mono } from "next/font/google";
|
||||||
import { AuthProvider } from "@/components/AuthProvider";
|
import { AuthProvider } from "@/components/AuthProvider";
|
||||||
import { ThemeProvider } from "@/components/ThemeProvider";
|
import { ThemeProvider } from "@/components/ThemeProvider";
|
||||||
import { ChromeSwitcher } from "@/components/ChromeSwitcher";
|
import { ChromeSwitcher } from "@/components/ChromeSwitcher";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
|
/* Sans for humans (headings, summaries, transcripts, buttons, nav);
|
||||||
|
* mono for machines (timestamps, talkgroups, unit ids). See UI_REDESIGN.md §2.1. */
|
||||||
|
const plexSans = IBM_Plex_Sans({
|
||||||
|
subsets: ["latin"],
|
||||||
|
weight: ["400", "500", "600"],
|
||||||
|
variable: "--font-sans",
|
||||||
|
display: "swap",
|
||||||
|
});
|
||||||
|
|
||||||
|
const plexMono = IBM_Plex_Mono({
|
||||||
|
subsets: ["latin"],
|
||||||
|
weight: ["400", "500"],
|
||||||
|
variable: "--font-mono",
|
||||||
|
display: "swap",
|
||||||
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "DRB — Public-Safety Radio Intelligence",
|
title: "DRB — Public-Safety Radio Intelligence",
|
||||||
description: "Live incident awareness from field SDR nodes — transcribed, correlated, and mapped in real time.",
|
description: "Live incident awareness from field SDR nodes — transcribed, correlated, and mapped in real time.",
|
||||||
@@ -11,12 +28,14 @@ export const metadata: Metadata = {
|
|||||||
|
|
||||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
// suppressHydrationWarning: the inline script below adds `.dark` to <html>
|
||||||
|
// before hydration, so the server/client className legitimately differs.
|
||||||
|
<html lang="en" suppressHydrationWarning className={`${plexSans.variable} ${plexMono.variable}`}>
|
||||||
<head>
|
<head>
|
||||||
{/* Prevent flash of wrong theme before React hydrates */}
|
{/* Prevent flash of wrong theme before React hydrates */}
|
||||||
<script dangerouslySetInnerHTML={{ __html: `(function(){try{var t=localStorage.getItem('drb-theme');if(t!=='light')document.documentElement.classList.add('dark');}catch(e){}})();` }} />
|
<script dangerouslySetInnerHTML={{ __html: `(function(){try{var t=localStorage.getItem('drb-theme');if(t!=='light')document.documentElement.classList.add('dark');}catch(e){}})();` }} />
|
||||||
</head>
|
</head>
|
||||||
<body className="min-h-screen bg-gray-950">
|
<body className="min-h-screen bg-page text-ink">
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<ChromeSwitcher>{children}</ChromeSwitcher>
|
<ChromeSwitcher>{children}</ChromeSwitcher>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const SIZE_CLASSES: Record<Size, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const BASE =
|
const BASE =
|
||||||
"inline-flex items-center justify-center font-semibold font-mono transition-colors " +
|
"inline-flex items-center justify-center font-semibold transition-colors " +
|
||||||
"disabled:opacity-50 disabled:cursor-not-allowed " +
|
"disabled:opacity-50 disabled:cursor-not-allowed " +
|
||||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-950";
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-950";
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,30 @@ const config: Config = {
|
|||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
mono: ["ui-monospace", "Cascadia Code", "Source Code Pro", "monospace"],
|
mono: ["var(--font-mono)", "ui-monospace", "Cascadia Code", "Source Code Pro", "monospace"],
|
||||||
sans: ["ui-sans-serif", "system-ui", "-apple-system", "Segoe UI", "Roboto", "Helvetica Neue", "Arial", "sans-serif"],
|
sans: ["var(--font-sans)", "ui-sans-serif", "system-ui", "-apple-system", "Segoe UI", "Roboto", "Helvetica Neue", "Arial", "sans-serif"],
|
||||||
|
},
|
||||||
|
// Semantic tokens — defined as CSS custom properties in app/globals.css on
|
||||||
|
// :root (light) and .dark (dark). Components must use these names, never a
|
||||||
|
// raw gray-9xx, so a theme is one variable block rather than an override sheet.
|
||||||
|
colors: {
|
||||||
|
page: "var(--page)",
|
||||||
|
surface: "var(--surface)",
|
||||||
|
raised: "var(--raised)",
|
||||||
|
line: "var(--line)",
|
||||||
|
"line-strong": "var(--line-strong)",
|
||||||
|
ink: {
|
||||||
|
DEFAULT: "var(--ink)",
|
||||||
|
2: "var(--ink-2)",
|
||||||
|
muted: "var(--ink-muted)",
|
||||||
|
},
|
||||||
|
accent: "var(--accent)",
|
||||||
|
"sev-moderate": "var(--sev-moderate)",
|
||||||
|
"sev-major": "var(--sev-major)",
|
||||||
|
"map-bg": "var(--map-bg)",
|
||||||
|
"map-block": "var(--map-block)",
|
||||||
|
"map-road": "var(--map-road)",
|
||||||
|
"map-water": "var(--map-water)",
|
||||||
},
|
},
|
||||||
// Marketing/product type scale — used by the (marketing) surface and
|
// Marketing/product type scale — used by the (marketing) surface and
|
||||||
// settings shell so headings read as a deliberate hierarchy rather than
|
// settings shell so headings read as a deliberate hierarchy rather than
|
||||||
|
|||||||
Reference in New Issue
Block a user