﻿/* AMA Client Tracker - the tracker's own stylesheet (HTML 7-240), ported token for token.
   The only additions are classes for markup the tracker wrote as inline styles (group
   headings, pills, note boxes, the month grid, the preview table) and the sign-in page. */

:root {
  --bg: #15181a;
  --panel: #1e2226;
  --panel-alt: #242a2e;
  --border: #33393e;
  --accent: #C2CAD1;
  --accent-dim: #9BA4AC;
  --yellow: #ffc300;
  --red: #e13438;
  --teal: #4fa893;
  /* WHO LOOKS AFTER SOMEBODY. Its own token rather than one of the four above, because each of
     those already means something: teal is "good" and "a job", yellow and red are how late
     something is, and the accent is "this opens". A rep is none of those - it is a person, and
     on a book where most contacts have nobody it wants to be findable by eye down a long list
     without reading as a warning. Close enough to --text-mute to sit in the same meta line,
     far enough to be picked out. */
  --rep: #8fa8c8;
  --text: #ece9e4;
  --text-mute: #A6AEB5;
  --text-mute2: #838C93;
  --mono: 'IBM Plex Mono', Consolas, monospace;
  --display: 'Oswald', 'Arial Narrow', sans-serif;
  --body: 'Inter', system-ui, sans-serif;
  /* The content column's width, shared with the environment ribbon so the two cannot
     drift apart: the ribbon is positioned against this column, not the viewport. */
  --wrap-max: 1100px;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; background: var(--bg); }
body {
  font-family: var(--body);
  font-weight: 600;
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
  padding: 0 0 80px 0;
}
.wrap { max-width: var(--wrap-max); margin: 0 auto; padding: 20px 16px; }
a { color: inherit; text-decoration: none; }

/* HEADER */
.hdr {
  display: flex; justify-content: space-between; align-items: flex-end;
  border-bottom: 3px solid var(--accent);
  padding-bottom: 14px; margin-bottom: 18px; flex-wrap: wrap; gap: 10px;
}
.hdr-logo { height: 44px; display: block; }
.hdr-right { text-align: right; }
.hdr-stamp { font-family: var(--mono); font-size: 11px; color: var(--text-mute2); font-weight: 600; text-align: right; }
/* THE VERSION, beside TODAY and at the foot of every page (ADR-0012). Quieter than anything a
   person works with: it is there to be read off a screenshot, not to be looked at. */
.hdr-version { color: var(--text-mute2); margin-left: 8px; font-weight: 600; }
.hdr-version:hover, .hdr-version:focus-visible { color: var(--text); text-decoration: underline; text-underline-offset: 3px; }
.site-foot { font-family: var(--mono); font-size: 11px; color: var(--text-mute2); text-align: center; margin-top: 24px; padding-top: 10px; border-top: 1px solid var(--border); }
.site-foot a { color: var(--text-mute2); text-decoration: underline; text-underline-offset: 3px; }
.site-foot a:hover, .site-foot a:focus-visible { color: var(--text); }
.whats-new-summary { font-weight: 600; margin-bottom: 8px; }
.whats-new-items { margin: 0; padding-left: 20px; line-height: 1.55; }
.whats-new-items li + li { margin-top: 6px; }
.link-btn { background: none; border: none; padding: 0; margin: 0; color: var(--accent); font: inherit; cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }

/* THE SHELL: THE LEFT MENU BESIDE THE PAGE (2026-10-29; ADR-0022, and SideMenu.razor for the three shapes).
   Until then the header carried the signed-in line - the name and up to fourteen small links across two
   lines for an Owner (the "admin strip", which had grown an item at a time since ADR-0005) - and the six
   screens were a strip of tabs under it. Now every screen is an item in the menu, one section per module,
   and the header keeps only what is about this moment and this person: TODAY and the version, the bell,
   and the person's own menu under their name.
   THE WIDTHS ARE THE PAGE'S, NOT THE MENU'S. The menu is fixed down the left and the page keeps its
   1100px column beside it; 1340 is 1100 and the open menu's 240, so the open menu is the default only
   where the column still fits whole. Below that the menu is a 64px rail, because every screen's own
   layout changes at a WINDOW width (five filter boxes become three at 1100, two at 900, one at 640) and
   a 240px menu pushing the page would squeeze each into a width it was never laid out for; the rail
   costs those screens 64px, which they have. Rejected: a menu that pushes the page at every width -
   it would need each screen's rules re-keyed to the column, a container query on every one of them,
   for a menu that is closed most of the time on a laptop anyway.
   Not by script: the shape is two classes on .shell the layout writes, so the first paint is right.
   The wider rules are written as ranges - (640px < width < 1340px) - rather than min-width 641 and
   max-width 1339, which leave a pixel between them that a fractional width (a zoomed page, a scaled
   display) falls into, drawing the open menu with no button to fold it. The phone's stay max-width 640,
   the form MainTabsOnAPhoneTests reads. */
.side-nav {
  position: fixed; top: 0; bottom: 0; left: 0; z-index: 120;
  width: 240px; display: flex; flex-direction: column;
  background: var(--panel); border-right: 1px solid var(--border);
  overflow-x: hidden; overflow-y: auto;
  container: side-nav / inline-size;
}
.shell-main { margin-left: 240px; }
@media (width >= 1340px) {
  .shell.railed .side-nav { width: 64px; }
  .shell.railed .shell-main { margin-left: 64px; }
}
@media (640px < width < 1340px) {
  .shell:not(.overlay-open) .side-nav { width: 64px; }
  .shell-main { margin-left: 64px; }
  .shell.overlay-open .side-nav { box-shadow: 8px 0 28px rgba(0, 0, 0, 0.55); }
}
/* A phone: a drawer, off the screen and out of the tab order until the header's button opens it. Visible AT ONCE
   when it opens and hidden only once it has slid away: a visibility that transitioned both ways is still hidden on
   the first frame of opening, which is when the menu puts focus on its close button - and focus on a hidden button
   silently goes nowhere (found running it, 2026-10-29). */
@media (max-width: 640px) {
  .side-nav { width: min(280px, 85vw); transform: translateX(-100%); visibility: hidden; transition: transform .18s ease, visibility 0s linear .18s; }
  .shell.overlay-open .side-nav { transform: none; visibility: visible; transition: transform .18s ease, visibility 0s; box-shadow: 8px 0 28px rgba(0, 0, 0, 0.55); }
  .shell-main { margin-left: 0; }
}
/* The backdrop is drawn whenever the menu is open over the page, at EVERY width (2026-11-02, the review of
   2026-10-30's WEB-5): the page is inert while it is open (MainLayout), and a page made inert with nothing over it
   is a dead one. Below 1340px that is the drawer's shade, as it always was. At 1340 and wider the menu is beside the
   page and is never opened over it - but one opened narrower stays open while the window widens past 1340, a
   tablet turned on its side, and a shade kept below 1340px, as it was until the page went inert, would have left
   that page inert under nothing. Drawn, a tap on it closes the menu there as anywhere. The rule outside every
   media block is held by MenuDrawerRenderTests. */
.side-backdrop { display: none; position: fixed; inset: 0; z-index: 110; background: rgba(0, 0, 0, 0.55); }
.shell.overlay-open .side-backdrop { display: block; }

.side-brand {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  min-height: 72px; padding: 14px 10px 14px 16px; border-bottom: 1px solid var(--border);
}
.side-home { display: block; min-width: 0; }
.side-logo { display: block; width: 100%; max-width: 168px; height: auto; }
.side-mark { display: none; width: 32px; height: 32px; }
.side-toggle {
  flex: none; display: none; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0; border-radius: 6px; cursor: pointer;
  background: transparent; border: 1px solid var(--border); color: var(--text-mute);
}
.side-toggle:hover, .side-toggle:focus-visible { color: var(--text); border-color: var(--accent-dim); }
@media (width >= 1340px) { .side-toggle-rail { display: flex; } }
@media (width < 1340px) { .side-toggle-over { display: flex; } }

.side-sections { flex: 1; padding: 10px 8px 24px; }
.side-section + .side-section { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border); }
.side-section-title {
  font-family: var(--mono); font-size: 10.5px; font-weight: 700; color: var(--text-mute2);
  text-transform: uppercase; letter-spacing: 1px; padding: 6px 10px 6px; white-space: nowrap;
}
/* The tabs' type, because these ARE the tabs: Oswald, uppercase, the lit one in the accent with dark ink. */
.side-link {
  position: relative; display: flex; align-items: center; gap: 12px; margin-top: 2px;
  padding: 9px 10px; border-radius: 6px; white-space: nowrap;
  font-family: var(--display); text-transform: uppercase; letter-spacing: 0.5px;
  font-size: 13.5px; font-weight: 700; color: var(--text-mute);
}
.side-link:hover { background: var(--panel-alt); color: var(--text); }
.side-link.active { background: var(--accent); color: #151515; }
.side-link:focus-visible { outline: 2px solid var(--accent-dim); outline-offset: 2px; }
.nav-icon { flex: none; display: block; }
.side-label { overflow: hidden; text-overflow: ellipsis; }
/* The count of new problem reports on the drawing, for the rail, where the label - which carries it - is not drawn. */
.side-count {
  display: none; position: absolute; top: 3px; right: 8px; min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: 9px; background: var(--yellow); color: #15181a;
  font-family: var(--mono); font-size: 10.5px; font-weight: 700; line-height: 17px; text-align: center; letter-spacing: 0;
}
/* THE RAIL, by the menu's own width rather than by the two media rules that make it narrow: one set of
   rules for both ways it gets there (folded on a wide window, or the default on a narrower one). */
@container side-nav (max-width: 120px) {
  .side-brand { flex-direction: column; justify-content: center; padding: 12px 0; gap: 10px; }
  .side-logo, .side-label, .side-section-title { display: none; }
  .side-mark { display: block; }
  .side-link { justify-content: center; padding: 10px 0; }
  .side-count { display: block; }
}

/* THE HEADER INSIDE THE SHELL: TODAY and the version on the left, the bell and the person's menu on the
   right. The wordmark heads the menu, so the header carries it only on a phone, where the menu is a
   drawer; the sign-in page's header (.login-card) is not inside the shell and keeps its logo. */
.shell .hdr { align-items: center; justify-content: flex-start; padding-bottom: 12px; }
.shell .hdr-logo { display: none; }
.shell .hdr-right { margin-right: auto; text-align: left; }
.shell .hdr-stamp { text-align: left; }
.hdr-menu {
  display: none; align-items: center; justify-content: center; flex: none;
  width: 40px; height: 40px; padding: 0; border-radius: 6px; cursor: pointer;
  background: transparent; border: 1px solid var(--border); color: var(--text);
}
.hdr-actions { display: flex; align-items: center; gap: 4px; }
.hdr-bell {
  position: relative; display: inline-flex; align-items: center; gap: 8px;
  min-height: 36px; padding: 6px 10px; border-radius: 6px;
  font-family: var(--mono); font-size: 11px; color: var(--text-mute);
}
.hdr-bell.has-new { color: var(--text); }
.hdr-bell:hover, .hdr-bell:focus-visible { background: var(--panel-alt); color: var(--text); }
/* The count on the drawing, for a window too narrow for the words; the words stay for a screen reader. */
.hdr-bell-count {
  display: none; position: absolute; top: 2px; right: 2px; min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: 9px; background: var(--yellow); color: #15181a;
  font-family: var(--mono); font-size: 10.5px; font-weight: 700; line-height: 17px; text-align: center;
}
@media (max-width: 899px) {
  .hdr-bell-text { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
  .hdr-bell-count { display: block; }
}
.user-menu { position: relative; }
.user-menu > summary {
  list-style: none; display: flex; align-items: center; gap: 8px; min-height: 36px;
  padding: 6px 10px; border-radius: 6px; cursor: pointer;
  font-family: var(--mono); font-size: 11px; font-weight: 700; color: var(--text);
}
.user-menu > summary::-webkit-details-marker { display: none; }
.user-menu > summary::after { content: ''; border: 4px solid transparent; border-top-color: currentColor; margin-top: 4px; }
.user-menu > summary:hover, .user-menu > summary:focus-visible, .user-menu[open] > summary { background: var(--panel-alt); }
.user-menu-items {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 105; min-width: 210px;
  display: flex; flex-direction: column; padding: 6px;
  background: var(--panel-alt); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
}
.user-menu-items a, .user-menu-btn {
  display: block; width: 100%; padding: 9px 10px; border-radius: 6px; text-align: left; cursor: pointer;
  background: none; border: none; font-family: var(--body); font-size: 13px; font-weight: 600; color: var(--text-mute);
}
.user-menu-items a:hover, .user-menu-items a:focus-visible, .user-menu-btn:hover, .user-menu-btn:focus-visible { background: var(--panel); color: var(--text); }
.user-menu-items form { margin: 4px 0 0; padding-top: 4px; border-top: 1px solid var(--border); }
/* A phone's header: the menu button, the wordmark, the bell and the person's drawing on one row, and
   TODAY under them; the name goes to a screen reader only, as the bell's words do. */
@media (max-width: 640px) {
  .hdr-menu { display: inline-flex; }
  .shell .hdr { gap: 8px; }
  .shell .hdr-logo { display: block; height: 30px; }
  .shell .hdr-right { order: 3; flex-basis: 100%; margin-right: 0; }
  .hdr-actions { margin-left: auto; }
  .user-name { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
}
/* The six tabs are the phone's: wider, the menu has them. Its own block, apart from the main strip's
   phone rule below, which MainTabsOnAPhoneTests holds to naming that strip and nothing else. */
.phone-tabs { display: none; }
@media (max-width: 640px) { .phone-tabs { display: block; } }

/* TABS */
.tabs {
  display: flex; gap: 4px; margin-bottom: 18px;
  background: var(--panel); border: 1px solid var(--border); border-radius: 8px;
  padding: 4px; overflow-x: auto;
}
.tab-btn {
  flex: 1; min-width: 90px;
  font-family: var(--display);
  text-transform: uppercase; letter-spacing: 0.5px;
  font-size: 12.5px; font-weight: 700;
  background: transparent; border: none; color: var(--text-mute);
  padding: 10px 8px; border-radius: 6px; cursor: pointer;
  transition: all .15s; white-space: nowrap; text-align: center;
}
.tab-btn.active { background: var(--accent); color: #151515; font-weight: 700; }
/* Tabs that carry a count (Contacts' Everyone / Customers / Prospects): on a phone "PROSPECTS
   (1271)" is wider than a third of the screen, so the label wraps under itself rather than the
   strip scrolling a tab out of sight. */
@media (max-width: 640px) { .tabs.counted .tab-btn { white-space: normal; min-width: 0; line-height: 1.25; padding: 8px 4px; } }
/* THE SIX MAIN TABS ON A PHONE: TWO ROWS OF THREE (Mike, 2026-10-23; the cleanup register's [1]).
   One row of six never fitted a phone, by arithmetic: at 375 wide the page's column is 343px
   (.wrap's 16px each side) and the strip's inside 333px (its 1px border and 4px padding each side),
   while six tabs at .tab-btn's min-width of 90px and five 4px gaps want 560px. So the strip showed
   three and a half tabs and scrolled the rest - Contacts and To-Dos behind a sideways swipe nobody
   is told about, and after a full page load the tab that was lit could be one of them. Now three
   equal columns, all six on screen and none scrolled: (333 - 2 x 4) / 3 is 108px a tab at 375, and
   TOUCHPOINTS, the longest label, is under 85px; 4px side padding keeps it on its line in the 90px a
   320-wide phone leaves. The 40px floor keeps every tab one a thumb can find, and the strip grows
   by about 45px (two 40px rows and a gap, for one of 38), once, at the top of the page.
   A grid, not the flex-wrap the register sketched: three columns is what grid-template-columns
   says, where a wrapped flex row says it through a basis of calc((100% - 8px) / 3) that has to sum
   with the gaps to the strip's width, and goes on summing only while nobody changes the gap.
   Keyed on .main-tabs, which only MainLayout's strip carries: Contacts' counted tabs have their own
   rule above, and the Dashboard's and Rep View's three-tab strips fit a phone as they are (three
   at 90px and two gaps is 278px, inside the 299 a panel's strip has at 375).
   Rejected: one scrolling row that opens at the active tab - the lit tab would be found, but the
   three either side of it would still be a swipe away, and it takes script on every navigation
   for what a stylesheet can lay out. MainTabsOnAPhoneTests pins the rule and the markup. */
@media (max-width: 640px) {
  .tabs.main-tabs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); overflow-x: visible; }
  .tabs.main-tabs .tab-btn { display: flex; align-items: center; justify-content: center; min-width: 0; min-height: 40px; padding: 8px 4px; white-space: normal; line-height: 1.25; }
}
.tab-btn:not(.active):hover { background: var(--panel-alt); color: var(--text); }

/* STATS BAR */
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-bottom: 20px; }
.stats.three { grid-template-columns: repeat(3, 1fr); }
.stat-card { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px; }
.stat-num { font-family: var(--mono); font-size: 26px; font-weight: 700; }
.stat-label { font-size: 11px; color: var(--text-mute); text-transform: uppercase; letter-spacing: 0.5px; margin-top: 3px; font-weight: 700; }
.stat-card.crit .stat-num { color: var(--red); }
.stat-card.warn .stat-num { color: var(--yellow); }
.stat-card.good .stat-num { color: var(--teal); }
/* A card you can press (To-Dos' Overdue, 2026-09-29): a real <button>, drawn as the card beside it -
   the browser's button font, colour and centring put back to the card's. A button holds phrasing
   content only, so its number and word are spans, drawn here as the blocks the other cards' divs
   are. .active marks the filter it has set, with the border a card already has - a class, not
   aria-pressed, because pressing it again does not unset it (Todos.razor says why). */
.stat-card-button { font: inherit; color: inherit; text-align: left; width: 100%; cursor: pointer; }
.stat-card-button .stat-num, .stat-card-button .stat-label { display: block; }
.stat-card-button:hover, .stat-card-button.active { border-color: var(--red); }
.stat-card-button:focus-visible { outline: 2px solid var(--accent-dim); outline-offset: 3px; }
@media (max-width: 640px) { .stats, .stats.three { grid-template-columns: repeat(2, 1fr); } }

/* PANEL / CARD */
.panel { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 16px; margin-bottom: 16px; }
.panel.accent { border-color: var(--accent); padding: 12px 16px; }
.panel-title {
  font-family: var(--display); text-transform: uppercase; letter-spacing: 0.5px;
  font-size: 14px; font-weight: 700; color: var(--text); margin-bottom: 12px;
  display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap;
}
.panel-title .hint { font-family: var(--body); font-weight: 600; font-size: 11.5px; color: var(--text-mute2); text-transform: none; letter-spacing: 0; }

/* A PANEL HEADING THAT IS ITS OWN SWITCH (Shared/CollapsiblePanel.razor, 2026-09-20). The
   whole bar is the target rather than the arrow: a small control beside a large inert one is
   the arrangement people miss. .panel-title is already a flex row, so the hint sits in the
   middle and the arrow is pushed to the right by margin-left:auto - nothing here changes the
   layout of an ordinary panel title. */
.panel-toggle { cursor: pointer; margin-bottom: 0; user-select: none; }
.panel-toggle:hover .panel-toggle-title, .panel-toggle:focus-visible .panel-toggle-title { color: var(--accent); }
.panel-toggle:focus-visible { outline: 2px solid var(--accent-dim); outline-offset: 3px; }
.panel-toggle-title { transition: color 0.12s ease; }
/* Pushed right whatever else is on the bar, and the same width open or shut so the heading
   does not shuffle sideways as it toggles. */
.panel-toggle-arrow { margin-left: auto; color: var(--text-mute2); font-size: 12px; width: 1em; text-align: center; }
.panel-toggle:hover .panel-toggle-arrow { color: var(--accent); }
/* The gap the heading drops when it is open, since .panel-toggle itself carries none. */
.panel-toggle + * { margin-top: 12px; }
.hint { font-weight: 600; font-size: 11.5px; color: var(--text-mute2); text-transform: none; letter-spacing: 0; }

/* FORMS */
label { font-size: 11.5px; color: var(--text-mute); text-transform: uppercase; letter-spacing: 0.5px; font-weight: 700; display: block; margin-bottom: 5px; }
input, select, textarea {
  width: 100%; background: var(--panel-alt); border: 1px solid var(--border);
  color: var(--text); padding: 9px 10px; border-radius: 6px; font-family: var(--body);
  font-size: 14px; font-weight: 600; margin-bottom: 12px;
}
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); }
textarea { resize: vertical; min-height: 60px; }
/* A NOTES BOX OPENS ABOUT SIX LINES TALL AND GROWS AS IT IS TYPED IN (the register's D5, 2026-09-29; the
   to-do package's 3.8). Every box whose cap is FieldLimits.Notes carries the class - to-dos, contacts,
   jobs, calls, meetings, and the change-note boxes - and nothing else does (NotesBoxShapeTests); the
   three other textareas keep the 60px above. field-sizing sizes the box to what is in it, up to 40% of
   the screen, then it scrolls inside, so Save stays in reach in a bottom sheet; the six lines are the
   floor, because a box sized to its content is one line tall when it is empty. A browser without
   field-sizing keeps rows="6" and grows through tracker.js, on the same class. The 5,000-character cap
   stays: it is set by the connection, not by the box. */
textarea.notes {
  line-height: 1.45; min-height: calc(6 * 1.45em + 20px); max-height: 40vh; overflow-y: auto;
  field-sizing: content;
}
/* 16px ON A PHONE, which is what stops iOS zooming in when a field takes focus - it zooms on any
   input, select or textarea smaller than that, typed into or not, and 14px sent the page sideways on
   every tap into a field (the to-do package's 3.8). The same three elements as the rule above. A class
   that sets its own size outranks this element rule, so the row selects (.priority-select, below)
   carry their own phone rule beside theirs. */
@media (max-width: 640px) { input, select, textarea { font-size: 16px; } }
/* THE CALENDAR AND CLOCK BUTTONS IN THE DATE AND TIME BOXES ARE THE BROWSER'S OWN, LIGHT ON THIS DARK SITE
   (a staff member's note of 2 October, "hard to see"; fixed 2026-10-29). The tracker's stylesheet carried
   filter: invert(0.9) here, which turned a dark icon light on a page that did not say it was dark. This
   one does - App.razor's <meta name="color-scheme" content="dark">, there since the first build - and
   Chrome and Edge already draw the icon light for it, so the invert turned it near-black on --panel-alt:
   about 1.2 to 1 against the box. The date-and-time boxes on the log reader never had the rule, and
   showed the icon plainly beside every date box that could not. No filter of any strength, either: a
   filter is not among what a Windows contrast theme overrides, so any filter here would invert the
   theme's own icon too. Firefox and Safari never match this pseudo-element - they draw their own button,
   or none - so only Chromium was ever affected. DateBoxIconTests holds the three rules to the pointer
   alone and the page to its dark declaration. */
input[type="date"]::-webkit-calendar-picker-indicator { cursor: pointer; }
input[type="time"]::-webkit-calendar-picker-indicator { cursor: pointer; }
input[type="datetime-local"]::-webkit-calendar-picker-indicator { cursor: pointer; }
input[type="checkbox"] { width: auto; margin-bottom: 0; accent-color: var(--accent); }
input[type="file"] { padding: 8px; }
input::file-selector-button {
  font-family: var(--display); text-transform: uppercase; letter-spacing: 0.5px; font-weight: 700; font-size: 12px;
  background: transparent; border: 1px solid var(--border); color: var(--text); border-radius: 6px; padding: 6px 12px; margin-right: 10px; cursor: pointer;
}
.tight { margin-bottom: 0; }
.check-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; text-transform: none; font-size: 13px; color: var(--text); letter-spacing: 0; cursor: pointer; }
.check-row.tight { margin-bottom: 0; }
.note-box { margin: 12px 0; padding: 10px 12px; background: var(--panel-alt); border: 1px solid var(--border); border-radius: 8px; }
.note-box > .client-meta { margin-top: 4px; }
/* THE FOLLOW-UP DATE IS A THIRD OF ITS BOX FROM 641px UP, AND THE WHOLE LINE ON A PHONE (2026-10-23).
   This box carried max-width:33% inline from the first rebuild, on the contact's page and Touchpoints'
   Log a Touchpoint dialog - most likely the tracker's own. On a phone a third of the note box is 71 to
   79px of content, and a date at the phone's 16px needs about 90 to 100 - the date being set was clipped, on the phone's own path for scheduling a follow-up after a
   call. Worked from the numbers, not seen on a device. Complete To-Do's box is half its line, which
   holds a date at 16px, and keeps its inline style. Wider than a phone is written (width > 640px), not
   min-width 641 (2026-11-02, the review of 2026-10-30's 6.1): between max-width 640 and min-width 641
   is a pixel a fractional width falls into - a zoomed page, a scaled display - where neither rule held,
   the gap the menu's ranges closed on 2026-10-29 (the shell's comment). Every rule in the sheet keeps to it
   (held by PhoneWidthGapTests). */
@media (width > 640px) { .follow-up-date { max-width: 33%; } }
.warn-box { background: rgba(194, 202, 209, 0.15); border: 1px solid var(--accent); border-radius: 8px; padding: 10px 12px; margin: 10px 0; font-size: 12.5px; color: var(--text); }
.error-box { background: rgba(225, 52, 56, 0.12); border: 1px solid var(--red); border-radius: 8px; padding: 10px 12px; margin: 10px 0; font-size: 12.5px; color: var(--text); }
/* RefusalBoundary's Development-only exception dump. Capped and scrollable because a Blazor
   stack trace is fifty frames and would otherwise push the page it is explaining off screen. */
.dev-exception { white-space: pre-wrap; word-break: break-word; max-height: 40vh; overflow: auto; margin: 10px 0 0; font-family: var(--mono); font-size: 11px; color: var(--text-mute); }
/* The reference the same failure carries in the log, in the monospace face so that 8 and B,
   0 and D read apart over the phone. */
.error-reference { margin-top: 6px; }
.error-reference strong { font-family: var(--mono); letter-spacing: 0.04em; }
.ac-wrap { position: relative; }
.ac-dropdown {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 50;
  background: var(--panel-alt); border: 1px solid var(--border); border-radius: 6px;
  max-height: 240px; overflow-y: auto; margin-top: -10px;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5);
}
.ac-item { padding: 8px 10px; cursor: pointer; border-bottom: 1px solid var(--border); }
.ac-item:last-child { border-bottom: none; }
.ac-item:hover { background: var(--panel); }
.ac-item-title { font-weight: 700; font-size: 13px; color: var(--text); }
.ac-item-sub { font-size: 11px; font-weight: 600; color: var(--text-mute); font-family: var(--mono); margin-top: 2px; }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 12px; }
.form-grid.three { grid-template-columns: 1fr 1fr 1fr; }
.form-grid.four { grid-template-columns: 1fr 1fr 1fr 1fr; }
.form-grid.five { grid-template-columns: 1fr 1fr 1fr 1fr 1fr; }
@media (max-width: 1100px) { .form-grid.five { grid-template-columns: 1fr 1fr 1fr; } }
@media (max-width: 900px) { .form-grid.four, .form-grid.five { grid-template-columns: 1fr 1fr; } }
@media (max-width: 640px) { .form-grid, .form-grid.three, .form-grid.four, .form-grid.five { grid-template-columns: 1fr; } }
/* Four short selects (the Contacts filters) stay two to a line on a phone; one to a line put the
   list itself another screen further down. A form of text boxes keeps one to a line.
   minmax(0, 1fr), not 1fr (2026-10-23): a bare 1fr will not shrink below what is in it, and a date box
   or a select is as wide as its longest option - History's two date boxes asked for 174px each, which
   with the gap is 360px on a 343px column, and the right-hand filters ran past the page's edge. */
@media (max-width: 640px) { .form-grid.four.filters, .form-grid.five.filters { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.rep-stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@media (max-width: 800px) { .rep-stats-grid { grid-template-columns: 1fr; } }
.inline-add { display: flex; gap: 8px; margin-bottom: 12px; }
.inline-add input { margin-bottom: 0; }
/* On a phone the search box takes the line and its buttons go under it: beside View and Add
   Contact it was squeezed to "Type to searc". */
@media (max-width: 640px) { .inline-add { flex-wrap: wrap; } .inline-add > .ac-wrap { flex: 1 1 100%; } }
.btn {
  font-family: var(--display); text-transform: uppercase; letter-spacing: 0.5px;
  font-weight: 700; font-size: 13px;
  background: var(--accent); color: #151515; border: none; border-radius: 6px;
  padding: 11px 18px; cursor: pointer; transition: background .15s; display: inline-block; line-height: normal;
}
.btn:hover { background: var(--accent-dim); }
.btn:disabled { opacity: 0.5; cursor: default; }
.btn.secondary { background: transparent; border: 1px solid var(--border); color: var(--text); }
.btn.secondary:hover { border-color: var(--accent); color: var(--accent); }
.btn.danger { background: transparent; border: 1px solid var(--red); color: var(--red); }
.btn.small { padding: 5px 10px; font-size: 11px; }
.btn.compact { padding: 8px 14px; font-size: 11px; white-space: nowrap; }
.btn-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }

/* PAGER - above and below Contacts and the Jobs Log (Shared/Pager.razor) */
.pager { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; justify-content: center; margin: 10px 0; }
.pager-where { font-size: 12px; color: var(--text-mute2); padding: 0 6px; white-space: nowrap; }

/* A phone number or an address you can tap (ADR-0007): quiet until pointed at. */
.contact-link { color: inherit; text-decoration: underline; text-decoration-color: var(--border); text-underline-offset: 2px; }
.contact-link:hover, .contact-link:focus-visible { color: var(--accent); text-decoration-color: var(--accent); }

/* CLIENT LIST / GAUGE ROWS */
.client-row {
  display: flex; align-items: center; gap: 14px;
  padding: 12px 10px; border-bottom: 1px solid var(--border); cursor: pointer;
  transition: background .1s;
}
.client-row.static { cursor: default; }
.client-row:last-child { border-bottom: none; }
.client-row:hover { background: var(--panel-alt); }
.client-name-block { flex: 1; min-width: 0; }
.client-name { font-weight: 700; font-size: 15.5px; letter-spacing: 0.1px; }
.client-name.small { font-size: 14px; }
.client-meta { font-family: var(--mono); font-size: 12px; font-weight: 600; color: var(--text-mute); margin-top: 3px; line-height: 1.5; }
.client-meta.italic { font-style: italic; }
.client-meta.inline { display: inline; }
.client-meta strong { color: var(--text); }
.client-meta strong.over { color: var(--red); }
/* The rep names wherever they are written: the dashboard, the contacts list, the contact's own
   page, and the pills on the edit dialog. One class, so they cannot drift into four colours.

   THE SECOND SELECTOR IS NOT BELT AND BRACES, it is the one that makes the contact's own page
   work, and it was wrong for a day. That page writes the value as `strong` to match the Last Job
   and Last Touchpoint lines above it, and `.client-meta strong` two lines up is a class plus an
   element - specificity 0-1-1 - which beats a bare `.rep-names` at 0-1-0. So the name rendered
   in --text and nobody could see it was meant to be blue except by comparing it with the list it
   was opened from. Found on staging, 2026-09-23.

   The selector it replaced was `.client-meta .rep-names strong`, which matched a `strong` INSIDE
   a `.rep-names` - a shape that exists nowhere. It was dead the moment it was written. */
.rep-names, .client-meta strong.rep-names { color: var(--rep); }
.pill.rep { color: var(--rep); border-color: var(--rep); }
.mono-sub { color: var(--text-mute); font-weight: 400; font-family: var(--mono); font-size: 11px; }
.mono-sub.larger { font-size: 12px; }
/* A NOTE KEEPS ITS LINE BREAKS WHERE IT IS SHOWN (the to-do package's 3.8): pre-line keeps the breaks
   somebody typed and still folds runs of spaces and wraps a long line. On the to-do and schedule rows
   (NoteText), the contact's own notes, the Jobs Log and the call log (note-lines), and the client timeline
   below. */
.note-lines, .note-text { white-space: pre-line; }
/* A long note on a row, clamped to three lines until its "more" is pressed - and only a note that has a
   "more" beside it is ever clamped (NoteText says why). */
.note-text.clamped {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden;
}
.note-text.open { display: block; }
.note-more {
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer; font: inherit; font-weight: 700;
  color: var(--accent); text-decoration: underline; text-underline-offset: 2px;
}
.note-more:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
/* A NAME MENTIONED IN A NOTE (ADR-0020): the rep colour Given by draws a person in, and the reader's own
   name marked as well so they can find themselves in a long note. Text, never a control (MentionText). */
.mention { color: var(--rep); font-weight: 700; }
.mention.mention-you { background: rgba(143, 168, 200, 0.18); border-radius: 3px; padding: 0 2px; }
/* A NOTIFICATION THAT WAS NEW when its page opened (ADR-0020's M9): marked for the visit, then seen. */
.notification-new { font-weight: 700; color: var(--rep); margin-right: 6px; font-size: 0.85em; text-transform: uppercase; letter-spacing: 0.04em; }
/* THE PICKER (tracker.js, 6): fixed to the window under the box it was opened for, above a dialog (200) and
   below the busy ring (300) and a question (400). Each line at least 44px, for a thumb. */
.mention-picker {
  position: fixed; z-index: 250; background: var(--panel-alt); border: 1px solid var(--border);
  border-radius: 6px; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5); overflow: hidden;
}
.mention-option { display: flex; flex-direction: column; justify-content: center; min-height: 44px; padding: 4px 12px; cursor: pointer; }
.mention-option:hover { background: var(--panel); }
/* The one Enter or Tab would choose, told apart from the one the pointer is over. */
.mention-option.active { background: var(--panel); box-shadow: inset 3px 0 0 var(--rep); }
.mention-option-name { color: var(--text); font-weight: 600; font-size: 14px; }
.mention-option-sign-in { color: var(--text-mute); font-family: var(--mono); font-size: 11.5px; }
/* The to-do or call a mention opened (?todo=, ?call=): marked, and focused so it is in view. */
.client-row.revealed, .timeline-item.revealed { outline: 2px solid var(--rep); outline-offset: 2px; border-radius: 6px; }
.company-tag { color: var(--accent); cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
.company-tag.mono { font-family: var(--mono); font-size: 11px; font-weight: 500; }
.contact-name-link { cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
.flag-badge {
  font-family: var(--display); font-size: 10.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.5px; padding: 4px 9px; border-radius: 20px; white-space: nowrap;
}
.flag-ok { background: rgba(79, 168, 147, 0.15); color: var(--teal); }
.flag-warn { background: rgba(255, 195, 0, 0.15); color: var(--yellow); }
.flag-high { background: rgba(194, 202, 209, 0.18); color: var(--accent); }
.flag-crit {
  background: repeating-linear-gradient(45deg, rgba(225, 52, 56, 0.25), rgba(225, 52, 56, 0.25) 6px, rgba(225, 52, 56, 0.4) 6px, rgba(225, 52, 56, 0.4) 12px);
  color: #ff9b9d;
}
/* Nobody has worked for or called them yet - a prospect, not an emergency (ADR-0007). */
.flag-none { background: transparent; border: 1px solid var(--border); color: var(--text-mute2); }
.flag-teal { background: rgba(79, 168, 147, 0.15); color: var(--teal); }
.flag-accent { background: rgba(194, 202, 209, 0.15); color: var(--accent); }
.flag-yellow { background: rgba(255, 195, 0, 0.15); color: var(--yellow); }
.flag-red { background: rgba(225, 52, 56, 0.15); color: var(--red); }
.priority-select { width: 130px; margin-bottom: 0; font-size: 11px; }
/* THE ROW SELECTS AT 16px ON A PHONE TOO (review of S13-S17, 2026-09-30). The 11px above is a class rule,
   so it outranks the element-only 16px rule under FORMS, and the state select on every To-Dos and Open
   Items row - the control P1 puts at the centre of the phone workflow - stayed at 11px and zoomed the page
   on every tap. The comment there said these selects were exempt because nobody types into them; iOS zooms
   on focus, not on typing, so that reason was false. Rejected: keeping 11px and accepting the zoom, which
   is the one thing 3.8's "Safari does not zoom" rules out. NotesBoxShapeTests holds both rules.

   AND AS WIDE AS ITS LONGEST STATE, NEVER LESS THAN 130px. At 16px the 130px box no longer holds the names:
   Roadblocked is about a hundred pixels of Inter at that size, and the box has about ninety once its
   padding and the browser's own arrow are taken out - worked from the numbers, not seen on a device, and
   a state is renamed freely on To-Do States. A select as wide as its widest option holds whatever the book
   names its states, a retired one's "(retired)" included; the floor keeps the priority selects the size
   they were, and the row wraps its controls on a phone anyway, so a wider select moves Edit and Delete
   under it rather than off the screen. Rejected: a wider fixed width, which is the same guess made again. */
@media (max-width: 640px) { .priority-select { font-size: 16px; width: auto; min-width: 130px; max-width: 100%; } }

/* A TO-DO'S STORY (2026-09-29, the to-do package's S11 and S12).

   The row's line (TodoLatestChange) is a real <button> drawn as the client-meta line it sits among - the
   browser's button face, border and centring put back - with its note beside it, not in it (a button's
   text is its name, read whole on every focus), drawn in the same type and CLAMPED TO ONE LINE, the
   register's P2: a note of five thousand characters on a row is a row nobody can read, and the list shows
   it whole. Underlined on hover and focus, as the other things on a row that open something are. Rejected:
   a span with role="button", Rep View's contact names' shape - a new control starts as a button. */
.todo-change-row { margin: 3px 0 0; }
.todo-change-line {
  display: block; width: 100%; background: none; border: 0; padding: 0; margin: 3px 0 0; text-align: left; cursor: pointer;
  font-family: var(--mono); font-size: 12px; font-weight: 600; color: var(--text-mute); line-height: 1.5;
}
.todo-change-row .todo-change-line { margin: 0; }
.todo-change-line:hover .todo-change-said, .todo-change-line:focus-visible .todo-change-said { text-decoration: underline; text-underline-offset: 2px; color: var(--text); }
.todo-change-line:focus-visible { outline: 2px solid var(--accent-dim); outline-offset: 3px; }
.todo-change-line.bare { width: auto; color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
.todo-change-line .todo-change-said { display: block; }
.todo-change-row .todo-change-note {
  display: block; font-family: var(--mono); font-size: 12px; font-weight: 400; color: var(--text-mute); line-height: 1.5;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Read by a screen reader, drawn nowhere - which to-do a row's line is about. */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }

/* The list (TodoChangeList): oldest first, a line per change, its notes beneath. A note keeps its own
   line breaks (pre-line) where its signature stays one line; a removed note's mark reads as a mark. */
.todo-changes { list-style: none; margin: 0; padding: 0; }
.todo-change { padding: 8px 0; border-bottom: 1px solid var(--border); font-size: 13px; }
.todo-change:last-child { border-bottom: 0; }
.todo-change-said { color: var(--text); }
.todo-note { margin: 6px 0 0 12px; font-size: 12.5px; color: var(--text-mute); }
.todo-note-words { white-space: pre-line; color: var(--text); }
.todo-note.removed { font-style: italic; }
.todo-note-actions { margin-left: 8px; }
.todo-note-actions .btn { vertical-align: middle; }

/* The strip under a row whose state was picked (TodoMoveStrip, the register's P1). */
.move-strip { margin: 0 0 12px; padding: 10px 12px; background: var(--panel-alt); border: 1px solid var(--accent); border-radius: 8px; }
.move-strip-title { font-weight: 700; margin-bottom: 8px; color: var(--text); }

/* Edit To-Do's list, below Save and Cancel: a heading on a desktop, and on a phone - where the dialog is a
   bottom sheet with a height limit - a button that opens it, collapsed, so Save stays in reach. */
.edit-changes { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 4px; }
@media (width > 640px) { .edit-changes-toggle { display: none; } }
@media (max-width: 640px) {
  .edit-changes-heading { display: none; }
  .edit-changes-toggle { margin-top: 10px; }
  .edit-changes:not(.open) .edit-changes-body { display: none; }
}

/* A LIST ROW ON A PHONE (2026-09-22). Every list - Contacts, the Dashboard, the Jobs Log, Team,
   Roles, the Rep View - lays a row out in one line: the name block, then whatever sits beside it
   (a badge, a gauge, the priority select, Edit and Delete). On a 375-pixel phone the controls took
   nearly all of it and squeezed the name block to NOTHING: Contacts' names, numbers and dates
   wrapped one word to a line, a row ran 350 to 450 pixels, and the priority pill sat on top of the
   text. Mike: "the contact list looks terrible on a phone". So on a phone the name block takes the
   whole first line and the controls go under it, Edit and Delete to the right. The priority PILL
   gives way where the priority SELECT is beside it - it says the same word; somebody who may not
   change priorities has no select, and keeps the pill. A Contacts row is about 125 pixels now.

   NEVER THE RED OVERDUE BADGE (2026-09-29, the to-do package). The rule matched any badge right
   before a .priority-select, and the to-do rows reuse that class for the state select - so on a
   phone, for everybody who can change records, the Overdue badge was hidden: the only red signal
   on a late row, and it does NOT say the same word as the select beside it (TodoRows.razor, both lists'
   rows, prints it instead of the state's name). Found by reading, not seen on a device.
   flag-crit is the Overdue badges' and FlagBadge's alone, and FlagBadge never sits beside a
   select. Rejected: a class of its own on the state select, which would have left the next row
   that reuses priority-select to fall into the same rule. OverdueBadgeOnAPhoneTests holds it. */
@media (max-width: 640px) {
  .client-row { flex-wrap: wrap; row-gap: 8px; column-gap: 10px; }
  .client-row > .client-name-block { flex: 1 1 100%; }
  .client-row > .btn-row { flex-wrap: nowrap; margin-left: auto; }
  .client-row > .flag-badge:not(.flag-crit):has(+ .priority-select) { display: none; }
}

/* GAUGE BAR */
.gauge { width: 150px; flex-shrink: 0; }
.gauge-track {
  position: relative; height: 8px; border-radius: 4px; overflow: hidden;
  background: linear-gradient(90deg, var(--teal) 0 15%, var(--yellow) 15% 33%, var(--accent) 33% 66%, var(--red) 66% 100%);
}
.gauge-marker { position: absolute; top: -3px; width: 2px; height: 14px; background: #fff; box-shadow: 0 0 4px rgba(0, 0, 0, 0.6); }
.gauge-days { font-family: var(--mono); font-size: 11px; color: var(--text-mute); text-align: right; margin-top: 3px; font-weight: 700; }
@media (max-width: 640px) { .gauge { width: 70px; } }

/* GROUP HEADINGS (the tracker's inline Oswald headings) */
.group-head {
  font-family: var(--display); text-transform: uppercase; letter-spacing: 0.5px; font-size: 12px; font-weight: 700;
  color: var(--accent); padding: 10px 0 6px; border-top: 1px solid var(--border); margin-top: 10px;
}
.group-head.first { border-top: none; margin-top: 0; }
.group-head.underline { font-weight: 600; padding: 8px 0 4px; border-top: none; border-bottom: 1px solid var(--border); margin: 0 0 4px; }
.group-head .count { color: var(--text-mute2); font-weight: 400; }
.group-head.red { color: var(--red); }
.group-head.yellow { color: var(--yellow); }
.group-head.teal { color: var(--teal); }
.group-head.mute { color: var(--text-mute); }
.group-head.clickable { cursor: pointer; }
.group { margin-bottom: 14px; }
.section-label { text-transform: uppercase; letter-spacing: 0.5px; margin: 14px 0 6px; color: var(--text-mute); }

/* PILLS */
.pills { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
.pill { display: flex; align-items: center; gap: 6px; background: var(--panel-alt); border: 1px solid var(--border); padding: 5px 10px; border-radius: 20px; color: var(--text); font-size: 12px; }
.pill .x { cursor: pointer; color: var(--red); font-weight: 700; }
/* A pill's ✕ as a real button (History's contact chip, 2026-09-29): the pill's look, none of a button's face. */
.pill button.x { background: none; border: 0; padding: 0; font: inherit; }

/* HISTORY'S CONTACT (the register's C4): the chip the list is narrowed to, and the name on a line that sets
   it - a real button drawn as a link, as the other things on a line that open something are. */
.filter-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 12px; }
.filter-chips .pill { display: inline-flex; }
.link-button {
  background: none; border: 0; padding: 0; margin-left: 6px; cursor: pointer; font: inherit; font-size: 12px;
  color: var(--accent); text-decoration: underline; text-underline-offset: 2px;
}
.link-button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* TIMELINE */
.timeline-item { display: flex; gap: 12px; padding: 12px 0; border-left: 2px solid var(--border); margin-left: 6px; padding-left: 16px; position: relative; }
.timeline-item::before { content: ''; position: absolute; left: -7px; top: 16px; width: 12px; height: 12px; border-radius: 50%; background: var(--accent); border: 2px solid var(--bg); }
.timeline-item.job::before { background: var(--teal); }
.timeline-content { flex: 1; min-width: 0; }
.timeline-top { display: flex; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.timeline-type { font-family: var(--display); font-size: 11.5px; font-weight: 700; text-transform: uppercase; color: var(--accent); letter-spacing: 0.5px; }
.timeline-item.job .timeline-type { color: var(--teal); }
.timeline-date { font-family: var(--mono); font-size: 11.5px; font-weight: 700; color: var(--text-mute); }
.timeline-note { font-size: 14px; font-weight: 600; color: var(--text); margin-top: 4px; line-height: 1.55; white-space: pre-line; }
.timeline-rep { font-size: 11.5px; color: var(--text-mute2); margin-top: 3px; font-weight: 700; }

/* A LONG UNBROKEN STRING WRAPS WHERE IT IS SHOWN (2026-10-23). An email address has nowhere a line may
   break - "." and "@" are not places - and a pasted link or an upload's file name has few, so one long
   enough ran past its row on a phone and the whole page scrolled sideways: about 44 characters of the
   11-12px mono on a contact's row or page, or one long token in a call's note. The book decides that,
   so it can start any day. break-word on the name block and the meta lines: .client-meta is a flex item
   in places (.breakdown-row), and anywhere would lower what it asks for there and break a word in a
   tight row. anywhere on the notes, which are prose in boxes of their own. And the timeline's content
   is let shrink (min-width: 0, above) - a flex item will not go below its longest word without it, so
   one long token in a note set the width of the whole timeline. The audit table already does this
   (.data-table td). Worked from the numbers, not seen on a device. */
.client-name-block, .client-meta { overflow-wrap: break-word; }
.timeline-note, .note-lines, .note-text, .todo-note-words { overflow-wrap: anywhere; }

.empty-state { text-align: center; padding: 40px 20px; color: var(--text-mute2); font-weight: 600; }
.empty-state.tight { padding: 16px; }
.empty-state .big { font-family: var(--display); font-weight: 700; font-size: 16px; color: var(--text-mute); margin-bottom: 6px; text-transform: uppercase; }

.back-link { color: var(--accent); cursor: pointer; font-size: 12px; font-family: var(--mono); font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 14px; display: inline-block; }

.toast-stack { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); display: flex; flex-direction: column; align-items: center; gap: 8px; z-index: 999; width: max-content; max-width: 90vw; }
/* On a phone the dialogs are bottom sheets, and a toast that now stays long enough to read would
   sit over their Save row - a tap meant for Save would dismiss the toast instead. At the top it
   covers nothing anybody is about to press. */
@media (max-width: 640px) { .toast-stack { top: 12px; bottom: auto; } }
.toast {
  background: var(--panel-alt); border: 1px solid var(--accent); color: var(--text);
  padding: 10px 18px; border-radius: 8px; font-size: 13px; font-weight: 600;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
  font-family: var(--mono); max-width: min(90vw, 720px); text-align: center;
  cursor: pointer; /* a click dismisses it (ToastService.Dismiss) */
}

::-webkit-scrollbar { height: 6px; width: 6px; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }

.fab {
  position: fixed; bottom: 20px; right: 20px;
  background: var(--accent); color: #151515; border: none;
  width: 56px; height: 56px; border-radius: 50%; font-size: 26px; font-weight: 700;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5); cursor: pointer; z-index: 100;
  display: flex; align-items: center; justify-content: center;
}
.modal-overlay { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.6); z-index: 200; display: flex; align-items: flex-end; justify-content: center; }
@media (width > 640px) { .modal-overlay { align-items: center; } }
.modal { background: var(--panel); border: 1px solid var(--border); border-radius: 14px 14px 0 0; padding: 20px; width: 100%; max-width: 480px; max-height: 88vh; overflow-y: auto; }
.modal.wide { max-width: 640px; }
@media (width > 640px) { .modal { border-radius: 14px; } }
.modal .message { font-size: 13.5px; line-height: 1.55; color: var(--text); white-space: pre-line; margin-bottom: 14px; }

/* BIG NUMBERS (Rep View cards) */
.split { display: flex; }
.split > div { flex: 1; text-align: center; padding: 6px 0; }
.split > div + div { border-left: 1px solid var(--border); }
.big-num { font-family: var(--mono); font-size: 32px; font-weight: 600; color: var(--teal); }
.big-num.red { color: var(--red); }
.big-num.larger { font-size: 36px; }
.centered { text-align: center; padding: 6px 0 2px; }
.breakdown { border-top: 1px solid var(--border); margin-top: 8px; padding-top: 6px; }
.breakdown-row { display: flex; justify-content: space-between; padding: 3px 0; }
.breakdown-row .client-meta { margin: 0; }
.breakdown-row .client-meta.value { color: var(--text); font-weight: 700; }

/* MONTH GRID */
.month-head { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; font-family: var(--mono); font-size: 10px; color: var(--text-mute); text-align: center; margin-bottom: 4px; }
.month-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.day-cell { padding: 8px 4px; text-align: center; cursor: pointer; border-radius: 6px; border: 1px solid transparent; font-family: var(--mono); font-size: 12px; color: var(--text); }
.day-cell.today { background: var(--panel-alt); border-color: var(--accent); }
.day-cell:hover { background: var(--panel-alt); }
.day-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--accent); margin: 3px auto 0; }
.day-title { font-family: var(--display); font-size: 16px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px; }
.day-title .today-tag { color: var(--accent); font-size: 11px; font-family: var(--mono); }

/* SHEET IMPORT PREVIEW */
.scroll-x { overflow-x: auto; }
.preview-table { width: 100%; border-collapse: collapse; font-family: var(--mono); font-size: 11px; }
.preview-table th { text-align: left; border-bottom: 1px solid var(--border); padding: 4px 8px; color: var(--text-mute); font-weight: 700; white-space: nowrap; }
.preview-table td { padding: 4px 8px; border-bottom: 1px solid var(--border); white-space: nowrap; }
.preview-table .none { color: var(--text-mute2); }

/* THE AUDIT VIEWER'S TABLE (ADR-0006). `data-table` was NEVER DEFINED - the page named a
   class that no stylesheet carried, so it rendered with no rules at all: no borders, no row
   separation, and columns sized by whatever the browser guessed from content. "Who" ran into
   "What" because nothing bounded either. The page had never been rendered by anything before
   2026-09-20 (no bUnit here, and the host tests cover endpoints rather than Razor), which is
   exactly the kind of thing only opening it finds.
   Not folded into .preview-table: that one is nowrap by design for a short import preview,
   and this table's whole job is long sentences that must wrap. */
.data-table { width: 100%; border-collapse: collapse; table-layout: fixed; font-family: var(--mono); font-size: 12px; }
.data-table th { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border); color: var(--text-mute); font-weight: 700; white-space: nowrap; }
.data-table td { padding: 8px 10px; border-bottom: 1px solid var(--border); vertical-align: top; overflow-wrap: anywhere; }
.data-table tbody tr:hover { background: var(--panel-alt); }
.data-table td.nowrap { white-space: nowrap; }
/* Fixed proportions, because content-sized columns are what let one long machine name
   squeeze the column carrying the sentence. When never wraps; What gets the remainder. */
/* 23ch is the timestamp - "2026-09-20 13:41:56 MDT" is exactly 23 characters - and the cell
   also carries 10px of padding each side. Set to 23ch it overflowed, and because this column
   is nowrap the overflow ran straight into Who: the two columns printed as one string with no
   gap. Found on production, on the screen this rule was written to fix. */
.data-table th:nth-child(1), .data-table td:nth-child(1) { width: calc(23ch + 24px); }
.data-table th:nth-child(2), .data-table td:nth-child(2) { width: 26%; }
.data-table th:nth-child(4), .data-table td:nth-child(4) { width: 20%; }
.data-table details { margin-top: 5px; }
.data-table summary { cursor: pointer; color: var(--text-mute2); }
.data-table summary:hover { color: var(--text-mute); }
/* ON A PHONE A ROW IS READ AS A CARD, NOT ACROSS A TABLE (2026-10-23). History could not be read on a
   phone. The fixed layout above asks for When's calc(23ch + 24px) - about 190px, nowrap - plus 26% for
   Who and 20% for Where from, before What gets anything; on a 343px column that is more than the
   column holds, so What was left the width of its own padding, and with overflow-wrap: anywhere
   every sentence broke a letter or two to a line, printed over Where from - one row measured 3,091px
   tall at 375 wide. Backups' "taken and put back" panel is the same markup and had the same table.
   So below 641px the four cells stop being columns: When and Who share the first line, What takes the
   full width under them, and Where from comes last. The headings go - kept for a screen reader - so
   the one cell that does not say what it is carries its own (data-label). Rejected: a sideways-
   scrolling table, which keeps the columns and loses the sentence off the edge of the screen. */
@media (max-width: 640px) {
  .data-table, .data-table tbody, .data-table td { display: block; }
  .data-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); }
  .data-table tr { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 2px 12px; padding: 10px 0; border-bottom: 1px solid var(--border); }
  .data-table th:nth-child(n), .data-table td:nth-child(n) { width: auto; padding: 0; border-bottom: none; }
  .data-table td:nth-child(3), .data-table td:nth-child(4) { grid-column: 1 / -1; }
  .data-table td[data-label]::before { content: attr(data-label) ": "; }
}

/* SIGN-IN */
.login-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px 16px; }
.login-card { width: 100%; max-width: 400px; }
.login-card .hdr { justify-content: center; }
.validation-message { color: var(--red); font-size: 12px; font-family: var(--mono); margin: -8px 0 10px; }

/* A NOTE ABOUT A LOG SEARCH (Pages/LogSearch.razor): the busy line, and each note a search came back
   with - read rather than used, so it is drawn to be noticed and then to be calm: a yellow edge down its
   left side, the panel's own background, the body font. Plain text, on every tier, production included.
   The rule was the Dashboard's hosting notice's until that notice left (2026-09-18, with
   HostingNotice.razor); its heading, collapsed line, footer and lists had no markup from then, and went
   on 2026-10-23. */
.notice {
  background: var(--panel); border: 1px solid var(--border); border-left: 4px solid var(--yellow);
  border-radius: 10px; padding: 16px 18px; margin-bottom: 16px;
}

/* ENVIRONMENT RIBBON - a corner tag on every non-Production host (Components/Shared/
   EnvironmentRibbon.razor): the same site on the dev rig and on the staging address look
   identical otherwise, and the book kept on staging is not the one production will carry.
   Mistaking one for the other is the failure this exists to prevent, so the ribbon is
   deliberately laid ACROSS the AMA logo: a header that looks untouched is exactly what
   would let a rehearsal site pass for the real one. Hence the left offset - it tracks the
   content column (--wrap-max), not the viewport, so the ribbon crosses the logo at every
   window width instead of drifting into the empty margin on a wide screen. Fixed, so it
   survives scrolling; under the modals; and pointer-events none, so it covers no control.
   Inside the shell (2026-10-29) the logo heads the left menu, at the window's own corner at
   every width - the rail's mark, the open menu's wordmark, a phone header's - so there the
   ribbon sits at the corner too; the sign-in page keeps the column's offset. */
.env-ribbon {
  position: fixed; top: 0; left: max(0px, calc((100vw - var(--wrap-max)) / 2));
  width: 140px; height: 140px; overflow: hidden;
  z-index: 150; pointer-events: none;
}
body:has(.shell) .env-ribbon { left: 0; }
.env-ribbon span {
  position: absolute; top: 30px; left: -42px; width: 190px;
  transform: rotate(-45deg);
  background: var(--yellow); color: #15181a;
  font-family: var(--display); font-weight: 700; font-size: 12px; letter-spacing: 2px; text-transform: uppercase;
  text-align: center; line-height: 24px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);
  opacity: 0.92;
}
.env-ribbon.dev span { background: var(--teal); color: #15181a; }

/* Blazor's reconnect / error UI, restyled to the panel palette */
#blazor-error-ui { display: none; position: fixed; bottom: 0; left: 0; right: 0; background: var(--panel-alt); border-top: 1px solid var(--red); color: var(--text); padding: 12px 20px; font-size: 13px; z-index: 1000; }
#blazor-error-ui .dismiss { position: absolute; right: 16px; top: 10px; cursor: pointer; }
/* The reconnect dialog. This used to be one line - `#components-reconnect-modal { color:
   var(--text) }` - which was worse than nothing: it left the framework drawing its own white
   card and coloured the text on it near-white, so the ONE message this site could show about
   a dropped connection was white on white.

   The class names below are the framework's contract, read off
   _framework/blazor.web.js rather than remembered. blazor.web.js has two implementations: its
   own dialog, and this one - "user-specified" - which is used the moment an element with this
   id exists. The difference that matters is that the default's rejected() calls
   location.reload() and the user-specified one only adds a class, so the link out of the
   rejected state below is load-bearing rather than decoration. A deploy produces exactly
   that state: the site restarts, and every open circuit is one the new process never had. */
#components-reconnect-modal { display: none; }

#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-retrying,
#components-reconnect-modal.components-reconnect-paused,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-resume-failed,
#components-reconnect-modal.components-reconnect-rejected {
  display: flex; position: fixed; inset: 0; z-index: 1001;
  background: rgba(0, 0, 0, 0.6); align-items: center; justify-content: center;
}

.reconnect-card {
  background: var(--panel-alt); border: 1px solid var(--border); border-radius: 8px;
  color: var(--text); padding: 18px 20px; max-width: 420px; margin: 16px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
}

.reconnect-title {
  font-family: var(--display); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.5px; font-size: 13px; color: var(--accent); margin-bottom: 8px;
}

/* One line per state, and the state class on the dialog decides which. */
.reconnect-line { display: none; font-size: 13px; margin: 0 0 12px; }

#components-reconnect-modal.components-reconnect-show .reconnect-when-trying,
#components-reconnect-modal.components-reconnect-retrying .reconnect-when-trying,
#components-reconnect-modal.components-reconnect-paused .reconnect-when-trying,
#components-reconnect-modal.components-reconnect-failed .reconnect-when-failed,
#components-reconnect-modal.components-reconnect-resume-failed .reconnect-when-failed,
#components-reconnect-modal.components-reconnect-rejected .reconnect-when-rejected {
  display: block;
}

/* THE NOTE RESCUE (the register's P8; tracker.js, part 5): the words of every notes box that held any
   when the connection could not be got back, read-only, each with Copy. The card can now be taller than
   a phone, so it scrolls inside itself rather than pushing its buttons off the screen.

   SIZED AGAINST THE OVERLAY IT IS CENTRED IN, not the viewport (review of S13-S17, 2026-09-30). It was
   100vh - 32px, and on iOS Safari and Chrome for Android 100vh is the viewport with the toolbars hidden,
   while the overlay's inset: 0 is the viewport actually shown - so a card at its limit overran the
   overlay, which does not scroll, by about half a toolbar at each end: the title and the bottom of "Back
   to the Dashboard" off the screen, exactly when the rescue had made the card tall. 100% of the overlay is
   what is shown, and 32px is the card's own 16px margin top and bottom. Rejected: the 100vh / 100dvh
   pair, which is right only where dvh is supported and still measures something other than the box the
   card sits in. */
.reconnect-card { max-height: calc(100% - 32px); overflow-y: auto; }
.reconnect-rescue { margin: 0 0 12px; }
.reconnect-rescue[hidden] { display: none; }
.reconnect-rescue-title {
  font-family: var(--display); font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px;
  font-size: 12px; color: var(--red); margin-bottom: 8px;
}
.reconnect-rescue-item { margin-bottom: 10px; }
.reconnect-rescue-label { font-size: 11.5px; color: var(--text-mute); font-weight: 700; margin-bottom: 4px; }
.reconnect-rescue-words { margin-bottom: 6px; min-height: 0; }

/* FOCUS, VISIBLE. The rule above turns the outline off on every form control and swaps the
   border colour instead, which reads well and works - for a control that HAS a border. A
   checkbox, a radio and a file input do not, so for those the swap is invisible and the
   whole screen could be tabbed through with nothing showing where you were. :focus-visible
   rather than :focus, so a mouse click does not paint a ring nobody asked for. */
input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible,
input[type="file"]:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

/* The tracker's rows, name links and company tags are divs and spans that behave as buttons
   (Keys.Activate gives them Enter and Space). A real button shows focus for free; these have
   to be told to. */
[role="button"]:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px;
}

.modal:focus { outline: none; }

/* The two administration screens (Team, Roles), added with the permission system.
   They reuse .panel, .client-row and .btn wholesale - the only genuinely new thing here is the
   permission checklist, which has no precedent in the tracker because the tracker had nothing
   to grant. A switched-off person is dimmed rather than hidden: they are still the name on
   two years of touchpoints, and a list that hid them would make that history look authorless. */
.client-row.off .client-name { color: var(--text-mute); text-decoration: line-through; }
.client-row.off { opacity: 0.72; }

.perm-checklist { margin: 14px 0 4px; }
.perm-group { border: 1px solid var(--border); border-radius: 10px; padding: 10px 14px 6px; margin-bottom: 12px; }
.perm-group > legend { font-size: 12px; font-weight: 700; letter-spacing: 0.4px; text-transform: uppercase; color: var(--text-mute); padding: 0 6px; }
.perm-group > .hint { margin: 0 0 8px; }
.perm-row { display: block; padding: 7px 0; border-bottom: 1px solid var(--border); cursor: pointer; }
.perm-row:last-child { border-bottom: none; }
.perm-row > input { margin-right: 8px; vertical-align: top; }
.perm-title { font-weight: 700; font-size: 13.5px; }
/* The consequence, not the name, is what somebody deciding this actually reads. */
.perm-consequence { display: block; margin: 2px 0 0 22px; font-size: 12px; color: var(--text-mute); line-height: 1.45; }
.perm-row-locked { cursor: not-allowed; opacity: 0.55; }
.perm-locked-note { display: block; margin: 3px 0 0 22px; font-size: 11.5px; color: var(--yellow); }

/* ---- THE BUSY RING (Services/BusyService.cs, Shared/BusyHost.razor) ----
   A layer over the page while something runs. It takes every tap from the first moment, but
   stays clear and empty for the first 600 ms - longer than the ring's own minimum hold, so a
   save that finishes in a quarter of a second does not flash a spinner. Above every dialog (200) so it covers a dialog's own Save; below the
   confirm dialog (400), which an upload shows part-way through its work; below the toasts
   (999), the error bar (1000) and the reconnect card (1001), so a dropped connection still
   shows over it. */
.modal-overlay.confirm { z-index: 400; }
.busy-overlay { position: fixed; inset: 0; z-index: 300; display: flex; align-items: center; justify-content: center; padding: 16px; background: rgba(0, 0, 0, 0); cursor: progress; animation: busy-dim 0.2s ease 0.6s forwards; }
.busy-card { background: var(--panel); border: 1px solid var(--border); border-radius: 14px; padding: 22px 26px; width: 100%; max-width: 420px; text-align: center; opacity: 0; animation: busy-show 0.2s ease 0.6s forwards; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.45); }
.busy-ring { width: 44px; height: 44px; margin: 0 auto 14px; border-radius: 50%; border: 4px solid var(--border); border-top-color: var(--accent); animation: busy-spin 0.9s linear infinite; }
.busy-text { font-size: 15px; font-weight: 600; color: var(--text); line-height: 1.45; }
.busy-step { margin-top: 6px; font-size: 13px; color: var(--text-mute); line-height: 1.45; }
.busy-hint { margin-top: 12px; font-size: 12px; color: var(--text-mute2); }
@keyframes busy-spin { to { transform: rotate(360deg); } }
@keyframes busy-dim { to { background: rgba(0, 0, 0, 0.55); } }
@keyframes busy-show { to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .busy-ring { animation-duration: 2.8s; } }

/* The plain forms - sign in, the two password screens, sign out - post without a circuit, so
   the ring cannot reach them; wwwroot/js/tracker.js marks the button instead while the post is
   on its way, and refuses a second one. */
.btn.is-busy, .link-btn.is-busy { opacity: 0.8; cursor: progress; }
.btn.is-busy::before { content: ""; display: inline-block; width: 12px; height: 12px; margin-right: 8px; vertical-align: -1px; border-radius: 50%; border: 2px solid currentColor; border-right-color: transparent; animation: busy-spin 0.8s linear infinite; }

/* ---- PROBLEM REPORTS (ADR-0013) ----
   The form over any page, My reports, and the triage screens. What somebody wrote keeps its own
   line breaks - it is the one place they described what happened, and a paragraph flattened into
   one line is harder to read than it was to write. */
.mono { font-family: var(--mono); }
.report-description { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 13px; line-height: 1.5; margin-top: 4px; }
.report-full { background: var(--panel-alt); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; margin: 10px 0; }
.report-picture { font-size: 12.5px; margin-top: 4px; }
.report-context { margin-top: 12px; font-size: 12.5px; }
.report-context summary { cursor: pointer; color: var(--text-mute); }
.report-context dl, .report-context-list { display: grid; grid-template-columns: max-content 1fr; gap: 3px 12px; margin: 8px 0; font-size: 12.5px; }
.report-context dt, .report-context-list dt { color: var(--text-mute); }
.report-context dd, .report-context-list dd { margin: 0; overflow-wrap: anywhere; }
.report-shots { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; }
.report-shot { display: block; max-width: 100%; max-height: 260px; border: 1px solid var(--border); border-radius: 6px; margin-top: 6px; }
.report-state { font-weight: 700; }
.report-state.state-new { color: var(--yellow); }
.report-state.state-fixed { color: var(--teal); }
/* A toast's "Report this" sits after its sentence and is not the toast: clicking it opens the form
   and leaves the message where it was (ToastHost stops the click from dismissing it). */
.toast-report { margin-left: 10px; font-weight: 700; }

/* ---- THE LOG READER (ADR-0014 decision 5) ----
   One block per match, its neighbours from the same tab or request dimmed around it. A stack keeps
   its own line breaks and scrolls sideways inside its box rather than widening the page. */
.log-hit { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; margin: 8px 0; }
.log-line { font-size: 12.5px; line-height: 1.5; padding: 2px 0; overflow-wrap: anywhere; }
.log-line.neighbour { color: var(--text-mute); }
.log-line.match { color: var(--text); }
.log-line.level-wrn .log-level { color: var(--yellow); }
.log-line.level-err .log-level, .log-line.level-ftl .log-level { color: var(--red); }
.log-time, .log-level, .log-context { margin-right: 8px; font-size: 11.5px; }
.log-context { color: var(--text-mute2); }
.log-origin { margin-left: 6px; font-size: 11.5px; color: var(--text-mute); }
.log-detail summary { cursor: pointer; color: var(--text-mute); font-size: 11.5px; }
.log-detail pre { font-family: var(--mono); font-size: 11.5px; white-space: pre; overflow-x: auto; background: var(--panel-alt); border: 1px solid var(--border); border-radius: 6px; padding: 8px; margin: 4px 0; }
