/* Apex Command — the shared visual language.
 *
 * Everything in the app is styled from this file. The Command display is the
 * reference: near-black, monospace, dense, thin rules, muted grey text with a
 * single amber accent. These are working screens people keep open all day, so
 * the rules are:
 *
 *   - Colour means "act". Amber, red and green are reserved for state. Nothing
 *     is coloured for decoration, or the one thing that matters stops standing out.
 *   - Density over whitespace. A rep scanning fifteen leads should not scroll.
 *   - Thin rules, never heavy borders. Structure should be felt, not seen.
 *
 * command.css adds only the fixed-panel layout and canvas on top of this.
 */
:root {
	/* ── Surfaces ─────────────────────────────────────────────────────────────
	 * Three of them, where there used to be one black doing all the work.
	 *
	 * --bg is a cool near-black rather than #000: pure black gives the orbital
	 * canvas nothing to glow against and flattens it. Everything is biased
	 * slightly blue-cool so amber reads WARM against it — a pure grey beside
	 * amber looks like a mistake, a cool grey looks chosen.
	 *
	 * --surface stays rgba ON PURPOSE. The canvas reads through the panels and
	 * that is the effect; a solid panel colour would kill it app-wide in one
	 * commit. If you need an opaque surface for one component, that is what
	 * --surface-solid is for. */
	--bg: #0C0E10;
	--surface: rgba(20, 23, 26, 0.9);
	--surface-solid: #14171A;
	/* Raised: a selected row, a hovered card, a menu over the page. */
	--surface-2: #1B1F24;
	/* One step further forward, for the thing you are ON — the active nav tab,
	   an initials plate. The reference has three steps and the nav needs to sit
	   above the panel it labels. */
	--surface-3: #23282E;
	--surface-raised: rgba(255, 255, 255, 0.035);
	/* Over #0C0E10 these land near #2B3138 and #3A424B — the reference's two
	   line weights. The old values were tuned against pure black and disappeared
	   once the page lifted off it. */
	--border: rgba(255, 255, 255, 0.105);
	--border-bright: rgba(255, 255, 255, 0.185);

	--text: #E7EAED;
	--text-bright: #F7F9FA;
	--text-dim: #9AA4AE;

	/* ── Brand accent ─────────────────────────────────────────────────────────
	 * ONE active nav item, ONE primary button, ONE selected row. That is the
	 * whole budget. It used to appear often enough to mean nothing, which is
	 * also why severity got its own tokens below — a page where the urgent thing
	 * and the navigation are the same colour makes you read both to find one. */
	--amber: #E8A33D;

	/* ── Severity — deliberately NOT the brand accent ─────────────────────────
	 * These say something needs doing. Keep them out of navigation and out of
	 * buttons, and keep the brand accent out of rows and badges. */
	--warn: #E6C15C;
	--red: #D4574A;
	--green: #57A97A;
	--blue: #8BB4D0;
	/* Information, not urgency: the stripe on an internal conversation. More
	   saturated than --blue, which is a link colour and has to stay readable as
	   running text. */
	--info: #5590CF;
	/* An outbound bubble. A solid cool slate rather than a wash — at 10% over the
	   page it was not reliably distinguishable from an inbound one. */
	--mine: #2A3A4A;
	--mine-line: #35485C;
	/* Ink for a filled-amber control, and the hover step. Dark enough to read on
	   the accent, warm enough not to look like a hole punched in it. */
	--on-amber: #201502;
	--amber-lift: #F0B25A;
	/* Pill plates: a solid dark tint of the state colour with light ink on it,
	   rather than a translucent wash. At 3px radius and mono caps a wash reads as
	   a disabled control; a plate reads as a label. */
	--warn-plate: #3A2F16;
	--warn-ink: #DCB35E;
	--red-plate: #3A1D1A;
	--red-ink: #E08B80;

	/* Tints of the above. They exist as tokens because every one of them used to
	   be a hand-written rgba() of the OLD amber, so changing the accent left
	   fifteen washes behind at the previous hue. If a screen needs a new tint,
	   add it here — never write the channels into a rule. */
	--amber-wash: rgba(232, 163, 61, 0.10);
	--amber-wash-strong: rgba(232, 163, 61, 0.20);
	--amber-line: rgba(232, 163, 61, 0.42);
	--warn-wash: rgba(230, 193, 92, 0.13);
	--warn-line: rgba(230, 193, 92, 0.38);
	--red-wash: rgba(212, 87, 74, 0.14);
	--green-line: rgba(87, 169, 122, 0.38);
	--blue-wash: rgba(139, 180, 208, 0.10);
	--blue-line: rgba(139, 180, 208, 0.35);
	--red-line: rgba(212, 87, 74, 0.38);
	--white-wash: rgba(255, 255, 255, 0.07);
	/* ── Portal build-stage colours ────────────────────────────────────────
	 * Phase 2 of the Crew & Project Manager portal (web/public/portal.css)
	 * needs one colour per build stage for a left border/chip on a project
	 * row. tests/tokens.test.js requires every colour value to live here,
	 * not in the sheet that uses it — same rule as everything else on this
	 * page — so these are tokens, not a portal-local exception. Deliberately
	 * none of them is --amber: amber means "needs doing" everywhere in this
	 * app, and phase 3's Not Ready flag needs that meaning free to claim.
	 *
	 * Retuned for phase 4 (the calendar), where these become small marks
	 * that can sit next to any other. Measured, not eyeballed (OKLab ΔE
	 * under Machado protan/deutan simulation): the old set had dirt-work vs
	 * --red at ΔE 0.8 and erection vs --red at 3.4 under red-green
	 * colourblindness — a rust mark was an error mark. The new set holds
	 * every stage pair at ΔE ≥ 6 with text or shape always alongside, and
	 * the rust/error-red pair specifically at ≥ 9. Two of these are
	 * DELIBERATE neutrals the chroma rule exempts: concrete IS grey, and
	 * complete is a job at rest. Brown, rust and error-red cannot all three
	 * be told apart at similar lightness under deutan vision — the axis they
	 * differ on is the axis that sight lacks — so the set separates on
	 * lightness and keeps --red banned from portal screens entirely
	 * (tests/tokens.test.js enforces the ban).
	 *
	 * Each stage has a pair: the base (borders, calendar marks — dark,
	 * tuned for pairwise separation) and -ink (chip TEXT — lifted to ≥ 4.5:1
	 * on --surface-2 for a phone read in direct sun). Same base/-ink split
	 * --red/--red-ink and --warn/--warn-ink already use. */
	--stage-permitting:        #5B7FBE; /* blueprint blue */
	--stage-dirt-work:         #8A6620; /* dark earth */
	--stage-rebar-layout:      #9D8CD9; /* layout-paint violet */
	--stage-concrete-pad:      #93A0AB; /* concrete — deliberately grey */
	--stage-erection:          #8F3E16; /* deep rust: ΔE ≥ 9 from --red under CVD */
	--stage-trim-finish:       #3D8578; /* teal, clear of --green (ΔE 11) */
	--stage-complete:          #4A5058; /* muted/dim — the job is at rest, not live */
	--stage-permitting-ink:    #8FA9D9;
	--stage-dirt-work-ink:     #C4A878;
	--stage-rebar-layout-ink:  #BCA9E8;
	--stage-concrete-pad-ink:  #AEBAC4;
	--stage-erection-ink:      #D89468;
	--stage-trim-finish-ink:   #7FB8A9;
	--stage-complete-ink:      #9AA1A9;
	/* Opaque overlays that must hide the canvas behind them — a menu, a panel
	   floating over the orbit. Darker than --bg on purpose. */
	--scrim: rgba(6, 7, 9, 0.96);
	--scrim-shadow: rgba(0, 0, 0, 0.7);
	/* The page colour, for gradients and knockouts that must vanish into it. */
	--bg-rgb: 12, 14, 16;

	/* ── Type ─────────────────────────────────────────────────────────────────
	 * There was no sans face at all: every name, message, button and heading in
	 * the app was set in a typeface designed for aligning code, which was doing
	 * more damage than the palette was.
	 *
	 * Sans carries content — names, prose, controls, headings. Mono stays where
	 * it reads as an INSTRUMENT rather than an accident: timestamps, counts,
	 * ids, money, system lines and the command palette. The test is whether
	 * digits need to line up under each other, or whether the thing is machine
	 * output being quoted.
	 *
	 * A system stack, no web font: this app serves everything itself under a
	 * strict CSP and a font request to a CDN would be the only external call in
	 * the whole product. */
	--sans: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
		"Helvetica Neue", Arial, "Noto Sans", sans-serif;
	--mono: Consolas, "SF Mono", "Cascadia Code", "Courier New", monospace;

	/* One spacing scale, used everywhere. */
	--s1: 4px;
	--s2: 8px;
	--s3: 14px;
	--s4: 20px;
	--s5: 32px;

	--radius: 8px;
	--radius-sm: 4px;

	/* ── How wide a page is allowed to be ─────────────────────────────────────
	 * Three answers, because there are three kinds of screen here and one cap
	 * was being applied to all of them. /messages was rendering as two small
	 * panels floating in the top-left of a mostly empty field: an inbox is an
	 * app surface, not a document.
	 *
	 *   prose  a page you read down. The cap earns its keep.
	 *   wide   a table or a board. It wants the window, but not an ultrawide
	 *          monitor's full 3440px — a row that long cannot be read across.
	 *   app    a fixed frame with its own scrolling regions. Full bleed. */
	--measure: 1180px;
	--measure-wide: 1760px;

	/* The header's real height, measured at runtime and rewritten whenever it
	   changes size — the nav wraps to a second row on narrow widths. Anything
	   positioned against the top of the viewport (the Command display's fixed
	   panels) offsets by this instead of guessing. The value here is only the
	   fallback used for the first paint, before the measurement lands. */
	--bar-h: 44px;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

body {
	background: var(--bg);
	color: var(--text);
	font-family: var(--sans);
	font-size: 13px;
	line-height: 1.55;
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}

/* Anything that is a reading rather than a word. `.mono` is applied by hand in
   the templates; these are the ones that are always instrument. */
.mono, code, time, .num, td.num, th.num,
.panel-count, .stat .n, .msg-row-when, .msg-m-at, .synced {
	font-family: var(--mono);
	font-variant-numeric: tabular-nums;
}

a { color: var(--blue); text-decoration: none; }
a:hover { color: var(--text-bright); }

/* ── Navigation as transitions ───────────────────────────────────────────────
 * This app is server-rendered and always will be — ten people clicking a
 * dashboard does not need a build step. So the continuity between pages comes
 * from cross-document view transitions rather than from a router: one CSS
 * at-rule, no JavaScript, nothing to go wrong under a strict CSP.
 *
 * The trick is what is NOT animated. `root` swaps instantly, which covers the
 * header, the nav and the canvas — all of them pixel-identical across pages, so
 * an instant swap reads as them never having moved. Only `main` cross-fades.
 * The effect is one persistent surface whose contents change, which is the
 * whole point: it should feel built at once, not assembled page by page.
 *
 * Where this is unsupported the fallback below does a plain fade-in, and the
 * pages work identically either way. */
@view-transition { navigation: auto; }

::view-transition-old(root),
::view-transition-new(root) { animation: none; }

main { view-transition-name: view; }
::view-transition-old(view) { animation: view-out .14s ease both; }
::view-transition-new(view) { animation: view-in .2s ease both; }

/* Exactly one nav link carries .on at a time, so the name is unique on both
   sides and the amber marker slides to the new page instead of blinking. */
.bar nav a.on { view-transition-name: nav-active; }

@keyframes view-out { to { opacity: 0; } }
@keyframes view-in { from { opacity: 0; } }

/* No view transitions here: give the content the same soft entrance so the app
   feels the same, just without the continuity between the two frames. */
@supports not (view-transition-name: none) {
	main { animation: view-in .2s ease both; }
}

/* A page that changes under you is exactly what this setting exists to stop.
   One frame, not a slower one. */
@media (prefers-reduced-motion: reduce) {
	@view-transition { navigation: none; }
	main { animation: none; }
	::view-transition-group(*),
	::view-transition-old(*),
	::view-transition-new(*) { animation: none !important; }
}

/* ── The header, identical on every page ─────────────────────────────────── */
.bar {
	display: flex; align-items: center; gap: var(--s3);
	padding: var(--s2) var(--s4);
	border-bottom: 1px solid var(--border);
	/* Its own surface, not a gradient over the page. Part of what makes the nav
	   read as floating above the work rather than being the top of it. */
	background: var(--surface-solid);
	min-height: 46px;
	flex-wrap: wrap;
	/* Position and stacking belong to .chrome, the wrapper. The header used to
	   stick on its own at a JS-measured offset so it would clear the act-as
	   banner, which stuck at 0 above it. That worked only while the measurement
	   landed; anywhere it did not, the banner covered the nav and there was no
	   way to leave the page. One sticky ancestor removes the race entirely. */
}
/* The wordmark. It used to be "A P E X   C O M M A N D" tracked out across the
   bar at 4px of letter-spacing, which is a label style — on a masthead it reads
   as a 1980s terminal. Tight, mono, and the only colour is the slash. */
.bar .brand {
	font-family: var(--mono);
	font-size: 12px; letter-spacing: 0; text-transform: none;
	color: var(--text-bright); white-space: nowrap;
}
.bar .brand .slash { color: var(--amber); margin: 0 1px; }

/* Navigation is words, not labels. Caps plus tracking was the single strongest
   thing still making this look unfinished. */
.bar nav { display: flex; gap: 2px; flex-wrap: wrap; align-items: center; }
.bar nav a {
	font-size: 13px; letter-spacing: 0; text-transform: none;
	color: var(--text-dim);
	padding: 4px 10px;
	border: 0; border-radius: 999px;
	line-height: 1.4;
}
.bar nav a:hover { color: var(--text-bright); background: var(--white-wash); }
/* Filled, not outlined. An outlined box on a dark ground reads as a form field
   waiting for input rather than as the page you are on. */
.bar nav a.on { color: var(--text-bright); background: var(--surface-3); }

.bar .right { margin-left: auto; display: flex; align-items: center; gap: var(--s2); }

/* Chips, not bare caps text. Anything in the right-hand cluster that is a
   READING gets a hairline round it so it reads as an instrument. */
.bar .chip {
	display: inline-flex; align-items: center; gap: 6px;
	padding: 3px 10px; border-radius: 999px;
	border: 1px solid var(--border);
	font-size: 11px; color: var(--text-dim);
	white-space: nowrap;
}
.bar .chip .dot {
	width: 6px; height: 6px; border-radius: 999px; flex: none;
	background: var(--green);
}
.bar .chip.stale { color: var(--warn); border-color: var(--warn-line); }
.bar .chip.stale .dot { background: var(--warn); }

/* The signed-in person is an avatar, not a shouted name. The name is on the
   title and in the menu it opens — a header is not the place to read it. */
.bar .avatar {
	width: 26px; height: 26px; border-radius: 999px;
	display: inline-flex; align-items: center; justify-content: center;
	padding: 0; border: 1px solid var(--border-bright);
	background: var(--surface-2); color: var(--text);
	font-family: var(--sans); font-size: 10px; font-weight: 600; letter-spacing: .3px;
	text-transform: none;
}
.bar .avatar:hover { background: var(--surface-raised); color: var(--text-bright); border-color: var(--text-dim); }
/* While acting as somebody else the avatar is the one thing that must not blend
   in — it is whose account everything below belongs to. */
.bar .avatar.acting { border-color: var(--amber); color: var(--amber); }
.bar .who { color: var(--text-dim); font-size: 11px; }

/* One measure for the whole app, and one vertical rhythm. Every page was
   setting its own spacing by accident — a panel here, a bare table there — so
   the eye had to re-learn the grid on each screen. Panels are grid children now
   and the gap is the only thing deciding the space between them. */
main {
	max-width: var(--measure);
	margin: 0 auto;
	padding: var(--s4) var(--s4) var(--s5);
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	align-content: start;
	gap: var(--s3);
}
/* Set by the route (web/server.js WIDTH), not guessed from the path in CSS —
   a new table page should declare what it is rather than inherit prose width
   and look like a mistake nobody notices. */
body[data-width="wide"] main { max-width: var(--measure-wide); }
body[data-width="app"] main { max-width: none; padding: var(--s3) var(--s3) var(--s3); }
/* The grid owns the gaps, so the components must not add their own. */
main > .panel, main > .card, main > details.panel { margin-bottom: 0; }
main > h1, main > h2 { margin-bottom: 0; }
main > h2 { margin-top: var(--s3); }

/* ── Type scale ──────────────────────────────────────────────────────────── */
h1 {
	font-size: 13px; letter-spacing: 3px; text-transform: uppercase;
	color: var(--text-bright); font-weight: 400;
	margin-bottom: var(--s3);
}
h2 {
	font-size: 11px; letter-spacing: 2.5px; text-transform: uppercase;
	color: var(--text-dim); font-weight: 400;
	margin: var(--s5) 0 var(--s2);
}
h2.urgent { color: var(--red); }
.warn-text, .status-warn { color: var(--warn); }
.panel-title.warn { color: var(--warn); }
.panel-title.error { color: var(--red); }
h3 { font-size: 12px; font-weight: 600; color: var(--text-bright); }

.dim, .muted { color: var(--text-dim); }
.small { font-size: 11px; }
.mono { font-variant-numeric: tabular-nums; }
.error { color: var(--red); }
.good { color: var(--green); }
code { color: var(--amber); font-size: .95em; }

/* ── Panels — the bordered card with the + affordance ────────────────────── */
.panel {
	background: var(--surface);
	backdrop-filter: blur(6px);
	border: 1px solid var(--border);
	border-radius: var(--radius);
	padding: var(--s3) var(--s4);
	margin-bottom: var(--s3);
}
.panel-hdr {
	display: flex; justify-content: space-between; align-items: baseline;
	gap: var(--s3);
	padding-bottom: var(--s2);
	margin-bottom: var(--s2);
	border-bottom: 1px solid var(--border);
}
.panel-title {
	font-size: 11px; letter-spacing: 2.5px; text-transform: uppercase;
	color: var(--text-dim);
	white-space: nowrap;
}
.panel-toggle { font-size: 10px; color: var(--text-dim); transition: transform .3s, color .2s; }

/* The right-hand slot of a panel header. Two kinds live there and they are not
   the same thing: a count, which is data and gets the bright tabular treatment,
   and a caption saying what the panel is measuring, which is prose and stays
   recessive. Before this they were both crammed into .panel-toggle — the
   Command display's + affordance — which is why captions were rendering at the
   size and weight of a control. */
.panel-count {
	font-size: 13px; color: var(--text-bright);
	font-variant-numeric: tabular-nums; line-height: 1;
}
.panel-count.warn { color: var(--warn); }
.panel-count.bad { color: var(--red); }
.panel-meta {
	font-size: 10px; color: var(--text-dim);
	text-align: right; line-height: 1.4;
	min-width: 0;
}
/* When a header carries both a count and a caption they are one unit on the
   right, not two things pushed to opposite ends of the bar by space-between. */
.panel-slot { display: flex; align-items: baseline; gap: var(--s2); min-width: 0; }

/* A panel that actually collapses uses <details>, so the + is a real control
   rather than a decoration borrowed from the Command display. */
details.panel > summary {
	list-style: none; cursor: pointer;
	display: flex; justify-content: space-between; align-items: center;
	font-size: 11px; letter-spacing: 2.5px; text-transform: uppercase;
	color: var(--text-dim);
}
details.panel > summary::-webkit-details-marker { display: none; }
details.panel > summary::after { content: '+'; font-size: 12px; transition: transform .3s; }
details.panel[open] > summary { color: var(--text-bright); margin-bottom: var(--s3); }
details.panel[open] > summary::after { transform: rotate(45deg); }
details.panel > summary:hover { color: var(--text-bright); }

/* ── Stat strip ──────────────────────────────────────────────────────────── */
.strip { display: flex; gap: var(--s5); flex-wrap: wrap; margin-bottom: var(--s4); }
.stat { font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--text-dim); }
.stat .n {
	display: block; font-size: 22px; letter-spacing: -0.5px; line-height: 1.1;
	color: var(--text-bright); font-variant-numeric: tabular-nums; text-transform: none;
	margin-bottom: 2px;
}
.stat.warn .n { color: var(--warn); }
.stat.bad .n { color: var(--red); }
/* A dash in a stat strip means "not measured yet", and rendering it in the same
   bright white as a real figure reads as a score. It has to be dim here for the
   same reason it is dim in a table. */
.stat.none .n { color: var(--text-dim); }

/* ── Tables get the same treatment as panels ─────────────────────────────── */
/* Density matched to the Command display's roster: 6px rows, thin rules, no
   vertical borders. A rep scanning fifteen leads should not have to scroll. */
table { width: 100%; border-collapse: collapse; font-size: 12px; }
th {
	text-align: left; font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase;
	color: var(--text-dim); font-weight: 400;
	padding: 6px var(--s2);
	border-bottom: 1px solid var(--border-bright);
	white-space: nowrap;
}
td {
	padding: 6px var(--s2);
	border-bottom: 1px solid var(--border);
	vertical-align: top;
}
tbody tr:last-child td { border-bottom: none; }
/* Numbers line up under each other or they cannot be compared down a column. */
td.mono, td.num, th.num { font-variant-numeric: tabular-nums; }
td.num, th.num { text-align: right; }
tbody tr:hover td { background: var(--blue-wash); }
td a { color: var(--text-bright); }
td a:hover { color: var(--blue); text-decoration: underline; text-underline-offset: 2px; }

/* ── State, and only state ───────────────────────────────────────────────────
 * Colour is a claim that something needs doing. These three classes are the
 * whole vocabulary on a row: amber says a reply is owed, red says a promise is
 * already broken, and dim says the value is absent rather than zero. Anything
 * decorative stays grey — if everything is coloured, the one thing that matters
 * stops standing out. */
.owed { color: var(--warn); }
.breach { color: var(--red); }
.none { color: var(--text-dim); }

/* ── A list of records with rules instead of boxes ───────────────────────────
 * Same job as a table where the data is not columnar — pipeline cards, feeds.
 * A border around every item draws six lines to separate two things; one rule
 * does it with one. */
.rows { list-style: none; }
.rows > li, .rows > .row {
	display: flex; align-items: baseline; gap: var(--s2);
	padding: 5px 0;
	border-bottom: 1px solid var(--border);
	font-size: 12px;
}
.rows > li:last-child, .rows > .row:last-child { border-bottom: none; }
.rows .row-name { color: var(--text-bright); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rows .row-name:hover { color: var(--blue); }
.rows .row-meta { margin-left: auto; display: flex; gap: var(--s2); font-size: 10px; color: var(--text-dim); white-space: nowrap; }

/* ── Controls ────────────────────────────────────────────────────────────── */
label {
	display: block; margin-bottom: var(--s3);
	font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--text-dim);
}
/* `textarea` belongs in this list and was missing from it. Every screen with a
   multi-line field was therefore rendering a white browser-default box in the
   middle of a near-black app, and the two templates that had one papered over it
   with inline styles — which is why it looked fixed in one place and broken in
   the next. One rule, all of them. */
input, select, textarea {
	display: block; width: 100%; margin-top: var(--s1);
	padding: var(--s2);
	font-family: var(--sans); font-size: 13px; line-height: 1.5;
	color: var(--text-bright); background: var(--bg);
	border: 1px solid var(--border-bright); border-radius: var(--radius-sm);
}
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--amber); }
input::placeholder, textarea::placeholder { color: var(--text-dim); }
textarea { resize: vertical; }

/* The default button is quiet: an outline that does not compete. The ONE
   primary action on a screen gets `.primary` — filled amber with dark text —
   which is the whole of the brand accent's budget alongside the active nav tab
   and the selected filter. */
button, .btn {
	display: inline-block; cursor: pointer;
	font-family: var(--sans); font-size: 12px; font-weight: 500;
	letter-spacing: 0; text-transform: none;
	padding: 7px 13px;
	color: var(--text); background: var(--surface-2);
	border: 1px solid var(--border-bright); border-radius: 7px;
}
button.primary, .btn.primary {
	background: var(--amber); border-color: var(--amber);
	color: var(--on-amber); font-weight: 500;
}
button.primary:hover, .btn.primary:hover { background: var(--amber-lift); color: var(--on-amber); }
button:hover, .btn:hover { background: var(--surface-3); color: var(--text-bright); }
.btn.ghost, button.ghost { background: none; color: var(--text-dim); border-color: var(--border-bright); }
.btn.ghost:hover, button.ghost:hover { color: var(--text-bright); border-color: var(--text-dim); }
button.link { border: none; background: none; padding: 0; color: var(--blue); }
button.link:hover { background: none; color: var(--text-bright); }

.actions { display: flex; gap: var(--s2); flex-wrap: wrap; align-items: center; margin: var(--s3) 0; }

/* Pill row — view toggles and filters */
.modes { display: flex; gap: var(--s1); flex-wrap: wrap; margin-bottom: var(--s3); }
.modes a {
	font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase;
	padding: var(--s1) var(--s3);
	color: var(--text-dim);
	border: 1px solid var(--border); border-radius: 999px;
}
.modes a:hover { color: var(--text-bright); border-color: var(--border-bright); }
.modes a.on { color: var(--amber); border-color: var(--amber); }

/* ── Tags and flags ──────────────────────────────────────────────────────── */
.tag {
	font-size: 9px; letter-spacing: 1px; text-transform: uppercase;
	padding: 2px 6px; border-radius: 3px;
	border: 1px solid var(--border-bright); color: var(--text-dim);
	white-space: nowrap;
}
.tag.real { color: var(--green); border-color: var(--green-line); }
.tag.declined, .tag.internal { color: var(--text-dim); }
.flag {
	font-size: 9px; letter-spacing: 1px; text-transform: uppercase;
	padding: 1px 5px; border-radius: 3px; margin-right: var(--s1);
	border: 1px solid var(--border);
	color: var(--text-dim);
}
.flag.yes { color: var(--green); border-color: var(--green-line); }

.out { white-space: nowrap; font-size: 10px; letter-spacing: 1px; text-transform: uppercase; color: var(--text-dim); }
.out:hover { color: var(--amber); }

/* ── Cards used for empty states and callouts ────────────────────────────── */
.card {
	background: var(--surface);
	backdrop-filter: blur(6px);
	border: 1px solid var(--border);
	border-radius: var(--radius);
	padding: var(--s4);
}
.narrow { max-width: 380px; margin: 12vh auto; }

@media (max-width: 700px) {
	main { padding: var(--s3); }
	.strip { gap: var(--s4); }
	table { font-size: 12px; }
}


/* ── The shared canvas layer ─────────────────────────────────────────────────
 * One background, rendered once for the whole app rather than per page. Full
 * brightness on the command display; dimmed and blurred everywhere else so it
 * reads as depth behind the work instead of competing with it.
 *
 * Motion must never make a number harder to read — that is the rule this file
 * enforces, and it is why the ambient state is blurred rather than merely
 * faded: a sharp moving dot behind a table is worse than a soft one. */
#orb {
	position: fixed; inset: 0;
	width: 100vw; height: 100vh;
	z-index: 0;
	pointer-events: none;
	transition: opacity .6s ease, filter .6s ease;
}
body[data-canvas="full"] #orb { opacity: 1; filter: none; pointer-events: auto; }
/* Behind a scrolling list the canvas is texture, not the subject. Dimmed and
   blurred enough that no dot ever competes with a row for attention — the test
   is whether a number is harder to read with it than without it. */
body[data-canvas="ambient"] #orb { opacity: .26; filter: blur(4px); }

/* Everything else sits above it.
 *
 * NOT `.bar` — it already carries its own position and z-index further up, and
 * naming it here silently overrode both: same specificity, later in the file, so
 * this rule won. The header stopped being sticky the day the canvas landed and
 * nobody noticed, because a header that scrolls away still looks fine on a short
 * page. It only became obvious when the act-as banner arrived: that IS sticky, at
 * z-index 40, so it parked itself over a header stuck at z-index 1 and the whole
 * nav became unclickable. If something needs lifting above the canvas, give it
 * its own rule rather than adding it to this list. */
main { position: relative; z-index: 1; }

/* ── The page chrome ─────────────────────────────────────────────────────────
 * The banner (when present) and the header, as a single sticky unit. Everything
 * that floats over the page — the lead panel, dialogs — must stack below this,
 * or it becomes possible to be on a page with no way off it. */
.chrome {
	position: sticky; top: 0; z-index: 60;
	/* Its own stacking context, so nothing inside can escape above it and
	   nothing outside can slip between the banner and the nav. */
	isolation: isolate;
}

/* ── Acting as someone else ──────────────────────────────────────────────────
 * Amber, full width, above the header and impossible to scroll away from. This
 * is the one place the app shouts, because the failure it prevents is an owner
 * forgetting whose screen they are on. It reads as a warning rather than an
 * error: nothing is wrong, but nothing here is yours. */
.viewas-bar {
	display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
	padding: var(--s2) var(--s4);
	background: var(--amber-wash);
	border-bottom: 1px solid var(--amber);
	font-size: 11px;
}
.viewas-who { color: var(--amber); letter-spacing: 1px; text-transform: uppercase; }
.viewas-who strong { color: var(--text-bright); font-weight: 400; }
.viewas-note { color: var(--text-dim); }
.viewas-bar form { margin-left: auto; }

.bar .synced { font-size: 11px; }
.bar .right form { display: inline; }

/* ── The lead panel ──────────────────────────────────────────────────────────
 * Opens when a body in the orbit is clicked. A shortcut, never the only door:
 * every lead here is reachable from the tables, which matters because the
 * canvas does not exist at all on a phone. */
.leadpanel {
	position: fixed; z-index: 35;
	top: calc(var(--bar-h) + 12px); right: 20px; bottom: 20px;
	width: 340px; max-width: calc(100vw - 40px);
	display: flex; flex-direction: column;
	padding: var(--s3) var(--s4);
	background: var(--scrim);
	backdrop-filter: blur(8px);
	border: 1px solid var(--border-bright);
	border-radius: var(--radius);
	overflow-y: auto;
	opacity: 0; transform: translateX(8px);
	transition: opacity .22s ease, transform .22s ease;
}
.leadpanel.open { opacity: 1; transform: none; }
/* `display: flex` above beats the user-agent rule for [hidden], so without this
   the panel stays in the layout — invisible at opacity 0, and still swallowing
   every click in the column it occupies. */
.leadpanel[hidden] { display: none; }

.leadpanel-head { display: flex; align-items: baseline; gap: var(--s2); margin-bottom: var(--s2); }
.leadpanel-name { font-size: 14px; color: var(--text-bright); }
.leadpanel-close {
	margin-left: auto; border: none; background: none; padding: 0 var(--s1);
	font-size: 16px; line-height: 1; color: var(--text-dim);
}
.leadpanel-close:hover { background: none; color: var(--text-bright); }

.leadpanel-meta {
	display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center;
	font-size: 11px; color: var(--text-dim);
	padding-bottom: var(--s2); border-bottom: 1px solid var(--border);
}
.leadpanel-last { padding: var(--s2) 0; border-bottom: 1px solid var(--border); }
.leadpanel-kind {
	font-size: 9.5px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--text-dim);
}
.leadpanel-quote {
	margin-top: var(--s1); font-size: 12px; color: var(--text);
	white-space: pre-wrap; word-break: break-word;
}
.leadpanel-note { font-size: 11px; padding: var(--s2) 0; }
.leadpanel-actions { display: flex; gap: var(--s2); margin-top: auto; padding-top: var(--s3); }

/* Reps are on phones. The canvas is decoration there and it costs battery, so
   it stops animating entirely rather than degrading to something janky — and
   with no canvas there is nothing to click, so the panel goes with it. */
@media (max-width: 700px) {
	#orb { display: none; }
	.leadpanel { display: none; }
}

/* A moving background is exactly what this setting exists to stop. */
@media (prefers-reduced-motion: reduce) {
	#orb { animation: none; }
	body[data-canvas="ambient"] #orb { opacity: .2; }
	* { transition-duration: .01ms !important; }
}

/* The unread badge. Amber because it is a claim that something needs doing —
   the same rule as everywhere else, so it reads as work rather than decoration. */
.bar nav .unread-dot {
	display: inline-block;
	min-width: 15px; padding: 0 4px;
	border-radius: 999px;
	background: var(--warn); color: var(--bg);
	font-size: 9px; line-height: 15px; text-align: center;
	font-variant-numeric: tabular-nums;
}
/* Somebody used your name. Twelve unread on a busy thread is background; one
   that names you is work, and the two must not look the same. A ring rather
   than another colour — the amber already means "needs doing", and a second
   hue would start a vocabulary this app does not have. */
.bar nav .unread-dot.named {
	box-shadow: 0 0 0 2px var(--bg), 0 0 0 3px var(--warn);
}
