/* ============================================================
   Audio Alliance Portal — common/styles.css
   Shared across all modules. Include in every module HTML:
   <link rel="stylesheet" href="/common/styles.css">
   Module-specific styles go in each module's own <style> block.
   ============================================================ */

@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600&family=DM+Mono:wght@400;500&display=swap');

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

/* ── VARIABLES ───────────────────────────────────────────── */
:root {
  /* HOUSE FONT — Acumin Pro via Adobe Fonts kit cpe6lxv (Sir Tim's ruling, 30 July 2026,
     chosen on /font-preview.html); DM Sans remains the loaded fallback. Mono duties
     stay DM Mono. Always reference these tokens, never literal family names. */
  --font-sans: 'acumin-pro', 'DM Sans', sans-serif;
  --font-mono: 'ibm-plex-mono', 'DM Mono', monospace;  /* IBM Plex Mono crowned 30 July 2026 (kit cpe6lxv); DM Mono fallback */
  /* RADIUS SCALE (Sir Tim's decree, 30 July 2026 — ending the 16-value sprawl):
     cards/panels/modals 10px · controls 7px · label-tags 6px (PROJECT etc — the
     Bootstrap balloon is dead) · true pills reserved for STATUS only. Micro radii
     ≤5px (swatches, dense patch/comms controls) deliberately keep their literals. */
  --r-card: 10px;
  --r-ctl: 7px;
  --r-chip: 6px;
  --r-pill: 999px;
  /* CHIP SWATCHES (31 July 2026 — Sir Tim's decree: colours stay strictly brand,
     so purple may never serve as LETTERING in dark; label-chips carry identity
     in tint + border and letter in var(--text). Confirmed-style SOLID fills with
     white text are the other lawful idiom and are untouched. */
  --chip-purple-bg: rgba(114, 48, 165, 0.15);
  --chip-purple-bd: rgba(114, 48, 165, 0.45);
  --chip-blue-bg:   rgba(61, 183, 228, 0.12);
  --chip-blue-bd:   rgba(61, 183, 228, 0.42);
  /* Tell the browser this is a dark UI: native form controls, date/time popups,
     select dropdowns and scrollbars render dark on Chrome + Safari (July 2026). */
  color-scheme: dark;
  --purple:      #7230A5;
  --purple-dim:  rgba(114, 48, 165, 0.12);
  --blue:        #3DB7E4;
  --bg:          #0e0e12;
  --surface:     #18181f;
  --surface-2:   #1f1f2a;
  --border:      #2a2a38;
  --border-2:    #35354a;
  /* ☠ A GRID LINE IS NOT A BORDER (Sir Tim, 23 Aug: the calendar's day lines
     "are not easy to see" in dark). `--border` measures 1.36:1 against the
     month grid's field and `--border-2` only 1.61:1 — both are edges around a
     surface, drawn to be felt rather than read. A line that DIVIDES DATA is
     structure: it is the thing that tells Tuesday from Wednesday, and WCAG
     1.4.11 asks 3:1 of a meaningful boundary. Measured, not guessed —
     `tools/prove-grid-lines.mjs` reports the ratio in both themes. */
  --grid-line:   #5f5f76;
  --text:        #f0eef8;
  --text-muted:  #a6a3bb;
  --text-dim:    #84819b;   /* dim tier bumped to 4.7:1 (certifier, 31 July pm) */
  --green:       #2ecc71;
  --red:         #e74c3c;
  --amber:       #e4963d;
}
/* Print guard: never let the dark scheme reach paper — form controls and
   native chrome print light regardless of the screen scheme (July 2026). */
@media print { :root { color-scheme: light; } }
/* ── Keyboard focus canon (31 July 2026, Sir Tim's commission) ───────────────
   One visible ring, both themes: blue indicates — including where your
   keyboard is. :focus-visible only, so mouse clicks stay ring-free; the
   inputs' existing :focus border-swaps are their own feedback and their
   higher-specificity outline suppressions deliberately still win there. */
:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }

/* ── House icons (SOLID hand — Sir Tim's ruling, 1 Aug 2026) ──────────────
   Filled Material-dialect glyphs in currentColor; sized to sit in text. */
.aa-ic { width: 1.1em; height: 1.1em; fill: currentColor; display: inline-block;
         vertical-align: -0.18em; flex: 0 0 auto; }


/* ── LIGHT THEME (30 July 2026 — Sitting 1 of the light-mode campaign) ──────
   Applied when common/theme.js sets html[data-theme="light"]. The default is
   Follow-system: theme.js resolves the preference ('system'|'light'|'dark',
   stored per-user in portal_staff.theme with localStorage as the flash-free
   cache) to an effective 'light' or 'dark' attribute before first paint.
   Brand purple survives both worlds; brand blue darkens here because #3DB7E4
   is 2.2:1 on white — unreadable as text. Do not add colours to modules as
   raw hex without asking how they read under this block (see Rule 26). */
html[data-theme="light"] {
  color-scheme: light;
  --purple-dim:  rgba(114, 48, 165, 0.13);
  --blue:        #0c74a4;   /* deepened 31 July pm (4.57 on blue tint, 5.6 on white) */
  --bg:          #f5fafd;
  --surface:     #ffffff;
  --surface-2:   #edf6fa;
  --border:      #dbedf6;
  --border-2:    #c6e4f1;
  --grid-line:   #789fb4;   /* the Light Temper's own blue, at grid strength */
  --text:        #1a1a1e;
  --text-muted:  #585861;
  --text-dim:    #6e6e78;
  --green:       #116e3a;   /* deepened 31 July pm — certifier arithmetic: ≥4.5 on own tints */
  --red:         #b3273b;
  --amber:       #7a5800;
  --chip-purple-bg: rgba(114, 48, 165, 0.13);
  --chip-purple-bd: rgba(114, 48, 165, 0.55);
  --chip-blue-bg:   rgba(61, 183, 228, 0.16);
  --chip-blue-bd:   rgba(61, 183, 228, 0.55);
}

/* ── BASE ────────────────────────────────────────────────── */
html, body {
  /* min-height, NOT height (21 Aug): a sticky child sticks only within its
     parent's box — height:100% capped that box at one viewport, so headers
     abandoned ship a screenful down. min-height lets the body grow with the
     document and the sticky range with it. */
  min-height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
}

/* ── TOPBAR ──────────────────────────────────────────────── */
/* ═══ THE HEADER CANON (Sir Tim's rulings, 21 Aug 2026): ONE topbar behaviour
   estate-wide. (1) STICKY everywhere, both platforms — back link and title
   always in reach; stickiness lives HERE and nowhere page-local (heights stay
   page-local: sidebars and table headers hang their own sticky offsets off
   them). (2) The staff NAME is retired from every topbar. (3) Sign out
   compacts to an icon-only button at ≤768px (class topbar-signout). Only the
   topbar floats; module toolbars scroll with the page. ═══ */
.topbar {
  grid-column: 1 / -1;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 1.5rem;
  gap: 1rem;
  position: sticky;
  top: 0;
  z-index: 50;
  min-height: 56px;
}
/* The title's size, weight and tracking live with .page-title below — ONE
   voice for the module name whether the page declares it (.page-title) or
   topbar.js builds it (.topbar-title). A base here would only fight it. */
/* ONE Sign out, every page (his report, 21 Aug): the pill below outranks the
   page-local .logout-btn rules that load after common (0,2,0 beats 0,1,0). */
.topbar .topbar-signout { display: inline-flex; align-items: center; background: var(--surface-2); color: var(--text-muted); border: 1px solid var(--border); border-radius: var(--r-ctl); padding: .3rem .6rem; font-size: .78rem; text-decoration: none; }
.topbar .topbar-signout:hover { color: var(--text); }
@media (max-width: 768px) {
  /* The grey box must GROW around wrapped content — fixed page-local heights
     let chips and buttons float outside the bar on iOS (his screenshots,
     21 Aug). !important is the canon defending itself against later-loaded
     page styles; heights stay fixed on desktop where nothing wraps. */
  /* ☠ THE HEADER CANON SAYS ONE ROW, AND THE STYLESHEET STILL WRAPPED (Sir Tim,
     23 Aug, from his iPhone: "Case labels header has got a bit lost. Light dark
     button should be top right."). `flex-wrap: wrap` sent `.topbar-right` — the
     theme chip's own seat — onto a second row on six pages, leaving the module
     name stranded in the middle of a three-deep header. The 31 July two-row
     ruling was superseded on 21 August; this is the CSS catching up.
     Nothing wraps now: the back link TRUNCATES instead, because a job name out
     of Current is arbitrarily long and it is the one thing here that can afford
     an ellipsis. `min-width:0` at every level or the flex children refuse to
     shrink below min-content (trap 44). `prove-headers.mjs` measures it. */
  /* ☠ AND THE ONE-ROW RULE IS NOT FOR EVERY HEADER (Sir Tim, 23 Aug: "Calendar
     and device library... are different things and shouldn't have the overall
     rule applied to them"). Those two headers CARRY A TOOLBAR — the Calendar's
     view tabs, Transport and Crew ride in the bar by design and have since
     7 July. A rule written for CHROME must not govern a WORKSPACE.
     The exemption is DECLARED, never taken by a page quietly re-declaring
     `flex-wrap` after common — which is exactly how four pages had drifted. A
     header that carries tools says so with `.topbar-tools`, and
     `prove-headers.mjs` reports it as exempt BY NAME rather than passing it
     silently or failing it: an exemption nobody can see is a fault in waiting. */
  .topbar { height: auto !important; row-gap: .2rem; padding-top: .35rem; padding-bottom: .35rem; }
  .topbar:not(.topbar-tools) { flex-wrap: nowrap; }
  .topbar:not(.topbar-tools) > * { min-width: 0; }
  .topbar:not(.topbar-tools) .topbar-right { flex: 0 0 auto; margin-left: auto; }
  /* icon-only on mobile — !important defends against later-loaded page rules
     and common's own .btn-sm, both of which were re-showing the text. */
  /* Height mirrors the theme chip exactly (its .aa-theme-btn: 5px padding,
     .85rem line-height 1, 1px border ≈ 25.6px) — his eye, 21 Aug: the two
     must stand level. Icon 13.6px = the chip's own text height; 6px pad. */
  .topbar .topbar-signout { font-size: 0 !important; line-height: 0 !important; padding: 5px 9px !important; border-radius: 8px; }
  .topbar .topbar-signout::before {
    content: ''; display: inline-block; width: 13.6px; height: 13.6px; background: currentColor;
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M10 3H4v18h6' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M15 7l5 5-5 5M20 12H9' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M10 3H4v18h6' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M15 7l5 5-5 5M20 12H9' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  }
}
.topbar-left  { display: flex; align-items: center; gap: 1rem; min-width: 0; }
.topbar-crumb { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .82rem; color: var(--text-muted, var(--muted, #9690a8)); }
/* ═══ THE PDF BUTTON CANON (Sir Tim's three rulings, 21 Aug 2026): every PDF
   button across the estate wears ONE dress — the patch-sheet's brand-blue
   outline — says ONE word, "PDF", and carries ONE glyph (a page, drawn here
   once as a mask so no module needs a sprite edit). btn-sm rides along in
   dense toolbars; tooltips keep each button's fuller story. The buttons OPEN
   (AAPDF.openPdf, check 19) — no download verbs, no download arrows. ═══ */
.btn-pdf { color: var(--blue); border-color: var(--blue-border, rgba(61,183,228,.45)); }
.btn-pdf:hover { background: var(--blue-dim, rgba(61,183,228,.12)); }
.btn-pdf::before {
  content: ''; display: inline-block; width: .95em; height: .95em;
  margin-right: .4em; vertical-align: -.12em; background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 2h9l5 5v15H6z' fill='none' stroke='black' stroke-width='2.4' stroke-linejoin='round'/%3E%3Cpath d='M15 2v5h5' fill='none' stroke='black' stroke-width='2.4' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 2h9l5 5v15H6z' fill='none' stroke='black' stroke-width='2.4' stroke-linejoin='round'/%3E%3Cpath d='M15 2v5h5' fill='none' stroke='black' stroke-width='2.4' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* ═══ AN ICON textContent CANNOT ERASE (23 Aug 2026) ═══
   ☠ Sir Tim pressed RF Builder's Sync: the icon showed, the button said "Syncing…",
   and the icon vanished. An inline <svg> is a CHILD NODE, so any `.textContent =`
   wipes it — and a button whose LABEL changes at runtime is exactly the button that
   does that. Check 23 caught the one-step form and missed this one, because the code
   holds the button in a variable first; chasing the dataflow is chasing the symptom.
   So these buttons carry no child icon at all. The glyph is a ::before MASK, drawn
   from the very same path as the sprite symbol (generated from common/icons.js, so
   the hand cannot drift), and nothing in the DOM can remove it. This is the
   .btn-pdf pattern (21 Aug) generalised — the cure the estate already owned.
   USE THIS on any button whose label is rewritten while the page runs. */
.btn-ic::before {
  content: ''; display: inline-block; width: 1.05em; height: 1.05em;
  margin-right: .4em; vertical-align: -.18em; background: currentColor;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: contain;   mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
.btn-ic--mail::before { -webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4-8 5-8-5V6l8 5 8-5v2z'/></svg>"); mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4-8 5-8-5V6l8 5 8-5v2z'/></svg>"); }
.btn-ic--upload::before { -webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M9 16h6v-6h4l-7-7-7 7h4v6zM5 18v2h14v-2H5z'/></svg>"); mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M9 16h6v-6h4l-7-7-7 7h4v6zM5 18v2h14v-2H5z'/></svg>"); }
.btn-ic--refresh::before { -webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.65 6.35A7.96 7.96 0 0 0 12 4c-4.42 0-7.99 3.58-8 8s3.58 8 8 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z'/></svg>"); mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.65 6.35A7.96 7.96 0 0 0 12 4c-4.42 0-7.99 3.58-8 8s3.58 8 8 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z'/></svg>"); }
.btn-ic--bell::before { -webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z'/></svg>"); mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z'/></svg>"); }
.btn-ic--grid::before { -webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M3 3h8v8H3V3zm10 0h8v8h-8V3zM3 13h8v8H3v-8zm10 0h8v8h-8v-8z'/></svg>"); mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M3 3h8v8H3V3zm10 0h8v8h-8V3zM3 13h8v8H3v-8zm10 0h8v8h-8v-8z'/></svg>"); }
.btn-ic--chevron-up::before { -webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z'/></svg>"); mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z'/></svg>"); }
.btn-ic--chevron-down::before { -webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M16.59 8.59 12 13.17 7.41 8.59 6 10l6 6 6-6z'/></svg>"); mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M16.59 8.59 12 13.17 7.41 8.59 6 10l6 6 6-6z'/></svg>"); }
.btn-ic--close::before { -webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/></svg>"); mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/></svg>"); }
.btn-ic--check::before { -webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/></svg>"); mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/></svg>"); }

.topbar-data  { font-family: var(--font-mono); font-size: .78rem; color: var(--text-dim, var(--muted, #9690a8)); white-space: nowrap; }
/* Canon voices (4 July 2026): names/prose = DM Sans (.page-title, .topbar-crumb); data values (numbers, dates) = DM Mono (.topbar-data); transient statuses = sans .78 muted. */
.topbar .back-btn { flex-shrink: 0; white-space: nowrap; }
.topbar-right { display: flex; align-items: center; gap: 0.75rem; }
/* ☠ MEASURED AT 79x15 ON EVERY STAFF PAGE (23 Aug 2026, first Chromium render of
   portal-main). A back link is the one control a reader reaches for when they are
   lost, and fifteen pixels of it is not a target — it is a dare. The crew portal
   was brought to 44px on 22 Aug; the staff portal had never been rendered at all,
   so nothing had ever measured this. min-height, never height: the label must still
   grow if it wraps. Negative inline margin keeps the OPTICAL position unchanged —
   the box grows, the text does not move. */
.back-btn {
  color: var(--text-muted);
  text-decoration: none;
  font-size: 0.82rem;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 44px;
  padding: 0 0.5rem;
  margin: 0 -0.5rem;
  transition: color 0.15s;
}
.back-btn:hover { color: var(--text); }
.logo-link {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  transition: opacity 0.15s;
}
.logo-link:hover { opacity: 0.8; }
/* THE MODULE NAME'S ONE VOICE (Sir Tim's eye, 22 Aug): Case Labels — the
   topbar.js pilot — read SMALLER than every other module, because the built
   title carried its own .95rem with no tracking while declared titles carry
   1rem/-0.2px. Measured in Chromium: 15.2px against 16px. Both classes now
   share this rule, so a migrated page and an unmigrated one are the same
   header to the eye. */
.page-title, .topbar-title { font-size: 1rem; font-weight: 600; letter-spacing: -0.2px; }

/* ── SAVE STATUS ─────────────────────────────────────────── */
.save-status {
  font-size: 0.75rem;
  color: var(--text-dim);
  font-family: var(--font-mono);
  min-width: 80px;
  text-align: right;
}

/* ── SIDEBAR ─────────────────────────────────────────────── */
.sidebar {
  background: var(--surface);
  border-right: 1px solid var(--border);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}
.sidebar-section { padding: 1.25rem; border-bottom: 1px solid var(--border); }
.sidebar-label {
  font-size: 0.65rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-dim);
  margin-bottom: 0.75rem;
  font-family: var(--font-mono);
}
.sidebar-link {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 1rem;
  color: var(--text-muted);
  text-decoration: none;
  font-size: 0.85rem;
  transition: color 0.12s, background 0.12s;
  border-left: 2px solid transparent;
  cursor: pointer;
}
.sidebar-link:hover  { color: var(--text); background: var(--surface-2); }
.sidebar-link.active { color: var(--text); border-left-color: var(--purple); background: var(--purple-dim); }

/* ── BUTTONS ─────────────────────────────────────────────── */
.btn {
  text-decoration: none;  /* anchors wearing .btn are buttons, not links (2 Aug audit) */
  padding: 0.5rem 1rem;
  border-radius:var(--r-ctl);
  font-size: 0.85rem;
  font-weight: 500;
  font-family: var(--font-sans);
  cursor: pointer;
  border: none;
  /* Explicit base paint (10 July 2026): the base previously declared NO
     background/color and silently leaned on the browser's default buttonface —
     which color-scheme:dark flipped from light to UA-grey overnight (Sir Tim
     caught it on the Comms toolbar). Bare .btn / .btn.btn-sm (127 portal-wide)
     now wear the theme explicitly; variants below override and are unchanged. */
  background: var(--surface-2);
  color: var(--text);
  transition: opacity 0.15s, transform 0.1s;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}
.btn:hover  { opacity: 0.88; }
.btn:active { transform: scale(0.97); }
.btn-primary   { background: var(--purple); color: #fff; }
.btn-secondary { background: var(--surface-2); color: var(--text-muted); border: 1px solid var(--border); }
.btn-danger    { background: transparent; color: var(--danger, var(--red, #e05252)); border: 1px solid var(--danger, var(--red, #e05252)); }
.btn-danger:hover { background: rgba(224,82,82,.12); }
/* ↑ converted to the canonical RF-style outline (4 July 2026): ✕ prefix + verbose label naming the victim. */
.btn-success   { background: rgba(46,204,113,0.15); color: var(--green); border: 1px solid rgba(46,204,113,0.3); }
.btn-sm        { padding: 0.3rem 0.6rem; font-size: 0.78rem; border-radius:var(--r-ctl); }

/* ── FORM FIELDS ─────────────────────────────────────────── */
.field { margin-bottom: 0.875rem; }
.field label {
  display: block;
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--text-muted);
  margin-bottom: 0.3rem;
}
.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 0.5rem 0.75rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius:var(--r-ctl);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 0.85rem;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.field input:focus,
.field select:focus,
.field textarea:focus { outline: none; border-color: var(--purple); box-shadow: 0 0 0 3px rgba(114,48,165,0.12); }
.field textarea { resize: vertical; min-height: 60px; }

/* ═══ A NOTES BOX YOU CAN SEE INTO (Sir Tim, 13 Aug) ═══
   "Notes box needs to expand when pressed on."
   60px is two lines. On a phone with the keyboard up that is barely one line of
   visible text, so you type loading instructions into a slot and cannot read back
   what you have written.
   CSS-ONLY, DELIBERATELY. An auto-growing textarea driven by JavaScript needs a
   handler on input, on load, on paste and after every re-render — and this leg body
   is rebuilt from a template string on every keystroke elsewhere in the card, so a
   bound handler would be lost precisely when it mattered. :focus-within needs
   nothing bound and cannot go missing. (Safari 10.1+, so no concern.)
   IT GROWS, NEVER SHRINKS BELOW WHAT IS ALREADY THERE: `resize: vertical` is
   preserved, so a box dragged taller by hand stays where it was put. */
.field textarea:focus, .leg-field textarea:focus, .booking-field textarea:focus { min-height: 160px; }
.field textarea, .leg-field textarea, .booking-field textarea { transition: min-height 0.15s ease; }
@media (max-width: 768px) {
  /* Taller still on a phone, where the keyboard takes half the screen and the
     surrounding page can scroll to keep the box in view. */
  .field textarea:focus, .leg-field textarea:focus, .booking-field textarea:focus { min-height: 180px; }
}
.field select option { background: var(--surface-2); }

/* ── JOB SEARCH ──────────────────────────────────────────── */
.search-wrap { position: relative; }
.search-results {
  position: absolute;
  top: calc(100% + 4px);
  left: 0; right: 0;
  background: var(--surface-2);
  border: 1px solid var(--border-2);
  border-radius:var(--r-ctl);
  z-index: 50;
  max-height: 200px;
  overflow-y: auto;
  display: none;
}
.search-results.open { display: block; }
.search-result {
  padding: 0.6rem 0.75rem;
  cursor: pointer;
  font-size: 0.83rem;
  transition: background 0.1s;
  border-bottom: 1px solid var(--border);
}
.search-result:last-child { border-bottom: none; }
.search-result:hover { background: var(--border); }
.search-result .sr-name { font-weight: 500; }
.search-result .sr-meta {
  font-size: 0.73rem;
  color: var(--text-dim);
  font-family: var(--font-mono);
  margin-top: 1px;
}

/* ── LOADED JOB INDICATOR ────────────────────────────────── */
.loaded-job {
  background: var(--purple-dim);
  border: 1px solid rgba(114,48,165,0.3);
  border-radius: 8px;
  padding: 0.75rem;
  font-size: 0.83rem;
  display: none;
}
.loaded-job.show { display: block; }
.loaded-job .job-name { font-weight: 600; margin-bottom: 0.2rem; }
.loaded-job .job-meta {
  font-size: 0.73rem;
  color: var(--text-muted);
  font-family: var(--font-mono);
}

/* ── LOCATION SEARCH ─────────────────────────────────────── */
.leg-location-wrap { position: relative; }
.leg-location-results {
  position: absolute;
  top: calc(100% + 4px);
  left: 0; right: 0;
  background: var(--surface-2);
  border: 1px solid var(--border-2);
  border-radius: 8px;
  z-index: 50;
  max-height: 180px;
  overflow-y: auto;
  display: none;
  box-shadow: 0 4px 16px rgba(0,0,0,0.3);
}
.leg-location-results.open { display: block; }
.leg-loc-result {
  padding: 0.55rem 0.75rem;
  cursor: pointer;
  font-size: 0.83rem;
  border-bottom: 1px solid var(--border);
  transition: background 0.1s;
}
.leg-loc-result:last-child { border-bottom: none; }
.leg-loc-result:hover { background: var(--border); }
.leg-loc-result .lr-name { font-weight: 500; }
.leg-loc-result .lr-addr { font-size: 0.72rem; color: var(--text-dim); margin-top: 1px; }
.leg-loc-result .lr-type {
  font-size: 0.68rem;
  color: var(--text-dim);
  font-family: var(--font-mono);
  float: right;
}

/* ── W3W ─────────────────────────────────────────────────── */
.w3w-input-wrap { display: flex; gap: 0.5rem; align-items: center; }
.w3w-input-wrap input { flex: 1; border-color: #e11f26 !important; }
.w3w-badge { font-size: 0.78rem; color: #e11f26; font-weight: 700; white-space: nowrap; }

/* W3W map modal */
.w3w-map-modal {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.75);
  z-index: 300;
  display: flex; align-items: center; justify-content: center;
  padding: 1.5rem;
  opacity: 0; pointer-events: none;
  transition: opacity 0.2s;
}
.w3w-map-modal.open { opacity: 1; pointer-events: all; }
.w3w-map-inner {
  background: var(--surface);
  border: 1px solid var(--border-2);
  border-radius:var(--r-card);
  width: 100%; max-width: 680px;
  overflow: hidden;
  display: flex; flex-direction: column;
  transform: translateY(12px);
  transition: transform 0.2s;
}
.w3w-map-modal.open .w3w-map-inner { transform: translateY(0); }
.w3w-map-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--border);
}
.w3w-map-header h3 { font-size: 0.95rem; font-weight: 600; display: flex; align-items: center; gap: 0.5rem; }
.w3w-map-header h3 span { color: #e11f26; font-weight: 700; }
.w3w-map-component-wrap { height: 420px; position: relative; }
.w3w-map-footer {
  padding: 0.875rem 1.25rem;
  border-top: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
}
.w3w-selected-display {
  font-size: 0.88rem;
  color: #e11f26;
  font-weight: 700;
  font-family: var(--font-mono);
}
.w3w-selected-display.empty {
  color: var(--text-dim);
  font-weight: 400;
  font-family: var(--font-sans);
  font-size: 0.83rem;
}

/* ── MODALS ──────────────────────────────────────────────── */
.modal-overlay {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.7);
  z-index: 200;
  display: flex; align-items: center; justify-content: center;
  padding: 1.5rem;
  opacity: 0; pointer-events: none;
  transition: opacity 0.2s;
}
.modal-overlay.open { opacity: 1; pointer-events: all; }
.modal {
  background: var(--surface);
  border: 1px solid var(--border-2);
  border-radius:var(--r-card);
  width: 100%; max-width: 700px; max-height: 90vh;
  display: flex; flex-direction: column;
  transform: translateY(12px);
  transition: transform 0.2s;
}
.modal-overlay.open .modal { transform: translateY(0); }
.modal-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 1.25rem 1.5rem;
  border-bottom: 1px solid var(--border);
}
.modal-header h2 { font-size: 1rem; font-weight: 600; }
.modal-body { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 1.5rem; }
/* ☠ `fr` IS A SUGGESTION UNTIL YOU ALLOW SHRINKING (Sir Tim, 10 Aug — Block Assign
   "overruns… desk channel number and preview").
   A grid or flex item's automatic minimum size is MIN-CONTENT, and a <select>
   reports its min-content as its LONGEST OPTION — "Y002-Yamaha-DSP-R10-Monitors
   (144 RX)". So a `2fr 1fr 1fr auto` row refused to shrink, grew past the modal,
   pushed the last column off the edge and gave the modal a horizontal scrollbar.
   The preview was not overrunning at all; it was being cut by the scrolled modal.
   SHARED, because `.modal-body` is shared and nine other pages carry grids with the
   same latent fault. SAFE, because `min-width:auto` only has special meaning for
   flex and grid items and computes to 0 for ordinary block boxes — so this changes
   nothing anywhere it is not needed. */
.modal-body div { min-width: 0; }
.modal-body select, .modal-body input { min-width: 0; max-width: 100%; }
.modal-body select { text-overflow: ellipsis; }
.modal-footer {
  display: flex; gap: 0.75rem;
  padding: 1rem 1.5rem;
  border-top: 1px solid var(--border);
}

/* ── MANUAL JOB MODAL ────────────────────────────────────── */
#manual-job-modal .modal { max-width: 480px; }
#mj-existing { margin-top: 1rem; }

/* ── CONTACT SEARCH MODAL ────────────────────────────────── */
#contact-search-modal .modal { max-width: 480px; }

/* ── TOAST ───────────────────────────────────────────────── */
.toast {
  position: fixed;
  bottom: 1.5rem; right: 1.5rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.75rem 1.25rem;
  font-size: 0.88rem;
  color: var(--text);
  box-shadow: 0 4px 16px rgba(0,0,0,0.4);
  z-index: 999;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.2s, transform 0.2s;
  pointer-events: none;
}
.toast.show    { opacity: 1; transform: translateY(0); }
.toast.success { border-left: 3px solid var(--green); }
.toast.error   { border-left: 3px solid var(--red); }

/* ── DRAG HANDLE ─────────────────────────────────────────── */
.drag-handle {
  cursor: grab;
  color: var(--text-dim);
  padding: 0 0.25rem;
  font-size: 1rem;
  user-select: none;
}
.drag-handle:active { cursor: grabbing; }

/* ── ▲▼ REORDER BUTTONS (6 Aug 2026) ──────────────────────
   The touch companion to .drag-handle. HTML5 drag-and-drop has no touch support on
   iOS at all, so every ⠿ grip is dead on a phone; for LISTS, tapping is the better
   gesture anyway. Neutral by rule — a reorder is a peer action, not a primary, and
   colour is reserved for primacy and indication. Targets are 28px on a mouse and
   grow to 36px on coarse pointers where a fingertip needs the room. Tokens only,
   so light mode follows for free.
   ON DESKTOP THE GRIP STILL DRAGS — these are purely additive. */
.reorder-btns { display: inline-flex; flex-direction: column; gap: 2px; flex-shrink: 0; }
.reorder-btn {
  padding: 0; width: 28px; height: 14px; line-height: 1;
  font-size: 0.6rem; border-radius: var(--r-chip);
  color: var(--text-muted); background: var(--surface-2);
  border: 1px solid var(--border);
  display: flex; align-items: center; justify-content: center;
}
.reorder-btn:hover { color: var(--text); border-color: var(--border-2); }
.reorder-btn:disabled { opacity: .35; cursor: not-allowed; }
@media (pointer: coarse) {
  .reorder-btn { width: 36px; height: 18px; font-size: 0.7rem; }
}

/* ── TIME CRITICAL / FLAG ROW ────────────────────────────── */
.time-critical-row { display: flex; align-items: center; gap: 0.6rem; padding: 0.6rem 0.75rem; background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; cursor: pointer; }
.time-critical-row input[type=checkbox] { width: 16px; height: 16px; accent-color: var(--red); cursor: pointer; flex-shrink: 0; }
.time-critical-row label { font-size: 0.85rem; font-weight: 500; color: var(--text-muted); cursor: pointer; }
.time-critical-row.checked { border-color: var(--red); background: rgba(231,76,60,0.08); }
.time-critical-row.checked label { color: var(--red); }

/* ── EMPTY STATE ─────────────────────────────────────────── */
.empty {
  padding: 2rem;
  text-align: center;
  color: var(--text-dim);
  font-size: 0.88rem;
}

/* ── CROSS-BROWSER SELECT ────────────────────────────────── */
/* Suppresses Safari/iOS native styling, applies consistent look */
/* Excludes compact inline selects (e.g. grid-ch-select) */
select:not(.grid-ch-select):not([size]):not([multiple]) {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23888'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.6rem center;
  background-size: 10px 6px;
  padding-right: 2rem;
}
/* Compact inline selects — just strip native appearance */
select.grid-ch-select {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
}

/* ── Crowned empty/landing state (builder redesign, July 2026) ──
   Icon + headline + one guiding sentence + primary CTA. Modelled on the
   crew-pack admin empty state. Used by builder landing panels. */
.aa-empty { display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; padding:2.5rem 1.5rem; }
.aa-empty .aa-empty-ico { font-size:2.6rem; opacity:.35; margin-bottom:.9rem; line-height:1; }
.aa-empty h3 { font-size:.95rem; font-weight:600; margin:0 0 6px; color:var(--text-muted, var(--muted, #9690a8)); }
.aa-empty p { font-size:.83rem; line-height:1.5; max-width:360px; margin:0 0 1.1rem; color:var(--text-dim, var(--muted, #9690a8)); }


/* ── Mobile strip canon (6 July 2026 — Sir Tim's phone exhibits).
     Long titles ellipsize instead of wrapping the strip into chaos;
     the crumb yields; the logo shrinks. Applies portal-wide. ── */
@media (max-width: 768px) {
  html, body { overflow-x: hidden; max-width: 100vw; }

  /* ═══ INPUTS MUST BE ALLOWED TO SHRINK (13 Aug) ═══
     `flex: 1` does not mean "shrink to fit": a flex or grid item keeps
     `min-width: auto`, which resolves to MIN-CONTENT — and an <input> reports about
     twenty characters. So a field refuses to go narrow, holds its panel wider than
     the phone, and with `overflow-x: hidden` directly above the surplus becomes
     UNREACHABLE rather than merely off-screen.
     ☠ AND THIS RULE WAS LOST ONCE ALREADY, HOURS AFTER BEING WRITTEN. Moving the
     contact-row block below its base rule, my removal span began at the contact-row
     comment and ran past this rule, which had been inserted just above it. The pages
     that carry their own scoped copies (transport-brief, local-crew) kept working, so
     nothing looked wrong — the loss was invisible until the live files were compared
     field by field. **A REMOVAL SPAN MUST BE ANCHORED ON BOTH ENDS OF THE THING BEING
     REMOVED, never on a neighbour.** */
  input, select, textarea { min-width: 0; max-width: 100%; }

  .shell, .main, .section-card, .section-body { min-width: 0; }
  .section-card, .section-body { max-width: 100%; overflow-x: hidden; }
  .main, .shell { max-width: 100%; }
  .main { overflow-x: hidden; }
  .topbar { padding: 0 0.75rem; gap: 0.5rem; }
  .topbar-left { gap: 0.5rem; flex: 1 1 0; min-width: 0; }
  .topbar-left img, .topbar > .logo-link img { display: none; }   /* Sir Tim's ruling: logo dropped on mobile (canonised 31 July 2026; job-calendar pioneered; second selector catches drawer-labels' bare topbar) */
  /* ── RETIRED 23 Aug 2026 — the two-row mobile topbar canon, v2 (31 July).
     It sent the job/opportunity CRUMB to a second row as the scroll anchor and
     deliberately UN-HID the crumbs several pages had suppressed. THE HEADER
     CANON of 21 August superseded it with ONE row, and on 23 August the CSS
     caught up — but with the crumb still un-hidden it simply trailed the theme
     chip along row one (Sir Tim's Drawer Labels screenshot: a title cut to
     "D..." and a job name running off the right edge). Hidden on a phone again:
     the BACK LINK already names the job, in the one place a reader looks in
     order to leave. Desktop untouched. ── */
  /* ── AND UN-HIDDEN, PROPERLY, 12 Sep 2026 (Sir Tim: "with a project of many opps, it
     might be useful to know which of the opps you are working on"). The 24 Aug rule
     below collapses the back label to an arrow, so since then NOTHING on a phone's row
     named the job or the opportunity. The crumb rides the row after the title — the
     title keeps its 8rem floor and the crumb takes what is left, ellipsised — never a
     second row, never past the chip. ── */
  .topbar:not(.topbar-tools) { flex-wrap: nowrap; }
  .topbar:not(.topbar-tools) .topbar-left { flex-wrap: nowrap; flex: 1 1 auto; min-width: 0; overflow: hidden; }
  /* The back link gives way first: `.topbar .back-btn` above sets
     `flex-shrink:0`, which is right on desktop and is what forces the wrap on a
     phone. Here it shrinks and ellipsises, and the module NAME keeps its size —
     the name is the page's identity, the back link is context. */
  /* ☠ 2.2rem IS 35px, AND THE TOUCH FLOOR IS 44 (Sir Tim, 24 Aug: "iOS crew pack on
     portal header is still a mess"). Measured on the crew-pack page at 390px, the
     back link rendered 55px of a 235px label — and its floor let it go to 35px, a
     target under the estate's own canon on EVERY migrated page, not just that one.
     The floor is the shared file's business; which LABEL a page can afford to drop
     is the page's. */
  .topbar:not(.topbar-tools) .back-btn {
    flex: 0 0 auto; min-width: 44px; min-height: 44px;
    display: inline-flex; align-items: center; justify-content: center;
    overflow: hidden;
    /* ☠ THE LABEL GIVES WAY ENTIRELY, NOT TO A STUMP (Sir Tim, 24 Aug: "iOS crew
       pack on portal header is still a mess"). Measured across the estate at 390
       and 360: TEN headers were rendering a back label at 29–48% of its content —
       "← Ignite - Legal Bar - Grosve", "← Ign" — hard against the title beside it.
       The rule above already ruled which of the two wins: "the name is the page's
       identity, the back link is context", and gave the title an 8rem floor. On a
       phone there is room for ONE of them, so context gives way — but a fragment
       of a job name is not context, it is wreckage. `font-size: 0` drops the label
       whatever a page put there (most set it with textContent, so there is no span
       to hide) and `::before` draws the arrow at a readable size. The full label
       returns above 768px, and it stays in the accessible name at every width. */
    font-size: 0;
  }
  .topbar:not(.topbar-tools) .back-btn::before { content: '←'; font-size: 1.15rem; line-height: 1; }
  .topbar:not(.topbar-tools) .topbar-crumb { display: block; flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .78rem; }
  /* The phone shrink applies to the built title too — without it the migrated
     pages read LARGER than the rest at ≤768 (15.2px against 14.08px, the same
     divergence the other way up). */
  /* ☠ AND THE NAME WAS STILL THE ONE THAT GAVE WAY (23 Aug, second look). Both
     the back link and the title were `flex: 0 1 auto`, so they shrank together
     and the longer neighbour won: "Drawer Labels" came out as "Drawer..." while
     a full job name sat beside it. A FLOOR is what expresses the priority — the
     name may ellipsise, but only after it has had 8rem, which fits every module
     name in the estate. Where the title IS a job name (the job hub) it still
     truncates past that floor, which is correct there. */
  .topbar:not(.topbar-tools) .page-title, .topbar:not(.topbar-tools) .topbar-title { white-space: nowrap !important; overflow: hidden !important; text-overflow: ellipsis !important; min-width: min(8rem, 42vw); flex: 0 1 auto; font-size: 0.88rem; }
  .topbar-tools .topbar-crumb { display: none; }   /* a tools bar has no room for context on a phone */
  .topbar .back-btn { font-size: 0.8rem; }
  /* Sir Tim's ruling (6 July): no logged-in avatar chip on mobile. */
  .topbar-right { flex-shrink: 0; gap: 0.4rem; }
  .topbar .user-chip { display: none !important; }
  .topbar .logout-btn { font-size: 0.72rem; padding: 0.25rem 0.5rem; white-space: nowrap; }
}

/* ─────────────────────────────────────────────────────────────────────────
   iOS focus-zoom fix (7 July 2026, Sir Tim) — the staff Home-Screen web app
   zoomed in whenever a form field was tapped, because iOS Safari/WebKit
   auto-zooms any input/textarea/select whose font-size is below 16px. Forcing
   16px on TOUCH devices (coarse pointer) stops the zoom at its root while
   leaving pinch-to-zoom fully intact (unlike a viewport lock). Desktop is
   untouched — the rule is gated to coarse pointers only. 16px is the exact
   threshold; we do not enlarge beyond it, so the visual change is minimal.
   ───────────────────────────────────────────────────────────────────────── */
@media (pointer: coarse) {
  input,
  textarea,
  select,
  .disc-composer textarea,
  .jr-compose textarea {
    font-size: 16px !important;
  }
}

/* ── BRIEF SEARCH DROPDOWNS + CONTACT ROWS (promoted from Transport Brief,
      10 July 2026 — shared by Transport Brief + Local Crew after Sir Tim's
      unification ruling). Includes first-ever styling for .leg-loc-result /
      .lr-* rows (contact + memory picks), which had NO CSS anywhere and were
      rendering bare; both class spellings now share one canonical look. ── */
.leg-location-wrap { position: relative; }
.leg-location-results { position: absolute; top: 100%; left: 0; right: 0; margin-top: 3px; background: var(--surface); border: 1px solid var(--border-2); border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,0.3); z-index: 100; max-height: 200px; overflow-y: auto; display: none; }
.leg-location-results.open { display: block; }
.leg-location-result, .leg-loc-result { padding: 0.6rem 0.75rem; cursor: pointer; border-bottom: 1px solid var(--border); font-size: 0.82rem; }
.leg-location-result:last-child, .leg-loc-result:last-child { border-bottom: none; }
.leg-location-result:hover, .leg-loc-result:hover { background: var(--surface-2); }
.lr-name { font-weight: 500; }
.lr-addr { font-size: 0.74rem; color: var(--text-muted); margin-top: 1px; }
.lr-type { display: inline-block; font-size: 0.6rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); background: var(--chip-blue-bg); border-radius: 4px; padding: 1px 5px; margin-bottom: 2px; }
.contact-row { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; }
.contact-row input { flex: 1; background: var(--surface); border: 1px solid var(--border); border-radius:var(--r-ctl); padding: 0.4rem 0.65rem; color: var(--text); font-family: var(--font-sans); font-size: 0.82rem; }
/* The Name input sits inside the wrap, so it is NOT a flex child — without
   this it collapses to intrinsic width (Sir Tim's screenshot, 10 July). */
.contact-row .leg-location-wrap input { width: 100%; }
.contact-row input:focus { outline: none; border-color: var(--purple); }

/* ═══ THE CONTACT ROW ON A PHONE (Sir Tim, 13 Aug) ═══
   ☠ I WROTE THIS RULE ONCE ALREADY AND IT NEVER APPLIED. It went into the mobile
   block at line 537 — ABOVE the base `.contact-row { display: flex }` at 644. Equal
   specificity, and the LATER rule wins regardless of the media query, so flex
   overrode grid at every width and the row stayed on one line. Sir Tim's screenshot
   showed it unchanged and I had reported it as done.
   A MEDIA QUERY DOES NOT RAISE SPECIFICITY. It only narrows when a rule applies; it
   does not decide which rule wins. An override must come AFTER what it overrides —
   which is why this now sits directly beneath the base rule rather than in the
   general mobile block far above it.
   FIVE CONTROLS, TWO ROWS: name and its picker on the first, role, phone and remove
   on the second. A grid rather than a wrap, because wrapping reflows unpredictably
   as a name grows. */
@media (max-width: 768px) {
  .contact-row { display: grid; grid-template-columns: 1fr auto auto; gap: 0.4rem; align-items: center; }
  .contact-row > * { min-width: 0; }
  .contact-row .leg-location-wrap { grid-column: 1 / 3; }
  .contact-row > .btn-secondary { grid-column: 3; }
  .contact-row > input[placeholder="Role"] { grid-column: 1; }
  .contact-row > input[type="tel"] { grid-column: 2; }
  .contact-row > .btn-danger { grid-column: 3; }
  .contact-row > input { max-width: none !important; }
}

/* light-temper topbar: AA blue ~3% (canon, 2 Aug) */
html[data-theme="light"] .topbar { background:#f7fbfd; }
