/* ===========================================================================
   AGRO MEATS ACCOUNTS CRM -- branding, spec section 8
   ===========================================================================
   The palette was read from Logo_VIANDES_AGRO_INC.pdf, not assumed. The converted
   logo at public/logo.svg contains exactly two colours and they are these:

     rgb(0%, 57.255554%, 81.176758%)     = #0092CF   primary blue
     rgb(93.725586%, 25.097656%, 20.783997%) = #EF4035   accent red

   RULES ENFORCED HERE
     * #0092CF drives every primary interaction: buttons, links, active states,
       focus rings, the selected tab, the selected activity type.
     * #EF4035 appears in the logo and in genuine warnings ONLY -- the rate
       compression panel and hard errors. It is never a button, never a primary
       action, never a "save". Red reads as destructive.
     * Data-quality flags are AMBER, deliberately not red: they are honesty about
       the extraction, not business warnings, and must not compete with rate
       compression, which spec section 5 says must not be diluted.
     * Neutral dark slate text, generous whitespace, one clean sans serif.
     * Restrained. This is a tool someone stares at for eight hours.
   =========================================================================== */

:root {
  --blue:        #0092CF;
  --blue-dark:   #0079AC;
  --blue-darker: #005C83;
  --blue-tint:   #E8F5FC;
  --blue-tint-2: #D2ECF8;

  --red:         #EF4035;      /* warnings and the logo. NEVER an action. */
  --red-tint:    #FEF0EF;
  --red-border:  #F7BFBB;

  --amber:       #B4770E;      /* data-quality flags */
  --amber-tint:  #FDF6E7;
  --amber-border:#EFD9A5;

  --slate-900: #14202B;
  --slate-800: #1F2D3A;
  --slate-700: #38495A;
  --slate-600: #4E5F6D;
  --slate-500: #5F6E7A;
  --slate-400: #85929B;
  --slate-300: #C3CED6;
  --slate-200: #DFE6EB;
  --slate-100: #EEF2F5;
  --slate-50:  #F7F9FA;
  --white:     #FFFFFF;

  --green:      #1F7A4D;
  --green-tint: #EAF6F0;

  --radius: 6px;
  --radius-lg: 10px;
  --shadow-sm: 0 1px 2px rgba(20, 32, 43, .06), 0 1px 3px rgba(20, 32, 43, .05);
  --shadow-md: 0 4px 14px rgba(20, 32, 43, .10);
  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* =========================================================================
     PHASE 10 -- THE SCALES, WRITTEN DOWN
     =========================================================================
     Phases 5-9 used consistent-looking numbers chosen one screen at a time. They
     are now named, so "is this on the scale?" is a question with an answer and a
     new panel cannot introduce a 13px gap or a 15px heading by accident.

     SPACING -- a 4px base. Everything is a step; nothing is between steps.
     There is no --sp-5 at 20px on purpose: the jump 16 -> 24 is what keeps
     section rhythm distinguishable from element rhythm.  */
  --sp-1:  4px;   /* inside a chip, between a label and its value */
  --sp-2:  8px;   /* between siblings in a row */
  --sp-3: 12px;   /* cell padding, control gaps */
  --sp-4: 16px;   /* inside a card */
  --sp-6: 24px;   /* between cards, page gutter */
  --sp-8: 32px;   /* between major sections */

  /* TYPE -- 6 sizes, and that is the whole set. 14px is the body; the two below
     it are for metadata; the three above are headings and figures. Line heights
     are bound to the size rather than chosen per rule. */
  --fs-xs:  11.5px;  --lh-xs: 1.45;   /* provenance chips, legends, footnotes */
  --fs-sm:  12.5px;  --lh-sm: 1.5;    /* table metadata, labels, prose asides */
  --fs-md:  14px;    --lh-md: 1.5;    /* body, controls, table cells */
  --fs-lg:  15px;    --lh-lg: 1.4;    /* card titles */
  --fs-xl:  18px;    --lh-xl: 1.35;   /* section headings */
  --fs-2xl: 22px;    --lh-2xl: 1.25;  /* screen title */

  /* DENSITY -- one row height for every table in the application. 40px is the
     figure that came out of looking at 91 rows on a 1000px viewport: 34px reads
     as a spreadsheet and 48px puts a rep's fourth screenful below the fold. */
  --row-pad-y: 7px;
  --cell-pad-x: 12px;

  /* Minimum touch target. 44px is the phone figure; it applies to the row Log
     button and every control in the mobile composer. */
  --touch: 44px;
}

* { box-sizing: border-box; }

html, body, #root { height: 100%; }

body {
  margin: 0;
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.5;
  color: var(--slate-800);
  background: var(--slate-50);
  -webkit-font-smoothing: antialiased;
}

h1 { font-size: var(--fs-2xl); line-height: var(--lh-2xl); font-weight: 650;
     letter-spacing: -.015em; margin: 0 0 2px; color: var(--slate-900); }
h2 { font-size: var(--fs-xl); line-height: var(--lh-xl); font-weight: 650;
     margin: 0 0 var(--sp-2); color: var(--slate-900); letter-spacing: -.008em; }
/* The card eyebrow. Uppercase and small, so it labels its card without competing
   with the data inside it -- the one place letter-spacing earns its keep. */
h3 { font-size: var(--fs-sm); font-weight: 650; text-transform: uppercase;
     letter-spacing: .06em; color: var(--slate-500); margin: 0 0 var(--sp-3); }

a, .link {
  color: var(--blue-dark); text-decoration: none; background: none; border: 0;
  padding: 0; font: inherit; cursor: pointer; text-align: left;
}
a:hover, .link:hover { color: var(--blue-darker); text-decoration: underline; }

.muted { color: var(--slate-500); }
.tiny  { font-size: 11.5px; line-height: 1.45; }
.mono  { font-family: var(--mono); font-size: 12.5px; }
.num   { font-variant-numeric: tabular-nums; text-align: right; }
.req   { color: var(--red); }

/* Account data renders exactly as stored. This class marks every such element so
   a reviewer can see at a glance which strings are data and which are chrome. */
.verbatim { font-variant-ligatures: none; }

.boot { display: grid; place-items: center; height: 100%; color: var(--slate-400); font-size: 28px; }

/* ---- buttons -------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; gap: 7px;
  border: 1px solid transparent; border-radius: var(--radius);
  padding: 7px 13px; font: inherit; font-weight: 550; cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
  white-space: nowrap;
}
.btn:focus-visible, .link:focus-visible, select:focus-visible, input:focus-visible,
textarea:focus-visible, .tab:focus-visible, .lang:focus-visible, .user-btn:focus-visible {
  outline: 2px solid var(--blue); outline-offset: 2px;
}
.btn-primary { background: var(--blue); color: var(--white); }
.btn-primary:hover:not(:disabled) { background: var(--blue-dark); }
.btn-primary:disabled { background: var(--slate-300); color: var(--white); cursor: not-allowed; }
.btn-ghost { background: var(--white); color: var(--slate-700); border-color: var(--slate-300); }
.btn-ghost:hover { border-color: var(--blue); color: var(--blue-dark); }

/* ---- .btn-soft: the REPEATED primary action ------------------------------- */
/* Introduced in Phase 10 for one specific problem. "Log" is the most-used control
   in the application and it appears on EVERY row, so as a solid #0092CF block it
   was 91 saturated rectangles down the right-hand side of the rep's home screen --
   a wall of blue that made the screen feel loud and, worse, made the one action
   that should stand out indistinguishable from itself 90 times over.
   Same hue, same affordance, a third of the ink; it fills solid on hover and
   focus, so it still reads as the primary thing to do on the row.
   THE SOLID .btn-primary IS UNCHANGED and remains what a single decisive action
   wears -- Save, Apply this move, Log activity on the detail header. e2e_manager
   M26 asserts rx-confirm is exactly rgb(0,146,207), and it still is. */
.btn-soft {
  background: var(--blue-tint); color: var(--blue-darker); border-color: #A9DAF1;
}
.btn-soft:hover:not(:disabled) { background: var(--blue); border-color: var(--blue); color: var(--white); }
.btn-soft:disabled { background: var(--slate-100); border-color: var(--slate-200); color: var(--slate-400); cursor: not-allowed; }
.btn-sm { padding: 5px 10px; font-size: 13px; }
/* The in-row action. Sized so the ROW is 41px rather than the button forcing it to
   47px: with .btn-sm the Log button was the tallest thing in every row and set the
   density of the whole screen by accident. 6px shorter x 91 rows is most of a
   screenful. Mobile overrides it back to a 44px full-width target. */
.btn-row { padding: var(--sp-1) var(--sp-3); font-size: var(--fs-sm); line-height: 1.4; }
.btn-xs { padding: 2px 7px; font-size: 11.5px; }
.icon-btn { background: none; border: 0; font-size: 20px; line-height: 1; color: var(--slate-400);
            cursor: pointer; padding: 0 4px; margin-left: auto; }
.icon-btn:hover { color: var(--slate-700); }
.kbd { font-family: var(--mono); font-size: 10.5px; opacity: .8; border: 1px solid rgba(255,255,255,.45);
       border-radius: 3px; padding: 0 4px; }

input[type=text], input[type=search], input[type=date], input[type=number], select, textarea {
  font: inherit; color: var(--slate-800); background: var(--white);
  border: 1px solid var(--slate-300); border-radius: var(--radius); padding: 6px 9px;
}
input:disabled, select:disabled { background: var(--slate-100); color: var(--slate-400); }

/* ---- shell --------------------------------------------------------------- */
.shell { min-height: 100%; display: flex; flex-direction: column; }

.topbar {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-6);
  padding: var(--sp-2) var(--sp-6); background: var(--white);
  border-bottom: 1px solid var(--slate-200); box-shadow: var(--shadow-sm);
  position: sticky; top: 0; z-index: 30;
}
.brand { display: flex; align-items: center; gap: var(--sp-3); background: none; border: 0;
         cursor: pointer; padding: var(--sp-1) var(--sp-2) var(--sp-1) 0; border-radius: var(--radius); }
/* 44px, not 38px. The mark is a stacked wordmark inside a near-square viewBox
   (272 x 262), so at 38px the letters of AGRO rendered about 9px tall and read as
   a smudge. 44 is the largest that does not push the bar past 60px. A vertical
   rule separates it from the product name so the logo is not read as part of the
   typography. */
.logo { height: 44px; width: auto; display: block; }
.logo-lg { height: 62px; }
.brand-text { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.2;
              padding-left: var(--sp-3); border-left: 1px solid var(--slate-200); }
.brand-name { font-weight: 650; font-size: var(--fs-lg); color: var(--slate-900); letter-spacing: -.015em; }
.brand-sub { font-size: var(--fs-xs); color: var(--blue-dark); font-weight: 600; letter-spacing: .09em;
             text-transform: uppercase; }

.topbar-right { display: flex; align-items: center; gap: var(--sp-4); }
/* The identity block is metadata, not navigation, so it is separated from the two
   buttons on its left by a rule instead of by a wider gap. */
.whoami { display: flex; align-items: center; gap: var(--sp-2);
          padding-left: var(--sp-4); border-left: 1px solid var(--slate-200); }

.lang-toggle { display: inline-flex; border: 1px solid var(--slate-300); border-radius: var(--radius); overflow: hidden; }
.lang { background: var(--white); border: 0; padding: 5px 12px; font: inherit; font-weight: 600;
        font-size: 12.5px; color: var(--slate-500); cursor: pointer; }
.lang + .lang { border-left: 1px solid var(--slate-200); }
.lang-on { background: var(--blue); color: var(--white); }

.main { flex: 1; padding: 26px 28px 40px; max-width: 1680px; width: 100%; margin: 0 auto; }
.foot { padding: 12px 28px 20px; border-top: 1px solid var(--slate-200); background: var(--white); }

/* ---- login (dev) -------------------------------------------------------- */
.login-screen { min-height: 100%; display: grid; place-items: start center; padding: 56px 20px; }
.login-card { background: var(--white); border: 1px solid var(--slate-200); border-radius: var(--radius-lg);
              box-shadow: var(--shadow-md); padding: 32px; width: 100%; max-width: 680px; }
.login-brand { display: flex; align-items: center; gap: 16px; margin-bottom: 22px; }
/* The sign-in screen's language toggle sits at the far end of the brand row, where
   the top bar's own toggle is once you are signed in. */
.login-lang { margin-left: auto; }
.dev-banner { background: var(--amber-tint); border: 1px solid var(--amber-border);
              border-left: 4px solid var(--amber); border-radius: var(--radius);
              padding: 12px 14px; margin-bottom: 24px; color: #6B4A08; }
.dev-banner strong { display: block; margin-bottom: 4px; }
.dev-banner p { margin: 0; font-size: 12.5px; }
/* A six-item role filter does not need 615px. */
.login-card .field select { max-width: 260px; }
.user-list { list-style: none; padding: 0; margin: var(--sp-4) 0 0; display: flex; flex-direction: column; gap: var(--sp-1); }
.user-btn { width: 100%; display: grid; grid-template-columns: 1.4fr .9fr 60px 1.5fr; gap: 12px;
            align-items: center; background: var(--white); border: 1px solid var(--slate-200);
            border-radius: var(--radius); padding: 10px 14px; font: inherit; cursor: pointer; text-align: left; }
.user-btn:hover { border-color: var(--blue); background: var(--blue-tint); }
.user-name { font-weight: 600; color: var(--slate-900); }
.user-role { color: var(--slate-600); font-size: 12.5px; }
.user-rep { font-family: var(--mono); font-size: 12px; color: var(--blue-darker); }
.user-email { font-size: 12px; }

/* ---- list screen -------------------------------------------------------- */
.screen-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; margin-bottom: 16px; }
.counts { margin: 0; color: var(--slate-500); font-size: 12.5px; font-variant-numeric: tabular-nums; }

.scope-switch { display: inline-flex; border: 1px solid var(--slate-300); border-radius: var(--radius);
                overflow: hidden; background: var(--white); }
.tab { background: var(--white); border: 0; padding: 7px 15px; font: inherit; font-weight: 550;
       font-size: 13px; color: var(--slate-600); cursor: pointer; }
.tab + .tab { border-left: 1px solid var(--slate-200); }
.tab-on { background: var(--blue); color: var(--white); }

.summary { display: flex; gap: 1px; background: var(--slate-200); border: 1px solid var(--slate-200);
           border-radius: var(--radius); overflow: hidden; margin-bottom: var(--sp-4); }
.summary-cell { background: var(--white); padding: var(--sp-3) var(--sp-4); flex: 1; min-width: 0; }
/* Proportional figures on a standalone number, tabular only in columns -- these
   are read one at a time, not compared down a column. */
.summary-num { font-size: var(--fs-xl); font-weight: 650; color: var(--slate-900);
               font-variant-numeric: proportional-nums; line-height: 1.2;
               letter-spacing: -.01em; }
.summary-lab { font-size: var(--fs-xs); line-height: var(--lh-xs); color: var(--slate-500);
               margin-top: 1px; }

.toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3);
           background: var(--white); border: 1px solid var(--slate-200); border-radius: var(--radius);
           padding: var(--sp-3) var(--sp-4); box-shadow: var(--shadow-sm); }
.search { flex: 1 1 300px; min-width: 240px; }
.tool { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-sm);
        color: var(--slate-600); white-space: nowrap; }
.tool select, .tool input { font-size: 13px; }
.days { width: 62px; text-align: right; font-variant-numeric: tabular-nums; }
.threshold-note { margin: var(--sp-2) 0 var(--sp-4); }

.table-wrap { background: var(--white); border: 1px solid var(--slate-200); border-radius: var(--radius);
              overflow: auto; box-shadow: var(--shadow-sm); }
/* A refetch holds the previous render at reduced opacity instead of flashing a
   skeleton, so the page never jumps under a rep who is mid-scan. Same rule the
   dashboard's charts follow. */
.table-wrap.is-loading { opacity: .55; }
.table-wrap { transition: opacity .12s linear; }

table.grid { width: 100%; border-collapse: collapse; }
table.grid th { position: sticky; top: 0; z-index: 2; background: var(--slate-100);
                text-align: left; font-size: var(--fs-xs); font-weight: 650; text-transform: uppercase;
                letter-spacing: .05em; color: var(--slate-500);
                padding: var(--sp-2) var(--cell-pad-x);
                border-bottom: 1px solid var(--slate-200); white-space: nowrap; }
table.grid th.sortable { cursor: pointer; user-select: none; }
table.grid th.sortable:hover { color: var(--blue-dark); background: var(--slate-200); }
table.grid th.sorted { color: var(--blue-dark); }
table.grid th.sortable:focus-visible { outline: 2px solid var(--blue); outline-offset: -2px; }
/* The arrow is a fixed-width slot whether or not the column is the sorted one, so
   turning sort on does not shift the header label sideways. */
.sort-arrow { display: inline-block; width: 10px; margin-left: var(--sp-1);
              font-size: 9px; opacity: .55; text-align: center; }
th.sorted .sort-arrow { opacity: 1; }

/* Density: 7px vertical padding on 14px/1.5 text is a 40px single-line row. Middle
   alignment rather than top, so the 8 single-line cells sit on the optical centre
   of the two-line account cell instead of hanging from its first line. */
table.grid td { padding: var(--row-pad-y) var(--cell-pad-x);
                border-bottom: 1px solid var(--slate-100); vertical-align: middle; }
table.grid tbody tr:last-child > td { border-bottom: 0; }
table.grid tbody tr:hover > td { background: var(--slate-50); }
.col-credit, .col-cust_no { white-space: nowrap; }
.col-log { width: 1%; white-space: nowrap; text-align: right; }
/* Header alignment follows the cell it heads. A right-aligned money column under a
   left-aligned header is the single most common misalignment in a data table, and
   it is visible from across the room once you have seen it. */
th.col-credit { text-align: right; }
th.col-log { text-align: right; }
th.col-num, td.col-num { text-align: right; }

/* Column widths, so the slack is DISTRIBUTED rather than all landing in the one
   column that happens to be elastic. Before this the account column absorbed
   360px of empty space and the four columns to its right were crowded against the
   window edge. Percentages, not pixels: the same proportions hold at 1280 and at
   1680.

   INSIDE a min-width query rather than overridden in the phone block, and that is
   deliberate. A `td` percentage width has three classes of specificity; every
   sensible reset in the phone block has two, so overriding it there means either
   an !important or a contrived selector. Scoping it to the widths where columns
   exist at all means the rule simply is not there when the table is a card list --
   which is also the honest description of what is going on. (Found by measuring:
   the account cell was 92px wide inside a 366px card, i.e. 27% of it.) */
@media (min-width: 641px) {
  table.grid .col-cust_no { width: 8ch; }
  .list-screen table.grid .col-account { width: 27%; }
  .list-screen table.grid .col-contact { width: 19%; }
  .list-screen table.grid .col-last    { width: 12%; }
  .list-screen table.grid .col-fu      { width: 11%; }
  .list-screen table.grid .col-credit  { width: 11%; }
}
/* City has no data in any row (there is no city column in the salesheet), so it
   gets the narrowest width that still fits its header and centres the dash --
   a left-aligned em dash under a left-aligned header reads as a missing value in
   a column that has values. */
.col-city { width: 64px; text-align: center; }
th.col-city { text-align: center; }

/* Long account names truncate with a tooltip rather than wrapping, so every row is
   one height. The title attribute carries the full name, and textContent is
   untouched -- E38 compares all 91 names character for character across EN and FR.
   No max-width in ch: the truncation point is whatever the flex row leaves over,
   so it is correct at 1280px and at 1680px without a magic number. */
.cell-trunc { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acct-cell { display: flex; align-items: baseline; gap: var(--sp-2); min-width: 0; }
.acct-cell > .link { min-width: 0; flex: 0 1 auto; }
.acct-cell > .badges { flex: none; margin-left: 0; }

/* THE EMPTY STATE. Not a grey word in the middle of a white box: a heading, then
   the sentence that says what to do about it. */
.empty { text-align: center; padding: var(--sp-8) var(--sp-4); color: var(--slate-500); }
.empty-title { font-size: var(--fs-lg); font-weight: 600; color: var(--slate-700); }
.empty-hint { font-size: var(--fs-sm); color: var(--slate-500); margin: var(--sp-2) auto 0; max-width: 46ch; }
.empty-actions { margin-top: var(--sp-3); }

/* --- row highlighting by inactivity. Threshold is configurable, never fixed. ---
   PHASE 10 CORRECTION, and it is a legibility fix rather than a taste one.
   `row-never` used to tint the whole row --slate-100. On this data EVERY account
   has never been contacted, so 91 of 91 rows were grey: the table read as
   uniformly disabled, the hover highlight was invisible against it, and a
   highlight that fires on every row conveys nothing. Never-contacted is the
   BASELINE here, not an exception (the untouched panel says so in words), so it
   now carries the left rule and the pill only. warn and alert -- which are
   genuinely exceptional -- keep their tint. */
tr.row-warn  > td { background: var(--amber-tint); }
tr.row-warn:hover  > td { background: #FBF0D8; }
tr.row-alert > td { background: var(--red-tint); }
tr.row-alert:hover > td { background: #FCE4E2; }
tr.row-warn  > td:first-child { box-shadow: inset 3px 0 0 var(--amber); }
tr.row-alert > td:first-child { box-shadow: inset 3px 0 0 var(--red); }
tr.row-never > td:first-child { box-shadow: inset 3px 0 0 var(--slate-300); }
.never-pill { display: inline-block; background: var(--slate-100); color: var(--slate-600);
              border: 1px solid var(--slate-200); border-radius: 10px; padding: 0 var(--sp-2);
              font-size: var(--fs-xs); font-weight: 550; white-space: nowrap; }

/* Commodity rides INLINE after the account name on the desktop table, not on a
   second line. As a block it added 18px to all 91 rows to carry one short word --
   "Beef" -- and turned a 36px row into a 54px one, which is a third of a rep's
   screenful spent on a label. It becomes a block again on a phone, where the card
   has the vertical room and the horizontal room is what is scarce. */
.commodities { font-size: var(--fs-xs); color: var(--slate-500); white-space: nowrap; flex: none; }
.list-screen table.grid td.col-account { line-height: 1.4; }

/* The per-cell label that only mobile shows. Present in the DOM at every width so
   the phone layout needs no second markup path and no CSS-generated text (which
   could not be translated). */
.cell-label { display: none; }
.badges { display: inline-flex; flex-wrap: wrap; gap: 4px; margin-left: 7px; vertical-align: 1px; }
.badge { display: inline-block; border-radius: 10px; padding: 1px 8px; font-size: 11px; font-weight: 600;
         white-space: nowrap; }
.badge-primary { background: var(--blue-tint-2); color: var(--blue-darker); }
.badge-mute { background: var(--slate-200); color: var(--slate-600); }
.badge-data { background: var(--amber-tint); color: var(--amber); border: 1px solid var(--amber-border); }
.badge-warn { background: var(--red-tint); color: #B3241B; border: 1px solid var(--red-border); }
.chip-static { background: var(--slate-100); border: 1px solid var(--slate-200); border-radius: 4px;
               padding: 1px 6px; color: var(--slate-700); }

.list-foot { display: flex; align-items: center; gap: 16px; padding: 12px 2px; }
.legend { display: flex; align-items: center; gap: 8px; color: var(--slate-500); font-size: 11.5px; margin: 4px 0 0; }
.swatch { width: 13px; height: 13px; border-radius: 3px; display: inline-block; margin-left: 14px; }
.legend .swatch:first-child { margin-left: 0; }
.swatch-never { background: var(--slate-100); border: 1px solid var(--slate-300); }
.swatch-warn  { background: var(--amber-tint); border: 1px solid var(--amber); }
.swatch-alert { background: var(--red-tint); border: 1px solid var(--red); }

/* ---- log activity popover ---------------------------------------------- */
tr.log-row > td { background: var(--blue-tint); box-shadow: inset 3px 0 0 var(--blue); padding: 0; }
.log-inline { margin-bottom: 18px; }
.log-pop { background: var(--white); border: 1px solid var(--blue); border-radius: var(--radius);
           box-shadow: var(--shadow-md); padding: 14px 16px; margin: 10px;
           display: flex; flex-direction: column; gap: 10px; max-width: 760px; }
.log-inline .log-pop { margin: 0; }
.log-pop-head { display: flex; align-items: center; gap: 10px; }
.log-pop-anchor { color: var(--slate-500); font-size: 12.5px; }
.type-chips { display: flex; gap: 6px; }
.chip { background: var(--white); border: 1px solid var(--slate-300); border-radius: 999px;
        padding: 5px 14px; font: inherit; font-size: 13px; font-weight: 550; color: var(--slate-700);
        cursor: pointer; }
.chip:hover { border-color: var(--blue); color: var(--blue-dark); }
.chip-on { background: var(--blue); border-color: var(--blue); color: var(--white); }
.log-body { width: 100%; resize: vertical; font-size: 14px; }
.log-pop-foot { display: flex; align-items: center; justify-content: space-between; gap: 16px; }

/* Confirmations size to their own text instead of ruling a line across a 1,600px
   page for the word "Logged". Errors stay full width -- an error must not be
   possible to overlook, and its text is usually a sentence anyway. */
.flash { display: inline-flex; align-items: center; gap: var(--sp-2);
         background: var(--green-tint); border: 1px solid #B6DCC7; border-left: 4px solid var(--green);
         color: #14512F; border-radius: var(--radius); padding: var(--sp-2) var(--sp-3);
         margin: var(--sp-2) 0; font-size: 13px; align-self: flex-start; max-width: 100%; }
.flash::before { content: "✓"; font-weight: 700; color: var(--green); }
.inline-error { background: var(--red-tint); border: 1px solid var(--red-border);
                border-left: 4px solid var(--red); color: #A81F17; border-radius: var(--radius);
                padding: var(--sp-2) var(--sp-3); font-size: 13px; }
.inline-ok { color: var(--green); font-size: var(--fs-sm); }

/* ---- detail ------------------------------------------------------------- */
.detail-screen { display: flex; flex-direction: column; gap: var(--sp-4); }
.detail-top { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.detail-head { padding-bottom: var(--sp-1); }
.detail-sub { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; margin-top: var(--sp-1); }
/* 1.35fr / 1fr left a tall left column beside a short right one and a large void
   under Activity. The right column is now narrower and STICKY: follow-up and the
   feed stay in view while the salesheet block and the coverage rows scroll past,
   which is what a rep on a call actually needs, and the void goes away because the
   column is pinned rather than stretched. */
.detail-grid { display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(320px, 1fr);
               gap: var(--sp-4); align-items: start; }
.detail-col { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }
.detail-col-side { position: sticky; top: 84px; }

.card { background: var(--white); border: 1px solid var(--slate-200); border-radius: var(--radius);
        padding: var(--sp-4) var(--sp-4) var(--sp-4); box-shadow: var(--shadow-sm); }

dl.fields { margin: 0; display: flex; flex-direction: column; gap: 0; }
.field-row { display: grid; grid-template-columns: 148px minmax(0, 1fr); gap: var(--sp-3);
             padding: var(--sp-2) 0; border-bottom: 1px solid var(--slate-100); align-items: baseline; }
.field-row:last-child { border-bottom: 0; }
.field-row dt { color: var(--slate-500); font-size: var(--fs-sm); }
.field-row dd { margin: 0; color: var(--slate-900); display: flex; flex-wrap: wrap;
                align-items: baseline; gap: var(--sp-2); }

/* provenance: which values came from the salesheet and which a rep overrode.
   PHASE 10: "FROM THE SALESHEET" appears on nearly every field, so as a bordered
   uppercase pill it out-weighted the values it was annotating -- six identical
   badges louder than the phone number they were describing. The DEFAULT case is
   now borderless, lower-case-height and pale: present for anyone who looks, silent
   for anyone reading the data. The OVERRIDDEN case keeps the full pill, because
   that one is genuinely exceptional -- 3 of 2,973 rows -- and is the thing a
   reader needs to notice. Same words, same order, same tooltip. */
.prov { font-size: 10.5px; font-weight: 600; letter-spacing: .04em;
        border-radius: 3px; padding: 1px 5px; white-space: nowrap; }
.prov-src { background: none; color: var(--slate-400); border: 0; padding-left: 0;
            text-transform: lowercase; font-variant: small-caps; letter-spacing: .06em; }
.prov-cur { background: var(--blue-tint-2); color: var(--blue-darker); border: 1px solid #A9DAF1;
            text-transform: uppercase; }
.inline-edit { display: inline-flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; }

.notice { background: var(--blue-tint); border: 1px solid #BFE4F6; border-left: 4px solid var(--blue);
          border-radius: var(--radius); padding: 10px 13px; font-size: 12.5px; color: var(--blue-darker);
          margin-bottom: 14px; }

.coverage { border: 1px solid var(--slate-200); border-radius: var(--radius); padding: 12px 14px; margin-bottom: 10px; }
.coverage-mine { border-color: #A9DAF1; background: #FBFDFF; }
.coverage-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin-bottom: 8px; }
.coverage-rep { font-weight: 600; color: var(--slate-900); }

/* Genuine warning. The only red panel in the application. */
.warn-panel { background: var(--red-tint); border: 1px solid var(--red-border);
              border-left: 5px solid var(--red); border-radius: var(--radius); padding: 14px 16px; }
.warn-panel-title { font-weight: 700; color: #A81F17; display: flex; align-items: center; gap: 8px;
                    text-transform: uppercase; letter-spacing: .04em; font-size: 12.5px; }
.warn-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--red); display: inline-block; }
.warn-panel p { margin: 7px 0 0; color: #7E1712; font-size: 13px; }
.warn-panel-note { font-family: var(--mono); font-size: 12px; }

.flag-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.flag-item { background: var(--amber-tint); border: 1px solid var(--amber-border);
             border-left: 4px solid var(--amber); border-radius: var(--radius); padding: 9px 12px; }
.flag-title { font-weight: 650; color: #6B4A08; font-size: 12.5px; }
.flag-body { color: #6B4A08; font-size: 12.5px; margin-top: 3px; }

.feed { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0; }
.feed-item { padding: 11px 0 11px 14px; border-left: 2px solid var(--slate-200);
             position: relative; margin-left: 3px; }
.feed-item::before { content: ""; position: absolute; left: -5px; top: 16px; width: 8px; height: 8px;
                     border-radius: 50%; background: var(--blue); }
.feed-meta { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin-bottom: 3px; }
.feed-when { font-size: 12px; color: var(--slate-500); font-variant-numeric: tabular-nums; }
.feed-body { color: var(--slate-800); white-space: pre-wrap; }
.type-tag { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
            border-radius: 3px; padding: 1px 7px; background: var(--blue-tint-2); color: var(--blue-darker); }
.type-note { background: var(--slate-200); color: var(--slate-700); }
.type-voicemail { background: #EDE7F8; color: #4B3D80; }
.type-email { background: var(--green-tint); color: #14512F; }

.field { display: flex; flex-direction: column; gap: var(--sp-1); margin-bottom: var(--sp-3); }
.field-label { font-size: 12px; color: var(--slate-500); font-weight: 550; }
/* A date and a reason are short values; a full-width input for either reads as an
   unfinished form. Constrained, and the constraint lifts on a phone. */
.field-narrow input, .field-narrow select { max-width: 220px; }
.check { display: flex; align-items: center; gap: var(--sp-2); margin: var(--sp-3) 0; font-size: 13px; }
.check input { accent-color: var(--blue); }
.row-end { display: flex; justify-content: flex-end; gap: var(--sp-2); margin-top: var(--sp-3); }
/* A card's own action row gets a hairline above it, so the button belongs to the
   card instead of floating in the whitespace under the last field. */
.card > .row-end { border-top: 1px solid var(--slate-100); padding-top: var(--sp-3); }
.contact-editor { margin-top: var(--sp-1); }

@media (max-width: 1100px) {
  .detail-grid { grid-template-columns: minmax(0, 1fr); }
  .detail-col-side { position: static; }
  .summary { flex-wrap: wrap; }
}

.check-inline { gap: 6px; }
.check-inline input { margin: 0; }

/* Customer number: the alpha prefix is visually distinct from the digits, so a
   letter O can never be read as a zero. See src/components/CustNo.jsx. */
.cust-no { font-variant-numeric: slashed-zero tabular-nums; white-space: nowrap; }
.cust-no-alpha { font-weight: 700; color: var(--slate-900); }

/* ===========================================================================
   PHASE 6 -- Excel template / export / upload
   ===========================================================================
   Colour discipline, restated because this screen is where it is easiest to get
   wrong: the refusal panel is the ONLY red thing here, because a refused file is
   a genuine warning. Archived counts are AMBER -- archiving is bookkeeping, not
   a fault, and #EF4035 on "12 coverage rows archived" would read as data loss
   when nothing is ever deleted. Check and Apply are #0092CF like every other
   primary action in the product.
   =========================================================================== */

/* PHASE 10, THE DEDICATED PASS. This screen is a STEPPED TASK -- get a file, upload
   it, read the diff, confirm -- not a dashboard, so it is bounded like a document
   instead of stretching three short forms across a 1,600px window. Everything
   inside it was already on the token scale; what was wrong was width, weight and
   one real bug (see .up-summary). */
.up-screen { display: flex; flex-direction: column; gap: var(--sp-4);
  max-width: 1180px; }
.up-head { display: flex; flex-direction: column; gap: var(--sp-1); }
.up-intro { max-width: 84ch; margin: 0; }
.up-card { padding: 18px 20px; }
/* 15px, which is --fs-lg, which the token block calls "card titles". It was 14px --
   the body size -- so a card's own title weighed the same as the sentence under it. */
.up-h2 {
  font-size: var(--fs-lg); font-weight: 650; margin: 0 0 var(--sp-3);
  color: var(--slate-900); letter-spacing: -.005em;
}
/* The sub-heading inside a step: smaller than the step's own title, and the same
   uppercase eyebrow every other card in the application uses. */
.up-sum-h { margin: var(--sp-4) 0 var(--sp-2); }

.up-dl { display: flex; gap: 26px; flex-wrap: wrap; }
.up-dl-item { max-width: 30ch; display: flex; flex-direction: column; gap: 6px; }
.up-dl-item p { margin: 0; }
/* A bare `.btn` has a transparent border and no background, so two of the three
   download buttons were falling through to the browser's default grey -- three peer
   actions in three different treatments, one of them not from this design system at
   all. Template is the empty-handed option (ghost); the two exports are the ones a
   manager actually uses, so they get the soft blue. */

.up-pick { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.up-pick input[type=file] { font: inherit; font-size: 13px; }
.up-pick input[type=file]::file-selector-button {
  font: inherit; font-size: 13px; padding: 6px 12px; margin-right: 10px;
  border: 1px solid var(--slate-300); background: var(--white);
  border-radius: var(--radius); color: var(--slate-800); cursor: pointer;
}
.up-pick input[type=file]::file-selector-button:hover { border-color: var(--blue); color: var(--blue-dark); }

/* Bounded. A 60-character radio label in a full-width bordered box put 900px of
   empty white inside the control, which reads as a layout that gave up. */
.up-scope { border: 0; margin: 0 0 14px; padding: 0; max-width: 860px; }
.up-scope legend { padding: 0; margin-bottom: 8px; }
.up-radio {
  display: flex; gap: 10px; align-items: flex-start; padding: 9px 12px;
  border: 1px solid var(--slate-200); border-radius: var(--radius);
  margin-bottom: 6px; cursor: pointer; background: var(--white);
}
.up-radio:hover { border-color: var(--slate-300); }
.up-radio-on { border-color: var(--blue); background: var(--blue-tint); }
.up-radio input { margin-top: 3px; accent-color: var(--blue); }
.up-radio span { display: flex; flex-direction: column; gap: 2px; }
.up-radio em { font-style: normal; max-width: 92ch; }

.up-actions { display: flex; gap: 10px; align-items: center; margin-top: 4px; }
.up-confirm { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--slate-200); }
.btn-lg { padding: 9px 20px; font-size: 14px; }

.up-error { margin-top: 14px; }
.up-detail {
  margin: 8px 0 0; padding: 10px 12px; font-family: var(--mono); font-size: 11.5px;
  white-space: pre-wrap; background: var(--white); border: 1px solid var(--red-border);
  border-radius: var(--radius); color: var(--slate-800); max-height: 260px; overflow: auto;
}

.up-summary { margin: 12px 0 6px; flex-wrap: wrap; }
.summary-num-warn { color: var(--amber); }

.up-clean {
  margin: 10px 0; padding: 12px 14px; border-radius: var(--radius);
  background: var(--green-tint); border: 1px solid #BFE0CF; color: #14532D;
}
.up-clean p { margin: 4px 0 0; }

.up-problems { margin: 12px 0; border-radius: var(--radius); overflow: hidden; }
/* Tinted with dark text and a left rule, like every other panel in the product,
   instead of a saturated bar of white-on-#EF4035 / white-on-#B4770E. The refusal is
   still unmissable -- it has a red panel of its own above it saying the file was
   refused -- and the NOTICE version was the real problem: a solid amber bar for
   "2 things to be aware of" shouted as loudly as the refusal did. */
.up-problems-head { padding: var(--sp-2) var(--sp-3); font-weight: 650;
  font-size: var(--fs-sm); border-bottom: 1px solid rgba(20, 32, 43, .09); }
.up-problems ul { list-style: none; margin: 0; padding: 0; }
/* 92px wrapped "Rows 14, 15, 16" onto two lines and made a one-line problem two
   rows tall. 116px holds four row numbers, which is what upload_validate groups. */
.up-problems li {
  display: grid; grid-template-columns: 116px 1fr; gap: 2px var(--sp-3);
  padding: 9px 12px; border-top: 1px solid rgba(20, 32, 43, .07);
}
.up-problem-row { font-size: 11.5px; font-weight: 650; padding-top: 1px; }
.up-problem-text { font-size: 13px; }
.up-problem-detail { grid-column: 2; font-size: 11.5px; color: var(--slate-600); }
.up-problems-error { background: var(--red-tint); border: 1px solid var(--red-border);
  border-left: 4px solid var(--red); }
.up-problems-error .up-problems-head { background: #FDE4E1; color: #A81F17; }
.up-problems-notice { background: var(--amber-tint); border: 1px solid var(--amber-border);
  border-left: 4px solid var(--amber); }
.up-problems-notice .up-problems-head { background: #FBEFD5; color: #6B4A08; }

.up-section { margin: 8px 0; border: 1px solid var(--slate-200); border-radius: var(--radius); }
.up-section-head {
  display: flex; gap: 10px; align-items: center; width: 100%; text-align: left;
  padding: 9px 12px; background: var(--slate-50); border: 0; cursor: pointer;
  font: inherit; font-size: 13px; color: var(--slate-800); border-radius: var(--radius);
}
.up-section-head:hover { background: var(--slate-100); }
.up-caret { color: var(--slate-400); width: 10px; }
.up-section .table-wrap { border: 0; border-top: 1px solid var(--slate-200); border-radius: 0; }
/* The six preview tables carry `class="grid"` as of Phase 10 -- they were bare
   <table>s rendering with the browser's default 1px-padding cells, in a screen
   where every other table is on the density scale. And their identifying columns
   are bounded, so the slack lands in the elastic column (an account name, a value)
   instead of giving 110px to a two-digit worksheet row number. Inside the same
   min-width query as every other ch-based column width in this file: below it the
   table scrolls rather than being re-laid out. */
@media (min-width: 641px) {
  .up-section table.grid .up-c-row   { width: 5ch; }
  .up-section table.grid .up-c-cust  { width: 10ch; }
  .up-section table.grid .up-c-rep   { width: 8ch; }
  .up-section table.grid .up-c-ent   { width: 13ch; }
  .up-section table.grid .up-c-field { width: 17ch; }
  .up-section table.grid .up-c-cmdty { width: 12ch; }
}
.up-section .flag-body { display: block; padding: 10px 12px; border-top: 1px solid var(--slate-200); }
.up-trunc { padding: 6px 12px; margin: 0; }

.up-old { color: var(--slate-500); text-decoration: line-through; }
.up-new { color: var(--blue-darker); font-weight: 600; }
.up-notes { margin-top: 16px; }
.up-applied { border-color: #BFE0CF; }

/* ---- THE SUMMARY STRIP, WHICH WAS CLIPPING ITS MOST IMPORTANT CELL --------
   `.summary` is a non-wrapping flex row with `overflow: hidden`, and this strip had
   SIXTEEN cells in it with a 96px floor -- 1,536px of cells. At the 1,440px the
   Phase 6 screenshots were taken at, the last three were simply cut off the right
   edge, and one of them was `total_changes`: the number that says whether this
   upload does anything at all. The tests never saw it because a clipped cell keeps
   its textContent -- a screen can lie without a single assertion failing.
   A wrapping GRID instead: every cell is at least 118px (the French labels are
   longer -- « Lignes de couverture inchangées » -- and 96px broke them over four
   lines), and a row that does not fit becomes a second row. Nothing is hidden. */
.up-summary { display: grid; gap: 1px;
  grid-template-columns: repeat(auto-fit, minmax(118px, 1fr)); }
.up-summary .summary-cell { min-width: 0; }
/* EXPLICIT COLUMN COUNTS, because auto-fit leaves a hole and a hole in a strip of
   counts is the worst thing a count can look like -- an empty bordered box reads as
   a cell whose number failed to load (the same reasoning as the phone summary's
   spanning last cell). auto-fit gave 9 columns for the 12-cell strip: nine cells,
   then three, then six empty grey boxes. 12 = 6x2, 8 = 4x2, 3 = 3x1, and those
   counts are fixed by the arrays in Upload.jsx directly above the markup. */
.up-summary-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.up-summary-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.up-summary-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }

/* The headline figure. One number, its own line, the size the number deserves. */
.up-total { display: flex; align-items: center; gap: var(--sp-6); flex-wrap: wrap;
  background: var(--blue-tint); border: 1px solid #BFE4F6;
  border-left: 4px solid var(--blue); border-radius: var(--radius);
  padding: var(--sp-3) var(--sp-4); margin: var(--sp-3) 0 0; }
.up-total-fig { display: flex; align-items: baseline; gap: var(--sp-3); }
.up-total-fig .summary-num { font-size: 30px; line-height: 1.1; font-weight: 680;
  letter-spacing: -.02em; color: var(--blue-darker);
  font-variant-numeric: proportional-nums; }
.up-total-fig .summary-lab { font-size: var(--fs-md); color: var(--blue-darker);
  font-weight: 600; margin: 0; }
.up-total-note { margin: 0; font-size: var(--fs-sm); line-height: var(--lh-sm);
  color: var(--slate-600); }

/* The standing rules of the import path. NOT three amber panels: amber means a
   data-quality flag in this application, and these are true of every upload. */
.up-rules { margin: var(--sp-4) 0 0; }
.up-rules ul { margin: 0; padding: 0; list-style: none;
  border: 1px solid var(--slate-200); border-radius: var(--radius);
  background: var(--slate-50); }
.up-rules li { padding: var(--sp-2) var(--sp-4); font-size: var(--fs-sm);
  line-height: 1.55; color: var(--slate-600); max-width: 110ch; }
.up-rules li + li { border-top: 1px solid var(--slate-200); }

/* =============================================================================
   PHASE 7 -- SALES MANAGER
   =============================================================================
   Branding, restated because this is the screen where it is easiest to get wrong:
   #0092CF is every primary action INCLUDING "Apply this move", and #EF4035 appears
   only on the archive warning and the refusal panel. A reassignment feels
   destructive, so it is made unmistakable by the sentence above the button, not by
   turning the button red. Spec section 8.
   ========================================================================== */

.mgr-screen { display: flex; flex-direction: column; gap: 14px; }
.mgr-intro { max-width: 86ch; margin: 6px 0 0; }
.mgr-tabs { align-self: flex-start; }
.mgr-scope { max-width: 100ch; margin: -6px 0 0; }

/* ---- the coverage set: what replaces the owner column ---------------------
   Chips wrap, so a five-rep account is two lines rather than a horizontal
   scrollbar, and the whole set is legible without opening the row. */
/* PHASE 10: the count pill and the chips share the first line instead of the pill
   taking a line to itself. nowrap on the OUTER flex and wrap on the inner chip
   list, because a wrapping flex container decides its line breaks from each item's
   max-content size before any shrinking happens -- so `min-width: 0` on the chip
   list was not enough to stop the whole list being pushed to a second line by a
   60px pill. Four and five reps still wrap INSIDE the chip list, which is the
   documented behaviour and better than a horizontal scrollbar. */
.cov-set { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: nowrap; min-width: 0; }
.cov-set > .cov-count { flex: none; }
.cov-set > .cov-chips { flex: 1 1 auto; min-width: 0; }
.cov-count {
  font-size: 11px; font-weight: 700; color: var(--blue-darker);
  background: var(--blue-tint-2); border-radius: 10px; padding: 1px 8px;
  white-space: nowrap;
}
.cov-chips { display: flex; flex-wrap: wrap; gap: 5px; }
.cov-chip {
  display: inline-flex; align-items: baseline; gap: 6px; font: inherit; font-size: 12px;
  background: var(--white); border: 1px solid var(--slate-300); border-radius: 4px;
  padding: 2px 7px; cursor: pointer; color: var(--slate-700); text-align: left;
}
.cov-chip:hover:not(:disabled) { border-color: var(--blue); background: var(--blue-tint); }
.cov-chip:disabled { cursor: default; }
/* Rep code gets the same font-independent treatment as a customer number: this
   data set's whole history is glyph confusion. */
.cov-rep {
  font-family: var(--mono); font-weight: 700; font-size: 12px; color: var(--blue-darker);
  font-variant-numeric: slashed-zero;
}
.cov-cmdty { color: var(--slate-600); }
.cov-flag {
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
  color: var(--amber); background: var(--amber-tint); border: 1px solid var(--amber-border);
  border-radius: 3px; padding: 0 4px;
}
.cov-notes {
  font-size: 10.5px; font-weight: 700; color: var(--green); background: var(--green-tint);
  border-radius: 8px; padding: 0 5px; font-variant-numeric: tabular-nums;
}
/* An archived coverage row after a reassignment. Struck through and labelled --
   it still exists and still holds its notes, and that is the point. */
.cov-chip-archived {
  background: var(--slate-50); border-style: dashed; border-color: var(--slate-300);
  color: var(--slate-400);
}
.cov-chip-archived .cov-rep { color: var(--slate-400); text-decoration: line-through; }
.cov-chip-archived .cov-cmdty { text-decoration: line-through; }
.cov-chip-archived .cov-flag { color: var(--slate-500); background: var(--slate-100);
  border-color: var(--slate-200); }

.col-coverage { min-width: 300px; }
.col-reps, .col-when { white-space: nowrap; }

/* Manager table column widths, same reasoning as the rep list's: distribute the
   slack instead of dropping it all in one elastic column. Coverage is the widest
   because it carries up to five chips. */
@media (min-width: 641px) {
  .mgr-screen table.grid .col-account  { width: 24%; }
  .mgr-screen table.grid .col-coverage { width: 30%; }
  .mgr-screen table.grid .col-when     { width: 11%; }
  .mgr-screen table.grid .col-fu       { width: 9%; }
  .mgr-screen table.grid .col-credit   { width: 9%; }
}

/* ---- the reassignment panel ---------------------------------------------- */
/* PHASE 10, THE DEDICATED PASS. This is the most consequential control in the
   application -- it moves a book and archives coverage -- and it had had structural
   polish only. Four things were wrong, all of them legibility rather than taste:
     1. The panel was 1,540px wide with 700px of content in it, so a 40-character
        radio label stretched a full-width bordered box across the window and the
        reason input was a 1,500px slot for six words.
     2. THE PREVIEW HAD NO BEFORE/AFTER. It stated the outcome in three prose
        bullets, which means the manager assembles the diff in their head.
     3. The notes-affected count was the fifth of seven identical cells.
     4. The confirm button sat alone in 60px of whitespace under a muted footnote.
   The panel is now a bounded column, the diff is a diff, the note count is a
   figure, and the confirm restates the move in a sentence with the real values in
   it. #EF4035 appears nowhere in this block: red is warnings only. */
.rx-panel { padding: 18px 20px; border-color: #A9DAF1; box-shadow: var(--shadow-md);
  max-width: 1180px; }
.rx-panel .up-h2 { font-size: var(--fs-lg); }
.rx-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.rx-acct-name { font-weight: 500; color: var(--slate-600); }
/* The current coverage, labelled. A bare row of rep chips under the title does not
   say "this is what is true right now", which is the one thing it has to say. */
.rx-today { margin: var(--sp-3) 0 var(--sp-4); }
.rx-today h3 { margin-bottom: var(--sp-2); }
.rx-grid { display: flex; gap: 22px; flex-wrap: wrap; border: 0; margin: 14px 0 0; padding: 0; }
.rx-field { display: flex; flex-direction: column; gap: 4px; min-width: 320px; }
.rx-field select, .rx-field input[type=text] { font: inherit; font-size: 13px; padding: 6px 8px;
  border: 1px solid var(--slate-300); border-radius: var(--radius); background: var(--white); }
.rx-what { border: 0; margin: var(--sp-4) 0 0; padding: 0; max-width: 780px; }
.rx-what legend { padding: 0; margin-bottom: 6px; }
.rx-cmdty-picks { display: flex; gap: 14px; flex-wrap: wrap; font-style: normal; margin-top: 4px; }
.rx-cmdty { display: inline-flex; align-items: center; gap: 5px; font-size: 13px;
  color: var(--slate-700); }
.rx-cmdty input { accent-color: var(--blue); }
/* `min-width: 100%` beat the max-width it was written next to, so this was a
   1,500px single-line input. A reason is a short sentence and now looks like one. */
.rx-reason { margin: var(--sp-4) 0; min-width: 0; max-width: 640px; }
.rx-preview { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--slate-200); }
.rx-preview-head { display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; }
.rx-preview-head h3 { margin-bottom: var(--sp-2); }
/* "Nothing has been written" is a fact about the state of the screen, so it reads
   as a status chip on the heading line rather than as a full-width banner louder
   than the diff it introduces. */
.rx-dry { font-size: var(--fs-xs); line-height: var(--lh-xs); font-weight: 600;
  color: var(--blue-darker); background: var(--blue-tint); border: 1px solid #BFE4F6;
  border-radius: 10px; padding: 1px var(--sp-2); }
.rx-line { margin: var(--sp-2) 0 var(--sp-3); font-size: var(--fs-md); }

/* ---- THE BEFORE / AFTER DIFF -------------------------------------------- */
/* A four-column grid rather than a table: two of the four cells are chip sets and
   one is a glyph, so there is no column here a manager would ever sort. The rep
   column is fixed so the two rows' arrows line up under each other, which is what
   makes the pair read as one change instead of two statements. */
.rx-diff { border: 1px solid var(--slate-200); border-radius: var(--radius);
  background: var(--slate-50); margin: var(--sp-3) 0; overflow: hidden; }
/* The before column is bounded rather than elastic: with two 1fr columns the
   arrow ended up 300px to the right of the chips it points away from, which reads
   as two unrelated cells rather than as one before/after. */
.rx-diff-head, .rx-diff-row {
  display: grid;
  grid-template-columns: minmax(170px, 250px) minmax(140px, 280px) 22px minmax(0, 1fr);
  gap: var(--sp-3); align-items: baseline; padding: var(--sp-2) var(--sp-4);
}
.rx-diff-head { font-size: var(--fs-xs); line-height: var(--lh-xs); font-weight: 650;
  text-transform: uppercase; letter-spacing: .05em; color: var(--slate-500);
  background: var(--slate-100); border-bottom: 1px solid var(--slate-200); }
.rx-diff-row { background: var(--white); padding: var(--sp-3) var(--sp-4); }
.rx-diff-row + .rx-diff-row { border-top: 1px solid var(--slate-100); }
.rx-diff-rep { display: flex; align-items: baseline; gap: var(--sp-2); min-width: 0; }
.rx-diff-name { color: var(--slate-700); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.rx-diff-side { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap;
  min-width: 0; }
.rx-diff-arrow { color: var(--slate-400); font-size: 15px; text-align: center; }
.rx-diff-none { font-size: var(--fs-sm); color: var(--slate-500); }
.rx-diff-tag { font-size: var(--fs-xs); color: var(--slate-500); }
.rx-diff-legend { margin: 0; padding: var(--sp-2) var(--sp-4) var(--sp-3);
  font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--slate-500);
  border-top: 1px solid var(--slate-100); background: var(--slate-50); }

/* One chip per commodity. FILLED = moves; outline = stays. The distinction is
   never carried by hue alone: the sentence above the diff names what moves, the
   legend under it says which treatment is which, and every chip carries its own
   word. */
.rx-chips { display: inline-flex; flex-wrap: wrap; gap: var(--sp-1); }
.rx-chip { display: inline-block; font-size: var(--fs-sm); line-height: 1.5;
  border-radius: 4px; padding: 1px var(--sp-2); background: var(--white);
  border: 1px solid var(--slate-300); color: var(--slate-700); white-space: nowrap; }
.rx-chip-moves { background: var(--blue); border-color: var(--blue); color: var(--white);
  font-weight: 600; }

/* THE NOTES DECISION. Blue-tinted, not red: nothing is being destroyed, and
   colouring it red would say the opposite of what the words say. The count leads,
   because "how many notes does this touch" is the question, and it was previously
   answerable only by reading the fifth cell of a seven-cell strip. */
.rx-notes {
  display: flex; align-items: flex-start; gap: var(--sp-4);
  background: var(--blue-tint); border: 1px solid #BFE4F6; border-left: 4px solid var(--blue);
  border-radius: var(--radius); padding: var(--sp-3) var(--sp-4); margin: var(--sp-3) 0;
}
.rx-notes-fig { flex: none; width: 108px; text-align: center; padding-right: var(--sp-4);
  border-right: 1px solid #BFE4F6; }
.rx-notes-num { font-size: 34px; line-height: 1.05; font-weight: 680;
  letter-spacing: -.02em; color: var(--blue-darker);
  font-variant-numeric: proportional-nums; }
.rx-notes-num-lab { font-size: var(--fs-xs); line-height: var(--lh-xs); color: var(--blue-darker);
  margin-top: var(--sp-1); }
.rx-notes-body { min-width: 0; }
.rx-notes-title { font-weight: 700; color: var(--blue-darker); margin-bottom: 6px; }
.rx-notes p { margin: 6px 0 0; font-size: 13px; color: var(--slate-700); max-width: 92ch; }

.rx-sum { margin: var(--sp-4) 0 0; }
.rx-sum h3 { margin-bottom: var(--sp-2); }

.rx-effects { margin: 12px 0; padding-left: 20px; font-size: 13px; color: var(--slate-700); }
.rx-effects li { margin: 4px 0; max-width: 96ch; }

/* Genuine warnings, so amber/red is correct here. */
.rx-warn {
  background: var(--amber-tint); border: 1px solid var(--amber-border);
  border-left: 4px solid var(--amber); border-radius: var(--radius);
  padding: 11px 14px; margin: 12px 0;
}
.rx-warn-title { font-weight: 700; color: var(--amber); margin-bottom: 4px; }
.rx-warn ul { margin: 4px 0 0; padding-left: 20px; }
.rx-warn li { font-size: 13px; color: #7A5108; margin: 3px 0; max-width: 96ch; }

.rx-reimport { max-width: 96ch; margin: 10px 0 0; }
/* THE RECEIPT. `.flash` is an inline-flex strip sized to its own text, which is
   right for the word "Logged" and wrong here: this one carries a title, two
   sentences and a button, and as a row they were three cramped columns of prose
   with the reset button wedged against the right edge. Block, so it reads
   top-to-bottom like the confirmation of a consequential action should. */
.rx-applied { display: block; margin-top: var(--sp-6); padding: var(--sp-3) var(--sp-4);
  max-width: 96ch; }
.rx-applied::before { margin-right: var(--sp-2); }
.rx-applied strong { font-size: var(--fs-lg); }
.rx-applied p { margin: var(--sp-2) 0 0; font-size: 13px; max-width: 92ch; }
.rx-applied .btn { margin-top: var(--sp-3); }

/* ---- THE CONFIRM, SHARED BY THE TWO SCREENS THAT WRITE ------------------- */
/* Used by the reassignment panel AND the Excel upload, because they are the same
   moment: the last step before something is written, at the bottom of a long page
   of diff the reader has scrolled through.
   A decisive action needs to be unmistakable, and #EF4035 is not available for it:
   red is warnings only, and a red button here would say "this destroys something"
   when nothing is ever destroyed. So the SENTENCE does the work -- the change
   restated with its real values, plus what goes to the audit log -- and the button
   is the unchanged solid #0092CF that every single decisive action in the product
   wears (e2e_manager M26 asserts exactly that, and it still holds). */
.confirm-box { background: var(--blue-tint); border: 1px solid #BFE4F6;
  border-left: 4px solid var(--blue); border-radius: var(--radius);
  margin-top: var(--sp-6); padding: var(--sp-4); }
.confirm-lead { margin: 0; font-size: var(--fs-md); line-height: 1.45;
  color: var(--slate-900); max-width: 90ch; }
.confirm-sub { margin: var(--sp-2) 0 var(--sp-4); font-size: var(--fs-sm);
  line-height: var(--lh-sm); color: var(--slate-600); max-width: 90ch; }
/* NO `quotes` OVERRIDE. <q> takes its marks from the element's language, and the
   browser's defaults are already right -- "…" in English and « … » in French. The
   first version of this rule hard-coded English curly quotes and put them around a
   French sentence, which is precisely the kind of leak the French pass looks for. */
.confirm-reason { color: var(--slate-900); font-weight: 550; }
.confirm-missing { color: var(--amber); font-weight: 600; }
.confirm-box .up-actions { margin: 0; }

/* The manager toolbar carries nine controls; without wrapping it pushes the
   table off screen at 1280px, which is the width of the office laptops. */
.mgr-screen .toolbar { row-gap: 10px; }
.mgr-screen .summary-cell { min-width: 104px; }

/* ===========================================================================
   PHASE 8 -- THE TEAM MANAGER'S SCREENS
   ===========================================================================
   Branding, spec section 8: #0092CF (--blue) drives everything primary,
   including the timeline bars. #EF4035 (--red) is NOT used here at all, on
   purpose: nothing on this screen is a warning. The roster/visibility gap is
   correct and deliberate behaviour, so it gets the blue informational treatment
   and prose -- making it red would say "something is wrong", and what is wrong
   is only ever a manager misreading a partial number as a whole one.
   =========================================================================== */

.team-screen { display: flex; flex-direction: column; gap: 16px; }

/* The gap panel. Wider line-height than a normal notice because it is prose a
   manager has to actually read, not a label. */
.team-gap { padding: 14px 16px; }
.team-gap p { margin: 8px 0 0; font-size: 13px; line-height: 1.55; }
.team-panel-title { font-weight: 700; color: var(--blue-dark); font-size: 13px;
                    letter-spacing: .01em; }

.team-own { padding: 14px 16px; }
.team-own p { margin: 6px 0 0; font-size: 13px; line-height: 1.5; }

.team-roster-card, .team-rep, .team-timeline { padding: 14px 16px; }

/* The selected roster row, so the drill-down above and the row below are
   visibly the same rep. */
.row-selected > td { background: var(--blue-tint); }

/* Right-align every count. tabular-nums so 1 and 130 line up in a column a
   manager scans vertically. */
.col-num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }

.team-readonly { margin: 12px 0; }
.team-readonly p { margin: 6px 0 0; font-size: 13px; line-height: 1.5; }

/* Team roster: nothing in this table benefits from wrapping. A rep name, a
   membership badge, a count and an access badge are all short, and letting them
   wrap made the nine roster rows four different heights. The table scrolls
   horizontally if it has to -- the manager screens are desktop-first by decision
   (see the mobile block at the foot of this file). */
.team-screen table.grid td, .team-screen table.grid th { white-space: nowrap; }
.team-screen table.grid .badges { flex-wrap: nowrap; }
/* ... except the two places that are PROSE. The secondary-member sentence is a
   full sentence in a table cell; nowrap on it pushed the roster 300px wider than
   the window and put the counts behind a horizontal scrollbar. It wraps, inside a
   bounded column. */
.team-screen table.grid td .tiny,
.team-screen table.grid td .muted { white-space: normal; }
.team-screen table.grid td [data-testid="team-secondary-note"] { max-width: 46ch; }

/* ---- PHASE 10: off-roster rows ------------------------------------------- */
/* Inside the gap panel, separated by a rule rather than by a second card, because
   it is the same subject: the difference between the roster and the book. Not red
   and not amber -- nothing here is broken and nothing is lost, and #EF4035 on a
   correct-but-surprising number would tell a manager to escalate. */
.team-offroster { margin-top: 14px; padding-top: 12px; border-top: 1px solid #BFE4F6; }
.team-offroster p { margin: 6px 0 0; font-size: 13px; line-height: 1.55; }
.team-offroster-list {
  list-style: none; margin: 10px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 4px;
}
.team-offroster-list li {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; font-size: 13px;
}
.team-offroster-list .chip-static { font-size: 12px; }
.team-offroster-open { margin-top: var(--sp-3); }

/* ---- PHASE 10, SECOND PASS: the off-roster rows, as a real section --------- */
/* Directly under the roster card and styled as its peer, because it is the rest of
   the same book. Still not red and still not amber: nothing is broken. The
   disclosure is the same control the upload preview's sections use -- one idiom for
   "a list that might be three rows or three hundred". */
.team-offroster-card { padding: 14px 16px; }
.team-offroster-card .up-section-head { margin: var(--sp-2) 0 0; width: auto;
  border: 1px solid var(--slate-200); }
.team-offroster-card .table-wrap { margin-top: var(--sp-2); }
.team-offroster-card p.tiny { max-width: 110ch; }

/* ---- activity over time -------------------------------------------------- */
/* Only rendered when there is something to draw. The zero state is prose, below. */
.team-bars { display: flex; align-items: flex-end; gap: 4px; height: 120px;
             padding: 10px 0 0; border-bottom: 1px solid var(--slate-200); }
.team-bar-col { flex: 1; display: flex; flex-direction: column; justify-content: flex-end;
                align-items: center; height: 100%; min-width: 0; }
.team-bar { width: 70%; min-height: 2px; background: var(--blue); border-radius: 2px 2px 0 0; }
.team-bar-lab { color: var(--slate-400); margin-top: 4px; white-space: nowrap;
                font-variant-numeric: tabular-nums; }

/* THE HONEST EMPTY STATE. Deliberately not centred grey text: it carries real
   numbers and has to read as a statement of fact, not as "no data". */
.team-zero { background: var(--slate-50, #F8FAFC); border: 1px solid var(--slate-200);
             border-left: 4px solid var(--blue); border-radius: 4px;
             padding: 14px 16px; margin: 10px 0; }
.team-zero p { margin: 6px 0 0; font-size: 13px; line-height: 1.55; }
.team-zero-head { font-weight: 650; color: var(--slate-900); }

/* ===========================================================================
   PHASE 9 -- THE EXECUTIVE DASHBOARD, AND THE VISUALISATION PALETTE
   ===========================================================================
   This block is the BRAND INSTANCE of the data-viz method: the parameters the
   method needs (ramps, categorical order, de-emphasis, status, surfaces, ink,
   typeface), filled in with Viandes Agro's own colours instead of a placeholder
   palette. Nothing about the method changed; only these values.

   Read from Logo_VIANDES_AGRO_INC.pdf, not assumed: #0092CF and #EF4035 are the
   only two colours in the logo.

   ---------------------------------------------------------------------------
   THE BRAND BLUE RAMP  (single hue, generated -- not eyeballed)
   ---------------------------------------------------------------------------
   #0092CF converted to OKLCH is L 0.6253, C 0.1365, H 236.98. Every step below
   holds that hue and chroma (chroma clamped to the sRGB gamut) and moves only
   lightness, so the ramp is one hue by construction rather than by taste:

     100 #D1ECFF  1.22:1     400 #0092CF  3.49:1   <- brand primary, series slot 1
     150 #AEDFFF  1.42:1     450 #0480B6  4.40:1
     200 #88D1FF  1.67:1     500 #0071A2  5.41:1
     250 #5BC3FF  1.96:1     550 #00628D  6.71:1
     300 #3FB3F2  2.35:1     600 #015378  8.37:1
     350 #29A3E1  2.83:1     650 #004565 10.30:1
                             700 #003752 12.59:1
   (ratios are WCAG contrast against the white chart surface)

   ---------------------------------------------------------------------------
   WHAT WAS VALIDATED, WITH THE SCRIPT, NOT BY EYE
   ---------------------------------------------------------------------------
   dataviz scripts/validate_palette.js, light mode, surface #FFFFFF:

   1. Series slot 1 alone -- "#0092CF"
        lightness band PASS (L 0.625 in 0.43-0.77) - chroma PASS (0.137)
        contrast PASS (3.49:1)                      -> ALL CHECKS PASS
   2. The pair that touches in every stacked mark -- "#0092CF,#4E5F6D"
        CVD separation   PASS  dE 16.8 (deutan), 18.7 (tritan)   [target >= 8]
        normal-vision    PASS  dE 18.3                            [floor >= 15]
        contrast         PASS  both >= 3:1 (3.49 and 6.60)
        chroma           FAIL  #4E5F6D at 0.031 -- DELIBERATE, SEE BELOW
   3. The activity family, as a 2-step ordinal ramp -- "#3FB3F2,#0092CF" --ordinal
        monotone L PASS - adjacent dL 0.105 PASS (>= 0.06)
        light-end contrast PASS (2.35:1, >= 2.0)  - single hue PASS (spread 0deg)
        -> ALL CHECKS PASS

   THE ONE FAILING CHECK IS FAILED ON PURPOSE. The chroma floor (C >= 0.10)
   exists so that a hue doing IDENTITY work cannot read as grey. #4E5F6D is not
   an identity hue: it is the de-emphasis / "not yet" role, and reading as grey is
   the entire point -- grey means "no activity has been logged here". Identity is
   carried by the legend, the direct labels and the table view, never by hue
   alone, which is what the floor is protecting. Every other gate the pair has to
   clear -- including the hard normal-vision floor of 15 -- it clears.

   Rejected on measurement, not on taste: #85929B (dE 11.9 normal, below the 15
   floor) and #C3CED6 (1.60:1, below 3:1 for a mark).

   ---------------------------------------------------------------------------
   #EF4035 IS NOT IN THIS BLOCK AS A SERIES COLOUR AND NEVER WILL BE
   ---------------------------------------------------------------------------
   Warnings only, per spec section 8 and the standing rule. Nothing on this
   dashboard is a warning: 2,973 rows never contacted is a baseline to improve
   from, not a fault, and painting it red would editorialise a number the
   executive is being asked to read. --viz-status-critical exists for a genuine
   refusal panel (a dashboard the database declined to compute) and is used
   nowhere else.

   ---------------------------------------------------------------------------
   DARK MODE: SELECTED AND VALIDATED, DELIBERATELY NOT REACHABLE YET
   ---------------------------------------------------------------------------
   The dark steps below are chosen from the same ramp for the dark band and
   validated against surface #14202B (blue #0092CF: L 0.625 in 0.48-0.67, 4.73:1;
   de-emphasis #A8B4BC: CVD dE 13.7, normal-vision dE 18.2, both >= 3:1). They
   are scoped to :root[data-theme="dark"] ONLY -- NOT to prefers-color-scheme --
   and NOTHING IN THE APPLICATION SETS data-theme. That is on purpose: this
   application has no dark theme in any phase, so honouring the OS setting here
   would put dark chart cards inside a light application, which looks broken and
   is worse than not shipping it. Phase 10's branding pass owns the decision; when
   it lands, one attribute turns these on and the validator gets re-run against
   whatever surface it picks.
   =========================================================================== */

.viz-root {
  color-scheme: light;

  /* the ramp, as roles */
  --viz-surface:      var(--white);          /* the chart surface itself */
  --viz-plane:        var(--slate-50);       /* the page behind the cards */
  --viz-series-1:     #0092CF;               /* calls / activity. Slot 1. */
  --viz-series-1-lo:  #3FB3F2;               /* step 300: "other activity" */
  --viz-context:      #4E5F6D;               /* de-emphasis: never contacted */
  --viz-ink:          var(--slate-900);
  --viz-ink-2:        var(--slate-600);
  --viz-muted:        var(--slate-400);      /* axis + tick labels */
  --viz-grid:         var(--slate-200);      /* hairline, SOLID, never dashed */
  --viz-axis:         var(--slate-300);
  --viz-status-critical: var(--red);         /* refusals only. Never a series. */
}

:root[data-theme="dark"] .viz-root {
  color-scheme: dark;
  --viz-surface:      #14202B;
  --viz-plane:        #0D1620;
  --viz-series-1:     #0092CF;
  --viz-series-1-lo:  #29A3E1;
  --viz-context:      #A8B4BC;
  --viz-ink:          #FFFFFF;
  --viz-ink-2:        #C3CED6;
  --viz-muted:        #85929B;
  --viz-grid:         #26333F;
  --viz-axis:         #38495A;
}

.exec-screen { display: flex; flex-direction: column; gap: 16px; }

/* ---- the one filter row, above everything it scopes ---------------------- */
.viz-filters { display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
               padding: 10px 14px; background: var(--white);
               border: 1px solid var(--slate-200); border-radius: var(--radius); }
.viz-filters .tool { margin: 0; }
.viz-filter-note { color: var(--slate-500); font-size: 11.5px; }

/* ---- hero figure: ONE per view, in the same sans as everything else ------ */
/* Proportional figures, NOT tabular: tabular-nums gives every digit the width of
   a 0 and makes a large standalone number look loose. */
.viz-hero { padding: 18px 20px 16px; }
.viz-hero-row { display: flex; align-items: flex-start; gap: 28px; flex-wrap: wrap; }
.viz-hero-fig { font-size: 52px; line-height: 1.05; font-weight: 680;
                letter-spacing: -.02em; color: var(--viz-ink);
                font-variant-numeric: proportional-nums; }
/* The SECOND figure in the hero card, and deliberately not a second hero: one
   hero per view. Same sans, same proportional figures, half the size. */
.viz-hero-second { padding-left: 24px; border-left: 1px solid var(--slate-200); }
.viz-hero-fig2 { font-size: 30px; line-height: 1.15; font-weight: 650;
                 color: var(--viz-ink-2); font-variant-numeric: proportional-nums; }
.viz-hero-lab { font-size: 13px; font-weight: 600; color: var(--slate-600);
                margin-bottom: 2px; }
.viz-hero-sub { font-size: 12.5px; color: var(--slate-500); line-height: 1.5;
                max-width: 62ch; }

/* ---- stat tiles ---------------------------------------------------------- */
.viz-tiles { display: grid; gap: 12px;
             grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); }
.viz-tile { background: var(--white); border: 1px solid var(--slate-200);
            border-radius: var(--radius); padding: 12px 14px; }
.viz-tile-lab { font-size: 11.5px; color: var(--slate-500); line-height: 1.35;
                min-height: 2.7em; }
.viz-tile-val { font-size: 26px; font-weight: 650; color: var(--viz-ink);
                margin-top: 6px; font-variant-numeric: proportional-nums; }
.viz-tile-sub { font-size: 11.5px; color: var(--slate-500); margin-top: 2px; }
.viz-tile-both { border-left: 3px solid var(--blue); }

/* ---- chart card: the container OWNS the x-axis band, so no nested scroll -- */
.viz-card { padding: 14px 16px; }
.viz-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
            margin-bottom: 2px; }
.viz-title { font-size: 15px; font-weight: 650; color: var(--viz-ink); margin: 0; }
.viz-sub { font-size: 12px; color: var(--slate-500); margin: 2px 0 12px;
           line-height: 1.5; max-width: 88ch; }
.viz-src { font-size: 11px; color: var(--slate-400); font-family: var(--mono); }
.viz-figure { margin: 0; }
/* max-width matches the viewBox width the chart components draw in, so at the
   desktop layout the scale factor is 1 and a 20-unit bar is a 20px bar. Below that
   width marks scale DOWN, never up past the 24px cap. */
.viz-svg { display: block; width: 100%; max-width: 1400px; height: auto; overflow: visible; }

/* Marks. Gridlines and axes are SOLID hairlines one step off the surface. */
.viz-grid-line { stroke: var(--viz-grid); stroke-width: 1; shape-rendering: crispEdges; }
.viz-axis-line { stroke: var(--viz-axis); stroke-width: 1; shape-rendering: crispEdges; }
.viz-tick { fill: var(--viz-muted); font-size: 11px;
            font-variant-numeric: tabular-nums; }
.viz-cat  { fill: var(--viz-ink-2); font-size: 12.5px; }
.viz-val  { fill: var(--viz-ink); font-size: 12.5px; font-weight: 600;
            font-variant-numeric: tabular-nums; }
.viz-bar  { fill: var(--viz-series-1); }
.viz-bar-lo { fill: var(--viz-series-1-lo); }
.viz-bar-ctx { fill: var(--viz-context); }
/* The hit target is bigger than the mark and is what carries the tooltip. */
.viz-hit  { fill: transparent; cursor: default; }
.viz-row:hover .viz-bar, .viz-row:focus-visible .viz-bar,
.viz-row:hover .viz-bar-lo, .viz-row:focus-visible .viz-bar-lo,
.viz-row:hover .viz-bar-ctx, .viz-row:focus-visible .viz-bar-ctx { opacity: .82; }
.viz-row.clickable .viz-hit { cursor: pointer; }
.viz-row:focus-visible { outline: 2px solid var(--blue); outline-offset: 1px; }

/* Legend: ALWAYS present for two or more series. Swatch mirrors the mark. */
.viz-legend { display: flex; gap: 16px; flex-wrap: wrap; margin: 0 0 10px;
              padding: 0; list-style: none; }
.viz-legend li { display: flex; align-items: center; gap: 6px; font-size: 12px;
                 color: var(--slate-600); }
.viz-key { width: 12px; height: 12px; border-radius: 2px; flex: none; }
.viz-key-1 { background: var(--viz-series-1); }
.viz-key-1-lo { background: var(--viz-series-1-lo); }
.viz-key-ctx { background: var(--viz-context); }

/* Tooltip. Values lead, labels follow. */
.viz-tip { position: fixed; z-index: 40; pointer-events: none;
           background: var(--slate-900); color: var(--white);
           border-radius: var(--radius); padding: 7px 10px; font-size: 12px;
           line-height: 1.45; box-shadow: var(--shadow-md); max-width: 280px; }
.viz-tip-val { font-weight: 700; font-variant-numeric: tabular-nums; }
.viz-tip-lab { color: var(--slate-300); }
.viz-tip-row { display: flex; align-items: center; gap: 6px; }
.viz-tip-key { width: 10px; height: 3px; border-radius: 2px; flex: none; }

/* The table twin. Every chart has one; it is the WCAG-clean equivalent and the
   reason a tooltip never gates a value. */
.viz-table-toggle { margin-left: auto; }
.viz-table { margin-top: 12px; }
.viz-table table { width: 100%; }
.viz-table th, .viz-table td { padding: 5px 8px; }

/* THE HONEST EMPTY STATE. Not centred grey text: it carries the real
   denominators and reads as a statement of fact, not as "no data". */
.viz-zero { background: var(--blue-tint); border: 1px solid var(--blue-tint-2);
            border-left: 4px solid var(--blue); border-radius: 4px;
            padding: 13px 15px; margin: 4px 0 0; }
.viz-zero-head { font-weight: 650; color: var(--slate-900); font-size: 13px; }
.viz-zero p { margin: 6px 0 0; font-size: 12.5px; line-height: 1.55; }

/* Provenance: which SQL function produced each panel, and how long it took. */
.viz-prov { padding: 12px 16px; }
.viz-prov table { width: 100%; font-size: 11.5px; }
.viz-prov td, .viz-prov th { padding: 3px 8px; }
.viz-prov code { font-family: var(--mono); font-size: 11px; color: var(--slate-700); }

/* Deferred-by-instruction panel: an absence stated with a live number in it. */
.viz-deferred { padding: 12px 16px; }
.viz-deferred p { margin: 6px 0 0; font-size: 12.5px; line-height: 1.55; }

/* Forced colours / print: hue is gone, so identity has to come from somewhere
   else. The legend and the table twin are always present, and the marks get a
   visible edge so adjacent segments still separate. */
@media (forced-colors: active) {
  .viz-bar, .viz-bar-lo, .viz-bar-ctx { forced-color-adjust: none;
    fill: CanvasText; stroke: Canvas; stroke-width: 2; }
  .viz-bar-lo { fill: GrayText; }
  .viz-bar-ctx { fill: GrayText; }
}
@media print {
  .viz-table { display: block !important; }
  .viz-table-toggle, .viz-filters { display: none; }
}

/* ===========================================================================
   PHASE 10 -- THE MOBILE PASS
   ===========================================================================
   Spec section 8 asks for a tool used all day; the carryover asks specifically
   that THE REP LIST AND THE ACCOUNT DETAIL be usable on a phone, and that LOG
   ACTIVITY work one-handed because it is the most-used control in the product.

   WHAT IS AND IS NOT RESPONSIVE, STATED RATHER THAN DISCOVERED
   -----------------------------------------------------------
   Phone-first, tested at 390x844 (the narrowest current iPhone):
     * the rep account list          -> a card list, one card per coverage row
     * the account detail            -> one column, labels above values
     * LOG ACTIVITY                  -> a bottom sheet in the thumb zone
     * sign-in, the top bar, filters

   DESKTOP-FIRST, and left that way on purpose:
     * the sales-manager section (coverage, untouched, reassignment, history)
     * the team-manager roster and drill-down
     * the executive dashboard
   Those three are 9-to-13-column tables and multi-series charts read side by
   side; squeezing them onto a phone would mean deciding which columns a manager
   may not have, and nobody has asked for them on a phone. They stay legible and
   horizontally scrollable rather than being re-laid out into something that
   silently drops data. The report says so in as many words.

   THE MECHANISM FOR THE LIST
   --------------------------
   The table becomes blocks and each row becomes a card. The per-cell labels are
   REAL ELEMENTS in the DOM at every width (.cell-label, display:none on desktop),
   never CSS ::before content -- generated content cannot come out of the
   translation files, and a French phone showing English column names would fail
   spec section 9. It also means ONE markup path: every data-testid, every row
   count and every text assertion in the e2e suites reads the same DOM at 390px as
   at 1600px.
   =========================================================================== */

/* ---- 1080px: the office laptop. Tighten the gutters, keep every layout. ---- */
@media (max-width: 1080px) {
  .main { padding: var(--sp-4) var(--sp-4) var(--sp-8); }
  .topbar { padding: var(--sp-2) var(--sp-4); gap: var(--sp-3); }
  .topbar-right { gap: var(--sp-3); }
  .whoami { padding-left: var(--sp-3); }
}

/* ---- 820px: tablet / narrow window. The top bar folds; tables scroll. ------ */
@media (max-width: 820px) {
  .topbar { flex-wrap: wrap; row-gap: var(--sp-2); }
  .topbar-right { flex-wrap: wrap; justify-content: flex-end; }
  .screen-head { flex-direction: column; align-items: stretch; }
  .scope-switch { align-self: flex-start; }
  /* 7 cells across a 780px page is 110px each and the French labels clip. Two
     rows of readable cells beats one row of truncated ones. */
  .summary { flex-wrap: wrap; }
  .summary-cell { flex: 1 1 33%; min-width: 150px; }
  .toolbar { padding: var(--sp-3); }
}

/* ===========================================================================
   640px AND BELOW -- THE PHONE
   =========================================================================== */
@media (max-width: 640px) {

  /* ---- shell ------------------------------------------------------------- */
  body { font-size: var(--fs-md); }
  .main { padding: var(--sp-4) var(--sp-3) 96px; }   /* 96px: room for the sheet */
  .topbar { padding: var(--sp-2) var(--sp-3); gap: var(--sp-2); row-gap: var(--sp-2); }
  .logo { height: 34px; }
  .brand-text { padding-left: var(--sp-2); }
  .brand-name { font-size: var(--fs-md); }
  .brand-sub { font-size: 10px; letter-spacing: .07em; }
  .topbar-right { gap: var(--sp-2); width: 100%; justify-content: space-between; }
  /* "Signed in as" is a label for a name that is right beside it; the phone keeps
     the name, the role and the rep code and drops the four words of chrome. */
  .whoami { padding-left: 0; border-left: 0; gap: var(--sp-1); min-width: 0; }
  .whoami .tiny { display: none; }
  .whoami strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .foot { padding: var(--sp-3); }

  h1 { font-size: var(--fs-xl); }
  h2 { font-size: var(--fs-lg); }

  /* ---- summary strip: 2 up ---------------------------------------------- */
  .summary { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; }
  .summary-cell { min-width: 0; padding: var(--sp-2) var(--sp-3); }
  /* 7 cells in 2 columns leaves a grey hole in the last row. The odd one out
     spans instead -- an empty bordered box reads as a cell whose number failed
     to load, which is the worst thing a count can look like here. */
  .summary-cell:last-child:nth-child(odd) { grid-column: 1 / -1; }
  .summary-num { font-size: var(--fs-lg); }

  /* ---- filters: stacked, full width, thumb-sized ------------------------- */
  .toolbar { flex-direction: column; align-items: stretch; gap: var(--sp-2); }
  .search { flex: none; width: 100%; min-width: 0; }
  .tool { justify-content: space-between; width: 100%; }
  .tool select, .tool input { flex: 1 1 auto; min-width: 0; }
  /* A checkbox given flex:1 stretches into a 200px box with a tick in the corner. */
  .tool input[type=checkbox] { flex: none; }
  /* Fixed label column so every control in the stack starts at the same x --
     ragged left edges on six stacked filters is the cheapest thing to get right. */
  .tool > span:first-child { flex: 0 0 96px; }
  .tool.check-inline { justify-content: flex-start; gap: var(--sp-2); }
  .days { flex: none; width: 76px; }
  input[type=text], input[type=search], input[type=date], input[type=number],
  select, textarea { min-height: 40px; font-size: var(--fs-md); }
  .toolbar .btn { min-height: var(--touch); justify-content: center; }

  /* ---- THE REP LIST AS CARDS -------------------------------------------- */
  /* One card per coverage row. The header row is removed rather than hidden with
     visibility, so it takes no space; sorting moves to the toolbar's own control
     on a phone (the columns are no longer columns, so a column header is not a
     thing to tap). */
  .table-wrap { border: 0; background: none; box-shadow: none; overflow: visible; }
  table.grid, table.grid tbody, table.grid tr, table.grid td { display: block; width: auto; }
  table.grid thead { display: none; }
  table.grid tr {
    background: var(--white); border: 1px solid var(--slate-200);
    border-radius: var(--radius); box-shadow: var(--shadow-sm);
    margin-bottom: var(--sp-3); padding: var(--sp-3); position: relative;
  }
  table.grid td {
    display: grid; grid-template-columns: 13.5ch minmax(0, 1fr); gap: var(--sp-3);
    align-items: baseline; padding: var(--sp-1) 0; border-bottom: 0;
    /* Grid items stretch by default, which turned the "Never contacted" pill into
       a 240px bar. Values start at their column and end where they end. */
    justify-items: start;
  }
  /* The two cells that DO want the full card width. */
  table.grid td.col-account, table.grid td.col-log { justify-items: stretch; }
  table.grid tbody tr:hover > td { background: none; }
  .cell-label {
    display: block; font-size: var(--fs-xs); line-height: var(--lh-xs);
    text-transform: uppercase; letter-spacing: .05em; color: var(--slate-400);
    font-weight: 650;
    /* French labels are longer than English ones -- "LIMITE DE CRÉDIT" against
       "CREDIT LIMIT" -- and a label that overflows its grid column eats the gap
       and touches its own value. It wraps instead. */
    overflow-wrap: anywhere; min-width: 0;
  }
  /* The account name is the card's title: full width, no label, wraps in full --
     truncating the one string a rep is scanning for would be the wrong economy. */
  table.grid td.col-account { grid-template-columns: minmax(0, 1fr); padding-bottom: var(--sp-2); }
  table.grid td.col-account .cell-label { display: none; }
  table.grid td.col-account .link { font-size: var(--fs-lg); font-weight: 600; line-height: 1.3; }
  .acct-cell { flex-direction: column; align-items: flex-start; gap: var(--sp-1); }
  .commodities { white-space: normal; }
  .cell-trunc { max-width: none; white-space: normal; overflow: visible; }
  /* Customer number sits on the title line's right, where a card number belongs. */
  table.grid td.col-cust_no { position: absolute; top: var(--sp-3); right: var(--sp-3);
                              grid-template-columns: minmax(0, 1fr); padding: 0; width: auto; }
  table.grid td.col-cust_no .cell-label { display: none; }
  table.grid td.col-account .link { padding-right: 7ch; }
  /* City is an em dash on every row in the whole database. On a 390px card that is
     a line of pure noise, so the phone drops the cell and the DESKTOP keeps it --
     the column the spec asks for is still there, still explained, still asserted. */
  table.grid td.col-city { display: none; }
  table.grid td.col-credit { text-align: left; }
  table.grid td.col-credit { font-variant-numeric: tabular-nums; }
  /* THE LOG BUTTON: full width, 44px, at the bottom of the card, which on a phone
     held in one hand is the part of the card the thumb reaches. */
  table.grid td.col-log { grid-template-columns: minmax(0, 1fr); padding: var(--sp-3) 0 0;
                          margin-top: var(--sp-2); border-top: 1px solid var(--slate-100);
                          text-align: left; width: auto; }
  table.grid td.col-log .cell-label { display: none; }
  table.grid td.col-log .btn { width: 100%; min-height: var(--touch); justify-content: center;
                               font-size: var(--fs-md); padding: var(--sp-2) var(--sp-3); }
  /* SOLID here, soft on the desktop, and the difference is the reason .btn-soft
     exists. The desktop shows 91 rows at once, so a solid fill 91 times is a wall;
     a phone shows one card at a time, so there is no wall to avoid -- and a
     full-width tinted button at 44px reads as disabled. Same colour, same action,
     opposite failure modes. */
  table.grid td.col-log .btn-soft { background: var(--blue); color: var(--white);
                                    border-color: var(--blue); }
  /* The inactivity rule moves from the first cell's left edge to the card's, so it
     still reads at a glance down a column of cards. */
  tr.row-warn  > td:first-child,
  tr.row-alert > td:first-child,
  tr.row-never > td:first-child { box-shadow: none; }
  tr.row-warn  { border-left: 3px solid var(--amber); }
  tr.row-alert { border-left: 3px solid var(--red); }
  tr.row-never { border-left: 3px solid var(--slate-300); }
  tr.row-warn > td, tr.row-alert > td { background: none; }
  tr.row-warn, tr.row-alert { background: var(--white); }

  /* ---- THE COMPOSER AS A BOTTOM SHEET ----------------------------------- */
  /* Spec section 6: "log activity is the most-used control in the entire
     application ... under ten seconds". On a phone that means the thumb, so the
     composer leaves the row it was opened from and docks to the bottom of the
     viewport. One tap opens it, the textarea is already focused, Save is a
     full-width 44px target at the bottom edge. Escape and the × still close it,
     and nothing about the save path changes. */
  tr.log-row { padding: 0; margin: 0; border: 0; box-shadow: none; background: none; }
  tr.log-row > td { padding: 0; display: block; box-shadow: none; background: none; }
  .log-pop, .log-inline .log-pop {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
    margin: 0; max-width: none; border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    border: 0; border-top: 3px solid var(--blue);
    box-shadow: 0 -8px 28px rgba(20, 32, 43, .22);
    padding: var(--sp-4) var(--sp-3) calc(var(--sp-4) + env(safe-area-inset-bottom, 0px));
    gap: var(--sp-3); max-height: 82vh; overflow-y: auto;
  }
  .log-pop-head { gap: var(--sp-2); flex-wrap: nowrap; }
  .log-pop-head strong { flex: none; white-space: nowrap; }
  .log-pop-anchor { font-size: var(--fs-xs); overflow: hidden; text-overflow: ellipsis;
                    white-space: nowrap; }
  .log-pop .icon-btn { min-width: var(--touch); min-height: 32px; text-align: right; }
  /* Four types, one row, no wrapping and no scroll: 4 x ~78px fits 390px. */
  .type-chips { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-1); }
  .chip { padding: 0 var(--sp-1); min-height: 40px; font-size: 13px;
          border-radius: var(--radius); text-align: center; justify-content: center; }
  .log-body { font-size: 16px; min-height: 92px; }   /* 16px: iOS does not zoom */
  .log-pop-foot { flex-direction: column-reverse; align-items: stretch; gap: var(--sp-2); }
  .log-pop-foot .btn { width: 100%; min-height: var(--touch); justify-content: center;
                       font-size: var(--fs-md); }
  .log-pop-foot .muted { text-align: center; }
  .log-pop-foot .kbd { display: none; }   /* no Ctrl key on a phone */

  /* ---- account detail --------------------------------------------------- */
  .detail-grid { grid-template-columns: minmax(0, 1fr); gap: var(--sp-3); }
  .detail-col { gap: var(--sp-3); }
  .card { padding: var(--sp-3); }
  /* Label above value: a 148px label column leaves 8ch for a phone number. */
  .field-row { grid-template-columns: minmax(0, 1fr); gap: 2px; padding: var(--sp-2) 0; }
  .field-row dt { font-size: var(--fs-xs); text-transform: uppercase;
                  letter-spacing: .05em; color: var(--slate-400); font-weight: 650; }
  /* Back stays in the header. LOG ACTIVITY leaves it and becomes a fixed bar in
     the thumb zone, so the most-used control in the application is one tap away
     wherever the rep has scrolled to. The composer sheet is z-index 50 and covers
     it when open, which is correct: the sheet has its own Save. .main reserves
     96px of bottom padding so the bar never hides the last card. */
  .detail-top { flex-direction: column; align-items: stretch; gap: var(--sp-2); }
  .detail-top .btn { min-height: var(--touch); justify-content: center; }
  .detail-log-btn {
    position: fixed; left: var(--sp-3); right: var(--sp-3);
    bottom: calc(var(--sp-3) + env(safe-area-inset-bottom, 0px));
    z-index: 45; min-height: var(--touch); justify-content: center;
    font-size: var(--fs-md); font-weight: 600;
    box-shadow: 0 6px 20px rgba(0, 92, 131, .35);
  }
  .field-narrow input, .field-narrow select { max-width: none; }
  .row-end { flex-direction: column-reverse; }
  .row-end .btn { width: 100%; min-height: var(--touch); justify-content: center; }
  .coverage { padding: var(--sp-3); }
  .feed-item { padding-left: var(--sp-3); }

  /* ---- login ------------------------------------------------------------ */
  .login-screen { padding: var(--sp-4) var(--sp-3); }
  .login-card { padding: var(--sp-4); }
  .user-btn { grid-template-columns: minmax(0, 1fr); gap: 2px; min-height: var(--touch); }
  .user-email { overflow: hidden; text-overflow: ellipsis; }

  /* ---- the desktop-first sections: legible, scrollable, not re-laid out --- */
  /* These four screens keep their table layout on a phone. The card treatment
     above is scoped to the LIST screen's table only (below), so a 13-column
     manager table is not silently reshaped into cards with 13 stacked labels. */
  .mgr-screen .table-wrap, .team-screen .table-wrap, .exec-screen .table-wrap,
  .up-screen .table-wrap {
    border: 1px solid var(--slate-200); background: var(--white);
    box-shadow: var(--shadow-sm); overflow: auto;
  }
  .mgr-screen table.grid, .team-screen table.grid, .exec-screen table.grid,
  .up-screen table.grid { display: table; width: 100%; }
  .mgr-screen table.grid thead, .team-screen table.grid thead,
  .exec-screen table.grid thead, .up-screen table.grid thead { display: table-header-group; }
  .mgr-screen table.grid tbody, .team-screen table.grid tbody,
  .exec-screen table.grid tbody, .up-screen table.grid tbody { display: table-row-group; }
  .mgr-screen table.grid tr, .team-screen table.grid tr,
  .exec-screen table.grid tr, .up-screen table.grid tr {
    display: table-row; margin: 0; padding: 0; border: 0; border-radius: 0;
    box-shadow: none; background: none;
  }
  .mgr-screen table.grid td, .team-screen table.grid td,
  .exec-screen table.grid td, .up-screen table.grid td {
    display: table-cell; padding: var(--row-pad-y) var(--cell-pad-x);
    border-bottom: 1px solid var(--slate-100);
  }
  .mgr-screen .cell-label, .team-screen .cell-label,
  .exec-screen .cell-label, .up-screen .cell-label { display: none; }
  .mgr-screen table.grid td.col-city, .team-screen table.grid td.col-city { display: table-cell; }
  .mgr-screen .mgr-tabs, .team-screen .mgr-tabs { overflow-x: auto; max-width: 100%; }
  .viz-hero-fig { font-size: 38px; }
  .viz-tiles { grid-template-columns: 1fr 1fr; }
}

/* ---- 380px and below: the smallest phone still in use --------------------- */
@media (max-width: 380px) {
  .summary { grid-template-columns: 1fr; }
  table.grid td { grid-template-columns: minmax(0, 1fr); gap: 0; }
  table.grid td .cell-label { margin-bottom: 1px; }
  td.col-cust_no { position: static; }
  td.col-account .link { padding-right: 0; }
}

/* ---- reduced motion: there is little animation, and it all goes ----------- */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ===========================================================================
   THE STATIC REWRITE -- sign-in and change-password.
   ===========================================================================
   Everything here uses the tokens already declared in :root, so the sign-in
   screen and the password panel sit on the same 4px spacing scale, the same six
   type sizes and the same 44px touch target as every other screen. Nothing new
   is invented; a 13px gap cannot creep in through this block.
   ------------------------------------------------------------------------- */

/* --- the sign-in form ---------------------------------------------------- */
.login-form { display: flex; flex-direction: column; gap: var(--sp-4); margin-top: var(--sp-6); }
.login-form .field { display: flex; flex-direction: column; gap: var(--sp-1); }
.login-form input {
  height: 44px;                       /* the touch target, same as everywhere */
  padding: 0 var(--sp-3);
  font: inherit; font-size: var(--fs-md);
  border: 1px solid var(--slate-300); border-radius: var(--radius);
  background: var(--white); color: var(--ink);
}
.login-form input:focus-visible { outline: 2px solid var(--blue); outline-offset: 1px; }
.btn-wide { width: 100%; height: 44px; font-size: var(--fs-md); }
.login-help { margin: 0; text-align: center; }

/* --- the change-password panel ------------------------------------------- */
.modal-backdrop {
  position: fixed; inset: 0; z-index: 60;      /* above the composer sheet (50) */
  background: rgba(20, 32, 43, 0.45);
  display: flex; align-items: center; justify-content: center;
  padding: var(--sp-4);
}
.modal {
  width: min(440px, 100%);
  max-height: calc(100vh - var(--sp-8));
  overflow: auto;
  padding: var(--sp-6);
}
.modal .field { display: flex; flex-direction: column; gap: var(--sp-1); margin-top: var(--sp-4); }
.modal input {
  height: 44px; padding: 0 var(--sp-3);
  font: inherit; font-size: var(--fs-md);
  border: 1px solid var(--slate-300); border-radius: var(--radius);
}
.modal input:focus-visible { outline: 2px solid var(--blue); outline-offset: 1px; }

.card-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.card-head h2 { margin: 0; font-size: var(--fs-xl); line-height: var(--lh-xl); }

/* A hairline above the actions, so the button belongs to the card rather than
   floating in whitespace -- the same rule the other cards follow. */
.card-actions {
  display: flex; gap: var(--sp-2); align-items: center;
  margin-top: var(--sp-6); padding-top: var(--sp-4);
  border-top: 1px solid var(--slate-200);
}

/* Said BEFORE the button is pressed. Amber, not red: nothing is broken, the
   form simply is not finished. Red stays reserved for genuine warnings. */
.warn-text { color: var(--amber-ink, #8A5A00); margin: var(--sp-2) 0 0; }

/* --- flash variants ------------------------------------------------------ */
/* .flash is inline-flex and carries a ✓ pseudo-element. An ERROR must not be
   possible to overlook and must not claim success, so it is full width and its
   mark is replaced. */
.flash-ok { }
.flash-error {
  display: flex; width: 100%;
  background: #FDECEA; border-color: #F5C2BD; color: #8B1F16;
}
.flash-error::before { content: "!"; color: var(--red); }

/* The sign-in card was sized for the dev picker, which was a long list of people.
   The real form is six controls, so 680px of width and a top-anchored card left
   it stranded in the corner of a 1600px screen. Centred, and narrower. */
.login-screen { min-height: 100vh; place-items: center; }
.login-card { max-width: 460px; }
