/*
 * One design system for the whole app: a gray canvas, a light sidebar sitting
 * directly on it, and the page as a white rounded sheet. Everything derives
 * from the token block so both themes stay in lockstep. Each colour token is one
 * light-dark(light, dark) pair, so a theme is never defined twice. Which half
 * applies is `color-scheme`: both by default, so the OS decides; the Theme
 * setting writes data-theme="light" or "dark" on <html> (server-rendered, so
 * the first paint is right) and that pins it. `color-scheme` also makes form
 * controls and scrollbars follow.
 *
 * Served from /static/ under a content-hashed name — see ui/assets.ts. It is a
 * real stylesheet rather than a string in a .ts module so that every editor,
 * formatter and language server treats it as one, and so that the page can
 * carry a Content-Security-Policy with no 'unsafe-inline'.
 */
@font-face {
  font-family: "InterVariable";
  src: url("/static/fonts/InterVariable.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
:root {
  color-scheme: light dark;
  --nav-w: 64px; --nav-w-open: 200px;
  /* The palettes, each value written once. A token the app uses (--bg, below) is
     light-dark() of its two primitives: --light-X is the light value, --dark-X
     the dark one. `color-scheme` picks the half; the Theme setting pins it
     (rules after this block). Cream is the light palette; a different one
     replaces only --light-* (see [data-palette="gray"]). --warn is the text on
     --warn-bg, the awaiting-approval badge: #8f6311 is the old amber's hue at
     4.79:1, where the old one was 4.21:1. */
  /* surfaces */
  --light-bg:#faf6ec; --dark-bg:#181816;
  --light-surface:#fffdf8; --dark-surface:#222220;
  --light-surface-2:#f8f3e6; --dark-surface-2:#2a2a28;
  --light-surface-3:#f1ead9; --dark-surface-3:#323230;
  /* ink */
  --light-ink:#100e0a; --dark-ink:#ececea;
  --light-ink-muted:#504b41; --dark-ink-muted:#a3a39f;
  --light-ink-subtle:#766f62; --dark-ink-subtle:#7c7c78;
  --light-ink-invert:#ffffff; --dark-ink-invert:#1a1a1a;
  /* lines */
  --light-line:#ddd4c0; --dark-line:#31312e;
  --light-line-strong:#c9bfa8; --dark-line-strong:#3c3c39;
  /* hover tints */
  --light-tint:rgba(40,30,10,.05); --dark-tint:rgba(255,255,255,.05);
  --light-tint-strong:rgba(40,30,10,.08); --dark-tint-strong:rgba(255,255,255,.09);
  /* action */
  --light-primary:#100e0a; --dark-primary:#ececea;
  --light-primary-hover:#333331; --dark-primary-hover:#ffffff;
  --light-accent:#4a6d8c; --dark-accent:#8fb0cc;
  --light-accent-hover:#3a5a76; --dark-accent-hover:#a9c4da;
  --light-accent-light:#edf1f5; --dark-accent-light:#1e2933;
  --light-focus-ring:rgba(74,109,140,.25); --dark-focus-ring:rgba(143,176,204,.3);
  /* status */
  --light-ok:#0f7b4d; --dark-ok:#5ec98f;
  --light-ok-bg:#e7f4ec; --dark-ok-bg:#17301f;
  --light-ok-line:#c3e2d0; --dark-ok-line:#25482f;
  --light-warn:#8f6311; --dark-warn:#dcae55;
  --light-warn-bg:#faf3e2; --dark-warn-bg:#322810;
  --light-warn-line:#ecd9a8; --dark-warn-line:#5a4618;
  --light-bad:#b42318; --dark-bad:#f08a80;
  --light-bad-bg:#fbeae8; --dark-bad-bg:#3a1d1a;
  --light-bad-line:#f0c7c1; --dark-bad-line:#57302b;
  /* shadows */
  --light-shadow-1:rgba(40,30,10,.07); --dark-shadow-1:rgba(0,0,0,.35);
  --light-shadow-2:rgba(40,30,10,.08); --dark-shadow-2:rgba(0,0,0,.4);
  --light-shadow-2b:rgba(40,30,10,.06); --dark-shadow-2b:rgba(0,0,0,.3);
  /* what the app uses */
  --bg:light-dark(var(--light-bg),var(--dark-bg));
  --surface:light-dark(var(--light-surface),var(--dark-surface));
  --surface-2:light-dark(var(--light-surface-2),var(--dark-surface-2));
  --surface-3:light-dark(var(--light-surface-3),var(--dark-surface-3));
  --ink:light-dark(var(--light-ink),var(--dark-ink));
  --ink-muted:light-dark(var(--light-ink-muted),var(--dark-ink-muted));
  --ink-subtle:light-dark(var(--light-ink-subtle),var(--dark-ink-subtle));
  --ink-invert:light-dark(var(--light-ink-invert),var(--dark-ink-invert));
  --line:light-dark(var(--light-line),var(--dark-line));
  --line-strong:light-dark(var(--light-line-strong),var(--dark-line-strong));
  --tint:light-dark(var(--light-tint),var(--dark-tint));
  --tint-strong:light-dark(var(--light-tint-strong),var(--dark-tint-strong));
  --primary:light-dark(var(--light-primary),var(--dark-primary));
  --primary-hover:light-dark(var(--light-primary-hover),var(--dark-primary-hover));
  --accent:light-dark(var(--light-accent),var(--dark-accent));
  --accent-hover:light-dark(var(--light-accent-hover),var(--dark-accent-hover));
  --accent-light:light-dark(var(--light-accent-light),var(--dark-accent-light));
  --focus-ring:light-dark(var(--light-focus-ring),var(--dark-focus-ring));
  --ok:light-dark(var(--light-ok),var(--dark-ok));
  --ok-bg:light-dark(var(--light-ok-bg),var(--dark-ok-bg));
  --ok-line:light-dark(var(--light-ok-line),var(--dark-ok-line));
  --warn:light-dark(var(--light-warn),var(--dark-warn));
  --warn-bg:light-dark(var(--light-warn-bg),var(--dark-warn-bg));
  --warn-line:light-dark(var(--light-warn-line),var(--dark-warn-line));
  --bad:light-dark(var(--light-bad),var(--dark-bad));
  --bad-bg:light-dark(var(--light-bad-bg),var(--dark-bad-bg));
  --bad-line:light-dark(var(--light-bad-line),var(--dark-bad-line));
  --shadow-1:0 1px 2px light-dark(var(--light-shadow-1),var(--dark-shadow-1));
  --shadow-2:0 1px 3px light-dark(var(--light-shadow-2),var(--dark-shadow-2)), 0 4px 14px light-dark(var(--light-shadow-2b),var(--dark-shadow-2b));
  /* scales */
  --r-sm:6px; --r-md:8px; --r-lg:12px;
  /* Both native-select shells and enhanced picker triggers use this mark. */
  --select-chevron:linear-gradient(45deg,transparent 38%,currentColor 38% 62%,transparent 62%),
                   linear-gradient(135deg,transparent 38%,currentColor 38% 62%,transparent 62%);
  /* Two control sizes in the whole app, and only two.
     A form's controls are 34px — button, a.btn, input and select all state
     line-height:1.5 with 7px of vertical padding so a Save comes out exactly
     level with the field above it.
     Everything that sits *inside* something else — a table row, the filter
     band — is one tier down at 28px, and that is what this token is. Stated
     once because it had drifted into four values that were all nearly the
     same: 5px 14px in the filter band's actions, 5px 12px on its More toggle,
     5px 10px on its fields, and the full 7px 16px on every button in a table
     row. The payment schedule is the case that made it a rule — three
     full-size buttons in one cell, stacked, gave a row holding a date and an
     amount the height of four rows of figures. */
  --pad-sm:4px 10px;
}
/* Today's gray light theme, kept for a future theme switcher and not offered in
   the UI: nothing in the app sets data-palette yet. It overrides only the light
   primitives, so dark is untouched. The values are the gray theme's, except
   --light-warn, which keeps the darker #8f6311 because the old #996c14 failed
   WCAG AA (4.21:1) on its badge. */
:root[data-palette="gray"] {
  --light-bg:#ebebe9; --light-surface:#ffffff; --light-surface-2:#f7f7f5; --light-surface-3:#efefed;
  --light-ink:#1a1a1a; --light-ink-muted:#6f6f6c; --light-ink-subtle:#9b9b97; --light-ink-invert:#ffffff;
  --light-line:#e4e4e1; --light-line-strong:#d6d6d2;
  --light-tint:rgba(26,26,26,.05); --light-tint-strong:rgba(26,26,26,.08);
  --light-primary:#1a1a1a; --light-primary-hover:#333331; --light-accent:#4a6d8c; --light-accent-hover:#3a5a76; --light-accent-light:#edf1f5; --light-focus-ring:rgba(74,109,140,.25);
  --light-ok:#0f7b4d; --light-ok-bg:#e7f4ec; --light-ok-line:#c3e2d0; --light-warn:#8f6311; --light-warn-bg:#faf3e2; --light-bad:#b42318; --light-bad-bg:#fbeae8; --light-bad-line:#f0c7c1;
  --light-shadow-1:rgba(20,20,19,.05); --light-shadow-2:rgba(20,20,19,.07); --light-shadow-2b:rgba(20,20,19,.05);
}
/* The Theme setting. Absent means System, which is the default above. */
:root[data-theme=light] { color-scheme: light; }
:root[data-theme=dark] { color-scheme: dark; }
* { box-sizing:border-box; }
/* The UA's [hidden] rule loses to any author display (e.g. a.btn), and the
   live filter toggles hidden on elements that have one. */
[hidden] { display:none !important; }
body { margin:0; font:12px/1.5 "InterVariable",-apple-system,"Hiragino Sans","Noto Sans JP",sans-serif;
       background:var(--bg); color:var(--ink);
       font-feature-settings:"cv05","cv11"; -webkit-font-smoothing:antialiased; }
/* htmx's own indicator rules, which it would otherwise inject as an inline
   <style> at boot — refused by style-src. They are verbatim what htmx 2.0.10
   ships, and `includeIndicatorStyles:false` in Layout's meta config is what
   stops the duplicate. The one user is the document upload button
   (hx-indicator in routes/entities.tsx); if that stops spinning, look here. */
.htmx-indicator { opacity:0; visibility:hidden; }
.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator { opacity:1; visibility:visible;
                               transition:opacity 200ms ease-in; }

/* An inline svg is display:inline, which puts it on the text baseline and
   leaves descender space under every icon — a row of them sits a pixel or two
   low and the gap is uneven. This was a style="display:block" on the svg
   itself until the page grew a Content-Security-Policy; the attribute is what
   style-src refuses, not the declaration. */
.ico svg { display:block; }

/* ── sidebar ─────────────────────────────────────────────────────── */
/* The sidebar sits directly on the gray canvas — no fill, no border. Its rows
   are quiet by default; the active one becomes a small white card. */
.shell { display:flex; min-height:100vh; }
.side { width:var(--nav-w); flex:0 0 auto; display:flex; flex-direction:column;
        padding:12px 10px; overflow-x:hidden; overflow-y:auto;
        position:sticky; top:0; height:100vh; transition:width .2s ease; }
body[data-nav="open"] .side { width:var(--nav-w-open); }

/* Org/user card at the top: who you are, where you are, and the collapse
   control — the sidebar's one always-elevated element. */
.side .org { display:flex; align-items:center; gap:10px; padding:8px;
             background:var(--surface); border:1px solid var(--line);
             border-radius:var(--r-md); box-shadow:var(--shadow-1); }
.side .org .avatar { width:26px; height:26px; border-radius:7px; background:var(--primary);
                     color:var(--ink-invert); font-size:12px; font-weight:600; display:flex;
                     align-items:center; justify-content:center; flex:none; }
/* A column, stated rather than inherited: the table cells' `.who` (further
   down) is a flex row, and it matches this card too, so with the language
   switch as a third child it squeezed the name and email out of sight. */
.side .org .who { flex:1; min-width:0; display:flex; flex-direction:column; align-items:stretch; gap:0; max-width:none; }
.side .org .name { font-size:12px; font-weight:600; line-height:1.3;
                   white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.side .org .mail { font-size:11px; color:var(--ink-muted); line-height:1.3;
                   white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
/* Text-tone utilities, so a colour decision is a class rather than a style
   attribute repeated across route modules. */
.danger-text { color:var(--bad); }
.muted-text { color:var(--ink-muted); }
.subtle-text { color:var(--ink-subtle); }
.nowrap { white-space:nowrap; }
.gap-left { margin-left:8px; }
.inline-form { display:inline; }
.side .org .toggle { flex:none; display:flex; padding:4px; background:none; border:none;
                     border-radius:6px; color:var(--ink-subtle); cursor:pointer; }
.side .org .toggle:hover { color:var(--ink); background:var(--tint); }
/* Collapsed: the card folds to a column — avatar with the toggle underneath. */
body:not([data-nav="open"]) .side .org { flex-direction:column; gap:6px; padding:8px 6px; }
body:not([data-nav="open"]) .side .org .who { display:none; }

/* Account block at the foot: her own things (who she is, her language, signing
   out), in one fixed place, apart from the organisation card above. The items
   list above it is flex:1, which pins this to the bottom of the rail. */
.side .acct { display:flex; flex-direction:column; gap:8px; padding:10px; margin-top:10px;
              background:var(--surface); border:1px solid var(--line);
              border-radius:var(--r-md); box-shadow:var(--shadow-1); }
.side .acct .top { display:flex; align-items:center; gap:10px; }
.side .acct .avatar { width:28px; height:28px; border-radius:50%; background:var(--accent);
                      color:var(--ink-invert); font-size:12px; font-weight:600; display:flex;
                      align-items:center; justify-content:center; flex:none; }
.side .acct .who { flex:1; min-width:0; display:flex; flex-direction:column; align-items:stretch; gap:0; max-width:none; }
.side .acct .name { font-size:12px; font-weight:600; line-height:1.3;
                    white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.side .acct .mail { font-size:11px; color:var(--ink-muted); line-height:1.3;
                    white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
/* A setting is a small muted label above its control, in every language. Beside
   each other, English "Language" plus the pill needed more than the block's
   content width and the pill stuck out; stacked, the control takes the full
   width and its segments share it equally. */
.side .acct .srow { display:flex; flex-direction:column; align-items:stretch; gap:4px;
                    font-size:11px; color:var(--ink-muted); }
.side .acct .lang { display:flex; padding:2px; }
.side .acct .lang button { flex:1; min-width:0; text-align:center; padding:2px 8px; font-size:11px;
                           letter-spacing:0; white-space:nowrap; }
.side .acct .lang.theme button { padding:2px 7px; }
.side .acct .lang.theme .ico svg { width:13px; height:13px; }
.side .acct form[action="/logout"] { display:block; }
.side .acct .signout:hover { background:var(--tint); border-color:var(--line-strong); }
.side .acct .signout { display:flex; align-items:center; justify-content:center; gap:7px; width:100%; }
/* Collapsed: the avatar and an icon-only sign-out; the name, email and
   language are a click away in the expanded rail and in her account settings. */
body:not([data-nav="open"]) .side .acct { padding:8px 6px; align-items:center; }
body:not([data-nav="open"]) .side .acct .who,
body:not([data-nav="open"]) .side .acct .srow,
body:not([data-nav="open"]) .side .acct .signout .label { display:none; }
body:not([data-nav="open"]) .side .acct .signout { padding:6px; }

/* Nav search: the button that opens the ⌘K palette. It looks like a text field
   because that is what it does, but it is a button — there is nothing to type
   into here, the typing happens in the palette. JS-only, so it stays hidden
   until the script un-hides it. */
.side .nav-q { display:flex; flex-direction:row; align-items:center; gap:8px;
               width:100%; padding:7px 10px; margin-top:10px; text-align:left;
               background:var(--surface); border:1px solid var(--line);
               border-radius:var(--r-sm); color:var(--ink-subtle);
               font:inherit; font-size:12px; cursor:pointer; }
.side .nav-q:hover { border-color:var(--line-strong); color:var(--ink-muted); }
.side .nav-q .label { flex:1; min-width:0; overflow:hidden;
                      text-overflow:ellipsis; white-space:nowrap; }
.side .nav-q kbd { flex:none; font-family:inherit; font-size:10px; font-weight:600;
                   color:var(--ink-subtle); background:var(--surface-2);
                   border:1px solid var(--line); border-radius:5px; padding:2px 5px; }
body:not([data-nav="open"]) .side .nav-q { display:none; }

/* ── ⌘K omnibar ──────────────────────────────────────────────────── */
/* The org's entities, vendors, contracts and invoices, filtered in the browser
   from one payload — so this is a keystroke-latency surface, not a form. */
#omni { position:fixed; inset:0; z-index:60; display:flex; justify-content:center;
        align-items:flex-start; padding:12vh 16px 16px; }
#omni .scrim { position:absolute; inset:0; background:rgba(10,14,20,.45); }
.omni-panel { position:relative; display:flex; flex-direction:column; overflow:hidden;
              width:min(860px,100%); max-height:min(70vh,560px);
              container-type:inline-size;
              background:var(--surface); border:1px solid var(--line-strong);
              border-radius:var(--r-lg); box-shadow:var(--shadow-2);
              animation:omni-in .13s ease-out; }
@keyframes omni-in { from { opacity:0; transform:translateY(-8px) scale(.985); } }
.omni-field { display:flex; align-items:center; gap:10px; flex:none; padding:0 14px;
              border-bottom:1px solid var(--line); color:var(--ink-subtle); }
.omni-field input { flex:1; min-width:0; padding:14px 0; border:none; background:none;
                    font:inherit; font-size:15px; color:var(--ink); }
.omni-field input:focus { outline:none; box-shadow:none; border:none; }
.omni-field input::placeholder { color:var(--ink-subtle); }
.omni-results { flex:1; min-height:0; overflow-y:auto; padding:6px; }
/* Group heading, with the total when it is more than the few rows shown. Sticky
   so a scrolled list still says which group the row under the cursor is in. */
.omni-group { position:sticky; top:-6px; z-index:1;
              display:flex; align-items:center; gap:6px; padding:8px 8px 4px;
              background:var(--surface); font-size:10px; font-weight:600;
              letter-spacing:.05em; text-transform:uppercase; color:var(--ink-subtle); }
.omni-group .n { font-weight:400; letter-spacing:0; text-transform:none; }
.omni-hit { display:flex; align-items:center; gap:10px; padding:7px 8px;
            border-radius:var(--r-sm); cursor:pointer; }
.omni-hit .ico { flex:none; color:var(--ink-subtle); }
.omni-hit .txt { flex:1; min-width:0; }
.omni-hit .t, .omni-hit .s { display:block; white-space:nowrap;
                             overflow:hidden; text-overflow:ellipsis; }
.omni-hit .s { font-size:11px; color:var(--ink-muted); }
/* Between an SPC and one of its own pages on the same row. Subtle, so the row
   still reads as the SPC first and the page second. */
.omni-hit .t .sep { margin:0 .35em; color:var(--ink-subtle); }
.omni-hit .badge { flex:none; }
.omni-hit mark { background:var(--accent-light); color:var(--accent);
                 border-radius:2px; padding:0 1px; font-weight:600; }
.omni-hit[aria-selected="true"] { background:var(--surface-3); }
.omni-hit[aria-selected="true"] .ico { color:var(--ink); }
/* An invoice result: number, then vendor, amount, due date and SPC, then the
   status. Narrow, the middle wraps beneath the number as a second line; wide,
   the pieces sit in columns so a list of one vendor's invoices reads as a
   table and the amounts line up on the right. */
.omni-inv .s { white-space:normal; overflow:visible; line-height:1.5; margin-top:2px; }
.omni-inv .s > span { white-space:nowrap; }
/* Pieces are separated by a space the line can break at, plus a margin. A "・"
   between them cannot start a line, so with one the row refused to wrap and ran
   off the panel. */
.omni-inv .s > span { margin-right:.7em; }
.omni-inv .amt { font-variant-numeric:tabular-nums; color:var(--ink); font-weight:600; }
/* Every .omni-hit .t is nowrap+ellipsis already; the number must not be cut. */
.omni-inv .t { overflow-wrap:anywhere; white-space:normal; }
/* One width for every status, so the columns beside it start in the same place
   on every row; each row is its own grid. */
.omni-inv .badge { min-width:68px; text-align:center; }
.omni-all { padding-left:32px; font-size:12px; font-weight:600; color:var(--accent); }
@container (min-width: 720px) {
  .omni-inv .txt { display:grid; align-items:center; column-gap:10px;
                   grid-template-columns:120px minmax(0,1fr) 110px 100px 150px; }
  .omni-inv .s { display:contents; }
  .omni-inv .t { grid-column:1; grid-row:1; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; font-weight:600; }
  .omni-inv .v { grid-column:2; grid-row:1; overflow:hidden; text-overflow:ellipsis; font-size:13px; color:var(--ink); }
  .omni-inv .spc { grid-column:3; grid-row:1; overflow:hidden; text-overflow:ellipsis; }
  .omni-inv .amt { grid-column:4; grid-row:1; text-align:right; }
  .omni-inv .due { grid-column:5; grid-row:1; }
  .omni-inv .s > span { margin-right:0; }
  .omni-inv .dl { display:none; }
}
.omni-empty { padding:28px 12px; text-align:center; color:var(--ink-muted); }
.omni-field kbd { font-family:inherit; font-size:10px; font-weight:600;
             background:var(--surface-2); border:1px solid var(--line);
             border-radius:4px; padding:1px 4px; }
/* The page behind the palette must not scroll under it. */
body[data-omni] { overflow:hidden; }
@media (prefers-reduced-motion: reduce) { .omni-panel { animation:none; } }

/* Section label between nav groups. */
.side .sect { margin:16px 10px 6px; font-size:10px; font-weight:600;
              letter-spacing:.05em; text-transform:uppercase; color:var(--ink-subtle);
              white-space:nowrap; }
body:not([data-nav="open"]) .side .sect { visibility:hidden; margin:8px 0 0; height:8px; }

.side .item { display:flex; align-items:center; gap:10px; height:34px; padding:0 10px;
              margin-bottom:2px; border-radius:var(--r-md); font-size:12px;
              color:var(--ink-muted); text-decoration:none; white-space:nowrap;
              border:1px solid transparent; background:none; width:100%;
              font-family:inherit; cursor:pointer; transition:color .15s; }
.side .item .label { overflow:hidden; text-overflow:ellipsis; }
.side .item:hover { color:var(--ink); background:var(--tint); }
/* Active row = small white card, per the reference. */
.side .item.active { color:var(--ink); background:var(--surface);
                     border-color:var(--line-strong); box-shadow:var(--shadow-1);
                     font-weight:600; }
.side .item svg { flex-shrink:0; }
/* Collapsed rail: icon-only rows. The label leaves the page entirely — an
   aria-label on the row carries the name for assistive tech, and hovering or
   focusing the icon shows it as a tooltip beside the rail (see .nav-tip). */
body:not([data-nav="open"]) .side .item { justify-content:center; padding:0; }
body:not([data-nav="open"]) .side .item .label { display:none; }

/* The collapsed rail's tooltip. The rail clips its own overflow and scrolls,
   so the bubble cannot live inside it: the script parks one element on <body>
   and positions it against the hovered row. */
.nav-tip { position:fixed; z-index:70; pointer-events:none;
           padding:4px 8px; border-radius:var(--r-sm);
           background:var(--ink); color:var(--ink-invert);
           font-size:11px; line-height:1.4; white-space:nowrap;
           box-shadow:var(--shadow-2); opacity:0; transition:opacity .1s ease; }
.nav-tip[data-on] { opacity:1; }
.ui-button.info-tip-trigger { display:inline-flex; align-items:center; justify-content:center;
  width:24px; height:24px; min-height:0; margin:-4px 0 -4px 1px; padding:0; border:0;
  background:transparent; color:var(--ink-muted); font-size:10px; line-height:1;
  vertical-align:middle; cursor:help; }
/* The button is the 24px hit area (WCAG 2.2 SC 2.5.8); the circle you see stays 15px. */
.info-tip-trigger > span { display:inline-flex; align-items:center; justify-content:center;
  width:15px; height:15px; border:1px solid var(--ink-subtle); border-radius:50%; box-sizing:border-box; }
.info-tip-trigger:focus-visible { outline:2px solid var(--ink); outline-offset:-2px; border-radius:50%; }
.info-popover { position:fixed; z-index:80; max-width:min(300px, calc(100vw - 16px));
  padding:8px 10px; border-radius:var(--r-sm); background:var(--ink); color:var(--ink-invert);
  font-size:12px; line-height:1.5; box-shadow:var(--shadow-2); }
.info-popover[hidden] { display:none; }
#page-banners:empty { display:none; }
@media (prefers-reduced-motion: reduce) { .nav-tip { transition:none; } }

/* The open SPC's tabs, hanging under the SPCs item. The rule runs down from
   the icon's centre, so the group reads as belonging to the row above it.
   Quieter than the items themselves — this is where you are inside a section,
   not which section you are in, and the tab row above the page says it too. */
.side .sub { display:flex; flex-direction:column; margin:0 0 6px 18px;
             padding-left:12px; border-left:1px solid var(--line); }
.side .sub a { padding:5px 8px; margin-bottom:1px; border-radius:var(--r-md);
               font-size:12px; color:var(--ink-subtle); text-decoration:none;
               white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
               transition:color .15s; }
.side .sub a:hover { color:var(--ink); background:var(--tint); }
.side .sub a.on { color:var(--ink); font-weight:600; }
/* Collapsed there is no room for a label, and an unlabelled list of seven
   identical rows is worse than none — the tab row on the page still has them. */
body:not([data-nav="open"]) .side .sub { display:none; }

/* Below this the rail is collapsed whatever the stored preference says: 200px
   of navigation against a 375px screen leaves the page about 50px to render
   into, which no table or form recovers from. The toggle goes with it rather
   than staying on as a control that visibly does nothing. The preference
   itself is untouched — widen the window and the rail comes back open. */
@media (max-width:640px) {
  body[data-nav="open"] .side { width:var(--nav-w); }
  body[data-nav="open"] .side .org { flex-direction:column; gap:6px; padding:8px 6px; }
  body[data-nav="open"] .side .org .who,
  body[data-nav="open"] .side .nav-q { display:none; }
  body[data-nav="open"] .side .acct { padding:8px 6px; align-items:center; }
  body[data-nav="open"] .side .acct .who,
  body[data-nav="open"] .side .acct .srow,
  body[data-nav="open"] .side .acct .signout .label { display:none; }
  body[data-nav="open"] .side .acct .signout { padding:6px; }
  body[data-nav="open"] .side .sect { visibility:hidden; margin:8px 0 0; height:8px; }
  body[data-nav="open"] .side .item { justify-content:center; padding:0; }
  body[data-nav="open"] .side .item .label { display:none; }
  body[data-nav="open"] .side .sub { display:none; }
  .side .org .toggle { display:none; }
}

.side .items { flex:1; display:flex; flex-direction:column; }
/* Language switch: a two-segment pill. On the signed-out pages (top right),
   in the sidebar's account card and on /settings/account: a new person must
   find her language before she can read anything else. */
.lang { display:inline-flex; background:var(--surface-2); border:1px solid var(--line);
        border-radius:var(--r-sm); padding:3px; gap:2px; }
.lang button { background:none; border:none; cursor:pointer; font:inherit;
               font-size:12px; font-weight:500; letter-spacing:.4px;
               padding:4px 12px; border-radius:var(--r-sm); color:var(--ink-muted); }
.lang button:hover { color:var(--ink); }
.lang .on { background:var(--surface); color:var(--ink); cursor:default;
            box-shadow:var(--shadow-1); }
/* The Theme switch is the same pill with an icon in each segment. */
.lang.theme button { display:inline-flex; align-items:center; justify-content:center; padding:4px 9px; }
.lang.theme button.on { cursor:default; }
.lang.theme .ico svg { width:14px; height:14px; }
.lang-bar { display:flex; justify-content:flex-end; padding:16px 24px 0; }

/* ── content ─────────────────────────────────────────────────────── */
/* Two-column page body. Cards keep their own margins, so the grid only has to
   split the columns; it folds to one column when there is no room for two. */
/* minmax(0,…) rather than a bare 1fr: a track's automatic minimum is the
   min-content of what is in it, which a table would set to the sum of its
   columns — the column would refuse to narrow and the card would hang off the
   sheet instead of the table scrolling inside it. */
.cols2 { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:0 16px; align-items:start; }
@media (max-width:900px) { .cols2 { grid-template-columns:minmax(0,1fr); } }

.content { flex:1 1 0%; min-width:0; display:flex; flex-direction:column; }
/* The page itself is a white rounded sheet floating on the gray canvas. Only
   when the sidebar is present: the login page keeps the bare gray canvas. */
main { flex:1; min-width:0; padding:24px 28px 32px; }
.side + .content main { background:var(--surface); border:1px solid var(--line);
                        border-radius:var(--r-lg); margin:12px 12px 0 4px;
                        box-shadow:var(--shadow-1); }
.foot { text-align:right; text-transform:uppercase; color:var(--ink-subtle); font-size:11px; padding:10px 24px 12px; }
.foot a { color:inherit; text-decoration:none; }
.foot a:hover { text-decoration:underline; }

h1 { font-size:24px; font-weight:600; letter-spacing:-.02em; margin:0 0 16px; }
h2 { font-size:15px; font-weight:600; letter-spacing:-.01em; margin:24px 0 8px; }
/* Title block at the top of the sheet: optional breadcrumb, big title, and the
   page-level action cluster on the right. */
.page-head { display:flex; align-items:center; justify-content:space-between; gap:16px;
             margin:0 0 18px; }
.page-head h1 { margin:0; }
.page-head .crumb { margin:0 0 2px; font-size:11px; color:var(--ink-muted); }
.page-head .crumb a { color:inherit; text-decoration:none; }
.page-head .crumb a:hover { text-decoration:underline; }
/* Square ghost icon button — a control with a shape instead of a word. Its one
   use is the pencil that opens a table row for editing, so it is sized to the
   compact tier (28px, the same height as every other control in a row) rather
   than to a form. It carried a 32px size and a comment naming two controls
   that were never built; this is its first caller. */
.btn-icon { display:inline-flex; align-items:center; justify-content:center;
            width:28px; height:28px; padding:0; background:var(--surface);
            color:var(--ink); text-decoration:none;
            border:1px solid var(--line-strong); border-radius:var(--r-sm); }
.btn-icon:hover { background:var(--surface-2); }
/* Refused, the way a disabled tab is: it keeps its place in the row and stops
   being a link, rather than leaving a hole where an operator expects a control. */
.btn-icon.off { opacity:.5; cursor:not-allowed; }
/* A card's own heading sits at its top edge — no leading gap to collapse. */
.card > h2:first-child { margin-top:0; }
/* On the white sheet a card is a quiet container: hairline border, no shadow. */
.card { background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg);
        padding:20px 24px; margin-bottom:16px; }
table { width:100%; border-collapse:collapse; }
/* Every table scrolls inside its own card rather than widening the page. A
   table's minimum width is the sum of its columns, so one long status or a
   column of hashes would otherwise push the whole sheet sideways and leave the
   card's right edge off screen. The negative margin lets a scrolled table run
   to the card's padding edge; at rest nothing about the layout changes. */
.tscroll { overflow-x:auto; margin:0 -24px; padding:0 24px; }
/* Headers label the column and are short by construction — wrapping one only
   makes the header row taller for nothing. Cell content still wraps. */
th { text-align:left; color:var(--ink-muted); font-weight:500; padding:7px 10px; border-bottom:1px solid var(--line); white-space:nowrap; }
td { padding:7px 10px; border-bottom:1px solid var(--line); }
/* Key/value tables put the <th> in the body, so the last row's rule has to
   lift off both cells — scoped to tbody so a <thead> row keeps its underline. */
tr:last-child td, tbody tr:last-child th { border-bottom:none; }
/* No zebra: the hairline rule already separates rows, and a striped table on a
   white sheet reads as a spreadsheet. The hover tint is the row affordance. */
tbody tr:hover td { background:var(--surface-2); }
/* Whole-row link: the <a> inside stays the real, keyboard-reachable target. */
tr.row-link { cursor:pointer; }
tr.row-link a { color:inherit; text-decoration:none; }
th.num, td.num { text-align:right; font-variant-numeric:tabular-nums; }
/* Secondary identifier stacked under a cell's main value (e.g. an invoice
   number under its vendor) — present, but never competing for the scan. */
td .cell-sub { display:block; margin-top:1px; font-size:11px; color:var(--ink-muted); }
/* Same idea, but trailing the value on one line — for a short alias (an SPC's
   bird codename) that reads as part of the name rather than under it. */
td .cell-alias { margin-left:6px; font-size:11px; color:var(--ink-muted); }
a { color:var(--accent); }
/* A badge is one token — never let a two-word status break across lines. Its
   corner matches the other small controls; a full pill reads louder than a
   status deserves next to a rectangular input. */
.badge { display:inline-block; padding:2px 8px; border-radius:var(--r-sm); font-size:11px; font-weight:500; white-space:nowrap; }
/* The reason a record stopped sits on its own line under the status pill, not
   inside it: the pill names the state and nothing else, so one state looks the
   same on every row. The reason is a phrase and may wrap; the pill never does. */
.status-why, .spill { display:inline-flex; flex-direction:column; align-items:flex-start; gap:2px; }
.status-why .why, .spill .why { font-size:11px; color:var(--ink-muted); white-space:normal; }
.badge.ok { background:var(--ok-bg); color:var(--ok); } .badge.warn { background:var(--warn-bg); color:var(--warn); }
.badge.bad { background:var(--bad-bg); color:var(--bad); } .badge.mut { background:var(--surface-2); color:var(--ink-muted); }
/* .inline also wraps the submit/cancel pair at the foot of a stacked form,
   where the gap is what keeps the two buttons from touching. */
.inline { display:flex; gap:10px; flex-wrap:wrap; align-items:flex-end; }
/* A row with no field labels above its controls, where a text link sits beside
   them: bottom-aligning would drop the link below the controls' text. */
.inline.center { align-items:center; }
/* A column of fields. Not `form.stack`: when one Save covers several panels the
   form is the whole page, and it is the fields inside each panel that stack. */
.stack { display:flex; flex-direction:column; gap:12px; align-items:flex-start; max-width:420px; }
.stack label { align-self:stretch; }
.stack > .ui-field { align-self:stretch; }
/* A textarea can sit directly in the stack because its placeholder is its
   prompt. Stretch it like a labelled field; otherwise its browser intrinsic
   width leaves a small editor floating inside a full-width form column. */
.ui-textarea { align-self:stretch; width:100%; }
/* The same column of fields, laid out two abreast. For a form long enough that
   its submit is off screen when it opens: the cost line's is thirteen controls,
   every one of them short, and at one column reaching Save is a page and a half
   of scrolling past fields nobody is changing.
   Only where there is room — a sheet is 460px wide and keeps one column unless
   it is `wide`, which is why this is a prop on the form rather than a rule on
   .stack.
   Anything that is not a field spans both: the submit row and the explanatory
   paragraph belong to the form, not to a column of it. */
.stack.two { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr);
             gap:12px 20px; max-width:760px; align-items:start; }
.stack.two > :not(label):not(.ui-field) { grid-column:1 / -1; }
@media (max-width:640px) { .stack.two { grid-template-columns:minmax(0,1fr); } }
/* The page's own submit row, under the last panel rather than inside it. */
.page-actions { margin-top:16px; }
/* Details and access are sections of one edit card because one Save persists
   both. Archive keeps its own card boundary and independent submission. */
.spc-settings-section h3 { margin:0 0 12px; font-size:12px; font-weight:600; }
.spc-settings-section + .spc-settings-section { margin-top:24px; padding-top:20px; border-top:1px solid var(--line); }
.spc-settings-actions { margin-top:20px; }
/* Counts stay beside their team, quieter than the name, rather than reading
   as an unrelated column on the far side of the form. */
label.spc-team-access { flex-wrap:wrap; }
.spc-team-access .team-name { overflow-wrap:anywhere; }
.spc-team-access .team-count { font-size:11px; color:var(--ink-muted); white-space:nowrap; }
.ui-label,.ui-field { display:flex; flex-direction:column; gap:6px; font-size:12px; font-weight:500; color:var(--ink); }
.ui-label[data-control-size="compact"],.ui-field[data-control-size="compact"] { font-size:11px; font-weight:400; color:var(--ink-muted); gap:4px; }
/* Each primitive selects its density. No table/filter ancestor sets padding. */
[data-control-size] { --field-padding:7px 12px; --button-padding:7px 16px; --select-chevron-inset:12px; }
[data-control-size="compact"] { --field-padding:var(--pad-sm); --button-padding:var(--pad-sm); --select-chevron-inset:10px; }
/* line-height is stated because a control does not inherit the body's — without
   it an input comes out a few pixels shorter than the button beside it. */
.ui-input,.ui-select { padding:var(--field-padding); border:1px solid var(--line-strong); border-radius:var(--r-sm);
               font-size:12px; font-family:inherit; line-height:1.5;
               background:var(--surface); color:var(--ink); }
.ui-input:focus,.ui-select:focus { border-color:var(--accent); outline:none; box-shadow:0 0 0 3px var(--focus-ring); }
/* Single-choice selects use one chevron everywhere. The native arrow owns its
   inset and rendered four pixels from the edge on macOS while the field text
   sat twelve pixels from the other side. Two small gradients draw the mark in
   currentColor, so it follows light, dark and disabled text without another
   asset. Multiple selects have no arrow; Pick hides its native select under a
   searchable control and must keep that validation target otherwise intact. */
.ui-select:not([multiple]):not(.ms-native) {
  appearance:none; -webkit-appearance:none; padding-right:calc(var(--select-chevron-inset) + 18px);
  background-image:var(--select-chevron);
  background-position:
    calc(100% - var(--select-chevron-inset) - 8px) 50%,
    calc(100% - var(--select-chevron-inset) - 4px) 50%;
  background-size:4px 4px,4px 4px; background-repeat:no-repeat;
}
/* Textarea chrome belongs to the component, not its form or modal parent.
   Feedback opts into its taller minimum through the component variant. */
.ui-textarea { padding:7px 12px; border:1px solid var(--line-strong); border-radius:var(--r-sm);
                  font-size:12px; font-family:inherit; line-height:1.5;
                  background:var(--surface); color:var(--ink);
                  min-height:96px; resize:vertical; }
.ui-textarea:focus { border-color:var(--accent); outline:none; box-shadow:0 0 0 3px var(--focus-ring); }
/* The UA sizes a file input to fit its button plus the filename it might come
   to hold, and that width is a floor — on a narrow card it is the one control
   that will not come in with the rest of the form. The label has to be allowed
   to shrink too, or its own min-content keeps the row wide regardless. */
input[type=file] { max-width:100%; }
/* The native chooser had its own padding and font on top of the input's,
   making uploads taller than adjacent fields. Let the chooser own the shared
   padding once; keep the real file input for selection and validation. */
.ui-input[type=file] { padding:0; padding-inline-end:12px; overflow:hidden; }
.ui-input[type=file]::file-selector-button {
  appearance:none; font:inherit; line-height:inherit; vertical-align:baseline;
  padding:var(--field-padding); margin:0 12px 0 0;
  border:0; border-inline-end:1px solid var(--line-strong); border-radius:0;
  background:var(--surface-2); color:var(--ink); cursor:pointer;
}
.ui-input[type=file]:not(:disabled)::file-selector-button:hover { background:var(--surface-3); }
.ui-input[type=file]:disabled::file-selector-button { color:var(--ink-muted); cursor:not-allowed; }
.inline > label { min-width:0; max-width:100%; }
/* line-height is stated on both button and a.btn so a submit/cancel pair
   (one of each) comes out exactly the same height — a <button> would
   otherwise use the UA's "normal" while the link inherits body's 1.5. */
.ui-button { background:var(--primary); color:var(--ink-invert); border:1px solid var(--primary);
         border-radius:var(--r-sm); padding:var(--button-padding);
         font-size:12px; font-family:inherit; line-height:1.5; font-weight:500; cursor:pointer; }
button:hover { background:var(--primary-hover); border-color:var(--primary-hover); }
button.ghost, a.btn.ghost { background:var(--surface); color:var(--ink);
                            border:1px solid var(--line-strong); }
/* Submit-in-flight: dim the button and spin a ring so a keyboard submit
   (Enter) reads as "working", not "nothing happened". The ring inherits
   currentColor so it survives the dark theme's light-on-dark primary. */
button:disabled { opacity:.75; cursor:default; }
/* Writable's fieldset exists only to carry disabledness down to the controls
   inside it — strip the box the UA gives a fieldset so wrapping changes nothing
   about the layout. Its own controls read as refused rather than in-flight. */
#cost-line-actions { flex-wrap:wrap; margin:0 0 16px; }
fieldset.gate { border:0; margin:0; padding:0; min-width:0; }
fieldset.gate:disabled { cursor:not-allowed; }
fieldset.gate:disabled button { opacity:.5; cursor:not-allowed; }
/* The fields have to look refused too, or a form of normal-looking inputs that
   silently swallows every keystroke is worse than one that is plainly closed.
   Cancel is deliberately left alone: it is a link, it still works, and it is
   the way out of a panel opened by someone who cannot submit it. */
fieldset.gate:disabled :is(input,select,textarea) { background-color:var(--surface-2);
  color:var(--ink-muted); cursor:not-allowed; }
button.loading::before, button.htmx-request::before { content:""; display:inline-block;
  width:12px; height:12px; margin-right:8px; vertical-align:-2px; border-radius:50%;
  border:2px solid currentColor; border-right-color:transparent; opacity:.8;
  animation:spin .6s linear infinite; }
@keyframes spin { to { transform:rotate(360deg); } }
/* A badge for a state Kai is still working on (Verifying) spins, so a card that
   is polling reads as busy rather than stuck. */
.badge.busy::before { content:""; display:inline-block; width:9px; height:9px; margin-right:5px;
  vertical-align:-1px; border-radius:50%; border:1.5px solid currentColor;
  border-right-color:transparent; animation:spin .8s linear infinite; }
@media (prefers-reduced-motion: reduce) { .badge.busy::before { animation:none; } }
/* htmx marks the in-flight element (and can mark an indicator) with
   .htmx-request; a polled region dims subtly while it refetches. */
.htmx-request.poll-region { opacity:.6; transition:opacity .2s; }
/* Link that looks like a button (navigation, not submission). */
.ui-button-link.btn { display:inline-block; background:var(--primary); color:var(--ink-invert);
        border:1px solid var(--primary); border-radius:var(--r-sm); padding:var(--button-padding);
        font-size:12px; line-height:1.5; font-weight:500; text-decoration:none; }
a.btn:hover { background:var(--primary-hover); border-color:var(--primary-hover); }
a.btn.ghost:hover, button.ghost:hover { background:var(--surface-2); }
/* The actions cell: side by side and hard right, so a row that offers three
   things stays one row tall and the column stays as narrow as its widest set.
   Each button used to be its own block, which is what stacked them. */
td .row-acts { display:flex; flex-wrap:wrap; gap:6px; align-items:center; justify-content:flex-end; }
/* A row that edits in place: both halves ship in every row and the class on the
   <tr> picks one. Which is the whole point — showing a form that is already in
   the document is a class, not a page load, and the alternative (fetch the row's
   fields when the pencil is pressed) is a round trip to render markup we had
   already rendered. See ROW_EDIT_JS.
   display:contents rather than block or flex: the wrappers exist to be switched
   and should be invisible to layout, so what is revealed lays out as the direct
   child of the cell it always was — the td padding rule above, the .row-acts
   flex, all of it unchanged and none of it aware this mechanism exists. */
tr .rw { display:none; }
tr.editing .ro { display:none; }
tr.editing .rw { display:contents; }
/* Save/Remove/Cancel stay on one line even where the column would rather they
   wrapped. Narrow enough — a phone — and the actions column shrinks to its
   widest single button, which stacks the three and makes the open row three
   times the height of every other one: the stacked-button shape this pencil was
   built to get rid of, arriving by a different route. Every table already sits
   in a .tscroll, so the width this asks for costs a sideways scroll and nothing
   else. */
tr.editing .row-acts { flex-wrap:nowrap; }
/* The open row, marked the way a selected row would be. Without it a table of
   fixed rows with one set of inputs in the middle reads as a rendering fault. */
tr.editing > td { background:var(--surface-2); }
/* The row an upload turned out to duplicate, so the toast naming it has a row to point at. */
tr.row-existing > td { background:var(--surface-2); }
.card-head { display:flex; align-items:center; justify-content:space-between; gap:16px; margin-bottom:8px; }
.card-head h2 { margin:0 !important; }
/* Entity-detail tabs (server-rendered; the active one comes from the path). */
/* The active underline is an inset shadow, not a border with a -1px pull: a child
   overhanging the padding box makes the scroll container scrollable on both axes. */
.tabs { display:flex; gap:2px; align-items:center; border-bottom:1px solid var(--line);
        margin:0 0 20px; overflow-x:auto; overflow-y:hidden; scrollbar-width:none; }
.tabs::-webkit-scrollbar { display:none; }
.tabs .tab { display:flex; align-items:center; gap:6px; padding:9px 12px; font-size:12px;
             font-weight:500; color:var(--ink-muted); text-decoration:none; white-space:nowrap; }
.tabs .tab:hover { color:var(--ink); }
.tabs .tab.on { color:var(--ink); font-weight:600; box-shadow:inset 0 -2px 0 var(--ink); }
.tabs .tab-n { background:var(--surface-2); border:1px solid var(--line); color:var(--ink-muted);
               border-radius:999px; padding:0 7px; font-size:11px; font-weight:500; }
.tabs .tab.on .tab-n { background:var(--ink); border-color:var(--ink); color:var(--ink-invert); }
/* A tab pushed to the far end of the row — page-level actions (Edit) sit there
   rather than in the title row, so the title stays a title. Actions read as
   small ghost pills, not as tabs. */
.tabs .tab.end { margin-left:auto; margin-bottom:8px; padding:4px 12px;
                 background:var(--surface); color:var(--ink); font-weight:500;
                 border:1px solid var(--line-strong); border-radius:var(--r-sm); }
.tabs .tab.end:hover { background:var(--surface-2); }
/* Only the first one claims the gap — the rest sit flush beside it, or the
   free space would be split between them. */
.tabs .tab.end ~ .tab.end { margin-left:8px; }
/* A tab the current role may not use. Rendered as a span rather than a link, so
   it is inert without relying on the styling to stop the click. */
.tabs .tab.off { opacity:.5; cursor:not-allowed; }
.tabs .tab.off:hover { color:var(--ink-muted); background:var(--surface); }
/* A second tab row, inside the page rather than in its head: which view of the
   current tab's data is on screen. Segmented pills rather than a second
   underlined row, so there is no doubt which row is the page's own tabs. */
.tabs.sub { border-bottom:none; gap:4px; margin:0 0 16px; }
/* Every pill carries the border, transparent until it is the active one, or the
   row would shift sideways as the selection moves. */
.tabs.sub .tab { padding:5px 10px; border:1px solid transparent; border-radius:var(--r-sm); }
.tabs.sub .tab.on { box-shadow:none; background:var(--surface-2); border-color:var(--line-strong); }
/* An action at the end of the sub row. The two rules above are later than the
   .tab.end rule and equally specific, so they would take its border and padding
   away; this puts them back. The 8px bottom margin the head row uses to lift a
   pill off the underline is not wanted here — this row has no underline. */
.tabs.sub .tab.end { margin-bottom:0; padding:4px 12px; border-color:var(--line-strong);
                     color:var(--ink); }
/* Cost Sheet separates doing from reading. Export and write actions occupy the
   upper row; the grouping and view selectors then share one line below it. */
.cost-sheet-actions .tabs.sub { justify-content:flex-end; margin-bottom:12px; }
/* Four tabs now rather than two, so the row wraps rather than squeezing: on a
   narrow viewport the tab group drops under the pills instead of the two
   groups fighting over one line. */
.cost-sheet-controls { display:flex; flex-wrap:wrap; align-items:flex-start;
                       gap:8px 16px; margin:0 0 16px; }
.cost-sheet-controls .phase-pills { flex:1 1 auto; min-width:0; margin:0; }
.cost-sheet-controls .tabs.sub { flex:0 0 auto; margin:0 0 0 auto; overflow:visible;
                                 gap:0; align-items:center; }
/* Two controls in one row, and they must not read as one row of four. The
   Schedule toggle keeps the pill shape and a checked state; the three widths
   are welded into a single segmented control, which is what says "pick exactly
   one of these" without a label saying it. */
.cost-sheet-controls .tabs.sub .tab { margin-bottom:0; padding:5px 12px;
                                      border:1px solid var(--line); border-radius:0;
                                      margin-left:-1px; color:var(--ink-muted); }
.cost-sheet-controls .tabs.sub .tab:first-child { border-radius:999px; margin-left:0;
                                                  margin-right:16px; }
.cost-sheet-controls .tabs.sub .tab:nth-child(2) { border-radius:var(--r-md) 0 0 var(--r-md); }
.cost-sheet-controls .tabs.sub .tab:last-child { border-radius:0 var(--r-md) var(--r-md) 0; }
.cost-sheet-controls .tabs.sub .tab:hover { color:var(--ink); background:var(--surface-2); }
/* The lit state is the same ink-on-surface the phase pills use, so "chosen"
   looks the same everywhere on this row. */
.cost-sheet-controls .tabs.sub .tab.on { background:var(--ink); border-color:var(--ink);
                                         color:var(--surface); font-weight:600; z-index:1; }
/* Right-hand side sheet (<Sheet>, ui/sheet.tsx). Open state is the URL
   fragment (:target); client.js adds focus, Escape and the unsaved-changes
   question on top of it.
   Closed, visibility waits for the slide to finish before hiding the sheet,
   or the panel would vanish at the first frame instead of leaving. */
.sheet { position:fixed; inset:0; z-index:50; visibility:hidden;
         transition:visibility 0s linear .25s; }
.sheet:target { visibility:visible; transition-delay:0s; }
.sheet-scrim { position:absolute; inset:0; background:rgba(10,14,20,.45);
               opacity:0; transition:opacity .2s ease; }
.sheet:target .sheet-scrim { opacity:1; }
/* Full height, so the header and footer are the viewport's top and bottom
   edges and only the fields between them scroll. The shadow falls left, onto
   the page it covers; --shadow-2 is a card's, too faint to lift a whole
   column off a dimmed page. */
.sheet-panel { position:absolute; top:0; right:0; height:100%; width:min(460px,92vw);
               box-sizing:border-box; display:flex; flex-direction:column;
               background:var(--surface); border-left:1px solid var(--line-strong);
               border-radius:var(--r-lg) 0 0 var(--r-lg);
               box-shadow:-12px 0 40px rgba(10,14,20,.16);
               transform:translateX(100%); transition:transform .25s cubic-bezier(.2,.8,.2,1); }
.sheet:target .sheet-panel { transform:none; }
.sheet-panel:focus { outline:none; }
/* The form is the whole panel — header, fields and footer — so the buttons
   are its own and the footer needs no form= to submit it. */
.sheet-form { display:flex; flex-direction:column; flex:1; min-height:0; }
.sheet-head { display:flex; align-items:flex-start; justify-content:space-between; gap:16px;
              padding:18px 18px 16px 24px; border-bottom:1px solid var(--line); }
.sheet-heading { min-width:0; }
.sheet-head h2 { margin:0; }
.sheet-title { display:flex; align-items:center; }
.sheet-title .info-tip-trigger { flex:none; }
/* The record the sheet acts on. One line: a long SPC name is still that SPC
   when its tail is cut, and two lines would push the fields down unevenly. */
.sheet-sub { margin:3px 0 0; font-size:12px; color:var(--ink-muted);
             white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.sheet-close { flex:none; display:inline-flex; align-items:center; justify-content:center;
               width:30px; height:30px; margin-top:-4px; border-radius:var(--r-sm);
               color:var(--ink-muted); text-decoration:none; }
.sheet-close:hover { background:var(--surface-2); color:var(--ink); }
.sheet-body { flex:1; min-height:0; overflow-y:auto; overscroll-behavior:contain;
              padding:20px 24px 28px; }
.sheet-body > .stack { max-width:none; }
.sheet-body > .stack > .err { align-self:stretch; margin-bottom:0; }
/* Every group inside a sheet takes the sheet's width, as its direct stack
   does, rather than keeping the 420px cap and ending at its widest control. */
.sheet-body .stack .stack { max-width:none; align-self:stretch; }
.sheet-body .stack > div:not([hidden]) { align-self:stretch; }
.sheet-body h3 { font-size:11px; color:var(--ink-muted); font-weight:600;
                 margin:12px 0 0; text-transform:uppercase; letter-spacing:.04em; }
/* Cancel then the primary, at the right, where the eye ends a form. Stretch
   for the same reason as .modal-acts below: a <button> and an <a> side by
   side only agree on a height when the row decides it. */
.sheet-foot { display:flex; justify-content:flex-end; align-items:stretch; gap:8px;
              padding:12px 24px; border-top:1px solid var(--line); background:var(--surface-2);
              border-bottom-left-radius:var(--r-lg); }
.sheet-foot a.btn { display:inline-flex; align-items:center; }
.sheet-foot .btn, .sheet-foot button { white-space:nowrap; flex:none; }
.sheet-note { margin-right:auto; align-self:center; font-size:12px; color:var(--ink-muted); min-width:0; }
/* The refused field's reason, at the left of the footer, against the button that was pressed. */
.sheet-foot-note { margin:0 auto 0 0; align-self:center; font-size:12px; color:var(--bad, #b42318); min-width:0; }
.sheet-foot-note[hidden] { display:none; }
.sheet-discard .inline { margin-top:18px; justify-content:flex-end; }
@media (prefers-reduced-motion: reduce) {
  .sheet, .sheet-scrim, .sheet-panel { transition:none; }
}
/* Centered modal. Same :target mechanism as the sheet above, but centered
   rather than edge-anchored: the feedback box is a short interruption you
   dismiss, not a surface you work in beside the page. */
.modal { position:fixed; inset:0; z-index:55; visibility:hidden;
         display:flex; align-items:center; justify-content:center; padding:16px; }
.modal:target { visibility:visible; }
.modal .scrim { position:absolute; inset:0; background:rgba(10,14,20,.45);
                opacity:0; transition:opacity .2s ease; }
.modal:target .scrim { opacity:1; }
.modal .panel { position:relative; width:min(520px,100%); box-sizing:border-box;
                max-height:90vh; overflow-y:auto; padding:22px 24px;
                background:var(--surface); border:1px solid var(--line-strong);
                border-radius:var(--r-lg); box-shadow:var(--shadow-2);
                transform:translateY(-8px) scale(.99); opacity:0;
                transition:transform .18s ease, opacity .18s ease; }
.modal:target .panel { transform:none; opacity:1; }
.modal .close { font-size:22px; line-height:1; color:var(--ink-muted); text-decoration:none; }
/* The action row needs its own top margin: .modal label carries the spacing
   between every other pair of rows here, and the buttons follow a control
   rather than a label — so without this they sit flush against the field
   above them, tighter than any other gap in the box. */
.modal-acts { margin-top:18px; }
/* Stretch, not flex-end. The submit is a <button> and the cancel is an <a>,
   two elements the UA gives slightly different intrinsic heights; bottom-
   aligning them lines up their baselines and leaves their top edges a pixel or
   two apart, which is exactly the kind of misalignment you see but cannot name.
   Stretching makes the row decide the height and both agree to it. */
.modal-acts .inline { align-items:stretch; }
/* An <a> has no line box to centre once it is stretched, so it needs to say
   how its own text sits. The <button> already centres its content. */
.modal-acts a.btn { display:inline-flex; align-items:center; }
.modal label { display:block; font-size:11px; font-weight:600; color:var(--ink-muted);
               text-transform:uppercase; letter-spacing:.04em; margin:14px 0 5px; }
.ui-textarea[data-variant="feedback"] { min-height:120px; }
/* The page being reported. Shown because the reporter should see exactly what
   is being attached to their words, and readonly because editing it would only
   ever make the report wrong. */
.modal input[readonly] { width:100%; font:inherit; color:var(--ink-muted);
                         background:var(--surface-2); cursor:default; }
@media (prefers-reduced-motion: reduce) {
  .modal .scrim, .modal .panel { transition:none; }
}
button.danger { background:var(--bad); color:#fff; }
.ui-button.danger { border-color:var(--bad); }
.err { background:var(--bad-bg); color:var(--bad); border:1px solid var(--bad-line); border-radius:8px; padding:10px 14px; margin-bottom:16px; }
.flash { background:var(--ok-bg); color:var(--ok); border:1px solid var(--ok-line); border-radius:8px; padding:10px 14px; margin-bottom:16px; }
/* minmax(0,…) on both tracks, because a fixed 220px label column beside a value
   that cannot break — a 法人番号, a JSON blob — sizes the grid past its
   container and runs the values off the right of the sheet. */
.kv { display:grid; grid-template-columns:minmax(0,220px) minmax(0,1fr); gap:8px 16px; font-size:12px; }
.kv dt { color:var(--ink-muted); }
.kv dd { margin:0; min-width:0; overflow-wrap:anywhere; }
/* Too narrow to hold two columns: the label goes above its value rather than
   squeezing both into a strip that fits neither. */
@media (max-width:640px) {
  .kv { grid-template-columns:1fr; gap:0; }
  .kv dt { margin-top:10px; }
  .kv dt:first-child { margin-top:0; }
}
/* Same bottom gap as a card, so a KPI row above a table sits on the page grid.
   Inside a card it is the last thing in the box and the padding is the gap. */
/* min(220px,100%) rather than a bare 220px: below that the track would still
   claim its 220 and push the tiles past the card. */
.grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(min(220px,100%),1fr)); gap:14px; margin-bottom:16px; }
.card > .grid:last-child { margin-bottom:0; }
.stat { display:block; background:var(--surface); border:1px solid var(--line);
        border-radius:var(--r-lg); padding:16px 20px; color:inherit; text-decoration:none; }
a.stat:hover { background:var(--surface-2); }
.stat .n { font-size:22px; font-weight:600; letter-spacing:-.02em; } .stat .l { color:var(--ink-muted); font-size:11px; }
/* The compact row. A narrower track so seven tiles fit on one line at the width
   this app is used at, instead of wrapping to two and pushing the table they
   describe below the fold. The label keeps its size — it is already the
   smallest type on the page, and shrinking it further is what makes a tile
   strip unreadable rather than merely dense. */
.grid.compact { grid-template-columns:repeat(auto-fill,minmax(min(148px,100%),1fr)); gap:8px; }
.grid.compact .stat { padding:10px 12px; border-radius:var(--r-md); }
.grid.compact .stat .n { font-size:16px; }
code { background:var(--surface-2); padding:1px 5px; border-radius:4px; font-size:11px; }
/* ── home: one card of tasks (views/home.tsx) ───────────────────────── */
/* A task is a row of the card: its name, the counts within it as the app's
   badges, and its figure at the end in the stat tile's type; the first items
   sit under it in a hairline table. Every one of them is a link. */
.card.home-tasks { padding:0; }
.home-task { padding:16px 20px; border-top:1px solid var(--line); }
.home-task:first-child { border-top:0; }
.home-head { display:flex; align-items:center; gap:12px; flex-wrap:wrap; }
.home-title { font-size:14px; font-weight:600; color:var(--ink); text-decoration:none; }
.home-title:hover { text-decoration:underline; }
a.badge { text-decoration:none; }
a.badge:hover { text-decoration:underline; }
.home-n { margin-left:auto; font-size:22px; font-weight:600; letter-spacing:-.02em; font-variant-numeric:tabular-nums;
  color:var(--ink); text-decoration:none; white-space:nowrap; }
.home-n small { font-size:12px; font-weight:500; color:var(--ink-muted); margin-left:2px; }
.home-n::after { content:"›"; margin-left:8px; font-size:18px; font-weight:400; color:var(--ink-subtle); }
.home-peek { margin-top:10px; overflow-x:auto; border:1px solid var(--line); border-radius:var(--r-md); }
.home-peek td { padding:6px 12px; }
.home-peek tr:last-child td { border-bottom:0; }
.home-late { color:var(--bad); }
.home-more { display:block; padding:6px 12px 0; text-align:right; }
.home-empty { padding:22px 20px; font-size:13px; }
/* The upload button's SPC menu: RowMenu's pop-up under a labelled primary button. */
.menu-button > summary.ui-button { display:inline-block; }
.menu-button[open] > summary.ui-button { background:var(--primary-hover); border-color:var(--primary-hover); }
.menu-button .ra-pop[popover] { min-width:240px; padding:8px; }
.ra-pop-head { margin:2px 6px 6px; color:var(--ink-muted); }
/* ── development cost schedule ───────────────────────────────────── */
/* Budget variance. Amber for over, green for under, nothing for on-plan — and
   deliberately no red anywhere: on a page of financial figures red reads as
   "error" or "negative", and a line running over budget is neither. */
.v-over  { color:var(--warn); font-weight:500; }
.v-under { color:var(--ok);   font-weight:500; }
/* Reserved for a figure well past its plan — see varianceTone, which escalates
   at the same 20% jarvis does. Heavier as well as redder, so the step up is
   legible without relying on hue alone. */
.v-critical { color:var(--bad); font-weight:600; }
/* Approved but not yet paid: present, but not yet a fact about the bank. */
.v-ahead { color:var(--ink-muted); }
/* A planned payment nobody has billed yet. Muted like .v-ahead, because it is
   also not a fact about the bank — and dashed on top of that, because the two
   sit in the same column and are different claims: an approved invoice is a
   bill, an installment is an intention. Colour alone could not carry that, and
   a second grey would just look like the first one. */
.v-plan { color:var(--ink-muted); border-bottom:1px dashed currentColor; }
/* …and past its date with nothing invoiced against it. Amber rather than a
   third grey: a schedule that has slipped is the same class of signal as a line
   running over its budget, so it wears the same colour that does. */
.v-plan.late { color:var(--warn); }
/* The total row under a rollup table. */
tr.rollup td { font-weight:600; background:var(--surface-2);
               border-top:1px solid var(--line-strong); }
/* A category header inside the schedule: the subtotal for the lines indented
   under it. Tinted rather than bold-only, because it has to read as a band
   across a table whose rows are otherwise all the same weight — and lighter
   than .rollup, which closes the table and should still be the heaviest thing
   in it. The rule above wins where both apply; they never do. */
tr.cat td { background:var(--surface-2); font-weight:600;
            border-top:1px solid var(--line); }
/* Hover belongs to rows you can click. A category header is not one, so it
   keeps its band colour instead of lighting up like a link. */
tbody tr.cat:hover td, tbody tr.rollup:hover td { background:var(--surface-2); }
/* A phase heading inside the schedule: the level above the category, and the
   band the four investment phases are told apart by. Heavier than .cat and
   flush left, so the table reads phase → category → line down the first column
   without needing a rule per level. Not as heavy as .rollup: the grand total
   still closes the table. */
tr.phase td { background:var(--surface-2); font-weight:700;
              border-top:1px solid var(--line-strong); }
tbody tr.phase:hover td { background:var(--surface-2); }
/* The two levels under a phase heading. inline-block so the indent is part of
   the text rather than of the cell, which keeps it out of the way of .num's
   right alignment on every other column. */
.ind { display:inline-block; padding-left:18px; }
.ind2 { display:inline-block; padding-left:36px; }
/* The phase filter above both Investment Cost views.
   Pills rather than a <select>: five options, all worth showing at once, and
   the count beside each is the reason — an empty phase reads as empty before
   anyone clicks it. Deliberately not styled like .tabs.sub below it: this row
   chooses a grouping and that row chooses how to look at it. */
.phase-pills { display:flex; flex-wrap:wrap; gap:6px; margin:0 0 12px; }
.phase-pills .pill { display:inline-flex; align-items:center; gap:6px;
                     padding:4px 12px; border-radius:999px; font-size:13px;
                     border:1px solid var(--line); color:var(--ink-muted);
                     background:var(--surface); text-decoration:none; }
.phase-pills .pill:hover { border-color:var(--line-strong); color:var(--ink); }
.phase-pills .pill.on { background:var(--ink); border-color:var(--ink);
                        color:var(--surface); font-weight:600; }
.pill-n { font-variant-numeric:tabular-nums; opacity:.65; font-size:12px; }
/* Narrowing is a class on the wrapper, never a request: every row is already in
   the document. A row carries the phase it belongs to — heading, category and
   line alike — so a pill takes the whole band and never leaves a subtotal
   standing over rows that have gone.

   The grand total carries no phase class and so falls away with them, which is
   the point: under Development the Development heading *is* the total of what
   is on screen, and a whole-investment figure at the foot of a filtered table
   would state money the rows above it do not add up to. */
/* The Schedule toggle. The cost columns are always rendered and this rule is
   the whole of turning them off, which is what lets the toggle cost no request:
   the periods beside them do not re-render, and a table somebody had scrolled
   two years along stays where it was. `visibility` would leave the columns
   occupying their width, so it has to be display:none — a table cell dropped
   this way takes its share of the row with it. */
.phase-scope[data-schedule="off"] .col-sched { display:none; }
/* Confidence badges are the tallest ordinary one-line content in this table.
   Give every body cell that same floor even while the schedule columns are
   hidden, or toggling Schedule makes the rows compress by a few pixels and the
   whole period grid jumps vertically. Taller stacked month figures still grow
   naturally beyond it. */
.phase-scope tbody td { height:36px; }
.phase-scope[data-phase-on="acquisition"] tbody tr:not(.ph-acquisition),
.phase-scope[data-phase-on="development"] tbody tr:not(.ph-development),
.phase-scope[data-phase-on="operation"] tbody tr:not(.ph-operation),
.phase-scope[data-phase-on="financing"] tbody tr:not(.ph-financing) { display:none; }
/* Every line in the category is firm. Underlined rather than coloured: the
   Confidence column's other rows are badges, and a fourth badge colour for
   "all of them" would compete with the three that describe single lines. The
   offset keeps the rule off the digits' baseline. */
.all-firm { text-decoration:underline; text-underline-offset:3px; }
/* The month axis is as wide as the project is long. Every table already scrolls
   inside its card (.tscroll); what this adds is a table sized to its content
   rather than to the card, and a pinned first column — scrolled to 2027, a row
   of figures with no line name against it is unreadable. */
.hscroll table { min-width:100%; width:max-content; }
/* No left bleed here: .tscroll's negative margin lets a table run under the
   card's padding, and a sticky column pins to the scrollport's content edge —
   so the padding strip stays uncovered and the months slide through it beside
   the pinned names. Nothing needs to show left of the pin anyway. */
.hscroll { margin-left:0; padding-left:0; }
/* The rule down the pin's right edge is a shadow rather than a border, and it
   has to be. `table` is border-collapse:collapse, and a collapsed border is
   painted by the table, not by the cell that declared it — so `border-right`
   here stayed with the table and slid away with the months, leaving a stray
   vertical line drifting through the figures. An inset shadow is painted by the
   cell, so it travels with the cell, which is the whole job. */
.hscroll th.pin, .hscroll td.pin { position:sticky; left:0; z-index:1;
        background:var(--surface); box-shadow:inset -1px 0 var(--line); }
.hscroll tbody tr:hover td.pin { background:var(--surface-2); }
/* The period grid shares Schedule's phase/category/line hierarchy. The pinned label
   cell has a more specific background rule than the row bands above, so carry
   their tint onto the pin explicitly or the left edge breaks into white gaps
   while the period cells remain grouped. */
.hscroll tr.phase td.pin,
.hscroll tr.cat td.pin,
.hscroll tr.rollup td.pin { background:var(--surface-2); }
/* Where the schedule is standing on its own timeline. A month grid is a row of
   dates and a table gives no hint which of them have already been and gone —
   scrolled two years along, "2026-11" and "2027-11" look identical, and one of
   them is a fact while the other is a plan.

   Only the header changes. The figures are already spending colour on what kind
   of money they are — .v-ahead for approved-not-paid, .v-plan for planned —
   and dimming a past month's amount would say it counts for less, which is the
   opposite of true: a past month is the part of the schedule that actually
   happened. */
th.past { color:var(--ink-subtle); font-weight:400; }
th.now { color:var(--accent); font-weight:600; }
/* The boundary itself, on the left edge of the first column that is not yet
   history: everything to the left of this rule has already happened.

   A real border rather than the pin's inset shadow, and the difference is
   position:sticky. A collapsed border is painted by the table, so it stays where
   the table put it while a sticky cell slides out from under it — which is the
   stray line the pin ran into. These columns scroll with the table, so the
   border travels with them, which is the whole requirement. */
th.edge, td.edge { border-left:2px solid var(--accent); }
/* Milestone markers inside a period heading, under the period's own label.
   Named .mstone rather than .ms because .ms and the whole .ms-* namespace are
   already the filter multi-select's, a few hundred lines below. A block so the
   markers stack under the date rather than widening a column that has 60
   siblings; normal weight and case because the heading above is the emphasis. */
.mstone { display:block; margin-top:3px; font-weight:400; text-transform:none;
          letter-spacing:0; }
.mstone-tag { display:block; font-size:10px; line-height:1.35; color:var(--accent);
              white-space:normal; }
/* Pinned to an edge column because its real date is off the axis. Muted, so it
   does not read as something that happened in the column it sits over. */
.mstone-tag.before, .mstone-tag.after { color:var(--ink-muted); }
.mstone-when { display:block; font-size:9px; color:var(--ink-muted); }
/* Secondary (other-language) vendor name, under the primary one. */
.login-wrap { max-width:360px; margin:12vh auto; }
.login-wrap form { display:flex; flex-direction:column; gap:12px; }
.login-wrap form + form { margin-top:20px; }
/* Account status sits above the form in both full pages and card swaps. */
.account-status { margin-bottom:12px; }
.identity-methods { list-style:none; padding:0; margin:0; container-type:inline-size; }
.identity-method { display:grid; grid-template-columns:36px minmax(0,1fr) auto;
                   align-items:center; gap:12px; padding:16px 0; }
.identity-method + .identity-method { border-top:1px solid var(--line); }
.identity-method-icon { width:36px; height:36px; display:flex; align-items:center;
                        justify-content:center; background:var(--surface-2);
                        border:1px solid var(--line); border-radius:10px; color:var(--ink-muted); }
.identity-method-google { font-size:20px; font-weight:600; }
.identity-method-info { min-width:0; }
.identity-method-name { font-weight:500; margin-bottom:6px; overflow-wrap:anywhere; }
.identity-method-action { margin:0; justify-self:end; }
/* Respond to the card width, including narrow columns on desktop. */
@container (max-width:380px) {
  .identity-method-action { grid-column:2 / -1; justify-self:start; }
}

/* Empty state for a table with no rows — replaces the bare header row. */
.empty { color:var(--ink-muted); margin:0; padding:10px 10px 2px; }
/* ── list filters ────────────────────────────────────────────────── */
/* One wrapping row above a table. Every control is the same height so the
   row reads as a single band regardless of how the fields wrap. */
/* Actions share the primary fields' row. Absolute positioning used a guessed
   label height and reserved width, which drifted when fields wrapped. */
.filters { display:grid; grid-template-columns:minmax(0,1fr) auto; align-items:end; gap:10px 14px;
           padding-bottom:14px; margin-bottom:4px; border-bottom:1px solid var(--line); }
.filter-primary { display:flex; flex-wrap:wrap; align-items:flex-end; gap:10px 14px; min-width:0; }
/* The band is secondary chrome: its labels are quiet metadata, and its controls
   sit one tier shorter than a form's — same type size, less padding. */
/* The floors are min(…,100%) so a control keeps its comfortable size until the
   band is genuinely narrower than that, and then shrinks rather than hanging
   off the card. */
.filters .search { flex:1 1 240px; min-width:min(180px,100%); }
/* min/max and from/to sit side by side under one label. */
.filters .pair { display:flex; align-items:center; gap:6px; }
.filters .pair input { flex:0 1 120px; min-width:0; }
.filters .pair .sep { color:var(--ink-subtle); }
/* Multi-select: a button that opens a searchable checkbox list. The native
   <select multiple> underneath is what submits, and is all that shows without
   JS — so it is sized like the other controls rather than hidden here. */
.filters .multi { position:relative; }
.filters .multi select[multiple] { min-width:160px; }
.ms { position:relative; display:block; }
.ms-btn { display:flex; align-items:center; gap:6px; min-width:min(160px,100%); max-width:240px;
          padding:var(--field-padding); font-size:12px; font-weight:400; text-align:left; background:var(--surface);
          color:var(--ink); border:1px solid var(--line-strong); border-radius:var(--r-sm); cursor:pointer; }
.ms-btn::after { content:""; margin-left:auto; flex:none; width:8px; height:4px;
                 background-image:var(--select-chevron); background-size:4px 4px;
                 background-position:left center,right center; background-repeat:no-repeat; }
/* Restated, because the base button rule's hover is a filled primary — this one
   is a control that opens a list, not an action. */
.ms-btn:hover { background:var(--surface-3); border-color:var(--line-strong); color:var(--ink); }
.ms-btn:focus-visible { border-color:var(--accent); outline:none; box-shadow:0 0 0 3px var(--focus-ring); }
.ms-btn > span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ms-btn.on { border-color:var(--accent); background:var(--accent-light); }
.ms-pop { position:absolute; z-index:20; top:calc(100% + 4px); left:0; width:260px;
          padding:8px; background:var(--surface); border:1px solid var(--line);
          border-radius:var(--r-md); box-shadow:var(--shadow-2); }
.ms-pop .ms-q { width:100%; box-sizing:border-box; }
.ms-list { max-height:220px; overflow:auto; margin-top:6px; }
/* The base label rule stacks a control above its text; an option row is the
   other way round. */
.ms-list label { display:flex; flex-direction:row; align-items:center; gap:8px;
                 padding:5px 6px; font-size:12px; font-weight:400; color:var(--ink);
                 border-radius:var(--r-sm); cursor:pointer; }
.ms-list label input { flex:none; }
.ms-list label:hover { background:var(--surface-3); }
.ms-none { color:var(--ink-muted); font-size:12px; padding:6px; }
/* The same popover over a single choice, in a form rather than in the filter
   band — so its button is sized like the controls around it (a filter's is one
   tier shorter) and the list is as wide as the field it drops from. */
.pick { position:relative; }
.pick .ms-btn { width:100%; max-width:none; }
.pick .ms-pop { width:100%; min-width:220px; }
/* The native <select> the popover covers, taken out of sight but NOT out of
   the form's constraint validation — which is why this is a rule here rather
   than the hidden attribute the script used to set. That attribute bars an
   element from being focused, and a browser asked to submit a form holding an
   invalid control it cannot point at refuses and says nothing: on a required
   Pick that is an Attach button which does nothing, forever, with no message.
   Laid over the button, so the validation bubble points at what was clicked. */
.pick select.ms-native { position:absolute; left:0; bottom:0; width:100%; height:34px;
                         opacity:0; pointer-events:none; }
/* One choice, taken on click: a row is a button, not a checkbox. Restated
   rather than inherited, because the base button rule is a filled primary. */
.ms-list button { display:block; width:100%; text-align:left; padding:5px 6px;
                  font-size:12px; font-weight:400; font-family:inherit; line-height:1.5;
                  color:var(--ink); background:none; border:0;
                  border-radius:var(--r-sm); cursor:pointer; }
.ms-list button:hover { background:var(--surface-3); border-color:transparent; }
.ms-list button.on { background:var(--accent-light); }
/* A checkbox row in a normal form — same inversion of the base label rule as
   .ms-list, since the box belongs beside its name, not above it. */
label.check { flex-direction:row; align-items:center; gap:8px; font-weight:400; cursor:pointer; }
label.check input { flex:none; }
/* Extra fields occupy the next grid row, so opening them does not move the
   toggle. Keep Clear's space reserved to prevent primary fields rewrapping
   when filtering reveals it; row-reverse keeps the toggle at the right edge. */
.filters .corner { display:flex;
                   flex-direction:row-reverse; align-items:center; gap:8px; }
.filters .corner .acts { display:flex; gap:8px; }
.filters .corner .clear[hidden] { display:inline-block !important; visibility:hidden; }
/* The filters a link set (FilterChips): named under the band, each with its
   ×, in the accent an applied multi-select already wears (.ms-btn.on). */
.filter-chips { grid-column:1 / -1; display:flex; flex-wrap:wrap; gap:8px; }
.filter-chips[hidden], .filter-chip[hidden] { display:none; }
.filter-chip { display:inline-flex; align-items:center; gap:4px; padding:2px 3px 2px 10px; font-size:12px; font-weight:500;
  color:var(--accent); background:var(--accent-light); border:1px solid var(--accent); border-radius:var(--r-sm); }
.filter-chip-x { display:grid; place-items:center; width:18px; height:18px; padding:0; font-size:13px; line-height:1;
  color:inherit; background:none; border:0; border-radius:4px; cursor:pointer; }
.filter-chip-x:hover { background:var(--surface); border-color:transparent; }
/* "More filters": the fields nobody reaches for on most visits. They render
   outside the <details> to span the band's full width, so hide-when-closed is
   this rule rather than the UA's. */
.filters:not(:has(.more[open])) .more-row { display:none; }
.ui-disclosure > summary { list-style:none; cursor:pointer; white-space:nowrap;
                           padding:var(--button-padding); font-size:12px; color:var(--ink-muted);
                           background:var(--surface); border:1px solid var(--line-strong);
                           border-radius:var(--r-sm); }
.ui-disclosure > summary::-webkit-details-marker { display:none; }
.ui-disclosure > summary::after { content:" +"; color:var(--ink-subtle); }
.ui-disclosure[open] > summary::after { content:" −"; }
#cost-line-contracts .ui-disclosure > summary,
#cost-line-invoices .ui-disclosure > summary { width:fit-content; margin-top:8px; }
#cost-line-contracts .ui-disclosure[open] > form,
#cost-line-invoices .ui-disclosure[open] > form { margin-top:12px; }

/* Quiet disclosure keeps evidence secondary without inventing a page-specific control. */
.ui-disclosure-quiet > summary { display:flex; align-items:center; gap:.4rem; padding:.45rem 0; border:0; border-radius:0; background:transparent; color:var(--accent); white-space:normal; }
.ui-disclosure-quiet > summary::before { content:"▸"; }
.ui-disclosure-quiet[open] > summary::before { content:"▾"; }
.ui-disclosure-quiet > summary::after, .ui-disclosure-quiet[open] > summary::after { content:none; }
.ui-disclosure-quiet > summary:focus-visible { outline:2px solid var(--accent); outline-offset:3px; }

.filters .more-row { grid-column:1 / -1; min-width:0; display:flex; flex-wrap:wrap;
                     align-items:flex-end; gap:10px 14px; }
.filter-count { color:var(--ink-muted); font-size:12px; margin:0; padding:10px 10px 0; }
/* On narrow screens actions get their own row instead of squeezing fields. */
@media (max-width:700px) {
  .filters { grid-template-columns:minmax(0,1fr); }
  .filters .corner { justify-self:end; }
}

/* Outside swapped cards: feedback does not move forms or discard their drafts. */
.toast-region { position: fixed; right: 24px; bottom: 24px; z-index: 120; max-width: calc(100vw - 32px); }
.toast { display: flex; align-items: center; gap: 12px; padding: 12px 14px; max-width: 380px; border: 1px solid var(--line); border-radius: 12px; background: var(--surface); color: var(--ink); box-shadow: 0 8px 30px #0002; }
.toast-icon { display: grid; place-items: center; flex-shrink: 0; width: 26px; height: 26px; border-radius: 50%; background: var(--ok-bg); color: var(--ok); }
.toast [data-toast-message] { overflow-wrap: anywhere; }
.toast .toast-dismiss { flex-shrink: 0; margin-left: 8px; font-size: 18px; }
@media (max-width: 480px) { .toast-region { right: 16px; bottom: 16px; } }
/* StaticToast: no script on the signed-out pages to hide it, so it hides itself. */
.toast-fade { animation: toast-fade .3s ease 6s forwards; }
@keyframes toast-fade { to { opacity: 0; visibility: hidden; } }
@media (prefers-reduced-motion: reduce) { .toast-fade { animation-duration: 0s; } }

/* The Drive page, top to bottom: the platform card (the Drive and its alerts),
   then the organizations, each a disclosure holding its SPC rows. One column;
   rows stack below 560px, so nothing scrolls sideways. */
.drive-admin { display:grid; gap:16px; max-width:960px; margin-inline:0; min-width:0; }
.drive-admin-unavailable { max-width:720px; }
.drive-admin .err, .drive-admin .drive-admin-success { margin:0; }
.drive-admin-success { padding:10px 12px; border:1px solid var(--ok-line); border-radius:var(--r-sm);
                       background:var(--ok-bg); color:var(--ok); }
.drive-admin-top { display:flex; justify-content:flex-end; align-items:center; gap:10px; }
.drive-admin .card { margin:0; }
.drive-admin form { margin:0; }
.drive-admin dialog form { display:block; }
.drive-admin a.btn svg, .drive-admin button svg { margin-right:6px; vertical-align:-2px; }
.drive-admin :is(a, button, summary):focus-visible { outline:2px solid var(--accent); outline-offset:3px; border-radius:var(--r-sm); }

.drive-platform-actions { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
#drive-shared-card .card-head { flex-wrap:wrap; }
.drive-platform-actions .btn { white-space:nowrap; }
.drive-refresh { display:inline-flex; align-items:center; justify-content:center; padding:6px; }
.drive-refresh.htmx-request::before { content:none; }
.drive-refresh.htmx-request svg { animation:drive-admin-spin 1s linear infinite; }
.drive-platform-state { display:flex; align-items:center; gap:8px; flex-wrap:wrap; color:var(--ink-muted); }
.drive-state { display:inline-flex; align-items:center; gap:6px; }
.drive-state.bad { color:var(--bad); }
.drive-dot { width:8px; height:8px; border-radius:50%; display:inline-block; background:var(--line-strong); flex:none; }
.drive-dot.ok { background:var(--ok); }
.drive-dot.warn { background:var(--warn); }
.drive-dot.bad { background:var(--bad); }
.drive-spin { width:10px; height:10px; border-width:2px; }
.drive-platform-note { margin:10px 0 0; color:var(--ink-muted); }

.drive-alert { margin-top:14px; padding:12px 14px; border-radius:var(--r-sm); border:1px solid var(--line); background:var(--surface-2); }
.drive-alert-warn { background:var(--warn-bg); border-color:transparent; }
.drive-alert-bad { background:var(--bad-bg); border-color:transparent; }
.drive-alert h3 { margin:0; font-size:13px; font-weight:650; display:flex; align-items:center; gap:8px; }
.drive-alert p { margin:4px 0 0; color:var(--ink-muted); overflow-wrap:anywhere; }
.drive-alert code { font-size:11px; }
.drive-alert-action { margin-top:10px; display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
.drive-alert-action form { margin:0; }
.drive-job { display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin:8px 0 0; color:var(--ink); }
.drive-job-count { color:var(--ink-muted); }

.drive-details { margin-top:12px; color:var(--ink-muted); }
.drive-details > summary { cursor:pointer; padding:4px 0; font-size:12px; font-weight:650; }
.drive-admin-state { display:flex; flex-wrap:wrap; align-items:baseline; justify-content:space-between; gap:4px 16px;
                     padding:8px 0; border-bottom:1px solid var(--line); }
.drive-admin-state:last-child { border-bottom:0; }
.drive-admin-state span { color:var(--ink-muted); }
.drive-admin-state strong, .drive-admin-state a { font-weight:600; text-align:right; overflow-wrap:anywhere; min-width:0; color:var(--ink); }
.drive-admin-state code { font-size:11px; }

.drive-section-action { display:flex; align-items:center; gap:8px; }
.drive-muted { color:var(--ink-muted); font-size:12px; }
.drive-admin-link-form, .drive-admin-link-all { margin:0; }

.drive-org { border-top:1px solid var(--line); }
.drive-org:first-of-type { border-top:0; }
.drive-org-row { display:grid; grid-template-columns:18px minmax(0,1fr) auto auto; align-items:center; gap:10px;
                 padding:11px 2px; cursor:pointer; list-style:none; }
.drive-org-row::-webkit-details-marker { display:none; }
.drive-org-row::before { content:""; width:6px; height:6px; border-right:1.5px solid var(--ink-subtle);
                         border-bottom:1.5px solid var(--ink-subtle); transform:rotate(-45deg); margin-left:4px; transition:transform .15s; }
.drive-org[open] > .drive-org-row::before { transform:rotate(45deg); }
.drive-org-name { font-weight:600; font-size:14px; overflow-wrap:anywhere; }
.drive-org-sum { color:var(--ink-muted); }
.drive-org-open { color:var(--accent); font-size:12px; white-space:nowrap; }
.drive-org-body { margin:0 0 10px 28px; }
.drive-org-note { margin:6px 0 0; color:var(--ink-muted); }
.drive-org-alert { margin-bottom:10px; padding:10px 12px; border-radius:var(--r-sm); background:var(--bad-bg); }
.drive-org-alert p { margin:0; color:var(--ink); overflow-wrap:anywhere; }
.drive-org-alert .drive-admin-action-row { display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-top:10px; }
.drive-org-alert .drive-admin-action-row form { margin:0; }
.drive-org-body .drive-job { margin:0 0 8px; }

.drive-spc-list { list-style:none; margin:0; padding:0; min-width:0; }
.drive-spc { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(0,240px); align-items:center; gap:10px;
             padding:7px 0; border-top:1px dashed var(--line); min-width:0; }
.drive-spc:first-child { border-top:0; }
.drive-spc-name { overflow-wrap:anywhere; min-width:0; }
.drive-spc-word { display:flex; align-items:center; color:var(--ink-muted); min-width:0; overflow-wrap:anywhere; }
.drive-spc-word.ok { color:var(--ok); }
.drive-spc-word.warn { color:var(--warn); }
.drive-spc-word.bad { color:var(--bad); }
.drive-spc-spin { display:inline-block; flex:none; width:10px; height:10px; border:2px solid var(--line); border-top-color:var(--accent);
  border-radius:50%; margin-right:6px; animation:drive-admin-spin .8s linear infinite; }
.drive-job .drive-admin-spinner { width:14px; height:14px; border-width:2px; }
.drive-spc-acts { display:flex; flex-wrap:wrap; gap:6px; justify-content:flex-end; border:0; margin:0; padding:0; min-width:0; }
.drive-spc-acts form, .drive-spc-acts .drive-spc-repairs { display:inline-flex; align-items:center; gap:6px; margin:0; }
.drive-spc-hint { grid-column:1 / -1; margin:-4px 0 0; color:var(--ink-muted); font-size:12px; overflow-wrap:anywhere; }

@media (max-width:560px) {
  .drive-org-row { grid-template-columns:18px minmax(0,1fr); }
  .drive-org-sum, .drive-org-open { grid-column:2; }
  .drive-org-body { margin-left:0; }
  .drive-spc { grid-template-columns:1fr; gap:6px; }
  .drive-spc-acts { justify-content:flex-start; }
}
.drive-admin-spinner { display:block; width:32px; height:32px; border:3px solid var(--line);
  border-top-color:var(--accent); border-radius:50%; animation:drive-admin-spin .8s linear infinite; }
@keyframes drive-admin-spin { to { transform:rotate(360deg); } }
@media (prefers-reduced-motion:reduce) { .drive-admin-spinner, .drive-spc-spin, .drive-refresh.htmx-request svg { animation:none; } }

.action-confirm { width:min(520px,calc(100% - 32px)); box-sizing:border-box;
  max-height:90vh; overflow-y:auto; padding:22px 24px; color:var(--ink);
  background:var(--surface); border:1px solid var(--line-strong);
  border-radius:var(--r-lg); box-shadow:var(--shadow-2); }
.action-confirm::backdrop { background:rgba(10,14,20,.45); }
.action-confirm h2 { margin-top:0; }
.action-confirm p { line-height:1.5; }
/* Same reason as .modal-acts: the button row follows a control, and nothing
   else puts space between them, so Cancel sat flush against the field above. */
.action-confirm form > .inline:last-child { margin-top:18px; }

/* The issued link replaces the confirmation step in the same dialog. */
.reset-link-dialog:has([data-reset-issued]) [data-reset-step] { display:none; }
.reset-link-dialog [data-reset-step] form > label { max-width:200px; margin:12px 0 18px; }
.reset-link-copy { display:flex; gap:8px; align-items:center; margin:14px 0 12px; }
/* Read-only and visibly so: a quiet code-style box, not a field to edit. */
.reset-link-copy .ui-input.reset-link-value { flex:1; min-width:0; padding:0 10px; background:var(--surface-2);
  border-color:var(--line); color:var(--ink-muted); text-overflow:ellipsis; cursor:text; }
.reset-link-facts { margin:0; padding-left:18px; color:var(--ink-muted); font-size:13px; line-height:1.6; }
.reset-link-hint { margin:10px 0 16px; color:var(--ink-muted); font-size:13px; }
.reset-link-dialog [data-reset-issued] .inline { justify-content:flex-end; }

.drive-refresh { display:inline-flex; align-items:center; justify-content:center; padding:6px; }
.drive-refresh.htmx-request::before { content:none; }
.drive-refresh.htmx-request svg { animation:drive-admin-spin 1s linear infinite; }

.fm { display: grid; gap: 1rem; min-width: 0; }
.fm header h1 { margin: 0; }
.fm-intake { display:grid; gap:.6rem; }
.fm-dropzone { display:grid; grid-template-columns:auto minmax(0,1fr) auto; align-items:center; gap:1.25rem; min-height:112px; padding:1.25rem 1.5rem; border:1px dashed var(--line-strong); border-radius:var(--r-lg); background:var(--surface); transition:border-color .15s,background .15s,box-shadow .15s; }
.fm-dropzone.fm-dragging { border-color:var(--accent); background:var(--accent-light); }
/* Fade what can't be used, not the notice saying why (it fell to about
   3.3:1) nor the SPC select, which stays usable when one unlinked SPC is chosen. */
.fm-dropzone[aria-disabled=true] :is(.fm-upload-icon, .fm-drop-copy > strong, .fm-choose) { opacity:.55; }
.fm-upload-icon { display:grid; place-items:center; width:52px; height:52px; border-radius:var(--r-lg); background:var(--accent-light); color:var(--accent); font-size:1.65rem; }
.fm-dragging .fm-upload-icon { background:var(--surface); }
.fm-drop-copy { min-width:0; }
.fm-drop-copy strong { display:block; font-size:.98rem; font-weight:650; letter-spacing:-.015em; }
.fm-upload-notice { margin:.45rem 0 0; color:var(--warn); font-size:.82rem; line-height:1.45; }
.fm-upload-notice a { color:inherit; text-decoration:underline; }
.fm-drop-actions { display:flex; align-items:end; gap:.75rem; }
.fm-spc-field.ui-label { display:grid; gap:.35rem; min-width:220px; color:var(--ink-muted); font-size:.72rem; font-weight:600; letter-spacing:.03em; text-transform:uppercase; }
.fm-spc-field .ui-select { width:100%; color:var(--ink); font-size:.85rem; font-weight:500; letter-spacing:normal; text-transform:none; }
.fm-choose.ui-label { position:relative; display:grid; place-items:center; min-height:36px; cursor:pointer; padding:.55rem 1rem; border-radius:var(--r-sm); background:var(--primary); color:var(--ink-invert); font-size:.82rem; font-weight:600; white-space:nowrap; }
.fm-file-input { position:absolute; inset:0; width:100%; height:100%; opacity:0; cursor:pointer; }
.fm-choose:focus-within { outline:2px solid var(--ink-muted); outline-offset:3px; }
.fm-activity { border:1px solid var(--line); border-radius:12px; overflow:hidden; }
.fm-activity summary { padding:.75rem 1rem; cursor:pointer; font-size:.8rem; }
/* Keep the disclosure affordance before its label, separate from help. */
.fm-activity.ui-disclosure > summary::after { content:none; }
.fm-activity > summary::before { content:""; display:inline-block; width:8px; height:8px; margin-right:.65rem; background-image:var(--select-chevron); background-size:4px 4px; background-position:left center,right center; background-repeat:no-repeat; transform:rotate(-90deg); }
.fm-activity[open] > summary::before { transform:rotate(0deg); }
.fm-activity summary span { margin-left:.3rem; padding:.1rem .35rem; border-radius:4px; background:var(--surface-2); }
.fm-activity summary .fm-activity-failed { margin-left:.3rem; padding:0; background:none; color:var(--ink-muted); }
.fm-activity summary .fm-activity-failed .badge { margin-left:.15rem; padding:.1rem .45rem; background:var(--bad-bg); color:var(--bad); }
.fm-upload-list { max-height:min(44vh,400px); overflow:auto; }
.fm-upload-item { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,12rem) 9rem; gap:.4rem .75rem; align-items:center; padding:.55rem 1rem; border-top:1px solid var(--line); font-size:.8rem; }
.fm-upload-item[data-phase=awaiting_spc] { grid-template-columns:minmax(0,1fr) minmax(220px,auto) auto; background:var(--accent-light); }
.fm-upload-name { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-weight:600; }
.fm-upload-state { grid-column:2; overflow-wrap:anywhere; color:var(--ink-muted); font-size:.74rem; }
.fm-upload-item button { grid-column:3; grid-row:1; justify-self:end; }
.fm-upload-item[data-phase=awaiting_spc] .fm-upload-state { grid-column:1; }
.fm-upload-item[data-phase=awaiting_spc] button { grid-column:3; grid-row:1/3; background:var(--surface); color:var(--bad); border:1px solid var(--bad-line); }
.fm-upload-item[data-phase=awaiting_spc] button:hover { background:var(--bad-bg); }
.fm-upload-destination.ui-label:not([hidden]) { display:grid; grid-column:2; grid-row:1/3; gap:.3rem; min-width:220px; color:var(--ink-muted); font-size:.7rem; font-weight:600; letter-spacing:.03em; text-transform:uppercase; }
.fm-upload-destination .ui-select { width:100%; color:var(--ink); font-size:.85rem; font-weight:500; letter-spacing:normal; text-transform:none; }
.fm-upload-item[data-phase=ready] .fm-upload-state { color:var(--ok); }
.fm-upload-item[data-phase=failed] .fm-upload-state { color:var(--bad); }
/* The SPC overview's アップロード: the shared upload box as one button, its results below it. */
.upload-button { display:grid; justify-items:end; gap:.6rem; margin-bottom:1rem; }
.upload-button > .fm-activity { justify-self:stretch; }
.fm-queue-tabs { display:flex; gap:1.4rem; border-bottom:1px solid var(--line); }
.fm-queue-tabs button { border:0; border-radius:0; background:none; box-shadow:none; padding:.65rem 0; color:var(--ink-muted); }
.fm-queue-tabs button[aria-pressed=true] { color:var(--ink); border-bottom:2px solid var(--ink); }
.fm-queue-tabs span { margin-left:.3rem; padding:.1rem .3rem; border-radius:4px; background:var(--surface-3); font-size:.7rem; }
.fm-list.card { min-width:0; margin-bottom:0; }
.fm-filters { display: flex; flex-wrap: wrap; gap: .5rem; padding-bottom:14px; margin-bottom:4px; border-bottom:1px solid var(--line); }
.fm-filters [type=search] { flex: 1 1 16rem; }
.fm-table-wrap tr[hidden] { display: none; }
.fm-type-source { display:inline-block; margin-left:.45rem; padding:.05rem .35rem; border:1px solid var(--line-strong); border-radius:4px; color:var(--ink-muted); font-size:.68rem; font-weight:600; line-height:1.3; vertical-align:middle; white-space:nowrap; }
.fm-row-actions { white-space:nowrap; }
.fm-row-actions > * + * { margin-left:6px; }
.fm-delete-button.ghost, .fm-delete-confirm button[type=submit] { color:var(--bad); border-color:var(--bad-line); background:var(--surface); }
.fm-delete-button.ghost:hover, .fm-delete-confirm button[type=submit]:hover { background:var(--bad-bg); }
.fm-delete-button:disabled { opacity:.45; }
.fm-delete-confirm p { overflow-wrap:anywhere; }
.fm-pagination { display: flex; align-items: center; gap: .7rem; justify-content: flex-end; margin-top:14px; }
.fm-pagination [data-file-range] { margin-right: auto; }
.fm-dialog { width: min(96vw, 1200px); height: min(92dvh, 850px); max-width: none; max-height: none; padding: 0; border: 1px solid var(--line-strong); border-radius: var(--r-lg); background: var(--surface); color: var(--ink); overflow: hidden; }
/* The reviews wrapper needs a sized row: its old auto height left the PDF
   iframe halfway up a fixed-height dialog with empty space beneath it. */
.fm-dialog[open] { display: grid; grid-template-rows: auto minmax(0, 1fr); }
.fm-dialog::backdrop { background: #0c1014cc; }
.fm-dialog > header { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .8rem 1rem; border-bottom: 1px solid var(--line); background: var(--surface-2); }
.fm-dialog > header h2 { min-width: 0; font-size: 1rem; margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fm-dialog > header nav { display: flex; align-items: center; gap: .4rem; flex-shrink: 0; }
.fm-dialog > [data-file-reviews] { min-height: 0; overflow: hidden; }
/* Data on the left, the original on the right, as on the record pages. */
.fm-dialog [data-file-review]:not([hidden]) { display: grid; grid-template-columns: minmax(310px, .8fr) minmax(0, 1.2fr); grid-template-rows:minmax(0,1fr) auto; height: 100%; min-height: 0; }
.fm-dialog[open]:has([data-file-review]:not([hidden])[data-file-unreadable=true]) { display: block; width: min(92vw, 640px); height: fit-content; max-height: 90dvh; overflow: auto; }
.fm-dialog [data-file-review][data-file-unreadable=true]:not([hidden]) { display: block; height: auto; }
.fm-dialog [data-file-review][data-file-unreadable=true] .fm-preview { display: none; }
.fm-dialog [data-file-review][data-file-unreadable=true] .fm-detail { max-height: 75dvh; }
.fm-preview { display: grid; grid-template-rows: minmax(0, 1fr); min-width: 0; min-height: 0; background: var(--surface-3); border-left: 1px solid var(--line); }
/* The footer carries the review's own actions, under the data they act on;
   the download is in the preview's bar. */
.fm-review-footer { display:grid; grid-column:1/-1; grid-row:2; grid-template-columns:minmax(310px,.8fr) minmax(0,1.2fr); border-top:1px solid var(--line); }
.fm-review-actions { display:flex; align-items:center; justify-content:flex-end; gap:.6rem; padding:.65rem 1rem; background:var(--surface); }
.fm-detail { display:flex; flex-direction:column; min-width: 0; min-height: 0; overflow: auto; }
.fm-detail > [data-file-pane] { padding: 0 1.25rem 1.25rem; }
.fm-detail > [data-file-pane=details]:not([hidden]) { display:flex; flex-direction:column; flex:1 0 auto; }
.fm-detail dl { display: grid; grid-template-columns: minmax(6.5rem, auto) minmax(0, 1fr); gap: .75rem 1rem; margin: 0; padding: 1.25rem 0; }
.fm-detail dt { color: var(--ink-muted); }
.fm-detail dd { margin: 0; overflow-wrap: anywhere; }
.fm-detail form { padding: 1rem 0; border-top: 1px solid var(--line); }
.fm-detail form h3 { margin:0 0 .85rem; font-size:.85rem; font-weight:650; color:var(--ink); }
.fm-detail form fieldset { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: end; gap: .75rem; margin: 0; padding: 0; border: 0; }
.fm-detail form label { min-width: 0; }
.fm-detail form button { white-space: nowrap; }
.fm-detail .fm-classify fieldset { grid-template-columns:minmax(0,1fr); }
.fm-detail .fm-classify .ui-select { width:100%; }
.fm-detail .fm-classify button { justify-self:end; }
.fm-review-type,.fm-record { padding:1rem 0; border-top:1px solid var(--line); }
.fm-review-heading { display:flex; align-items:center; justify-content:space-between; gap:.75rem; margin-bottom:.8rem; }
.fm-review-heading h3 { margin:0; font-size:.85rem; font-weight:650; }
.fm-detail .fm-review-type form,.fm-detail .fm-review-heading form { padding:0; border:0; }
.fm-review-heading .fm-ai-fill { flex:none; }
.fm-record-modes { display:grid; grid-template-columns:1fr 1fr; gap:.25rem; padding:.2rem; border:1px solid var(--line); border-radius:var(--r-sm); background:var(--surface-2); }
.fm-record-modes .ui-button { justify-content:center; width:100%; border:0; background:transparent; color:var(--ink-muted); box-shadow:none; }
.fm-record-modes .ui-button[aria-pressed=true] { background:var(--surface); color:var(--ink); box-shadow:0 1px 3px #0002; }
.fm-record > [data-file-record-panel=existing] form { border:0; padding:1rem 0 0; }
.fm-record > [data-file-record-panel=existing] .ui-select { width:100%; }
.fm-detail form.fm-draft { padding:1rem 0 0; border:0; }
.fm-draft-state { display:flex; justify-content:flex-end; }
.fm-detail form.fm-draft fieldset { display:grid; grid-template-columns:minmax(0,1fr); align-items:stretch; gap:.8rem; margin:0; padding:0; border:0; }
.fm-draft fieldset h4 { margin:.45rem 0 0; padding-top:.85rem; border-top:1px solid var(--line); font-size:.8rem; }
.fm-draft fieldset h4:first-child { margin:0; padding:0; border:0; }
.fm-draft .ui-label { min-width:0; }
.fm-draft .ui-input,.fm-draft .ui-select { width:100%; }
.fm-draft .ms-list .picker-option { white-space:normal; overflow-wrap:anywhere; }
.fm-draft-pair { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:.75rem; }
.fm-review-actions .fm-archive fieldset { display:flex; margin:0; padding:0; border:0; }
.fm-review-actions .fm-archive button { background:transparent; border-color:var(--bad-line); color:var(--bad); }
.fm-review-actions .fm-archive button:hover { background:var(--bad-bg); }
.fm-tabs { display: flex; gap: 1.25rem; padding: 0 1.25rem; border-bottom: 1px solid var(--line); }
.fm-tabs .ui-button { padding: .9rem 0 .7rem; border: 0; border-bottom: 2px solid transparent; border-radius: 0; background: none; color: var(--ink-muted); box-shadow: none; }
.fm-tabs .ui-button[aria-pressed=true] { border-bottom-color: var(--accent); color: var(--ink); font-weight: 700; }
.fm-tabs .ui-button:hover { color: var(--ink); }
.fm-detail [data-file-pane=history] li { margin: .6rem 0; }
.fm-detail [data-file-pane=history] li span { display: block; font-size: .8rem; opacity: .7; }
@media (max-width: 750px) { .fm-dropzone { grid-template-columns:auto minmax(0,1fr); gap:1rem; } .fm-drop-actions { grid-column:1/-1; display:grid; grid-template-columns:minmax(0,1fr) auto; width:100%; } .fm-spc-field.ui-label { min-width:0; } .fm-dialog > [data-file-reviews] { overflow: auto; } .fm-dialog [data-file-review]:not([hidden]) { grid-template-columns: 1fr; grid-template-rows: minmax(40vh, 55vh) auto auto; height: auto; } .fm-preview { order:-1; border-left: 0; } .fm-detail { overflow: visible; } .fm-review-footer { grid-row:3; grid-template-columns:minmax(0,1fr) auto; } }
@media (max-width: 600px) {
  .fm-upload-item { grid-template-columns:minmax(0,1fr) auto; }
  .fm-upload-state { grid-column:1; }
  .fm-upload-item button { grid-column:2; grid-row:1/3; }
}
@media (max-width: 480px) { .fm-dropzone { padding:1rem; } .fm-drop-actions { grid-template-columns:1fr; } .fm-choose.ui-label { justify-self:stretch; } .fm-upload-item[data-phase=awaiting_spc] { grid-template-columns:minmax(0,1fr); } .fm-upload-destination.ui-label:not([hidden]) { grid-column:1; grid-row:4; min-width:0; margin-top:.45rem; } .fm-upload-item[data-phase=awaiting_spc] button { grid-column:1; grid-row:auto; justify-self:start; } .fm-upload-item[data-phase=awaiting_spc] button { grid-row:5; justify-self:end; } .fm-dialog { width: calc(100vw - 1rem); height: calc(100dvh - 1rem); } .fm-dialog > header { padding: .65rem .8rem; } .fm-detail form fieldset, .fm-draft-pair { grid-template-columns: 1fr; } .fm-detail form button { justify-self: end; } }

/* The document details shell stays stable across manual and automatic states. */
.fm-page-heading { display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:1rem; margin-bottom:1.25rem; }
.fm-page-heading h1 { margin:0; }
.fm-org-switch { display:flex; align-items:center; gap:.5rem; margin-right:auto; }
.fm-org-switch .ui-label { display:flex; flex-direction:row; align-items:center; gap:.45rem; }
.fm-review-section { padding:1rem 0; border-top:1px solid var(--line); }
.fm-review-section p { margin:.5rem 0 .85rem; }
.fm-detail .fm-review-section dl { padding:0 0 .75rem; }
.fm-detail .fm-review-section form { padding:.5rem 0 0; border:0; }
.fm-check-evidence { border-top:1px solid var(--line); margin-top:1rem; padding-top:1rem; }
.fm-check-evidence h4 { margin:1rem 0 .5rem; }
.fm-detail dl { grid-template-columns:7rem minmax(0,1fr); }
.fm-status-reason { display:block; margin-top:.35rem; font-size:.75rem; color:var(--ink-muted); max-width:24rem; white-space:normal; }
.fm-field-error { display:block; margin-top:.3rem; color:var(--bad); font-size:.75rem; font-weight:400; line-height:1.5; }
.fm-detail [aria-invalid=true] { border-color:var(--bad); }
.fm-exception { font-weight:600; }
.trash-actions { display:flex; align-items:center; gap:.4rem; flex-wrap:wrap; }

/* Source-first review: business result and its actions share one reading order. */
.fm-source-review .fm-preview { display:flex; flex-direction:column; min-height:0; }
.fm-dialog-heading { min-width:0; }
.fm-dialog-heading small { display:block; margin-top:.2rem; color:var(--ink-muted); font-size:.8rem; }
.fm-dialog > header { padding:.8rem 1.25rem; }
.fm-dialog .fm-dialog-heading h2 { font-size:.9rem; }
.fm-dialog:has(.fm-source-review:not([hidden])) { width:min(97vw,1320px); height:94dvh; }
.fm-dialog .fm-source-review:not([hidden]) { grid-template-columns:minmax(350px,1fr) minmax(0,1.4fr); grid-template-rows:minmax(0,1fr); }
.fm-source-review .fm-detail { padding:0 1.6rem; }
.fm-source-review .fm-tabs { position:sticky; top:0; z-index:1; flex:none; padding:0; background:var(--surface); }
.fm-source-review .fm-tabs .ui-button { padding:1rem 0 .8rem; }
.fm-source-review .fm-detail > [data-file-pane] { padding:0 0 1.5rem; }
.fm-readiness { display:flex; gap:.75rem; padding:1.8rem 0 1.4rem; margin-bottom:1.4rem; border-bottom:1px solid var(--line); }
.fm-readiness h3 { font-size:1.35rem; line-height:1.3; margin:0 0 .65rem; letter-spacing:-.025em; }
.fm-readiness p { color:var(--ink-muted); font-size:.82rem; line-height:1.65; margin:0; }
.fm-readiness-mark { flex:none; width:1.5rem; height:1.5rem; line-height:1.5rem; text-align:center; border-radius:50%; background:var(--warn-bg); color:var(--warn); font-weight:700; }
.fm-readiness[data-tone=ready] .fm-readiness-mark { background:var(--ok-bg); color:var(--ok); }
.fm-readiness[data-tone=waiting] .fm-readiness-mark { background:var(--accent-light); color:var(--accent); }
.fm-detail .fm-business-values { display:block; padding:0; margin:0 0 1.5rem; font-size:.875rem; }
.fm-business-row { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1.2fr); gap:1rem; padding:.75rem 0; border-bottom:1px solid var(--line); align-items:baseline; }
.fm-business-row dd { text-align:right; font-weight:600; font-variant-numeric:tabular-nums; }
.fm-business-row[data-uncertain=true] dd { color:var(--warn); }
/* A bank field the two checks read differently, in the payee card. */
tr[data-uncertain=true] td { color:var(--warn); background:var(--warn-bg); }
.fm-value-comparison { display:flex; flex-direction:column; gap:.25rem; margin-top:.45rem; font-size:.75rem; font-weight:400; }
.fm-detail .fm-summary-meta { font-size:.8rem; padding:.75rem 0; }
.fm-check-evidence { padding-top:.65rem; border-top:1px solid var(--line); }
.fm-document-options { margin-top:.35rem; }
.fm-summary-actions { display:flex; align-items:center; flex-wrap:wrap; gap:.65rem; padding:1.35rem 0 .3rem; }
.fm-summary-actions .fm-edit-secondary { border-color:transparent; background:transparent; }
.fm-source-review .fm-classify { border:0; padding-top:1.75rem; }
@media(max-width:900px) {
  .fm-source-review .fm-detail { padding:0 1.25rem; }
}
@media(max-width:750px) {
  .fm-dialog .fm-source-review:not([hidden]) { grid-template-columns:1fr; grid-template-rows:minmax(35vh,45vh) auto; }
  .fm-source-review .fm-preview { order:-1; border-left:0; }
  .fm-source-review .fm-detail { overflow:visible; }
  .fm-source-review .fm-tabs { position:static; }
}
.fm-detail .fm-human-form fieldset { grid-template-columns:minmax(0,1fr); gap:.8rem; }
.fm-human-form [data-human-error] { display:block; }
.fm-human-form [data-human-error][hidden] { display:none; }
.fm-check-identities { display:grid; grid-template-columns:1fr 1fr; gap:.5rem; font-size:.75rem; padding:.6rem 0; }
.fm-check-identities span { display:block; overflow-wrap:anywhere; }
.fm-check-table { overflow:auto; max-height:20rem; }
.fm-check-table table { width:100%; font-size:.75rem; border-collapse:collapse; }
.fm-check-table th,.fm-check-table td { padding:.55rem .4rem; border-bottom:1px solid var(--line); text-align:left; overflow-wrap:anywhere; }
.fm-check-evidence pre { max-height:15rem; overflow:auto; font-size:.7rem; white-space:pre-wrap; overflow-wrap:anywhere; }
.fm-review-issues { font-size:.8rem; padding-left:1rem; }
@media(max-width:800px) { .fm-source-review .fm-detail form button { justify-self:start; } }
.fm-human-form label:has([name=source_verified]) { display:flex; flex-direction:row; align-items:flex-start; gap:.6rem; }
.fm-human-form [name=source_verified] { flex:0 0 auto; margin:.15rem 0 0; }

.fm-source-evidence { display: block; font-size: .75rem; font-weight: normal; color: var(--ink-muted); margin-top: .3rem; overflow-wrap: anywhere; }

/* External capture rows on /drive/imports. "Open in {org}" for another of the
   viewer's orgs is a POST (/org switches the org first), so it is a button,
   but it reads as the plain link the active org gets. */
.external-captures > td { padding-top:0; }
.external-captures tbody td:last-child { text-align:right; white-space:nowrap; }
.external-open { display:inline; margin:0; }
.external-open .ui-button.link, .external-open .ui-button.link:hover { background:none; border:0; padding:0; box-shadow:none;
  color:var(--accent); font-size:inherit; font-weight:inherit; text-decoration:underline; }
/* 支払: a payment list's step bar, the selection bar on 未完了, a list's actions. */
.steps { display:flex; gap:4px; list-style:none; padding:0; margin:0 0 6px; counter-reset:step; flex-wrap:wrap; }
.steps li { counter-increment:step; flex:1 1 0; min-width:7em; padding:6px 10px; font-size:12px; color:var(--ink-subtle);
  border-top:3px solid var(--line); }
.steps li::before { content:counter(step) ". "; }
.steps li.done { color:var(--ink-muted); border-top-color:var(--ok); }
.steps li.on { color:var(--ink); font-weight:600; border-top-color:var(--accent); }
.steps li.skip { color:var(--ink-subtle); border-top-style:dashed; }
.select-bar { margin:0 0 12px; }
.list-acts { margin:12px 0; }
.plain { list-style:none; padding:0; margin:0 0 10px; }
.plain li { padding:3px 0; }

/* 支払: the start page, the builder, the batch page and its dialogs.
   Docs: docs/features/payments.md#pages */
td.chk, th.chk { width:28px; padding-right:0; }
tr.off td { color:var(--ink-subtle); }
tr.grp td { background:var(--surface-2); font-size:11px; font-weight:600; color:var(--ink-muted); letter-spacing:.02em; }
tr.grp:hover td { background:var(--surface-2); }
/* A payer group's Mark all paid sits at the end of its header row, not under it. */
tr.grp .grp-act { float:right; margin:-4px 0; }
tr.grp .grp-act button { padding:2px 8px; font-size:11px; }
/* A line's bank-result buttons stack rather than break mid-word (却下…, 再支払へ戻す…). */
#lines .row-acts button { white-space:nowrap; }
tr.hit td { background:var(--bad-bg) !important; }
tr.old td { color:var(--ink-muted); }
tr.total td { font-weight:600; border-top:1px solid var(--line-strong); }
td.vendor { min-width:180px; }
.notice { background:var(--warn-bg); color:var(--warn); border:1px solid var(--line-strong); border-radius:8px; padding:10px 14px; margin-bottom:16px; }
.notice strong, .err strong { font-weight:600; }
.notice .inline, .err .inline { margin-top:8px; align-items:center; }
.nib { display:flex; align-items:center; gap:12px; justify-content:space-between; flex-wrap:wrap; }
.vis { font-size:11px; color:var(--ink-muted); font-weight:400; }
.mono { font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:11px; }
.field-err { color:var(--bad); font-size:11px; }
/* Builder: the running selection sits under the rows it counts. */
.batch-bar { position:sticky; bottom:0; z-index:5; display:flex; align-items:center; gap:14px; flex-wrap:wrap;
  margin:12px -24px -20px; padding:12px 24px; background:var(--surface); border-top:1px solid var(--line-strong);
  border-radius:0 0 var(--r-lg) var(--r-lg); box-shadow:0 -6px 14px rgba(20,20,19,.06); }
.batch-bar .sum { font-size:13px; }
.batch-bar .sum strong { font-weight:600; font-variant-numeric:tabular-nums; }
.batch-bar .grow { flex:1; }
.batch-bar .ui-label { flex-direction:row; align-items:center; gap:8px; }
/* The builder's band sits inside its card, over the table, as a list's does over its own. The
   runs' wrapper is only there to be swapped after a Fix save; its select lines up with SPC's. */
.builder-filters { margin-bottom:0; }
.builder-months { display:contents; }
/* The builder's bulk action takes the place of the column titles while rows are ticked, so the
   table does not jump. It is anchored to the Invoice title and runs over the hidden ones. */
.pay-table thead th { position:relative; }
.pay-table th .bulk { display:none; position:absolute; left:10px; top:0; bottom:0; align-items:center; gap:10px; white-space:nowrap; z-index:1; }
.pay-table thead.picking th:not(.chk) { color:transparent; }
.pay-table thead.picking th .bulk { display:flex; }
.warn-cell { min-width:150px; }
.warn-cell .cell-sub { display:block; }
.warn-cell .fix { margin:-4px 0 6px; white-space:nowrap; }
.warn-cell .fix + .wl { margin-top:2px; }
.wl { display:flex; gap:4px; flex-wrap:wrap; }
.narrow { display:none; margin-top:4px; font-size:12px; }
.narrow .ui-select { margin-top:4px; }
/* Narrow content (side nav open, under ~1100px): Due and Pay from fold under
   the vendor, so vendor, invoice number and warnings keep their width. */
@media (max-width:1100px) {
  .wide { display:none; } .narrow { display:block; } td.vendor { min-width:140px; }
  .warn-cell { min-width:120px; } .warn-cell .fix { display:block; margin-left:0; }
}
/* A laptop: the side nav is ~190px, so a 1366 or 1440 screen leaves ~1050-1100px for content, which
   eight columns do not fit (the table scrolled sideways and Pay from wrapped to three lines). Only
   Pay from folds under the vendor here, as all of Due, Payment month and Pay from do under 1100px. */
@media (min-width:1101px) and (max-width:1440px) {
  .pay-table .col-pay { display:none; }
  .pay-table .narrow { display:block; margin-top:2px; } .pay-table .narrow-dates { display:none; }
}
/* Under ~1300px even that does not fit (1280 leaves ~940px): fold as under 1100px. */
@media (min-width:1101px) and (max-width:1300px) {
  .pay-table .wide { display:none; } .pay-table .narrow-dates { display:block; }
  .pay-table td.vendor { min-width:140px; } .pay-table .warn-cell { min-width:120px; }
}
/* Batch page */
.next-bar { display:flex; align-items:center; gap:16px; flex-wrap:wrap; }
.next-bar .grow { flex:1; }
.next-bar .note { font-size:12px; color:var(--ink-muted); text-align:right; }
.next-total { font-size:20px; font-weight:600; letter-spacing:-.01em; font-variant-numeric:tabular-nums; }
.pm-stamps { display:inline-flex; gap:4px; vertical-align:middle; }
.pm-stamp { width:22px; height:22px; border-radius:50%; border:1.5px dashed var(--line-strong); display:inline-flex;
  align-items:center; justify-content:center; font-size:9px; font-weight:700; letter-spacing:.02em; }
.pm-stamp.on { border:1.5px solid var(--ok); color:var(--ok); background:var(--ok-bg); }
.card .pm-stamps { margin:2px 0 10px; }
.pm-ah { display:flex; align-items:center; justify-content:space-between; margin-bottom:6px; }
.pm-ah h2 { margin:0; }
.pm-stamplist { list-style:none; padding:0; margin:0; display:flex; flex-direction:column; gap:8px; }
.pm-stamplist li { display:flex; gap:8px; align-items:flex-start; }
.pm-stamplist .cell-sub { display:block; font-size:11px; color:var(--ink-muted); }
.pm-tick { width:16px; height:16px; color:var(--ok); display:inline-block; vertical-align:middle; flex:none; }
.next-bar .pm-why { max-width:460px; color:var(--ink); }
/* Approve dialog */
.big-total { font-size:30px; font-weight:600; letter-spacing:-.02em; font-variant-numeric:tabular-nums; margin:4px 0 0; }
.ack-h { margin:14px 0 0; font-weight:600; font-size:12px; }
.ack { display:flex; flex-direction:column; gap:6px; margin:6px 0 12px; }
.ack label.check { display:flex; align-items:flex-start; gap:8px; padding:8px 10px; border:1px solid var(--line); border-radius:var(--r-sm);
  text-transform:none; letter-spacing:0; font-size:12px; font-weight:400; color:var(--ink); margin:0; }
.ack label.check input { margin-top:2px; }
.ack .w { display:flex; gap:6px; flex-wrap:wrap; margin-top:3px; }
.digits { width:96px; font-variant-numeric:tabular-nums; letter-spacing:.2em; }
/* Start page and empty states */
.perm { padding:32px 24px; }
.perm p { margin:0 0 8px; color:var(--ink-muted); font-size:13px; }
/* 支払 start page: the months under one line of links that names them. */
/* The filter band is the app's own (.filters); it sits over every month rather than inside one card. */
.pay-filters { margin-bottom:16px; }
/* The box has no label above it, so it sits level with the controls, not with their labels. */
.ui-label.pay-mine { flex-direction:row; align-items:center; gap:6px; white-space:nowrap; align-self:flex-end; min-height:28px; }
/* Payments tables read top-down: a cell of three lines and one of one start on the same line, so a
   row's date, month and amount share a baseline (#108). The start page's months, the builder and the
   batch page's lines alike; a row's button and checkbox sit on that first line too. */
.pay-month td, #payment-builder td, #lines td { vertical-align:top; }
/* A row's button is taller than a line of text: centred on the first line rather than hanging below it. */
.pay-month td .btn { margin:-4px 0; }
/* The months as one line of chips over them, where the rail beside them took a laptop's width (#108).
   Pinned at the top of the page on the page's surface, one line high whatever the width (it scrolls
   sideways instead of wrapping), so an open month's pinned heading can sit exactly under it. Tall
   enough that a focused chip's ring is not cut off by the scroll box. A chip is the month alone; its
   state is its colour, never a number beside it (a number there reads as a day). */
.pay-jumps { position:sticky; top:0; z-index:4; display:flex; align-items:center; gap:6px; height:44px;
  margin:0 0 8px; overflow-x:auto; white-space:nowrap; font-size:12px; background:var(--surface); }
.pay-jumps > .muted-text { font-size:13px; margin-right:4px; }
.pay-jumps a { display:inline-block; padding:1px 10px; border:1px solid var(--line); border-radius:999px;
  color:var(--ink-muted); line-height:20px; text-decoration:none; }
.pay-jumps a:hover { background:var(--surface-2); color:var(--ink); }
.pay-jumps a.overdue { border-color:var(--bad-line); background:var(--bad-bg); color:var(--bad); }
.pay-jumps a.current { border-color:var(--accent); color:var(--ink); font-weight:600; }
.pay-jumps a.settled { color:var(--ink-subtle); }
/* Active's year, said where it changes; a hairline before every year but the first, and before Later. */
.pay-jump-year { margin:0 2px 0 4px; color:var(--ink-subtle); font-variant-numeric:tabular-nums; }
.pay-jump-year:not(.first)::before, .pay-jump-sep::before { content:""; display:inline-block; width:1px; height:16px;
  margin-right:10px; vertical-align:middle; background:var(--line); }
.pay-jump-sep::before { margin:0 4px; }
/* Past payments' pager: one year at a time, the year between its arrows. */
.pay-pager { display:inline-flex; align-items:center; gap:2px; margin-right:4px; }
.pay-pager .ui-button { padding:0 4px; }
.pay-pager .ui-button:disabled { opacity:.35; cursor:default; }
.pay-pager-year { min-width:44px; text-align:center; font-size:13px; font-weight:600; color:var(--ink);
  font-variant-numeric:tabular-nums; }
.pay-jumps ~ .pay-month { scroll-margin-top:56px; }
/* Pinned under the line with room above the title: the padding is taken back out of the card's own
   top padding, so the heading sits where it did while the month is not scrolled. */
.pay-jumps ~ .pay-month[open] > summary { top:44px; padding-top:10px; margin-top:-10px; }
/* A month is an ordinary card that folds: its head is laid out as .card-head
   (title, the state as a badge, the totals where other cards keep their
   controls), and stays pinned over its rows while they scroll. */
.pay-month { scroll-margin-top:12px; }
.pay-month > summary { display:flex; align-items:center; gap:10px; flex-wrap:wrap; padding:0; white-space:normal;
  font-size:13px; color:var(--ink); border:0; border-radius:0; background:var(--surface); }
.pay-month.ui-disclosure > summary::after, .pay-month.ui-disclosure[open] > summary::after { content:none; }
.pay-month > summary::before { content:""; width:6px; height:6px; border:solid var(--ink-muted); border-width:0 1.5px 1.5px 0;
  transform:rotate(-45deg); transition:transform .15s; flex:none; }
.pay-month[open] > summary { position:sticky; top:0; z-index:3; padding-bottom:8px; }
.pay-month[open] > summary::before { transform:rotate(45deg); }
.pay-month > summary h2 { margin:0; }
.pay-empty-month { margin:6px 0 0; }
/* A next step is one control; a long SPC name may wrap instead, as it does on every list. */
.pay-month td .btn { white-space:nowrap; }
/* Eight columns and a one-line action fit the card at a laptop width only with a little less side padding than the default 10px. */
.pay-month th, .pay-month td { padding-left:8px; padding-right:8px; }
.pay-totals { display:flex; gap:16px; margin-left:auto; font-variant-numeric:tabular-nums; }
@media (prefers-reduced-motion: reduce) { .pay-month > summary::before { transition:none; } }
/* The paid fold is a row of the table, quiet as a footnote line, not a button set apart from it. */
tr.pay-fold-row td { padding:0; border-bottom:0; }
tbody tr.pay-fold-row:hover td { background:none; }
.ui-button.pay-fold { display:inline-flex; align-items:center; gap:8px; padding:9px 10px; background:none; border:0; border-radius:0;
  color:var(--ink-muted); font-weight:400; text-align:left; }
.ui-button.pay-fold:hover { background:var(--surface-2); color:var(--ink); border:0; }
.ui-button.pay-fold::before { content:""; width:5px; height:5px; border:solid currentColor; border-width:0 1.5px 1.5px 0;
  transform:rotate(-45deg); transition:transform .15s; }
.ui-button.pay-fold[aria-expanded="true"]::before { transform:rotate(45deg); }
tr.pay-done td { color:var(--ink-subtle); }
tr.pay-done .badge, tr.pay-done .pm-stamps { opacity:.7; }
tr.pay-fold-row td { padding:4px 10px; }
.pay-none, .builder-none { padding:24px 10px; text-align:center; }
@media (max-width:900px) {
  .pay-totals { margin-left:16px; flex-basis:100%; }
}
.empty-start { text-align:center; padding:48px 24px; }
.empty-start h2 { margin:0 0 4px; }
.empty-start .muted-text { margin:0 0 16px; }
.empty-start .inline { justify-content:center; }
.skel { display:block; height:10px; border-radius:4px; background:linear-gradient(90deg,var(--surface-3),var(--surface-2),var(--surface-3)); background-size:200% 100%; animation:skel 1.2s linear infinite; }
@keyframes skel { to { background-position:-200% 0; } }

/* The settings hub: a grouped list, not a table. The person's own card leads,
   then one list per group, so each reads as a single block. Capped in width
   because a row is a label and a short line, and a full-width row leaves its
   chevron a screen away from what it opens. */
.set-hub { max-width:640px; }
.set-me { display:flex; align-items:center; gap:14px; padding:14px 16px; margin:0 0 28px;
          background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg);
          color:var(--ink); text-decoration:none; }
.set-me-avatar { width:40px; height:40px; border-radius:10px; flex:none; display:flex; align-items:center;
                 justify-content:center; background:var(--primary); color:var(--ink-invert);
                 font-size:17px; font-weight:600; }
.set-me-who { flex:1; min-width:0; display:flex; flex-direction:column; }
.set-me-name { font-size:15px; font-weight:600; letter-spacing:-.01em; }
.set-me-mail { font-size:12px; color:var(--ink-muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.set-go { display:flex; align-items:center; gap:4px; color:var(--ink-muted); white-space:nowrap; }
.set-chev { display:flex; color:var(--ink-subtle); }
.set-group { margin:0 0 24px; }
.set-group > h2 { margin:0 0 8px; padding-left:2px; font-size:13px; font-weight:500; color:var(--ink-muted); }
.set-list { list-style:none; margin:0; padding:0; background:var(--surface); border:1px solid var(--line);
            border-radius:var(--r-lg); overflow:hidden; }
.set-row { position:relative; display:flex; align-items:center; gap:12px; padding:10px 14px;
           color:var(--ink); text-decoration:none; }
.set-me:hover, .set-row:hover { background:var(--surface-2); }
/* Inset, because the list clips its rows to its rounded corners. */
.set-me:focus-visible, .set-row:focus-visible { outline:2px solid var(--accent); outline-offset:-2px; }
/* The divider starts at the text, not the icon, as a grouped list's does. */
.set-list li + li .set-row::before { content:""; position:absolute; top:0; right:0; left:54px;
                                     border-top:1px solid var(--line); }
.set-ico { width:28px; height:28px; flex:none; display:flex; align-items:center; justify-content:center;
           border-radius:var(--r-sm); background:var(--surface-3); color:var(--ink); }
.set-text { flex:1; min-width:0; display:flex; flex-direction:column; }
.set-label { font-size:13px; font-weight:500; }
.set-sub { font-size:11px; color:var(--ink-muted); }

/* ── Shared list, row-action, add, detail and section components ──────────────
   DataTable, Pager, RowActions/RowMenu, InlineAdd, DetailHead/DefList,
   StatusPill, Section/SettingRow (one file each in this folder). Every rule is
   scoped under a class of their own (.dt, .pager, .ra*, .add-*, .dhead-sub,
   .facts, .deflist, .spill, section.sect, .setting) so the bare table/th/td,
   .tscroll, .kv, .badge and .row-acts rules — and through them the cost sheet,
   which uses .pin and .hscroll — render exactly as before. No new colours,
   sizes or radii: tokens only, and controls keep the sizes their primitives
   give them. */

/* DataTable. The card is the size container, so priority columns answer to the
   table's own width rather than the window's — the same table sits full-width
   on one page and beside a panel on another. */
.dt { container:dt / inline-size; }
.dt-body { position:relative; }
.dt-body.htmx-request .dt-frame, .dt-body.htmx-request .pager { opacity:.6; transition:opacity .15s; }
.dt-err { display:flex; align-items:center; justify-content:space-between; gap:12px; margin:0 0 10px; }
.dt-err[hidden] { display:none; }
.dt-frame { position:relative; }
/* Flush with the card padding on the left so the pinned ident column pins
   there; bleeding to the card edge on the right like .tscroll. */
.dt-scroll { overflow:auto; margin:0 -24px 0 0; padding:0 24px 0 0; }
/* Viewport fit: the card fills the window, the header row sticks and the pager
   stays in view while the rows scroll. client.js measures --dt-top. A nested
   scroller in a short window is worse than a long page, so it turns off there. */
.dt-scroll[data-fit] { max-height:max(240px, calc(100dvh - var(--dt-top, 220px))); }
@media (max-height:600px) { .dt-scroll[data-fit] { max-height:none; } }
.dt thead th { position:sticky; top:0; z-index:2; background:var(--surface); box-shadow:inset 0 -1px 0 var(--line); }
.dt tbody tr:hover td { background:var(--surface-2); }
.dt th.ident, .dt td.ident { position:sticky; left:0; z-index:1; background:var(--surface); min-width:14em; max-width:22em; }
.dt thead th.ident { z-index:3; }
.dt tbody tr:hover td.ident, .dt tbody tr:hover td.acts { background:var(--surface-2); }
.dt-frame[data-edge=mid] .ident, .dt-frame[data-edge=end] .ident { box-shadow:inset -1px 0 0 var(--line-strong); }
.dt th.acts, .dt td.acts { position:sticky; right:0; z-index:1; background:var(--surface); width:1%; }
.dt thead th.acts { z-index:3; }
.dt-frame[data-edge=start] .acts, .dt-frame[data-edge=mid] .acts { box-shadow:inset 1px 0 0 var(--line-strong); }
/* The cue that more columns are to the right. With a pinned actions column its
   edge line says the same thing, so the fade is not drawn over the actions. */
.dt-frame::after { content:""; position:absolute; top:0; bottom:0; right:-24px; width:48px; pointer-events:none;
  background:linear-gradient(to left, var(--surface), transparent); opacity:0; transition:opacity .15s; }
.dt-frame[data-edge=start]::after, .dt-frame[data-edge=mid]::after { opacity:1; }
.dt-frame.has-acts::after { display:none; }
@container dt (max-width:900px) { .dt .p3 { display:none; } }
@container dt (max-width:600px) { .dt .p2 { display:none; } }
.dt-body > .empty { display:flex; align-items:center; flex-wrap:wrap; gap:10px; padding:18px 10px 8px; }

/* Pager: the range, an optional size select, Previous/Next. */
.pager { display:flex; justify-content:space-between; align-items:center; gap:12px; padding-top:10px; border-top:1px solid var(--line); }
.pager-range { margin-right:auto; color:var(--ink-muted); font-variant-numeric:tabular-nums; }
.pager-nav { display:flex; gap:6px; align-items:center; }
.pager-nav .ui-button, .ra { display:inline-flex; align-items:center; gap:4px; }

/* Row actions: ghost, compact, never filled. A destructive one turns red only
   on hover or focus; the filled red button lives in its confirm. */
.dt .row-acts { flex-wrap:nowrap; }
.ra { white-space:nowrap; }
.ra-danger:hover, .ra-danger:focus-visible { color:var(--bad); border-color:var(--bad-line); }
span.ra.off { background:var(--surface); color:var(--ink); border:1px solid var(--line-strong); border-radius:var(--r-sm);
  padding:var(--pad-sm); font-size:12px; line-height:1.5; font-weight:500; opacity:.5; cursor:not-allowed; }
@container dt (max-width:600px) { .dt .ra-wide { display:none; } }
@container dt (min-width:601px) { .dt .ra-narrow { display:none; } }
.ra-more { display:inline-block; }
.ra-more > summary { list-style:none; cursor:pointer; }
.ra-more > summary::-webkit-details-marker { display:none; }
.ra-more[open] > summary { background:var(--surface-2); }
/* The record page's その他: a named button the size of the Edit beside it, not
   a row's icon. Ghost, because the one dark button belongs to the page's action. */
.head-more > summary { display:inline-flex; align-items:center; gap:4px; padding:var(--pad-sm);
  font-size:12px; line-height:1.5; font-weight:500; background:var(--surface); color:var(--ink);
  border:1px solid var(--line-strong); border-radius:var(--r-sm); user-select:none; }
.head-more > summary:hover, .head-more[open] > summary { background:var(--surface-2); }
.head-more > summary:focus-visible { outline:2px solid var(--focus-ring); outline-offset:1px; }
/* The invoice page's duplicate question, asked before its form: the warning
   tone of a held invoice, this invoice beside each registered one, the rows
   that differ in weight and the amount she is approving in red. */
.dup-question { border:1px solid var(--warn-line); background:var(--warn-bg); border-radius:var(--r-lg);
  padding:18px 20px; margin:0 0 16px; }
.dup-question h2 { margin:0 0 12px; font-size:15px; font-weight:600; }
.dup-compare { width:100%; border-collapse:separate; border-spacing:0; background:var(--surface);
  border:1px solid var(--line); border-radius:var(--r-md); overflow:hidden; }
.dup-compare th, .dup-compare td { padding:7px 12px; text-align:left; border-bottom:1px solid var(--line); font-weight:400; }
.dup-compare thead th { color:var(--ink-muted); background:var(--surface-2); font-weight:500; }
.dup-compare tbody th { color:var(--ink-muted); width:110px; }
.dup-compare tr:last-child > * { border-bottom:0; }
.dup-compare tr.differs td { font-weight:600; }
.dup-compare tr.differs td:nth-of-type(1) { color:var(--bad); }
.dup-question .dup-open { margin:8px 0 0; }
.dup-choices { display:flex; flex-wrap:wrap; gap:10px; margin-top:14px; }
.dup-question .dup-choice { flex:1 1 180px; display:flex; flex-direction:column; align-items:flex-start; text-align:left;
  padding:10px 16px; font-size:13px; }
.dup-question .dup-choice small { font-weight:400; font-size:11.5px; color:var(--ink-muted); margin-top:2px; }
.dup-question .dup-same { color:var(--bad); }
.dup-chosen { display:flex; align-items:center; gap:10px; margin:12px 0 8px; }
.dup-chosen .ui-button { margin-left:auto; }
/* Right after a decision: what she did, in the success tone, and the next
   invoice waiting with its one dark button at the end of the line. */
.decision-band { display:flex; flex-wrap:nowrap; align-items:center; gap:8px 16px; margin:0 0 16px;
  border:1px solid var(--ok-line); background:var(--ok-bg); border-radius:var(--r-lg); padding:14px 20px; }
.decision-band:focus { outline:none; }
.decision-band .decision-done { color:var(--ok); font-size:15px; white-space:nowrap; }
.decision-band .decision-next { border-left:1px solid var(--ok-line); padding-left:16px; min-width:0; flex:1; }
.decision-band.rejected .decision-next { border-left:0; padding-left:0; }
.decision-band .decision-go { flex:none; }
.decision-band .decision-go { margin-left:auto; }
.decision-note.corrected { border-left-color:var(--accent); background:var(--accent-light); }
.corrected-fields { margin:4px 0; padding-left:18px; }
/* A decided record's decision (rejected: who, when, why), above its facts. */
.decision-note { border-left:3px solid var(--line-strong); background:var(--surface-2);
  border-radius:0 var(--r-md) var(--r-md) 0; padding:10px 14px; margin:0 0 16px; }
/* A top-layer popover (popover="manual"), placed by client.js from the
   summary's rect: a fixed pop-up inside .dt was clipped, because a size
   container is the containing block of its fixed descendants. */
.ra-pop[popover] { margin:0; inset:auto; min-width:168px; padding:4px; flex-direction:column;
  background:var(--surface); color:var(--ink); border:1px solid var(--line); border-radius:var(--r-md); box-shadow:var(--shadow-2); }
.ra-pop[popover]:popover-open { display:flex; }
.ra-pop .ra { background:none; border:0; color:var(--ink); justify-content:flex-start; text-align:left; border-radius:var(--r-sm); cursor:pointer; }
.ra-pop .ra:hover, .ra-pop .ra:focus-visible { background:var(--surface-2); }
.ra-pop .ra-danger:hover, .ra-pop .ra-danger:focus-visible { color:var(--bad); }
/* A RowAction that is a link (Open invoice, Download file) keeps the icon gap:
   `.ui-button-link.btn` sets inline-block and outranks `.ra`. */
.ui-button-link.ra { display:inline-flex; align-items:center; gap:4px; }
/* InlineAdd.Opener carries an icon too. */
.ui-button.add-open { display:inline-flex; align-items:center; gap:4px; }
/* ...without beating the narrow/wide switch, which is as specific and earlier. */
@container dt (max-width:600px) { .dt .ui-button-link.ra-wide { display:none; } }
@container dt (min-width:601px) { .dt .ui-button-link.ra-narrow { display:none; } }
/* In the row menu every item reads as a menu item: a link or an inert item
   loses the ghost border too (a.btn.ghost and span.ra.off outrank .ra-pop .ra). */
.ra-pop a.ra, .ra-pop span.ra.off { border:0; background:none; }
.ra-pop a.ra:hover, .ra-pop a.ra:focus-visible { background:var(--surface-2); }

/* InlineAdd: the panel sits between the filter band and the table. */
.add-panel { background:var(--surface-2); border:1px solid var(--line); border-radius:var(--r-md); padding:12px 14px; margin:0 0 14px; }
.add-panel form { align-items:flex-end; }
.add-panel .err { margin:0 0 10px; }
.add-acts { display:flex; gap:6px; align-items:center; }
.card.adding .add-open { display:none; }
.card.adding .dt-body > .empty { display:none; }
/* The row a write just added, for one response: the only motion here, and it
   answers an action. */
tr.added td { animation:dt-added 1.6s ease-out; }
@keyframes dt-added { from { background:var(--accent-light); } }
@media (prefers-reduced-motion:reduce) { tr.added td { animation:none; } }

/* Detail head: the record's second identity and its key figures. */
.page-head .crumb svg { vertical-align:-2px; margin-right:2px; }
.dhead-sub { margin:4px 0 0; color:var(--ink-muted); font-size:13px; }
.facts { display:flex; flex-wrap:wrap; gap:8px 36px; margin:16px 0 0; }
.facts > div { display:flex; flex-direction:column; gap:2px; }
.facts dt { font-size:11px; color:var(--ink-muted); }
.facts dd { margin:0; font-size:13px; font-weight:500; }
.facts dd.num { font-variant-numeric:tabular-nums; }

/* DefList: the .kv grid, plus the one quiet "Not set" line. */
.deflist-wrap { container-type:inline-size; }
.deflist dd .cell-sub { display:block; font-size:11px; color:var(--ink-muted); margin-top:1px; }
.deflist dd.id { word-break:keep-all; }
.deflist dt.unset, .deflist dd.unset { color:var(--ink-subtle); padding-top:6px; border-top:1px solid var(--line); }
@container (max-width:560px) { .deflist { grid-template-columns:minmax(0,1fr); gap:0; } .deflist dd { margin-bottom:6px; } }

/* StatusPill: a known reason under the pill is capped; the full text is the pill's title. */
.spill .why { max-width:18em; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* Section and SettingRow. section.sect, not .sect: the sidebar's group labels
   are div.sect. */
section.sect { container-type:inline-size; }
section.sect + section.sect { margin-top:24px; padding-top:20px; border-top:1px solid var(--line); }
.sect-head { display:flex; align-items:center; justify-content:space-between; gap:12px; margin:0 0 8px; }
.sect-head h3 { margin:0; font-size:12px; font-weight:600; }
.setting { display:grid; grid-template-columns:180px minmax(0,1fr) auto; gap:4px 16px; align-items:center; padding:10px 0; border-bottom:1px solid var(--line); }
.setting:last-child { border-bottom:0; }
.setting-l { color:var(--ink-muted); }
.setting-a { display:flex; gap:6px; justify-content:flex-end; }
@container (max-width:560px) { .setting { grid-template-columns:minmax(0,1fr) auto; } .setting-l { grid-column:1 / -1; } }

/* ── Segmented, Identity (Access management) ────────────────────────────────
   Scoped under their own classes (.seg-*, .who, .org-row) for
   the same reason as the block above. */

/* Segmented: the language switch's pill, as a radio group. 28px compact like
   the selects beside it in a filter band; 34px in a sheet's form. */
.seg-group { display:inline-flex; align-self:flex-start; background:var(--surface-2); border:1px solid var(--line);
  border-radius:var(--r-sm); padding:2px; gap:2px; }
.seg { position:relative; display:inline-flex; align-items:center; font-size:12px; line-height:1.5; font-weight:500;
  padding:2px 10px; border-radius:var(--r-sm); color:var(--ink-muted); cursor:pointer; white-space:nowrap; }
[data-control-size="normal"] .seg { padding:5px 12px; }
.seg input { position:absolute; opacity:0; width:1px; height:1px; margin:0; pointer-events:none; }
.seg:hover { color:var(--ink); }
.seg:has(input:checked) { background:var(--surface); color:var(--ink); box-shadow:var(--shadow-1); }
.seg:has(input:focus-visible) { box-shadow:0 0 0 3px var(--focus-ring); }
.seg[aria-disabled] { opacity:.5; cursor:not-allowed; }
.seg[aria-disabled]:hover { color:var(--ink-muted); }

/* Identity: icon, name, second line. Names never wrap; the title has them
   whole. .who, not the spec's .ident: DataTable already puts .ident on the
   pinned column's cells, and a flex cell is no longer a table cell. */
.who { display:flex; gap:8px; align-items:flex-start; min-width:0; max-width:22em; }
.who .ico { color:var(--ink-subtle); margin-top:2px; flex:none; display:inline-flex; }
.who-t { min-width:0; display:flex; flex-direction:column; }
.who .t { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.who .cell-sub { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.who .badge { margin-left:4px; vertical-align:1px; }

/* A grouping row inside a DataTable: the org a run of scope rows belongs to. */
.dt tr.org-row td { font-weight:600; padding-top:14px; }
.dt tr.org-row td svg { vertical-align:-2px; margin-right:6px; color:var(--ink-subtle); }
.dt tbody tr.org-row:hover td { background:var(--surface); }
.dt tr.muted td { color:var(--ink-muted); }

/* A sheet field that only one choice shows (Person | Group, a global scope). */
fieldset.choice { border:0; margin:0; padding:0; min-width:0; }
fieldset.choice[hidden] { display:none; }
.head-acts { display:flex; gap:8px; align-items:center; flex-wrap:wrap; justify-content:flex-end; }
.group-head .add-panel { margin:12px 0 0; }
.designation { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
.stack-tight { display:flex; flex-direction:column; gap:6px; }
/* The Lucide user, users, mail and shield-check icons, drawn by mask in the
   text colour of .ico (see identity.tsx for why not inline SVG). */
.who .ico[class*=" i-"] { width:14px; height:14px; background:currentColor; -webkit-mask:var(--i) center / 14px 14px no-repeat; mask:var(--i) center / 14px 14px no-repeat; }
.who .i-person { --i:url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='black'%20stroke-width='2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M19%2021v-2a4%204%200%200%200-4-4H9a4%204%200%200%200-4%204v2'/%3E%3Ccircle%20cx='12'%20cy='7'%20r='4'/%3E%3C/svg%3E"); }
.who .i-group { --i:url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='black'%20stroke-width='2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M16%2021v-2a4%204%200%200%200-4-4H6a4%204%200%200%200-4%204v2'/%3E%3Cpath%20d='M16%203.128a4%204%200%200%201%200%207.744'/%3E%3Cpath%20d='M22%2021v-2a4%204%200%200%200-3-3.87'/%3E%3Ccircle%20cx='9'%20cy='7'%20r='4'/%3E%3C/svg%3E"); }
.who .i-invitation { --i:url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='black'%20stroke-width='2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='m22%207-8.991%205.727a2%202%200%200%201-2.009%200L2%207'/%3E%3Crect%20x='2'%20y='4'%20width='20'%20height='16'%20rx='2'/%3E%3C/svg%3E"); }
.who .i-manager { --i:url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='black'%20stroke-width='2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M20%2013c0%205-3.5%207.5-7.66%208.95a1%201%200%200%201-.67-.01C7.5%2020.5%204%2018%204%2013V6a1%201%200%200%201%201-1c2%200%204.5-1.2%206.24-2.72a1.17%201.17%200%200%201%201.52%200C14.51%203.81%2017%205%2019%205a1%201%200%200%201%201%201z'/%3E%3Cpath%20d='m9%2012%202%202%204-4'/%3E%3C/svg%3E"); }

/* Payments tab of Access management: the approval rule, the people matrix, and their dialogs. */
.pm-rule { display:flex; align-items:flex-start; gap:18px; }
.pm-rule .pm-slots { flex:none; gap:6px; margin:0; min-width:28px; }
.pm-rule .pm-stamp { width:28px; height:28px; border:1.5px dashed var(--ink-subtle); }
.pm-rule-text { display:flex; flex-direction:column; align-items:flex-start; gap:6px; }
.pm-rule-text p { margin:0; }
.pm-rule-n { font-size:15px; font-weight:500; line-height:22px; letter-spacing:-.005em; }
.pm-rule-n .pm-nval { font-weight:650; font-variant-numeric:tabular-nums; }
@media (max-width: 600px) { .pm-rule { flex-direction:column; gap:12px; } }
.pm-prep { display:inline-flex; font-weight:600; font-size:13px; }
.pm-caution { background:var(--warn-bg); color:var(--warn); border-radius:8px; padding:10px 14px; margin:12px 0 0; }
.pm-people .card-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:4px; }
.pm-people .card-head h2 { margin:0; }
.pm-matrix th, .pm-matrix td { vertical-align:middle; }
.pm-matrix .pm-cap { text-align:center; width:15%; }
.pm-name { font-weight:600; }
.pm-none { color:var(--ink-subtle); }
.pm-act { text-align:right; white-space:nowrap; width:1%; }
.pm-empty p { max-width:620px; }
.pm-boxes { display:flex; flex-direction:column; gap:6px; margin:6px 0 12px; }
.pm-capbox { display:flex !important; align-items:flex-start !important; padding:8px 10px; border:1px solid var(--line); border-radius:var(--r-sm); }
.pm-capbox input { margin-top:2px; }
.pm-fixed { font-weight:600; font-size:12px; color:var(--ink); }
.pm-lose { margin:4px 0 10px; padding-left:18px; }
/* The sheet's fields and its preview span the sheet; the controls keep their own width. */
.pm-rule-fields, .pm-preview { align-self:stretch; }
/* The preview: a summary line, then one row per kind of effect, its name and
   count at the left and the batches at the right. */
.pm-preview { display:flex; flex-direction:column; gap:6px; }
.pm-preview p, .pm-preview .err { margin:0; }
.pm-summary { font-weight:600; }
.pm-group { display:flex; align-items:baseline; justify-content:space-between; gap:12px;
            padding:6px 10px; border:1px solid var(--line); border-radius:var(--r-sm); }
.pm-group.warn { background:var(--warn-bg); border-color:transparent; }
.pm-group.warn .pm-glabel { color:var(--warn); }
.pm-glabel { font-weight:500; white-space:nowrap; }
.pm-gcount { font-variant-numeric:tabular-nums; color:var(--ink-muted); margin-left:4px; }
.pm-gids { display:flex; flex-wrap:wrap; justify-content:flex-end; gap:4px 12px; min-width:0; }
.pm-gid { white-space:nowrap; font-variant-numeric:tabular-nums; }
.pm-gact { color:var(--ink-muted); white-space:nowrap; }
.pm-preview:empty { display:none; }

/* The replacement node no longer owns Chrome's original :target reference. */
#new-invoice.invoice-refusal-open { visibility: visible; transition-delay: 0s; }
#new-invoice.invoice-refusal-open .sheet-scrim { opacity: 1; }
#new-invoice.invoice-refusal-open .sheet-panel { transform: none; }

/* The Storage card: plain links into the Kai folder, then the import sources
   as rows of name, type, state and trouble, with the row's menu at the end. */
.storage-links { display:flex; flex-wrap:wrap; gap:4px 16px; margin:4px 0 12px; }
.storage-imports { margin-top:8px; padding-top:12px; border-top:1px solid var(--line); }
.storage-imports .card-head h3 { margin:0; font-size:13px; font-weight:600; }
.storage-sources { list-style:none; margin:0; padding:0; }
.storage-sources li { display:flex; align-items:center; flex-wrap:wrap; gap:4px 12px; padding:8px 0;
  border-top:1px solid var(--line); }
.storage-sources li:first-child { border-top:0; }
.storage-sources .end { margin-left:auto; }
/* Switch (ui/switch.tsx): a native checkbox role=switch drawn by CSS alone. */
.ui-switch-field { display:flex; align-items:center; gap:12px; }
.ui-switch-body { flex:1; min-width:0; display:flex; flex-direction:column; }
.ui-switch-line { display:flex; align-items:center; gap:6px; min-width:0; }
.ui-switch-text { cursor:pointer; display:inline-flex; align-items:center; min-height:24px; }
.ui-switch-hint { display:block; min-width:0; }
.ui-switch-aside { flex:none; display:flex; align-items:center; gap:6px; }
.ui-switch { appearance:none; -webkit-appearance:none; flex:none; margin:0 -3px; box-sizing:content-box; width:32px; height:18px; padding:3px; border-radius:12px;
  background-color:var(--line-strong); background-image:radial-gradient(circle, var(--surface) 0 6px, transparent 6.5px);
  background-repeat:no-repeat; background-size:18px 18px; background-position:0 0; background-origin:content-box; background-clip:content-box;
  transition:background-position .15s, background-color .15s; cursor:pointer; }
.ui-switch:checked { background-color:var(--primary); background-position:14px 0; }
.ui-switch:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
.ui-switch:disabled { opacity:.45; cursor:not-allowed; }
.ui-switch-field:has(.ui-switch:disabled) .ui-switch-text { cursor:not-allowed; color:var(--ink-muted); }
@media (prefers-reduced-motion: reduce) { .ui-switch { transition:none; } }
/* Forced colors drops the track's thumb gradient and any var() colour, so draw both from system colors. */
@media (forced-colors: active) {
  .ui-switch { forced-color-adjust:none; border:1px solid CanvasText; padding:2px; background-clip:border-box;
    background-color:Canvas; background-image:radial-gradient(circle, CanvasText 0 6px, transparent 6.5px); }
  .ui-switch:checked { background-color:Highlight; background-image:radial-gradient(circle, HighlightText 0 6px, transparent 6.5px); }
  .ui-switch:disabled { opacity:1; border-color:GrayText; background-image:radial-gradient(circle, GrayText 0 6px, transparent 6.5px); }
}
/* File types sheets (views/file-types-sheet.tsx) */
.ft-pills { display:flex; flex-wrap:wrap; gap:6px; }
.ft-pill { display:inline-flex; align-items:center; gap:4px; }
.ft-section { align-self:stretch; }
.ft-section + .ft-section { margin-top:16px; }
.ft-section-head { display:flex; align-items:center; justify-content:space-between; }
.ft-switches { list-style:none; margin:0; padding:0; align-self:stretch; }
.ft-switches li { padding:7px 0; border-bottom:1px solid var(--line); }
.ft-mime { display:block; max-width:100%; font-size:11px; color:var(--ink-muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.ft-mime:hover, .ft-mime:focus { white-space:normal; overflow-wrap:anywhere; }
.ft-mime:focus-visible { outline:2px solid var(--accent); outline-offset:1px; }
.ft-icon-acts .ra { width:28px; height:28px; padding:0; justify-content:center; }
.ft-inherit-switch { align-self:stretch; padding:4px 0 8px; border-bottom:1px solid var(--line-strong); }
.ft-inherited, .ft-own { align-self:stretch; }
.ft-sheet:has(.ft-inherit-switch .ui-switch:checked) .ft-own { display:none; }
.ft-sheet:not(:has(.ft-inherit-switch .ui-switch:checked)) .ft-inherited { display:none; }
.storage-trouble { display:inline-flex; align-items:center; gap:6px; }

/* A table card's footer: its action or links, spaced off the last row. */
.card-foot { margin-top: 14px; }
/* One line of label, value and a link pushed to the far end. */
.card-foot-row { display: flex; align-items: center; gap: 8px; }
.card-foot-row .end { margin-left: auto; }
/* A footer with more than one line, at the card's full width so a row's
   pushed-end link reaches the edge (.stack caps itself at a form's width). */
.card-foot-stack { display: flex; flex-direction: column; gap: 12px; }

/* ── A record's original beside its data ─────────────────────────────────
   The invoice and contract pages put the record on the left and its file on
   the right, with one hairline between them: the common pattern in tools like
   these, and Kai's standard (UI guide). main becomes the container, so the
   split folds by the sheet's own width, not the viewport's: an open sidebar
   takes width and should fold the page sooner. */
main.with-doc { container:sheet / inline-size; padding:0; }
.doc-split { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); align-items:start; }
.doc-data { min-width:0; padding:24px 28px 32px; }
/* The pane stays in view while the data scrolls. */
.doc-pane { position:sticky; top:0; height:100dvh; min-width:0; display:flex; flex-direction:column;
            border-left:1px solid var(--line); background:var(--surface-3);
            border-radius:0 calc(var(--r-lg) - 1px) calc(var(--r-lg) - 1px) 0; overflow:hidden; }
/* The pane's bar names the file, so the record's own "Original" row only
   earns its place once the pane has folded below the data, out of view. */
.doc-data .kv > .folded-only { display:none; }
@container sheet (max-width: 960px) {
  .doc-split { grid-template-columns:minmax(0,1fr); }
  .doc-pane { position:static; height:80dvh; border-left:0; border-top:1px solid var(--line);
              border-radius:0 0 calc(var(--r-lg) - 1px) calc(var(--r-lg) - 1px); }
  .doc-data .kv > .folded-only { display:block; }
}
/* Half a sheet is narrow for .kv's label track: labels take only what they
   need, up to 160px, so a long bank line wraps once rather than three times. */
.doc-data .kv { grid-template-columns:fit-content(160px) minmax(0,1fr); gap:8px 20px; }

/* The preview itself (ui/document-preview.tsx), wherever it sits. */
.doc-preview { display:flex; flex-direction:column; flex:1; min-width:0; min-height:0; container:pane / inline-size; }
.doc-bar { display:flex; align-items:center; gap:8px; flex:none; min-height:40px; padding:0 8px 0 14px;
           border-bottom:1px solid var(--line); background:var(--surface-2); }
.doc-bar .doc-name { flex:0 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-weight:500; }
.doc-bar .doc-kind { flex:1 0 auto; color:var(--ink-subtle); }
.doc-bar .doc-acts { display:flex; gap:2px; flex:none; }
.doc-bar .doc-acts a { display:inline-flex; align-items:center; gap:5px; padding:var(--pad-sm); border-radius:var(--r-sm);
                       color:var(--ink-muted); text-decoration:none; }
.doc-bar .doc-acts a:hover { background:var(--tint); color:var(--ink); }
.doc-bar .doc-acts a:focus-visible { outline:2px solid var(--accent); outline-offset:-2px; color:var(--ink); }
/* A narrow pane keeps the bar one line: the actions drop their words and keep
   their names through aria-label. */
@container pane (max-width: 440px) { .doc-bar .doc-act-label { display:none; } .doc-bar .doc-acts a { padding:6px; } }
.doc-stage { flex:1; min-height:0; display:flex; }
.doc-stage > iframe, .doc-stage > img { display:block; width:100%; height:100%; min-height:0; border:0; object-fit:contain; }
.doc-stage > iframe.doc-text { background:var(--surface); }
/* Nothing to show: said once, with the download when there is a file. */
.doc-none { margin:auto; max-width:22rem; padding:2rem; text-align:center; color:var(--ink-muted); line-height:1.6; }
.doc-none p { margin:0 0 12px; }
.doc-none p:last-child { margin:0; }
.doc-none .doc-mark { width:36px; height:44px; margin:0 auto 14px; border:1.5px solid var(--line-strong); border-radius:4px;
                      background:linear-gradient(225deg,var(--surface-3) 9px,var(--line-strong) 9px 10.5px,var(--surface) 10.5px); }

/* DatePicker (ui/date-picker.tsx): the text date field (2026/12/25) with Kai's own
   calendar button, whose calendar can mark the days banks are closed, which the
   browser's picker cannot. The message under it says why a typed date was refused. A holiday is struck through with a dot, red for a national
   holiday or the year-end closure and amber, as its "Added" badge, for the
   org's own; every day that cannot be picked is greyed. */
.dp { position:relative; display:inline-flex; flex-wrap:wrap; align-items:center; align-self:flex-start; width:max-content; max-width:100%; }
.dp .ui-input { width:13em; padding-inline-end:32px; }
.dp-msg { flex:0 0 100%; width:0; min-width:100%; margin-top:2px; font-weight:400; }
.dp-msg[hidden] { display:none; }
.dp-open { position:absolute; top:4px; inset-inline-end:3px; display:grid; place-items:center; width:26px; height:22px; padding:0;
  border:0; border-radius:4px; background:none; color:var(--ink-muted); cursor:pointer; }
.dp-open:hover { background:var(--surface-3); color:var(--ink); }
.dp-open:focus-visible { outline:2px solid var(--accent); outline-offset:0; }
.dp-pop[popover] { margin:0; inset:auto; position:fixed; width:252px; padding:10px; color:var(--ink); background:var(--surface);
  border:1px solid var(--line); border-radius:var(--r-md); box-shadow:var(--shadow-2); }
.dp-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:6px; }
.dp-month { font-size:13px; font-weight:600; }
.dp-nav { width:26px; height:24px; border:1px solid var(--line); border-radius:var(--r-sm); background:var(--surface); color:var(--ink); cursor:pointer; }
.dp-nav:disabled { color:var(--ink-subtle); cursor:default; }
.dp-grid { display:grid; grid-template-columns:repeat(7,1fr); gap:2px; }
.dp-dow { text-align:center; font-size:11px; color:var(--ink-subtle); padding:2px 0 4px; }
.dp-day { position:relative; height:30px; padding:0; border:0; border-radius:var(--r-sm); background:none; font:inherit; font-size:13px;
  font-variant-numeric:tabular-nums; color:var(--ink); cursor:pointer; }
.dp-day:hover:not([aria-disabled=true]) { background:var(--surface-3); }
.dp-day:focus-visible { outline:2px solid var(--accent); outline-offset:-2px; }
.dp-day[aria-disabled=true] { color:var(--ink-subtle); cursor:not-allowed; }
.dp-day.dp-today { box-shadow:inset 0 0 0 1px var(--line-strong); }
.dp-day[aria-selected=true] { background:var(--primary); color:var(--ink-invert); }
.dp-hol { text-decoration:line-through; text-decoration-color:var(--ink-subtle); }
.dp-hol::after { content:""; position:absolute; left:50%; bottom:3px; width:4px; height:4px; margin-left:-2px; border-radius:50%; background:var(--bad); }
.dp-added::after { background:var(--warn); }
.dp-status { margin-top:8px; padding-top:8px; border-top:1px solid var(--line); font-size:12px; color:var(--ink-muted); min-height:18px; }

/* The invoice page's edit mode (views/invoice-fix.tsx): the left column's
   cards become a form, the original stays in the pane beside it. Inside a
   card each group takes the card's width, as in a sheet, so the pickers,
   inputs and the payee select end at one edge. */
.invoice-edit .card .stack { max-width:none; }
.invoice-edit .card .stack .stack { align-self:stretch; }
.invoice-edit .card .stack > div:not([hidden]) { align-self:stretch; }
/* Two abreast per rate, and the two rates one gap apart like every other field. */
.invoice-edit [data-invoice-split] { display:flex; flex-direction:column; gap:12px; }
.invoice-edit [data-invoice-split] .inline > label { flex:1 1 0; }
.invoice-edit-blocker { margin:0 0 12px; }
/* Save and Cancel stay at the bottom of the column while it scrolls, so a
   long form never hides the way out; Cancel then the primary, at the right. */
.invoice-edit .edit-bar { position:sticky; bottom:0; z-index:2; display:flex; justify-content:flex-end; gap:8px;
                          margin:4px -28px 0; padding:12px 28px; border-top:1px solid var(--line); background:var(--surface-2); }
.invoice-edit .edit-bar a.btn { display:inline-flex; align-items:center; }
.edit-discard .inline { margin-top:18px; justify-content:flex-end; }

/* An amount in yen: the ¥ sits inside the box's left edge (ui/yen-input.tsx). */
.yen-field { position:relative; display:block; }
.yen-field .yen-mark { position:absolute; inset-block:0; inset-inline-start:10px; display:flex; align-items:center;
                       color:var(--ink-muted); font-size:12px; pointer-events:none; }
.yen-field .yen-input { width:100%; box-sizing:border-box; padding-inline-start:26px; font-variant-numeric:tabular-nums; }
.amount-mismatch .kv { margin:14px 0 0; }
.amount-mismatch [data-mismatch-typed] { font-weight:600; }
.amount-mismatch .inline { margin-top:18px; justify-content:flex-end; }
