  /* ==========================================================================
     Fonts, served from this origin.

     This file used to open by pulling Roboto straight from Google's font
     service, which handed every visitor's IP address to a third party before
     a single pixel was drawn, and meant no Content-Security-Policy could
     honestly say `style-src 'self'`. The WOFF2 files below are the same
     Roboto and Roboto Mono, fetched once and kept in static/fonts/.

     They are the variable-weight files, which is why one file per subset
     covers 400 through 900 - the weights the templates ask for - and why
     `font-weight` is a range rather than a number. latin and latin-ext are
     the only subsets kept: the interface is English and the Cyrillic, Greek
     and Vietnamese cuts were never drawn from.

     font-display: swap is what Google's own CSS asked for, so text is
     readable in the fallback face while the WOFF2 is still arriving.
     ========================================================================== */

  @font-face {
    font-family: 'Roboto';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url('/static/fonts/roboto-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                   U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                   U+2212, U+2215, U+FEFF, U+FFFD;
  }
  @font-face {
    font-family: 'Roboto';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url('/static/fonts/roboto-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
                   U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
                   U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face {
    font-family: 'Roboto';
    font-style: italic;
    font-weight: 100 900;
    font-display: swap;
    src: url('/static/fonts/roboto-italic-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                   U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                   U+2212, U+2215, U+FEFF, U+FFFD;
  }
  @font-face {
    font-family: 'Roboto';
    font-style: italic;
    font-weight: 100 900;
    font-display: swap;
    src: url('/static/fonts/roboto-italic-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
                   U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
                   U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face {
    font-family: 'Roboto Mono';
    font-style: normal;
    font-weight: 100 700;
    font-display: swap;
    src: url('/static/fonts/roboto-mono-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                   U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                   U+2212, U+2215, U+FEFF, U+FFFD;
  }
  @font-face {
    font-family: 'Roboto Mono';
    font-style: normal;
    font-weight: 100 700;
    font-display: swap;
    src: url('/static/fonts/roboto-mono-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
                   U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
                   U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }


  /* ==========================================================================
     CountIDS — Material Design, light theme only.
     There are deliberately no dark-mode rules in this file: the site renders
     light whatever the operating system asks for. `color-scheme: light` below
     also pins native controls (selects, scrollbars, autofill) to light.
     ========================================================================== */

  :root {
    color-scheme: light;

    /* surfaces */
    --bg: #F8F9FA;                  /* Google grey 50 — app background */
    --surface: #FFFFFF;             /* cards, top bar, menus */
    --surface-sunken: #F8F9FA;      /* wells, table-row hover, summaries */
    --surface-sunken-alt: #F1F3F4;  /* Google grey 100 — disabled, neutral chip */

    /* lines */
    --border: #DADCE0;              /* Google grey 300 — dividers, outlines */
    --border-strong: #BDC1C6;       /* Google grey 400 — emphasised outline */

    /* type */
    --text: #202124;                /* Google grey 900 — primary text */
    --text-muted: #5F6368;          /* Google grey 700 — secondary text */
    --text-faint: #80868B;          /* Google grey 600 — tertiary text */

    /* brand accent (kept: CountIDS purple, not Google blue) */
    --accent: #7E3494;
    --accent-strong: #6B2C7E;       /* hover / pressed / on-tint text */
    --accent-soft: #F3E8F7;         /* purple-tinted container */
    --accent-soft-border: #E4CDEC;
    --accent-state: rgba(126, 52, 148, 0.08);  /* Material state layer */
    --accent-state-strong: rgba(126, 52, 148, 0.14);

    /* status */
    --success: #137333;
    --success-soft: #E6F4EA;
    --success-border: #CEEAD6;
    --warning: #B06000;
    --warning-soft: #FEF7E0;
    --warning-border: #FEEFC3;
    --danger: #C5221F;
    --danger-soft: #FCE8E6;
    --danger-border: #F6AEA9;

    /* Material elevation */
    --shadow: 0 1px 2px 0 rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15);
    --shadow-2: 0 1px 2px 0 rgba(60, 64, 67, 0.3), 0 2px 6px 2px rgba(60, 64, 67, 0.15);
  }

  * { box-sizing: border-box; }

  /* The browser's own [hidden] rule is a bare `display: none`, which any of
     the layout classes below (.stack, .row, .callout - all flex) silently
     beats. Scripts here hide and show panels with el.hidden, so it has to win. */
  [hidden] { display: none !important; }

  html, body {
    margin: 0;
    padding: 0;
    background: var(--bg);
    color: var(--text);
    font-family: 'Roboto', system-ui, -apple-system, sans-serif;
    font-size: 15px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
  }

  code, .mono, .ids-num, .fee, .doc-id {
    font-family: 'Roboto Mono', ui-monospace, 'SF Mono', monospace;
    font-variant-numeric: tabular-nums;
  }

  a { color: var(--accent); }
  a:hover { color: var(--accent-strong); }

  /* ---------- shell ---------- */

  .shell {
    max-width: 1040px;
    margin: 0 auto;
    padding: 0 28px 96px;
  }

  /* every page's footer - links plus the copyright line, quiet enough that
     it never competes with the screen above it */
  .site-footer {
    max-width: 1040px;
    margin: 0 auto;
    padding: 20px 28px 40px;
    text-align: center;
    color: var(--text-muted);
    font-size: 12.5px;
  }

  .site-footer-links a {
    color: var(--text-muted);
    margin: 0 10px;
    text-decoration: none;
  }
  .site-footer-links a:hover { color: var(--accent); text-decoration: underline; }


  .topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 10px 28px;
    background: var(--surface);
    box-shadow: var(--shadow);
    position: sticky;
    top: 0;
    z-index: 20;
  }

  .brand {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: none;
  }

  .brand-logo { height: 42px; width: auto; display: block; }

  /* the whole brand is one link; it must not read as body copy */
  .brand-link {
    text-decoration: none;
    color: inherit;
    display: flex;
    align-items: center;
    gap: 10px;
  }

  .brand-name {
    font-size: 30px;
    line-height: 1;
    letter-spacing: -0.01em;
    white-space: nowrap;
  }
  .brand-name .count { font-family: 'Roboto', sans-serif; font-weight: 400; color: #000; }
  .brand-name .ids { font-family: 'Roboto', sans-serif; font-weight: 900; color: #7E3494; }

  .brand .mark {
    font-weight: 500;
    font-size: 17px;
    letter-spacing: 0;
  }

  .brand .sub {
    font-size: 12.5px;
    color: var(--text-muted);
  }

  /* top-bar actions — plain Material text buttons, no container */

  /* the signed-in address, sitting in the nav at a button's own rhythm */
  .screen-nav .user-email {
    padding: 7px 11px;
    font-size: 12.5px;
    color: var(--text-faint);
  }
  .screen-nav form { margin: 0; }

  .screen-nav {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 4px;
    background: transparent;
    border: none;
    border-radius: 0;
    padding: 0;
  }

  .screen-nav button {
    appearance: none;
    border: none;
    background: transparent;
    color: var(--text-muted);
    font: inherit;
    font-size: 13px;
    font-weight: 500;
    letter-spacing: 0.01em;
    padding: 6px 12px;
    border-radius: 4px;
    cursor: pointer;
    white-space: nowrap;
    transition: background-color 0.15s ease, color 0.15s ease;
  }

  .screen-nav button:hover { color: var(--accent); background: var(--accent-state); }

  .screen-nav button.active {
    background: var(--accent-soft);
    color: var(--accent-strong);
    box-shadow: none;
  }

  /* the top bar's "Applications" link and "Log out" submit button share one
     Material text-button look, so they line up whichever element they are */
  .screen-nav a.btn-ghost,
  .screen-nav button.btn-ghost {
    display: inline-flex;
    align-items: center;
    font: inherit;
    font-size: 13px;
    font-weight: 500;
    letter-spacing: 0.01em;
    color: var(--text-muted);
    background: transparent;
    border: none;
    border-radius: 4px;
    padding: 6px 12px;
    cursor: pointer;
    text-decoration: none;
    transition: background-color 0.15s ease, color 0.15s ease;
  }
  .screen-nav a.btn-ghost:hover,
  .screen-nav button.btn-ghost:hover {
    color: var(--accent);
    background: var(--accent-state);
    text-decoration: none;
  }

  button:focus-visible,
  a:focus-visible,
  summary:focus-visible,
  [tabindex]:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }

  .screen {
    display: none;
    padding-top: 36px;
    animation: fade 0.15s ease;
  }
  .screen.active { display: block; }

  @media (prefers-reduced-motion: no-preference) {
    @keyframes fade { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: none; } }
  }

  .screen-label {
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin: 0 0 6px;
  }

  /* ---------- the application heading (_app_header.html) ----------
     Replaces the uppercase .screen-label the application screens used to
     open with: the number is the one thing on the page that says which
     file you are in, so it is bold and a step larger than a label. Under
     it the applicant and the title, and for some screens a note saying
     which screen this is - both quieter than the number, because the
     number is what the reader came here to recognise. */

  .app-header { margin: 0 0 8px; }
  /* The header and the pencil that opens the form under it. flex-end so the
     pencil sits on the header's bottom line - the applicant and title, or
     the number when there is neither. */
  .app-header-row { display: flex; align-items: flex-end; gap: 6px; }
  .pencil-btn {
    appearance: none;
    background: none;
    border: 0;
    padding: 2px;
    margin: 0 0 10px;
    color: var(--text-faint);
    cursor: pointer;
    display: inline-flex;
    line-height: 1;
    border-radius: 4px;
  }
  .pencil-btn:hover, .pencil-btn:focus-visible { color: var(--accent); }
  .icon-link {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 13px;
    color: var(--text-muted);
    text-decoration: none;
  }
  .icon-link:hover { color: var(--accent); }
  .app-number {
    margin: 0;
    font-size: 17px;
    font-weight: 600;
    color: var(--text);
  }
  .app-identity {
    margin: 2px 0 0;
    font-size: 13px;
    color: var(--text-muted);
    overflow-wrap: break-word;
  }
  /* The same uppercase label the screens used to open with, kept for the
     screens whose own heading does not say which of them it is. */
  .app-note {
    margin: 2px 0 0;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
  }

  /* Breadcrumbs under the topbar: the page itself last and unlinked. */
  .breadcrumbs ol {
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    gap: 4px 6px;
    margin: 0 0 12px;
    padding: 0;
    font-size: 12.5px;
    color: var(--text-muted);
  }
  .breadcrumbs li + li::before { content: "›"; margin-right: 6px; color: var(--text-faint); }
  .breadcrumbs a { color: var(--accent); text-decoration: none; }
  .breadcrumbs a:hover { text-decoration: underline; }

  h1 {
    font-size: 24px;
    font-weight: 400;
    letter-spacing: 0;
    color: var(--text);
    margin: 0 0 28px;
    text-wrap: balance;
    overflow-wrap: break-word;
  }

  h2 {
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin: 0 0 14px;
  }

  /* ---------- cards ---------- */

  .card {
    background: var(--surface);
    border: none;
    border-radius: 8px;
    padding: 24px 26px;
    box-shadow: var(--shadow);
  }

  /* the applications table card sits tighter than a prose card */
  .card-table { padding: 8px 26px; }

  /* the shell's own top gutter, under the top bar */
  .screen-body { padding-top: 36px; }

  .stack { display: flex; flex-direction: column; gap: 20px; }
  .row { display: flex; gap: 12px; }
  .row.wrap { flex-wrap: wrap; }
  .spread { display: flex; align-items: center; justify-content: space-between; gap: 16px; }

  /* ---------- headline fact row ---------- */

  .facts {
    display: grid;
    grid-template-columns: repeat(3, auto);
    gap: 4px 40px;
    margin-bottom: 4px;
  }
  @media (max-width: 640px) { .facts { grid-template-columns: 1fr; } }

  .fact .k {
    font-size: 11.5px;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin-bottom: 3px;
  }
  .fact .v {
    font-size: 15px;
    font-weight: 500;
    color: var(--text);
  }
  .fact .v.mono { font-size: 14.5px; }

  /* ---------- table (Material data table) ---------- */

  table {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
    background: var(--surface);
  }

  .table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .added-note { color: var(--text-muted); font-style: italic; }

  thead th {
    text-align: left;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.01em;
    color: var(--text-muted);
    padding: 0 16px 12px;
    border-bottom: 1px solid var(--border);
    white-space: nowrap;
  }
  thead th.num { text-align: right; }

  tbody td {
    padding: 14px 16px;
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
    color: var(--text);
  }
  tbody tr:last-child td { border-bottom: none; }
  tbody tr:hover td { background: var(--surface-sunken); }
  td.num { text-align: right; }
  td.muted { color: var(--text-muted); }

  tbody tr.clickable { cursor: pointer; }
  tbody tr.clickable:hover td { background: var(--accent-soft); }

  /* A note belonging to the row above it, so the payment warning sits under
     the figures it is about. The row and its note rows (a written note, then
     the engine's payment note) are separate <tr>s, so the row's own bottom
     border would draw a line between them: any row followed by a note row
     drops its border, and only the last one in the group keeps the line
     that separates the whole entry from the next. */
  tr.note-row td {
    padding: 0 16px 12px;
    border-bottom: 1px solid var(--border);
  }
  tbody tr:has(+ tr.note-row) td { border-bottom: none; }

  /* Hover lights the whole entry, not one <tr> of it: hovering the row
     lights the note rows under it, and hovering a note row lights the row
     (and the other note row) above it. A mismatch cell keeps its own red
     or yellow tint, as it does when its own row is hovered. */
  tbody tr:hover + tr.note-row td,
  tbody tr:hover + tr.note-row + tr.note-row td,
  tbody tr:has(+ tr.note-row:hover) td:not(.fee-mismatch),
  tbody tr:has(+ tr.note-row + tr.note-row:hover) td:not(.fee-mismatch) {
    background: var(--surface-sunken);
  }

  /* The engine's payment note under its row (S8): red for a shortfall,
     which stops pricing, yellow for an overpayment, which is only a
     mismatch to check. Set in the size of the "Mismatch: ..." line in the
     Size fee cell above it, which it explains. */
  tr.note-row td.note-danger { color: var(--danger); }
  tr.note-row td.note-warn { color: var(--warning); }
  tr.note-row td.note-danger,
  tr.note-row td.note-warn { font-size: 11.5px; }

  /* The Size fee cell says what the row owed as well as what it paid: the
     threshold it crossed under the amount, and - when the two figures do not
     agree - the mismatch in the cell's own colour, so a wrong payment is
     found by looking down the column rather than by reading every row.
     nowrap because the sentence is one fact and a numeric column is narrow;
     the table already scrolls sideways when it has to - except on a phone,
     where it is lifted below: a mismatch line is the longest thing in the
     table and nowrap made the whole ledger scroll sideways to read it. */
  .fee-line {
    font-size: 11.5px;
    line-height: 1.4;
    margin-top: 3px;
    color: var(--text-muted);
    white-space: nowrap;
    font-family: 'Roboto', sans-serif;
  }

  td.fee-mismatch,
  tbody tr:hover td.fee-mismatch { background: none; color: var(--danger); }
  td.fee-mismatch .fee-line { color: var(--danger); }
  /* Paid more than was due: a warning, not something to fix before going
     on, so the text (and the refund note under it) turns warning-coloured
     instead of danger-coloured. Neither state - over or under - puts a
     background tint on the cell itself, on the row or on hover; the
     colour of the text and figures, and the note underneath, carry it. */
  td.fee-mismatch.fee-overpaid,
  tbody tr:hover td.fee-mismatch.fee-overpaid { background: none; color: var(--warning); }
  td.fee-mismatch.fee-overpaid .fee-line { color: var(--warning); }

  .totals-row td {
    border-top: 1px solid var(--border-strong);
    border-bottom: none;
    background: transparent !important;
    padding-top: 16px;
    font-weight: 500;
  }

  /* ---------- chips ---------- */

  .chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.01em;
    padding: 4px 10px 4px 9px;
    border-radius: 100px;
    border: 1px solid transparent;
    white-space: nowrap;
  }
  .chip .dot { width: 6px; height: 6px; border-radius: 50%; flex: none; }

  .chip.confirmed { background: var(--success-soft); color: var(--success); border-color: var(--success-border); }
  .chip.confirmed .dot { background: var(--success); }

  .chip.review { background: var(--warning-soft); color: var(--warning); border-color: var(--warning-border); }
  .chip.review .dot { background: var(--warning); }

  .chip.exact { background: var(--accent-soft); color: var(--accent-strong); border-color: var(--accent-soft-border); }
  .chip.exact .dot { background: var(--accent); }

  .chip.pending { background: var(--surface-sunken-alt); color: var(--text-muted); border-color: var(--border); }
  .chip.pending .dot { background: var(--text-faint); }

  /* ---------- buttons (Material contained / outlined / text) ---------- */

  /* `.btn` is worn by <button>, <a> and <span> across the templates, so the
     base rule is deliberately element-agnostic. */
  .btn {
    appearance: none;
    font: inherit;
    font-family: 'Roboto', system-ui, sans-serif;
    font-weight: 500;
    font-size: 14px;
    letter-spacing: 0.01em;
    line-height: 20px;
    border-radius: 4px;
    padding: 8px 20px;
    cursor: pointer;
    border: 1px solid transparent;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    white-space: nowrap;
    text-decoration: none;
    transition: background-color 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
  }

  .btn-primary {
    background: var(--accent);
    color: #FFFFFF;
    box-shadow: var(--shadow);
  }
  .btn-primary:hover { color: #FFFFFF; background: var(--accent-strong); box-shadow: var(--shadow-2); }
  .btn-primary:active { background: var(--accent-strong); box-shadow: var(--shadow); }
  .btn-primary:disabled {
    background: var(--surface-sunken-alt);
    color: var(--text-faint);
    box-shadow: none;
    cursor: not-allowed;
  }

  .btn-secondary {
    background: var(--surface);
    color: var(--accent);
    border-color: var(--border);
  }
  .btn-secondary:hover { background: var(--accent-state); border-color: var(--accent-soft-border); }
  .btn-secondary:active { background: var(--accent-state-strong); }

  .btn-ghost {
    background: transparent;
    color: var(--accent);
    padding-left: 8px;
    padding-right: 8px;
    box-shadow: none;
  }
  .btn-ghost:hover { background: var(--accent-state); text-decoration: none; }

  .btn-danger {
    background: var(--danger);
    color: #FFFFFF;
    box-shadow: var(--shadow);
  }
  .btn-danger:hover { color: #FFFFFF; box-shadow: var(--shadow-2); }
  .btn-danger:active { box-shadow: var(--shadow); }

  /* The one green button in the app: "Cancel" beside a row's "Delete IDS?"
     (see .row-delete-confirm below). Everywhere else Cancel is a quiet
     .btn-secondary - here it is the safe choice next to a red one, so it
     gets a colour of its own rather than reading as a second way to say
     yes. */
  .btn-success {
    background: var(--success);
    color: #FFFFFF;
    box-shadow: var(--shadow);
  }
  .btn-success:hover { color: #FFFFFF; box-shadow: var(--shadow-2); }
  .btn-success:active { box-shadow: var(--shadow); }

  .btn-sm { font-size: 13px; padding: 6px 14px; }
  .btn-block { width: 100%; justify-content: center; }

  /* ---------- form fields (Material outlined text fields) ---------- */

  label.field {
    display: flex;
    flex-direction: column;
    gap: 6px;
    font-size: 13px;
    font-weight: 500;
    color: var(--text-muted);
  }

  input[type="text"],
  input[type="email"],
  input[type="number"],
  input[type="search"],
  input[type="date"],
  select,
  textarea {
    font: inherit;
    font-size: 14px;
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 9px 12px;
    min-width: 0;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
  }

  input[type="text"]:hover,
  input[type="email"]:hover,
  input[type="number"]:hover,
  input[type="search"]:hover,
  input[type="date"]:hover,
  select:hover,
  textarea:hover { border-color: var(--border-strong); }

  /* focus = 2px purple outline, drawn as border + inset ring so nothing shifts */
  input[type="text"]:focus,
  input[type="email"]:focus,
  input[type="number"]:focus,
  input[type="search"]:focus,
  input[type="date"]:focus,
  select:focus,
  textarea:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: inset 0 0 0 1px var(--accent);
  }

  input::placeholder, textarea::placeholder { color: var(--text-faint); }

  label.field input,
  label.field select {
    font-family: 'Roboto Mono', monospace;
  }
  label.field .hint { font-size: 11.5px; color: var(--text-muted); font-weight: 400; }

  /* the live long-form echo under a date input - styled like a hint even where
     it sits outside label.field (the edit table puts it in a <td>), and taken
     out of the flow entirely while empty, so a blank date field is not left
     carrying the column's 6px gap under nothing. */
  .date-echo { display: block; font-size: 11.5px; color: var(--text-muted); font-weight: 400; }
  .date-echo:empty { display: none; }

  /* A row of fields sitting side by side where only one of them carries a
     date echo. In the flow, that echo makes its own field taller, and a
     bottom-aligned row answers by lifting that field's input a line above
     its neighbours' - so the two inputs stopped being level the moment a
     date was picked. The echo is taken out of the flow instead and the row
     keeps a line of room underneath for it, which leaves every input in the
     row on one baseline whether a date has been chosen or not. */
  .row.echo-row { padding-bottom: 18px; }
  .row.echo-row > label.field { position: relative; }
  .row.echo-row .date-echo {
    position: absolute;
    top: 100%;
    left: 0;
    margin-top: 4px;
    white-space: nowrap;
  }

  /* the waitlist form uses bare <label> wrappers */
  .login-card form label {
    display: flex;
    flex-direction: column;
    gap: 6px;
    font-size: 13px;
    font-weight: 500;
    color: var(--text-muted);
  }

  .search-input {
    position: relative;
    flex: 1;
    min-width: 220px;
  }
  .search-input svg {
    position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
    color: var(--text-muted); pointer-events: none;
  }
  .search-input input {
    width: 100%;
    padding: 10px 12px 10px 40px;
  }

  hr.div { border: none; border-top: 1px solid var(--border); margin: 4px 0; }

  /* ---------- callouts (Material banners) ---------- */

  .callout {
    display: flex;
    gap: 12px;
    padding: 14px 16px;
    border-radius: 8px;
    font-size: 13.5px;
    line-height: 1.5;
  }
  .callout .icon { flex: none; margin-top: 1px; }
  .callout .text { flex: 1; min-width: 0; }
  /* Informational: purple text, normal weight, and nothing else. No box, no
     tint and no icon - an informational line is meant to be read in passing,
     not to compete with the form for attention (Dvir, review of
     2026-09-22).
     Its second line keeps the first line's colour: a muted grey under
     purple read as a different message. */
  .callout.info {
    background: none;
    border: 0;
    padding: 0;
    border-radius: 0;
    color: var(--accent);
    font-weight: 400;
  }
  .callout.info .icon { display: none; }
  .callout.info b { font-weight: inherit; }
  .callout.info .muted-line { color: inherit; }
  .callout.info a { color: var(--accent-strong); text-decoration: underline; }
  /* The waitlist confirmation keeps the info callout's plain layout but
     reads in normal body text, not purple (owner request 2026-10-03). */
  .callout.info.waitlist-joined { color: var(--text); }
  .callout.warn { background: var(--warning-soft); color: var(--warning); border: 1px solid var(--warning-border); }
  .callout.warn .icon { color: var(--warning); }
  .callout.danger { background: var(--danger-soft); color: var(--danger); border: 1px solid var(--danger-border); }
  .callout.danger .icon { color: var(--danger); }
  .callout.danger a { color: var(--danger); text-decoration: underline; }
  .callout b { font-weight: 500; }
  .callout .muted-line { color: var(--text-muted); margin-top: 4px; }
  .callout.warn .muted-line { color: var(--warning); opacity: 0.85; }

  /* ---------- a field the server refused (S5) ----------
     The input and its sentence share a wrapping row: the sentence sits to
     the right while there is room and drops under the input when there is
     not - a narrow card column, a table cell - and always drops under it
     on a phone. */
  .field-control {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 10px;
  }
  .field-control > input { flex: 1 1 8em; min-width: 0; }
  .field-error {
    color: var(--danger);
    font-family: 'Roboto', sans-serif;
    font-size: 12.5px;
    font-weight: 500;
    line-height: 1.4;
  }
  input[aria-invalid="true"],
  input[aria-invalid="true"]:hover,
  input[aria-invalid="true"]:focus {
    border-color: var(--danger);
    box-shadow: inset 0 0 0 1px var(--danger);
  }
  .form-error-summary {
    color: var(--danger);
    font-weight: 500;
    margin: -8px 0 16px;
  }
  @media (max-width: 640px) {
    .field-control { flex-direction: column; align-items: stretch; }
    /* In a column the flex basis is the input's height: 8em made every
       field 128px tall. Stretching across the column gives the width. */
    .field-control > input { flex: none; }
  }
  .check-row {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 14px;
    color: var(--text);
    cursor: pointer;
  }
  .check-row input { width: 16px; height: 16px; margin: 0; accent-color: var(--danger); }
  input[type="checkbox"][aria-invalid="true"] { outline: 2px solid var(--danger); outline-offset: 2px; }
  .btn[disabled] { opacity: 0.5; cursor: not-allowed; }

  /* ---------- segmented toggle ---------- */

  .segmented {
    display: inline-flex;
    background: var(--surface-sunken-alt);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 3px;
    gap: 2px;
  }
  .segmented button {
    appearance: none; border: none; background: transparent;
    font: inherit; font-size: 13px; font-weight: 500; letter-spacing: 0.01em;
    color: var(--text-muted); padding: 7px 14px; border-radius: 6px; cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease;
  }
  .segmented button:hover { color: var(--accent); background: var(--accent-state); }
  .segmented button.active { background: var(--surface); color: var(--accent-strong); box-shadow: var(--shadow); }

  .panel { display: none; }
  .panel.active { display: block; }

  /* ---------- upload queue ---------- */

  /* a <label>, so the whole zone opens the picker; the input it wraps is
     parked at 1x1 and invisible rather than removed, because a display:none
     file input cannot be reached by a click on its label in every browser. */
  .dropzone {
    display: block;
    cursor: pointer;
    border: 1px dashed var(--border-strong);
    border-radius: 8px;
    padding: 28px;
    text-align: center;
    color: var(--text-muted);
    font-size: 13.5px;
    background: var(--surface-sunken);
    transition: background-color 0.15s ease, border-color 0.15s ease;
  }
  .dropzone:hover { border-color: var(--accent); background: var(--accent-state); }
  .dropzone .big { color: var(--text); font-weight: 500; font-size: 14.5px; margin-bottom: 4px; }
  .dropzone input[type="file"] { position: absolute; width: 1px; height: 1px; opacity: 0; }

  /* One bar for every wait in the app: the upload going up (ledger screen)
     and the PDF being read (processing screen). They sit on different pages
     but describe the same job, so they are deliberately the same object. */
  .progress .track {
    background: var(--accent-soft);
    border: 1px solid var(--accent-soft-border);
    border-radius: 100px;
    height: 10px;
    overflow: hidden;
  }
  .progress .fill { background: var(--accent); height: 100%; width: 0%; }
  .progress .label { font-size: 12px; color: var(--text-faint); margin-top: 6px; }

  @media (prefers-reduced-motion: no-preference) {
    .progress .fill { transition: width 0.2s ease; }
  }

  .queue-item {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 12px 4px;
    border-bottom: 1px solid var(--border);
  }
  .queue-item:last-child { border-bottom: none; }
  .queue-item .name { font-size: 13.5px; font-weight: 500; }
  .queue-item .meta { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
  .queue-item .count { font-family: 'Roboto Mono', monospace; font-size: 14px; font-weight: 500; margin-left: auto; margin-right: 4px; }
  /* The two ends of a row: read and ready to review, or not read at all.
     Colour is never the only sign - the line under the name says which,
     and the tick and cross are characters, not background images. */
  .queue-item.ready .meta, .queue-item.ready .count { color: var(--success); }
  .queue-item.failed .meta, .queue-item.failed .count { color: var(--danger); }
  .spinner {
    width: 14px; height: 14px; border-radius: 50%;
    border: 2px solid var(--border); border-top-color: var(--accent);
    animation: spin 0.8s linear infinite; flex: none;
  }
  @media (prefers-reduced-motion: reduce) { .spinner { animation: none; } }
  @keyframes spin { to { transform: rotate(360deg); } }

  /* ---------- stepper ---------- */

  .stepper {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12.5px;
    color: var(--text-muted);
    font-weight: 500;
  }
  .stepper .dots { display: flex; gap: 5px; }
  .stepper .dots span { width: 6px; height: 6px; border-radius: 50%; background: var(--border-strong); }
  .stepper .dots span.done { background: var(--success); }
  .stepper .dots span.now { background: var(--accent); width: 8px; height: 8px; }

  /* ---------- help page: FAQ ---------- */

  details.faq {
    border-top: 1px solid var(--border);
    padding: 12px 0 0;
  }
  details.faq:first-of-type { border-top: none; padding-top: 0; }
  details.faq > summary {
    cursor: pointer;
    font-weight: 500;
    color: var(--text);
  }
  details.faq > p { margin: 8px 0 4px; }

  /* ---------- review screen: sections ---------- */

  details.section {
    border: 1px solid var(--border);
    border-radius: 8px;
    margin-bottom: 10px;
    overflow: hidden;
    background: var(--surface);
  }
  details.section > summary {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 16px;
    background: var(--surface-sunken);
    color: var(--text);
    font-size: 13.5px;
    font-weight: 500;
    transition: background-color 0.15s ease;
  }
  details.section > summary:hover { background: var(--accent-state); }
  details.section > summary::-webkit-details-marker { display: none; }
  details.section > summary .n {
    font-family: 'Roboto Mono', monospace;
    font-weight: 500;
    color: var(--text-muted);
    margin-left: 8px;
  }
  details.section > summary .caret {
    transition: transform 0.15s ease;
    color: var(--text-muted);
  }
  details.section[open] > summary .caret { transform: rotate(90deg); }
  details.section table { font-size: 13px; }
  details.section thead th { padding: 12px 16px 10px; }

  /* The per-section correction boxes. They are padded rather than the whole
     open panel, so the citation table still runs edge to edge and keeps its
     own header padding. */
  .section-count-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 16px;
    border-top: 1px solid var(--border);
  }
  .section-count-row .lbl { font-size: 13px; color: var(--text-muted); }
  .section-count-row input {
    font-family: 'Roboto Mono', monospace;
    font-size: 15px;
    font-weight: 500;
    width: 84px;
    text-align: right;
    padding: 6px 10px;
  }
  .citations-box {
    display: block;
    padding: 12px 16px 14px;
    border-top: 1px solid var(--border);
  }
  .citations-box .lbl {
    display: block;
    font-size: 12.5px;
    color: var(--text-muted);
    margin-bottom: 6px;
  }
  .citations-box textarea {
    display: block;
    width: 100%;
    box-sizing: border-box;
    min-height: 76px;
    font-family: 'Roboto Mono', monospace;
    font-size: 12.5px;
    padding: 8px 10px;
    resize: vertical;
  }
  .citations-box .hint {
    display: block;
    margin-top: 6px;
    font-size: 12px;
    color: var(--text-muted);
  }

  .unclassified {
    border: 1px dashed var(--border-strong);
    border-radius: 8px;
    padding: 16px;
    font-size: 13px;
    background: var(--surface);
  }
  .unclassified .title { font-weight: 500; color: var(--text); margin-bottom: 10px; }
  .unc-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 0;
    border-top: 1px solid var(--border);
  }
  .unc-row:first-of-type { border-top: none; }
  .unc-row .snippet {
    font-family: 'Roboto Mono', monospace;
    font-size: 12px;
    color: var(--text-muted);
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .unc-row .page {
    font-family: 'Roboto Mono', monospace;
    font-size: 11px;
    color: var(--text-faint);
    flex: none;
    width: 24px;
  }
  .unc-row select {
    font-size: 12.5px;
    font-weight: 500;
    padding: 6px 8px;
    flex: none;
    width: 176px;
  }

  .total-override {
    display: flex;
    align-items: baseline;
    gap: 14px;
    padding: 16px 18px;
    background: var(--surface-sunken);
    border: 1px solid var(--border);
    border-radius: 8px;
    flex-wrap: wrap;
  }
  /* flex: none keeps the 90px against `.field-control > input`, which
     would otherwise grow the box across the row once an error sits
     beside it (and, in the phone column, grow it downwards). */
  .total-override input {
    flex: none;
    font-family: 'Roboto Mono', monospace;
    font-size: 20px;
    font-weight: 500;
    width: 90px;
    text-align: right;
    padding: 6px 10px;
  }
  .total-override .lbl { font-size: 13px; color: var(--text-muted); }
  /* Where review.js says whether what is on screen has reached the server.
     min-height keeps the row from jumping the first time it says anything. */
  .total-override .save-status {
    font-size: 12px;
    color: var(--text-muted);
    min-height: 1em;
  }
  .total-override .save-status.lost { color: var(--danger); }
  .total-override .hint { margin-left: auto; font-size: 12px; color: var(--text-muted); }

  /* A note about one citation row, under the citation it is about.
     One definition: there were two, and the first still laid the note out as
     a flex container with a gap for a `.mark` child that had already been
     deleted - three declarations this one overrode anyway, next to a
     font-size it also overrode. What the first one really contributed was
     the spacing, which is kept here. */
  .row-note {
    display: block;
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--text-muted);
    font-family: 'Roboto', sans-serif;
    border-radius: 8px;
    padding: 8px 10px;
  }

  /* ---------- result screen ---------- */

  .result-hero {
    text-align: center;
    padding: 40px 20px 34px;
  }
  .result-hero .amount {
    font-family: 'Roboto Mono', monospace;
    font-size: 52px;
    font-weight: 500;
    letter-spacing: -0.02em;
    line-height: 1;
    color: var(--text);
  }
  .result-hero .amount.zero { color: var(--success); }
  .result-hero .cite {
    margin-top: 10px;
    font-size: 14px;
    color: var(--text-muted);
  }
  .result-hero .codes {
    margin-top: 4px;
    font-family: 'Roboto Mono', monospace;
    font-size: 12.5px;
    color: var(--text-faint);
  }

  .explain {
    font-size: 14px;
    line-height: 1.65;
    color: var(--text);
    background: var(--surface-sunken);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 16px 18px;
  }

  .assertion-box {
    display: flex;
    align-items: center;
    gap: 14px;
    border: 1px solid var(--border);
    background: var(--surface-sunken);
    border-radius: 8px;
    padding: 16px 18px;
    font-size: 14px;
    font-weight: 400;
    color: var(--text);
  }
  .assertion-box p { margin: 0; flex: 1; }

  .footnote {
    font-size: 12.5px;
    color: var(--text-muted);
    text-align: center;
  }

  /* ---------- the centred one-card screens ---------- */

  /* delete_app.html, error.html, maintenance.html and not_invited.html (landing.html reuses the class but overrides the height below): one
     card in the middle of the page, nothing else on it. The class names are
     the sign-in card these grew out of - sign-in is Auth0's own page now, and
     there is no login.html in this app - but the shape is the same and so is
     the arithmetic below.

     Everything above and below the card on such a page: the 62px top bar
     (10px of padding either side of a 42px logo), .screen-body's 36px top
     gutter, the shell's 96px bottom padding and the 78.75px footer (20 + 40
     of padding around one 18.75px line of links: 12.5px type at a line-height
     of 1.5) - 272.75px. The footer was two lines (45.5px, measured in a
     browser at 641px) until the copyright line under the links was dropped
     on 2026-10-05. Subtracting less than that is a scrollbar on every one of
     those four screens, which is what the pre-footer 200px had become.
     Rounded up to 273. tests/web/test_footer.py adds these up out of this file, so a
     change to the top bar, the shell or the footer fails there rather than
     overflowing here. */
  .center-viewport {
    min-height: calc(100vh - 273px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding-top: 20px;
  }
  .login-card { width: min(360px, 100%); }
  .login-mark { text-align: center; margin-bottom: 28px; }
  .login-mark .mark { font-weight: 500; font-size: 19px; letter-spacing: 0; }
  .login-mark .sub { font-size: 12.5px; color: var(--text-muted); margin-top: 3px; }

  /* ---------- public landing page (landing.html) ----------
     Standalone page: no top bar and no .shell, so its viewport height only
     has to leave room for the footer. */
  .landing-page { overflow-x: hidden; }
  .landing { width: min(480px, 100%); margin: 0 auto; }
  .landing .login-mark { margin-bottom: 20px; }
  .landing h1 { font-size: 19px; font-weight: 500; line-height: 1.3; margin: 0; text-align: left; text-wrap: auto; }
  .waitlist-heading { font-size: 19px; font-weight: 500; line-height: 1.3; margin: 0; text-align: left; text-wrap: auto; }
  .landing ul { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 8px; }
  .beta-notice {
    background: var(--warning-soft); border: 1px solid var(--warning-border);
    border-left: 4px solid var(--warning); border-radius: 8px;
    padding: 14px 16px; font-size: 14.5px; line-height: 1.5; color: var(--text);
  }
  .beta-notice b { display: block; font-weight: 500; color: var(--warning); margin-bottom: 4px; }
  .landing-page .center-viewport { min-height: calc(100vh - 140px); padding: 24px 16px; }
  .landing-page .site-footer { padding-left: 16px; padding-right: 16px; }

  /* ---------- landing page product glimpse (_landing_demo.html) ----------
     A small ledger that plays itself, on the screen of a laptop drawn in
     CSS: an IDS is added, its references are counted, the size fee comes
     out. The laptop is there so that it reads as a preview of the product
     and not as one more card of the page. It borrows the ledger's own table, chip
     and mono rules so it looks like the product, and only adds what is
     peculiar to it. It is CSS and nothing else - there is no script - so it
     plays the same with JavaScript on or off.

     The base rules draw the FINAL frame - both rows in, the bar at 109,
     both markers lit, "$500 due" - which is what a browser that cannot run
     the animations below is left showing. The animations then take each
     piece from its first-frame state to that one, all on one 15.5 s loop:

        0.0 s  empty ledger
        0.4    IDS_1.pdf drops in          1.2-3.2   scan bar sweeps it
        3.4    row 1 slides in, at 0       3.9-5.4   its Refs and Running, and the
                                                     meter and bar, count 0 -> 38
        6.0    "Fetch from USPTO" pulses   6.0-8.0   (two pulses)
        8.2    row 2 slides in            8.9-12.3   its Refs 0 -> 71, its Running
                                                     38 -> 109, the meter and bar 38 -> 109
        12.5   "$500 due"                  13.0      row 2's Size fee says so
        13.3-15.15  last frame held        15.15-15.5  fade out, then again

     Every animation below is 15.5 s and starts together, so a keyframe's
     percentage is its time in that table over 15.5 s (1 s = 6.45%). The
     meter's total and bar are given exactly the keyframes of the Running
     cell they follow - same span, same easing - so they are the same number
     at every moment and the meter can never disagree with the table. The
     two markers light when the count passes 50 and 100, which on the
     ease-in-out used for that last count is 9.8 s and 11.4 s in; "> 100
     refs." follows the 100 marker.

     It plays whatever the visitor's reduced-motion setting says, and none
     of this sits behind a prefers-reduced-motion query. That is the owner's
     decision: the glimpse is the point of the page. What stands in for the
     setting is the pause control (.ld-pause, a checkbox - see the end),
     which is what WCAG 2.2.2 asks of anything that moves for more than
     five seconds.

     The numbers - four cells of the table and the meter's total - are
     counters, not text: an integer custom property (--n) is animated, and
     counter-reset / counter() turns it into digits in a ::before.
     @property is what lets --n be animated as an integer. In a browser
     without it --n is an ordinary custom property, which animates in jumps
     instead of smoothly - 0 then 38, 0 then 71, 38 then 109 - rougher, but
     it still ends on the right numbers (38/38, 71/109, total 109), and with
     no animation support at all the base value of --n is the final one.
     That is why there is no digit in the markup to hide.

     Sized in em from one font-size taken off the width of the box it is in
     (container query units), so the same markup is a 448px laptop beside the
     card on a desktop and a ~310px one inside it on a phone, with nothing to
     scroll. The screen is always 16:10, so it is wide and short: the
     application and the two things that arrive take the top row, the table
     the left of the rest, and the meter over the fee the right. */

  @property --n { syntax: '<integer>'; inherits: false; initial-value: 0; }

  .visually-hidden {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
    white-space: nowrap; border: 0;
  }

  .landing-demo {
    --ld-loop: 15.5s;
    position: relative; container-type: inline-size;
    display: flex; flex-direction: column;
  }

  /* The screen: a 16:10 panel in a dark bezel, the laptop's lid. Its own
     width is the whole of the demo's box, and everything inside is sized
     in em off one font-size, taken from that width less the two bezels
     (47 ems of room: the table, which cannot wrap, needs about 27 of the
     60% it is given, and the top row about 36 of the whole). The
     bezel thins on a narrow screen, where every pixel is wanted for the
     content. */
  .ld {
    --bez: clamp(5px, 2cqw, 11px);
    font-size: 10px;                           /* where container units are unknown */
    font-size: calc((100cqw - 2 * var(--bez)) / 47);
    aspect-ratio: 16 / 10;
    display: flex; flex-direction: column; gap: 1.2em;
    padding: 1em 1.1em;
    background: var(--surface);
    border: var(--bez) solid #1f2024;
    border-bottom-width: calc(var(--bez) * 1.15);
    border-radius: calc(var(--bez) * 1.6) calc(var(--bez) * 1.6) 3px 3px;
    overflow: hidden;
    pointer-events: none; user-select: none;
  }

  /* the top row: the application on the left, what arrives on the right */
  .ld-top { display: flex; align-items: center; justify-content: space-between; gap: 1em; }
  .ld-head { display: flex; align-items: baseline; gap: .8em; white-space: nowrap; }
  .ld-title { font-size: 1.35em; font-weight: 500; }
  .ld-sub { color: var(--text-muted); }

  .ld-intake { display: flex; align-items: center; gap: .6em; }
  .ld .chip { font-size: 1em; gap: .5em; padding: .35em .85em .35em .75em; }
  .ld-ico { width: .95em; height: 1.1em; flex: none; }
  .ld-file { position: relative; overflow: hidden; }
  .ld-file::after {                            /* the scan bar */
    content: ''; position: absolute; top: 0; bottom: 0; left: 0; width: 2px;
    background: var(--accent); box-shadow: 0 0 .7em var(--accent); opacity: 0;
  }

  /* below it, two columns: the table (60%) beside the meter and the fee. The
     screen is wide and short, so the room is spent on the width; whatever
     height is left over is shared out above and below, not stranded at the
     bottom. */
  .ld-main {
    flex: 1; min-height: 0;
    display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    column-gap: 1.8em; align-items: center;
  }
  .ld-side { display: flex; flex-direction: column; gap: 2.2em; min-width: 0; }

  /* the digits of the table's counters and the meter's total (see the
     comment at the top). The cells are a fixed three digits wide, in the
     mono face and right-aligned, so a number growing from one digit to
     three moves nothing. */
  .ld-n, .ld-total-n { counter-reset: n var(--n); }
  .ld-n::before, .ld-total-n::before { content: counter(n); }
  .ld-n { display: inline-block; min-width: 3ch; text-align: right; }
  .ld-n-1a, .ld-n-1b { --n: 38; }
  .ld-n-2a { --n: 71; }
  .ld-n-2b, .ld-total-n { --n: 109; }

  /* the ledger's own table, at the demo's size */
  .ld table { font-size: 1em; }
  .ld th, .ld td { white-space: nowrap; }
  .ld thead th { font-size: .95em; padding: 0 .3em .8em; }
  .ld tbody td { padding: 1.7em .3em; }
  .ld td .chip { padding: .3em .65em .3em .6em; gap: .4em; }
  .ld tbody tr:hover td { background: none; }
  .ld thead th:first-child, .ld tbody td:first-child { padding-left: .15em; }
  .ld thead th:last-child, .ld tbody td:last-child { padding-right: .15em; }
  .ld .muted { color: var(--text-muted); }
  .ld-fee-cell { display: inline-grid; justify-items: end; }
  .ld-fee-cell > span { grid-area: 1 / 1; }
  .ld-fee-wait { color: var(--text-muted); opacity: 0; }

  /* the bar: 150 across, the two fee thresholds marked on it. A marker's
     `color` is its lit state: the tick is currentColor and the label
     inherits it. */
  .ld-meter-head { display: flex; justify-content: space-between; color: var(--text-muted); margin-bottom: .8em; }
  .ld-track { position: relative; height: .75em; margin-bottom: 1.8em; border-radius: 1em; background: var(--surface-sunken-alt); }
  .ld-fill { position: absolute; top: 0; bottom: 0; left: 0; width: 72.667%; border-radius: inherit; background: var(--accent); }
  .ld-mark {
    position: absolute; top: -.4em; bottom: -.4em; width: 2px; margin-left: -1px;
    color: var(--accent-strong); background: currentColor; box-shadow: 0 0 0 1px var(--surface);
  }
  .ld-mark-50 { left: 33.333%; }
  .ld-mark-100 { left: 66.667%; }
  .ld-mark-label {
    position: absolute; top: 100%; left: 50%; transform: translateX(-50%); margin-top: .3em;
    font-weight: 500;
  }

  /* the fee: the ledger's "> 100 refs. - $500 due" line, made the point. In
     the narrow column the label sits over the figures, not beside them. */
  .ld-fee {
    display: flex; flex-direction: column; align-items: flex-start; gap: .7em;
    padding: 1.1em 1.1em;
    background: var(--surface-sunken);
    border: 1px solid var(--border);
    border-radius: .7em;
  }
  .ld-fee-k { color: var(--text-muted); }
  .ld-fee-v { position: relative; display: flex; align-items: center; gap: .5em; min-height: 2em; }
  .ld-fee-idle { position: absolute; left: 0; opacity: 0; color: var(--text-muted); }
  .ld-fee-tier { color: var(--text-muted); white-space: nowrap; }
  .ld-fee-due {
    font-size: 1.2em; font-weight: 500; color: var(--accent-strong); white-space: nowrap;
    background: var(--accent-soft); border: 1px solid var(--accent-soft-border);
    border-radius: .45em; padding: .1em .5em;
  }

  /* The rest of the laptop: a base a little wider than the lid, with the
     notch a thumb would lift it by (decoration, so nothing in it is read
     out), and a caption ABOVE the lid, on a row with the pause control at
     its right-hand end. The caption and the control come before the laptop
     in the markup, after the checkbox, so that the checkbox still precedes
     everything it has to reach with `~`. */
  .ld-base {
    height: 14px; margin: 0 -3%;
    background: linear-gradient(#dfe1e5, #aeb1b7);
    border-radius: 0 0 18px 18px;
    box-shadow: 0 10px 18px rgba(32, 33, 36, .28);
    position: relative;
  }
  .ld-base::before {
    content: ''; position: absolute; top: 0; left: 50%; transform: translateX(-50%);
    width: 18%; height: 5px; background: #8f9298; border-radius: 0 0 8px 8px;
  }
  .ld-cap {
    min-height: 32px; margin-bottom: 14px;
    display: flex; align-items: center; justify-content: center;
    font-size: 13.5px; font-weight: 500; color: var(--text-muted);
  }

  /* Pause / play: a checkbox, because a checkbox keeps its own state and a
     stylesheet can read it (`:checked ~`) with no script. The input comes
     first in the markup, visually hidden but still focusable; the label is
     the button the eye sees. Checked freezes every animation in .ld where
     it stands; unchecked lets them run on from there. */
  .ld-pause {
    position: absolute; right: 0; top: 0;
    display: inline-flex; align-items: center; justify-content: center;
    width: 32px; height: 32px;
    border: 1px solid var(--border); border-radius: 50%;
    background: var(--surface); color: var(--text-muted);
    cursor: pointer;
  }
  .ld-pause:hover { color: var(--accent); background: var(--accent-state); }
  .ld-pause svg { width: 12px; height: 12px; }
  .ld-ico-play { display: none; }
  .ld-pause-input:checked ~ .ld-pause .ld-ico-pause { display: none; }
  .ld-pause-input:checked ~ .ld-pause .ld-ico-play { display: inline; }
  .ld-pause-input:focus-visible ~ .ld-pause { outline: 2px solid var(--accent); outline-offset: 2px; }
  .ld-pause-input:checked ~ .ld,
  .ld-pause-input:checked ~ .ld *,
  .ld-pause-input:checked ~ .ld *::before,
  .ld-pause-input:checked ~ .ld *::after { animation-play-state: paused; }
  @media (hover: none) and (pointer: coarse) {
    .ld-pause { width: 44px; height: 44px; }
    .ld-cap { min-height: 44px; }
  }

  /* Wide enough for two columns: the laptop leaves the card and stands
     beside it, the pair centred and the bottom of the laptop's base level
     with the bottom of the card (the caption row rides above it, and the
     whole group is pushed down, so it never reaches the logo). The card is
     the box it is placed against (`left: 100%` is its right edge, `bottom:
     0` its bottom), and the whole .landing moves left by half of what was
     added, so the card and the laptop together, not the card alone, are
     what sit in the middle. The laptop is 448px wide when the window has
     the room and shrinks with it from there - four fifths of what is left
     once the card, the gap and the page's gutters (40px here, a little
     over the 16px they are, for a scrollbar) are taken off. */
  @media (min-width: 900px) {
    .landing-page { --ld-gap: 28px; --ld-w: clamp(280px, calc((100vw - 480px - var(--ld-gap) - 40px) * .8), 448px); }
    .landing { position: relative; left: calc((var(--ld-w) + var(--ld-gap)) / -2); }
    .landing .card { position: relative; }
    .landing-demo { position: absolute; bottom: 0; left: calc(100% + var(--ld-gap)); width: var(--ld-w); }
  }

  /* On a phone the laptop is wide and the card is narrow, so the laptop
     leaves the card: it keeps its place in the flow, after the bullets, but
     takes the width of the window less a 7px gutter each side (the margin
     pulls it out of the card's padding symmetrically, whatever that is). A
     thinner bezel, tighter columns, and - under 480px - no Size fee column
     in the table (the fee panel and "$500 due" carry the fee) buy the
     table the room to be read at about 8px. */
  @media (max-width: 640px) {
    .landing-demo { --ld-wide: min(100vw - 14px, 560px); width: var(--ld-wide); margin-inline: calc(50% - var(--ld-wide) / 2); }
    .ld { --bez: clamp(4px, 1.2cqw, 11px); padding: .9em .8em; }
    .ld-base { margin: 0 -1.2%; }
    .ld-pause { right: 6px; }
    .ld-main { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); column-gap: 1.2em; }
    .ld-fee-v { flex-wrap: wrap; }
  }
  @media (max-width: 479px) {
    .ld thead th:nth-child(4), .ld tbody td:nth-child(4) { display: none; }
    /* 39 ems of room, not 47: without that column the widest thing left is
       the top row, about 36 */
    .ld { font-size: calc((100cqw - 2 * var(--bez)) / 39); }
  }

  /* ---- the loop. Each rule gives one piece its animation; the keyframes
     follow, in the order of the story. `linear` is the clock; the easing is
     set where a keyframe wants it. A pair of keyframes a hundredth of a
     percent apart is a step. */

  .ld                          { animation: ld-fade var(--ld-loop) linear infinite; }
  .ld-file                     { animation: ld-drop var(--ld-loop) linear infinite; }
  .ld-file::after              { animation: ld-scan var(--ld-loop) linear infinite; }
  .ld-fetch                    { animation: ld-dim var(--ld-loop) linear infinite, ld-pulse var(--ld-loop) linear infinite; }
  .ld-row-1 td                 { animation: ld-row-1 var(--ld-loop) linear infinite; }
  .ld-n-1a, .ld-n-1b           { animation: ld-n-1 var(--ld-loop) linear infinite; }
  .ld-n-2a                     { animation: ld-n-2a var(--ld-loop) linear infinite; }
  .ld-n-2b                     { animation: ld-n-2b var(--ld-loop) linear infinite; }
  .ld-row-2 td                 { animation: ld-row-2 var(--ld-loop) linear infinite; }
  .ld-total-n                  { animation: ld-total var(--ld-loop) linear infinite; }
  .ld-fill                     { animation: ld-bar var(--ld-loop) linear infinite; }
  .ld-mark-50                  { animation: ld-lit-50 var(--ld-loop) linear infinite; }
  .ld-mark-100                 { animation: ld-lit-100 var(--ld-loop) linear infinite; }
  .ld-fee-idle                 { animation: ld-idle var(--ld-loop) linear infinite; }
  .ld-fee-tier                 { animation: ld-tier var(--ld-loop) linear infinite; }
  .ld-fee-due                  { animation: ld-due var(--ld-loop) linear infinite; }
  .ld-fee-wait                 { animation: ld-wait var(--ld-loop) linear infinite; }
  .ld-fee-paid                 { animation: ld-paid var(--ld-loop) linear infinite; }

  /* the whole card: dark at both ends of the loop, so the cut back to an
     empty ledger is not seen */
  @keyframes ld-fade {
    0% { opacity: 0; }
    2%, 97.74% { opacity: 1; }
    100% { opacity: 0; }
  }

  /* 0.4 s: IDS_1.pdf drops in, with a little overshoot */
  @keyframes ld-drop {
    0%, 2.58% { opacity: 0; transform: translateY(-1.4em); animation-timing-function: cubic-bezier(.2, .9, .3, 1.25); }
    5.81%, 100% { opacity: 1; transform: none; }
  }

  /* 1.2 s: the scan bar sweeps the file chip there and back */
  @keyframes ld-scan {
    0%, 7.73% { left: 0; opacity: 0; }
    7.74% { left: 0; opacity: 1; animation-timing-function: ease-in-out; }
    14.19% { left: calc(100% - 2px); opacity: 1; animation-timing-function: ease-in-out; }
    20.65% { left: 0; opacity: 1; }
    20.66%, 100% { left: 0; opacity: 0; }
  }

  /* 6.0 s: "Fetch from USPTO" wakes up and pulses twice */
  @keyframes ld-dim {
    0%, 38.6% { opacity: .4; }
    40.65%, 100% { opacity: 1; }
  }
  @keyframes ld-pulse {
    0%, 38.70% { box-shadow: 0 0 0 0 transparent; }
    38.71% { box-shadow: 0 0 0 0 var(--accent-soft-border); animation-timing-function: ease-out; }
    45.16% { box-shadow: 0 0 0 .8em transparent; }
    45.17% { box-shadow: 0 0 0 0 var(--accent-soft-border); animation-timing-function: ease-out; }
    51.61%, 100% { box-shadow: 0 0 0 .8em transparent; }
  }

  /* 3.4 s and 8.2 s: a row slides in and its background fades from tinted */
  @keyframes ld-row-1 {
    0%, 21.93% { opacity: 0; transform: translateY(-.8em); background-color: transparent; }
    21.94% { opacity: 0; transform: translateY(-.8em); background-color: var(--accent-soft); }
    24.84% { opacity: 1; transform: none; }
    29.68%, 100% { opacity: 1; transform: none; background-color: transparent; }
  }
  @keyframes ld-row-2 {
    0%, 52.89% { opacity: 0; transform: translateY(-.8em); background-color: transparent; }
    52.90% { opacity: 0; transform: translateY(-.8em); background-color: var(--accent-soft); }
    55.81% { opacity: 1; transform: none; }
    60.65%, 100% { opacity: 1; transform: none; background-color: transparent; }
  }

  /* The cells that count. Each comes in at its start value, waits for the
     slide to finish, and counts on one ease-in-out, accent-coloured while
     it does: row 1's Refs and Running 0 -> 38 (3.9-5.4 s); row 2's Refs
     0 -> 71 and Running 38 -> 109 (8.9-12.3 s). */
  @keyframes ld-n-1 {
    0%, 25.15% { --n: 0; color: var(--text); }
    25.16% { --n: 0; color: var(--accent); animation-timing-function: ease-in-out; }
    34.84% { --n: 38; color: var(--accent); }
    34.85%, 100% { --n: 38; color: var(--text); }
  }
  @keyframes ld-n-2a {
    0%, 57.41% { --n: 0; color: var(--text); }
    57.42% { --n: 0; color: var(--accent); animation-timing-function: ease-in-out; }
    79.35% { --n: 71; color: var(--accent); }
    79.36%, 100% { --n: 71; color: var(--text); }
  }
  @keyframes ld-n-2b {
    0%, 57.41% { --n: 38; color: var(--text); }
    57.42% { --n: 38; color: var(--accent); animation-timing-function: ease-in-out; }
    79.35% { --n: 109; color: var(--accent); }
    79.36%, 100% { --n: 109; color: var(--text); }
  }

  /* The meter's total and the bar: the Running cell's own spans and easing
     (0 -> 38 at 3.9-5.4 s, 38 -> 109 at 8.9-12.3 s), so they are the same
     number at every moment. 38/150 is 25.333% and 109/150 is 72.667%. */
  @keyframes ld-total {
    0%, 25.15% { --n: 0; color: var(--text-faint); }
    25.16% { --n: 0; color: var(--text); animation-timing-function: ease-in-out; }
    34.84%, 57.41% { --n: 38; color: var(--text); }
    57.42% { --n: 38; color: var(--text); animation-timing-function: ease-in-out; }
    79.35%, 100% { --n: 109; color: var(--text); }
  }
  @keyframes ld-bar {
    0%, 25.15% { width: 0; }
    25.16% { width: 0; animation-timing-function: ease-in-out; }
    34.84%, 57.41% { width: 25.333%; }
    57.42% { width: 25.333%; animation-timing-function: ease-in-out; }
    79.35%, 100% { width: 72.667%; }
  }

  /* the markers light as the count passes them (9.8 s and 11.4 s) */
  @keyframes ld-lit-50 {
    0%, 63.53% { color: var(--text-faint); }
    63.54%, 100% { color: var(--accent-strong); }
  }
  @keyframes ld-lit-100 {
    0%, 73.77% { color: var(--text-faint); }
    73.78%, 100% { color: var(--accent-strong); }
  }

  /* the fee panel: a dash, then "> 100 refs." as the 100 marker lights,
     then "$500 due" (12.5 s) */
  @keyframes ld-idle {
    0%, 73.77% { opacity: 1; }
    75.7%, 100% { opacity: 0; }
  }
  @keyframes ld-tier {
    0%, 73.77% { opacity: 0; }
    75.7%, 100% { opacity: 1; }
  }
  @keyframes ld-due {
    0%, 80.64% { opacity: 0; }
    82.58%, 100% { opacity: 1; }
  }

  /* 13.0 s: row 2's Size fee goes from an ellipsis to $500 */
  @keyframes ld-wait {
    0%, 83.87% { opacity: 1; }
    85.8%, 100% { opacity: 0; }
  }
  @keyframes ld-paid {
    0%, 83.87% { opacity: 0; }
    85.8%, 100% { opacity: 1; }
  }

  /* ---------- error pages (error.html: every 4xx and the 500) ---------- */

  /* The card is the login card's, so only the two things inside it that are
     new need naming. The heading is deliberately quieter than an h1's
     default: a page that shouts "Page not found" at someone who mistyped a
     digit is a page making the moment worse than it is. */
  .error-heading {
    margin: 0;
    font-size: 17px;
    font-weight: 500;
  }
  .error-back { color: var(--accent); text-decoration: none; }
  .error-back:hover { text-decoration: underline; }

  /* ---------- choose application ---------- */

  .app-row-name { font-weight: 500; }
  .app-row-docket { color: var(--text-muted); font-size: 12.5px; margin-top: 1px; }

  /* ---------- choice cards (first action on a brand-new application) ---------- */

  .choice-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
  }
  @media (max-width: 640px) { .choice-grid { grid-template-columns: 1fr; } }

  .choice-card {
    text-decoration: none;
    color: inherit;
    display: flex;
    flex-direction: column;
    gap: 12px;
    align-items: flex-start;
  }
  /* The hover shadow and the transition that carried it went with the
     anchor: these cards are <div class="card choice-card">, and each one's
     own <button data-opens> is the thing that is pressed. */
  .choice-card .icon-badge {
    width: 40px; height: 40px;
    border-radius: 50%;
    background: var(--accent-soft);
    color: var(--accent-strong);
    display: flex; align-items: center; justify-content: center;
    flex: none;
  }
  .choice-card h3 { font-size: 16px; font-weight: 500; color: var(--text); margin: 2px 0 0; }
  .choice-card p { font-size: 13.5px; color: var(--text-muted); margin: 0; line-height: 1.5; flex: 1; }
  .choice-card .paused-note { font-size: 13px; color: var(--danger); margin: 0; line-height: 1.5; }
  .choice-card .paused-note a { color: var(--danger); text-decoration: underline; }

  /* The card whose panel is open. Its button has done its job, so it drops
     out of the accent colour and reads as the pressed half of a pair - the
     other card's button is still the live invitation. */
  .choice-card .btn.is-open,
  .choice-card .btn.is-open:hover,
  .choice-card .btn.is-open:active {
    background: var(--surface-sunken-alt);
    color: var(--text-muted);
    box-shadow: none;
  }

  /* ---------- the three ways to add a filing ---------- */

  /* Equal columns, so no one alternative reads as the default. They are
     alternatives, not steps: whichever fits the paper in front of you. */
  .choice-row {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 16px;
    align-items: start;
  }
  @media (max-width: 720px) { .choice-row { grid-template-columns: 1fr; } }

  .choice-option {
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-width: 0;                 /* a dropzone must not widen its column */
    padding: 16px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface-sunken);
  }
  .choice-option h3 { font-size: 14px; font-weight: 500; color: var(--text); margin: 0; }
  .choice-option > p { font-size: 13px; color: var(--text-muted); margin: 0; line-height: 1.5; }
  .choice-option .hint { font-size: 12px; color: var(--text-faint); margin: 0; }
  /* a third of a card is no room for 28px of padding around the prompt */
  .choice-option .dropzone { padding: 20px 12px; }

  /* ---------- editable history table ---------- */

  .edit-table input {
    font-family: 'Roboto Mono', monospace;
    font-size: 13.5px;
    padding: 7px 10px;
    width: 100%;
  }
  .edit-table td { padding: 8px 10px; }
  .edit-table th { padding-bottom: 10px; }
  .edit-table tbody tr:hover td { background: transparent; }

  .icon-btn {
    appearance: none;
    background: transparent;
    border: none;
    color: var(--text-muted);
    cursor: pointer;
    padding: 6px;
    border-radius: 50%;
    display: inline-flex;
    transition: background-color 0.15s ease, color 0.15s ease;
  }
  .icon-btn:hover { color: var(--danger); background: var(--danger-soft); }

  /* The bin's in-place "are you sure" (edit-history.js): the action cell is
     the containing block, so the confirm group can anchor to its right edge
     and grow left without ever changing what width the cell itself asks
     the table for. table-layout stays auto and nothing here sets a width -
     w-36 on the header cell already pins the column, same as before this
     row had two states. */
  .row-action { position: relative; }

  .row-delete-confirm {
    position: absolute;
    top: 50%;
    right: 0;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    gap: 6px;
    /* Its own background: the Note input moves out of the way (below), but
       this still sits over whatever the row's Source cell draws under it. */
    padding: 4px 6px;
    border-radius: 6px;
    background: var(--surface);
    box-shadow: var(--shadow-2);
    white-space: nowrap;
    z-index: 1;
  }

  /* The Note input on the row that is asking: pushed left out from under
     the buttons above rather than left to sit under them, so what was
     typed there stays readable. edit-history.js sets --confirm-shift to
     exactly how far the buttons actually overlap it - measured off their
     real rendered width, since "Delete IDS?" is only as wide as its own
     font makes it - and clears the property again on Cancel/Escape, which
     is what brings the input back to its normal place. Pushed this far
     left it lands on the Source cell, which is why it needs the raised
     stacking order and its own background - input[type="text"] already
     paints one further up this file; repeated here only as a reminder that
     this rule now depends on it. */
  .edit-table input[name="note[]"] {
    position: relative;
    z-index: 2;
    background: var(--surface);
    transform: translateX(calc(-1 * var(--confirm-shift, 0px)));
  }
  @media (prefers-reduced-motion: no-preference) {
    .edit-table input[name="note[]"] { transition: transform 0.15s ease; }
  }

  .add-row-btn {
    display: flex;
    align-items: center;
    gap: 8px;
    font: inherit;
    font-size: 14px;
    font-weight: 500;
    letter-spacing: 0.01em;
    color: var(--accent);
    background: transparent;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    padding: 9px 12px;
    margin-top: 4px;
    transition: background-color 0.15s ease;
  }
  .add-row-btn:hover { background: var(--accent-state); text-decoration: none; }

  /* ---------- misc icons (inline svg, not emoji) ---------- */
  svg.i { flex: none; }

  /* ==========================================================================
     Utilities.

     Every one of these was a style="" attribute on an element in a template
     until the Content-Security-Policy went in - `style-src 'self'` refuses
     inline style attributes, so the one-off widths, margins and alignments
     the screens carried had to become names. They are deliberately literal
     rather than clever: a class called .w-150 is the same 150px it replaced,
     which is what makes the move reviewable against the old markup.

     Nothing here uses !important. The phone rules at the foot of this file do,
     and they still win, because they come later and are more specific than a
     single utility class.
     ========================================================================== */

  .flush { margin: 0; }
  .text-muted { color: var(--text-muted); }
  .text-right { text-align: right; }
  .strong { font-weight: 600; }
  .small { font-size: 12.5px; }

  /* the second line under a status line, on the two waiting screens */
  .detail-line { font-size: 12.5px; color: var(--text-muted); }

  /* the sentence that introduces a card, under its heading */
  .lede { margin: 0; font-size: 13.5px; color: var(--text-muted); }
  .lede.pull-up { margin-top: -10px; }   /* sits right under an h2 */

  /* a quieter second heading inside a card ("Or enter counts by hand") */
  .subhead {
    margin: 0;
    font-size: 12.5px;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--text-faint);
  }

  /* the logo and wordmark, centred, on the login-style cards */
  .brand-row { display: flex; align-items: center; justify-content: center; gap: 10px; }

  .push-right { margin-left: auto; }

  .mt-8 { margin-top: 8px; }
  .mt-12 { margin-top: 12px; }
  .mt-14 { margin-top: 14px; }
  .mt-20 { margin-top: 20px; }
  .mb-6 { margin-bottom: 6px; }
  .mb-10 { margin-bottom: 10px; }
  .mb-18 { margin-bottom: 18px; }

  /* a stack of form fields inside a narrow column, tighter than the 20px
     gap .stack uses between whole cards */
  .stack.gap-12 { gap: 12px; }

  .row.middle { align-items: center; }
  .row.bottom { align-items: flex-end; }
  .row.end { justify-content: flex-end; }
  .row.between { justify-content: space-between; }
  .row.gap-8 { gap: 8px; }

  .maxw-460 { max-width: 460px; }
  .maxw-640 { max-width: 640px; }
  .maxw-720 { max-width: 720px; }
  .maxw-760 { max-width: 760px; }

  .w-36 { width: 36px; }
  .w-80 { width: 80px; }
  .w-90 { width: 90px; }
  .w-100 { width: 100px; }
  .w-110 { width: 110px; }
  .w-140 { width: 140px; }
  .w-150 { width: 150px; }
  .w-170 { width: 170px; }
  .w-220 { width: 220px; }

  /* the shortfall / overpayment lists inside a callout */
  .callout .shortfall-list { margin: 6px 0 0; padding-left: 18px; }
  .callout .callout-line { margin-top: 6px; }
  /* The overpayment notice on the fee result (S10, ruling R4): its first
     line is the purple informational text, and the figures and the line
     telling the person what to pay now are ordinary body text. */
  .callout.info.overpaid .shortfall-list,
  .callout.info.overpaid .callout-line { color: var(--text); font-weight: 400; }

  /* ==========================================================================
     Phones (360–640px). Everything here is additive: nothing above this line
     changes at desktop widths. Two earlier `max-width: 640px` one-liners
     (.facts, .choice-grid) stay where they are, next to their base rules.
     ========================================================================== */

  @media (max-width: 640px) {

    /* --- shell + top bar --- */

    .shell { padding: 0 16px 64px; }

    .site-footer { padding: 16px 16px 28px; }

    .topbar { padding: 7px 16px; gap: 8px; }

    .brand { gap: 8px; min-width: 0; }
    .brand-logo { height: 28px; }
    .brand-name { font-size: 20px; }
    .brand .sub { display: none; }   /* "37 CFR 1.17(v)" strapline */

    /* the signed-in address is the first thing to go; the log-out button stays */
    .screen-nav .user-email { display: none; }
    .screen-nav { gap: 0; }
    .screen-nav button,
    .screen-nav a.btn-ghost,
    .screen-nav button.btn-ghost { padding: 6px 8px; }

    /* --- cards + headings --- */

    .card { padding: 16px; }
    .card-table { padding: 8px 12px; }

    h1 { font-size: 21px; margin-bottom: 20px; }

    .callout { padding: 12px 14px; gap: 10px; }

    .card ul { padding-left: 20px; }   /* privacy.html / terms.html prose */

    /* --- rows and forms ---
       Several templates put a width or an alignment utility on a form-row
       child (.w-140, .w-170, .push-right, .row.bottom). Those were inline
       styles once, and `!important` was the only way to beat them; it is kept
       now that they are classes because it says plainly that at this width
       the column layout wins whatever a screen asked for. Used here and
       nowhere else in this block. */

    .row { flex-wrap: wrap; }
    .spread { flex-wrap: wrap; }

    .row.wrap { flex-direction: column; align-items: stretch !important; }
    .row.wrap > label.field,
    .row.wrap > .btn,
    .row.wrap > button,
    .row.wrap > .search-input,
    .row.wrap > form {
      width: 100% !important;
      min-width: 0;
      margin-left: 0 !important;
    }

    .row > form { width: 100%; }   /* price_result.html: "record" form gets its own line */
    .search-input { min-width: 0; }

    /* The fields are stacked here, so there is nothing left to keep level:
       the date echo goes back under its own input, in the flow. */
    .row.echo-row { padding-bottom: 0; }
    .row.echo-row .date-echo { position: static; margin-top: 0; white-space: normal; }

    /* --- segmented toggle (ledger.html: item count vs. draft PDF) --- */

    .segmented { display: flex; width: 100%; }
    .segmented button { flex: 1; padding: 8px 6px; font-size: 12.5px; }

    /* --- dropzone --- */

    .dropzone { padding: 20px 14px; }

    /* --- tables ---
       They keep scrolling horizontally inside .table-scroll; only the cell
       padding is tightened so more columns fit before that kicks in. */

    thead th { padding-left: 10px; padding-right: 10px; }
    tbody td { padding-left: 10px; padding-right: 10px; }
    details.section thead th { padding: 10px 10px 8px; }
    .edit-table td { padding: 8px 6px; }

    /* The one line that will not fit: "Mismatch: $0 paid, $200 due (>50
       refs.)" under a numeric column at 360px. Held on one line it drags
       the whole table sideways; wrapped at the comma it costs a row two
       lines of its own height and nothing else moves. */
    .fee-line { white-space: normal; }

    /* --- review screens --- */

    .unc-row { flex-wrap: wrap; }
    .unc-row .snippet { flex: 1 1 100%; }
    .unc-row select { width: 100%; flex: 1 1 100%; }   /* was a fixed 176px */

    .total-override { gap: 10px; padding: 14px; }
    .total-override .hint { margin-left: 0; flex: 1 1 100%; }

    /* --- result screen (price_result.html) ---
       52px is wider than a 360px viewport once the amount hits five figures. */

    .result-hero { padding: 28px 8px 24px; }
    .result-hero .amount { font-size: 40px; }

    /* --- the centred one-card screens --- */

    /* Same sum with the phone's own numbers: a 42px top bar (7 + 28 + 7),
       the same 36px gutter, 64px of shell padding and a 62.75px footer (16 +
       28 of padding around the one 18.75px line of links, which fits on one
       line even at 320px) - 204.75px, rounded up to 205. */
    .center-viewport { min-height: calc(100vh - 205px); padding-top: 12px; }
  }

  /* ==========================================================================
     Touch targets — coarse pointers only, so a desktop mouse never sees this.
     Brings the interactive elements up to roughly 44px of effective height.
     ========================================================================== */

  @media (hover: none) and (pointer: coarse) {

    .btn, .btn-sm { min-height: 44px; }
    .fb-reply-send, .fba-send { min-height: 40px; }

    .screen-nav button,
    .screen-nav a.btn-ghost,
    .screen-nav button.btn-ghost { min-height: 44px; }

    .segmented button { min-height: 44px; }
    .add-row-btn { min-height: 44px; }
    details.section > summary { min-height: 44px; }

    .icon-btn {
      min-width: 44px;
      min-height: 44px;
      align-items: center;
      justify-content: center;
    }

    /* A finger-sized target that still draws as a small icon. */
    .pencil-btn {
      padding: 14px;
      margin: -14px -14px -4px -8px;
      min-width: 44px;
      min-height: 44px;
      align-items: center;
      justify-content: center;
    }

    /* 16px inputs also stop iOS Safari zooming in on focus.
       input[type="file"] is deliberately absent — the upload inputs are
       hidden at 1x1px and must stay that way. */
    input[type="text"],
    input[type="email"],
    input[type="number"],
    input[type="search"],
    input[type="date"],
    select,
    textarea {
      min-height: 44px;
      font-size: 16px;
    }
    .edit-table input { font-size: 16px; }
  }

  /* ---------- the USPTO fetch list (S14) ---------- */
  /* --text-muted, not --text-faint: grey, but still 4.5:1 on the row (WCAG AA). */
  .fetch-table tr.in-ledger td { color: var(--text-muted); }
  .fetch-table tr.dup-choice td { padding-top: 0; color: var(--danger); font-size: 13px; }
  .dup-warning { font-weight: 500; margin-right: 8px; }
  .dup-option { position: relative; cursor: pointer; color: var(--danger); text-decoration: underline; }
  .dup-option input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }
  .dup-option:has(input:checked) { font-weight: 600; text-decoration: none; }
  .dup-option:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
  .dup-sep { margin: 0 6px; color: var(--text-faint); }
  .back-link { font-size: 13.5px; color: var(--accent); text-decoration: none; }
  .back-link:hover { text-decoration: underline; }

  /* The app's own "leave this page?" dialog on the auto-saving pages. */
  .leave-dialog {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 20px;
    max-width: 420px;
    color: var(--text);
    box-shadow: var(--shadow-2);
  }
  .leave-dialog::backdrop { background: rgba(32, 33, 36, 0.4); }
  .leave-dialog p { margin: 0 0 16px; font-size: 14px; line-height: 1.5; }

  /* ---------- files left waiting for review ---------- */
  /* The ledger's "N files are waiting for your review" line and the mark on
     the applications list (templates/ledger.html, choose.html). The line
     itself is a plain .callout.info; all it adds is a "Continue reviewing"
     that is sometimes a form - files never made into a batch are posted to
     begin-review - and must then look exactly like the link it stands for. */
  .waiting-reviews-form { display: inline; margin: 0; }
  .waiting-reviews-go {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: var(--accent-strong);
    text-decoration: underline;
    cursor: pointer;
  }
  .waiting-reviews-go:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
  .app-row-name + .chip.waiting { margin-top: 4px; }
  .chip.waiting { background: var(--accent-soft); color: var(--accent-strong); border-color: var(--accent-soft-border); }
  .chip.waiting .dot { background: var(--accent); }

  /* ---------- table pages (static/js/table-pager.js) ---------- */
  /* The bar above and below a long table: where the rows on screen sit in
     the whole, the page buttons, and the page size. The script builds it,
     so without one there is no bar and every row shows. It wraps on a
     phone, and every button is a 40px target. */
  tr[data-paged-out] { display: none; }

  .pager {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px 16px;
    padding: 8px 0;
    font-size: 13px;
    color: var(--text-muted);
  }
  /* Paged from the bottom bar, the top one is scrolled to - clear of the
     sticky top bar. */
  .pager-top { scroll-margin-top: 80px; }
  .pager-count { font-variant-numeric: tabular-nums; }
  .pager-pages { display: flex; flex-wrap: wrap; align-items: center; gap: 2px; }
  .pager-pages button {
    appearance: none;
    font: inherit;
    font-weight: 500;
    min-width: 40px;
    min-height: 40px;
    padding: 0 10px;
    border: 1px solid transparent;
    border-radius: 4px;
    background: transparent;
    color: var(--accent);
    cursor: pointer;
    transition: background-color 0.15s ease;
  }
  .pager-pages button:hover { background: var(--accent-state); }
  .pager-pages button[aria-current="page"] {
    background: var(--accent-soft);
    border-color: var(--accent-soft-border);
    color: var(--accent-strong);
  }
  .pager-pages button:disabled { background: transparent; color: var(--text-faint); cursor: default; }
  .pager-gap { min-width: 24px; text-align: center; color: var(--text-faint); }
  .pager-size { display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; }
  .pager-size select { min-height: 40px; padding: 0 10px; font-size: 13px; }
  @media (max-width: 640px) {
    .pager { justify-content: center; }
  }

  /* ---------- data subject access request form (/privacy/request) ---------- */

  .dsar-form fieldset { border: 0; margin: 0; padding: 0; min-width: 0; display: flex; flex-direction: column; gap: 10px; }
  .dsar-field { display: flex; flex-direction: column; gap: 6px; }
  .dsar-label, .dsar-form label.field > span:first-child { font-size: 13px; font-weight: 500; color: var(--text-muted); padding: 0; }
  .dsar-website { font-size: 14px; color: var(--text); }
  .dsar-required { color: var(--danger); }
  .dsar-form label.field input,
  .dsar-form label.field select { font-family: inherit; }
  .dsar-form textarea { width: 100%; resize: vertical; }
  .dsar-form .check-row.dsar-choice { align-items: flex-start; }
  .dsar-form .check-row.dsar-choice input { flex: none; margin-top: 2px; accent-color: var(--accent); }
  .dsar-form .hint.over-limit { color: var(--danger); }
  .dsar-error-list { margin: 6px 0 0; padding-left: 20px; }
