/*
  app.css — decision 0384, phase 4 of docs/design/document-viewer.md.

  Extracted verbatim from index.html's own inline <style> block, which
  is where every rule below lived until now. The reason is a second
  real HTML page: document-window.html, the pop-out this phase adds,
  needs the same classes (.panel, .cardhead, .doctabs, .vpreview, and
  everything page-renderer.js and activity.js draw into) that
  index.html already defines. Two copies of one stylesheet is exactly
  the drift this project has caught and fixed elsewhere (0067, 0079,
  SUPERSEDED.md) — one file, linked from both pages, is the only
  version that cannot disagree with itself.

  Nothing here was rewritten in the move; only relocated.
*/

/*
  **`hidden` always hides — decision 0572.** A class rule giving an
  element `display: flex` (or grid, or block) ties with the browser's own
  `[hidden] { display: none }` and wins, so setting `hidden` does
  nothing. Found four times: 0271, the pop-out backdrop, 0504's timeline,
  and 0571's Attachments pane, which stayed on screen under the Document
  and Timeline tabs. Each was fixed with its own `[hidden]` rule. This
  one rule ends the pattern for every element, present and future.
*/
[hidden] { display: none !important; }

  /*
    Restraint in service of legibility, not impression — decision 0101.
    This is a tool somebody uses all day, so the quiet has to make the
    work easier to read rather than make the page look expensive.

    Three things carry it, and no new assets:

      1. Vertical rhythm that GROUPS. A label sits close to its field
         and far from the next one, so the eye reads three pairs rather
         than one list of six things.
      2. One element leading. The heading is larger and the rest steps
         down from it, where before everything had equal weight.
      3. No card. A frame makes the form an object floating on a
         surface; letting it sit in space is more confident, and only
         works once the composition holds it together.
  */

  :root {
    /* An explicit scale, because tokens.css has none. Each step is a
       ratio rather than an arbitrary number, so the relationships hold
       if any one changes. */
    --step-0: 14px;
    --step-1: 15px;
    --step-2: 26px;

    /* Rhythm. The tight value binds a label to its field; the loose
       one separates one pair from the next. Their RATIO is what does
       the grouping — roughly one to four. */
    --gap-tight: 7px;
    --gap-loose: 26px;
  }

  body {
    margin: 0;
    min-height: 100vh;
    display: grid;
    place-items: center;
    /* A single wash of the brand behind the page. Not a background
       image: an external image would make a customer's login page
       depend on a third party, which is an availability question on
       the authentication path and a data-protection one in a finance
       product. This is one gradient and no requests. */
    background:
      radial-gradient(120% 90% at 50% -10%, color-mix(in oklab, var(--brand-fill) 11%, transparent), transparent 65%),
      var(--surface-0);
    font: var(--step-0)/1.5 var(--font-sans);
    color: var(--text-primary);
    -webkit-font-smoothing: antialiased;
  }

  main {
    width: min(360px, calc(100vw - 40px));
    /* No card, no border, no panel. */
    padding: 0 0 40px;
  }

  /*
    The brand mark: a short rule rather than a full-width bar. A bar
    spanning the form reads as a container's edge; a mark reads as
    identity, and stays out of the way of the work.
  */
  /*
    **The product's logo on sign-in — decision 1052**, in place of the short
    rule: Day's navy lettering, or Night's white, as the menu's. A customer's
    own logo (0840) takes its place. Beside it, the product's name in the
    heading is for screen readers only; a customer's name or Welcome shows.
  */
  .signinlogo { display: none; height: 30px; width: auto; max-width: 100%; margin-bottom: 22px; }
  .signinlogo.day { display: block; }
  @media (prefers-color-scheme: dark) {
    :root:not([data-mood="day"]):not([data-mood="livery"]) .signinlogo.day { display: none; }
    :root:not([data-mood="day"]):not([data-mood="livery"]) .signinlogo.night { display: block; }
  }
  :root[data-mood="night"] .signinlogo.day { display: none; }
  :root[data-mood="night"] .signinlogo.night { display: block; }
  :root[data-mood="livery"].livery-logo .signinlogo.day,
  :root[data-mood="livery"].livery-logo .signinlogo.night { display: none; }
  #product.productname {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }

  .mark {
    width: 34px;
    height: 3px;
    background: var(--brand-fill);
    border-radius: 2px;
    margin-bottom: 22px;
  }

  /*
    **Where to work, as cards — decision 0847.** One per environment the
    person may reach, the last one used first and ringed (Enter takes it),
    edged in the customer's own colour.
  */
  #envcards .who { color: var(--text-secondary); margin: 0 0 18px; font-size: var(--text-base); }
  #envcards .pick { font-size: var(--text-sm); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-muted); margin: 0 0 8px; }
  #envcards .envgroup { font-size: var(--text-sm); font-weight: 700; color: var(--text-secondary); margin: 14px 0 6px; }
  .linklike { background: none; border: 0; padding: 0; font: inherit; color: var(--text-accent); text-decoration: underline; cursor: pointer; }
  .envcard {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    text-align: left;
    font: inherit;
    color: var(--text-primary);
    background: var(--surface-2);
    border: 0.5px solid var(--border-strong);
    border-left: 4px solid var(--tone, var(--brand-fill));
    border-radius: 10px;
    padding: 10px 12px;
    margin-bottom: 8px;
    cursor: pointer;
    transition: transform 120ms ease, box-shadow 120ms ease;
  }
  .envcard:hover:not(:disabled), .envcard:focus-visible { transform: translateY(-1px); box-shadow: 0 4px 14px rgba(18, 26, 38, 0.12); outline: none; }
  .envcard.first { box-shadow: 0 0 0 2px var(--tone, var(--brand-fill)); }
  .envcard:disabled { opacity: 0.6; cursor: default; }
  .envcard .clogo { width: 34px; height: 34px; flex: none; border-radius: 8px; display: grid; place-items: center; font-weight: 800; background: var(--surface-1); color: var(--text-secondary); overflow: hidden; }
  .envcard .clogo img { width: 100%; height: 100%; object-fit: contain; }
  .envcard .cbody { flex: 1; display: grid; }
  .envcard .cname { font-weight: 700; font-size: var(--text-lg); }
  .envcard .cmeta { color: var(--text-muted); font-size: var(--text-sm); }
  .envcard .kind { display: inline-block; border-radius: 999px; padding: 0 7px; font-weight: 700; font-size: 11px; margin-right: 2px; }
  .envcard .kind.sandbox { background: var(--bg-accent); color: var(--text-accent); }
  .envcard .kind.production { background: var(--bg-success); color: var(--text-success); }
  .envcard .carrow { color: var(--text-muted); font-size: var(--text-lg); }

  /*
    **The backdrop — decision 0847**, in the customer's livery only (Day
    and Night stay Invoice Aware's own). Their picture under a veil, or
    without one their logo large and faint; the form sits on a card so it
    reads whatever is behind it.
  */
  .signin-backdrop { display: none; position: fixed; inset: 0; z-index: -1; pointer-events: none; }
  :root[data-mood="livery"].signin-photo .signin-backdrop {
    display: block;
    background:
      linear-gradient(90deg, rgba(12, 10, 20, 0.7), rgba(12, 10, 20, 0.25) 60%, rgba(12, 10, 20, 0.1)),
      var(--signin-backdrop) center / cover no-repeat;
  }
  :root[data-mood="livery"].signin-logo .signin-backdrop { display: block; border-top: 6px solid var(--brand-bar); }
  :root[data-mood="livery"].signin-logo .signin-backdrop::after {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--signin-logo) no-repeat right -60px bottom -80px / 460px;
    opacity: 0.08;
  }
  :root[data-mood="livery"]:is(.signin-photo, .signin-logo) #signin-view {
    background: color-mix(in srgb, var(--surface-2) 97%, transparent);
    border-radius: 16px;
    padding: 28px 28px 22px;
    box-shadow: 0 12px 40px rgba(18, 26, 38, 0.16);
  }

  /* Decision 0840: the customer's logo on sign-in, while their livery is worn. */
  #productlogo { display: none; width: 56px; height: 56px; object-fit: contain; margin-bottom: 14px; }
  :root[data-mood="livery"].livery-logo #productlogo { display: block; }

  h1 {
    font-size: var(--step-2);
    font-weight: 600;
    letter-spacing: -0.021em; /* Large text sets loose; pulled back in. */
    margin: 0 0 6px;
  }

  .sub {
    color: var(--text-secondary);
    margin: 0 0 34px;
  }

  /* The grouping, stated once rather than per field. */
  .field + .field { margin-top: var(--gap-loose); }

  label {
    display: block;
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text-secondary);
    margin-bottom: var(--gap-tight);
  }

  /**
   * **Text-like inputs only** — decision 0282's same reasoning,
   * applied here too even though nothing on this specific form is a
   * checkbox today: the next field added to it should not have to
   * rediscover why one would look wrong.
   */
  input:not([type="checkbox"]):not([type="radio"]), select, textarea {
    width: 100%;
    font-size: var(--step-1);
    min-height: 38px;
    background: var(--surface-1);
    border: 0.5px solid var(--border);
    border-radius: var(--radius);
    padding: 8px 11px;
    color: var(--text-primary);
    /* Only the border moves on focus. A shifting shadow or size makes
       a form feel unsteady under the hands. */
    transition: border-color 120ms ease;
  }

  /* The request page's note (decision 0806): the same field, a few lines tall. */
  textarea { min-height: 84px; font: inherit; font-size: var(--text-base); resize: vertical; }
  /* A field people never see, for robots that fill in every field (0806). */
  .trap { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }

  input:focus, select:focus, textarea:focus {
    outline: none;
    border-color: var(--brand-fill);
  }

  select:disabled { color: var(--text-muted); }

  button[type="submit"] {
    width: 100%;
    margin-top: 34px;
    min-height: 40px;
    font-size: var(--step-1);
    font-weight: 500;
    background: var(--brand-fill);
    color: #fff;
    border: none;
    border-radius: var(--radius);
    transition: opacity 120ms ease;
  }

  button[type="submit"]:hover:not(:disabled) { opacity: 0.9; }
  button[type="submit"]:disabled { opacity: 0.4; cursor: default; }

  /*
    Messages sit below the form and are quiet by default. A red panel
    for a mistyped password treats an ordinary event as an emergency;
    the text carries it.
  */
  .problem, .since {
    margin-top: 22px;
    font-size: var(--text-base);
    line-height: 1.55;
  }

  .problem {
    color: var(--text-warning);
  }

  .problem:empty, .since:empty { display: none; }

  .since { color: var(--text-secondary); }

  /* The page's own way out (decision 0807): quiet, in the page's colours, not the browser's. */
  #start > p:last-child, #welcome-signin { margin-top: 22px; font-size: var(--text-sm); }
  #start a, #welcome a { color: var(--text-secondary); text-decoration: none; }
  #start a:hover, #welcome a:hover { color: var(--text-primary); text-decoration: underline; }

  /*
    The Task Manager, inside a persistent frame — decision 0108.

    A denser layout than the sign-in card on purpose: this is a screen
    somebody reads all day, so restraint has to serve legibility rather
    than air (0101).
  */
  body.working {
    display: block;
    place-items: initial;
    background: var(--surface-0);
    /**
     * **No page padding (Dan, 6 November 2026).** `tokens.css`'s bare
     * `body` rule gives every page `padding: 2rem 1rem`, meant for plain
     * text pages; nothing here reset it, so every screen sat 32px below
     * the top of the window (plus `.main`'s own 22px) with a 16px strip
     * at each side. Reported live with a screenshot: "a little too much
     * border at the top". `padding: 0` puts the frame flush to the
     * window; `.main`'s own padding is now the only space above the
     * title. Measured live before shipping: `.topbar` moved from 54px to
     * 22px from the top, `.nav` flush left. `.nav`'s `margin-top: -32px`
     * (decision 0438) existed only to cancel this padding and is gone
     * with it. Same reset `body.docwindowbody` makes (decision 0526).
     */
    padding: 0;
  }

  .frame {
    display: grid;
    /* Navigation, then everything else. The nav is narrow and fixed:
       it is a place to return from, not a thing to read. */
    grid-template-columns: 200px minmax(0, 1fr);
    min-height: 100vh;
  }

  .nav {
    border-right: 0.5px solid var(--border);
    padding: 20px 12px;
    background: var(--surface-1);
  }



  /*
    The mark at the foot of the column — decision 0145.

    The nav is a flex column so `.who` can be pushed down; the logo
    sits below it, centred, and quiet.
  */
  .nav {
    display: flex;
    flex-direction: column;
    /**
     * **Pinned to the browser window, not the page** — decision 0281,
     * the operator's own request: "align the side panel bottom, with
     * the bottom of the browser size... this will ensure that user,
     * and instance are always visible, and not hidden from view."
     *
     * `.frame` is a grid row, and a grid cell stretches to match its
     * row's own height by default — so `.nav` grew as tall as `.main`
     * did, and on any screen with enough content to scroll, `.who`'s
     * own `margin-top: auto` pushed the signed-in name and environment
     * far down the page rather than to the bottom of what was actually
     * visible. `position: sticky` with an explicit `height: 100vh`
     * keeps the nav exactly one viewport tall and pinned to it as the
     * page scrolls; `align-self: start` is what stops the grid's own
     * stretch from overriding that height with the row's taller one.
     *
     * **`box-sizing: border-box`, decision 0437 — `height: 100vh` was
     * never actually one viewport tall.** Checked live, against the
     * deployed page: `.nav`'s own rendered box measured 1040px against
     * a 1000px viewport — not content overflowing (0436's own
     * diagnosis, and wrong: `.navscroll`'s content was confirmed, by
     * the same live check, to fit its own box with zero overflow).
     * The real cause is this rule's own `padding: 20px 12px` — with
     * the default `box-sizing: content-box`, an explicit `height`
     * sets the CONTENT box only, and padding is added on top of it,
     * so `height: 100vh` plus 20px top and 20px bottom padding
     * rendered as 100vh + 40px, every time, regardless of content.
     * `border-box` is what makes an explicit `height` mean the total,
     * rendered box — padding included — which is what "one viewport
     * tall" has to mean for `.who` to actually reach the bottom edge
     * of it.
     */
    position: sticky;
    top: 0;
    height: 100vh;
    box-sizing: border-box;
    align-self: start;
    /**
     * **No `margin-top: -32px` any more.** Decision 0438 used it to cancel
     * `body`'s `padding: 2rem 1rem` so `.nav` sat flush at the top; since
     * 6 November 2026 `body.working` has no padding, so `.nav` is flush
     * on its own and the margin would push it 32px above the window.
     */
    /**
     * **The nav itself is the collapse toggle, decision 0311** — a
     * standalone `.navcollapsetoggle` button used to be the only
     * clickable, non-navigating thing here; now the whole nav is,
     * `cursor: pointer` says so the same way `tr.clickable` already
     * signals a clickable row that carries no button of its own.
     */
    cursor: pointer;
  }
  .nav .who { margin-top: auto; }
  /**
   * **The nav's own content, capped independently of `.who` —
   * decision 0436.** `.nav` has held one viewport's height since
   * decision 0281, but that alone only keeps the BOX one viewport
   * tall; nothing kept what's inside it that short. Reported live:
   * *"the height of the side menu... so the username and instance,
   * which appear at the bottom can always be seen on-screen"* — this
   * rule defends against the nav's own content someday genuinely
   * outgrowing its box (three more configuration screens landed after
   * decision 0281 shipped, with nobody re-checking a fixed-height
   * sidebar against a longer list each time).
   *
   * **This decision's own live measurement — `.nav`'s rendered height
   * at 1040px against a 1000px box — was misread as content overflow
   * at the time.** Decision 0437's own follow-up measurement found
   * `.navscroll`'s content fit its own allotted space with zero
   * overflow; the 40px gap was `.nav`'s own padding, added on top of
   * `height: 100vh` by the default `box-sizing: content-box` (see
   * `.nav`'s own `box-sizing: border-box`, above, decision 0437's
   * real fix). This rule stayed, because the protection it offers
   * against genuine future overflow is real even though it wasn't
   * what the reported symptom needed — left as a defensive measure,
   * not removed, once the actual cause was found.
   *
   * `flex: 1 1 auto; min-height: 0` is what lets a flex child shrink
   * below its own content height at all — a flex item's default
   * `min-height: auto` means "as tall as my content," which silently
   * defeats `overflow-y` no matter what it's set to. `min-height: 0`
   * is the one line that makes the overflow rule underneath it real.
   *
   * **`.who` stays a direct sibling of `.navscroll`, outside it** —
   * tasks.js only wraps the logo and nav items, so this rule scrolls
   * the part of the nav that keeps growing without touching `.who`'s
   * own `margin-top: auto`, which still pushes it to the bottom of
   * `.nav`'s own fixed, one-viewport box exactly as decision 0281
   * left it.
   */
  .nav .navscroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    /**
     * **Decision 0437** — reported live as a new, unwanted horizontal
     * scrollbar, introduced by this rule's own `overflow-y: auto`.
     * Confirmed live: `overflow-x` computed to `auto` here even though
     * nothing set it — the CSS Overflow spec requires it. When one
     * axis is `visible` (the default, and this rule's own silence on
     * `overflow-x`) and the other isn't, the browser promotes the
     * `visible` one to `auto` too, rather than leaving the two axes
     * disagreeing. A small, pre-existing horizontal overflow in the
     * nav column (confirmed live: this element's own content measured
     * a few pixels wider than its box) used to bleed harmlessly under
     * the old default (`overflow-x: visible` lets content render past
     * the edge without a scrollbar); once `overflow-y: auto` forced
     * `overflow-x` to `auto` as well, that same overflow started
     * drawing an actual scrollbar. `overflow-x: hidden` is the
     * standard pairing for a `overflow-y: auto` rule that was never
     * meant to scroll sideways — states the axis explicitly rather
     * than leaving it for the spec's own promotion rule to decide.
     */
    overflow-x: hidden;
  }

  /*
    Two marks, swapped by the mood — decision 0145.

    **The navy wordmark all but vanishes** on the night surface:
    `#001842` against `#0d1626` is a difference of value nobody can
    read, and the mark becomes an outline. A light variant carries the
    wordmark at the same weight as the text beside it, and the `V`
    keeps its gradient in both because the gradient carries itself.

    Swapped in CSS rather than in script, so it follows the mood without
    a second thing to remember (decision 0139) and needs no repaint.
  */
  .brandmark.light { display: none; }

  @media (prefers-color-scheme: dark) {
    :root:not([data-mood="day"]):not([data-mood="livery"]) .brandmark.dark { display: none; }
    :root:not([data-mood="day"]):not([data-mood="livery"]) .brandmark.light { display: block; }
  }

  :root[data-mood="night"] .brandmark.dark { display: none; }
  :root[data-mood="night"] .brandmark.light { display: block; }

  :root[data-mood="day"] .brandmark.dark { display: block; }
  :root[data-mood="day"] .brandmark.light { display: none; }

  /*
    **The customer's livery — decision 0840.** Its own mood, beside Day
    and Night. In it, the customer's logo takes the place of
    Invoice Aware's (when one was uploaded), the bar colour runs as a band
    across the top of the screen and marks the current menu item, and
    the environment chip wears the chip colours. `--livery-bar` is only
    ever defined inside the livery mood's block, so Day and Night keep
    each menu item's own hue.
  */
  :root[data-mood="livery"] .brandmark.dark { display: block; }
  :root[data-mood="livery"] .brandmark.light { display: none; }
  .brandmark.livery { display: none; object-fit: contain; max-height: 64px; opacity: 1; }
  :root[data-mood="livery"].livery-logo .brandmark.livery { display: block; }
  :root[data-mood="livery"].livery-logo .brandmark.dark,
  :root[data-mood="livery"].livery-logo .brandmark.light { display: none; }
  :root[data-mood="livery"].livery-logo .frame.collapsed img.navmark { display: none; }
  :root[data-mood="livery"].livery-logo .frame.collapsed .brandmark.livery { display: block; max-width: 36px; }
  :root[data-mood="livery"] .main { box-shadow: inset 0 4px 0 var(--livery-bar, transparent); }

  /* Decision 0849: a warned licence, across the top of every screen. */
  .licencebanner {
    margin: 0 0 16px;
    padding: 10px 14px;
    border-radius: var(--radius);
    background: var(--bg-warning);
    color: var(--text-warning);
    border: 0.5px solid var(--border-warning);
    font-weight: 600;
  }

  /* Fleet updates, slice 3: the preview screens are marked on every page. */
  :root.preview body::after {
    content: "Preview · sandboxes only";
    position: fixed;
    right: 14px;
    bottom: 14px;
    z-index: 100;
    pointer-events: none;
    background: var(--bg-warning);
    color: var(--text-warning);
    border: 0.5px solid var(--border-warning);
    border-radius: 999px;
    padding: 3px 12px;
    font-size: var(--text-sm);
    font-weight: 600;
  }

  .envchip {
    display: inline-block;
    margin-top: 6px;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    background: var(--env-chip);
    color: var(--env-chip-text);
    border-radius: 999px;
    padding: 2px 10px;
    font-size: var(--text-sm);
    font-weight: 600;
    white-space: nowrap;
  }

  .moodmenu {
    position: fixed;
    z-index: 60;
    min-width: 200px;
    max-width: 260px;
    background: var(--surface-2);
    border: 0.5px solid var(--border-strong);
    border-radius: var(--radius);
    padding: 6px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
  }
  .moodmenu .mi {
    display: flex;
    gap: 8px;
    align-items: center;
    width: 100%;
    padding: 7px 8px;
    border: 0;
    border-radius: 6px;
    background: none;
    color: var(--text-primary);
    font: inherit;
    font-size: var(--text-base);
    text-align: left;
    cursor: pointer;
  }
  .moodmenu .mi svg { width: 16px; height: 16px; flex: none; }
  .moodmenu .mi:hover { background: var(--surface-1); }
  .moodmenu .mi.on { background: var(--bg-accent); color: var(--text-accent); }
  .moodmenu .mhint { font-size: var(--text-sm); color: var(--text-muted); padding: 6px 8px 2px; }

  .brandmark {
    display: block;
    width: 100%;
    /* 60% of where it started, at the operator's asking: at the head
       of a column it orients rather than announces. Decision 1052: the
       Invoice Aware wordmark is long and low (7.8 : 1), so it is held by
       its height instead, 20px, its pack's smallest, about 156px wide. */
    max-width: 160px;
    height: auto;
    margin: 2px auto 20px;
    /*
      **Quieter than the interface it sits in.** A brand mark on a tool
      somebody uses all day should be present rather than insistent, and
      the navy reads heavy against the daytime blue of decision 0139.
    */
    opacity: 0.85;
  }

  .nav a {
    display: block;
    padding: 8px 10px;
    border-radius: var(--radius);
    color: var(--text-secondary);
    text-decoration: none;
    font-size: var(--text-base);
    cursor: pointer;
  }

  .nav a:hover { background: var(--surface-2); }
  .nav a.on { background: var(--surface-2); color: var(--text-primary); }

  /*
    Icons on every entry — decision 0274, sized up and re-centred at
    decision 0276's own asking: "increase the size on the icons... the
    current font is very small." `align-items: center` plus a shared
    `line-height: 1` on icon and label is what actually centres them —
    the icon's own 24px box and the label's default line box do not
    share a baseline without it, which read as slightly misaligned at
    the smaller size.
  */
  /**
   * **`.nav .navitem`, not `.navitem` alone** — found live, the same
   * class of mistake as decision 0275's brandmark fix, on a different
   * property. `.nav a` computes to (0,1,1) — the element selector "a"
   * counts — against `.navitem` alone at (0,1,0), so `.nav a`'s own
   * `display: block` was winning over this rule's `display: flex` the
   * entire time. Every flex property below it — `align-items`, `gap`,
   * and `.frame.collapsed .navitem`'s own `justify-content: center` —
   * had no container to act on, which is why centering the icon in
   * the folded nav never worked regardless of what else changed.
   * Scoped under `.nav` to reach (0,2,0), clearly ahead of `.nav a`'s
   * (0,1,1) rather than tied and hoping source order saves it.
   */
  .nav .navitem {
    display: flex;
    align-items: center;
    gap: 14px;
    width: 100%;
    padding: 10px 12px;
    /**
     * **Its own cursor, decision 0311** — the nav around it now
     * toggles collapse on click; a nav item's own cursor still needs
     * to say "this navigates," not "this folds the menu," since the
     * two are genuinely different actions sharing one region.
     */
    cursor: pointer;
  }
  .navitem svg { width: 24px; height: 24px; flex: 0 0 auto; }

  /*
    **Coloured icons on hover and selection — decision 0527**, option D
    from the mock-up the operator chose. At rest an icon looks exactly
    as before: `.navicon` inherits the link's own grey and has no
    background. On hover, or on the open screen, the icon takes its own
    colour (`--hue`, one of the five `--nav-N` tokens) on a soft tile of
    the same colour, and the open screen gets a coloured bar on its
    left edge. The tile is 32px with a -4px margin, so the row keeps
    the height and label position it always had.
  */
  .navhue1 { --hue: var(--nav-1); }
  .navhue2 { --hue: var(--nav-2); }
  .navhue3 { --hue: var(--nav-3); }
  .navhue4 { --hue: var(--nav-4); }
  .navhue5 { --hue: var(--nav-5); }
  /* Decision 0840: in the customer's livery the menu takes its accent, not VibeFinance's five hues. */
  :root[data-mood="livery"] .nav [class*="navhue"] { --hue: var(--text-accent); }
  .nav .navitem { position: relative; }
  .navicon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 32px;
    height: 32px;
    margin: -4px;
    border-radius: 7px;
    transition: color 0.15s, background 0.15s;
  }
  .nav .navicon svg { width: 22px; height: 22px; }
  .nav .navitem:hover .navicon,
  .nav .navitem.on .navicon {
    color: var(--hue);
    background: color-mix(in srgb, var(--hue) 16%, transparent);
  }
  .nav .navitem.on::before {
    content: "";
    position: absolute;
    left: 0;
    top: 8px;
    bottom: 8px;
    width: 3px;
    border-radius: 3px;
    background: var(--livery-bar, var(--hue));
  }
  .navlabel { flex: 1 1 auto; min-width: 0; font-size: var(--text-lg); line-height: 1; }

  /*
    **A static heading, not a folder — decision 0346.** Reported live:
    "arrange the links... under headings." Decision 0274 tried a
    single, collapsible "Vibe AP" folder wrapping most of the nav, and
    decision 0276 undid it at the same operator's own request: "no
    sub menu exists and the menu items beneath it are always
    displayed." This is that — several static labels, nothing to
    click, nothing to expand or collapse, every item beneath a
    heading shown exactly the way a flat `.navitem` always has been.
  */
  .nav .navgroup {
    font-size: var(--text-sm);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    padding: 0 12px;
    margin: 16px 0 4px;
  }
  .nav .navgroup:first-of-type { margin-top: 0; }
  /*
    **Abbreviated when folded — decision 0525**, the operator's own
    request: "include some abbreviated separators in when retracted...
    AP, SM, CONF, AR and EXP." Until then a folded nav hid its headings
    outright, leaving one unbroken column of icons. The full name stays
    in the page, visually hidden (not `display: none`), so a screen
    reader still announces it; the short form is `aria-hidden`.
  */
  .nav .navgroupshort { display: none; }
  .frame.collapsed .navgroup {
    padding: 0;
    text-align: center;
    letter-spacing: 0.02em;
  }
  .frame.collapsed .navgroupshort { display: inline; }
  .frame.collapsed .navgrouplong {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }

  /*
    The whole nav folded to icons — decision 0274, the operator's own
    request. `.frame.collapsed` narrows the grid column itself, not
    just the nav's own width, since a narrower `.nav` inside an
    unchanged grid track would leave the gained space unused rather
    than given back to the page.
  */
  .frame.collapsed { grid-template-columns: 64px minmax(0, 1fr); }
  .frame.collapsed .navlabel,
  .frame.collapsed .who {
    display: none;
  }
  /**
   * **Matched at the same specificity as the rules it has to beat**,
   * not the class alone — found live: `:root[data-mood="day"]
   * .brandmark.dark { display: block; }` carries four class-level
   * selectors against this rule's three, so it won regardless of
   * which came later in the file, and the full wordmark kept showing
   * above the "V" mark rather than being replaced by it. Naming
   * `.dark` and `.light` explicitly here ties the specificity; being
   * later in the file is what then makes this one win.
   */
  /*
   * Decision 1053: and `:root` before it, for the automatic mood's own rule
   * (`:root:not([data-mood="day"]):not([data-mood="livery"]) .brandmark.light`,
   * five), which showed Night's whole logo above the mark on a folded menu
   * in a dark system theme (Dan, 6 November, with a screenshot).
   */
  :root .frame.collapsed .brandmark.dark,
  :root .frame.collapsed .brandmark.light {
    display: none;
  }
  .frame.collapsed .navmark { display: block; }
  .frame.collapsed .navitem {
    justify-content: center;
    padding: 8px 0;
  }

  /*
    The "V" alone — decision 0274. The same mark the full wordmark's
    own first letter already draws, cropped from the same source
    image rather than redrawn, shown only once the full logo's own
    "ibe finance" has nowhere left to sit.
  */
  .navmark {
    display: none;
    /* Decision 1052: the Invoice Aware slash-and-A mark, about 126 : 88. */
    width: 30px;
    height: auto;
    margin: 2px auto 20px;
  }

  /* The create form — decision 0128. Inline, because three fields and
     a button is a row rather than a stack. */
  .newsource {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 14px;
  }

  /*
    Beside the list rather than below it — decision 0130. Three short
    fields in a narrow column stack; the same fields across a full width
    would leave most of it empty.
  */
  .newsource.stacked { flex-direction: column; align-items: stretch; }
  .newsource.stacked .kf { min-width: 0; }

  /* The document a row names, clickable — decision 0142. A link
     rather than a button, because it navigates rather than decides. */
  button.subjectlink {
    padding: 0;
    min-height: 0;
    border: none;
    background: none;
    color: var(--text-accent);
    font: inherit;
    font-size: var(--text-base);
    text-align: left;
    cursor: pointer;
  }

  button.subjectlink:hover { text-decoration: underline; }

  /*
    The process, as a sequence — decision 0149.

    **Chevrons rather than tabs**, because this is an order and the
    shape should say so. A tab row says "pick one of these"; a sequence
    says "the document passes through these, in this order".
  */
  .process { display: flex; overflow-x: auto; padding: 2px 0; }

  .stage {
    flex: 1 0 auto;
    min-width: 104px;
    padding: 9px 12px 9px 22px;
    background: var(--surface-1);
    border: 0.5px solid var(--border);
    border-left: none;
    border-radius: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
    text-align: left;
    white-space: nowrap;
    position: relative;
    min-height: 0;
  }
  /* A draft's own stages, drag-to-reorder — decision 0352. Never
     applied to the live version's own, read-only display. */
  .stage[draggable="true"] { cursor: grab; }
  .stage[draggable="true"]:active { cursor: grabbing; }
  /* Decision 0709 — on a draft, a stage's name opens its settings. */
  .stage .stagename { all: unset; cursor: pointer; color: inherit; font: inherit; }
  .stage .stagename:hover { color: var(--text-primary); text-decoration: underline; }
  .stage .stagename:focus-visible { outline: 2px solid var(--text-accent); outline-offset: 2px; }

  .stage:first-child { border-left: 0.5px solid var(--border); border-radius: var(--radius) 0 0 var(--radius); padding-left: 14px; }
  .stage:last-child { border-radius: 0 var(--radius) var(--radius) 0; }

  .stage::after {
    content: "";
    position: absolute;
    right: -9px; top: 50%;
    width: 16px; height: 16px;
    margin-top: -8px;
    background: var(--surface-1);
    border-top: 0.5px solid var(--border);
    border-right: 0.5px solid var(--border);
    transform: rotate(45deg);
    z-index: 1;
  }
  .stage:last-child::after { display: none; }

  /* Where the rules being shown actually run. The one bold thing. */
  .stage.here { background: var(--text-accent); border-color: var(--text-accent); color: #fff; font-weight: 600; }
  .stage.here::after { background: var(--text-accent); border-color: var(--text-accent); }

  .stage .count { display: block; font-size: var(--text-sm); opacity: 0.85; font-weight: 400; }

  /*
    A stage the document has not reached — decision 0151.

    Dimmed rather than hidden, so the sequence still reads as a whole
    and somebody can see what is still to come.
  */
  .stage.ahead { opacity: 0.55; }

  /*
    Writing a rule — decision 0153.

    The read-back is the one bold thing: somebody has to check the
    system understood their sentence, and everything else on the screen
    is subordinate to that.
  */
  .readback {
    background: var(--bg-accent);
    border-left: 3px solid var(--border-accent);
    border-radius: 0 var(--radius) var(--radius) 0;
    padding: 12px 16px;
    margin-bottom: 12px;
  }

  .readback .clause { display: block; margin: 2px 0; }
  .readback .clause.heading { margin-bottom: 6px; }
  .readback .clause.then { margin-top: 10px; }
  .readback .nested { padding-left: 18px; border-left: 1px solid var(--border); margin: 4px 0; }

  /* The Business Term, available and not insisted upon. */
  .readback .term { color: var(--text-accent); border-bottom: 1px dotted var(--border-accent); cursor: help; }

  /* A refusal is an answer, not a failure (decision 0033). */
  /*
    A document nothing could read — decision 0161. Warning rather than
    error: nothing went wrong, and there is something for a person to
    do.
  */
  /*
    A system alert — decision 0272. The same card shape whatever
    raised it, whether the unreadable-document note or, later, a real
    entry in the Alerts tab. Not orange: this is a fact the system is
    reporting, not a thing demanding urgent action, and the mock-up
    this follows explicitly did not want the warning colour carried
    over.
  */
  .systemalert {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    background: var(--surface-1);
    border-radius: var(--radius);
    padding: 11px 14px;
    margin-bottom: 12px;
  }
  .systemalert .icon { flex: 0 0 auto; margin-top: 1px; color: var(--text-accent); }
  .systemalert .icon svg { width: 18px; height: 18px; }
  .systemalertlabel {
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-accent);
    text-transform: uppercase;
    letter-spacing: 0.02em;
  }
  .systemalertheadline { font-size: var(--text-sm); font-weight: 600; margin-top: 2px; }
  .systemalertdetail { font-size: var(--text-sm); color: var(--text-secondary); margin-top: 2px; }

  .refusal {
    background: var(--bg-warning);
    border-left: 3px solid var(--text-warning);
    border-radius: 0 var(--radius) var(--radius) 0;
    padding: 12px 16px;
    color: var(--text-warning);
  }

  .composebar { display: flex; gap: 12px; align-items: center; margin-top: 10px; }
  .composebar svg { width: 16px; height: 16px; margin-right: 5px; vertical-align: -3px; }

  .example { display: flex; gap: 14px; align-items: flex-start; padding: 12px 0; border-bottom: 0.5px solid var(--border); }
  .example:last-child { border-bottom: none; }
  .example .verdict { flex: 0 0 96px; font-size: var(--text-sm); font-weight: 600; }
  .example .verdict.fires { color: var(--text-accent); }
  .example .verdict.quiet { color: var(--text-muted); }
  .example .body { flex: 1; }
  /*
    An example shows what the rule turns on — decision 0159. The rest
    is folded away rather than dropped: an example is evidence, and
    evidence somebody cannot inspect is an assertion.
  */
  .example .facts { font-size: var(--text-sm); color: var(--text-secondary); }
  .example .decisive { color: var(--text-primary); font-size: var(--text-base); }
  .example .morefacts { margin-top: 5px; }
  /**
   * The purchase order CSV format reference — decision 0373. Same
   * shape as `.morefacts` above: a native `<details>` disclosure, its
   * own summary the only thing shown until a person actually wants
   * the full column reference.
   */
  .csvformat { margin-top: 14px; }
  .csvformat summary { cursor: pointer; color: var(--text-secondary); font-size: var(--text-sm); }
  .csvformat h4 { margin: 14px 0 6px; font-size: var(--text-sm); color: var(--text-secondary); }
  .example .morefacts summary { cursor: pointer; color: var(--text-muted); font-size: var(--text-sm); }
  .example .morefacts > div { margin-top: 5px; line-height: 1.5; }
  .example .confirmed { color: var(--text-success); font-size: var(--text-sm); font-weight: 600; }

  .gate { display: flex; align-items: center; gap: 14px; padding: 14px 18px; background: var(--surface-1); border: 0.5px solid var(--border); border-radius: var(--radius); }
  .gate .why { font-size: var(--text-sm); color: var(--text-muted); }
  .gate svg { width: 16px; height: 16px; margin-right: 5px; vertical-align: -3px; }
  /* A rule's own name, next to a way to change it — decision 0266. */
  .rulenamerow { display: flex; align-items: center; gap: 10px; margin: 4px 0 2px; }
  .rulename { margin: 0; font-size: var(--text-lg); }

  /*
    A sentence is read on one line, not across a wide screen — decision
    0156. A rule box at 1600px would put a clause at each end and
    nothing in the middle, so it stops where prose stops being readable.
  */
  #sentence { max-width: 62ch; }

  /*
    The document manager — decision 0164.

    Eleven columns is wider than a laptop, which is why a person can
    turn columns off. It scrolls rather than wrapping.
  */
  .tablewrap { overflow-x: auto; }
  .tablewrap table { width: 100%; border-collapse: collapse; }
  .tablewrap th {
    text-align: left;
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-muted);
    padding: 0 10px 8px;
    border-bottom: 0.5px solid var(--border);
    white-space: nowrap;
  }
  .tablewrap td { padding: 10px; border-bottom: 0.5px solid var(--border); vertical-align: top; }
  .tablewrap tbody tr:last-child td { border-bottom: none; }
  .tablewrap tbody tr:hover { background: var(--surface-1); }
  .tablewrap .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }

  .searchrow { display: flex; gap: 10px; align-items: center; }
  .searchrow input[type="search"] {
    flex: 1;
    max-width: 460px;
    padding: 9px 12px;
    border: 0.5px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--surface-2);
    color: var(--text-primary);
  }
  /**
   * The row's own page-size picker — decision 0376, found live: the
   * global `input, select { width: 100% }` rule (above, for a
   * standalone field filling its own container) makes an unconstrained
   * `<select>` want to be as wide as the whole flex row once it has no
   * container of its own to be 100% of, crowding out the search box
   * beside it down to a sliver. The same fix `.filters select` already
   * uses elsewhere: a real width instead of the inherited 100%.
   */
  .searchrow select { width: auto; min-width: 4.5em; flex: none; }

  .status.done { color: var(--text-success); font-weight: 600; font-size: var(--text-sm); }
  .status.waiting { color: var(--text-warning); font-weight: 600; font-size: var(--text-sm); }
  .status.moving, .status.outside { color: var(--text-secondary); font-size: var(--text-sm); }
  .status.unreadable { color: var(--text-warning); font-weight: 600; font-size: var(--text-sm); }
  /*
    **`returned` and `archived` — decision 0501.** Both terminal, like
    `done`, but neither is a success the way `done` is, so neither
    borrows its green. `returned` (a person just took a real action on
    this) gets the same accent colour Reassign/Return To Supplier's own
    action icons already use elsewhere; `archived` (nothing further
    needed, ever) is quieter than even `moving`/`outside` — the same
    "closed, not merely idle" `--text-muted` gives a disabled `<select>`.
  */
  .status.returned { color: var(--text-accent); font-weight: 600; font-size: var(--text-sm); }
  .status.archived { color: var(--text-muted); font-size: var(--text-sm); }
  /* Decision 0731: a credit note credited it. */
  .status.credited { color: var(--text-secondary); font-size: var(--text-sm); }
  /*
    **Status as a pill — decision 0520.** The Documents list's own
    status, shaped as a pill in the colour each status already had
    (above): the text keeps that colour, the fill is the same family's
    background tint, and the edge its border. Only `.statuspill` gets
    the shape, so any other `.status` elsewhere is unchanged.
  */
  .status.statuspill {
    display: inline-block;
    padding: 2px 10px;
    border-radius: 999px;
    border: 0.5px solid var(--border);
    background: var(--surface-1);
    line-height: 1.5;
    white-space: nowrap;
  }
  .statuspill.done { background: var(--bg-success); border-color: var(--border-success); }
  .statuspill.waiting,
  .statuspill.unreadable { background: var(--bg-warning); border-color: var(--border-warning); }
  .statuspill.returned { background: var(--bg-accent); border-color: var(--border-accent); }
  .statuspill.moving,
  .statuspill.outside { background: var(--surface-1); border-color: var(--border-strong); }
  .statuspill.archived { background: transparent; border-color: var(--border); }
  .statuspill.credited { background: transparent; border-color: var(--border-strong); }
  /* Decision 0737: a goods receipt among the invoices, said in the Type column. */
  .doctypereceipt { color: var(--text-accent); font-weight: 600; }
  /* Decision 0769: an order, as a receipt. */
  .doctypeorder { color: var(--text-accent); font-weight: 600; }
  /*
    **The Tasks list's stage pill — decision 0522.** The same pill shape
    as the status pill, coloured by the stage's place in its process from
    the chart palette (--chart-1..5, which is already redefined for dark
    mode). Text is the palette colour, the fill a light tint of it, and
    the edge a stronger tint.
  */
  .stagepill {
    display: inline-block;
    padding: 2px 10px;
    border-radius: 999px;
    border: 0.5px solid var(--border-strong);
    background: var(--surface-1);
    font-size: var(--text-sm);
    font-weight: 600;
    line-height: 1.5;
    white-space: nowrap;
  }
  .stagepill.tone1 { --tone: var(--chart-1); }
  .stagepill.tone2 { --tone: var(--chart-2); }
  .stagepill.tone3 { --tone: var(--chart-3); }
  .stagepill.tone4 { --tone: var(--chart-4); }
  .stagepill.tone5 { --tone: var(--chart-5); }
  .stagepill[class*="tone"] {
    color: var(--tone);
    background: color-mix(in srgb, var(--tone) 12%, transparent);
    border-color: color-mix(in srgb, var(--tone) 45%, transparent);
  }

  .unread { color: var(--text-warning); }
  .hands.none { color: var(--text-success); font-size: var(--text-sm); }
  .hands.some { color: var(--text-muted); font-size: var(--text-sm); }
  .empty { padding: 28px 10px; text-align: center; }

  /*
    Which columns to show, kept in the browser (decision 0164).

    **Named `columnpicker`, not `columns`** — decision 0177. It was
    `columns`, which the viewer and the sources screen have used for a
    **two-column layout** since decision 0108. This rule came later and
    won, so the viewer's own layout was styled as a dropdown and its
    panels collapsed against each other.
  */
  .columnpicker { position: relative; margin-left: auto; }
  .columnpicker summary { list-style: none; cursor: pointer; color: var(--text-muted); padding: 7px 9px; border: 0.5px solid var(--border-strong); border-radius: var(--radius); display: inline-flex; }
  .columnpicker summary::-webkit-details-marker { display: none; }
  /* Decision 0789: the shared `columns` icon carries no size of its own. */
  .columnpicker summary svg { width: 16px; height: 16px; }
  .columnpicker summary:hover { color: var(--text-accent); border-color: var(--border-accent); }
  .columnlist {
    position: absolute; right: 0; top: calc(100% + 6px); z-index: 5;
    min-width: 170px; padding: 10px 12px;
    background: var(--surface-2);
    border: 0.5px solid var(--border-strong);
    border-radius: var(--radius);
    box-shadow: 0 6px 18px rgba(0, 0, 0, .12);
  }
  .columnlist label { display: block; padding: 3px 0; font-size: var(--text-sm); cursor: pointer; }
  /*
    **A deliberate size, not whatever the browser happens to render** —
    decision 0282. Excluding the checkbox from the global input rules
    fixed the real bug (a native checkbox styled as a 32px text box),
    but a bare, unstyled checkbox is a different size on every browser
    and platform. Sized against the label's own `--text-sm` beside it,
    the same discipline this scale already applies everywhere else.
  */
  .columnlist input[type="checkbox"] {
    width: 14px;
    height: 14px;
    margin: 0 8px 0 0;
    vertical-align: middle;
  }

  /*
    What a person needs before they start — decision 0175. Beneath the
    reference, where the status card used to sit below the fold.
  */
  /*
    Four facts about this document, read as a list — decision 0180.

    `Stage`, `Unique Ref`, `Waiting` and `Owner` were three different
    treatments: a bold heading, a subtitle, and a row of two. They
    answer the same kind of question — **what is this and where does it
    stand** — so they read as one column, evenly spaced, in one voice.

    The heading keeps its weight; everything under it is the same size
    and colour.
  */
  .subhead { display: flex; flex-direction: column; gap: 4px; margin: 4px 0 0; }

  /*
    **One rule, because there were three** — decision 0181.
    
    `.topbar .sub` was set twice, and the later one won with
    `--text-base` while the subhead sat at `--text-sm`. Two lines meant
    to read as one list did not.
    
    Reported as *"same font, same weight, same size — they look
    different"*, which they were.
  */
  /*
    **The viewer's heading is a list of four facts, not a heading and
    three notes** — decision 0182.

    `Stage`, `Unique Ref`, `Waiting` and `Owner` answer the same kind of
    question, and the operator asked twice for them to match. Decision
    0181 matched three of them and left the `h2` bold and a size larger,
    on my own reasoning that a heading is a heading.
    
    **It was asked for plainly and I kept overriding it.** They match.
  */
  /*
    **Decision 0782 lifts the viewer's heading out of this list.** The
    heading is now the document's own number, with its type and status
    as pills; Dan chose it as a heading (option B), as the supplier
    network has it. `Unique Ref` joined the line beneath.
  */
  .topbar .sub,
  .subhead > div {
    margin: 4px 0 0;
    font-size: var(--text-sm);
    font-weight: 400;
    color: var(--text-secondary);
    line-height: 1.5;
  }

  .subhead > div { margin: 0; }

  .versionhead { display: flex; align-items: baseline; gap: 12px; margin-bottom: 8px; }
  .versionhead h3 { margin: 0; }
  .said { color: var(--text-secondary); margin: 0 0 10px; }

  /*
    An icon beside a label, not above it — decision 0154.

    0122 stacked them for the action row, where a column of icons is
    the point. In a sentence-shaped button a 20px icon reads as
    cumbersome, which is what it was called.
  */
  .composebar button svg { width: 15px; height: 15px; }

  .rulestate { font-size: var(--text-sm); font-weight: 600; min-width: 96px; display: flex; align-items: center; gap: 5px; }
  .rulestate svg { width: 14px; height: 14px; }
  .rulestate.live { color: var(--text-success); }
  .rulestate.paused { color: var(--text-muted); }
  /* Decision 0838: a version a newer one replaced. */
  .rulestate.replaced { color: var(--text-muted); }
  .rulestate.awaiting_confirmation { color: var(--text-warning); }
  .rulestate.draft { color: var(--text-muted); }

  .rowactions { display: flex; gap: 4px; white-space: nowrap; }
  .rowactions button { padding: 4px 8px; min-height: 26px; font-size: var(--text-sm); }

  /* A retired source is shown, not hidden: somebody looking at where
     invoices arrive should see what stopped as well as what runs. */
  tr.retired td { opacity: 0.55; }

  .newsource .kf { margin: 0; min-width: 160px; }

  /* The identifier a name becomes, shown as somebody types
     (decision 0129). */
  .slugpreview {
    font-size: var(--text-sm);
    min-height: 16px;
    margin-top: 3px;
  }

  /* An address a supplier will be given — decision 0126. */
  .addr { font-variant-numeric: tabular-nums; }
  .pending { color: var(--text-warning); font-size: var(--text-sm); }

  .nav .who {
    margin-top: auto;
    padding: 12px 10px 0;
    border-top: 0.5px solid var(--border);
    font-size: var(--text-sm);
    color: var(--text-secondary);
    /* Decision 0897 (Dan): the name and the environment chip centred under the menu. */
    text-align: center;
  }

  .main { padding: 22px 26px 60px; min-width: 0; }

  /* The document's identity, always visible. Decision 0108: opening a
     document should feel like looking at something, not like going
     somewhere and having to find the way back. */
  .topbar {
    display: flex;
    align-items: center;
    gap: 12px;
    padding-bottom: 16px;
    margin-bottom: 18px;
    border-bottom: 0.5px solid var(--border);
  }

  /*
    **Every screen except the viewer**, where the four lines are a list
    — decision 0182. A heading is a heading elsewhere.
  */
  .topbar h2 { margin: 0; font-size: var(--text-xl); font-weight: 600; }
  /* Decision 0782: a document page's type and status, beside its number. */
  .topbar h2 .typepill, .topbar h2 .status.statuspill { margin-left: 8px; vertical-align: middle; font-size: var(--text-sm); font-weight: 500; }
  .topbar h2 .typepill { display: inline-block; padding: 2px 9px; border-radius: 10px; background: var(--bg-accent); color: var(--text-accent); border: 0.5px solid var(--border-accent); }
  /*
    **Almost every screen's own title, decision 0308** — reported
    live: "replicate the font increase on other pages? Tasks, Sources,
    Suppliers, Rules, Documents." Decision 0307's own scoped
    `.dashboardpage` override applied `--text-xl` to one screen; once
    five more asked for the identical value, the override was the
    norm and `--text-lg` the exception — inverted here rather than
    repeating the same scoped rule five more times.

    **The viewer needs no override of its own here.** Its own `#viewer
    .topbar h2` rule (decision 0182, above) already wins over this
    general one by id specificity regardless of what value either
    carries — a second rule targeting the same selector would only
    have shadowed that one by coming later in the file, which very
    nearly happened: this line originally repeated `#viewer .topbar
    h2` alongside `.composepage`, silently overriding decision 0182's
    own `--text-sm` with `--text-lg` since an equal-specificity tie
    resolves by source order. Caught before shipping by the typography
    suite's own two tests for that exact rule, further up the file.

    **"Write a rule" does need one**, since it was never named and has
    no existing rule of its own to fall back on.
  */
  .composepage .topbar h2 { font-size: var(--text-lg); }

  .topbar .right { margin-left: auto; display: flex; gap: 8px; align-items: center; }
  /*
    **The boundary between what a page added and what every screen
    has** — decision 0304, from a mock-up: "a small vertical line...
    which distinguishes the standard set of icons... from the other
    page specific icons." Shorter than the row itself, the same way a
    divider in a menu never touches its own edges.
  */
  .topbardivider { width: 1px; height: 28px; background: var(--border); flex-shrink: 0; }

  /* Panels. Each a thing somebody can look at on its own. */
  .panel {
    background: var(--surface-2);
    border: 0.5px solid var(--border);
    border-radius: var(--radius);
    padding: 16px 18px;
    /*
      **Panels stacked in a column have always touched** — decision
      0178.
      
      The grid's own `gap` spaced the two columns and nothing spaced
      panels *within* one, so the process row met the party boxes and
      the invoice header met its lines. Reported twice, as two separate
      screens, because it looks like a different problem each time.
      
      On the panel rather than on each screen: every one of them is a
      card in a stack, and a rule per screen is a rule the next screen
      forgets.
    */
    margin: 0 0 14px;
  }

  /* Nothing dangling below the last card in a column. */
  .panel:last-child { margin-bottom: 0; }

  /*
    **The Tasks screen's From agents and Conversations cards — decision
    0707.** Each is a panel alone in its own holder, so the rule above
    took its margin as "last in a column" and the card touched the search
    bar below it. Reported by Dan, 9 October 2026: "please could you create
    a small space between the From Agents card and the Invoice search bar".
    The holder carries the stack's usual gap while it is shown.
  */
  #agentnotes:not([hidden]),
  #receiptconversations:not([hidden]) { margin-bottom: 14px; }

  /*
    **A grid spaces its own children** — decision 0179.

    Decision 0178 gave every panel a bottom margin, which is right for a
    stack and wrong inside a grid: `.parties` already has a `gap`, and
    its panels **stretch to the same row height**. The `:last-child`
    reset then applied to the Buyer alone, so the Seller lost 14px to a
    margin inside an equally-tall box and the two borders stopped
    lining up.

    Reported as *"why is the border under seller different from the
    border under buyer?"* — which is the sort of thing only a person
    looking at it would ask, and exactly right.
  */
  .parties > .panel { margin-bottom: 0; }

  /*
    The Seller card — decision 0221, from the operator's own mock-up.

    **Label and value on one line**, because these are short, read-only
    and being scanned against a document. The keying form stacks them
    and should: its values are inputs.

    **One column, not two** — decision 0387. Two columns ran Name/VAT
    beside Address in parallel, half the card's width each; a single
    column runs Name, VAT, Address in that vertical order, each row
    getting the full width instead. That's what actually stopped the
    wrapping this reported live as the reason for asking: halving the
    width was making a value wrap that the full width didn't need to.
  */
  .sellergrid {
    display: grid;
    /* Still `minmax(0, …)`, not a bare `1fr` — decision 0223, same
       problem on one track as on two: a country name with nowhere to
       break refuses to shrink below its own content width otherwise. */
    grid-template-columns: minmax(0, 1fr);
    gap: 4px;
  }
  .sfield {
    display: grid;
    /* The value track needs the same treatment: a long email has no
       spaces to break at and would widen the row rather than wrap. */
    grid-template-columns: 88px minmax(0, 1fr);
    gap: 8px;
    align-items: baseline;
    line-height: 1.5;
    /* An address line, an email or a VAT number written without spaces
       has nowhere natural to break. */
    overflow-wrap: anywhere;
  }
  /*
    **`.sfield.address` is gone — decision 0387, superseding 0280.**

    0280 stacked the address under its own label because the card was
    two columns then, and the address's own column was only half the
    card's width — stacking gave it the full width of that half rather
    than sharing it with an 88px label. `.sellergrid` is one column
    now, full card width, so the address is back to the same
    label-left layout every other field on the card already uses:
    `addressBlock()` renders a plain `.sfield`, with the address's own
    lines stacked as `<div>`s inside its value column rather than
    beneath the label. Reported live, once the wider card was in front
    of the operator: "now the address can sit to the right of the
    address title, rather than beneath it."
  */
  .slabel {
    color: var(--text-muted);
    /* **A label that wraps pushes its value down** and the column stops
       lining up, which is the whole reason for a fixed width. */
    white-space: nowrap;
  }

  /*
    **An amber ribbon on a card with something to do** — decision 0222,
    the same shape as decision 0161's unreadable notice and for the same
    reason its comment gives: nothing went wrong, and there is something
    for a person to do.

    Deliberately not a red border. An unmatched supplier may be a
    genuinely new one, and leaving it is a real answer.
  */
  /*
    **A card's own action, beside its title** — decision 0228.

    Bottom right first, and the operator was right that it was wrong:
    *"it might extend the card size... There is space in the top right
    already, due to the card heading creating some empty space."*

    A footer adds height to a card in a column already short of it. The
    heading row has room doing nothing.
  */
  .cardhead {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 8px;
  }
  .cardhead > h3 { margin-bottom: 0; }
  /* A card's period, beside its title — decision 0617. */
  .cardhead > .windowpick { flex: 0 0 auto; font-size: var(--text-sm); padding: 2px 6px; max-width: 50%; }
  /*
    Pulled up and out, so the button's own padding does not push the
    title's baseline down — the row should be the height of the title,
    not of the button.
  */
  .cardhead > .actionlink {
    margin: -6px -4px 0 0;
  }

  /*
    **Tabs — decision 0333, restyled as a segmented pill, decision
    0399.** Built underline-only, the same reasoning `.doctabs` itself
    quoted at 0398 — "no pill background, no card wrapper, so it reads
    as navigation within the page." Asked directly to match the
    Document tabs' own pill instead: "the most recent tab select
    update to pill box, also... applied to the tabs in the Access
    screen." Same shape as `.doctabs`/`.doctab`/`.doctab.on` — filled
    surface, fully rounded, the active tab lifted onto `--surface-2`
    with `--tab-active-shadow` (0398's own token, reused rather than a
    second one invented for the same job) — under the class names this
    screen already had (`.tabbar`, `.tab`, `.tab.active`). A second
    caller, decision 0417 — `ap-analytics.js`'s own pill tabs, "similar
    pill-box tabs seen in the Access screen," the operator's own
    words — reuses this same component rather than a second copy of
    it.
  */
  .tabbar {
    display: flex;
    align-items: center;
    gap: 2px;
    background: var(--surface-1);
    border: 0.5px solid var(--border);
    border-radius: 999px;
    padding: 3px;
    margin: 4px 0 16px;
  }
  .tabbar .tab {
    background: none;
    border: 0.5px solid transparent;
    border-radius: 999px;
    padding: 6px 14px;
    color: var(--text-muted);
  }
  .tabbar .tab.active {
    background: var(--surface-2);
    border-color: var(--border-strong);
    box-shadow: var(--tab-active-shadow);
    color: var(--text-primary);
    font-weight: 600;
  }

  /* A supplier row opens one — decision 0230. */
  tr.clickable { cursor: pointer; }
  tr.clickable:hover { background: var(--surface-1); }

  .statebuttons {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin: 10px 0;
  }
  /*
    **No margin inside a `.cardhead` row, decision 0300** — Load and
    New supplier moved into the same title-left, action-right row
    Change Seller and Header Fields already use. `.statebuttons`'s own
    `margin: 10px 0` is right for a block sitting beneath a heading,
    which is what it always did before; inside a flex row aligned to
    `flex-start`, that same margin would nudge the buttons down from
    the title's own top edge rather than sitting level with it.
  */
  .cardhead .statebuttons { margin: 0; }

  /*
    **The process picker's own spacing, decision 0356** — reported
    live: more room between the dropdown and the process illustration
    beneath it. Scoped to this one cardhead, not `.cardhead` itself:
    every other screen's own cardhead sits directly above a table or
    a form, where the tighter, current spacing already reads fine.
  */
  .cardhead.processpicker { margin-bottom: 14px; }

  /*
    **Label above value**, unlike the read-only party cards. These are
    inputs: a label beside a text box makes the box narrow, and the
    argument for inline labels (decision 0221) was that the values were
    short and read-only.
  */
  .editgrid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px 14px;
    margin: 10px 0;
  }
  .editgrid > label {
    color: var(--text-muted);
    align-self: end;
  }
  .editgrid > input:not([type="checkbox"]) { width: 100%; }
  /* Decision 1014 — under a supplier's own tolerances, across both columns. */
  .editgrid > .suppliertolerancehint { grid-column: 1 / -1; margin: 0; }
  /* Decision 0547 — the supplier site's project-only tick, below its details. */
  .supplierprojectonly { margin: 12px 0 0; }
  .supplierprojectonly p { margin: 2px 0 0 24px; }
  /*
    **The same bug decision 0282 already fixed once, in a different
    grid.** Return Reasons' own "Active" checkbox (decision 0498) sits
    directly inside `.editgrid`, and the rule above stretched it to
    fill its whole grid column — reported live from a screenshot: "the
    check box on return reasons seems kinda large." Same fix, same
    reasoning: a bare, unstyled checkbox is a different size on every
    browser and platform, so it gets a deliberate size rather than
    whatever the browser renders, sized against the label's own
    `--text-sm` beside it.
  */
  .editgrid > input[type="checkbox"] {
    width: 14px;
    height: 14px;
    margin: 0 8px 0 0;
    vertical-align: middle;
  }

  /*
    **Return Reasons' own row — decision 0499's own layout pass.**
    `.editgrid`'s two columns are for label-above-value *pairs*; asked
    to hold four unrelated controls (a text input, a label, a
    checkbox, an action) it split them across two visual lines
    instead — the label input and "Active" text on one, the checkbox
    and Save link dropped beneath. Reported live: "do you have any
    capacity for interface design?" A plain flex row instead, the
    same shape `.assignmentrow` already gives a label-on-the-left/
    action-on-the-right list elsewhere on this screen, widened for the
    extra checkbox in the middle.
  */
  .returnreasonlist { margin: 6px 0 10px; }
  .returnreasonrow {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 8px 0;
    border-bottom: 0.5px solid var(--border);
  }
  .returnreasonrow:last-child { border-bottom: none; }
  .returnreasonrow > input[type="text"] { flex: 1; min-width: 0; }
  /*
    **The checkbox and its own "Active" text are one `<label>`**, not
    two elements joined by `for`/`id` — the same shape the Return To
    Supplier picker's own AP-team checkbox already uses (`viewer.js`),
    so clicking the word toggles the box here too. Sized the same
    14px as `.editgrid`'s own checkbox fix immediately above, for the
    same reason: a deliberate size, not whatever the browser renders.
  */
  .returnreasonactive {
    display: flex;
    align-items: center;
    gap: 6px;
    color: var(--text-muted);
    font-size: var(--text-sm);
    white-space: nowrap;
    cursor: pointer;
  }
  .returnreasonactive input[type="checkbox"] {
    width: 14px;
    height: 14px;
    margin: 0;
  }
  /* The "add a new reason" row's own ID field — narrower and fixed,
     since a reason id is a short slug next to a full-sentence label
     that wants the room. */
  .returnreasonnew > input[type="text"]:first-child { flex: 0 0 160px; }

  /*
    **Selects, not just inputs — decision 0341.** `orgPicker`,
    `managerPicker`, `costCentrePicker`, and now the currency dropdown
    all sit in this same grid; none had an explicit width rule, left
    to whatever a browser's own intrinsic sizing for a `<select>`
    happens to be — inconsistent at best, and visibly wrong once an
    option list runs to 178 entries of varying length.
  */
  .editgrid > select { width: 100%; }

  @media (max-width: 520px) {
    .editgrid { grid-template-columns: 1fr; }
  }

  /*
    **Permission checkboxes, grouped by category — decision 0326.
    Name and description, two columns, scrollable — decision 0331.**
    The same namespacing the permissions themselves already use
    (`AP.*`, `Admin.*`), so a form offering the closed vocabulary
    reads the way the vocabulary itself is organised. Reported live:
    "the Permissions are sometimes a little difficult to understand
    what capability is provisioned... listing the Permission next to
    a short description... a scroll bar on the pop-out would be
    needed to work down the list."
  */
  .permissiongroups { margin: 10px 0; }
  .permissiongroups.scrollable {
    max-height: 320px;
    overflow-y: auto;
    border: 0.5px solid var(--border);
    border-radius: var(--radius);
    padding: 10px 12px;
  }
  .permissiongroup { margin: 0 0 12px; }
  .permissiongroup:last-child { margin-bottom: 0; }
  .permissiongroup h4 {
    color: var(--text-muted);
    font-size: var(--text-sm);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin: 0 0 6px;
  }
  .permissionrows { display: flex; flex-direction: column; }
  .permissionrow {
    display: grid;
    grid-template-columns: minmax(150px, 200px) 1fr;
    gap: 4px 16px;
    align-items: center;
    padding: 6px 0;
    border-bottom: 0.5px solid var(--border);
  }
  .permissionrow:last-child { border-bottom: none; }
  .permissionname {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--text-base);
  }
  .permissiondesc { line-height: 1.3; }

  @media (max-width: 520px) {
    .permissionrow { grid-template-columns: 1fr; }
  }

  /*
    **A person's own assignments, each with its own remove control —
    decision 0327.** The same list-of-rows shape `.statebuttons`
    already gives a row of actions, turned sideways: a label on the
    left, one small action on the right, one row per assignment.
  */
  .assignmentlist { margin: 6px 0 10px; }
  .assignmentrow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 6px 0;
    border-bottom: 0.5px solid var(--border);
  }
  .assignmentrow:last-child { border-bottom: none; }
  .assignmentrow button {
    color: var(--text-muted);
    font-size: var(--text-sm);
  }

  /*
    **Stage Restrictions — decision 0483.** A stage's own rule set
    name (or "Automatic," when it has none), beside the stage's own
    title in a `.cardhead` — read-only, unlike `.chip`'s own clickable
    pill this could otherwise have reused, so no `cursor: pointer` and
    no `.on` state.
  */
  .stagebadge {
    font-size: var(--text-sm);
    color: var(--text-muted);
    background: var(--surface-1);
    border: 0.5px solid var(--border);
    border-radius: 999px;
    padding: 3px 10px;
    white-space: nowrap;
  }

  /* A small heading above a group of rows within a panel — the same
     job `.permissiongroup h4` already does, generalised for reuse
     rather than copied a second time for Stage Restrictions. */
  .sectionlabel {
    color: var(--text-muted);
    font-size: var(--text-sm);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin: 14px 0 4px;
  }

  /*
    **Stage Restrictions as one shared 3-column grid — decision 0507,
    rebuilt as 0510.** Reported live, from the operator's own
    screenshot after `/design` mocked it up: "the configuration cards
    on display here... could potentially be 1/3 screen width... 7
    stages which gets very deep in the browser page."

    **0507 through 0509 only put a stage that offers Account Coding
    restrictions in the grid**, leaving a transitionary one
    (`.stageslim`, below) full width — each round re-litigating how to
    size a run of offered stages that had no adjacent partner, because
    a lone card never shares a row with anything regardless of its own
    width. **0510 drops the distinction entirely**: every stage card,
    `.panel` or `.stageslim` alike, is a plain grid item here, in
    stage order, with nothing sorted by `offerFieldRestrictions` for
    layout purposes any more (`ap-setup.js` no longer branches on it
    when building this list) — asked for directly, once the alternating
    "Intake is wide, Validation is narrow, Matching is wide, Coding is
    narrow" look of the run-based version, achieving no depth reduction
    for a real org whose offered stages never landed next to each
    other, was pointed out live against the deployed result.

    **`.stagegrid > .panel { margin-bottom: 0 }` and its `.stageslim`
    twin below**, the same rule `.parties > .panel` already gives its
    own grid (decision 0179) — a card's own bottom margin is right for
    a stack and wrong once the grid's own `gap` is already spacing
    every card.
  */
  .stagegrid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 14px;
    margin: 0 0 14px;
  }
  .stagegrid > .panel { margin-bottom: 0; }

  @media (max-width: 1400px) {
    .stagegrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  }
  @media (max-width: 900px) {
    .stagegrid { grid-template-columns: minmax(0, 1fr); }
  }

  /*
    **A transitionary stage's card (`.stageslim`) — decision 0507,
    laid out like any other since 0671.** Its three toggles used to wrap
    beside the explanation, in a tighter card; Dan found the checkboxes
    unaligned and the two kinds of card inconsistent, so it now has the
    same padding and the same full-width toggle rows (`.stagetoggles`).
  */
  /*
    **Editable-field checkboxes as wrapping chips, not one full-width
    row each — decision 0507.** Three short labels (Project, Commodity
    code, General ledger code) never needed a whole row apiece; a grid
    card a third of the screen's width has even less reason to give
    them one. Each row keeps `.assignmentrow` and its own
    `stagerestrict-` id — only the container that used to stack them
    changed, so nothing that finds a field by that row already breaks.
  */
  .stagefields {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 18px;
    margin: 6px 0 4px;
  }
  .stagefields .assignmentrow {
    border-bottom: none;
    padding: 2px 0;
    width: auto;
  }
  /* A field hidden everywhere carries its own hint line beneath the
     label — the one field row here that still needs the chip row's
     full width rather than sharing a line with its neighbours. */
  .stagefields .fieldhidden { flex-basis: 100%; }

  /*
    **The one-time API key display — decision 0328.** Monospace so
    the characters are unambiguous to copy correctly; read-only, not
    disabled, so the value can still be selected and copied.
  */
  .apikeydisplay {
    width: 100%;
    font-family: monospace;
    margin: 8px 0;
  }

  /*
    **Picking an existing person to add to a team — decision 0332.**
    A select and a small button in one row, the same shape a search
    box beside its own button already takes elsewhere in this app.
  */
  .memberpickerrow {
    display: flex;
    gap: 8px;
    align-items: center;
  }
  /*
    **The currency field, wide enough to read what is selected —
    decision 0341.** Reported live, from a real screenshot: with an
    amount field also in this row (the approval/spend limit case,
    decision 0334, not this row's own original team-member-picker
    shape), the unstyled amount input took its own natural width first
    and left the currency select — `flex: 1`, whose own `flex-basis`
    starts at nothing — squeezed to a sliver. A `min-width` here holds
    the line regardless of what else shares the row; the team-member
    picker, with no amount field to compete with, still grows to fill
    the row exactly as before, `min-width` only ever raising a floor.
  */
  .memberpickerrow select { flex: 1; min-width: 12em; }
  /*
    **A fixed width, not a greedy one — decision 0343.** Reported
    live, from a real screenshot: `flex: 1` grows to fill whatever
    space the amount field's own fixed, narrow width leaves behind —
    correct for the team-member picker this rule was written for
    (nothing else in that row competes for space), wrong for the
    currency picker, where "fill the rest of the row" left a long
    stretch of empty select for a short currency name. A specific
    class, specific enough to win over the rule above by specificity
    alone, replaces "grow to fill" with a fixed, considered width —
    comfortable for the vast majority of this list's own entries,
    `text-overflow: ellipsis` rather than wrapping or overflowing for
    the rare name (Venezuela's own, renamed mid-transition) longer
    than that.
  */
  .memberpickerrow select.currencypicker {
    flex: 0 0 auto;
    min-width: 0;
    width: 20em;
    max-width: 20em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /*
    **The amount field grows, so Set lands at the row's own right
    edge — decision 0344.** Reported live, from a real screenshot:
    with the currency field fixed (decision 0343) and the amount
    field also fixed, neither had any `flex-grow` at all — the row's
    own leftover width sat empty after Set rather than being filled,
    so the button landed short of where the fields above it end.
    `flex: 1` here gives that leftover width somewhere to go, the same
    "grow to fill" decision 0341 first gave the currency field before
    0343 corrected it — right this time, since nothing else in this
    row now competes for the same space.
  */
  .memberpickerrow input[type="number"] { flex: 1; min-width: 6em; }

  /*
    **A real line between properties and limits — decision 0345.**
    Reported live: "place a horizontal line between the Budget
    Holder, and Approval limit fields." A genuine `<hr>` rather than
    a border borrowed from some other element's own edge, styled with
    the same border colour this app already uses everywhere else a
    line divides one section from another.
  */
  .fielddivider {
    border: none;
    border-top: 0.5px solid var(--border);
    margin: 14px 0;
  }

  /*
    The dashboard — decision 0242.

    **Two columns, and cards that choose their own width.** A grid of
    equal thirds would make a four-figure count as wide as a worklist,
    and the worklist is the one somebody came for.
  */
  /*
    **Four columns, and a card asks for what it needs** — decision 0244.

    The first version gave every card an equal half, so a count sat in a
    panel the width of a worklist with a void beside it. A count and a
    table are both cards and are not the same size of thing.
  */
  /*
    **A strip of figures, then the wider cards** — decision 0246.

    Mixing the two in one grid gave every row a ragged bottom: a short
    tile beside a tall chart left a void the height of the chart. **A
    figure and a chart are different heights by nature**, and a grid
    that lets them fight produces holes.

    The strip stretches, so four figures are four equal cards rather
    than four different ones.
  */
  /*
    **Flex, not grid** — decision 0246.

    `auto-fit` sizes tracks to a minimum and then collapses the empty
    ones, which is right for a gallery and wrong for a row of figures:
    three tiles took three narrow columns and left the rest of the row
    empty.

    Flex with `grow` shares the whole row between however many there
    are — three take thirds, four take quarters — and `min-width` is
    what decides when they wrap rather than a column count.
  */
  /*
    **One gap, everywhere** — decision 0246.

    The strip, the grid and a panel's own margin were 12px, 12px and
    14px, which reads as a mistake rather than a rhythm. A dashboard is
    a page of rectangles and **the eye measures the space between them
    against itself**; two values two pixels apart are the worst case,
    because it looks wrong and nobody can say why.
  */
  .dashflow { --dash-gap: 12px; }

  /*
    **One flow, not several bands — decision 0366.** Reported live:
    "can we change all cards, with the exception of the list card 'on
    my clock', to be the same, so that they can occupy the same line,
    if so desired? I want a user to have flexibility to move to the
    top, or bottom of the page, and occupy either two cards per row or
    three cards per row, using their own discretion." And, on how a
    person actually sets that: "can the UI figure it out based on how
    many cards are on the row?... A narrow card (those without chart)
    can support up to 4 on a row. A graphic card (those with graphics)
    can support up to three cards in a row. The list card, only 1 in a
    row."
    
    Decisions 0246, 0364 and 0365 each split cards into their own band
    by size, and a card's stored order never decided which band it
    rendered in — which is exactly why a card that changed size could
    not be moved back to where it had been (decision 0364's own
    report). One flex-wrap flow, ordered exactly as stored, with each
    card's own minimum width read from its kind, removes the bands
    that reordering could disagree with in the first place: any card
    can now sit beside any other, in whichever order it was moved to.
  */
  .dashflow {
    display: flex;
    flex-wrap: wrap;
    gap: var(--dash-gap);
    align-items: stretch;
  }
  .dashflow > .panel {
    display: flex;
    flex-direction: column;
    /*
      **A panel's own margin is off inside a dashboard** — decision
      0246, then 0261 for the bands 0246 itself missed. Baseline for
      every card now that there is only the one flow to miss it in.
    */
    margin: 0;
  }
  /*
    **A chart or donut pinned to the bottom of its card** — decision
    0261. `donutChart()` returns a wrapping `.donutwrap`, not a raw
    `svg`; a bar chart sat pinned to the bottom of a stretched card
    while a donut sat pinned to the top with visible empty space below
    it, which read as two cards of different heights even when their
    borders were already equal.
  */
  .dashflow > .panel > svg,
  .dashflow > .panel > .donutwrap {
    margin-top: auto;
  }
  .dashflow .sub { margin: 2px 0 10px; }

  /*
    **Narrow: no chart, up to four across.** Grows to share, shrinks
    to fit, and wraps below 240px — decision 0257 raised this from
    200px at the operator's own asking that a tile be easier to read
    "across a desk." On a phone this still means one per row; on a
    desk it means three or four rather than five or six cramped ones.
  */
  .card-narrow {
    flex: 1 1 240px;
    min-width: 240px;
    min-height: 148px;
    /*
      **Roomier than the base panel** — decision 0257. The same
      padding as every other panel was right when a tile held one line
      of text (decision 0246); it is tight around a 40px figure and a
      background line.
    */
    padding: 20px 22px 18px;
    /* A positioning context for the background chart below. */
    position: relative;
    overflow: hidden;
  }

  /*
    **A chart behind the text, not beside it** — decision 0257.
    Absolutely positioned and given `z-index: 0` explicitly rather than
    relying on paint order: a statically positioned element is not
    guaranteed to sit below a positioned sibling just because it comes
    later in the document, and this must never end up on top of the
    number it is meant to sit behind.
  */
  .tilebg {
    position: absolute;
    inset: auto 0 0 0;
    height: 46px;
    z-index: 0;
    pointer-events: none;
  }
  .tilefg {
    position: relative;
    z-index: 1;
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
  }

  /*
    **Graphic: has a chart, up to three across** — a wider minimum
    than narrow's own 240px, since a chart's labels crowd sooner than
    a single figure does.
  */
  .card-graphic {
    flex: 1 1 280px;
    min-width: 280px;
  }

  /*
    **List: a table, one to a row** — reported directly: "The list
    card, only 1 in a row." A `flex-basis` of the full row leaves no
    room for a second card beside it in a wrapping flex row, which is
    what forces the line break both before and after it without
    naming a position for it specially.
  */
  .card-list {
    flex: 1 1 100%;
    width: 100%;
  }

  .bignum {
    font-size: var(--text-figure);
    line-height: 1.1;
    /* Lining figures of one width, so 11 and 44 are the same size on
       screen and a column of counts does not shimmer. */
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.02em;
    margin-top: 2px;
  }
  .bignum.warn { color: var(--text-warning); }
  /* Three small figures in the space of one, which is what makes a
     tile denser than a panel rather than emptier. */
  .figures { display: flex; gap: 18px; }
  .figures .bignum { font-size: var(--text-xl); }
  /* The scale's smallest step, rather than a size below it. */
  .tiny { font-size: var(--text-sm); }

  /*
    **A supplier name is left, a number is right** — decision 0246.

    `th` centres by default, so the first heading floated over the
    middle of a wide column while the names beneath it began at the
    left. A heading that does not sit above its column is a heading
    for something else.
  */
  .clocktable { width: 100%; margin-top: 10px; }
  .clocktable th { text-align: left; font-weight: 400; color: var(--text-muted); }
  .clocktable th.num, .clocktable .num { text-align: right; white-space: nowrap; }
  /* The names get what they need; the numbers get what they are. */
  .clocktable th:first-child { width: 50%; }
  .clocktable td { padding: 7px 0; }

  /* A sort is a choice among few, so it reads as chips rather than a menu. */
  .chips { display: flex; gap: 6px; }
  .chip {
    background: none;
    border: 0.5px solid var(--border);
    border-radius: 999px;
    padding: 3px 10px;
    font: inherit;
    font-size: var(--text-sm);
    color: var(--text-muted);
    cursor: pointer;
  }
  .chip.on { color: var(--text-primary); border-color: var(--text-accent); }

  /*
    **A drill-through says what it is showing** — decision 0259, the
    documents and suppliers screens' shared banner for a dashboard
    card's click-through. `.chip` reused for "Clear filter" rather than
    a second button style for the same action.
  */
  .alertbanner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    color: var(--text-primary);
  }

  /* The picker — decision 0243. Cards, because a list of nine names
     says nothing about what each one shows. */
  .pickgrid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    margin: 10px 0;
  }
  .pickcard {
    text-align: left;
    background: none;
    border: 0.5px solid var(--border);
    border-radius: var(--radius);
    padding: 9px 11px;
    font: inherit;
    color: inherit;
    cursor: pointer;
  }
  .pickcard:hover { background: var(--surface-1); }
  .pickcard.on { border-color: var(--text-accent); }
  @media (max-width: 520px) { .pickgrid { grid-template-columns: 1fr; } }

  /*
    **Hidden on the left, displayed on the right** — decision 0262, the
    operator's own picture of the picker: "two columns... center arrows
    to add (move right) or remove (move left)."
  */
  .moverpopout { width: min(680px, 100%); }
  .movergrid {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    gap: 10px;
    align-items: start;
    margin: 10px 0;
  }
  .movercollabel {
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-bottom: 6px;
  }
  .movercol {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-height: 160px;
    max-height: 320px;
    overflow-y: auto;
  }
  /* A list row is a list row, not a two-up card — full width, tighter. */
  .movercol .pickcard { padding: 8px 10px; }

  .moverarrows {
    display: flex;
    flex-direction: column;
    gap: 8px;
    align-self: center;
  }
  .moverarrow {
    width: 36px;
    height: 36px;
    border-radius: 999px;
    border: 0.5px solid var(--border-strong);
    background: var(--surface-1);
    color: var(--text-primary);
    font-size: var(--text-lg);
    cursor: pointer;
    line-height: 1;
  }
  .moverarrow:hover:not(:disabled) { border-color: var(--text-accent); }
  .moverarrow:disabled { opacity: 0.35; cursor: default; }

  @media (max-width: 560px) {
    /* Stacked, with the arrows as a horizontal pair between the lists
       rather than a column with nothing either side of it. */
    .movergrid { grid-template-columns: 1fr; }
    .moverarrows { flex-direction: row; justify-content: center; }
  }

  /* The ring beside its legend — decision 0247. */
  .donutwrap {
    display: flex;
    align-items: center;
    gap: 18px;
    margin-top: auto;
  }
  .donutwrap > svg { flex: 0 0 auto; }
  .donutlegend { flex: 1 1 auto; min-width: 0; }
  .donutkey {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--text-sm);
    padding: 2px 0;
  }
  /* The value sits right, so a column of them reads as a column. */
  .donutkey > span:nth-child(2) { flex: 1 1 auto; min-width: 0; }
  .donutdot { width: 9px; height: 9px; border-radius: 999px; flex: 0 0 auto; }

  /*
    **A legend row that leads somewhere** — decision 0264. Widened
    hit area beyond the text itself, since a row a few pixels tall is a
    small target otherwise.
  */
  .donutkey.clickable {
    cursor: pointer;
    margin: 0 -6px;
    padding: 2px 6px;
    border-radius: 6px;
  }
  .donutkey.clickable:hover, .donutkey.clickable:focus-visible { background: var(--surface-1); }
  /* A ring whose slices lead somewhere — decision 0614. */
  .donutwrap svg.clickable { cursor: pointer; }
  .donutwrap svg circle.clickable { cursor: pointer; }
  .donutwrap svg circle.clickable:hover { opacity: 0.8; }

  @media (max-width: 480px) {
    .donutwrap { flex-direction: column; align-items: stretch; }
  }

  /*
    A key row for a chart with no ring to sit beside — decision 0415,
    `stackedBarChart`'s own legend. `.donutkey`'s row shape, reused;
    laid out left to right and wrapping rather than in `.donutlegend`'s
    own single column, since this sits under a chart of any width
    instead of beside a fixed-size ring.
  */
  .chartlegend {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 18px;
    margin-top: 10px;
  }
  .chartkey {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--text-sm);
  }
  .chartdot { width: 9px; height: 9px; border-radius: 999px; flex: 0 0 auto; }

  /*
    One currency's own ranked list, labelled — decision 0416. Shown
    only when a supplier's spend is genuinely split across more than
    one currency (`supplier-performance.js`'s own `currencySection`);
    the ordinary single-currency case renders `barList` directly with
    no wrapper or label at all.
  */
  .spendcurrency { margin-top: 14px; }
  .spendcurrency:first-of-type { margin-top: 0; }
  .spendcurrency > .muted {
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: 0.02em;
    margin-bottom: 6px;
  }

  /*
    One currency's own accruals, labelled with its own total —
    decision 0417's own follow-on. Not `.spendcurrency` reused: that
    class's own header is a bare currency code, hidden entirely for
    the ordinary single-currency case (`supplier-performance.js`'s own
    `currencySection`); here the header is always shown, because the
    accrued total is the report's own headline figure rather than a
    disambiguation the common case can drop. Same spacing rule,
    honest name.
  */
  .accrualcurrency { margin-top: 14px; }
  .accrualcurrency:first-of-type { margin-top: 0; }
  .accrualcurrency > .muted {
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: 0.02em;
    margin-bottom: 6px;
  }

  /*
    One currency's own spend-under-management ring — decision 0419.
    Same spacing rhythm as `.spendcurrency`/`.accrualcurrency`, a
    separate class because this one wraps a `.donutwrap` rather than a
    `barList`: nesting it inside `.spendundermanagementtile` (rather
    than a bare `.donutwrap` sitting directly under `.panel`) opts each
    currency's own ring out of `.dashflow > .panel > .donutwrap`'s own
    pin-to-bottom rule above, which assumes a card has only the one
    ring `donutChart()` ever gives it.
  */
  .spendundermanagementtile { margin-top: 14px; }
  .spendundermanagementtile:first-of-type { margin-top: 0; }

  /*
    One team's own ranked member list, labelled with its own name and
    imbalance — decision 0428's own workload-balance card. Not
    `.spendcurrency` reused: that class's own header hides for the
    ordinary single-currency case; a team's own name is always shown
    here, the same "always shown" choice `.accrualcurrency` already
    made and for the same reason — an honest, separately-named class
    rather than a currency class doing double duty.
  */
  .teamgroup { margin-top: 12px; }
  .teamgroup:first-of-type { margin-top: 0; }
  /* Decision 0615: a team's bar with its members' key tucked beneath. */
  .teamgroup .chartlegend { margin-top: 2px; gap: 2px 14px; }
  .teamgroup .chartkey { font-size: var(--text-sm); }
  .balancequiet { margin-top: 12px; font-size: var(--text-sm); }
  .teamgrouphead {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin-bottom: 6px;
  }
  .teamgrouphead > .muted {
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: 0.02em;
  }

  /*
    **The worklist scrolls rather than growing** — decision 0250.

    Twenty-five rows is a card taller than the screen, and everything
    beneath it becomes unreachable without scrolling past somebody
    else's work. A fixed height makes the page's shape stable whatever
    is in the queue.
  */
  .clockscroll {
    max-height: 340px;
    overflow-y: auto;
    margin-top: 10px;
  }
  .clockscroll thead th {
    position: sticky;
    top: 0;
    /* Opaque, or the rows scroll under a transparent heading. */
    background: var(--surface-2);
    z-index: 1;
  }

  /* The count and the ring side by side, in a tile. */
  .stagesplit {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
  }

  /*
    **A graphic, where a chart would otherwise sit** — decision 0259.
    Sized like the stage tile's ring beside it, muted rather than
    outlined in a warning colour even when the count is not zero: the
    figure already carries the alert, and a red icon beside a red
    number would be saying the same thing twice.
  */
  .tileicon {
    color: var(--text-muted);
    flex: 0 0 auto;
  }
  .tileicon svg { width: 44px; height: 44px; stroke-width: 1.3; }

  tr.clickable, .panel.clickable { cursor: pointer; }
  tr.clickable:hover { background: var(--surface-1); }
  .panel.clickable:hover { border-color: var(--text-accent); }

  /*
    **Fraud Prevention, tiles on top and a short list per check** —
    decision 0618 (fraud-list.js). The tiles share one row and wrap on a
    narrow screen; a flagged count is in the warning colour.
  */
  tr.clickable:focus-visible { background: var(--surface-1); }
  .dashflow > .panel.fraudtiles, .panel.fraudtiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
  .fraudtile {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 10px 12px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
  }
  .fraudtile.clickable { cursor: pointer; }
  .fraudtile.clickable:hover, .fraudtile.clickable:focus-visible { border-color: var(--text-accent); background: var(--surface-1); }
  .fraudtilecount { font-size: var(--text-xl); font-weight: 600; color: var(--text-secondary); }
  .fraudtile.flagged .fraudtilecount { color: var(--text-warning); }
  .fraudtilelabel { font-size: var(--text-sm); color: var(--text-muted); overflow-wrap: anywhere; }
  .cardhead.fraudhead { justify-content: flex-start; align-items: baseline; }
  .fraudcount { font-size: var(--text-sm); color: var(--text-muted); font-weight: 400; }
  .fraudshowall { flex: 0 0 auto; margin-left: auto; }
  .fraudlist { scroll-margin-top: 12px; }
  .fraudtabs { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; }
  /* A supplier's name that opens it — decision 0619: a link, at the table's own size. */
  .linklike.suppliername { font-size: inherit; font-weight: inherit; text-align: left; }

  .barlist-row { margin-bottom: 9px; }
  .barlist-head { display: flex; justify-content: space-between; font-size: var(--text-sm); gap: 10px; }
  .barlist-track { height: 6px; background: var(--border); border-radius: 3px; margin-top: 3px; }
  .barlist-fill { height: 6px; border-radius: 3px; }

  /*
    **A list row that leads somewhere** — decision 0411, the same
    widened hit area `.donutkey.clickable` already gives a legend row.
  */
  .barlist-row.clickable {
    cursor: pointer;
    margin-left: -6px;
    margin-right: -6px;
    padding: 2px 6px;
    border-radius: 6px;
  }
  .barlist-row.clickable:hover, .barlist-row.clickable:focus-visible { background: var(--surface-1); }
  /* A person's name that opens their work — decision 0616, handling time. */
  .teamgrouphead.clickable { cursor: pointer; margin: 0 -6px 6px; padding: 2px 6px; border-radius: 6px; }
  .teamgrouphead.clickable:hover, .teamgrouphead.clickable:focus-visible { background: var(--surface-1); }

  /*
    **Stacked bars lying down, the name on the left** — decision 0611.
    A grid so every bar starts at the same place whatever the name's
    length; a long name wraps within its column rather than pushing
    its bar along.
  */
  .stackrow {
    display: grid;
    grid-template-columns: minmax(80px, 28%) 1fr auto;
    align-items: center;
    gap: 10px;
    padding: 3px 0;
    font-size: var(--text-sm);
  }
  .stackrow-name { overflow-wrap: anywhere; }
  .stackrow-track { display: flex; gap: 2px; height: 12px; }
  .stackrow-seg { height: 12px; border-radius: 2px; min-width: 2px; }
  .stackrow-total { min-width: 2ch; text-align: right; }
  .stackrow.clickable {
    cursor: pointer;
    margin-left: -6px;
    margin-right: -6px;
    padding: 3px 6px;
    border-radius: 6px;
  }
  .stackrow.clickable:hover, .stackrow.clickable:focus-visible { background: var(--surface-1); }
  /* A segment, or a key row, that leads somewhere of its own — decision 0615. */
  .stackrow-seg.clickable { cursor: pointer; }
  .stackrow-seg.clickable:hover { opacity: 0.8; }
  .chartkey.clickable { cursor: pointer; margin: 0 -6px; padding: 1px 6px; border-radius: 6px; }
  .chartkey.clickable:hover, .chartkey.clickable:focus-visible { background: var(--surface-1); }

  /*
    **A person chosen on the left, their ring on the right** — decision
    0612, Open tasks by user.

    **The key under the drop-down** — decision 0613. The first version
    put the picker, the ring and its key side by side, and a card three
    to a row (about 290px) squeezed the key to nothing and spilled it
    past the card's edge. Now the left column holds the drop-down and
    the key beneath it, and the right the ring, a little smaller. Laid
    out by the card's own width, not the window's: where even that does
    not fit, the ring goes under the key.
  */
  .opentasks-body { container: opentasks / inline-size; margin-top: 10px; }
  .opentasks-layout { display: flex; align-items: flex-start; gap: 16px 28px; }
  .opentasks-left { flex: 1 1 auto; min-width: 0; max-width: 300px; display: flex; flex-direction: column; gap: 10px; }
  .opentasks-pick { display: flex; flex-direction: column; gap: 4px; font-size: var(--text-sm); }
  /* In the card's heading since decision 0712, sized like the period pickers beside it. */
  .opentasks-user { width: auto; }
  .opentasks-find { width: 100%; font-size: var(--text-sm); }
  .opentasks-key .donutkey > span:nth-child(2) { overflow-wrap: anywhere; }
  .opentasks-ring { flex: 0 0 auto; }
  .opentasks-ring .donutwrap { margin-top: 0; }
  @container opentasks (max-width: 250px) {
    .opentasks-layout { flex-direction: column; align-items: stretch; }
    .opentasks-ring { align-self: center; }
  }

  /*
    **A bar that leads somewhere** — decision 0411. Unlike a donut's
    arc, a bar is wide and tall enough to be its own click target, so
    this sits on the SVG group itself rather than needing an HTML
    legend row beside the chart.
  */
  .bargroup.clickable { cursor: pointer; }
  .bargroup.clickable:hover { opacity: 0.82; }

  .panel.needsattention {
    border-left: 3px solid var(--text-warning);
    border-radius: 0 var(--radius) var(--radius) 0;
  }

  /*
    "Here because" — decision 0478 made it a slim, neutral card, on the
    grounds that it names an ordinary rule outcome, not a fault.

    **Amber now, and in Day's colours in both moods — decision 0528.**
    The operator's own request: "please can that card be highlighted in
    amber, with a solid amber sleeve to the left. I would like this
    field to stand out." From the mock-up they chose option B (an icon,
    the label in amber, a 6px sleeve), then: "please can you use the
    amber and palette used on the day card for both?" So the card
    re-declares the Day values of every token it uses, on itself: on a
    Night page it stays a pale amber card with dark text. It still
    does not borrow `.needsattention` (0478's point stands: that class
    means something broke).
  */
  .panel.reasonline {
    --bg-warning: #faeeda;
    --text-warning: #854f0b;
    --text-primary: #121a26;
    --text-secondary: #4a5768;
    --text-accent: #185fa5;
    --surface-2: #ffffff;
    padding: 10px 18px 10px 16px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    color: var(--text-primary);
    background: var(--bg-warning);
    border-color: color-mix(in srgb, var(--border-warning) 45%, transparent);
    border-left: 6px solid var(--border-warning);
    border-radius: 0 var(--radius) var(--radius) 0;
  }
  .reasonline-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    font-size: var(--text-base);
    color: var(--text-primary);
  }
  .reasonline-icon { display: inline-flex; color: var(--text-warning); }
  .reasonline-icon svg { width: 18px; height: 18px; }
  .reasonline-label { color: var(--text-warning); font-weight: 600; }
  .reasonline-row .linky { font-size: var(--text-sm); }
  .reasonline-detail {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    background: color-mix(in srgb, var(--surface-2) 60%, transparent);
    border-radius: var(--radius);
    padding: 8px 10px;
  }
  .reasonline-detail[hidden] { display: none; }

  /**
   * Decision 0487 — Complete refused because the reason above still
   * holds. A couple of pulses in the "look at this" tier (0395).
   *
   * **To a deeper amber, not from transparent — decision 0528.** The
   * card is amber at rest now, so pulsing to `--bg-warning` would
   * barely show, and pulsing through transparent would blank it. It
   * pulses from its own amber to a stronger mix of the sleeve colour.
   */
  .reasonline-flash {
    animation: reasonline-flash-pulse 0.5s ease-in-out 2;
  }
  @keyframes reasonline-flash-pulse {
    0%, 100% { background: var(--bg-warning); }
    50% { background: color-mix(in srgb, var(--border-warning) 45%, var(--bg-warning)); }
  }

  /* A button that reads as a link, because it opens rather than acts. */
  .linky {
    background: none;
    border: none;
    padding: 0;
    color: var(--text-accent);
    text-decoration: underline;
    cursor: pointer;
    font: inherit;
  }

  .backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    z-index: 50;
  }
  /*
    **`display: flex` beats `[hidden]`'s own default** — the exact
    pattern decision 0271 already found and fixed once, in a different
    element: a class rule and the browser's own `[hidden] { display:
    none }` land at equal specificity, and the author's own rule wins
    the tie. Reported live: "I cannot get away from the screen unless I
    refresh the browser" — `popoutBackdrop.hidden = true` was setting
    the attribute correctly; nothing was reading it, since `.backdrop`
    itself never yielded to it.
  */
  .backdrop[hidden] { display: none; }
  .popout {
    /*
      **`--surface-2`, which exists** — decision 0223.

      This read `--bg-panel`, a variable I invented: a token nothing
      defines is not an error, it is **transparent**, so the box
      rendered over the page with every word behind it showing through.
    */
    background: var(--surface-2);
    border: 0.5px solid var(--border);
    border-radius: var(--radius);
    padding: 16px;
    width: min(520px, 100%);
    max-height: 80vh;
    overflow: auto;
  }
  /**
   * **A wider variant, decision 0372.** The 520px default was sized
   * for the Suppliers edit form's own single-column layout; a
   * purchase order line table (nine columns) would be cramped into
   * near-uselessness at that width even with `.tablewrap`'s own
   * horizontal scroll doing its job.
   */
  .popout.wide { width: min(900px, 100%); }

  /*
    **The Matching stage's PO matching panel — decision 0530**, built
    from the agreed mock-up. The used-up bar keeps the mock-up's one
    change: "Already invoiced" in a quiet grey and "This invoice" in
    blue, with a hairline between, because two blues read as one. Over
    100% turns red.
  */
  .popout.pompanel { width: min(1120px, 100%); max-height: 88vh; padding: 18px 20px; }
  .pompanel .pmsub, .pmlinepop .pmsub { margin: 2px 0 0; color: var(--text-secondary); font-size: var(--text-sm); }
  .pmblock { border-top: 0.5px solid var(--border); padding-top: 14px; margin-top: 14px; }
  .pmhead { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; flex-wrap: wrap; margin-bottom: 10px; }
  .pmhead h4 { margin: 0; font-size: var(--text-lg); }
  .pmempty { background: var(--bg-warning); color: var(--text-warning); border-radius: var(--radius); padding: 12px 14px; }
  .pmcard { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); gap: 20px; background: var(--surface-1); border-radius: var(--radius); padding: 14px 16px; }
  .pmmeta { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px 16px; align-content: start; }
  .pmlabel { display: block; font-size: var(--text-sm); color: var(--text-muted); }
  .pmusagehead { display: flex; justify-content: space-between; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; font-size: var(--text-sm); }
  .pmbar { display: flex; height: 8px; border-radius: 999px; background: var(--surface-2); border: 0.5px solid var(--border); overflow: hidden; }
  .pmbar.big { height: 16px; }
  .pmbar.mini { display: inline-flex; width: 70px; height: 6px; vertical-align: middle; margin-right: 6px; }
  .pmseg.others { background: var(--text-muted); opacity: 0.55; }
  .pmseg.mine { background: var(--chart-1); }
  .pmseg.mine.over { background: var(--text-danger); }
  .pmseg.others + .pmseg.mine { box-shadow: -2px 0 0 var(--surface-2); }
  .pmlegend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 8px; font-size: var(--text-sm); color: var(--text-secondary); }
  .pmlegend i { display: inline-block; width: 10px; height: 10px; border-radius: 3px; margin-right: 5px; vertical-align: -1px; }
  .pmlegend i.others { background: var(--text-muted); opacity: 0.55; }
  .pmlegend i.mine { background: var(--chart-1); }
  .pmlegend i.left { background: var(--surface-2); border: 0.5px solid var(--border); }
  .pmlegend em { font-style: normal; color: var(--text-muted); }
  .pmover { color: var(--text-danger); font-weight: 600; }
  .pmwarntext { color: var(--text-warning); }
  /* Decision 0545 — the linked PO is on hold or closed. */
  .pmstatuswarn { margin: 0 0 10px; padding: 8px 12px; border-radius: var(--radius); border-left: 3px solid var(--severity-danger-text); background: var(--severity-danger-bg); color: var(--severity-danger-text); font-weight: 500; }
  .pmtable { width: 100%; border-collapse: collapse; }
  .pmtable th { text-align: left; font-size: var(--text-sm); font-weight: 500; color: var(--text-muted); padding: 6px 8px; border-bottom: 0.5px solid var(--border); }
  .pmtable td { padding: 10px 8px; border-bottom: 0.5px solid var(--border); vertical-align: top; }
  .pmtable .num { text-align: right; font-variant-numeric: tabular-nums; }
  .pmtable td.pmarrow { color: var(--text-muted); width: 24px; }
  .pmresults tr.current td { background: color-mix(in srgb, var(--text-accent) 7%, transparent); }
  .pmtablewrap { overflow-x: auto; }
  .pmunused { margin: 8px 0 0; color: var(--text-secondary); }
  .pmpill { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: var(--text-sm); font-weight: 600; white-space: nowrap; }
  .pmpill.ok { background: var(--bg-success); color: var(--text-success); }
  .pmpill.warn { background: var(--bg-warning); color: var(--text-warning); }
  .pmpill.bad { background: var(--bg-danger); color: var(--text-danger); }
  .pmpill.muted { background: var(--surface-1); color: var(--text-muted); }
  .pmsearchbar { display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center; margin-bottom: 8px; font-size: var(--text-sm); color: var(--text-secondary); }
  .pmsearchbar input[type="search"] { flex: 1 1 280px; }
  .pmuse { white-space: nowrap; }
  /* Decision 0532 — the per-line PO line picker, and who paired it. */
  .pmpair { width: 100%; margin-bottom: 4px; }
  .pmpaired { color: var(--text-accent); }
  /* Decision 0533 — how much of one PO line is used. */
  .pmlineuse { display: grid; gap: 3px; margin-top: 6px; }
  .pmlineuse .pmbar { height: 6px; }
  /* Decision 0534 — a suggested PO line, and its Accept. */
  .pmsuggest { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 10px; margin-top: 6px; padding: 6px 8px; border-radius: var(--radius); background: color-mix(in srgb, var(--text-accent) 10%, transparent); }
  .pmaccept { white-space: nowrap; }
  /* Decision 0536 — the invoice line Match column's chip, and its read-only pop-out. */
  /*
    Fixed widths — decision 0537. `.linetable` is `table-layout: fixed`, so
    0536's `width: 1%` made this column a few pixels wide and the chip
    spilled over the next column (reported live, beside the Coding icon).
    A long translation is cut with an ellipsis; the chip's hover text
    carries it in full.
  */
  .linetable th.matchcol, .linetable td.matchcol { width: 116px; white-space: nowrap; text-align: center; }
  .linetable td.matchcol .pmchip { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
  .pmchip { display: inline-flex; align-items: center; gap: 5px; padding: 2px 9px; border-radius: 999px; border: none; font: inherit; font-size: var(--text-sm); font-weight: 600; white-space: nowrap; cursor: pointer; }
  .pmchip.ok { background: var(--bg-success); color: var(--text-success); }
  .pmchip.warn { background: var(--bg-warning); color: var(--text-warning); }
  .pmchip.bad { background: var(--bg-danger); color: var(--text-danger); }
  /* Decision 0537 — a Non-PO line: resolved, not on the order, coded by hand. */
  .pmchip.muted { background: var(--surface-1); color: var(--text-secondary); border: 0.5px solid var(--border); }
  .pmnotice { margin: 0 0 10px; padding: 6px 10px; border-radius: var(--radius); background: color-mix(in srgb, var(--text-accent) 10%, transparent); color: var(--text-primary); }
  .pmchip:hover { filter: brightness(0.96); }
  .pmchip:focus-visible { outline: 2px solid var(--text-accent); outline-offset: 1px; }
  .pmdot { width: 6px; height: 6px; border-radius: 50%; background: var(--text-accent); display: inline-block; }
  .popout.pmlinepop { max-width: 560px; }
  .pmcompare { display: grid; grid-template-columns: 1fr auto 1fr; gap: 10px; align-items: start; margin: 6px 0 12px; }
  .pmside { display: grid; gap: 3px; padding: 8px 10px; border-radius: var(--radius); background: var(--surface-1); border: 0.5px solid var(--border); }
  .pmcompare .pmarrow { align-self: center; color: var(--text-muted); }
  .pmverdictrow { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; margin-bottom: 8px; }
  .pmpopfoot { margin: 14px 0 0; border-top: 0.5px solid var(--border); padding-top: 12px; display: flex; justify-content: flex-end; }
  /* Decision 0774: the line's Receipts, in its Match pop-out. */
  .rcpsection { margin-top: 14px; padding-top: 12px; border-top: 0.5px solid var(--border); }
  .rcphead { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; margin-bottom: 8px; }
  .rcphead h4 { margin: 0; font-size: var(--text-base); }
  .rcpgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
  .rcpcol { display: grid; gap: 3px; padding: 8px 10px; border-radius: var(--radius); background: var(--surface-1); border: 0.5px solid var(--border); }
  .rcpr { display: flex; justify-content: space-between; gap: 10px; font-size: var(--text-sm); }
  .rcpr b { font-weight: 500; font-variant-numeric: tabular-nums; }
  .rcpr.tot { border-top: 0.5px solid var(--border); padding-top: 3px; margin-top: 2px; }
  .rcpr.tot b { font-weight: 700; }
  .rcpresult { display: flex; justify-content: space-between; gap: 10px; margin-top: 8px; font-size: var(--text-sm); font-weight: 600; }
  .rcpresult b { font-variant-numeric: tabular-nums; }
  .rcpresult.over b { color: var(--text-danger); }
  .rcplist { margin-top: 10px; display: grid; }
  .rcprow { display: grid; grid-template-columns: minmax(6em, auto) 1fr auto auto; gap: 10px; align-items: center; font-size: var(--text-sm); padding: 5px 0; border-bottom: 0.5px solid var(--border); }
  @media (max-width: 560px) { .rcpgrid { grid-template-columns: 1fr; } .rcprow { grid-template-columns: 1fr auto; } }
  p.pmpopfoot { display: block; }
  .pmfoot { margin: 14px 0 0; border-top: 0.5px solid var(--border); padding-top: 12px; }
  @media (max-width: 800px) {
    .pmcard { grid-template-columns: 1fr; }
    .pmmeta { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  }

  /**
   * **`note()`'s own pop-out alert — decision 0493.** Reported live
   * against the first version (`.cardhead`/`.statebuttons`/
   * `.actionlink`, borrowed from the Reassign/Return pickers):
   * "It looks unaligned, ugly." Those three exist for a form or a
   * list — a heading, then fields, then a row of actions along the
   * bottom-left. An alert holds one sentence and one answer, so it
   * gets its own narrower, centred shape rather than a card built for
   * something wider with nothing to put in most of it.
   */
  .popout.notealert {
    width: min(340px, 100%);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
    padding: 28px 24px 24px;
    text-align: center;
  }
  .notealert-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: var(--bg-warning);
    color: var(--text-warning);
  }
  /*
    **The one caller that is not a problem** — Save succeeding. Every
    other message through here is a warning by nature (nothing to
    reassign to, a blocked pop-up, a save that failed); dressing
    "Saved" in the same amber triangle would tell a person something
    had gone wrong when nothing had.
  */
  .notealert-icon.success { background: var(--bg-success); color: var(--text-success); }
  .notealert-icon svg { width: 28px; height: 28px; }
  /* `pre-line` — decision 0511: a refused coding save lists one line per
     problem; a message with no line breaks renders exactly as before. */
  /*
    **Approval mode definitions — decision 0516.** Every mode listed
    under AP Setup's picker; the selected one is highlighted with the
    accent border and full-strength text, and the rest are muted.
  */
  .modedefs { display: grid; gap: 8px; margin-top: 12px; }
  .modedef {
    border: 0.5px solid var(--border);
    border-left: 3px solid transparent;
    border-radius: 6px;
    padding: 8px 12px;
    color: var(--text-secondary);
  }
  .modedef p { margin: 4px 0 0; }
  .modedef-title { font-weight: 600; }
  .modedef.selected {
    border-left-color: var(--border-accent);
    background: var(--bg-accent);
    color: var(--text-primary);
  }

  /*
    **Help — decision 0518.** A side panel, the operator's choice, so
    the document and its buttons stay visible while reading. Below the
    pop-out backdrop (50), so a picker opened from the page still sits
    on top of it. Full width on a phone.
  */
  .helppanel {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: min(400px, 100vw);
    z-index: 40;
    display: flex;
    flex-direction: column;
    background: var(--surface-2);
    border-left: 0.5px solid var(--border);
    box-shadow: -8px 0 24px rgba(0, 0, 0, 0.18);
    padding: 16px 18px;
  }
  .helppanel .helpbody { overflow-y: auto; min-height: 0; flex: 1; }
  .helpsection { margin-top: 14px; }
  .helpsection h4 { margin: 0 0 6px; }
  /* Decision 0562: help written at length — headings and lists. */
  .helpsection h5.helpsub { margin: 14px 0 4px; font-size: var(--text-sm); font-weight: 700; color: var(--text-primary); }
  .helpsection ul.helplist { margin: 4px 0 8px; padding-left: 18px; display: grid; gap: 4px; }
  .helpactions { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 10px; }
  .helpaction { border: 0.5px solid var(--border); border-radius: 6px; padding: 8px 10px; }
  .helpactiontitle { display: flex; align-items: center; gap: 6px; font-weight: 600; }
  .helpactiontitle svg { width: 16px; height: 16px; }
  .helpaction p { margin: 4px 0 0; }
  .helpwhy {
    margin: 6px 0 0;
    padding: 6px 8px;
    border-left: 3px solid var(--border-accent);
    background: var(--bg-accent);
    color: var(--text-primary);
  }
  .helpask textarea { width: 100%; min-height: 64px; margin: 4px 0 8px; box-sizing: border-box; }
  /* Ask's own panel — decision 0519. Each question above its answer. */
  .askthread { display: grid; gap: 10px; margin-top: 12px; }
  .askturn { display: grid; gap: 4px; }
  .askquestion {
    margin: 0;
    justify-self: end;
    max-width: 85%;
    padding: 6px 10px;
    border-radius: 10px;
    background: var(--bg-accent);
    color: var(--text-primary);
  }
  .askturn .helpanswer { margin: 0; }
  .helpanswer { white-space: pre-line; }
  /* Decision 0958: an AP Expert answer in Ask, drawn as Markdown (0950). */
  /* Decision 0959: every Ask answer drawn as Markdown. */
  .helpanswer.askmarkdown { white-space: normal; }
  .askmarkdown > .chatpara { margin: 0 0 6px; }
  .askmarkdown > .chatpara:last-child { margin-bottom: 0; }
  .askmarkdown > .chatheading { margin: 4px 0; font-weight: 600; }
  .askmarkdown > .chatlist { margin: 0 0 6px; padding-left: 18px; }
  .askexpertanswer > .askfromexpert { margin: 6px 0 0; }

  /* Decision 0513 — a message listing one line per problem. */
  .prelines { white-space: pre-line; }
  .notealert-message { font-size: var(--text-lg); color: var(--text-primary); line-height: 1.4; margin: 0; white-space: pre-line; }
  .notealert-ok {
    padding: 8px 28px;
    border-radius: 999px;
    border: 0.5px solid var(--border);
    background: var(--surface-1);
    color: var(--text-primary);
    font-weight: 600;
    font-size: var(--text-base);
    cursor: pointer;
  }
  .notealert-ok:hover {
    background: var(--bg-accent);
    color: var(--text-accent);
    border-color: var(--border-accent);
  }

  .searchbox {
    width: 100%;
    margin: 8px 0;
  }
  .searchresults { margin-bottom: 10px; }
  .searchresult {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: none;
    border-bottom: 0.5px solid var(--border);
    padding: 8px 4px;
    cursor: pointer;
    font: inherit;
    color: inherit;
  }
  /* `--bg-hover` was invented too. */
  .searchresult:hover { background: var(--surface-1); }

  /*
    **One searchable field, embedded in a form** — decision 0453, the
    invoice-line Coding pop-out's own four pickers. `openSearch()`'s
    own `.searchbox` is reused unchanged for the input; this only adds
    the small grid around it so a clear ("×") button sits beside the
    box rather than under it. **No results list of its own** — decision
    0458 moved that to one shared area below every field, so this is
    just the box and its clear button.

    **`width: 100%` of one shared column — decision 0461**, not its
    own stated fraction any more. Decision 0460's `66.6667%` here and
    `.codingcompanycode`'s own separate `66.6667%` below were two
    copies of the same number; reported live as visibly out of step
    with each other (see `--codingfield-w` on `.popout.codingpopout`
    below, the one place that number is stated now) once they had.
    `width` still lives on the grid itself, not just the input inside
    it, so the clear button narrows along with the box.
  */
  .codingsearch {
    display: grid;
    grid-template-columns: 1fr 28px;
    gap: 4px;
    align-items: start;
    width: 100%;
  }
  .codingsearch .searchbox { margin: 0; }

  /*
    **Org / Company Code, matched to the fields beneath it — decision
    0462, the actual reason 0461's own `width: 100%` still rendered
    wider than the search boxes (reported live again: "way out of
    line... it spills out of the page").** `.readonly` is a `<div>`,
    not an `<input>` — `tokens.css`'s own `box-sizing: border-box` rule
    is stated for `input:not([type=checkbox]):not([type=radio]),
    textarea` specifically, and never reaches it. A content-box div
    with `width: 100%` and its own `padding: 8px 11px` (`.readonly`,
    decision 0402) renders at *the column's own width plus 22px* — the
    real reason this box was ever wider than the search boxes, in
    every decision that touched it (0460's matching `66.6667%`, and
    0461's matching `width: 100%`, both matched the *number* and still
    rendered a different *pixel width*, since one box's padding counts
    toward its own width and the other's doesn't). `box-sizing:
    border-box` makes `width: 100%` mean the same thing here as it
    already does for a real `<input>`. `min-height` still restates
    `.readonly`'s own 32px as 38px — `.readonly` is shared far too
    widely to touch its own default for one popout.
  */
  /* Decision 0539: Org / Company Code is a context chip now (`.codingchip.codingcompanycode`), no longer a box to match. */

  /*
    **The Coding pop-out is a fixed size, and its search results are
    one shared area, not one per field — decision 0458.** Reported
    live, from a mock-up: each field's own results used to grow that
    field's row, so the whole pop-out reflowed — grew and shrank — on
    every keystroke. Now `.codingpopout` fixes the pop-out's own
    height; `.editgrid` (always exactly five rows: Org/Company Code
    plus the four pickers) never changes size, and `.codingresults`
    fills whatever height is left below it and scrolls internally
    instead of pushing the pop-out's own edges around.

    Only one field's results show at a time — whichever was searched
    most recently, per `codingResultsController` in `viewer.js` — so a
    shared area is unambiguous rather than a loss of information.

    **The pop-out's own width, computed rather than fixed — decision
    0461.** Decision 0458 set this to 900px for a two-column results
    grid; decision 0459 already put that grid back to one column, and
    decision 0460 narrowed the boxes inside without ever revisiting
    this, so Close (pinned to the pop-out's own edge by `.cardhead`'s
    `justify-content: space-between`) sat well past where the boxes
    actually ended — reported live, with a screenshot, and worked out
    first as a mock-up (`docs/design/mockups/line-coding-popout-widths.html`)
    rather than guessed and re-shipped. `--codingfield-label-w` and
    `--codingfield-w` are the only two numbers stated anywhere in this
    component now; the pop-out's own width is their sum plus its
    padding and the grid's own gap, so Close lands on the box's own
    right edge as an arithmetic consequence, not a second measurement
    someone has to keep matching by hand. Chosen live, from the
    mock-up's own slider: 190px / 320px.
  */
  /*
    **Redesigned as a card — decision 0539**, the operator's choice (A)
    from two mock-ups, in the Match pop-out's style. Still a fixed
    height with one shared results area that scrolls (0458), so nothing
    reflows as somebody types. The label/box column widths of
    0460–0462 are gone with the label column itself: each field is a
    card with its own label.
  */
  .popout.codingpopout {
    width: min(620px, 100%);
    height: min(780px, 90vh);
    display: flex;
    flex-direction: column;
  }
  .codingsub { margin: 2px 0 0; color: var(--text-secondary); font-size: var(--text-sm); }
  .codingctx { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 12px; flex: none; }
  .codingchip {
    display: inline-flex; align-items: baseline; gap: 6px; padding: 3px 10px; border-radius: 999px;
    background: var(--surface-1); border: 0.5px solid var(--border); font-size: var(--text-sm); color: var(--text-secondary);
  }
  .codingchip b { color: var(--text-primary); font-weight: 600; }
  .codingid { margin-left: 6px; font-size: var(--text-sm); color: var(--text-muted); font-weight: 400; }
  .codingchip .codingid { margin-left: 0; }
  .popout.codingpopout .codinglocked { flex: none; }
  .codingsuggest {
    flex: none; margin-bottom: 12px; padding: 10px 12px; border-radius: var(--radius);
    background: color-mix(in srgb, var(--text-accent) 9%, transparent);
    border: 0.5px solid color-mix(in srgb, var(--text-accent) 35%, transparent);
  }
  .codingsuggesthead { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
  .codingsuggestvalues { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 6px; }
  .codingsuggestwhy, .codingsuggestex, .codingsuggestdone { font-size: var(--text-sm); color: var(--text-secondary); }
  .codingsuggestex { margin-top: 3px; color: var(--text-muted); font-style: italic; }
  .codingmeter {
    display: inline-block; width: 56px; height: 6px; margin-right: 6px; vertical-align: middle; overflow: hidden;
    border-radius: 999px; background: var(--surface-2); border: 0.5px solid var(--border);
  }
  .codingmeter i { display: block; height: 100%; background: var(--chart-1); }
  .codingfields { flex: none; display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
  .codingfield {
    min-width: 0; padding: 8px 10px; border-radius: var(--radius);
    background: var(--surface-1); border: 0.5px solid var(--border);
  }
  /*
    **Day's pale green in both moods — decision 0541**, the operator's
    choice after seeing both: the fixed `--severity-ok-*` pair (0400),
    not `--bg-success`, which turns dark green at Night. The card's own
    label and tick take fixed colours too, since Night's light text
    would vanish on it; the inputs inside keep their own surfaces.
  */
  .codingfield.set { background: var(--severity-ok-bg); border-color: color-mix(in srgb, var(--border-success) 45%, transparent); }
  .codingfieldhead { display: flex; justify-content: space-between; margin-bottom: 5px; font-size: var(--text-sm); color: var(--text-muted); }
  .codingfield.set .codingfieldhead { color: color-mix(in srgb, var(--severity-value-text) 62%, transparent); }
  .codingfieldstatus { color: var(--text-success); font-weight: 700; }
  .codingfield.set .codingfieldstatus { color: var(--severity-ok-text); }
  .codingfield .readonly { box-sizing: border-box; width: 100%; min-height: 34px; }
  .codingfoot {
    flex: none; display: flex; justify-content: space-between; align-items: center; gap: 12px;
    margin-top: 10px; padding-top: 10px; border-top: 0.5px solid var(--border); font-size: var(--text-sm); color: var(--text-secondary);
  }
  /* Decision 0542 — the chosen project's budget bar, legible on a set card's fixed pale green in both moods. */
  .codingbudget { display: grid; gap: 3px; margin-top: 6px; }
  .codingbudget:empty { display: none; }
  .codingfield.set .codingbudget .muted { color: color-mix(in srgb, var(--severity-value-text) 62%, transparent); }
  .codingfield.set .codingbudget .pmover { color: var(--severity-danger-text); }
  .codingfield.set .codingbudget .pmbar { background: #ffffff; border-color: color-mix(in srgb, var(--severity-value-text) 20%, transparent); }
  .codingfield.set .codingbudget .pmseg.others { background: #7b8798; }
  .codingfield.set .codingbudget .pmseg.mine { background: #185fa5; }
  .codingfield.set .codingbudget .pmseg.mine.over { background: var(--severity-danger-text); }
  /* Decision 0542 — a project's status in the Account Coding list. */
  .codingstatus { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: var(--text-sm); font-weight: 600; }
  .codingstatus.active { background: var(--bg-success); color: var(--text-success); }
  .codingstatus.closed { background: var(--surface-1); color: var(--text-muted); }
  /* Decision 0540 — AP Setup's either/or choice: two stacked radio options with a line of help each. */
  .radiochoices { display: grid; gap: 8px; margin: 6px 0; }
  .radiochoice { display: flex; gap: 10px; align-items: flex-start; cursor: pointer; }
  .radiochoice input { margin-top: 3px; }
  .radiochoice > span { display: grid; gap: 2px; }
  /* Decision 0540 — Cost centre OR Project: one full-width card, a two-way switch at its head. */
  .codingfield.codingobject { grid-column: 1 / -1; }
  /* Decision 0547 — a project-only supplier site. */
  .codingprojectonly { margin: 2px 0 6px; }
  .codingswitch { display: inline-flex; border: 0.5px solid var(--border); border-radius: 999px; overflow: hidden; background: var(--surface-0); }
  .codingswitch button {
    border: none; background: transparent; padding: 3px 12px; font: inherit; font-size: var(--text-sm); color: var(--text-secondary); cursor: pointer;
  }
  .codingswitch button.on { background: var(--brand-fill); color: #fff; font-weight: 600; }
  @media (max-width: 560px) { .codingfields { grid-template-columns: 1fr; } }
  /* The one filled action in each pop-out, as the viewer's own footer draws it (`.vfoot .primary`). */
  .popout.codingpopout button.primary, .popout.pmlinepop button.primary, .popout.supplierask button.primary {
    background: var(--brand-fill); color: #fff; border: none; min-height: 34px; padding: 0 16px; font-weight: 500;
  }
  .codingresults {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    border-top: 0.5px solid var(--border);
    padding-top: 10px;
    margin-top: 6px;
  }
  .codingresultslabel { min-height: 1.2em; margin-bottom: 6px; }
  /*
    **One column, unwrapped rows** — decision 0459, reversing 0458's own
    two-column layout: reported live as harder to scan than a single
    list ("I would prefer 1 listed set of results rather than scrolling
    the results across two panels"). `overflow-y: auto` (unchanged from
    0458) keeps the scrollbar this now makes more likely to be needed,
    a single column holding fewer rows per screen height than two did.
    `.resultname` still truncates with an ellipsis rather than wrapping
    — the full name is still on the button's own `title` — and
    `.resultid` still never shrinks, since reading the code back is
    often the point of searching at all.
  */
  .codingresultslist {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 2px;
  }
  .codingresultslist .searchresult {
    display: flex;
    align-items: baseline;
    gap: 6px;
    border-bottom: none;
    padding: 5px 4px;
  }
  .codingresultslist .resultname {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .codingresultslist .resultid { flex: none; white-space: nowrap; }

  /*
    **Every card's own title, and a rule beneath it** — decision 0396,
    the second of the four pieces borrowed from
    e-invoicingcompliancecorner.com and agreed with the operator: the
    bold condensed heading, and "a horizontal line running the width
    of each card beneath the card title." `--heading-accent` and
    `--border-strong` both already carry Day and Night, so this needed
    no mood-specific rule of its own — decision 0139's usual discipline
    is what makes that true.

    `--text-lg` rather than `--text-base`: its own comment in
    tokens.css has called it "Panel headings" since decision 0124, and
    nothing used it for that until now.

    **Scoped to `.panel`'s own direct children, not bare `.cardhead`.**
    `.cardhead` is shared with every modal dialog — access.js,
    sources.js, suppliers.js, processes.js, purchase-orders.js all
    build a "Save"/"Close" pop-out head with it — and a dialog is not
    a card. `.panel > .cardhead` is what keeps the rule off every one
    of those.

    **The dashboard's own tiles get it too, decision 0397** —
    `.panel > .tilefg > .cardhead > h3` is that same title one level
    deeper, the shape every `card-narrow`/`card-graphic` tile builds
    through dashboard.js's own `panel()` helper. Decision 0396 first
    read `.tilefg`'s extra nesting as reason enough to leave those
    quiet, matching 0242's own "no verdict" treatment for a bare
    count. Shown side by side with the rest of the dashboard, live,
    the read was the opposite: a title that stops looking like every
    other card's is not "no verdict," it is a different card. Chosen
    directly, offered against two other ways to resolve it.
  */
  .panel > h3,
  .panel > .cardhead > h3,
  .panel > .tilefg > .cardhead > h3,
  /* Decision 0813: the supplier portal, one level into its panel, headed as the page's cards are. */
  .suppliersidepanel > .supplierportal > .cardhead > h3 {
    margin: 0;
    font-family: var(--font-heading);
    font-size: var(--text-lg);
    font-weight: 800;
    letter-spacing: 0.2px;
    text-transform: uppercase;
    color: var(--heading-accent);
  }
  .panel > h3,
  .panel > .cardhead,
  .panel > .tilefg > .cardhead,
  .suppliersidepanel > .supplierportal > .cardhead {
    padding-bottom: 10px;
    margin-bottom: 12px;
    border-bottom: 1px solid var(--border-strong);
  }

  /*
    **The title sits on the line, not floating above a tall action** —
    reported live once decision 0396 shipped: a card whose action is
    `actionLink()`'s icon-above-label shape (decision 0122) — Purchase
    Orders' own CSV Template/Load CSV, wrapped in `.statebuttons` and
    so outside `.cardhead > .actionlink`'s existing `-6px` pull — made
    the whole row as tall as the action, with the title left pinned to
    `flex-start` at the top and a bare gap opening beneath it, above
    the new rule.

    `align-items: flex-end` puts every title on the row's own bottom
    edge regardless of what the action beside it is shaped like, which
    is what "just above the horizontal line" asked for directly.
  */
  .panel > .cardhead {
    align-items: flex-end;
  }

  /*
    **The action itself, compacted** — the other half of the same
    report: a corner control does not need the same generous padding
    `.actionlink` gives a control sitting in a row of its own (the
    control row, the pagination arrows' own row). Scoped to a card's
    own heading, not `.actionlink` itself, so nothing outside a
    `.panel`'s `.cardhead` changes size.
  */
  .panel > .cardhead > .actionlink,
  .panel > .cardhead .statebuttons .actionlink {
    padding: 4px 6px;
    gap: 3px;
  }
  .panel > .cardhead > .actionlink svg,
  .panel > .cardhead .statebuttons .actionlink svg {
    width: 18px;
    height: 18px;
  }

  .stat .label { font-size: var(--text-sm); color: var(--text-secondary); }
  .stat .value { font-size: var(--text-xl); font-weight: 600; margin-top: 3px; }
  .stat .note { font-size: var(--text-sm); color: var(--text-muted); margin-top: 2px; }
  .stat.good .value { color: var(--text-success); }
  .stat.warn .value { color: var(--text-warning); }



  /* Seller beside buyer, collapsing on a narrow screen like everything
     else (decision 0108). */
  .parties {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 16px;
    margin-bottom: 16px;
  }
  /*
    Load a supplier file, beside the status ring — decision 0299,
    reported live: "the 'Load a supplier file' card is wide, and
    takes up space." Same shape `.parties` already gives the Seller
    and Buyer cards, named for this screen rather than reused under a
    name that says party.
  */
  .supplierhead {
    display: grid;
    /**
     * **The status card no longer matches the load-file card's own
     * width** — reported live: "the Supplier Status card width be
     * reduced, therefore the counts are closer to the text." An equal
     * `auto-fit` split gave it as much room as the load-file card's
     * own help text and file picker actually use, which the ring and
     * a four-row legend never needed — `.donutlegend`'s own
     * `flex: 1 1 auto` stretched the label to fill whatever was left,
     * pushing every count to the far right edge of space nothing was
     * using.
     */
    grid-template-columns: minmax(0, 1fr) minmax(260px, 380px);
    gap: 16px;
    margin-bottom: 16px;
  }
  /*
    **Still not matched, decision 0301** — reported live, against the
    real, deployed screen: "alignment between cards at the bottom
    still seems off." Decision 0300's own fix removed `align-items:
    start`, leaving grid's own default, stretch, to apply — which
    should have been enough, and evidently was not: the two panels
    still ended at different heights on the actual screen.

    `height: 100%` says directly, on the element itself, what
    `align-items: stretch` was only ever implying by omission. A more
    forceful, explicit instruction in place of one that was not
    visibly taking hold.
  */
  .supplierhead > .panel {
    height: 100%;
    box-sizing: border-box;
  }
  @media (max-width: 900px) {
    .supplierhead { grid-template-columns: 1fr; }
    .supplierhead > .panel { height: auto; }
  }

  /**
   * The same load-card-plus-status-chart split Suppliers already
   * uses, renamed for this screen rather than reusing a class that
   * says "supplier" — decision 0377, the operator's own request:
   * "to the right of the Load purchase orders card."
   */
  .poloadhead {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(260px, 380px);
    gap: 16px;
    margin-bottom: 16px;
  }
  .poloadhead > .panel { height: 100%; box-sizing: border-box; }
  @media (max-width: 900px) {
    .poloadhead { grid-template-columns: 1fr; }
    .poloadhead > .panel { height: auto; }
  }
  /*
    **The Seller card's own sub-line, decision 0290** — reported live:
    "The fields beneath the word Seller appear to be aligned to the
    bottom of the card. The Buyer card, details seem aligned to the
    top."

    The only `.sub` this screen ever renders sits here, and the base
    `.sub` rule (written for the sign-in page's own subtitle, a full
    sentence needing real air beneath it) carries a 34px bottom
    margin — never overridden here the way `#shell .sub` and
    `.vhead .sub` already override it for their own, unrelated
    contexts. A supplier identifier line is a caption, not an intro
    paragraph.
  */
  .parties .sub { margin: 2px 0 8px; }

  /*
    Three columns on a wide monitor, fewer as it narrows.

    A mockup rendered at 1536px never shows what three columns do at
    1280 -- an ordinary laptop -- so the panels reflow rather than
    shrink (decision 0108).

    **Shared with the Sources screen, decision 0177** — this rule
    itself stays exactly what it was: a plain two-track grid, no named
    areas, `.panel`/`.parties` free to sit however many of them like
    inside either side. The viewer's own reshaping (decision 0388,
    below) lives entirely under `#viewer .columns`, more specific than
    this rule without needing to touch it, because a bare `.columns`
    override here would reach `sources.js`'s own use of the same class
    too — decision 0177's whole point was one name meaning one thing,
    and diverging what the name *does* between two screens would undo
    that as surely as the duplicate rule it was written to fix.
  */
  .columns {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    gap: 16px;
    align-items: start;
  }

  /*
    **Named areas, not two independent stacks** — decision 0388, asked
    directly: "the bottom of the Document image card should align with
    the bottom of the invoice header card... this will allow the
    invoice line item card to extend full width." Two plain flow
    columns can only ever end where their own content ends; getting
    the Document card's bottom to land on a *specific row* on the
    other side, and getting Lines to run under both columns rather
    than being confined to one, both need the grid to know about rows,
    not just two side-by-side blocks. `document` is named in three
    rows so it spans exactly as far as `process` + `parties` + `header`
    do; `lines` and `note` name the same area across both column
    tracks, which is what lets them run full width.

    `#viewer` scopes every rule below to this one screen — `.c-process`
    and friends only ever exist here, but `.columns` itself does not,
    so the selector needs to say so rather than assume it.
  */
  #viewer .columns {
    position: relative;
    grid-template-areas:
      "process  document"
      "parties  document"
      "header   document"
      "lines    lines"
      "note     note";
    /* The Document card needs to stretch to fill the area the grid
       gives it (below); the base rule's own `align-items: start` was
       right for two independent stacks and wrong for a card meant to
       reach a specific row on the other side. */
    align-items: stretch;
    /**
     * **3:2, not the base rule's 2:1** — decision 0390, asked directly
     * against a mock-up of both: "I like this balance." The base
     * `.columns` rule's `minmax(0, 2fr) minmax(0, 1fr)` gave the
     * Document card a third of the row (Seller and Buyer splitting the
     * other two thirds between them); this narrows the left track and
     * widens the right one, so the Document card gets two fifths and
     * Seller/Buyer keep three fifths between them — overridden here
     * rather than in the base rule for the same reason the areas above
     * are: a bare `.columns` change would reach the Sources screen too
     * (decision 0177).
     */
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  }
  .c-process { grid-area: process; }
  .c-parties { grid-area: parties; }
  .c-header { grid-area: header; }
  .c-lines { grid-area: lines; }
  .c-note { grid-area: note; }
  .c-document { grid-area: document; min-height: 0; position: absolute; inset: 0; }

  /**
   * **Seller, Buyer and Header share the row Document no longer needs
   * — decision 0392.** Asked for directly, once a pop-out leaves the
   * Document card with nothing in it but a small notice: *"the Seller
   * card, buyer card and Invoice Header card can all occupy the same
   * row."* `viewer.js`'s `setDocPoppedOut()` is the only JS this
   * needed — it toggles this one class on `.columns` and moves
   * nothing: `.c-header` stays exactly where it always sat, as
   * `.c-parties`'s own sibling, and only the grid's own areas and
   * column widths change which row it lands in.
   *
   * **1:1:2, not 3:3:4** — the operator's own choice, "25%/25%/50%"
   * over "30%/30%/40%", after both were mocked up and measured
   * against real content rather than guessed at: at 30/30/40,
   * Header's own `.headersummary` grid no longer has room for its
   * natural columns and drops to fewer of them, growing *taller* than
   * the row this was meant to shrink; at 25/25/50 it keeps its own
   * width and the row stays as short as Seller/Buyer's content, not
   * Header's. `parties` spans the first two of the three tracks (not
   * a dedicated one of its own) — `.parties`'s existing `auto-fit`
   * grid (decision 0115) splits whatever width that gives it between
   * Seller and Buyer on its own, the same as it always has; the
   * combined width merely happens to be half the row now instead of
   * the whole thing.
   */
  #viewer .columns.docpoppedout {
    grid-template-areas:
      "process   process   document"
      "parties   parties   header"
      "lines     lines     lines"
      "note      note      note";
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 2fr);
  }
  /**
   * **Document no longer spans three rows — it shares one with
   * Process instead**, so it needs neither the absolute positioning
   * decision 0391 gave it to escape the grid's own sizing algorithm
   * (there is only one row to size here) nor, `position: static`
   * aside, any override of its own — `.c-document` is still a grid
   * item in that row, and `#viewer .columns`'s own `align-items:
   * stretch` already reaches it. The tabs and Expand button
   * (`.cardhead`) are hidden rather than shrunk to fit: `normalBody`
   * is already hidden the whole time this class is present (`
   * popoutStateSetter`, viewer.js), so every tab they would switch to
   * is hidden too, and a row of now-nonfunctional buttons is not
   * worth the room in a badge this size.
   */
  #viewer .columns.docpoppedout .c-document {
    position: static;
  }
  #viewer .columns.docpoppedout .c-document .cardhead { display: none; }
  /**
   * **Both cards in the row fill it, not just the stretched grid
   * item around them — decision 0393.** Asked for directly, against
   * a screenshot of the deployed card sitting visibly shorter than
   * Process beside it: "evaluate the panel height, so that the
   * height matches the height of the Process card."
   *
   * **Two bugs, not one, once actually measured.** `.c-document`'s
   * own outer box was already the row's full height — decision
   * 0392's own override (`height: auto; display: block;` on
   * `.c-document > .panel:not(.exceptions)`) was what stopped its
   * *visible* panel from filling that box, reasoned at the time as
   * needing "neither... a height matched to anything else," which a
   * real render shows was wrong. Deleting that override (rather than
   * adding a second, competing one) lets the general
   * `.c-document > .panel:not(.exceptions)` rule, above, do exactly
   * what it already does for the three-row case: `height: 100%` on
   * the panel, `flex: 1` on `.vpreview.vpoppedout`, its one visible
   * non-`.cardhead` child once popped out.
   *
   * **The second bug was Process's own, and only visible once
   * Document's real content — not an absolutely-positioned box
   * outside the flow — started sharing its row.** Before 0392,
   * `.c-document` was unconditionally `position: absolute` (0391),
   * so it never contributed to this row's own height at all; Process
   * was always the only real content in it, and its own panel was
   * always exactly the row's height, nothing to stretch. Once
   * Document became a genuine, `position: static` participant, the
   * row's height became whichever card's content is taller — and
   * measured directly, that is now sometimes Document's badge, not
   * Process's chevrons. Process's own panel needed the identical
   * `height: 100%` this rule gives Document's, so the shorter side
   * fills the row exactly the same way regardless of which side that
   * turns out to be.
   */
  /**
   * **A blanket `height: 100%` on every panel only ever worked because
   * there was only ever one — decision 0478 broke that assumption.**
   * Reported live, against a screenshot: popped out, the two cards at
   * the top ("Here because" and "Document open in a separate window")
   * were much too tall and misaligned, and the process timeline
   * visibly overlaid the Seller/Buyer row beneath it.
   *
   * The cause: `.c-process` can now hold up to three panels
   * (`workflowErrorPanel()`, `reasonLinePanel()`, `progressRow()`),
   * and the original rule below gave *each* of them `height: 100%` —
   * each one independently claiming the *row's* full height rather
   * than sharing it, so their stacked total overflowed the row by as
   * much as 200%, and that overflow is exactly what rendered as an
   * overlay onto the next grid row down. `workflowErrorPanel()` alone
   * could already have triggered this (a genuine stage error plus an
   * in-progress instance both render), but `openTaskReason` — present
   * on most open tasks, not only a rare engine failure — is what made
   * it common enough to notice.
   *
   * **Fixed with flex, not a second height rule.** `.c-process` is
   * already stretched to the row's real height by `#viewer .columns`'s
   * own `align-items: stretch` (decision 0393's comment above confirms
   * this already happens, for `.c-parties`) — nothing here needs to
   * *give* it that height, only to *share* it correctly among however
   * many panels it holds. A banner (error or reason) keeps its own
   * natural, compact height; only the last panel — `progressRow()`,
   * whenever it renders, exactly like before — grows to fill whatever
   * room is left. With a single panel (the only case this ever ran in
   * before), `flex: 1` on it reproduces the old `height: 100%` exactly.
   */
  #viewer .columns.docpoppedout .c-process {
    display: flex;
    flex-direction: column;
  }
  #viewer .columns.docpoppedout .c-process > .panel {
    box-sizing: border-box;
    flex: 0 0 auto;
  }
  #viewer .columns.docpoppedout .c-process > .panel:last-child {
    flex: 1 1 auto;
    min-height: 0;
  }
  /**
   * **The notice itself, shrunk from a box built to fill the whole
   * card down to a small corner note** — decision 0392, asked for
   * directly: "could occupy a much smaller card in the same space."
   *
   * **Text on the left, actions square and right-aligned — decision
   * 0393.** Reported live, against the deployed card: the text should
   * move left and read "Document open in a separate window" (the
   * string itself changes in `ui_strings`, migration 0122 — nothing
   * here decides wording), and the two actions should look like
   * "Header Fields" — the plain, unmodified `.actionlink` square,
   * icon above label — rather than the row-format, icon-beside-label
   * variant 0392 built. That row-format override is deleted outright
   * rather than kept dormant: `.vpoppedoutactions .actionlink` now
   * simply falls through to the shared `.actionlink` rule every other
   * cardhead action already uses. `justify-content: space-between`
   * on the flex row is what actually right-aligns the actions and
   * left-aligns the text — two children in a row, not one child told
   * to hug an edge.
   */
  #viewer .columns.docpoppedout .vpreview.vpoppedout {
    min-height: 0;
    border: none;
    background: none;
  }
  #viewer .columns.docpoppedout .vpreview.vpoppedout .vthumb {
    height: 100%;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 0;
    background: none;
    color: var(--text-secondary);
    font-size: var(--text-sm);
    text-align: left;
  }
  #viewer .columns.docpoppedout .vpoppedouttext { margin-bottom: 0; }

  /*
    **The Document card fills exactly the area the grid gives it**,
    the same flex chain decision 0386 built for the pop-out rather
    than a second guessed height: `.vpreview`'s own `calc(100vh -
    300px)` was measured against this app's own topbar and tabs on the
    assumption of a full page below them, which stopped being true the
    moment this card's own height became "as tall as three rows on the
    other side of the grid" instead. `:not(.exceptions)` matters here
    specifically — `exceptionPanel()` still shares this grid cell and
    still carries the base `.panel` class, and without the exclusion
    this rule's own specificity would beat the `.exceptions { display:
    none }` rule below and undo the hide.
  */
  .c-document > .panel:not(.exceptions) {
    height: 100%;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    min-height: 0;
  }
  .c-document > .panel:not(.exceptions) > *:not(.cardhead) { flex: 1; min-height: 0; }
  /**
   * **`100%`, not `auto` — decision 0390.** `auto` was measured (while
   * scoping the same decision's width change) to leave `.vpreview`
   * sitting at its own `min-height: 320px` floor regardless of how
   * much taller the card actually was: a block element's `auto`
   * height is content-based, not "fill the parent," and nothing inside
   * an empty or not-yet-drawn preview has any content to size to. Its
   * direct parent — the rule just above, matched by `> *:not(.cardhead)`
   * — already has a real, flex-computed height, so `100%` now resolves
   * against something definite instead of cascading `auto` down
   * through `.vthumb`'s own pre-existing `height: 100%` (decision
   * 0271) and leaving it, too, sized by nothing but that same floor.
   *
   * **`min-height: 0`, not the base rule's `320px` — decision 0391.**
   * 0390 left that floor in place, reasoned as harmless — "still the
   * floor for a card genuinely given less than that." Measured while
   * building 0391's fix and found wrong: once the Document card is
   * genuinely capped to whatever process+parties+header add up to
   * (0391, immediately below), a card given less than 320px hits this
   * floor and grows past its own container's bottom — `.vpreview`'s
   * `overflow: hidden` clips its own children, not itself, so the
   * element simply overflowed `normalBody` and every ancestor above it
   * that doesn't clip either, reproducing visually as the Document
   * card spilling into the Lines panel below it. Zero, not a smaller
   * floor: the whole point of 0391 is a card that is exactly as tall
   * as the rest of the row, never taller, however little that leaves
   * the preview.
   */
  .c-document .vpreview { height: 100%; min-height: 0; }

  /**
   * **The Timeline / Chat pane gets the same real height `.vpreview`
   * already has — decision 0504.** `normalBody`'s own `> *:not(.cardhead)`
   * rule (above) already gives every tab pane a definite, flex-computed
   * height to size against; `.vpreview` claims its own share of that
   * with `height: 100%`, but `.vtimeline` (`viewer.js`'s `timelinePane`)
   * had no class at all until this decision, so it fell back to plain
   * block flow — natural, content-based height, free to grow past
   * whatever the card actually had left. Reported live: the reply box
   * sat hard against the card's bottom edge with nothing below it, and
   * opening "Add person" pushed the whole feed down into the Invoice
   * Lines card beneath — the exact overflow-into-the-next-row bug
   * decision 0391 already fixed once for `.vpreview`, just never wired
   * up for this pane.
   *
   * **A flex column, not a bare height** — `.vtimeline` stacks three
   * pieces (`collaborators.js`'s own `.collabtabcontent`, an optional
   * `.systemalert`, and `activity.js`'s `.activitytabcontent`), and
   * only the last should ever grow: the collaborator bar and the
   * system alert keep whatever size their own content needs, `flex: 0
   * 0 auto`, and `.activitytabcontent` takes whatever the card has
   * left, `flex: 1 1 auto; min-height: 0`, the same "last one grows"
   * shape decision 0478's own `.c-process` fix already established for
   * a stack of unequal-height panels sharing one row.
   */
  .c-document .vtimeline { height: 100%; min-height: 0; display: flex; flex-direction: column; }
  .c-document .vtimeline > .collabtabcontent,
  .c-document .vtimeline > .systemalert {
    flex: 0 0 auto;
  }
  .c-document .vtimeline > .activitytabcontent { flex: 1 1 auto; min-height: 0; }
  /**
   * **The same `display` beats `[hidden]` pattern decision 0271 and
   * `.activitytabcontent`'s own rule already fixed twice, now here a
   * third time — decision 0506.** `timelinePane`/`.vtimeline` had no
   * class, and so no `display` rule of its own, until decision 0504
   * gave it `display: flex` — which is exactly what stopped the
   * browser's own default `[hidden] { display: none }` from taking
   * effect the moment `select()` (`viewer.js`) set `hidden` on it for
   * the Document/XML tabs. Reported live, against two screenshots:
   * with Document selected, the System Alert still showed, floating
   * over the Invoice Lines card beneath it — `.vtimeline` was never
   * actually hidden at all, only marked as such.
   */
  .c-document .vtimeline[hidden] { display: none; }

  /*
    Each of these is now the only thing in its own named row, spaced
    by `.columns`'s own `gap` (16px) rather than by the margins
    decision 0178/0179 gave a panel for sitting in a plain stack —
    without this, a panel's own bottom margin would double up on top
    of the grid gap that already spaces it from the next row.
  */
  .c-parties > .parties,
  .c-header > .panel,
  .c-lines > .panel {
    margin-bottom: 0;
  }

  /**
   * **Seller and Buyer stretch to Header's own bottom edge — decision
   * 0393, and only while popped out.** Reported live, against a
   * screenshot of the deployed row: Seller/Buyer ended well above
   * Header's own bottom, a visible gap neither card's border
   * explained. The cause: `#viewer .columns`'s own `align-items:
   * stretch` already stretches `.c-parties` itself (the grid item) to
   * the row's full height whenever Header's content makes that row
   * taller — checked directly, and it does — but nothing told
   * `.parties`, `.c-parties`'s own child and the thing that actually
   * draws Seller and Buyer's borders, to fill that stretched box; a
   * block element's `auto` height is content-based regardless of how
   * tall its parent has become. `height: 100%` here is the same fix
   * already used for `.c-document .vpreview` (decisions 0390/0391):
   * during row sizing this percentage is treated as `auto` (so it
   * cannot inflate the row it also depends on), and only resolves to
   * the real, settled row height once stretch applies afterwards.
   * `.parties`'s own internal grid then does the rest on its own —
   * its default `align-items: stretch` (decision 0179's own comment:
   * "its panels stretch to the same row height") already stretches
   * Seller and Buyer to whatever height `.parties` itself now has.
   *
   * **Only in this state** — asked for directly, "when the document
   * image is expanded only." Docked, `parties` and `header` are not
   * even in the same row (three separate rows, `#viewer .columns`'s
   * base areas), so there is no shared row height to stretch to and
   * this would do nothing there regardless; scoped under
   * `.docpoppedout` anyway, for the same reason nothing else in this
   * block reaches beyond it.
   */
  #viewer .columns.docpoppedout .c-parties > .parties {
    height: 100%;
  }

  /**
   * **The same fix, the other direction.** Decision 0393's own comment
   * above only ever covered Header being the *taller* of the two —
   * that was the direction actually observed and reported at the
   * time. Reported live since: with a Seller card long enough to need
   * it (an unmatched-supplier notice plus several editable fields),
   * `.c-parties` is now the taller side, and nothing told `.c-header`'s
   * own panel to stretch to match it — the identical "a block
   * element's `auto` height is content-based regardless of how tall
   * its parent has become" gap, just on the side that happened not to
   * be hit yet. Same fix, same reasoning: `height: 100%` is read as
   * `auto` during row sizing (so it cannot itself inflate the row) and
   * only resolves once the grid's own `align-items: stretch` has
   * settled the real row height.
   */
  #viewer .columns.docpoppedout .c-header > .panel {
    height: 100%;
    box-sizing: border-box;
  }

  @media (max-width: 1100px) {
    .frame { grid-template-columns: 1fr; }
    .nav {
      display: flex;
      gap: 6px;
      border-right: none;
      border-bottom: 0.5px solid var(--border);
      padding: 10px;
      /**
       * **Undoes decision 0281's sticky sidebar**, here specifically —
       * this nav is a horizontal bar at this width, not a column with
       * a foot to pin. `position: sticky` plus `height: 100vh` on a
       * bar this short would hold a mostly-empty, page-tall box open
       * at the top of the screen instead.
       */
      position: static;
      height: auto;
      /**
       * **Undoes decision 0438's own negative margin here too** — that
       * margin exists to cancel `body`'s own top padding for the wide,
       * sticky sidebar specifically; at this width `.nav` is an
       * ordinary flow element (a horizontal bar, `position: static`
       * above), so it needs no correction and should sit exactly where
       * normal flow already puts it, the same as everything else on
       * the page.
       */
      margin-top: 0;
    }
    .nav .mark, .nav .who { display: none; }
    /**
     * **Undoes `.navscroll`'s own wrapping, decision 0436** — that
     * wrapper exists to cap and scroll the nav's content in the wide,
     * one-viewport-tall sidebar; at this width `.nav` is a short
     * horizontal bar with no such box to fill, and every child
     * (brandmark images, `.navgroup` headings, `.navitem` links) needs
     * to be a direct flex item of `.nav`'s own row layout the same way
     * it always was before this wrapper existed. `display: contents`
     * removes `.navscroll` from the box tree entirely — no box, no
     * `overflow-y`, no width or height of its own — while leaving its
     * children exactly where `.nav`'s flex row expects to find them.
     */
    .nav .navscroll { display: contents; }
  }

  /*
    Its own media query, deliberately not folded into the shell's own
    one just above — that one is sliced by a test counting closing
    braces from its own start (decision 0281's own test, "undoes the
    sticky sidebar..."), and any rule landing inside that same block
    ahead of `.nav` would push what it's actually checking out of the
    window it slices. Two blocks sharing the same breakpoint is
    ordinary CSS; keeping each near what it belongs to, and after the
    block a fragile test already depends on rather than inside it, is
    worth more than merging them.
  */
  @media (max-width: 1100px) {
    /* `#viewer` again — the bare `.columns { grid-template-columns:
       minmax(0, 1fr); }` a few lines up already collapses the
       Sources screen to one column at this width too, and needs no
       change; this only needs to win the areas as well, for the
       viewer specifically, which the ID does regardless of order. */
    #viewer .columns {
      grid-template-columns: minmax(0, 1fr);
      grid-template-areas:
        "process"
        "document"
        "parties"
        "header"
        "lines"
        "note";
    }
    /* Back to a plain stack at this width, so the Document card gets
       its own honest height again rather than "as tall as three rows
       stacked above it," which is a fine rule on a wide screen and a
       strange one once everything is single-column already. */
    .c-document > .panel:not(.exceptions) { height: auto; }
    .c-document .vpreview { height: calc(100vh - 300px); }
    /* Same reasoning as `.vpreview` just above, for the Timeline / Chat
       pane — decision 0504. Without this, `.vtimeline`'s own wide-screen
       `height: 100%` would resolve against the now-`auto` panel above it,
       which is exactly nothing to be 100% of. */
    .c-document .vtimeline { height: calc(100vh - 300px); }
    /**
     * **`position: static` again — decision 0391.** The wide layout's
     * `position: absolute` (below) only works because "document"
     * shares its three rows with process/parties/header, which have
     * real content of their own to size those rows by even once
     * "document" stops contributing. At this width "document" is back
     * to occupying a row of its own, with nothing else in it — taking
     * it out of flow here left that row with nothing to size itself
     * by at all, measured collapsing to 0 before this reset was added.
     */
    .c-document { position: static; }
    /**
     * **The Seller/Buyer/Header reflow does not survive down here —
     * decision 0392.** `#viewer .columns.docpoppedout` (above, outside
     * any media query) has higher specificity than the plain `#viewer
     * .columns` rule just above in this same block (two classes beat
     * one), so without this it would keep winning even at this width
     * — squeezed the same three-across row into whatever this
     * viewport has, rather than the single honest column every other
     * state here already falls back to. Watched happen before adding
     * this: Process narrowed to hold a badge beside it that this
     * width has no real room for, and Header split into two
     * over-narrow field columns rather than staying with Seller and
     * Buyer's own single-column stack. This simply repeats the plain
     * narrow layout, popped out or not — the badge's own shrunk CSS
     * above still applies (it is not scoped to any width), only the
     * three-column reflow around it does not.
     */
    #viewer .columns.docpoppedout {
      grid-template-areas:
        "process"
        "document"
        "parties"
        "header"
        "lines"
        "note";
      grid-template-columns: minmax(0, 1fr);
    }
    #viewer .columns.docpoppedout .c-document { position: static; }
  }

  /*
    The document panel's two tabs — decision 0269, replacing decision
    0267's drawer. The document preview and the activity feed share
    the same panel the preview has always occupied, switched between
    with tabs rather than the feed opening in a strip below it.

    **Restyled as a segmented pill, decision 0398** — the third of the
    four pieces from e-invoicingcompliancecorner.com, the shape of
    that site's own "Arrivals board / List view" toggle. Built as a
    separate component from `.tabbar`/`.tab` (decision 0333, the
    Access screen's own Organizations/Roles/Teams/People switcher),
    reasoning that a row of *sections* wanted underline-only where
    Document/XML/Timeline & Chat, views of one thing, wanted the
    reference site's own toggle instead. **That separation lasted one
    decision**: 0399 asked for the Access screen's own tabs to match
    this pill too, so `.tabbar`/`.tab` now shares this exact shape
    (and this same `--tab-active-shadow` token) under its own class
    names — see 0399 and `SUPERSEDED.md` for why the reasoning above
    no longer holds, kept here rather than deleted since it explains
    why the two were ever built apart in the first place. Same class
    names throughout (`.doctabs`, `.doctab`, `.doctab.on`,
    `.activitycount`) — no JS file changed, this is styling only.
  */
  .doctabs {
    display: flex;
    align-items: center;
    gap: 2px;
    background: var(--surface-1);
    border: 0.5px solid var(--border);
    border-radius: 999px;
    padding: 3px;
  }
  .doctab {
    background: none; border: 0.5px solid transparent; border-radius: 999px;
    padding: 6px 14px; color: var(--text-muted);
    font-size: var(--text-sm); cursor: pointer; display: flex; align-items: center; gap: 6px;
  }
  .doctab.on {
    background: var(--surface-2); border-color: var(--border-strong);
    box-shadow: var(--tab-active-shadow); color: var(--text-primary); font-weight: 600;
  }
  .activitycount {
    background: var(--bg-accent); border-radius: 999px; font-size: var(--text-sm);
    padding: 1px 7px; color: var(--text-accent); font-weight: 600;
  }
  .activitytabcontent { display: flex; flex-direction: column; }
  /*
    **`hidden` alone is not enough here** — `display: flex` above wins
    over the browser's own default `[hidden] { display: none }` once
    any other rule sets `display` on the same element, so the pane
    stayed visible under the document even while its `hidden` property
    was correctly set. Reported live: "the timeline appears under the
    document image, and also within the Timeline / Chat tab."
  */
  .activitytabcontent[hidden] { display: none; }
  /**
   * **`flex: 1`, not a fixed `max-height: 300px` — decision 0504.** A
   * flat 300px read fine on its own but had nothing to do with how
   * tall the card around it actually was — too short to use the space
   * a wide window's Timeline / Chat card really has, and, paired with
   * `.activitytabcontent` previously having no real height of its own
   * either, no floor stopping the reply box below it from drifting off
   * the bottom of a short card. Now that `.activitytabcontent` is
   * itself given a real height by its own parent (`.vtimeline`/
   * `.docwindowtimeline`, both `flex: 1; min-height: 0`), the feed can
   * take exactly what is left after the reply box's own natural
   * height, and `.activityinput` (below) keeps a fixed spot at the
   * bottom of the card regardless of how many entries are above it —
   * the operator's own request, verbatim.
   */
  .activityfeed {
    flex: 1 1 auto; min-height: 0; overflow-y: auto; margin-bottom: 8px;
    display: flex; flex-direction: column; gap: 10px;
  }
  .activitysysline {
    display: flex; align-items: flex-start; gap: 8px;
    font-size: var(--text-sm); color: var(--text-muted); padding: 2px 0;
  }
  .activitydot {
    width: 5px; height: 5px; border-radius: 999px; background: var(--text-muted);
    flex: 0 0 auto; margin-top: 6px;
  }
  .activitymsg { color: var(--text-secondary); flex: 1 1 auto; }
  .activitywhen { white-space: nowrap; flex: 0 0 auto; color: var(--text-muted); font-size: var(--text-sm); }
  /*
    Decision 0488 — a button/action taken, using the real action icon
    (`icons.js`'s own `claim`/`release`/`return`/`return_to_supplier`/
    `discard` shapes) in place of `.activitydot`'s generic dot, plus an
    optional comment line underneath the message-and-timestamp row.
  */
  .activityaction { align-items: flex-start; }
  .activityactionicon {
    width: 15px; height: 15px; flex: 0 0 auto; margin-top: 2px; color: var(--text-muted);
  }
  .activityactionicon svg { width: 100%; height: 100%; }
  .activityactionbody { flex: 1 1 auto; min-width: 0; }
  .activitymsgrow { display: flex; gap: 8px; }
  .activitymsgrow .activitymsg { flex: 1 1 auto; }
  .activityactioncomment {
    color: var(--text-secondary); font-size: var(--text-sm); margin-top: 2px; line-height: 1.4;
  }
  .activitycomment { display: flex; gap: 9px; }
  .activityavatar {
    width: 26px; height: 26px; border-radius: 999px; background: var(--bg-accent);
    color: var(--text-accent); display: flex; align-items: center; justify-content: center;
    font-size: var(--text-sm); font-weight: 600; flex: 0 0 auto;
  }
  .activitybubble { background: var(--surface-1); border-radius: 10px; padding: 8px 11px; flex: 1 1 auto; min-width: 0; }
  .activitywho { font-size: var(--text-sm); font-weight: 600; display: flex; justify-content: space-between; gap: 8px; }
  .activitybody { font-size: var(--text-sm); color: var(--text-primary); margin-top: 2px; line-height: 1.45; }
  /* `flex: 0 0 auto` — decision 0504: pinned to its own natural height,
     never the thing that shrinks. `.activityfeed`'s own `min-height: 0`
     (above) is what absorbs the squeeze when the card is short, so this
     never has to give up any of its own room to stay on screen. */
  .activityinput { display: flex; gap: 8px; padding: 10px 14px; border-top: 0.5px solid var(--border); flex: 0 0 auto; }
  .activityinput textarea {
    flex: 1 1 auto; resize: none; min-height: 38px; max-height: 100px;
    border: 0.5px solid var(--border); border-radius: 8px; background: var(--surface-0, var(--surface-2));
    color: var(--text-primary); font: inherit; font-size: var(--text-sm); padding: 8px 10px;
  }
  .activityinput textarea:focus { outline: none; border-color: var(--text-accent); }
  .activitypost {
    border: 0.5px solid var(--border-strong); border-radius: 8px; background: var(--surface-1);
    color: var(--text-primary); font-size: var(--text-sm); padding: 8px 14px; cursor: pointer; flex: 0 0 auto;
    display: flex; align-items: center; gap: 6px;
  }
  .activitypost svg { width: 16px; height: 16px; }
  .activitypost:hover { border-color: var(--text-accent); }

  /*
    "Add person to conversation" — decision 0470. `collaborators.js`'s
    own roster-and-search bar, sitting above `.activityfeed` the same
    way the unreadable-document banner already does (`viewer.js`'s own
    `timelinePane` wrapper) — a standing row about who is here, not a
    timestamped event mixed into the feed below it.
  */
  .collabbar {
    display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
    padding: 10px 14px; border-bottom: 0.5px solid var(--border);
  }
  .collabchips { display: flex; flex-wrap: wrap; gap: 6px; flex: 1 1 auto; }
  .collabchip {
    display: flex; align-items: center; gap: 6px;
    background: var(--surface-1); border-radius: 999px; padding: 3px 10px 3px 3px;
    font-size: var(--text-sm); color: var(--text-primary);
  }
  .collabchip .activityavatar { width: 20px; height: 20px; font-size: 10px; }
  .collabchipremove {
    display: flex; align-items: center; justify-content: center;
    width: 16px; height: 16px; padding: 0; margin-left: 2px;
    background: none; border: 0; border-radius: 999px; color: var(--text-muted); cursor: pointer;
  }
  .collabchipremove:hover { background: var(--surface-2); color: var(--text-primary); }
  .collabchipremove svg { width: 10px; height: 10px; }
  .collabaddbtn {
    background: none; border: 0.5px solid var(--border); border-radius: 999px;
    padding: 5px 12px; color: var(--text-muted); font-size: var(--text-sm); cursor: pointer;
    display: flex; align-items: center; gap: 6px; flex: 0 0 auto;
  }
  .collabaddbtn:hover { border-color: var(--text-accent); color: var(--text-primary); }
  .collabaddbtn svg { width: 14px; height: 14px; }
  .collabsearch { flex: 1 1 100%; position: relative; }
  .collabsearch input {
    width: 100%; box-sizing: border-box; border: 0.5px solid var(--border); border-radius: 8px;
    background: var(--surface-0, var(--surface-2)); color: var(--text-primary);
    font: inherit; font-size: var(--text-sm); padding: 7px 10px;
  }
  .collabsearch input:focus { outline: none; border-color: var(--text-accent); }
  .collabsearchresults {
    margin-top: 4px; border: 0.5px solid var(--border); border-radius: 8px;
    background: var(--surface-1); max-height: 180px; overflow-y: auto;
  }
  .collabsearchresult {
    display: block; width: 100%; box-sizing: border-box; text-align: left;
    background: none; border: 0; border-bottom: 0.5px solid var(--border);
    padding: 7px 10px; font: inherit; font-size: var(--text-sm); color: var(--text-primary); cursor: pointer;
  }
  .collabsearchresult:last-child { border-bottom: 0; }
  .collabsearchresult:hover { background: var(--surface-2); }
  .collabsearchresult .muted { font-size: var(--text-sm); }

  #shell { width: auto; padding: 0; }

  #shell header {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-bottom: 26px;
  }

  #shell header .mark {
    width: 34px;
    height: 3px;
    margin: 0;
    flex: none;
  }

  #shell h1 { font-size: var(--text-xl); margin: 0; }
  #shell .sub { margin: 2px 0 0; font-size: var(--text-base); }
  #shell .signout { margin-left: auto; min-height: 30px; padding: 0 12px; }

  .filters { display: flex; gap: 10px; margin-bottom: 14px; }
  .filters select { width: auto; min-width: 150px; }
  /* Decision 1001: Tasks' Overdue filter, and the mark on an overdue row. */
  .filters .filtercheck { display: inline-flex; align-items: center; gap: 6px; font-size: var(--text-sm); color: var(--text-secondary); white-space: nowrap; }
  .filters .filtercheck input { width: auto; }
  .overduepill { margin-left: 6px; font-size: var(--text-sm); }
  /* Inside the search row since decision 0524: the row sets the spacing. */
  .searchrow .filters { margin-bottom: 0; }
  /*
    Decision 0789: Documents' bar laid out as the supplier network's (0734): the search, then the
    filters beside it, then rows, pages and columns on the right.
  */
  .docsearchrow input[type="search"] { flex: 0 1 460px; min-width: 200px; }
  .docsearchrow .filters select { min-width: 140px; }
  .docsearchrow .listcontrols { display: flex; align-items: center; gap: 8px; margin-left: auto; flex: none; }
  .docsearchrow .listcontrols .columnpicker { margin-left: 4px; }
  .docsearchrow .listcontrols .iconbutton svg { width: 16px; height: 16px; }
  @media (max-width: 1100px) { .docsearchrow { flex-wrap: wrap; } }

  .counts {
    margin: 0 0 12px;
    font-size: var(--text-base);
    color: var(--text-secondary);
  }

  #shell table { width: 100%; font-size: var(--text-base); }

  #shell th {
    text-align: left;
    font-weight: 500;
    font-size: var(--text-sm);
    color: var(--text-secondary);
    padding: 0 10px 8px;
    border-bottom: 0.5px solid var(--border);
  }

  #shell td {
    padding: 10px;
    border-bottom: 0.5px solid var(--border);
    vertical-align: middle;
  }

  /* Numbers align right and share a width, so amounts compare by eye. */
  #shell .num { text-align: right; font-variant-numeric: tabular-nums; }

  /* Mine leads. A row somebody else holds recedes without disappearing. */
  #shell tr.mine td:first-child { box-shadow: inset 2px 0 0 var(--brand-fill); }
  #shell tr.locked { color: var(--text-secondary); }

  .muted { color: var(--text-muted); }

  button.act {
    min-height: 26px;
    padding: 0 10px;
    margin-right: 6px;
    font-size: var(--text-sm);
  }

  button.act:disabled { opacity: 0.4; cursor: default; }

  /* The Validation viewer — decision 0106. */
  #viewer { width: auto; padding: 0; }

  /* The line table — decision 0109. */
  .linetable { width: 100%; table-layout: fixed; margin-bottom: 10px; }
  /*
    Matches the Tasks/Documents column-header style — decision 0404. A
    plain `<th>` renders bold by default, and inherits whatever text
    color is around it; nothing here ever set either, so it fell back to
    browser-default bold and `.panel`'s own `--text-primary` — a colour
    that reads as near-white at Night (`#e8eef7`), which is what the
    operator saw. `#shell th` is the rule actually governing the Tasks
    and Documents tables (an id selector, so it wins there over
    `.tablewrap th`'s own, lower-specificity, never-actually-applied
    weight/colour) — copied here rather than invented.
  */
  .linetable th { padding: 0 6px 6px; font-weight: 500; font-size: var(--text-sm); color: var(--text-secondary); }
  .linetable td { padding: 3px 6px; border: none; }
  .linetable input { min-height: 30px; font-size: var(--text-base); }

  /*
    A read-only field is text, not a disabled input (decision 0114). A
    greyed-out box invites clicking and reads as broken; plain text says
    the value is information rather than something to change.

    It still gets the same rounded corners as an editable field — decision
    0402's UI follow-up: a locked Invoice Header field and an editable
    Invoice Line field were rendering with a visibly different shape (no
    box at all vs. a bordered, rounded one), which read as a rendering
    bug rather than the intended editable/locked distinction. `--surface-0`
    (a step more muted than an input's own `--surface-1`) and no border
    keep it reading as a flatter, non-interactive fill rather than
    something to click into, while still sharing `var(--radius)` so the
    two field types share one visual language.
  */
  /*
    **No `box-sizing: border-box` here, unlike `tokens.css`'s own rule
    for `input`/`textarea` — decision 0462's own finding.** A `.readonly`
    div left at the browser's default `content-box` renders any
    explicit `width` you give it plus this padding on top, unlike a
    real `<input>` at the same width. Invisible everywhere today,
    since nowhere else puts an explicit percentage or `100%` width on
    one — `.popout.codingpopout .codingcompanycode` is the one place
    that does, and states its own `box-sizing: border-box` for exactly
    this reason. If a future screen ever gives `.readonly` an explicit
    width too, it will hit the identical gap.
  */
  .readonly {
    min-height: 32px;
    padding: 8px 11px;
    font-size: var(--text-base);
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
    background: var(--surface-0);
    border-radius: var(--radius);
  }

  .linetable .readonly { padding: 3px 6px; min-height: 30px; }
  .linetable th:first-child, .linetable td:first-child { width: 30px; }
  /*
    **Widened from 34px to 62px — decision 0453.** This column held
    only the "×" remove button; it now also holds the new Coding icon
    button beside it, always shown (see `lineRow`'s own comment on
    why), so the column needs room for two 28px buttons rather than
    one. **Back to one button — decision 0537**: Coding has its own
    column now.
  */
  .linetable th:last-child, .linetable td:last-child { width: 36px; }
  /* BT-130 is a three-character UN/ECE code, so its column is narrow. */
  .linetable td.short { width: 70px; }
  /*
    Decision 0679 — widths by field, not by position: the columns now
    follow a set order, so "the fourth column" no longer meant the unit.
    Quantity is narrow; Unit and VAT category are wide enough for their
    pickers ("EA · Each", "S · Standard rate"); Item name and
    Description share what is left.
  */
  .linetable th.lf-bt-129 { width: 6em; }
  .linetable th.lf-bt-130 { width: 9em; }
  .linetable th.lf-bt-151 { width: 12.5em; }
  .linetable th.lf-bt-152 { width: 6em; }

  /* Decision 0537 — Coding has its own column; the × keeps this one. */
  .lineactions { text-align: right; white-space: nowrap; }
  .linetable th.codingcol, .linetable td.codingcol { width: 64px; white-space: nowrap; text-align: center; }
  .codingcol .codingbtn.locked { opacity: 0.55; }
  .codinglocked { margin: 0 0 12px; padding: 8px 10px; border-radius: var(--radius); background: var(--surface-1); border: 0.5px solid var(--border); color: var(--text-secondary); font-size: var(--text-sm); }

  button.rm {
    min-height: 28px;
    width: 28px;
    padding: 0;
    color: var(--text-muted);
    border-color: transparent;
  }
  /* The new Coding icon button — decision 0453, the "×" glyph's own
     sibling in `.rm` styling but an SVG rather than text. */
  button.rm svg { width: 16px; height: 16px; }

  .linefoot { display: flex; align-items: center; gap: 12px; }

  /*
    Advisory, never blocking (decision 0109). An invoice whose lines do
    not sum to its printed total is a fact to record faithfully, not an
    input to prevent — so this states what it found and nothing stops
    saving.
  */
  .linetotal {
    margin-left: auto;
    font-size: var(--text-base);
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
  }

  .linetotal.off { color: var(--text-warning); }

  /*
    The validation panel — decision 0119.

    **Hidden for now, not removed** — the operator's own words, asked
    while reworking the space around the Document card: "can we hide
    the Exceptions card, but without removing the code, just the
    visibility." `exceptionPanel()` still builds it and `renderExceptions()`
    still fills `#exlist` on every load; nothing downstream of either
    knows this rule exists. Deleting this one line is the whole way
    back.
  */
  .exceptions { display: none; }

  /*
    **Fixed height and scrolling.** A document with fourteen exceptions
    must not push the fields it is complaining about off the screen: the
    panel is a companion to the form, not a thing that displaces it.
  */
  .exceptions .exlist {
    max-height: 190px;
    overflow-y: auto;
    margin: -4px -6px;
    padding: 4px 6px;
  }

  .exrow {
    padding: 8px 0;
    border-bottom: 0.5px solid var(--border);
    font-size: var(--text-base);
  }

  .exrow:last-child { border-bottom: none; }
  .extext { color: var(--severity-warning-text); }
  .exwhere { color: var(--text-muted); font-size: var(--text-sm); margin-top: 2px; }
  .exrow.muted .extext { color: var(--text-muted); }
  /* decision 0400 — a purchase-order mismatch reads as the stop tier
     here too, matching the field it highlights. */
  .exrow.danger .extext { color: var(--severity-danger-text); }

  /*
    **Red, amber and green on the key fields — decision 0400**, the
    fourth and last of the pieces from e-invoicingcompliancecorner.com
    (decision 0395). `.kf.warning` replaces `.kf.failing` one for one
    — same job, renamed because there are now three tiers rather than
    one: `.kf.danger` for a field that disagrees with a linked
    purchase order (decision 0400's own `po_mismatch` check, the first
    thing to actually consume `--bg-danger`/`--text-danger` since they
    shipped unused at 0395), `.kf.warning` for any of the other checks
    failing exactly as before, and `.kf.ok` for a field a check ran
    against and found to agree — new, since nothing before this
    confirmed a field rather than merely not flagging it (validation.ts's
    own `confirms`, alongside `involves`).

    **A dot beside the label, not just the field's own tint** — the
    mock-up's own small coloured markers, one small enough to sit next
    to a line-table header too, where a tinted cell would compete with
    a numeric total. Reuses `--border-danger`/`--border-warning`/
    `--border-success` directly, exactly as decision 0395's own
    write-up anticipated before this piece existed: "those can reuse
    [them] directly when that piece is built."

    A field still carries its reason as a tooltip either way, so
    somebody does not have to hunt through a hidden panel to find which
    of fourteen exceptions is theirs.
  */
  .kf-dot {
    display: inline-block;
    width: 7px;
    height: 7px;
    border-radius: 999px;
    margin-right: 6px;
    vertical-align: middle;
    flex: none;
  }
  .kf.danger .kf-dot { background: var(--border-danger); }
  .kf.warning .kf-dot { background: var(--border-warning); }
  .kf.ok .kf-dot { background: var(--border-success); }

  .kf.danger input,
  .kf.danger select,
  .kf.danger .readonly,
  .linetable td.danger input {
    border-color: var(--border-danger);
    background: var(--severity-danger-bg);
    color: var(--severity-value-text);
  }
  .kf.danger label { color: var(--severity-danger-text); }
  /*
    **A line's Coding button, when its coding is not on Account
    Coding's own lists** — decision 0511. The coding fields live in the
    pop-out, so the button that opens it is what carries the mark.
  */
  .codingcol .codingbtn.warning { color: var(--text-warning); }
  .codingcol .codingbtn.danger {
    color: var(--severity-danger-text);
    background: var(--severity-danger-bg);
    border-color: var(--border-danger);
  }
  /*
    **Coded — decision 0541.** The operator: "change the icon on the
    invoice line such that the icon has a bold green outline and a
    green fill". The tag's own outline drawn heavier in green, its body
    filled with the same pale green as a set field card, the same in
    both moods. Its tick stays unfilled.
  */
  .codingcol .codingbtn.coded { color: var(--border-success); }
  .codingcol .codingbtn.coded svg { stroke-width: 2.4; }
  .codingcol .codingbtn.coded svg path:first-child { fill: var(--severity-ok-bg); }

  .kf.warning input,
  .kf.warning select,
  .kf.warning .readonly,
  .linetable td.warning input {
    border-color: var(--border-warning);
    background: var(--severity-warning-bg);
    color: var(--severity-value-text);
  }
  .kf.warning label { color: var(--severity-warning-text); }

  .kf.ok input,
  .kf.ok select,
  .kf.ok .readonly,
  .linetable td.ok input {
    border-color: var(--border-success);
    background: var(--severity-ok-bg);
    color: var(--severity-value-text);
  }
  .kf.ok label { color: var(--severity-ok-text); }

  /*
    The document itself, in the panel — decision 0123.

    Fixed height so the panel does not grow with the document; the
    browser's own viewer scrolls and zooms inside it.
  */
  .vpreview {
    /**
     * **Reaches toward the bottom of the viewport** — decision 0271.
     * Reported live, once the unreadable-document note moved out of
     * this column and into the Timeline / Chat tab: "the Document
     * image should be aligned with the base of the bottom of the
     * screen."
     *
     * `calc(100vh - 300px)` is a real estimate of everything else
     * sharing this column's own vertical space above and below the
     * image — the topbar, the tabs, this panel's own padding, and the
     * action row beneath it — not a precise measurement taken from a
     * live page. `min-height` is the floor this box had before this
     * change, so a very short window or a page with more chrome above
     * it never gets an image smaller than the original fixed size.
     *
     * **The action row it counted is gone** — decision 0298 moved
     * Save and the task's actions to the topbar and Expand beside the
     * tabs, and this estimate was not revisited, so it now likely
     * reserves more than the page needs. Noted rather than changed in
     * decision 0380: the value is a visual judgement for somebody
     * looking at a live page, not one to guess at from here.
     */
    height: calc(100vh - 300px);
    min-height: 320px;
    border: 0.5px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--surface-1);
  }

  /**
   * `.vframe` is still live — the XML tab's own `<iframe>` (decision
   * 0380/0382). `.vimage` styled the Document tab's `<img>` from
   * decision 0123's original split; decision 0382 (phase 2 of
   * docs/design/document-viewer.md) replaced that element with
   * page-renderer.js's canvas, and nothing has created anything of
   * this class since — removed rather than left to describe a tag
   * nothing draws (decision 0385, phase 5 of the same design
   * document).
   */
  .vframe { width: 100%; height: 100%; border: none; display: block; }

  /*
    The page renderer — decision 0382, phase 2 of
    docs/design/document-viewer.md. Fills `.vpreview` exactly like the
    `<img>`/`<iframe>` it replaced; everything below lays out inside
    that fixed-height box.
  */
  .vpagesroot { display: flex; height: 100%; }
  /* Decision 0747: "no document retained" fills the row, so it sits in the
     middle of the document panel rather than shrinking to its words on the left. */
  .vpagesroot > .vthumb { flex: 1; }

  /* A vertical rail, per the operator's own request — thumbnails in a
     column beside the page, not a strip above it. */
  .vrail {
    width: 92px;
    flex-shrink: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 8px;
    border-right: 0.5px solid var(--border);
    background: var(--surface-1);
  }
  /**
   * **Docked, the rail stays hidden even for a multi-page document —
   * decision 0390**, the operator's own words: "the document image
   * thumbnail can be hidden when the card is docked in the validation
   * page. Should the user wish to view thumbnails then they can Expand
   * the card into a separate window." `pageViewer()`'s own
   * `rail.hidden` logic (page-renderer.js) is untouched and still runs
   * here — a single-page document still gets `hidden` set `true` the
   * same way it always has — this rule only adds a second, independent
   * reason the rail does not show, scoped to `#viewer` so the pop-out
   * window (`#docwindow-root`, decision 0384) is unaffected and still
   * shows it exactly as before, `.hidden` alone deciding it there.
   */
  #viewer .vrail { display: none; }

  .vrailthumb {
    width: 76px;
    height: 76px;
    padding: 0;
    display: grid;
    place-items: center;
    border: 1.5px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-0);
    overflow: hidden;
  }
  .vrailthumb.on { border-color: var(--text-accent); }
  .vrailthumb:hover { border-color: var(--text-secondary); }
  .vrailcanvas { max-width: 100%; max-height: 100%; }

  .vmain { flex: 1; min-width: 0; display: flex; flex-direction: column; }

  .vcontrols {
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 4px 8px;
    border-bottom: 0.5px solid var(--border);
    flex-shrink: 0;
  }
  .vpagestatus { margin-left: auto; font-size: var(--text-sm); color: var(--text-secondary); }

  .vcanvasholder {
    position: relative;
    flex: 1;
    overflow: auto;
    display: grid;
    place-items: center;
    background: var(--surface-0);
  }
  .vcanvas { max-width: 100%; }
  /**
   * **Lifted once zoomed in past the default step** — decision 0392.
   * `page-renderer.js`'s `draw()` toggles this the moment `zoomIndex`
   * passes `DEFAULT_ZOOM_INDEX`, not on every zoom level: below and at
   * the default, `max-width: 100%` above is what makes the initial
   * view fit the card (most scanned invoices are already wider than
   * it), and removing it unconditionally would have made *every*
   * document open already overflowing. Past that step, the same
   * clamp only ever hid the zoom a person had just asked for —
   * drawing at a higher resolution behind the same fixed box — so it
   * lifts, and `.vcanvasholder`'s own `overflow: auto` above (decision
   * 0382) is what turns the now-larger canvas into something
   * scrollable rather than something that overflows silently.
   */
  .vcanvas.zoomedin { max-width: none; }
  /**
   * **A grab cursor only while there is somewhere to drag to** —
   * `pannable` is the same condition as `.vcanvas.zoomedin` above,
   * toggled by the same line in `draw()`. `dragging` is set for the
   * duration of one pointer-down-to-up gesture.
   */
  .vcanvasholder.pannable { cursor: grab; }
  .vcanvasholder.pannable.dragging { cursor: grabbing; }

  /**
   * The highlight tool — decision 0394. `.vhighlightlayer` is
   * repositioned in script (`renderHighlights()`, page-renderer.js) to
   * sit exactly over the canvas's own rendered box, on `.vcanvasholder`
   * itself becoming `position: relative` above so that positioning is
   * relative to this box and not some further ancestor. `pointer-events:
   * none` on the layer keeps drags on the crosshair cursor reaching the
   * canvas underneath rather than the (otherwise invisible) overlay
   * `<div>` sitting on top of it.
   */
  .vhighlightlayer { position: absolute; pointer-events: none; }
  .vcanvasholder.highlighting { cursor: crosshair; }
  .vhighlight {
    position: absolute;
    background: rgba(255, 213, 79, 0.35);
    border: 1.5px solid rgba(230, 160, 0, 0.85);
    border-radius: 2px;
  }
  .vhighlightdraft { background: rgba(255, 213, 79, 0.22); border-style: dashed; }

  /**
   * Where a field's value is, and the lasso — decision 0697. Blue, not the
   * highlight tool's yellow: one is the system pointing, the other is the
   * person's own mark. The best place is solid; another place the same
   * value appears is dashed.
   */
  .vcanvasholder.lassoing { cursor: crosshair; }
  .vlocate {
    position: absolute;
    border: 1.5px dashed rgba(37, 99, 235, 0.7);
    border-radius: 3px;
  }
  .vlocate.best { border-style: solid; border-width: 2px; background: rgba(37, 99, 235, 0.14); box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.18); }
  .vlassoed { position: absolute; border: 2px solid rgba(22, 163, 74, 0.85); background: rgba(22, 163, 74, 0.14); border-radius: 3px; }
  .vlassopath { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
  .vlassopath polygon { fill: rgba(37, 99, 235, 0.08); stroke: rgba(37, 99, 235, 0.9); stroke-width: 1.5px; stroke-dasharray: 4 3; }
  .vhint { margin-left: auto; font-size: var(--text-sm); color: var(--text-secondary); }
  .vhint + .vpagestatus { margin-left: 12px; }
  .vhint:empty + .vpagestatus { margin-left: auto; }
  /* The AI's suggested field, offered on the toolbar — decision 0699. */
  /* The supplier portal on a supplier's page — decision 0715. */
  .supplierportal { margin-top: 14px; }
  .portalperson { display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline; padding: 4px 0; border-bottom: 0.5px solid var(--border); }
  .portalinvite { display: grid; gap: 8px; margin-top: 10px; max-width: 420px; }
  .portalcompanies { display: flex; flex-wrap: wrap; gap: 4px 14px; }
  .portalcompany { display: flex; align-items: center; gap: 6px; font-size: var(--text-sm); }
  .portalcompany input { width: auto; }
  .portalinvite .actionlink { justify-self: start; }
  /*
    Decision 0719 — the supplier pop-out: the record on the left, the
    supplier portal on the right, once the portal has something to show.
  */
  .popout.supplierpop.supplierpop-wide { width: min(1180px, 96vw); max-width: none; }
  .supplierpop-wide .suppliercols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 0 32px; align-items: start; }
  .supplierpop-wide .supplierside { border-left: 0.5px solid var(--border); padding-left: 28px; }
  .supplierpop-wide .supplierside .supplierportal { margin-top: 0; }
  .supplierpop-wide .supplierside .supplierportal h4 { margin-top: 0; }
  /* Decision 0776: Save's question, as an alert box over the supplier's pop-out. */
  .backdrop.supplieraskback { z-index: 60; }
  .popout.supplierask { width: min(480px, 92vw); }
  .popout.supplierask h3 { margin: 0 0 10px; }
  .supplieraskchanges { margin: 0 0 10px; padding-left: 18px; display: grid; gap: 4px; font-size: var(--text-sm); }
  .supplieraskbuttons { display: flex; justify-content: flex-end; gap: 10px; margin-top: 14px; }
  /* Decision 0795: Configuration › Environment. */
  .envgrid { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: 16px; align-items: start; }
  .envgrid > .panel { margin-bottom: 0; }
  .envareas { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 22px; margin-bottom: 14px; }
  .envarea { display: flex; align-items: center; gap: 8px; font-size: var(--text-sm); }
  /* Decision 0803: the learned layouts' own option. */
  .envsubarea { grid-column: 1 / -1; }
  /* Decision 0804: reference data, copied once. */
  .envreference { margin-top: 16px; padding-top: 12px; border-top: 0.5px solid var(--border); }
  .envwarn { color: var(--text-warning); }
  .envarea .envcount { margin-left: auto; text-align: right; }
  .envnotelabel { display: block; margin-bottom: 4px; }
  #env-packagenote { width: 100%; box-sizing: border-box; }
  .envkinds { width: 100%; font-size: var(--text-sm); }
  .envkinds td { padding: 8px 6px; border-bottom: 0.5px solid var(--border); vertical-align: top; }
  .envkinds td:last-child { white-space: nowrap; text-align: right; }
  #env-note:empty { display: none; }
  /* Decision 0796: Import, read against this environment. */
  .envtabs { margin-left: 8px; }
  #env-from { margin: -4px 0 12px; }
  #env-file { display: block; margin-bottom: 12px; font-size: var(--text-sm); }
  .envchecks { display: grid; gap: 6px; font-size: var(--text-sm); margin: 4px 0 14px; }
  .envcheck::before { font-weight: 700; margin-right: 6px; }
  .envcheck.ok::before { content: "\2713"; color: var(--text-success); }
  .envcheck.bad::before { content: "\2715"; color: var(--severity-danger-text); }
  .envcheck.warnline::before { content: "!"; color: var(--text-warning); }
  .envsection { margin-top: 18px; }
  .envsectionhead { display: flex; gap: 10px; align-items: baseline; margin-bottom: 6px; }
  /* Decision 0798: show or hide the rules that have not changed. */
  .envheadactions { margin-left: auto; display: inline-flex; gap: 12px; align-items: center; }
  /* Decision 0799: Compile again, the two forms side by side. */
  .envprogress { display: flex; gap: 10px; align-items: center; margin: 0 0 10px; }
  .envbar { flex: 1; height: 6px; border-radius: 3px; background: var(--surface-2); overflow: hidden; display: block; }
  .envbar i { display: block; height: 100%; background: var(--text-accent); }
  .envruleaction { text-align: right; white-space: nowrap; }
  .envtable tr.open td { border-bottom: none; }
  .envtable tr.envdetail td { padding-top: 0; }
  .envcmp { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 4px 0 2px; }
  .envside { border: 0.5px solid var(--border); border-radius: 10px; padding: 10px 12px; background: var(--surface-2); }
  .envside.kept { border-color: var(--border-accent); box-shadow: 0 0 0 1px var(--border-accent) inset; }
  .envsidewho { display: flex; justify-content: space-between; font-size: var(--text-sm); color: var(--text-muted); margin-bottom: 6px; }
  .envsidereads { font-size: var(--text-sm); line-height: 1.5; }
  .envkeep { display: flex; gap: 6px; align-items: center; margin-top: 8px; font-size: var(--text-sm); }
  .envkeep.off { opacity: .45; }
  .envverdict { margin-top: 8px; }
  .envwhy { border-left: 2px solid var(--border-strong); padding-left: 10px; margin: 6px 0 0; }
  @media (max-width: 720px) { .envcmp { grid-template-columns: 1fr; } }
  .envtable { width: 100%; font-size: var(--text-sm); border-collapse: collapse; }
  /* Decision 1025: what an import changes, and to what. */
  .envchanges { margin: 6px 0 0; padding-left: 18px; }
  .envchanges > li { margin: 6px 0; }
  .envchangevalues { margin: 2px 0 0; padding-left: 18px; list-style: circle; }
  .envtable th { text-align: left; color: var(--text-muted); font-weight: 500; padding: 6px 8px; border-bottom: 0.5px solid var(--border); }
  .envtable td { padding: 8px; border-bottom: 0.5px solid var(--border); vertical-align: top; }
  .envtable .envproblem { margin: 0 6px 4px 0; display: inline-block; }
  .envreadonly { margin-top: 14px; }
  /* Decision 0797: choosing each reference, Save as drafts, Activate all, History. */
  .envchoose { width: 100%; max-width: 420px; }
  .envsecret { width: 100%; max-width: 420px; margin-bottom: 4px; }
  .envor { margin-top: 6px; }
  .envchosen { margin: 0 8px 6px 0; display: inline-block; }
  .envretire { display: flex; gap: 8px; align-items: center; margin-top: 12px; }
  .envland { display: flex; gap: 16px; align-items: center; justify-content: space-between; flex-wrap: wrap; margin-top: 18px; padding: 12px 16px; border: 0.5px solid var(--border); border-radius: 10px; background: var(--surface-2); }
  .envland p { margin: 2px 0; }
  .envnotes { margin: 8px 0 0; padding-left: 18px; }
  #env-history .statebuttons { justify-content: flex-end; }
  /* Decision 0802: Waiting to be opened, above the Import card. */
  .envstack { display: grid; gap: 16px; }
  @media (max-width: 1100px) { .envgrid { grid-template-columns: minmax(0, 1fr); } .envareas { grid-template-columns: minmax(0, 1fr); } }
  /* Decision 0793: Undo credit, asked first; the reason box and who undid it. */
  .creditundoask textarea { width: 100%; box-sizing: border-box; margin-top: 6px; font: inherit; font-size: var(--text-sm); }
  .creditundoask .warn:empty { display: none; }
  .creditundo, .creditundone b { color: var(--text-warning); }
  .creditundone { margin-top: 4px; }
  .creditpanel .reasonline-row .compactlink, .creditpanel .creditundorefused { margin-left: auto; }
  .supplierpop-wide .portalinvite, .supplierpop-wide .portaledit { max-width: none; }
  .supplierpop-wide .portalcompanies-many { max-height: 236px; }
  @media (max-width: 900px) {
    .supplierpop-wide .suppliercols { grid-template-columns: minmax(0, 1fr); }
    .supplierpop-wide .supplierside { border-left: 0; padding-left: 0; border-top: 0.5px solid var(--border); padding-top: 14px; margin-top: 14px; }
  }
  /* Each linked or invited person as a row: who and for what on the left, actions on the right. */
  .supplierportal .portalperson { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; column-gap: 16px; row-gap: 0; align-items: center; padding: 8px 0; }
  .supplierportal .portalperson > span:first-child { grid-column: 1; grid-row: 1; font-weight: 700; overflow-wrap: anywhere; }
  .supplierportal .portalperson > span.muted { grid-column: 1; grid-row: 2; }
  .supplierportal .portalperson > button { grid-row: 1 / span 2; white-space: nowrap; }
  /* Decision 0720 — small icon-and-word actions; Invite beside the email address. */
  .portalinviterow { display: flex; gap: 10px; align-items: center; }
  .portalinviterow .portalemail { flex: 1 1 auto; min-width: 0; }
  .portalinviterow .actionlink { flex: 0 0 auto; }
  .supplierportal .portalperson > .actionlink { grid-row: 1 / span 2; white-space: nowrap; }
  .portaleditbuttons { gap: 6px; }
  .portalinvitehead { margin: 18px 0 6px; font-size: var(--text-sm); color: var(--text-secondary); font-weight: 700; }
  /* Decision 0739: goods receipts first under the heading; the people invited last, set off from the invitation above. */
  .supplierportal .receiptsharinghead { margin-top: 4px; }
  .supplierportal .portalpeoplelist { margin-top: 14px; border-top: 0.5px solid var(--border); }
  /* Decision 0718 — changing a person's companies in place, and choosing among many. */
  .portalpersonwrap { border-bottom: 0.5px solid var(--border); }
  .portalpersonwrap .portalperson { border-bottom: 0; }
  .portaledit { display: grid; gap: 8px; padding: 6px 0 12px; max-width: 460px; }
  .portaleditbuttons { display: flex; gap: 14px; align-items: center; }
  .portalpicker { display: grid; gap: 6px; }
  .portalpicktools { display: flex; flex-wrap: wrap; gap: 6px 12px; align-items: center; }
  .portalfind { flex: 1 1 180px; font-size: var(--text-sm); }
  .portalcompanies-many { display: grid; gap: 2px; max-height: 190px; overflow-y: auto; padding: 6px 8px; border: 0.5px solid var(--border); border-radius: var(--radius); }
  .portalnote { font-size: var(--text-sm); color: var(--text-success); margin-bottom: 6px; }
  .vsuggest { background: none; border: 0; padding: 0; font: inherit; color: var(--text-accent); text-decoration: underline; cursor: pointer; }
  /* Where this supplier usually puts a field — decision 0702. Amber: a suggestion, not a find. */
  .vexpected { position: absolute; border: 2px dashed rgba(217, 119, 6, 0.9); background: rgba(245, 158, 11, 0.1); border-radius: 3px; }
  /* The line net amount's suggestion — decision 0708. */
  .linecalc { display: flex; gap: 6px; align-items: baseline; flex-wrap: wrap; margin-top: 3px; font-size: var(--text-sm); color: var(--text-secondary); }
  .linecalc:empty { display: none; }
  .linecalcuse { background: none; border: 0; padding: 0; font: inherit; color: var(--text-accent); text-decoration: underline; cursor: pointer; }
  .linecalcwarn .moneyinput { border-color: rgba(217, 119, 6, 0.9); }
  /* A field whose value the lasso just filled — decision 0697. */
  .lassofilled { box-shadow: 0 0 0 2px rgba(22, 163, 74, 0.6); transition: box-shadow 0.6s; }

  /**
   * A toggled tool, not a one-shot action — the same `.on` shape
   * `.doctab` and `.vrailthumb` already give a currently-active choice,
   * reused here rather than inventing a second "this is active" look
   * for a third kind of control.
   */
  .iconbutton.on { background: var(--bg-accent); color: var(--text-accent); }

  .actionlink {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 5px;
    min-width: 62px;
    padding: 9px 6px;
    min-height: 0;
    border-color: transparent;
    background: transparent;
    color: var(--text-secondary);
    font-size: var(--text-sm);
    line-height: 1.2;
    text-align: center;
  }

  .actionlink svg { width: 20px; height: 20px; }
  /*
    **The language toggle's own "icon", decision 0302** — a short code
    standing in for the SVG shape every other `.actionlink` uses,
    since there is no glyph for "this is now in German" the way there
    is a sun for day and a moon for night. Sized and weighted to read
    at the same visual scale as the 20px icons beside it, not simply
    left as plain text.
  */
  .langbadge {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: 0.02em;
    border: 1px solid currentColor;
    border-radius: 4px;
  }
  .actionlink:hover:not(:disabled) { background: var(--surface-2); color: var(--text-primary); }

  /*
    One dominant action, still (decision 0108). Giving every action the
    same weight would lose which one a person is here to press.
  */
  .actionlink.primary { color: var(--text-primary); }
  .actionlink.primary svg { stroke-width: 2; }

  .actionlink:disabled { opacity: 0.4; }

  /**
   * A small, inline icon-only button — decision 0376's own pagination
   * arrows. `.actionlink`'s own icon-above-text stack is built for a
   * cardhead action and is far wider than four arrows sitting in one
   * row alongside a search box need to be.
   */
  .iconbutton {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    min-height: 0;
    min-width: 0;
    border-color: transparent;
    background: transparent;
    color: var(--text-secondary);
  }
  .iconbutton svg { width: 16px; height: 16px; }
  .iconbutton:hover:not(:disabled) { background: var(--surface-2); color: var(--text-primary); }
  .iconbutton:disabled { opacity: 0.4; }

  /* One action per line, in one place — decision 0108. */
  .actions button { display: block; }
  button.wide { width: 100%; margin: 0 0 8px; min-height: 32px; }
  .actions button.primary { margin-bottom: 12px; }

  .vhead {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding-bottom: 16px;
    border-bottom: 0.5px solid var(--border);
  }

  .vtitle { margin: 0; font-size: var(--text-lg); font-weight: 600; }
  .vhead .sub { margin: 2px 0 0; }
  .vhead .close { margin-left: auto; min-height: 30px; padding: 0 12px; }

  /* Document beside fields, so a person reads and types without
     switching windows — the whole point of the screen. */
  .vbody {
    display: grid;
    grid-template-columns: 190px minmax(0, 1fr);
    gap: 20px;
    padding: 22px 0;
  }

  .vthumb {
    /**
     * **Fills `.vpreview` now that box is taller** — decision 0271.
     * Only ever used inside it (the initial placeholder and the "no
     * document retained" fallback), so there is no other context this
     * would need to stay small for.
     */
    height: 100%;
    display: grid;
    place-items: center;
    background: var(--surface-1);
    color: var(--text-muted);
    font-size: var(--text-base);
    text-align: center;
    padding: 16px;
  }

  .vdoc .act { width: 100%; }

  .vfields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 12px;
    align-content: start;
  }

  /*
    The curated Invoice header summary — decision 0291.

    **`auto-fit`, not a fixed 5 columns**, matching every other grid on
    this screen (`.vfields`, `.parties`). The card sits in a column
    already sharing the screen with the document panel, narrower than
    the mock-up's own full-width canvas — a rigid 5-column grid would
    crush every value at anything less than a wide desktop. Wrapping
    to fewer, wider columns on a narrower screen keeps every value
    readable at the cost of a taller card, which is the right trade.
  */
  .headersummary {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
    gap: 16px;
  }
  .hscolumn {
    display: flex;
    flex-direction: column;
    gap: 14px;
  }

  /*
    "Header Fields"'s own pop-out — decision 0294, the operator's own
    request: "list the field in a single row stacked, with field name
    on the left and field value on the right." One column, not
    `.vfields`'s own multi-column grid, and `.kf` itself laid out as a
    row rather than its default label-above-value stack — scoped to
    `.hffields` alone, so every other use of `field()` on this screen
    keeps its own layout.
  */
  .hffields .kf {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 16px;
    padding: 8px 0;
    border-top: 0.5px solid var(--border);
  }
  .hffields .kf:first-child { border-top: none; }
  .hffields .kf label { margin-bottom: 0; flex-shrink: 0; }
  .hffields .kf input,
  .hffields .kf .readonly {
    text-align: right;
    max-width: 60%;
  }
  .hffields .kf .readonly { word-break: break-all; }

  .kf label {
    display: block;
    font-size: var(--text-sm);
    color: var(--text-secondary);
    margin-bottom: 4px;
  }

  /**
   * **Breathing room between stacked fields in a pop-out — decision
   * 0497.** Asked for directly, against Reassign's and Route To
   * Approver's own pickers: "insert a little space between the user
   * selection drop-down, and the title of the comments box." `.kf`
   * itself has no margin of its own (`.newsource`/`.hffields` above
   * give it one only in their own scoped layouts), so two stacked
   * `.kf` fields in a plain `.popout` sat flush against each other.
   * Scoped to `.popout` alone so this does not reach `.newsource`'s
   * own already-spaced flex row or `.hffields`'s own bordered rows.
   */
  .popout .kf + .kf {
    margin-top: 16px;
  }

  /**
   * **A comment box that looks like the rest of the form, and is
   * tall enough to write in — decision 0497.** The global text-input
   * rule above (`input:not([type="checkbox"]):not([type="radio"]),
   * select`) never included `textarea`, so every comment box built
   * from `labeled()` — Reassign's own, Return's mandatory reason, and
   * now Route To Approver's — rendered as a bare, browser-default box:
   * no border radius, no focus ring, and a default height too short
   * to write a real sentence into. Reported live, against exactly
   * this shape: "please can you make the comments box fixed height,
   * and taller than it is now." `resize: none` because "fixed height"
   * was the operator's own word for it, not merely a taller default.
   */
  .kf textarea {
    width: 100%;
    height: 140px;
    resize: none;
    font: inherit;
    font-size: var(--step-1);
    background: var(--surface-1);
    border: 0.5px solid var(--border);
    border-radius: var(--radius);
    padding: 8px 11px;
    color: var(--text-primary);
    transition: border-color 120ms ease;
  }
  .kf textarea:focus {
    outline: none;
    border-color: var(--brand-fill);
  }

  .vfoot {
    display: flex;
    align-items: center;
    gap: 16px;
    padding-top: 16px;
    border-top: 0.5px solid var(--border);
  }

  .vfoot .problem { margin: 0; flex: 1; }

  .vfoot .primary {
    background: var(--brand-fill);
    color: #fff;
    border: none;
    min-height: 34px;
    padding: 0 16px;
    font-weight: 500;
  }
  .since p { margin: 0 0 6px; }
  .since ul { margin: 0; padding-left: 16px; }
  .since li { color: var(--text-warning); }

  /*
    document-window.html — decision 0384, phase 4 of
    docs/design/document-viewer.md.

    Same reset `body.working` already gives the main shell: the bare
    `body`/`main` rules above this comment are the sign-in page's own
    narrow, centred layout, and every page loading this stylesheet
    inherits them by default (nothing scopes them to index.html).
    `#shell`/`#viewer` override the same way, just above.
  */
  /**
   * **`height`, not `min-height` — decision 0505.** `min-height: 100vh`
   * is a floor: it stops the window looking short, but sets no
   * ceiling, so once the page's own content wanted more room than the
   * viewport had, the *page* grew to fit it rather than clipping
   * anywhere. Reported live: "I add several new messages and the
   * height of the page grows and grows... please add a scroll bar in
   * the Timeline / Chat, rather than continue to add and expand the
   * page size." `overflow: hidden` is the other half — `height` alone
   * fixes this box's own size but does not stop a child that still
   * wants more room from spilling out of it; every level below this
   * one already resolves down to a genuine `overflow-y: auto`
   * (`.docwindowsplitright`, and, as of decision 0504,
   * `.activityfeed` itself) *once given a real ceiling to bound
   * against* — that fix already built the internal scrollbar this
   * decision asked for, it just had nothing to compute its own height
   * against, since this box, its outermost ancestor, was never
   * actually bounded either. The same `.activityfeed` change is very
   * likely what surfaced this: before it, the feed's own flat
   * `max-height: 300px` capped growth on its own regardless of how
   * tall the page around it was ever allowed to get, which is exactly
   * why a long conversation growing the window past the screen was
   * never reported until now.
   */
  /*
    **No page padding, and less above the card than below — decision
    0526.** Reported live, with a screenshot: "reduce the space at the
    top, and move any reclaimed space to the bottom of the screen. Right
    now the card sits a little low." The cause: `tokens.css`'s bare
    `body` rule gives every page `padding: 2rem 1rem`, and nothing here
    reset it. With `height: 100vh` that padding sat *outside* the
    height, so the card started 32px lower than `#docwindow-root`
    asked for and its bottom ran 64px past the window, clipped by
    `overflow: hidden`. `padding: 0` makes the window exactly one
    screen, and `#docwindow-root`'s own padding is now the only space
    around the card: 12px above, 20px below.
  */
  body.docwindowbody {
    display: flex;
    place-items: initial;
    height: 100vh;
    padding: 0;
    overflow: hidden;
    background: var(--surface-0);
  }
  #docwindow-root {
    flex: 1;
    display: flex;
    flex-direction: column;
    width: auto;
    max-width: none;
    margin: 0;
    padding: 12px 24px 20px;
    box-sizing: border-box;
    /**
     * **The other half of the same bug.** A flex item's default
     * minimum height is its own content's intrinsic height, not 0 — so
     * even with `body` now genuinely capped at `height: 100vh`, this
     * box would still refuse to shrink below whatever the document
     * image and Timeline / Chat wanted, pushing `body` past its own
     * fixed height exactly as before. The same reasoning
     * `.docwindowbody .panel` and every pane below it already carries
     * its own `min-height: 0` for.
     */
    min-height: 0;
  }

  /*
    **The panel fills the window rather than floating in it** —
    reported live, from a screenshot: "the card appears to float in
    centre screen rather than filling the page... have it use all
    available space, and resize as needed." The `max-width: 1100px;
    margin: 0 auto` this replaces was this page's own invention —
    `#viewer`/`.columns` (above) go edge to edge everywhere else in
    this stylesheet; nothing here caps a page's width, and this page
    should not have been the one exception.

    A flex chain rather than a second fixed height to guess at.
    `.vpreview`'s own `height: calc(100vh - 300px)` was measured
    against the *main app's* topbar, tabs and action row sitting above
    it (decision 0271's own comment says as much) — a page with none
    of that chrome has no honest way to reuse that number, and typing
    a different constant here would be the exact second guess decision
    0380 already warned against. `flex: 1` on the panel's own body
    lets it take whatever the window actually has left, and resize
    with it, rather than encoding an assumption about how tall this
    page's chrome is.
  */
  .docwindowbody .panel {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
  }
  .docwindowbody .panel > *:not(.cardhead) {
    flex: 1;
    min-height: 0;
  }
  .docwindowbody .vpreview { height: auto; }

  /**
   * **Timeline / Chat as a standing right-hand column, pop-out only**
   * — decision 0394, asked for directly: *"the timeline / chat should
   * appear on the right of the invoice image... when expanded in the
   * separate window only."* Built entirely in `initDocumentWindow()`'s
   * own layout code (`viewer.js`), never in `buildDocTabs()` — the
   * embedded validation-screen card calls that same shared function
   * (decision 0384's own reasoning for why it is shared at all) and
   * must go on tabbing between Document/XML/Timeline exactly as
   * before; nothing here is reachable from there.
   *
   * `.docwindowsplitleft` is the only thing `.panel > *:not(.cardhead)`
   * used to reach directly (the doc/xml panes) — now one level deeper,
   * so `.vpreview` needs its own `flex: 1; min-height: 0` restated
   * for this new parent rather than inherited from that rule.
   * `.docwindowsplitright` gets its own scroll rather than growing the
   * whole window: a long comment thread should not push the document
   * off screen.
   */
  .docwindowsplit { display: flex; flex: 1; min-height: 0; gap: 20px; }
  .docwindowsplitleft { flex: 1 1 68%; min-width: 0; display: flex; flex-direction: column; min-height: 0; }
  .docwindowsplitleft > .vpreview { flex: 1; min-height: 0; }
  /* Decision 0722: a little wider (was 25%), with its own Timeline / Chat and Supplier tabs. */
  .docwindowsplitright {
    flex: 0 0 32%;
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
    border-left: 0.5px solid var(--border);
    padding-left: 20px;
    overflow-y: auto;
  }
  .docwindowtimeline { display: flex; flex-direction: column; flex: 1; min-height: 0; }
  .docwindowtimeline .activitytabcontent { flex: 1; min-height: 0; }
  .docwindowtimeline[hidden] { display: none; }
  .docwindowrighttabs { flex: 0 0 auto; margin-bottom: 8px; }

  /**
   * **Stacks below the document rather than squeezing to a sliver** —
   * the same 900px width this stylesheet already treats as "too
   * narrow for a side-by-side layout" elsewhere (`.supplierhead`,
   * `.poloadhead`). A pop-out this narrow has already lost most of the
   * benefit of a side column; reading top-to-bottom beats a 25%-wide
   * strip nobody can read.
   */
  @media (max-width: 900px) {
    .docwindowsplit { flex-direction: column; }
    .docwindowsplitright {
      flex: 0 0 auto;
      max-height: 40vh;
      border-left: none;
      border-top: 0.5px solid var(--border);
      padding-left: 0;
      padding-top: 16px;
    }
  }

  /*
    The card that gives its space to the pop-out, once one is open —
    decision 0384. `.vpreview` already sizes and borders the box;
    `.vthumb` already centres content inside it (both shared with the
    embedded panel this reuses, not new rules).
  */
  .vpoppedouttext { margin-bottom: 10px; }
  .vpoppedoutactions { display: flex; gap: 10px; }

  /**
   * Talk to an AP Expert — decision 0430. A small, self-contained chat
   * inside the same `.panel.card-graphic` shell every other AP
   * Analytics card already uses, not a new page chrome of its own.
   */
  .chatmessages {
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-height: 360px;
    min-height: 80px;
    overflow-y: auto;
    padding: 4px 2px;
  }
  .chatbubble { display: flex; }
  .chatbubble > div {
    max-width: 80%;
    padding: 9px 13px;
    border-radius: var(--radius);
    line-height: 1.4;
  }
  .chatbubble-question { justify-content: flex-end; }
  .chatbubble-question > div { background: var(--bg-accent); color: var(--text-accent); }
  .chatbubble-answer { justify-content: flex-start; }
  .chatbubble-answer > div { background: var(--surface-1); color: var(--text-primary); }
  .chatactions { display: flex; gap: 8px; margin: -2px 0 0 2px; }
  /**
   * `align-items: center` — decision 0432, corrected by its own first
   * addendum. `flex-end` bottom-edge-aligned the input against
   * `.actionlink`'s own icon-above-label stack, which is mathematically
   * aligned but not visually aligned: the stack's weight sits in its
   * icon, near its own top, so a shared bottom edge still read as the
   * buttons floating above the input rather than sitting beside it
   * (measured ~9.5px off at this row's own sizes). `center` lines up
   * the two elements' actual vertical centres instead — still without
   * `stretch`'s own problem of pulling the input up to the buttons'
   * full height.
   */
  .chatinputrow { display: flex; align-items: center; gap: 10px; margin-top: 12px; }
  .chatinputrow .chatinput {
    flex: 1;
    padding: 9px 12px;
    border: 0.5px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--surface-2);
    color: var(--text-primary);
  }
  .chatinputrow .chatinput:disabled { opacity: 0.6; }

/*
  **Split coding — decision 0548.** The rows in the Coding pop-out, the
  bar showing how much of the line each takes, the balanced / still-to-
  allocate line, and the line table's "Split · n" chip. The chip and the
  balanced line use the fixed pale green of a coded line (0541), the
  still-to-allocate line the fixed warning pair, so both read the same
  in Day and Night.
*/
.linklike {
  border: none; background: none; padding: 0; font: inherit; font-size: var(--text-sm); font-weight: 600;
  color: var(--text-accent); cursor: pointer;
}
.linklike:hover { text-decoration: underline; }
.codingsplit { flex: 0 1 auto; min-height: 0; display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.codingsplit > * { flex: none; }
.codingsplit > .splittablewrap { flex: 0 1 auto; min-height: 90px; overflow: auto; }
.popout.codingpopout.splitting { width: min(940px, 100%); height: min(900px, 94vh); }
.popout.codingpopout.splitting .codingresults { flex: 1 0 90px; }
.codingsplit:empty { display: none; }
.codingsplitstart { justify-self: start; }
.splithead { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
.splithead h4 { margin: 0; font-size: var(--text-base); }
.splittable { width: 100%; border-collapse: separate; border-spacing: 0 6px; }
.splittable th { font-size: var(--text-sm); color: var(--text-muted); font-weight: 500; text-align: left; padding: 0 8px; }
.splittable th.num, .splittable td.num { text-align: right; white-space: nowrap; }
.splittable td {
  background: var(--surface-1); border-top: 0.5px solid var(--border); border-bottom: 0.5px solid var(--border);
  padding: 7px 8px; vertical-align: top;
}
.splittable td:first-child { border-left: 0.5px solid var(--border); border-radius: var(--radius) 0 0 var(--radius); }
.splittable td:last-child { border-right: 0.5px solid var(--border); border-radius: 0 var(--radius) var(--radius) 0; width: 22px; }
.splittable td.splitobject { min-width: 300px; }
.splitobjectrow { display: flex; align-items: center; gap: 8px; }
.splitobjectrow > :last-child { flex: 1; min-width: 0; }
.splitobjectrow .codingswitch { flex: none; }
.splittable td.splitobject > * + * { margin-top: 5px; }
.splittable td.splitgl { min-width: 180px; }
.splitkind { display: inline-block; font-size: var(--text-sm); font-weight: 600; color: var(--text-secondary); }
.codingswitch.small button { padding: 2px 9px; }
.splittable input.splitvalue { width: 76px; min-width: 76px; box-sizing: border-box; text-align: right; }
.splitcomputed { display: inline-block; min-width: 70px; padding-top: 7px; color: var(--text-secondary); }
.splitremove { border: none; background: none; color: var(--text-muted); cursor: pointer; font-size: var(--text-base); padding: 6px 2px; }
.splitremove:hover { color: var(--text-danger); }
.splitbar { display: flex; height: 8px; border-radius: 999px; overflow: hidden; background: var(--surface-2); }
.splitbar i { display: block; height: 100%; }
.splitbar i.c0 { background: var(--chart-1); }
.splitbar i.c1 { background: var(--chart-3); }
.splitbar i.c2 { background: var(--chart-2); }
.splitbar i.c3 { background: var(--chart-4); }
.splitbar i.c4 { background: var(--chart-5); }
.splitbar i.c5 { background: var(--text-muted); }
.splittotal { display: flex; justify-content: space-between; align-items: center; gap: 10px; padding: 8px 12px; border-radius: var(--radius); font-size: var(--text-sm); }
.splittotal.ok { background: var(--severity-ok-bg); color: var(--severity-ok-text); font-weight: 600; }
.splittotal.warn { background: var(--severity-warning-bg); color: var(--severity-warning-text); font-weight: 600; }
.splitfill {
  border: 0.5px solid color-mix(in srgb, var(--severity-warning-text) 40%, transparent); background: #ffffff; color: var(--severity-warning-text);
  border-radius: var(--radius); padding: 3px 10px; font: inherit; font-size: var(--text-sm); font-weight: 600; cursor: pointer;
}
.splitlinks { display: flex; gap: 18px; }
.codingsplitbudget { display: flex; align-items: center; gap: 6px; }
.codingsplitbudget:empty { display: none; }
.codingsplitbudget .mini { display: inline-block; width: 90px; height: 5px; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.codingsplitbudget .mini i { display: block; height: 100%; background: var(--chart-1); }
.codingsplitbudget .mini i.over { background: var(--text-danger); }
.popout.codingpopout button.primary:disabled { opacity: 0.45; cursor: not-allowed; }
.codingcol .codingbtn.split { width: auto; }
.splitchip {
  display: inline-flex; align-items: center; padding: 1px 8px; border-radius: 999px; white-space: nowrap;
  font-size: var(--text-sm); font-weight: 700; background: var(--surface-1); color: var(--text-secondary);
  border: 1.5px solid var(--border);
}
.codingcol .codingbtn.split.coded .splitchip { background: var(--severity-ok-bg); color: var(--severity-ok-text); border-color: var(--border-success); }
.codingcol .codingbtn.split.danger .splitchip { border-color: var(--text-danger); color: var(--text-danger); }

/* Decision 0551 — an approver's share of a split line: the rows their task is for. */
.panel.splitshare { display: grid; gap: 4px; }
.splitsharehead { font-weight: 600; }
.splitsharelist { margin: 0; padding-left: 18px; font-size: var(--text-sm); color: var(--text-secondary); }
.splitsharelist b { color: var(--text-primary); font-weight: 600; }

/* Decision 0552 — the ERP export screen. */
.erpreadyrow { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 6px; }
.erpreadyrow p { margin: 0; }
.erptable { width: 100%; border-collapse: collapse; font-size: var(--text-base); }
.erptable th { text-align: left; font-size: var(--text-sm); color: var(--text-muted); font-weight: 500; padding: 6px 8px; border-bottom: 0.5px solid var(--border); }
.erptable td { padding: 8px; border-bottom: 0.5px solid var(--border); }
.erptable .num { text-align: right; white-space: nowrap; }
/* Decision 0553 — an undone ERP export: still listed and downloadable, marked with who undid it and why. */
.erptable .erpactions { display: flex; justify-content: flex-end; gap: 6px; }
.erptable td:has(> .erpactions) { width: 1%; white-space: nowrap; }
.erptable tr.erpundone td { color: var(--text-secondary); }
.erpundonenote { margin-top: 3px; }
.erpundonepill { display: inline-block; padding: 1px 8px; border-radius: 999px; font-weight: 600; background: var(--severity-warning-bg); color: var(--severity-warning-text); }
/* Decision 0554 — Undo an ERP export: the app's own pop-out, sized like Discard's. */
.popout.erpundopop { width: min(520px, 100%); }
.popout.erpundopop textarea.erpundoreason { width: 100%; min-height: 84px; box-sizing: border-box; resize: vertical; }

/* Decision 0556 — the Route monitor: four counts, messages beside one message opened. */
.rmtiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; margin-bottom: 14px; }
.rmtile { background: var(--surface-2); border: 0.5px solid var(--border); border-radius: var(--radius); padding: 12px 16px; }
.rmtile .k { font-size: var(--text-sm); color: var(--text-muted); }
.rmtile .v { font-size: var(--text-xl); font-weight: 700; margin-top: 2px; }
.rmtile.bad .v { color: var(--text-danger); }
.rmtile.warn .v { color: var(--text-warning); }
.rmcols { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); gap: 14px; align-items: start; }
.rmcols > .panel { margin-bottom: 0; }
.rmfilters { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 10px; }
.rmchip { padding: 3px 12px; border-radius: 999px; border: 0.5px solid var(--border); background: var(--surface-1); color: var(--text-secondary); font: inherit; font-size: var(--text-sm); cursor: pointer; }
.rmchip.on { background: var(--text-accent); color: var(--surface-2); border-color: transparent; font-weight: 600; }
.rmsep { width: 1px; height: 18px; background: var(--border); margin: 0 4px; }
.rmtable { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.rmtable th { text-align: left; color: var(--text-muted); font-weight: 500; padding: 6px 8px; border-bottom: 0.5px solid var(--border); }
.rmtable td { padding: 8px; border-bottom: 0.5px solid var(--border); vertical-align: top; }
.rmtable tr.clickable { cursor: pointer; }
.rmtable tr.bad td { background: color-mix(in srgb, var(--text-danger) 6%, transparent); }
.rmtable tr.sel td { background: color-mix(in srgb, var(--text-accent) 10%, transparent); }
.rmtime { white-space: nowrap; color: var(--text-secondary); }
.rmpill { display: inline-block; padding: 1px 9px; border-radius: 999px; font-weight: 600; white-space: nowrap; }
.rmpill.ok { background: var(--severity-ok-bg); color: var(--severity-ok-text); }
.rmpill.warn { background: var(--severity-warning-bg); color: var(--severity-warning-text); }
.rmpill.bad { background: var(--severity-danger-bg); color: var(--severity-danger-text); }
.rmpill.q { background: var(--surface-1); color: var(--text-secondary); border: 0.5px solid var(--border); }
.rmchain { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 8px; margin: 10px 0; }
.rmpart { border: 0.5px solid var(--border); border-radius: var(--radius); padding: 8px 10px; background: var(--surface-1); font-size: var(--text-sm); }
.rmpart .k { color: var(--text-muted); }
.rmpart .d { margin-top: 2px; font-weight: 600; }
.rmpart.ok { background: var(--severity-ok-bg); border-color: color-mix(in srgb, var(--border-success) 45%, transparent); }
.rmpart.ok .d { color: var(--severity-ok-text); }
.rmpart.warn { background: var(--severity-warning-bg); }
.rmpart.warn .d { color: var(--severity-warning-text); }
.rmpart.bad { background: var(--severity-danger-bg); border-color: color-mix(in srgb, var(--border-danger) 60%, transparent); }
.rmpart.bad .d { color: var(--severity-danger-text); }
.rmpart.idle { opacity: 0.55; }
/* The pale severity fills keep Day's colours in both moods, so their words take the dark value text (as 0528's card does). */
.rmpart.ok .k, .rmpart.warn .k, .rmpart.bad .k { color: var(--severity-value-text); opacity: 0.7; }
.rmexplain { border-radius: var(--radius); padding: 12px 14px; color: var(--severity-value-text); background: var(--severity-danger-bg); border: 0.5px solid color-mix(in srgb, var(--border-danger) 60%, transparent); margin: 10px 0; }
.rmexplain .h { font-weight: 700; color: var(--severity-danger-text); }
.rmexplain p { margin: 6px 0 0; }
/* Decision 0558: an undone export is dealt with, not waiting: amber. */
.rmexplain.done { background: var(--severity-warning-bg); border-color: color-mix(in srgb, var(--border-warning) 60%, transparent); }
.rmexplain.done .h { color: var(--severity-warning-text); }
.rmtech { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: var(--text-sm); background: var(--surface-1); border: 0.5px solid var(--border); border-radius: var(--radius); padding: 10px 12px; margin: 10px 0; white-space: pre-wrap; word-break: break-word; }
.rmh4 { margin: 14px 0 6px; font-size: var(--text-base); }
.rmlist { margin: 0; padding-left: 18px; font-size: var(--text-sm); }
.rmorigs { display: flex; flex-wrap: wrap; gap: 8px; }
.rmorig { display: inline-flex; gap: 8px; align-items: center; padding: 5px 10px; border: 0.5px solid var(--border); border-radius: var(--radius); background: var(--surface-1); color: var(--text-primary); font: inherit; font-size: var(--text-sm); cursor: pointer; }
.rmorig.bad { border-color: color-mix(in srgb, var(--border-danger) 60%, transparent); }
.rmhistory { margin: 0; padding-left: 18px; font-size: var(--text-sm); color: var(--text-secondary); display: grid; gap: 3px; }
/* Decision 0560: what each e-invoice was, and what the EN 16931 checks found. */
.rmfmt { border: 0.5px solid var(--border); border-radius: var(--radius); background: var(--surface-1); padding: 8px 10px; margin-bottom: 6px; font-size: var(--text-sm); }
.rmfmthead { display: flex; justify-content: space-between; gap: 10px; align-items: baseline; }
.rmfmtname { font-weight: 600; color: var(--text-primary); overflow-wrap: anywhere; }
.rmrules { margin: 6px 0 4px; padding-left: 18px; display: grid; gap: 3px; }
.rmrule { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: var(--text-sm); color: var(--text-danger); }
@media (max-width: 1100px) { .rmcols { grid-template-columns: 1fr; } .rmtiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Decision 0557 — Routes: Sources beside Destinations, and one route as its five parts. */
.rtgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; margin-bottom: 14px; align-items: start; }
.rtgrid > .panel { margin-bottom: 0; }
.rttable { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.rttable th { text-align: left; color: var(--text-muted); font-weight: 500; padding: 6px 8px; border-bottom: 0.5px solid var(--border); }
.rttable td { padding: 8px; border-bottom: 0.5px solid var(--border); vertical-align: top; }
.rttable tr.clickable { cursor: pointer; }
.rttable tr.sel td { background: color-mix(in srgb, var(--text-accent) 10%, transparent); }
.rtname { font-weight: 600; color: var(--text-primary); }
.rtstd { display: inline-block; margin-top: 3px; padding: 1px 9px; border-radius: 999px; font-weight: 600; background: color-mix(in srgb, var(--text-accent) 12%, transparent); color: var(--text-accent); }
.rtchain { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 22px; margin: 12px 0 10px; }
.rtpart { position: relative; border: 0.5px solid var(--border); border-radius: var(--radius); padding: 10px 12px; background: var(--surface-1); min-height: 92px; }
.rtpart:not(:last-child)::after { content: "\2192"; position: absolute; right: -18px; top: 36px; color: var(--text-muted); }
.rtpart .k { font-size: var(--text-sm); color: var(--text-muted); }
.rtpart .v { font-weight: 600; margin-top: 4px; }
.rtpart .d { font-size: var(--text-sm); color: var(--text-secondary); margin-top: 3px; }
.rtpart.core { border-color: var(--border-accent); background: color-mix(in srgb, var(--text-accent) 7%, var(--surface-1)); }
/* Decision 0560 — a Source route's receiving formats, with the last 30 days. */
.rtformats { width: 100%; border-collapse: collapse; font-size: var(--text-sm); margin-top: 6px; }
.rtformats th { text-align: left; color: var(--text-muted); font-weight: 500; padding: 6px 8px; border-bottom: 0.5px solid var(--border); }
.rtformats th.n, .rtformats td.n { text-align: right; white-space: nowrap; }
.rtformats td { padding: 8px; border-bottom: 0.5px solid var(--border); vertical-align: top; }
.rtformats .fname { font-weight: 600; color: var(--text-primary); }
.rtformats .bad { color: var(--text-danger); }
.rtnote { border-radius: var(--radius); padding: 10px 12px; background: color-mix(in srgb, var(--text-accent) 7%, var(--surface-1)); border: 0.5px solid var(--border-accent); font-size: var(--text-sm); margin-top: 12px; }
.rtnote .h { font-weight: 700; color: var(--text-accent); margin-bottom: 3px; }

/* Decision 0561 — the mapping editor: a supplier's XML on the left, EN 16931 on the right, a line for each mapping. */
.meed { display: grid; grid-template-columns: minmax(0, 1fr) 380px; gap: 14px; align-items: start; }
.meed > .panel, .meed > div > .panel { margin-bottom: 14px; }
.megrid { display: grid; grid-template-columns: minmax(0, 1fr) 150px minmax(0, 1fr); position: relative; }
.megrid svg.melines { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; }
.meline { fill: none; stroke: var(--text-accent); stroke-width: 1.4; opacity: 0.8; }
.meline.sel { stroke-width: 2.6; opacity: 1; }
.mecol h4 { margin: 0 0 2px; font-size: var(--text-sm); text-transform: uppercase; letter-spacing: 0.03em; color: var(--text-accent); }
.meh4s { font-size: var(--text-sm); color: var(--text-secondary); margin-bottom: 10px; }
.megrp { font-size: var(--text-sm); color: var(--text-muted); margin: 12px 0 5px; font-weight: 600; }
.meel { display: flex; width: 100%; box-sizing: border-box; justify-content: space-between; gap: 8px; align-items: baseline; border: 0.5px solid var(--border); border-radius: var(--radius); background: var(--surface-1); color: var(--text-primary); padding: 6px 10px; margin-bottom: 6px; font: inherit; font-size: var(--text-sm); text-align: left; cursor: pointer; min-height: 32px; }
.meel.unused { opacity: 0.7; }
.meel.sel { border: 1.5px solid var(--border-accent); background: color-mix(in srgb, var(--text-accent) 9%, var(--surface-1)); opacity: 1; }
.meel.miss { border: 1px dashed var(--border-danger); background: color-mix(in srgb, var(--text-danger) 8%, transparent); }
.meel.miss .mebt, .meel.miss .mest { color: var(--text-danger); }
.mep { font-family: ui-monospace, Menlo, Consolas, monospace; }
.mesv { color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 50%; }
.mebt { font-weight: 600; }
.mebt i { font-style: normal; font-weight: 400; color: var(--text-muted); margin-left: 6px; }
.mereq { color: var(--text-danger); font-weight: 700; margin-left: 2px; }
.mest { color: var(--text-secondary); white-space: nowrap; }
/* Decision 0591: an outbound mapping level with nothing sent yet. */
.omempty { padding: 4px 0 6px; }
.mefx { position: absolute; transform: translate(-50%, -50%); font-style: italic; font-weight: 700; font-size: var(--text-sm); padding: 2px 7px; border-radius: 999px; background: var(--surface-2); border: 1px solid var(--border-strong); color: var(--text-primary); z-index: 2; }
.mefx.on { background: var(--text-accent); color: var(--surface-2); border-color: transparent; }
.mefx.static { position: static; transform: none; display: inline-block; }
.melegend { display: flex; flex-wrap: wrap; gap: 16px; font-size: var(--text-sm); color: var(--text-secondary); margin: 2px 0 12px; align-items: center; }
.mesw { display: inline-block; width: 22px; border-top: 2px solid var(--text-accent); vertical-align: middle; margin-right: 6px; }
.menote { border-radius: var(--radius); padding: 8px 12px; margin: 0 0 12px; font-size: var(--text-sm); background: var(--severity-warning-bg); color: var(--severity-warning-text); }
.menote.ok { background: var(--severity-ok-bg); color: var(--severity-ok-text); }
.mekv { display: grid; grid-template-columns: 90px minmax(0, 1fr); gap: 6px 10px; font-size: var(--text-sm); margin: 8px 0; align-items: center; }
.mekv.wide { grid-template-columns: 120px minmax(0, 1fr); }
.mekv .l { color: var(--text-muted); }
.meinput, .mesay { width: 100%; box-sizing: border-box; font: inherit; font-size: var(--text-sm); padding: 6px 10px; border: 0.5px solid var(--border-strong); border-radius: var(--radius); background: var(--surface-2); color: var(--text-primary); }
.mesay { margin-top: 8px; resize: vertical; }
.mesaid { border: 1.5px solid var(--border-accent); border-radius: var(--radius); padding: 6px 10px; font-size: var(--text-sm); margin-bottom: 6px; }
.mesteps { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 6px; }
.mestep { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: var(--text-sm); padding: 2px 9px; border-radius: 999px; background: color-mix(in srgb, var(--text-accent) 12%, transparent); color: var(--text-accent); }
.mearg { color: var(--text-primary); }
.meex { font-size: var(--text-sm); border-collapse: collapse; margin: 4px 0 8px; }
.meex td { padding: 2px 8px 2px 0; }
.meex td.bad, .rtformats .bad { color: var(--text-danger); }
.merefused { border-radius: var(--radius); padding: 8px 12px; background: var(--severity-danger-bg); color: var(--severity-danger-text); font-size: var(--text-sm); margin-top: 8px; }
/* Decision 1018: a problem box with no problem in it is not drawn (it was an empty red bar). */
.merefused:empty { display: none; }
.mebtns { margin-top: 8px; flex-wrap: wrap; }
/* Decision 0565: a CSV mapping's options, and an amount that adds up the lines. */
.mecheck { display: flex; align-items: center; gap: 6px; }
.mesum { margin: 4px 0 8px; color: var(--text-muted); }
/* Decisions 0563 and 0564: confirming a mapping's retirement, in a pop-out. */
.popout.meretirepop { width: min(480px, 100%); }
.rmfmtact { margin-top: 6px; }
/* Decision 0570: AI proposals in the mapping editor. */
.meprops { list-style: none; padding: 0; margin: 6px 0 10px; }
.meprop { border-top: 1px solid var(--border); padding: 8px 0; }
.meprophead { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.meai-threshold { display: flex; align-items: center; gap: 10px; margin: 8px 0; }
.meai-threshold input { flex: 1; }
.meaitag { margin-left: 6px; font-size: var(--text-sm); font-weight: 600; color: var(--text-accent); border: 1px solid var(--border-accent); border-radius: var(--radius); padding: 0 4px; }
/* Decision 0569: rules for the whole invoice, in the mapping editor. */
.merules { margin: 8px 0; padding-left: 20px; }
.merules li { margin-bottom: 10px; }
.merule { color: var(--text-muted); }
/* Decision 0568: look-up lists on the Routes screen, and one open in a pop-out. */
.rtlookupnew { display: flex; gap: 8px; align-items: center; margin-top: 10px; }
/*
  Decision 0820: Routes as tabs (Sources, Destinations, Look-up lists); a
  route opened as a page, its five parts above its own tabs; the longer
  explanations behind an (i), shown on hover or focus, or kept open once
  clicked.
*/
.rttabs { display: inline-flex; margin: 0 0 14px; }
.rtchainpanel { margin-bottom: 14px; }
.rtchainpanel .rtchain { margin: 0; }
.activitycount.rtwarn { background: var(--bg-warning); color: var(--text-warning); }
.rtline { display: flex; align-items: center; gap: 6px; margin: 0 0 8px; }
.rtmeta { display: grid; grid-template-columns: 160px 1fr; gap: 8px 14px; font-size: var(--text-sm); }
.rtmeta .l { color: var(--text-muted); }
.rtinfo { position: relative; display: inline-flex; }
.rtinfobtn {
  width: 18px; height: 18px; border-radius: 50%; border: 1px solid var(--text-muted); background: none; padding: 0;
  color: var(--text-muted); font-family: inherit; font-size: var(--text-sm); font-style: italic; font-weight: 600; line-height: 16px; cursor: pointer;
}
.rtinfobtn:hover, .rtinfo.open .rtinfobtn { color: var(--text-accent); border-color: var(--text-accent); }
.rtinfotip {
  display: none; position: absolute; top: 24px; left: -8px; z-index: 20; width: 360px; max-width: 80vw;
  background: var(--surface-2); border: 0.5px solid var(--border-strong); border-radius: var(--radius);
  padding: 10px 12px; box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); color: var(--text-primary); font-size: var(--text-sm); font-weight: 400;
}
.rtinfotip p { margin: 4px 0 0; color: var(--text-secondary); }
.rtinfotip p:first-child { margin-top: 0; }
.rtinfo:hover .rtinfotip, .rtinfo:focus-within .rtinfotip, .rtinfo.open .rtinfotip { display: block; }
/*
  Decision 0821: the five parts are buttons, the route page's navigation.
  The one chosen is outlined, with a pointer to what is behind it below;
  EN 16931 carries an "Internal model" tag instead of the outline it had
  (0557), so the outline only ever means the part chosen.
*/
button.rtpart { font: inherit; color: inherit; text-align: left; cursor: pointer; width: 100%; display: flex; flex-direction: column; align-items: flex-start; justify-content: flex-start; }
button.rtpart:hover { border-color: var(--border-strong); }
button.rtpart:focus-visible { outline: 2px solid var(--border-accent); outline-offset: 2px; }
.rtpart.on { border: 1.5px solid var(--border-accent); background: color-mix(in srgb, var(--text-accent) 8%, var(--surface-1)); }
.rtpart.on::before {
  content: ""; position: absolute; left: 50%; bottom: -21px; width: 12px; height: 12px; z-index: 2;
  background: var(--surface-1); border-left: 0.5px solid var(--border-accent); border-top: 0.5px solid var(--border-accent);
  transform: translateX(-50%) rotate(45deg);
}
.rtinternal { display: inline-block; margin-left: 6px; padding: 0 7px; border-radius: 999px; font-size: var(--text-sm); font-weight: 600; background: var(--bg-accent); color: var(--text-accent); vertical-align: 1px; }
.rtflag { display: inline-block; margin-top: 4px; }
.rtunder { border-color: var(--border-accent); }
.rtunderhead { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.rtunderhead h3 { margin: 0; }
/* Decision 0824: Add mapping at the right of part 3's heading; the example pop-out. */
.rtunderright { margin-left: auto; }
.popout.mexample { width: min(640px, 92vw); }
.mefound { font-size: var(--text-sm); color: var(--text-secondary); }
.mefound:empty { display: none; }
/* Decision 0826: a row's actions side by side (From an example, Make my own copy). */
.rtrowactions { display: inline-flex; gap: 10px; justify-content: flex-end; flex-wrap: wrap; }
/* Decision 0828: an EN 16931 term's BT number, or VibeFinance where EN 16931 has none. */
.mebtag { margin-left: 6px; font-size: var(--text-sm); font-weight: 400; color: var(--text-muted); }
.mebtag.vf { color: var(--text-warning); border: 0.5px solid var(--border-warning); border-radius: 999px; padding: 0 6px; }
.rtbadge { font-size: var(--text-sm); border: 0.5px solid var(--border-strong); border-radius: 999px; padding: 1px 9px; color: var(--text-muted); }
.rtbadge.ed { border-color: var(--border-success); color: var(--text-success); }
.rtsection + .rtsection { margin-top: 14px; }
.rtplaces > .panel { margin-top: 12px; }
.rtplace { border: 0.5px solid var(--border); border-radius: var(--radius); padding: 10px 12px; margin-top: 10px; background: var(--surface-0, var(--surface-1)); }
.rtplacehead { margin-bottom: 8px; }
.rtstages { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rtstage { border: 0.5px solid var(--border); border-radius: 8px; padding: 5px 10px; font-size: var(--text-sm); color: var(--text-secondary); }
.rtstage.on { border-color: var(--border-accent); color: var(--text-primary); font-weight: 600; }
.rtlookupnew .meinput { max-width: 280px; }
.popout.llpop { width: min(640px, 100%); max-height: 85vh; overflow: auto; }
.lltable td { padding: 2px 4px; }
.lltable .meinput { width: 100%; box-sizing: border-box; }
.llpaste { width: 100%; box-sizing: border-box; font: inherit; font-size: var(--text-sm); }
.llretire { border-top: 1px solid var(--border); margin-top: 12px; padding-top: 8px; }
/* Decision 0566: reading a captured file again with a newer mapping version. */
.rmreread { margin-top: 6px; }
@media (max-width: 1100px) { .meed { grid-template-columns: 1fr; } }

/* Decision 0557 — Process routes: sources into the entry stage, destinations out of the exit stage. */
.prgrid { display: grid; grid-template-columns: 250px 44px minmax(0, 1fr) 44px 250px; align-items: center; margin: 44px 0 6px; }
.prstack { position: relative; display: flex; flex-direction: column; gap: 12px; }
.prcolh { position: absolute; top: -30px; left: 0; font-weight: 700; font-size: var(--text-sm); color: var(--text-accent); text-transform: uppercase; letter-spacing: 0.03em; white-space: nowrap; }
.prcolh span { text-transform: none; font-weight: 400; color: var(--text-secondary); margin-left: 6px; letter-spacing: 0; }
.prcard { display: block; width: 100%; box-sizing: border-box; text-align: left; font: inherit; color: var(--text-primary); border: 1.5px solid var(--border-accent); border-radius: var(--radius); padding: 8px 12px; background: color-mix(in srgb, var(--text-accent) 7%, var(--surface-1)); cursor: pointer; overflow: hidden; }
.prcard.dim { border-style: dashed; background: var(--surface-1); }
.prcard.sel { box-shadow: 0 0 0 2px var(--text-accent); }
.prcard .t { font-size: var(--text-sm); font-weight: 700; color: var(--text-accent); letter-spacing: 0.03em; }
.prcard .v { font-weight: 600; margin-top: 3px; }
.prcard .d { font-size: var(--text-sm); color: var(--text-secondary); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.prcard .s { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 4px; }
.prempty { display: flex; align-items: center; justify-content: center; text-align: center; padding: 8px 12px; border: 0.5px dashed var(--border); border-radius: var(--radius); color: var(--text-muted); font-size: var(--text-sm); box-sizing: border-box; }
.prbracket { width: 44px; color: var(--text-muted); fill: none; stroke: currentColor; stroke-width: 1.5; }
.prflow { display: flex; align-items: center; gap: 6px; min-width: 0; }
.prstage { flex: 1; min-width: 0; text-align: center; border: 0.5px solid var(--border); border-radius: var(--radius); padding: 10px 6px; background: var(--surface-1); }
.prstage .sn { font-weight: 600; font-size: var(--text-sm); }
.prstage .sd { font-size: var(--text-sm); color: var(--text-accent); margin-top: 3px; }
.prstage.edge { flex: 1.35; border: 1.5px solid var(--border-accent); background: color-mix(in srgb, var(--text-accent) 7%, var(--surface-1)); }
.prarrow { color: var(--text-muted); flex: none; }
.prfields { display: grid; grid-template-columns: 180px 1fr; gap: 10px 16px; font-size: var(--text-sm); align-items: center; margin-top: 8px; }
.prfields .l { color: var(--text-muted); }
.prfields .addr { font-weight: 600; }
.prfields select { max-width: 360px; }
/* Decision 0691: an Email source's size limit and its message to a sender. */
.prfields.emaillimit { border-top: 1px solid var(--border); padding-top: 10px; margin-top: 12px; align-items: start; }
.prfields.emaillimit .l { padding-top: 6px; }
.prfields.emaillimit input.narrow { width: 80px; }
.prfields.emaillimit textarea { width: 100%; max-width: 560px; font: inherit; }
.prfields.emaillimit .inline { display: flex; align-items: center; gap: 10px; }
.popout.praddpop { width: min(520px, 100%); }
.rttable td:last-child { white-space: nowrap; }
@media (max-width: 1200px) { .rtgrid { grid-template-columns: 1fr; } .rtchain { grid-template-columns: 1fr; } .rtpart:not(:last-child)::after { content: none; } }

/* Decision 0559 — reprocess, dismiss and alerts in the Route monitor. */
.rmreason { margin: 2px 0 0 0; font-style: italic; }
.popout.rmpop { width: min(560px, 100%); }
.popout.rmpop textarea.rmreasonbox { width: 100%; min-height: 84px; box-sizing: border-box; resize: vertical; }
.popout.rmalertspop { width: min(640px, 100%); max-height: 86vh; overflow: auto; }
.rmalerts { display: grid; gap: 8px; margin: 8px 0; }
.rmalert { border: 0.5px solid var(--border); border-radius: var(--radius); padding: 8px 12px; background: var(--surface-1); display: grid; gap: 2px; }
.rmalerthead { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.rmalertform { border-top: 1px solid var(--border-strong); margin-top: 10px; padding-top: 4px; display: grid; gap: 8px; }
.rmalertform .kf { display: grid; gap: 4px; }
.rmalertform input[type="text"], .rmalertform select { width: 100%; box-sizing: border-box; }
.rmcheck { display: flex; align-items: center; gap: 6px; font-size: var(--text-sm); }
.popout .rmcheck input.rmnum { width: 80px; flex: none; padding: 4px 8px; margin-left: 4px; }
.popout .rmcheck input[type="checkbox"] { width: auto; flex: none; }
.rmformbuttons { justify-content: flex-start; }

/* Decision 0571 — the Attachments tab: what was received, grouped by message, and the chosen file below. */
.attachpane { display: flex; flex-direction: column; gap: 8px; padding: 8px; box-sizing: border-box; }
.attachpane[hidden] { display: none; }
.attachlist { flex: 0 1 auto; max-height: 45%; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; }
.attachhead { font-size: var(--text-sm); color: var(--text-muted); padding: 0 2px 4px; border-bottom: 1px solid var(--border); margin-bottom: 4px; }
.attachrow { display: flex; align-items: center; gap: 8px; padding: 1px 4px; border-radius: 6px; }
.attachrow.on { background: var(--surface-0); }
.attachrow .actionlink { flex-direction: row; min-width: 0; padding: 3px 6px; gap: 4px; }
.attachrow .actionlink svg { width: 14px; height: 14px; }
.attachpick { flex: 1 1 auto; min-width: 0; display: flex; align-items: baseline; gap: 8px; background: none; border: none; padding: 2px 0; text-align: left; cursor: pointer; color: var(--text-primary); font: inherit; font-size: var(--text-sm); }
.attachpick:hover .attachname { text-decoration: underline; }
.attachname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.attachdetail { color: var(--text-muted); font-size: var(--text-sm); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.attachthis { flex: 0 0 auto; font-size: var(--text-sm); color: var(--text-accent); border: 1px solid var(--border-accent); border-radius: 999px; padding: 0 8px; }
.attachview { flex: 1 1 auto; min-height: 200px; border-radius: var(--radius); overflow: hidden; background: var(--surface-0); }

/* Decision 0573 — Create → Upload documents. */
.creategrid { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: 20px; align-items: start; }
@media (max-width: 1100px) { .creategrid { grid-template-columns: minmax(0, 1fr); } }
.createfield { display: flex; flex-direction: column; gap: 6px; margin: 12px 0 16px; }
.createfield label { font-size: var(--text-sm); color: var(--text-secondary); }
.createdrop { border: 1.5px dashed var(--border-strong); border-radius: var(--radius); padding: 36px 20px; display: flex; flex-direction: column; align-items: center; gap: 10px; text-align: center; background: var(--surface-1); }
.createdrop.over { border-color: var(--border-accent); background: var(--surface-2); }
.createdrop.busy { opacity: 0.6; }
.createdropicon svg { width: 34px; height: 34px; color: var(--text-muted); }
.createdroptitle { font-weight: 600; }
.createmeta { font-size: var(--text-sm); color: var(--text-muted); }
.createempty { padding: 12px 0; }
.createsummary { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0; }
.createrow { display: grid; grid-template-columns: minmax(0, 200px) minmax(0, 1fr) auto; gap: 12px; align-items: center; padding: 10px 0; border-top: 1px solid var(--border); }
.createname { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.createstatus { display: flex; flex-direction: column; gap: 4px; align-items: flex-start; min-width: 0; }
.createact .actionlink { flex-direction: row; padding: 4px 8px; }
.createact .actionlink svg { width: 16px; height: 16px; }
.createpill { font-size: var(--text-sm); border-radius: 999px; padding: 1px 10px; border: 1px solid var(--border); color: var(--text-secondary); }
.createpill.ok { color: var(--text-success); border-color: var(--border-success); }
.createpill.bad { color: var(--text-danger); border-color: var(--border-danger); }
.createpill.info { color: var(--text-accent); border-color: var(--border-accent); }

/* Decision 0575 — Create an invoice. */
.createtabs { margin: 0 0 16px; display: inline-flex; }
/* Decision 0653 — Create → Goods receipts: what to create, then how. */
/* Decision 0742: Invoices / Goods receipts as a rounded pill (.doctabs). */
.createkinds { display: inline-flex; margin: 0 0 16px; }
/*
  Decision 0743: the ways to create, as cards — an icon, the way, and a
  line on what it is for. Chosen, a slim row with the chosen one outlined.
*/
.createcards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin: 0 0 16px; }
.createcard {
  display: flex; align-items: flex-start; gap: 14px; text-align: left; padding: 18px; min-height: 118px;
  border-radius: var(--radius); background: var(--surface-2); border: 0.5px solid var(--border);
  color: var(--text-primary); font: inherit; cursor: pointer;
}
.createcard:hover { border-color: var(--border-strong); }
.createcardicon { flex: none; width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; background: var(--bg-accent); color: var(--text-accent); }
.createcardicon svg { width: 22px; height: 22px; }
.createcardtext { display: flex; flex-direction: column; gap: 6px; flex: 1; min-width: 0; }
.createcardtext b { font-size: var(--text-lg); }
.createcards.compact .createcard { min-height: 0; padding: 12px 14px; align-items: center; }
.createcards.compact .createcardicon { width: 32px; height: 32px; }
.createcard.on { border: 1.5px solid var(--border-accent); box-shadow: var(--tab-active-shadow); }
@media (max-width: 900px) { .createcards { grid-template-columns: minmax(0, 1fr); } }
.createtarget { font-weight: 500; }
.createkinds { margin-right: 12px; }
.createtable td.nowrap { white-space: nowrap; }
.createkeyed { display: flex; flex-direction: column; gap: 16px; }
.createpill.q { color: var(--text-muted); }
.createintro { margin: 10px 0 0; }
.createfilepick { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.createfilepick .actionlink { flex-direction: row; padding: 4px 8px; }
.createfilepick .actionlink svg { width: 16px; height: 16px; }
.createcheck { display: flex; align-items: center; gap: 8px; margin-top: 4px; font-weight: 500; }
.createcheck input { width: auto; }
.createbuttons { display: flex; justify-content: flex-end; margin-top: 16px; }

/* Decision 0576 — Batch upload. */
.createradio { display: flex; align-items: center; gap: 8px; font-size: var(--text-sm); color: var(--text-primary); min-height: 32px; }
.createradio input { width: auto; }
.createinline { flex-direction: row; padding: 2px 8px; margin-left: auto; text-decoration: none; }
.createinline svg { width: 16px; height: 16px; }
.createinlineselect { margin-left: auto; width: auto; max-width: 60%; font-size: var(--text-sm); padding: 4px 8px; }
.createdetails { margin-top: 14px; font-size: var(--text-sm); }
.createdetails summary { cursor: pointer; color: var(--text-secondary); margin-bottom: 6px; }
.createtablewrap { max-height: 60vh; overflow-y: auto; }
.createtable { width: 100%; border-collapse: collapse; font-size: var(--text-sm); margin-top: 4px; }
.createtable th { text-align: left; color: var(--text-muted); font-weight: 500; padding: 6px 8px; border-bottom: 1px solid var(--border); position: sticky; top: 0; background: var(--surface-0); }
.createtable td { padding: 8px; border-bottom: 1px solid var(--border); vertical-align: top; }
.createtable .num { text-align: right; white-space: nowrap; }
.createtable tr.createproblem td { background: color-mix(in srgb, var(--text-danger) 6%, transparent); }
.createtable tr.createduplicate td { background: color-mix(in srgb, var(--text-warning) 6%, transparent); }
.createpill.warn { color: var(--text-warning); border-color: var(--border-warning); }
.createfoot { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-top: 16px; }
.createbtns { display: flex; gap: 8px; align-items: center; }
.createbtns .actionlink:not(.primary) { flex-direction: row; }
.createbtns .actionlink:not(.primary) svg { width: 16px; height: 16px; }
.createtable td:nth-child(1), .createtable td:nth-child(3) { white-space: nowrap; }

/* Decision 0578 — HTTPS in, on a Source panel. */
.httpsin { margin-top: 18px; border-top: 1px solid var(--border); padding-top: 12px; }
.httpshead h4 { margin: 0; font-size: var(--text-base); }
.httpsfield { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; }
.httpsfield label { font-size: var(--text-sm); color: var(--text-secondary); }
.httpscopy { display: flex; align-items: center; gap: 8px; min-width: 0; }
.httpscopy code { font-size: var(--text-sm); background: var(--surface-1); border: 1px solid var(--border); border-radius: 6px; padding: 6px 8px; overflow-x: auto; white-space: nowrap; flex: 1 1 auto; min-width: 0; }
.httpscopy .actionlink, .httpsin .httpskeys .actionlink { flex-direction: row; padding: 4px 8px; gap: 4px; }
.httpscopy .actionlink svg, .httpsin .httpskeys .actionlink svg { width: 16px; height: 16px; }
.httpskeys { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.httpskeys th { text-align: left; color: var(--text-muted); font-weight: 500; padding: 4px 6px; border-bottom: 1px solid var(--border); }
.httpskeys td { padding: 4px 6px; border-bottom: 1px solid var(--border); }
.httpskeys tr.httpsrevoked td { color: var(--text-muted); text-decoration: line-through; }
.httpskeys tr.httpsrevoked td:nth-child(4) { text-decoration: none; }
.httpspre { margin: 0; font-size: var(--text-sm); background: var(--surface-1); border: 1px solid var(--border); border-radius: 6px; padding: 8px; white-space: pre-wrap; word-break: break-all; }
.httpspop { max-width: 560px; }
.httpshead { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.httpsbuttons { display: flex; gap: 6px; }
/* Decision 0581 — Replace a key. */
.httpskeys td.httpsactions { white-space: nowrap; text-align: right; }
.httpskeys td.httpsactions .actionlink { display: inline-flex; }
.httpskeys tr.httpsreplaced td:nth-child(4) { color: var(--text-secondary); font-style: italic; }
.httpscheck { display: flex; align-items: center; gap: 8px; margin-top: 8px; font-size: var(--text-sm); }
/* Decision 1049: a subscription's events, in two columns where there is room. */
.integrationsevents { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); column-gap: 16px; }

/* Decision 0585 — HTTPS out, on a Destination panel. */
.docard { margin-top: 14px; padding: 12px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface-1); }
.docard h4 { margin: 0; font-size: var(--text-base); }
.docard .cardhead { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.dobuttons { display: flex; gap: 6px; }
.dobuttons .actionlink { flex-direction: row; padding: 4px 8px; gap: 4px; }
.dobuttons .actionlink svg { width: 16px; height: 16px; }
.dofield { display: flex; flex-direction: column; gap: 5px; margin-top: 10px; min-width: 0; }
.dofield label { font-size: var(--text-sm); color: var(--text-secondary); }
.dotwo { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.dostart { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-top: 12px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 8px; }
.dostart .actionlink { flex-direction: row; padding: 4px 8px; gap: 4px; }
.dostartpop { max-width: 560px; }
@media (max-width: 720px) { .dotwo { grid-template-columns: 1fr; } }
/* Decision 0595: submitting a Destination for review. */
.dotable { width: 100%; border-collapse: collapse; margin: 6px 0 10px; }
.dotable td { padding: 5px 8px 5px 0; vertical-align: top; border-bottom: 0.5px solid var(--border); }
.dosubform textarea { width: 100%; font: inherit; }
.dosubcustomers { margin: 2px 0 0 22px; }
.docheck { display: flex; align-items: center; gap: 7px; margin: 3px 0; font-size: var(--text-sm); color: var(--text-primary); }
.docheck input { margin: 0; }
/* Decision 0587 — a Destination's business units. */
.duline { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.duline .actionlink { flex-direction: row; padding: 2px 6px; gap: 4px; }
.duline .actionlink svg { width: 14px; height: 14px; }
.dounitspop { max-width: 520px; }
.dounits { max-height: 320px; overflow: auto; margin-top: 6px; }

/* Decision 0589 — the Route library. */
.rtlibrarylink { margin-top: 6px; }
.rtlibrarylink .actionlink { flex-direction: row; padding: 4px 8px; gap: 4px; }
.rtlibrarylink .actionlink svg { width: 16px; height: 16px; }
.libgrid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; margin-top: 16px; }
/* Decision 0590: four across on a wide screen, three, then two, then one. */
@media (max-width: 1600px) { .libgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 1150px) { .libgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 720px) { .libgrid { grid-template-columns: 1fr; } }
.libcard { display: flex; flex-direction: column; gap: 10px; margin: 0; }
.libhead { display: flex; gap: 12px; align-items: flex-start; }
.libtitle { flex: 1 1 auto; min-width: 0; }
.libadd { flex: none; margin-left: auto; }
.libhead h3 { margin: 0; }
.liblogo { width: 40px; height: 40px; border-radius: 8px; background: var(--surface-1); border: 1px solid var(--border); display: flex; align-items: center; justify-content: center; font-weight: 600; color: var(--text-accent); flex: none; }
.libkind { font-size: var(--text-sm); color: var(--text-accent); text-transform: uppercase; letter-spacing: .04em; }
.libdesc { line-height: 1.5; min-height: 40px; }
.libchips { display: flex; gap: 6px; flex-wrap: wrap; }
.libused { margin: 0; padding-left: 18px; font-size: var(--text-sm); }
.libused li { margin: 2px 0; display: flex; gap: 6px; align-items: center; }
.libused .linkish { background: none; border: 0; padding: 0; color: var(--text-accent); cursor: pointer; font: inherit; text-decoration: underline; }
.libfoot { display: flex; gap: 8px; align-items: center; margin-top: auto; flex-wrap: wrap; }
.libfoot .actionlink { flex-direction: row; padding: 4px 8px; gap: 4px; margin-left: auto; }
.libfoot .actionlink svg { width: 16px; height: 16px; }
.doconnector { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 6px; font-size: var(--text-sm); }
.doconnector .actionlink { flex-direction: row; padding: 2px 6px; gap: 4px; }
.doconnector .actionlink svg { width: 14px; height: 14px; }
.libcard > * { flex-shrink: 0; }
.libchips, .libfoot { align-items: center; }
.libchips .rmpill, .libfoot .rmpill { align-self: center; }
/* Decision 0601: the look-up lists a partner's connector reads; an empty one needs filling in. */
.dolists { flex-basis: 100%; }
.dolists .listempty { color: var(--text-warning); }
.libpartner { color: var(--text-accent); }

/* Agents — decision 0622. */
.agenthead { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }
.agentzone { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; color: var(--text-secondary); }
.agentzone .actionlink { flex-direction: row; padding: 2px 8px; gap: 4px; }
.agentzone .actionlink svg { width: 14px; height: 14px; }
/* Decision 0641: absence and cover. */
.absencetable { width: 100%; border-collapse: collapse; }
.absencetable th, .absencetable td { text-align: left; padding: 8px 8px 8px 0; vertical-align: top; border-bottom: 0.5px solid var(--border); }
.absencetable th { font-size: var(--text-sm); color: var(--text-muted); font-weight: 600; }
/* Decision 0642: the top bar's Absence button, coloured while away or covering. */
.absencebutton.away { color: var(--text-warning); }
.absencebutton.covering { color: var(--text-accent); }
.absenceticks { display: flex; flex-wrap: wrap; gap: 8px 22px; margin: 4px 0 10px; }
.absencetick { display: inline-flex; align-items: center; gap: 8px; }
.agenttable { width: 100%; border-collapse: collapse; }
.agenttable th, .agenttable td { text-align: left; padding: 8px 8px 8px 0; vertical-align: top; border-bottom: 0.5px solid var(--border); }
.agentname { font-weight: 700; color: var(--text-accent); }
/* Decision 0627: an agent's name opens its own page. */
.agentopen { background: none; border: 0; padding: 0; font: inherit; color: var(--text-accent); cursor: pointer; text-align: left; }
.agentopen:hover, .agentopen:focus-visible { text-decoration: underline; }
.agentsection h3 { margin: 0 0 8px; }
/* Decision 0629: a note's report opens in Documents. */
/* Decision 0631: a prepared action waiting for approval. */
.agentaction textarea { width: 100%; margin-top: 8px; }
/* Decision 0632: a supplier chaser, editable before it is sent. */
.agentchase input, .agentchase textarea { width: 100%; margin: 4px 0 8px; }
.agentchase label { display: block; margin-top: 6px; font-weight: 700; }
.agentchasemeta { display: grid; grid-template-columns: auto 1fr; gap: 2px 10px; margin: 8px 0 0; }
.agentchasemeta dt { color: var(--text-muted); }
.agentchasemeta dd { margin: 0; }
.agentactionresult { margin: 8px 0 0; }
.agentreportopen { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0 0 8px; }
/* Decision 0628: ready-made agents. */
.agentexamples { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; margin-top: 10px; }
.agentexample { border: 0.5px solid var(--border); border-radius: 6px; padding: 10px 12px; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.agentexample p { margin: 0; }
.agentexamplehead { display: flex; justify-content: space-between; align-items: flex-start; gap: 8px; }
.agentrunsgrid { display: grid; grid-template-columns: minmax(0, 300px) minmax(0, 1fr); gap: 16px; }
@media (max-width: 820px) { .agentrunsgrid { grid-template-columns: 1fr; } }
.agentrunlist { display: flex; flex-direction: column; border: 0.5px solid var(--border); border-radius: 6px; overflow: hidden; }
.agentrunpick { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 8px 10px; background: none; border: 0; border-bottom: 0.5px solid var(--border); font: inherit; color: inherit; cursor: pointer; text-align: left; }
.agentrunpick:last-child { border-bottom: 0; }
.agentrunpick.on { background: var(--bg-accent); }
.agentrundetail h4 { margin: 0 0 8px; }
.agentsteps { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 6px; }
.agentversion { border: 0.5px solid var(--border); border-radius: 6px; padding: 10px 12px; margin-bottom: 10px; }
.agentversion.current { border-color: var(--text-accent); }
.agentversionhead { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
.agentversionwords { margin: 0 0 6px; font-style: italic; }
.agentversionchanged { margin: 0 0 6px; }
.agentpeople, .agentevents { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 4px; }
.agentbuttons { flex-wrap: wrap; justify-content: flex-end; }
.agentconfirm { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.agentruns td { background: var(--surface-1); }
.agentform .cardhead { display: flex; justify-content: space-between; align-items: center; gap: 8px; flex-wrap: wrap; }
.agentform h3 { margin: 0; }
.agentorgs { display: flex; flex-wrap: wrap; gap: 6px 16px; }
.agentorg { display: inline-flex; align-items: center; gap: 6px; }
.agentwhen { display: flex; flex-wrap: wrap; gap: 0 16px; }
.agentwhen .dofield { min-width: 160px; }
#agents-note { margin: 10px 0; padding: 8px 12px; border-radius: 8px; background: var(--surface-1); border: 1px solid var(--border); }
.agentnotes h3 { margin: 0 0 2px; }
.agentnote { border-top: 0.5px solid var(--border); padding: 10px 0; }
/* Decision 0626: the AI summary on a note, marked as the AI's. */
.agentsummary { border: 1px dashed var(--border-accent, var(--border)); border-radius: 6px; background: var(--bg-accent, transparent); padding: 8px 12px; margin: 0 0 10px; }
.agentsummary p { margin: 0; }
.agentsummarytag { display: block; font-size: var(--text-sm); font-weight: 700; color: var(--text-accent, inherit); margin-bottom: 2px; }
.agentnotehead { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; flex-wrap: wrap; }
.agentnotename { font-weight: 700; margin-right: 8px; }
.agentnotetag { font-size: var(--text-sm); }
.agentnotedetail { margin-top: 8px; }
.agentreportwrap { overflow-x: auto; }
.agentreporttable { width: 100%; border-collapse: collapse; }
.agentreporttable th, .agentreporttable td { text-align: left; padding: 5px 10px 5px 0; border-bottom: 0.5px solid var(--border); white-space: nowrap; }
.agentreporttable .num { text-align: right; font-variant-numeric: tabular-nums; }
/* Agents, email — decision 0623. */
.agentto { margin-top: 2px; }
.agentstopnotice { margin-bottom: 12px; }
/* Agents, highlights — decision 0624. */
.agentreporttable tr.agenthighlight td { color: var(--text-danger); font-weight: 700; }
#agents-runnow-hint { margin: 6px 0 0; }
/* Agents, plain words — decision 0625. */
.agentdescribe { display: flex; flex-direction: column; gap: 6px; }
.agentdescribe textarea { width: 100%; font: inherit; }
.agentdescribe .dobuttons { justify-content: flex-start; }
.agentplan { margin-top: 12px; border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; background: var(--surface-1); }
.agentplan h4 { margin: 0 0 6px; }
.agentplanrows { display: flex; flex-direction: column; gap: 4px; }
.agentplanrow { display: grid; grid-template-columns: 90px minmax(0, 1fr); gap: 10px; }
.agentplanrow.missing span:last-child { color: var(--text-warning); }
.agentplantag { font-size: var(--text-sm); text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-accent); font-weight: 700; }
.agentrefusals { margin: 8px 0 0; padding-left: 18px; color: var(--text-warning); }
/* Decision 0634: the question, in words, in Edit steps. */
.agentquestion { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 2px; }
/* Decision 0636: the question builder in Edit steps. */
.agentqbuilder { display: flex; flex-direction: column; gap: 12px; }
.agentqfilters { display: flex; flex-direction: column; gap: 8px; }
.agentqrow { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.agentqrow select, .agentqvalue { min-width: 0; max-width: 100%; }
.agentqvalue { width: 160px; }
.agentqcurrency { width: 64px; text-transform: uppercase; }
.agentqvalues { display: flex; flex-wrap: wrap; gap: 8px; }
.agentqtried { display: flex; flex-direction: column; gap: 6px; }
.agentsteps { margin-top: 12px; }
.agentsteps > summary { cursor: pointer; font-weight: 700; }
/* Decision 0645: Goods Receipts. */
.receiptstable td .rmpill, .receiptfigures td .rmpill { margin: 0 4px 2px 0; }
.receiptscredit { margin-top: 8px; font-size: var(--text-sm); }
/* Decision 0651: where a CSV load goes. */
.receiptsprocess { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 4px 0 10px; }
/* Decision 0652: fixing a line at Matching. */
.receiptfix { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.receiptfixrow td { border-top: none; padding-top: 0; }
.receiptfix input.fixorder { width: 8em; flex: none; }
.receiptfix input.fixline { width: 4.5em; flex: none; }
.receiptfix input.fixqty { width: 6em; flex: none; }
.receiptfix input.fixunit { width: 5em; flex: none; text-transform: uppercase; }
#receipt-task { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.popout .receiptcorrected { white-space: normal; max-width: 11em; margin-left: auto; }
.receiptfix button { white-space: nowrap; }
.popout td.nowrap { white-space: nowrap; }
.receiptfigures .qtyinput { width: 7em; text-align: right; }
.receiptfigures td.num, .receiptfigures th.num, .receiptstable td.num, .receiptstable th.num { text-align: right; font-variant-numeric: tabular-nums; }
/* Decision 0646: a purchase order's receipts and returns. */
#po-receipts { margin-top: 14px; }
.pomovements { display: flex; flex-direction: column; gap: 4px; margin-top: 10px; font-size: var(--text-sm); }
.pomovement.cancelled span:first-child { text-decoration: line-through; }
/* Decision 0650: goods received not invoiced. */
.grnicontrols { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 8px 0 12px; }
.grnicontrols input[type="date"] { width: auto; }
.grnicontrols .actionlink { margin-left: auto; }
.grniover { color: var(--text-warning); }
.grnitiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; margin-bottom: 12px; }
.grnitile { display: flex; flex-direction: column; gap: 2px; padding: 10px 12px; border: 0.5px solid var(--border); border-radius: var(--radius); background: var(--surface-1); }
.grnitile b { font-size: var(--text-lg); }
.grnitable td.num, .grnitable th.num { text-align: right; font-variant-numeric: tabular-nums; }

/*
  Decision 0658: a receipt's pop-out, widened, with its Timeline and Chat
  in a panel on the right, as the invoice viewer has. The receipt scrolls
  on the left; the panel keeps the pop-out's height, its feed scrolls and
  the box to write in stays at the bottom. Stacked on a narrow screen.
*/
.popout.receiptpop {
  width: min(1440px, 96vw);
  height: min(900px, 90vh);
  max-height: 90vh;
  overflow: hidden;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(320px, 400px);
  gap: 0;
  padding: 0;
}
.popout.receiptpop .receiptmain { overflow: auto; padding: 16px; min-width: 0; }
.popout.receiptpop .receiptside {
  border-left: 0.5px solid var(--border);
  padding: 16px;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.receiptside .receipttimeline { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.receiptside .receipttimeline > h4 { margin-top: 0; }
.receiptside .receipttimeline .activityfeed { flex: 1 1 auto; min-height: 0; overflow: auto; }
.receiptside .receipttimeline .activityinput { flex: none; }
.receiptside .receipttimeline .collabbar { flex-wrap: wrap; }
/*
  Decision 0682 — what happened is a card (0675), laid out as in the
  Document viewer. The grid this panel gave its old dotted lines (a 12px
  column for the dot) outranked the card's own layout and squeezed its
  words into that 12px column, one word per line.
*/
@media (max-width: 900px) {
  .popout.receiptpop { display: block; height: auto; overflow: auto; }
  .popout.receiptpop .receiptside { border-left: none; border-top: 0.5px solid var(--border); }
}
/*
  Decision 0741: a receipt opened is a page of its own in the application's
  frame — the side menu, the top bar with the receipt's actions beside
  Back — as an invoice opened is. The receipt on the left; on the right its
  Attachments, Timeline / Chat and Supplier panel, the window's height and
  kept in view as the receipt scrolls, its box to write in at the bottom.
  Stacked on a narrow screen. The Supplier tab is laid out as Timeline /
  Chat's: the messages scroll, the box stays at the bottom.
*/
.receiptpage { display: grid; grid-template-columns: minmax(0, 1fr) minmax(340px, 440px); gap: 16px; align-items: start; }
.receiptpage > .receiptmain { min-width: 0; margin-bottom: 0; }
/* The window less the top bar above it, so its box to write in is in view from the start. */
.receiptpage > .receiptsidepanel { position: sticky; top: 16px; height: calc(100vh - 200px); min-height: 420px; display: flex; flex-direction: column; margin-bottom: 0; }
.receiptsidepanel > .receiptside { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.receiptview .receiptactions { display: flex; align-items: center; gap: 4px; }
.receiptmain .receiptcorrected { white-space: normal; max-width: 11em; margin-left: auto; }
.receiptmain td.nowrap { white-space: nowrap; }
/**
 * **An order's page splits 3:2, as the invoice viewer does — decision
 * 0760.** Asked for directly once the order gained a Document tab: "give
 * the document viewer equal width as that which we have configured in the
 * invoice viewer" (`#viewer .columns`, decision 0390). Goods receipts keep
 * their narrower side panel; only `.poview` is widened.
 */
@media (min-width: 901px) {
  .poview .receiptpage { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); }
}
/**
 * **An order laid out as the supplier network's is — decision 0766**
 * (0765): Seller, Buyer and Progress in a row, the header as read-only
 * fields beneath, and the lines and receipts as cards across the page.
 */
.poview .pocards { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.poview .pocards > .panel, .poview .pocard { margin-bottom: 0; }
.poview .porow3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; align-items: stretch; }
.poview .pocard .cardhead h3 { margin: 0; }
.poview .pocard > .sub { margin: 0 0 6px; }
.poview .pocard #po-receipts .cardhead { align-items: center; }
.poview .pocard #po-sentline, .poview .pocard #po-confirmedline { margin-top: 10px; }
.poview > .pocard { margin-top: 16px; }
.poview .pototals { margin: 10px 0 0 auto; width: 320px; font-size: var(--text-sm); }
.poview .pototals div { display: flex; justify-content: space-between; padding: 3px 0; }
.poview .pototals .grand { font-weight: 700; border-top: 0.5px solid var(--border); margin-top: 4px; padding-top: 6px; font-size: var(--text-base); }
@media (max-width: 1200px) { .poview .porow3 { grid-template-columns: minmax(0, 1fr); } }
/* Decision 0785: a goods receipt's line returned or rejected, tinted as on its PDF (0773) and in the network (0784). */
.grview tr.rcpwarn td { background: color-mix(in srgb, var(--bg-warning) 45%, transparent); }
/*
  The right panel ends where the Purchase order header ends (Dan, 13 October
  2026): it stretches to the cards' height and, with `contain: size`, adds
  none of its own, so a long Timeline scrolls inside it rather than growing
  the row. On a goods receipt the left column also holds What was received
  (decision 1066), so the panel, and the receipt's image, reach down to the
  end of the lines.
*/
@media (min-width: 901px) {
  .poview .receiptpage { align-items: stretch; }
  /*
    Decision 0787: the 420px includes the panel's padding and border, so a header shorter than the
    panel's content-box minimum no longer leaves the panel 9px lower than the cards.
  */
  .poview .receiptpage > .receiptsidepanel { position: static; height: auto; min-height: 420px; contain: size; box-sizing: border-box; }
}
@media (max-width: 900px) {
  .receiptpage { grid-template-columns: minmax(0, 1fr); }
  .receiptpage > .receiptsidepanel { position: static; height: 70vh; }
}
.receiptside .receiptsupplier > .suppliertab { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.receiptside .receiptsupplier .activityfeed { flex: 1 1 auto; min-height: 0; overflow: auto; }
.receiptside .receiptsupplier .activityinput { flex: none; margin-top: auto; }
/* Decision 0753: a purchase order opened, no conversation to have yet — the panel says why. */
.receiptside .posupplierempty { padding: 8px 2px; }
/* Decision 0659: Attachments and Timeline / Chat as tabs in the receipt's side panel, as in the invoice viewer. */
.receiptside .receipttabs { align-self: flex-start; margin-bottom: 12px; flex: none; }
.receiptside .receipttimeline[hidden], .receiptside .attachpane[hidden] { display: none; }
.receiptside .attachpane { flex: 1 1 auto; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: 10px; }
.receiptside .attachpane .attachview { flex: 1 1 auto; min-height: 240px; }
.receiptside .attachpane .attachview iframe { width: 100%; height: 100%; min-height: 240px; border: 0; }
/* Decision 0660: how many Conversations wait, on the menu item. */
.navbadge {
  flex: none; position: absolute; right: 28px; top: 50%; transform: translateY(-50%); min-width: 18px; height: 18px; padding: 0 6px; box-sizing: border-box;
  border-radius: 999px; background: var(--bg-danger); color: var(--text-danger);
  font-size: var(--text-sm); font-weight: 600; line-height: 18px; text-align: center;
}
.frame.collapsed .navbadge { transform: none; top: 6px; right: 6px; min-width: 8px; width: 8px; height: 8px; padding: 0; font-size: 0; }
/* Decision 0783: every tag beside another has the same space, as New messages and From the supplier lacked. */
.agentnotetag + .agentnotetag { margin-left: 6px; }
/* Decision 0660: about four conversations show; more scroll inside the section, so the tasks below stay in view. */
.receiptconversations .receiptconvlist { max-height: 23rem; overflow-y: auto; overscroll-behavior: contain; }
/* Dan: a small space between the Conversations section and the task search below it. */
#receiptconversations:not([hidden]),
#creditconfirms:not([hidden]) { margin-bottom: 16px; }
/* Decision 0661: Claim and Release in the task list, an icon with its word, as the Agents screen's actions. */
.taskactions .actionlink.act { min-height: 0; margin-right: 0; font-size: var(--text-sm); }
/* Decision 0662: a card's own actions in the invoice viewer, small, icon left of the word, as the Agents screen's. */
.actionlink.compactlink { flex-direction: row; padding: 4px 8px; gap: 4px; }
.actionlink.compactlink svg { width: 16px; height: 16px; }
/*
  Decision 0664: every compact action rests in the same softer colour and
  brightens on hover, primary or not (Dan: Upload receipts and Record a
  receipt "have a lighter font" and do not change when hovered, where
  Record a return does). The one to press is still told apart, by its
  bolder icon (`.actionlink.primary svg`, decision 0108).
*/
.actionlink.compactlink.primary { color: var(--text-secondary); }
.actionlink.compactlink:hover:not(:disabled) { color: var(--text-primary); }
/* Decision 0665: the last load's age sits under the file picker, inside the load card. */
#suppliers-freshness, .supplierhead .panel > .warn:last-child { margin-top: 10px; }
/*
  Decision 0666: the New supplier pop-out's Record supplier and Close rest
  in the softer colour and brighten on hover, as the compact actions do
  (0664); Record supplier keeps its bolder icon as the one to press (Dan).
*/
.newsupplierpop .actionlink.primary { color: var(--text-secondary); }
.newsupplierpop .actionlink:hover:not(:disabled) { color: var(--text-primary); }
/* Decision 0668: a person's actions together, at the end of the row. */
.personaction { width: 1%; white-space: nowrap; }

/*
  Decision 0670 — Approval Hierarchy's Supervisor overrides and Approval
  limit overrides, side by side at half the width each. The grid spaces
  them (0179), so the panels lose their own bottom margin, and both
  stretch to the taller one's height so their borders line up.
  Return Reasons' two lists use it too (0672, `.reasonpair`).
*/
.overridepair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.overridepair > .panel { margin-bottom: 0; }
.overridepair .editgrid select, .overridepair .editgrid input { min-width: 0; width: 100%; }
@media (max-width: 900px) { .overridepair { grid-template-columns: 1fr; } }

/*
  Decision 0671 — Stage Restrictions: a stage's Add (return target) sits
  on the right, beside "No return targets configured for this stage
  yet." or under the rows' Remove.
*/
.returntargetadd { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 6px 0; }
.returntargetadd > p { margin: 0; }
.returntargetadd .statebuttons { margin: 0; flex: none; }

/*
  Decision 0671 — a stage's three toggles (Offer Account Coding
  restrictions, Recheck the rule, Allow Discard) are full-width rows,
  checkbox on the right, with a line under each, whether or not the
  stage offers Account Coding restrictions. The last keeps its line,
  which sets them apart from Return targets below.
*/
.stagetoggles .assignmentrow:last-child { border-bottom: 0.5px solid var(--border); }

/*
  Decision 0675 — the Timeline / Chat's entries, from the mock-up Dan
  chose (option B), in both the Document viewer and a receipt's.

  **Day's colours in both themes.** Dan found Night's deeper tones too
  heavy here, so `.tlfeed` sets the colour tokens its entries use back
  to Day's values. The pane around them keeps its own theme.
*/
.tlfeed {
  --surface-1: #e8eff8;
  --surface-2: #ffffff;
  --text-primary: #121a26;
  --text-secondary: #4a5768;
  --text-muted: #7b8798;
  --border: rgba(18, 26, 38, 0.10);
  --border-strong: rgba(18, 26, 38, 0.18);
  --bg-accent: #e6f1fb;
  --text-accent: #185fa5;
  --border-accent: #378add;
  --bg-warning: #faeeda;
  --text-warning: #854f0b;
  --border-warning: #ef9f27;
  --bg-success: #eaf3de;
  --text-success: #3b6d11;
  --border-success: #639922;
  --bg-danger: #fbe4e1;
  --text-danger: #9c2b1f;
  --border-danger: #d64a36;
}

/* A system entry: a slim card, a coloured left edge, its symbol, a bold label, the words and the time. */
.tlcard {
  display: block;
  border-left: 3px solid var(--border-strong);
  border-radius: 6px;
  padding: 6px 10px;
  background: var(--surface-1);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}
.tlcard .tltop { display: flex; gap: 7px; align-items: flex-start; }
.tlcard .activityactionicon { width: 14px; height: 14px; margin-top: 1px; color: var(--text-secondary); }
.tlcard .tltext { flex: 1 1 auto; min-width: 0; line-height: 1.45; }
.tlcard .tllabel { color: var(--text-primary); font-weight: 600; }
.tlcard .tllabel::after { content: " · "; font-weight: 400; color: var(--text-muted); }
.tlcard .activitymsg { color: var(--text-secondary); }
.tlcard .activitywhen { color: var(--text-muted); }
.tlcard .activityactioncomment { color: var(--text-muted); margin: 2px 0 0 21px; }
.tlcard.tl-info { border-left-color: var(--border-accent); }
.tlcard.tl-info .activityactionicon { color: var(--text-accent); }
.tlcard.tl-warn { border-left-color: var(--border-warning); }
.tlcard.tl-warn .activityactionicon { color: var(--text-warning); }
.tlcard.tl-ok { border-left-color: var(--border-success); }
.tlcard.tl-ok .activityactionicon { color: var(--text-success); }
.tlcard.tl-bad { border-left-color: var(--border-danger); background: color-mix(in srgb, var(--bg-danger) 40%, var(--surface-1)); }
.tlcard.tl-bad .activityactionicon { color: var(--text-danger); }

/* A person's message: an outlined bubble; your own on the right, in the accent's tint. */
.tlchat .activitybubble { border: 0.5px solid var(--border-accent); background: color-mix(in srgb, var(--bg-accent) 35%, var(--surface-2)); color: var(--text-primary); }
.tlchat .activitywho { color: var(--text-primary); }
.tlchat .activitywho .activitywhen { font-weight: 400; }
.tlchat.mine { flex-direction: row-reverse; }
.tlchat.mine .activitybubble { background: var(--bg-accent); }

/*
  Decision 0676 — the Timeline's entries line up with the box to write
  in: the feed is inset by the same 14px as `.activityinput`, so a card's
  left edge meets the box's and its right edge the Post button's. The
  unreadable-document alert is the feed's first entry, so it scrolls
  with the rest; inside the feed the gap spaces it, not its own margin.
*/
/* Decision 0787: a gap under the line above the feed, so the first message does not touch it. */
.tlfeed { padding-inline: 14px; padding-top: 10px; }
.tlfeed > .systemalert { margin-bottom: 0; flex: 0 0 auto; color: var(--text-primary); }

/* Decision 0677 — an amount in the line table lines up on the right, as figures do. */
#lines .num .readonly,
#lines .moneyinput { text-align: right; font-variant-numeric: tabular-nums; }
/* Room for "£12,500.20", which the bare "12500.2" never needed: the table is fixed-layout, so its header sets the width. */
.linetable th.money { width: 8.5em; }
#lines .num { white-space: nowrap; }

/*
  Decision 0678 — the Header Fields pop-out as a compact list. Dan found
  each row's separator line, the shaded box behind each value, and the
  space between rows wasted room. A read-only value is plain text on the
  right; a field the person may edit keeps its input box, so it still
  reads as editable. The rows sit close, with no lines between them.
*/
.hffields .kf { border-top: none; padding: 5px 0; }
.popout .hffields .kf + .kf { margin-top: 0; }
.hffields .kf .readonly {
  background: transparent;
  min-height: 0;
  padding: 0;
  border-radius: 0;
  color: var(--text-primary);
}
/* Dan: "Expand the height to fit all fields on the screen." Up to the window's height, not 80%; it scrolls only past that. */
.popout.hfpopout { max-height: calc(100vh - 32px); }
/* Decision 0721 — the Supplier tab and Ask supplier. */
.suppliertab .supplierto { display: flex; align-items: center; gap: 6px; padding: 8px 14px; border-bottom: 0.5px solid var(--border); flex: 0 0 auto; }
.suppliertab .supplierto svg { width: 16px; height: 16px; flex: 0 0 auto; }
.supplieremailed { margin-top: 4px; font-size: var(--text-sm); color: var(--text-secondary); }
.supplieremailed.warn { color: var(--text-warning); }
.popout.asksupplier { width: min(560px, 100%); }
/* The question box as wide as the pop-out. Not `.askquestion`, which is Help's question bubble (0519, max 85%). */
.asksupplier textarea.asksupplierquestion { width: 100%; min-height: 96px; }
.asklines { display: flex; flex-wrap: wrap; gap: 6px 12px; }
.asklines .askline { display: inline-flex; align-items: center; gap: 4px; }
.supplierquery .reasonline-row { color: var(--text-primary); }
.suppliertab .activitybody { white-space: pre-line; }

/* Credit notes — decision 0731: AP Setup's tab, Credits to confirm on Tasks, and the viewer. */
.creditsetup .creditq { padding: 14px 0; border-top: 0.5px solid var(--border); }
.creditsetup .creditq:first-of-type { border-top: none; }
.creditsetup .creditqtitle { display: block; margin-bottom: 6px; }
.creditsetup .creditfixed { background: var(--surface-1); border-radius: var(--radius); padding: 10px 12px; margin: 0; }
.creditsetup .creditteam { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.creditsetup .creditteam select { width: auto; min-width: 220px; }
/* Decision 0778: which invoices a supplier may credit in the network. */
.creditsetup .cnstatusblock { margin: 12px 0 0 26px; }
.creditsetup .cnstatuses { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px 18px; margin: 8px 0 0; }
.creditsetup .cnstatuses label { display: flex; gap: 8px; align-items: center; font-size: var(--text-sm); }
.creditsetup .cnstatusblock p { margin: 8px 0 0; }
@media (max-width: 560px) { .creditsetup .cnstatuses { grid-template-columns: 1fr 1fr; } }
.creditconfirm .dobuttons { justify-content: flex-end; flex-wrap: wrap; }
.dobuttons input.creditnumber { width: 170px; flex: none; min-height: 32px; padding: 4px 10px; }
.creditpanel .dobuttons { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 8px; }
.creditpanel.waiting { border-left: 3px solid var(--border-warning, var(--border-strong)); }
/* The panel keeps Day's colours in Night (reasonline); so does the box in it (Dan, 11 October: dark text on a dark box). */
.creditpanel input.creditnumber { background: #ffffff; color: #121a26; border: 0.5px solid #b4bfcc; }
.creditpanel input.creditnumber::placeholder { color: #6b7686; }

/* Choose the line columns — decision 0734. */
.lineheadactions { display: flex; align-items: center; gap: 10px; margin-left: auto; }
.linecolumnpicker .linecolumnlist { min-width: 240px; display: grid; gap: 4px 24px; padding: 12px 14px; }
.linecolumnpicker .linecolumnlist.two { grid-template-columns: 1fr 1fr; min-width: 520px; }
.linecolgroup { display: flex; flex-direction: column; gap: 6px; }
.linecolgroup label { display: flex; align-items: center; gap: 6px; font-size: var(--text-sm); white-space: nowrap; }
.linecolhead { font-size: var(--text-sm); font-weight: 700; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 2px; }
.linecolumnpicker summary svg { width: 16px; height: 16px; }
.linecolfoot .linky { white-space: nowrap; }
.linecolfoot { grid-column: 1 / -1; display: flex; justify-content: space-between; gap: 12px; border-top: 0.5px solid var(--border); margin-top: 6px; padding-top: 8px; font-size: var(--text-sm); color: var(--text-muted); }
.linetable th.lf-linetotal { width: 8.5em; }
.linetable th.lf-bt-154 { width: 18em; }
.linetable th.lf-bt-132, .linetable th.lf-bt-127 { width: 7em; }
.linetable td.linetotalcell { color: var(--text-secondary); white-space: nowrap; text-align: right; padding-right: 12px; }
.linetable th.linematchcol, .linetable td.linematchcol { width: 6.5em; text-align: center; white-space: nowrap; }
.linematchyes { color: var(--text-success); font-weight: 700; }
.linematchno { color: var(--text-danger); font-weight: 700; }

/* Decision 0735: the line table never spills out of its card; long text on a second row. */
.linetablewrap { overflow-x: auto; }
.linetable { min-width: 100%; }
.linetable th.lf-bt-153 { width: 16em; }
.linetable th.lf-bt-151 { width: 9em; }
.linetable tr.lineprimary td { padding-bottom: 4px; }
.linetable tr.linesecond td { padding-top: 0; padding-bottom: 10px; }
.linetable tr.linesecond + tr.lineprimary td { border-top: 0.5px solid var(--border); padding-top: 10px; }
.linetable .linesub { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; max-width: 900px; }
.linetable .linesub.n2 { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.linetable .linesubfield label { display: block; font-size: var(--text-sm); color: var(--text-muted); margin: 0 0 2px 2px; }
.linetable .linesubfield input, .linetable .linesubfield .readonly { width: 100%; box-sizing: border-box; }
/* Dan, 12 October: a read-only line value is as tall as an editable one beside it (Line note beside Item description, Order line among the inputs). */
.linetable input:not([type="checkbox"]):not([type="radio"]), .linetable select, .linetable .readonly { box-sizing: border-box; min-height: 40px; }
.linetable .readonly { display: flex; align-items: center; padding: 8px 11px; }

  /* A purchase order held at Checking — decision 0750: the lines Checking found something on, and suppliers to choose. */
  #poin-lines tr.poinflagged td { background: var(--bg-warning); }
  .poinsuppliers { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; margin-top: 4px; }
  .poinsuppliers .linkish { background: none; border: 0; padding: 0; color: var(--text-accent); cursor: pointer; font: inherit; text-align: left; }
  .poinsuppliers .linkish:hover { text-decoration: underline; }

/* Decision 0759: Orders to suppliers, on Process routes — its ticks, and who orders go to. */
.oocheck { display: flex; align-items: flex-start; gap: 8px; margin: 8px 0; cursor: pointer; }
.oocheck input { margin-top: 3px; }
.oowho { margin: 4px 0 8px 18px; padding: 0; }
.oowho li { margin: 2px 0; }

/* Decision 0760: a purchase order's Document tab — the PDF, with its downloads above it. */
.podoc { display: flex; flex-direction: column; gap: 8px; flex: 1 1 auto; min-height: 0; }
.podoc[hidden] { display: none; }
/*
  Decision 0767: the frame fills the height its panel has and no more.
  Beside the order's cards (0765, 0766) the panel ends with the header card,
  and a 480px floor spilled the PDF out of its bottom. The floor stays where
  the page stacks and the panel has no height of its own.
*/
.podocframe { flex: 1 1 auto; width: 100%; min-height: 0; border: 0.5px solid var(--border); border-radius: var(--radius); background: var(--surface-2); }
@media (max-width: 1000px) { .podocframe { min-height: 480px; } }
/* Four tabs on an order's panel: each kept on one line. */
.poview .receipttabs .doctab { padding: 6px 10px; white-space: nowrap; }
/* Decision 0773: a goods receipt has four tabs too, and the Document; it splits 3:2, as an order's page does (0760). */
.receiptview .receipttabs .doctab { padding: 6px 10px; white-space: nowrap; }
@media (min-width: 901px) {
  .receiptview .receiptpage { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); }
}

/**
 * **A supplier opened is a page in the frame — decision 0812**, as a goods
 * receipt (0741) and an order are: the record in three cards, Payment and
 * matching, the layouts learned; the supplier portal on the right, kept in
 * view, only where there is one to show.
 */
.supplierview .supplieralert { margin-bottom: 12px; }
.supplierpage { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; align-items: start; }
.supplierpage.withportal { grid-template-columns: minmax(0, 3fr) minmax(320px, 2fr); }
.supplierpage > .suppliermain { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.supplierpage .suppliercard { margin-bottom: 0; }
.supplierpage .suppliercards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
.supplierpage .suppliercards .editgrid { grid-template-columns: minmax(0, 1fr); }
.supplierpage .suppliersidepanel { position: sticky; top: 16px; max-height: calc(100vh - 120px); overflow: auto; margin-bottom: 0; }
.supplierpage .suppliersidepanel[hidden] { display: none; }
.supplierpage .suppliersidepanel .supplierportal { margin-top: 0; }
.supplierpage .portalinvite, .supplierpage .portaledit { max-width: none; }
.supplierview .statebuttons { display: flex; align-items: center; gap: 4px; }
@media (max-width: 1100px) {
  .supplierpage .suppliercards { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 900px) {
  .supplierpage.withportal { grid-template-columns: minmax(0, 1fr); }
  .supplierpage .suppliersidepanel { position: static; max-height: none; }
}

/**
 * **AP Analytics' cards, drilled into — decision 0814.** A figure, a ring or a
 * tile that opens the records behind it looks and answers as every clickable
 * row on the screen does.
 */
.drillline.clickable, .drillring.clickable, .grnitile.clickable { cursor: pointer; border-radius: 6px; }
.drillline.clickable:hover, .drillline.clickable:focus-visible { color: var(--text-primary); background: var(--surface-1); }
.drillring.clickable:hover, .drillring.clickable:focus-visible { background: var(--surface-1); }
.grnitile.clickable:hover, .grnitile.clickable:focus-visible { border-color: var(--text-accent); background: var(--surface-1); }
.drillline.clickable { padding: 1px 4px; margin-left: -4px; }

/**
 * **A heading that sorts — decision 0817.** The heading itself is the
 * button, text and arrow; the sorted one bold, its arrow in the accent;
 * the others show a faint arrow on hover or focus. `th.num` keeps a
 * figure's heading on the right.
 */
th:has(> .sorthead) { padding: 0; }
.sorthead { all: unset; box-sizing: border-box; display: inline-flex; align-items: center; gap: 4px; width: 100%; padding: 8px; cursor: pointer; border-radius: 6px; color: inherit; font: inherit; }
th.num > .sorthead { justify-content: flex-end; }
.sorthead:hover, .sorthead:focus-visible { color: var(--text-primary); background: var(--surface-2); }
.sorthead:focus-visible { outline: 2px solid var(--text-accent); outline-offset: -2px; }
.sorthead .sortarrow { font-size: 0.75em; width: 1em; opacity: 0; }
.sorthead .sortarrow[data-dir="asc"]::after { content: "\25B2"; }
.sorthead .sortarrow[data-dir="desc"]::after { content: "\25BC"; }
.sorthead:hover .sortarrow, .sorthead:focus-visible .sortarrow { opacity: 0.45; }
th[aria-sort] > .sorthead { color: var(--text-primary); font-weight: 600; }
th[aria-sort] > .sorthead .sortarrow { opacity: 1; color: var(--text-accent); }

/* Decision 0830: Email out's settings, in four short groups, and Send again's reason. */
.eohead { margin: 14px 0 2px; font-size: var(--text-sm); font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
.eoagain { display: flex; gap: 6px; align-items: center; }
.eoagain input { min-width: 220px; }
/* Decision 0831: what Confirm receipt does, under its box. */
.eohint { margin: 2px 0 0 26px; }

/* Decision 0833: part 5 names its layout in a line, opening part 3; Send a test opens a small pop-out. */
.dolaid { display: flex; justify-content: space-between; align-items: center; gap: 12px; border: 0.5px dashed var(--border-strong); border-radius: 8px; padding: 6px 10px; margin: 8px 0 10px; font-size: var(--text-sm); }
.popout.dotestpop { max-width: 640px; }

/* Decision 0854: the original's fingerprint on the Timeline, and Check it. */
.activityfingerprint { font-variant-numeric: tabular-nums; }
.activityfingerprint .linkbtn { background: none; border: 0; padding: 0; color: var(--text-accent); font: inherit; cursor: pointer; text-decoration: underline; }
.activityfingerprint .linkbtn:disabled { opacity: .6; cursor: default; }
.activityfingerprint .fingerprintbad { color: var(--text-danger); font-weight: 600; }

/* Decision 0858: a recovery copy, across the top of every screen. */
.licencebanner.recoverybanner { background: var(--bg-accent); color: var(--text-accent); border-color: var(--border-accent); }

/*
  **Rule sets no stage uses — decision 0870.** The rules a set holds, listed under its count on
  the Rules screen and in the pop-out that asks before it is removed.
*/
.rulelist {
  margin: 4px 0 0;
  padding-left: 18px;
}
.rulelist li {
  margin: 2px 0;
}

/* Decision 0873: AP Setup › New suppliers, what happens when an invoice makes a supplier. */
.newsupplierssteps {
  margin: 12px 0 0;
  padding-left: 18px;
}
.newsupplierssteps li {
  margin: 4px 0;
}

/* Decision 0876: a team's pop-out says what uses it, or that nothing does and it can be removed. */
.teamusage:empty { display: none; }
.teamusage { margin-top: 10px; }
.teaminuse {
  border: 0.5px solid var(--border-warning);
  background: var(--bg-warning);
  color: var(--text-primary);
  border-radius: 8px;
  padding: 10px 12px;
  font-size: 13px;
}
.teaminuse strong { color: var(--text-warning); }

/* Decision 0879: an org's form, in three groups, each field with its help and what is wrong with it. */
.formsection {
  margin: 18px 0 8px;
  padding-top: 10px;
  border-top: 0.5px solid var(--border);
  font-size: 12px;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
.fieldwrap { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.fieldhelp { font-size: 12px; color: var(--text-muted); }
.fieldhelp:empty { display: none; }
.fieldbad { border-color: var(--border-danger) !important; }
.fieldbadtext { color: var(--severity-danger-text, #f28b82); }
.fieldwarn { color: var(--text-warning); }

/* Decision 0881: the request page's company section, a field's hint, and a hint saying what is wrong. */
#start .section {
  margin: 18px 0 6px;
  padding-top: 10px;
  border-top: 0.5px solid var(--border);
  font-size: 12px;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
#start .hint { font-size: 12px; color: var(--text-muted); margin: 3px 0 0; }
#start .hint:empty { display: none; }
#start .hint.bad { color: var(--severity-danger-text, #f28b82); }

/* Decision 0888: the request page's "Start with sample data", a box with its explanation beside it. */
.startcheck { display: flex; gap: 10px; align-items: flex-start; margin-top: var(--gap-loose); padding: 12px 14px; border: 0.5px solid var(--border-accent); background: var(--bg-accent); border-radius: 10px; cursor: pointer; }
.startcheck input { margin-top: 3px; flex: none; }
.startcheck .hint { display: block; margin-top: 2px; }

/* Decision 0888: Configuration › Environment › Sample data. */
.envsamplecounts { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; margin: 12px 0; }
.envsamplecount { background: var(--surface-2); border: 0.5px solid var(--border); border-radius: 8px; padding: 8px 10px; font-size: var(--text-sm); color: var(--text-muted); }
.envsamplecount b { display: block; font-size: 18px; color: var(--text-primary); }
.envsamplehead { margin: 16px 0 6px; font-size: var(--text-sm); text-transform: uppercase; letter-spacing: .05em; color: var(--text-secondary); }
.envsampleuses { border: 0.5px solid var(--border-warning); background: var(--bg-warning); border-radius: 8px; padding: 10px 12px; margin: 10px 0; font-size: var(--text-sm); }
.envsampleuses strong { color: var(--text-warning); }
/* Decision 0974 — the sample's test guides. */
.envguides { display: grid; grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); gap: 12px; margin: 8px 0 4px; }
/* Decision 1047 (Dan): never shorter than its thumbnail, so a long card's words and links stay clear of it. */
.envguide { min-height: 164px; display: grid; grid-template-columns: auto minmax(0, 1fr) auto; grid-template-rows: 1fr auto; column-gap: 12px; row-gap: 8px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface-2); padding: 12px 14px; }
.envguidenum { grid-column: 1; grid-row: 1 / span 2; width: 30px; height: 30px; border-radius: 50%; background: var(--bg-accent); color: var(--text-accent); font-weight: 700; display: flex; align-items: center; justify-content: center; }
.envguidebody { grid-column: 2; grid-row: 1; min-width: 0; }
.envguidetitle { font-weight: 700; }
.envguidedesc { font-size: var(--text-sm); color: var(--text-secondary); margin: 2px 0 4px; }
.envguidemeta { font-size: var(--text-sm); color: var(--text-secondary); }
/* Decision 1046: wrapping, so a guide with two files (17) keeps its links clear of the thumbnail. */
.envguideactions { grid-column: 2; grid-row: 2; display: flex; flex-wrap: wrap; gap: 6px 18px; min-width: 0; }
.envguideact { display: inline-flex; align-items: center; gap: 6px; font-size: var(--text-sm); text-decoration: none; color: var(--text-accent); white-space: nowrap; }
.envguideact svg { width: 16px; height: 16px; }
/* Decision 0985 — the guide's first page, to the right of its text. */
.envguidethumb { grid-column: 3; grid-row: 1 / span 2; display: block; width: 96px; align-self: start; }
.envguidethumb img { display: block; width: 100%; aspect-ratio: 210 / 297; object-fit: cover; object-position: top; border: 1px solid var(--border); border-radius: 4px; background: #fff; box-shadow: 0 1px 3px rgba(18, 26, 38, 0.12); }
@media (max-width: 480px) { .envguides { grid-template-columns: 1fr; } .envguidethumb { width: 72px; } }
@media (max-width: 800px) { .envsamplecounts { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/*
  **Supplier Performance: one filter row, and cards no deeper than ten rows — decision 0893.**
  Dan: "the cards get very long and the page gets very deep." The row spans the tab; the list
  cards sit two to a row; a card's list scrolls inside it, under its title, with how many are
  shown and its own Show all beneath.
*/
.dashflow > .panel.supplierfilter {
  flex: 1 1 100%;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 18px;
  padding: 10px 12px;
}
/* Decision 0894 (Dan): the search across the whole row, Show and Currency on the line beneath. */
.dashflow > .panel.supplierfilter > .suppliersearch {
  flex: 1 1 100%; width: 100%; max-width: 100%; margin: 0; box-sizing: border-box;
  padding: 6px 8px; border: 1px solid var(--border-strong); border-radius: 6px;
  background: var(--surface-2); color: var(--text-primary); font: inherit; font-size: var(--text-sm);
}
.seglabel { display: inline-flex; align-items: center; gap: 6px; font-size: var(--text-sm); color: var(--text-secondary); }
.seg { display: inline-flex; border: 1px solid var(--border-strong); border-radius: 6px; overflow: hidden; }
.seg > button { border: 0; background: none; padding: 5px 10px; font: inherit; font-size: var(--text-sm); color: var(--text-secondary); cursor: pointer; }
.seg > button + button { border-left: 1px solid var(--border-strong); }
.seg > button.on { background: var(--bg-accent); color: var(--text-accent); font-weight: 700; }
.dashflow > .panel.supplierlistcard { flex: 1 1 420px; max-height: 480px; }
.supplierlistcard .cardlist { overflow-y: auto; min-height: 0; flex: 1 1 auto; padding-right: 6px; scrollbar-gutter: stable; }
/* The table's own sideways scroll would hold its sticky header; the card's list scrolls both ways instead. */
.supplierlistcard .cardlist .tablewrap { overflow: visible; }
.supplierlistcard .cardlist thead th { position: sticky; top: 0; background: var(--surface-2); z-index: 1; }
.supplierlistcard .cardfoot {
  display: flex; justify-content: space-between; gap: 10px;
  border-top: 1px solid var(--border); margin-top: 8px; padding-top: 7px;
  font-size: var(--text-sm); color: var(--text-muted);
}
@media (max-width: 800px) { .dashflow > .panel.supplierlistcard { flex-basis: 100%; } }
/* Decision 0907: the state chosen on Goods Receipts' chart, above its list; ✕ clears it. */
.filterpill { display: inline-flex; gap: 6px; align-items: center; padding: 3px 6px 3px 10px; border-radius: 999px; background: var(--bg-accent); color: var(--text-accent); font-size: var(--text-sm); font-weight: 700; white-space: nowrap; }
.filterpill button { border: 0; background: none; color: inherit; cursor: pointer; font-size: var(--text-base); line-height: 1; padding: 0 4px; }
#receipts-notreceived p { margin: 0 0 8px; }

/* Decision 0917: Straight-through processing, under its ring. */
.stp .sublabel { font-size: var(--text-sm); color: var(--text-muted); margin: 14px 0 2px; }
.stp .stpfoot { font-size: var(--text-sm); margin-top: 4px; }
.stp .stpnote { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--border); }

/*
  Decision 0918: Show all is a pill at the top right of every AP Analytics list card, as Fraud
  Prevention's: the title, how many there are, then the pill. Fraud Prevention's open Documents (↗).
*/
.cardhead.listhead { justify-content: flex-start; align-items: baseline; flex-wrap: wrap; row-gap: 6px; }
/* Decision 0921: the title on one line; the period and the pill keep their size, and move under the
   title where the card is too narrow for all of them, rather than squeezing the title into a column.
   The title wraps only where it alone is wider than the card. */
.listhead > h3 { flex: 0 1 auto; min-width: 0; }
.listhead > .windowpick { flex: 0 0 auto; width: auto; max-width: 11em; }
.listhead > .cardpill { white-space: nowrap; }
.listhead > .windowpick { margin-left: auto; }
.listhead > .cardpill { flex: 0 0 auto; margin-left: auto; }
.listhead > .windowpick + .cardpill { margin-left: 0; }
.chip.cardpill:hover, .chip.fraudshowall:hover { color: var(--text-accent); border-color: var(--text-accent); }
.fraudshowall::after { content: " \2197"; }

/*
  Decision 0919 (and 0920): Operational and Financial Performance's list cards, as Supplier
  Performance's (0893): no deeper than ten rows, the list scrolling inside the card.
*/
.dashflow > .panel.listcard { flex: 1 1 420px; max-height: 480px; }
.listcard .cardlist { overflow-y: auto; min-height: 0; flex: 1 1 auto; padding-right: 6px; scrollbar-gutter: stable; }
.listcard .cardlist .tablewrap { overflow: visible; }
.listcard .cardlist thead th { position: sticky; top: 0; background: var(--surface-2); z-index: 1; }
@media (max-width: 800px) { .dashflow > .panel.listcard { flex-basis: 100%; } }

/*
  Decision 0922 (Dan): on every AP Analytics tab, at most four cards to a row, however wide the
  screen; fewer where a card would be narrower than 300px. A full-width list (Fraud Prevention's)
  stays full width.
*/
.analyticsflow > .panel.card-graphic,
.analyticsflow > .panel.listcard,
.analyticsflow > .panel.supplierlistcard {
  flex: 1 1 calc((100% - 3 * var(--dash-gap)) / 4);
  min-width: 300px;
  box-sizing: border-box;
}
@media (max-width: 800px) {
  .analyticsflow > .panel.card-graphic,
  .analyticsflow > .panel.listcard,
  .analyticsflow > .panel.supplierlistcard { flex-basis: 100%; min-width: 0; }
}
/* Decision 0922: Open tasks names the person shown, above the key, in place of its drop-down. */
.opentasks-name { font-weight: 600; font-size: var(--text-sm); color: var(--text-primary); }

/*
  Decision 0927 (Dan): Credits to confirm on Tasks no deeper than about five credits; the rest
  scroll inside the card, so the task list is not pushed down the page. The heading and its line
  stay put.
*/
.creditconfirms .receiptconvlist {
  max-height: 25rem;
  overflow-y: auto;
  padding-right: 6px;
  scrollbar-gutter: stable;
  border-bottom: 0.5px solid var(--border);
}

/*
  Decision 0931 (Dan): Purchase Orders' "Waiting in the Purchase Orders process" no deeper than
  about ten orders, as Credits to confirm on Tasks (0927); the rest scroll inside the card, its
  column headings staying in view. In Loadtest Ltd, 200 waiting made the card 19,000px deep.
*/
#po-waiting .tablewrap {
  max-height: 25rem;
  overflow-y: auto;
  scrollbar-gutter: stable;
}
#po-waiting thead th { position: sticky; top: 0; background: var(--surface-2); z-index: 1; }
/* Decision 0935: ERP Export's past exports, no deeper than the waiting orders (0931). */
.erphistory .erphistorywrap {
  max-height: 25rem;
  overflow-y: auto;
  scrollbar-gutter: stable;
}
.erphistory .erphistorywrap thead th { position: sticky; top: 0; background: var(--surface-2); z-index: 1; }

/* Decisions 0940 and 0941: AP Analytics' "Figures as at" and Refresh, at the right end of the tab bar. */
.tabbar .analyticsasat { display: flex; align-items: center; gap: 0.6rem; margin-left: auto; padding: 0 0.5rem 0 1rem; white-space: nowrap; }

/*
  Decision 0943 (Dan): the dashboard wraps after four cards, never five to a row ("5 on one page is
  too cramped"), as AP Analytics does (0922). The list card keeps its row to itself.
*/
.dashboardflow > .panel.card-narrow,
.dashboardflow > .panel.card-graphic {
  flex: 1 1 calc((100% - 3 * var(--dash-gap)) / 4);
  box-sizing: border-box;
}
@media (max-width: 800px) {
  .dashboardflow > .panel.card-narrow,
  .dashboardflow > .panel.card-graphic { flex-basis: 100%; min-width: 0; }
}
/*
  Decision 0944 (Dan): a chart sits at the top of its card, under its title and line, not pushed to
  the bottom. A graphic card's heading, line and chart follow each other from the top; a card the
  row makes taller has its room below. Number tiles keep their figure at the foot, as before.
*/
.dashboardflow > .panel.card-graphic > .tilefg { justify-content: flex-start; }

/* Decision 0945: the stages a ring's "All others" holds, listed under it, a step in and quieter. */
.donutkey.donutsubkey { padding-left: 14px; font-size: var(--text-sm); }
.donutkey.donutsubkey .donutdot { opacity: 0.5; }

/*
  Decision 0946 (Dan: "this chart looks a bit of a mess now"): on the dashboard a ring sits above
  its legend, centred, and the legend has the card's whole width. Each row is one line: a name too
  long for it ends in "…" (whole on hover), and the counts are a column at the right, never against
  the name.
*/
.dashboardflow .donutwrap { flex-direction: column; align-items: stretch; gap: 14px; }
/* Decision 0949: and at the top with it (0944): the ring's own margin pinned it to the card's foot (0261). */
.dashboardflow .donutwrap { margin-top: 0; }
.dashboardflow .donutwrap > svg { align-self: center; }
.dashboardflow .donutkey > span:nth-child(2) { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dashboardflow .donutkey > span:nth-child(3) { flex: 0 0 auto; padding-left: 12px; font-variant-numeric: tabular-nums; }

/*
  Decision 0950: an AP Expert answer's Markdown, drawn. Paragraphs and lists with room between them;
  a table that scrolls sideways inside the bubble, its numbers set right.
*/
.chatbubble-answer > div > .chatpara { margin: 0 0 8px; }
.chatbubble-answer > div > .chatpara:last-child { margin-bottom: 0; }
.chatbubble-answer > div > .chatheading { margin: 4px 0 6px; font-weight: 600; }
.chatbubble-answer > div > .chatlist { margin: 0 0 8px; padding-left: 20px; }
.chatbubble-answer > div > .chatlist li { margin: 2px 0; }
.chattablewrap { max-width: 100%; overflow-x: auto; margin: 6px 0 10px; }
.chattable { border-collapse: collapse; font-size: var(--text-sm); white-space: nowrap; }
.chattable th, .chattable td { padding: 5px 10px; border-bottom: 1px solid var(--border); text-align: left; }
.chattable th { color: var(--text-secondary); font-weight: 600; }
.chattable .num { text-align: right; font-variant-numeric: tabular-nums; }
.chatbubble-answer code { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 0.92em; }

/* Decision 0952: a ready-made agent's card opens its pop-out. */
.agentexample.clickable { cursor: pointer; }
.agentexample.clickable:hover, .agentexample.clickable:focus-visible { border-color: var(--border-strong); }
.popout.agentexamplepop { width: min(640px, 92vw); }
.popout.agentexamplepop h4 { margin: 16px 0 6px; }
.popout.agentexamplepop .agentexamplewords { margin: 8px 0 0; }

/* Decision 0954: the found box takes a hover (its title says what it holds and how sure); a press on it still reaches the page beneath. */
.vhighlightlayer .vhover { pointer-events: auto; cursor: help; }

/* Decision 0955: what a follow-up was taken to mean, small, under the question at its right. */
.chattakenas { align-self: flex-end; margin: -6px 2px 0 0; font-style: italic; }

/* Decision 1001: Configuration › Notifications — a short table of stages, a days box each. */
.notifytable { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.notifytable th { text-align: left; color: var(--text-muted); font-weight: 500; padding: 6px 8px; border-bottom: 0.5px solid var(--border); white-space: nowrap; }
.notifytable td { padding: 6px 8px; border-bottom: 0.5px solid var(--border); vertical-align: middle; }
.daysstep { display: inline-flex; align-items: center; gap: 6px; }
.daysinput, .daysstep input.daysinput, .editgrid input.daysinput { width: 72px; min-width: 0; flex: none; }
.daysstep > span { white-space: nowrap; }
.notifyhint { color: var(--text-muted); font-size: var(--text-sm); margin: 0 0 8px; }

/* Decision 1003: the ladder's five columns scroll on a narrow window; due soon under it; the Dashboard's overdue split. */
.notifyscroll { overflow-x: auto; }
.notifydue { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 10px 0 0; font-size: var(--text-sm); }
.overduesplit { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 8px; }
@media (max-width: 640px) { .overduesplit { grid-template-columns: 1fr; } }
/* Decision 1004: a rule set's name over its rules on Notifications › Reasons. */
.notifyset { margin: 14px 0 4px; font-size: var(--text-sm); color: var(--text-secondary); }
.remindercheck { display: inline-flex; align-items: center; gap: 8px; }
.remindercheck input { width: auto; }
/* Dan, 1 November 2026: Reasons' rows close together, with no line between them; the confirm pop-out's list. */
#shell .notifytable.notifywaits td { border-bottom: none; padding: 3px 8px; }
#shell .notifytable.notifywaits th { padding-bottom: 4px; }
#shell .notifytable.notifywaits select, #shell .notifytable.notifywaits input { padding-top: 5px; padding-bottom: 5px; }
.notifyconfirm { max-width: 560px; }
.notifychanges { margin: 4px 0 0; padding-left: 18px; font-size: var(--text-sm); }
.notifychanges li { margin: 2px 0; }
/* The same columns in every Reasons table, so rule sets line up. */
#shell .notifytable.notifywaits { table-layout: fixed; min-width: 640px; }
#shell .notifytable.notifywaits th:nth-child(1) { width: 46%; }
#shell .notifytable.notifywaits th:nth-child(2) { width: 32%; }
#shell .notifytable.notifywaits select { width: 100%; }

/* Decision 1034/1035: payments from the ERP — the viewer's line, Documents' notice, and its popouts. */
.paymentpanel.waiting, .paynotice.waiting { border-left: 3px solid var(--border-warning, var(--border-strong)); }
.paynotice .reasonline-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.paynotice .statebuttons { margin-left: auto; display: flex; gap: 6px; }
.paypop { max-width: 560px; }
.paylistpop { max-width: 920px; width: 100%; }
.paylist { width: 100%; border-collapse: collapse; }
.paylist th, .paylist td { text-align: left; padding: 6px 8px; border-bottom: 0.5px solid var(--border); vertical-align: top; }
.paylist .payactions { white-space: nowrap; text-align: right; }
.paymatchresults { display: flex; flex-direction: column; gap: 4px; margin: 8px 0; }
.paymatchpick { text-align: left; padding: 6px 10px; border: 0.5px solid var(--border); border-radius: var(--radius); background: transparent; color: inherit; cursor: pointer; }
.paymatchpick:hover { border-color: var(--border-strong); }
/* Decision 1037: a remittance advice, opened from Documents. */
.remitpop { max-width: 760px; width: 100%; }
.paylist th.num, .paylist td.num { text-align: right; }
.remithead { display: flex; align-items: center; gap: 8px; margin: 2px 0 10px; }
.remitsends { margin-top: 12px; }
.remitsends ul { margin: 6px 0 0; padding-left: 18px; }
/* Decision 1039: Paid within terms on Financial Performance. */
.paidwithin-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
#paidwithin-card > .cardhead { flex-wrap: wrap; align-items: center; }
.paidwithin-controls { justify-content: flex-end; }
/* Not the page's full-width select: three short choices on one line, beside the title or under it. */
.paidwithin-pick { width: auto; flex: none; font-size: var(--text-sm); padding: 2px 6px; }
/* One column: a card a quarter of the row is too narrow for the chart and its figures side by side. */
.paidwithin-cols { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; margin-top: 8px; }
.paidwithin-side { display: flex; flex-direction: column; gap: 6px; }
.paidwithin-headline { font-size: var(--text-lg); font-weight: 600; }
.paidwithin-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.paidwithin-table td { padding: 3px 0; border-bottom: 0.5px solid var(--border); }
.paidwithin-table td.num { text-align: right; white-space: nowrap; }
.paidwithin-table .codetier { margin-left: 6px; font-weight: 600; }
.codetier.met { color: var(--text-success); }
.codetier.notyet { color: var(--text-warning); }
.paidwithin-codehead { margin-top: 6px; }

/*
  Decision 1041 (Dan, 4 November: "ensure all cards are of equal depth ... in the GRNI card we have capped
  depth, and introduced a built in scroll within the card. Could that be done for other cards?"): every AP
  Analytics card no deeper than the list cards (480px, 0919), so a row's cards end level; a card with more
  scrolls inside itself, its heading and choices staying at the top.
*/
.analyticsflow > .panel.card-graphic:not(.listcard):not(.supplierlistcard) { max-height: 480px; overflow-y: auto; scrollbar-gutter: stable; }
.analyticsflow > .panel.card-graphic:not(.listcard):not(.supplierlistcard) > .cardhead {
  position: sticky; top: -16px; margin-top: -16px; padding-top: 16px; background: var(--surface-2); z-index: 2;
}

/* Decision 1045: the database's size, of D1's 10 GB. */
.envdbsize { font-size: var(--text-lg); font-weight: 600; margin: 6px 0 8px; }
.envdbbar { height: 8px; border-radius: 999px; background: var(--surface-1); border: 0.5px solid var(--border); overflow: hidden; margin-bottom: 8px; }
.envdbbar > span { display: block; height: 100%; background: var(--severity-ok-text); }
.envdbbar.warn > span { background: var(--severity-warning-text); }
.envdbbar.bad > span { background: var(--severity-danger-text); }
/*
  Decision 1059: Save as I go. Save, or how autosave stands, with a small arrow beside it that chooses
  between the two. The status reads as a sentence, not a button to press: green once saved, amber when a
  save was refused.
*/
.savecontrol { position: relative; display: inline-flex; align-items: stretch; }
.savemodetoggle { display: flex; align-items: center; padding: 0 2px; min-height: 0; border-color: transparent; background: transparent; color: var(--text-secondary); }
.savemodetoggle:hover { color: var(--text-primary); }
.savemodetoggle svg { width: 14px; height: 14px; }
.actionlink.savestatus { min-width: 84px; }
.actionlink.savestatus.ok { color: var(--text-success); }
.actionlink.savestatus.warn { color: var(--text-warning); }
.savemodemenu { position: absolute; top: 100%; right: 0; z-index: 30; display: flex; flex-direction: column; min-width: 230px; padding: 4px; background: var(--surface-2); border: 0.5px solid var(--border-strong); border-radius: var(--radius); box-shadow: 0 6px 18px rgba(18, 26, 38, 0.14); }
.savemodeoption { display: flex; align-items: center; gap: 8px; padding: 8px 10px; min-height: 0; border-color: transparent; background: transparent; color: var(--text-primary); font-size: var(--text-sm); text-align: left; white-space: nowrap; }
.savemodeoption:hover { background: var(--surface-1); }
.savemodeoption.chosen { font-weight: 600; }
.savemodetick { display: inline-flex; width: 16px; }
.savemodetick svg { width: 16px; height: 16px; }
/*
  Decision 1060: More. Every screen's own controls (Day or Night, Organisation, Absence, Language, Help,
  Ask, Sign out) and the viewer's less used task actions, in a list under one button at the end of the
  bar, each button laid out as a row. The bar keeps what a page adds, so the heading keeps its width.
*/
.morewrap { position: relative; display: inline-flex; }
.morebutton.flagged { position: relative; }
.morebutton.flagged::after { content: ""; position: absolute; top: 7px; right: 16px; width: 8px; height: 8px; border-radius: 50%; background: var(--border-warning); box-shadow: 0 0 0 2px var(--surface-0); }
.moremenu { position: absolute; top: 100%; right: 0; z-index: 50; display: flex; flex-direction: column; min-width: 220px; padding: 4px; background: var(--surface-2); border: 0.5px solid var(--border-strong); border-radius: var(--radius); box-shadow: 0 8px 22px rgba(18, 26, 38, 0.16); }
.moremenu[hidden] { display: none; }
.moremenu .actionlink { flex-direction: row; justify-content: flex-start; gap: 10px; min-width: 0; width: 100%; padding: 8px 10px; border-radius: 6px; color: var(--text-primary); text-align: left; white-space: nowrap; }
.moremenu .actionlink:hover:not(:disabled) { background: var(--surface-1); }
.moremenu .actionlink svg { width: 18px; height: 18px; color: var(--text-secondary); flex-shrink: 0; }
.moremenu .actionlink .langbadge { flex-shrink: 0; }
.moremenu .actionlink[data-action="discard"] { color: var(--text-danger); }
.moremenu .actionlink[data-action="discard"] svg { color: var(--text-danger); }
.moresep { height: 0; border-top: 0.5px solid var(--border); margin: 4px 2px; }
/* The line beneath the heading on one line; Waiting and Owner as small labels beside each other. */
.topbar .sub { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.copyref { display: inline-flex; align-items: center; padding: 2px; min-height: 0; border: 0; background: transparent; color: var(--text-secondary); border-radius: 4px; }
.copyref:hover { color: var(--text-primary); }
.copyref.copied { color: var(--text-success); }
.copyref svg { width: 14px; height: 14px; }
.topbar .subhead { flex-direction: row; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.topbar .subhead > div { padding: 1px 9px; border: 0.5px solid var(--border); border-radius: 999px; background: var(--surface-1); line-height: 1.6; max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
