/*
 * Built for one pair of hands in a truck, in daylight, possibly with reading
 * glasses in the other pocket.
 *
 * Rules this stylesheet is keeping:
 *  - nothing tappable under 52px tall. Apple's 44 is a minimum for a young
 *    thumb on a still phone, not a target.
 *  - 17px base and up. Never a 12px label carrying information he needs.
 *  - one primary action per screen, and it is the biggest thing on it.
 *  - safe-area insets honoured, because a home-screen PWA has no browser chrome
 *    to keep the buttons off the home indicator.
 */

/*
 * KEY'S THEME, TAKEN FROM THE SCORECARD.
 *
 * Dave, 2026-09-01: "go look at scorecard to get the correct theme. white and
 * yellow is not it."
 *
 * The first attempt read lib/brand/theme.css in the keysupport repo and came
 * out amber on near-white. That is a real Key file, but it is not what Key
 * actually looks like on screen: KeyForemanScorecard.html and Key Quick Look —
 * the two tools Dave's people use every week — share a palette whose own header
 * calls it "Key theme - dark page, light-grey cards". Charcoal page, mid-grey
 * cards, DEEP MAROON accent. Copied here verbatim so the three tools are one
 * family, which was the whole point of theming this at all.
 *
 *   --page  #232323   --card  #585858   --card2 #666666
 *   --ink   #ffffff   --mut   #e8e6e3   --mut2  #cfccc8
 *   --line  #6e6e6e   --acc   #6b1a24   --acc-ink #f0aab4
 *
 * ONE COMMITTED LOOK, no light mode. The Scorecard has no light variant and a
 * receipts app that went white in daylight while the Scorecard stayed dark
 * would look like a different company's software. Every colour below is painted
 * explicitly for that reason - nothing inherits from the host.
 *
 * THE MAROON IS A FILL AND NEVER A WORD. #6b1a24 on the charcoal page measures
 * 1.35:1, which is invisible. As a fill it carries WHITE at 11.68:1 - far
 * better than the amber it replaces, which managed 3.24:1 and was the thing
 * that made the old primary button hard work in the sun.
 *
 * AND THE MID-GREY CARD IS WHY THE STATUS COLOURS ARE PALER THAN THE
 * SCORECARD'S. #585858 is light enough that a saturated colour on top of it
 * fails: the Scorecard's own red measures 2.58:1 there, its amber 4.44:1. It
 * gets away with it by using those colours on the dark PAGE and using tinted
 * backgrounds on cards instead. This app puts small text on cards constantly -
 * "pick a phase", "Not read", every error - so each status colour has a lighter
 * form measured against #585858, and the Scorecard's originals are kept for
 * fills and borders where they belong.
 */
:root {
  --bg: #232323;
  --card: #585858;
  --card2: #666666;
  --ink: #ffffff;
  --muted: #e8e6e3;
  --faint: #cfccc8;
  --line: #6e6e6e;
  --line2: #828282;

  --accent: #6b1a24;
  --accent-deep: #4f1219;
  --accent-ink: #ffffff;
  /* The accent as a WORD. Maroon cannot be one, so this is the Scorecard's own
     --acc-ink rose, lightened until it clears AA on a card: 4.81:1 there and
     10.62:1 on the page. */
  --accent-text: #f6c9d0;

  /* Status. The first value of each pair is the Scorecard's, for fills and
     borders; the -ink one is lightened to be readable on a #585858 card. */
  --good: #46d17f;
  --good-ink: #8fe3b4;
  --warn: #f2c83f;
  --warn-ink: #f7d97e;
  --warn-bg: rgba(242, 200, 63, .22);
  --warn-line: rgba(242, 200, 63, .55);
  --bad: #ff6d5e;
  --bad-ink: #ffc9c2;
  --bad-bg: rgba(239, 90, 74, .22);

  --key: #f6c9d0;
  --raw: #c69b52;
  --personal: #cfccc8;
  --radius: 14px;
  --shadow: 0 2px 10px rgba(0, 0, 0, .35);
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/* The `hidden` attribute is how every view in this app is shown and hidden, and
   the UA stylesheet implements it as a plain `display: none` — which ANY of the
   `display: flex` / `display: grid` rules below silently outranks. That is not
   a cosmetic slip: it is how a signed-out visitor ends up looking at the tab
   bar, or a finance user at the capture button. Pinned here so no later layout
   rule can win. */
[hidden] { display: none !important; }

html, body {
  margin: 0; padding: 0; background: var(--bg); color: var(--ink);
  font: 17px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  -webkit-text-size-adjust: 100%;
}

body {
  padding: env(safe-area-inset-top) env(safe-area-inset-right)
           calc(env(safe-area-inset-bottom) + 12px) env(safe-area-inset-left);
  max-width: 640px; margin: 0 auto;
}

header {
  display: flex; align-items: baseline; gap: 10px;
  padding: 14px 18px 8px;
}
header h1 { font-size: 20px; margin: 0; font-weight: 640; letter-spacing: -.01em; }

/*
 * THE BADGE. It is a black mark on transparency, which is right on Key's paper
 * and invisible on the dark theme, so the dark theme inverts it. That works
 * only because the artwork is genuinely monochrome - a colour logo would come
 * out as a photographic negative, so this stays true only as long as the file
 * does. Width and height are on the <img> so the header does not jump while it
 * loads, which on a phone means the tabs do not move under a thumb already on
 * its way down.
 */
header .mark { display: block; height: 29px; width: auto; align-self: center; }
.mark-lg { display: block; height: 96px; width: auto; margin: 2px auto 18px; }
/* Always inverted now, not only in dark mode: the page is charcoal in every
   condition, and a black badge on it is a black square. The artwork is
   monochrome, which is the only reason a filter is honest here. */
header .mark, .mark-lg { filter: invert(1); }
header .who { color: var(--muted); font-size: 14px; margin-left: auto; }

nav.tabs { display: flex; gap: 6px; padding: 4px 14px 10px; }
nav.tabs button {
  flex: 1; min-height: 44px; border: 1px solid var(--line); background: transparent;
  color: var(--muted); border-radius: 10px; font-size: 15px; font-weight: 560;
  font-family: inherit; cursor: pointer;
}
nav.tabs button[aria-selected="true"] {
  background: var(--card); color: var(--ink); box-shadow: var(--shadow); border-color: transparent;
}

main { padding: 0 14px; }

.card {
  background: var(--card); border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 16px; margin: 0 0 12px;
}
.card.pad0 { padding: 0; overflow: hidden; }

/* ------------------------------------------------------------- the button */
.capture {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; width: 100%; min-height: 148px; border: 0; border-radius: var(--radius);
  background: var(--accent); color: var(--accent-ink); font-family: inherit;
  font-size: 21px; font-weight: 620; cursor: pointer; box-shadow: var(--shadow);
  margin: 4px 0 16px;
}
.capture:active { filter: brightness(.92); }

/* The second way in: a receipt he already has, rather than one he is taking
   now. Deliberately quieter than the camera button and directly beneath it -
   taking a photo is the common case and should stay the obvious one, but this
   has to be findable without hunting, because a receipt that arrived by email
   is otherwise impossible to add at all. */
.pickfile {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; min-height: 52px; margin: -8px 0 18px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--card); color: var(--ink);
  font-family: inherit; font-size: 16px; font-weight: 520; cursor: pointer;
}
.pickfile:active { background: var(--surface); }
.pickfile .sub2 { font-size: 13px; font-weight: 430; color: var(--muted); }
.pickfile[disabled] { opacity: .55; }
/* Drawn marks, sized like the emoji they replaced. They take currentColor, so
   they are the button's own ink in either theme with nothing to keep in sync. */
.capture .glyph { width: 42px; height: 42px; display: block; margin: 0 auto; }
.pickfile .glyph-sm { width: 26px; height: 26px; flex: 0 0 auto; }
.capture .sub { font-size: 14px; font-weight: 450; opacity: .85; }

/* ------------------------------------------------------------- money line */
.owed { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.owed .fig { font-size: 30px; font-weight: 660; letter-spacing: -.02em; }
.owed .lbl { color: var(--muted); font-size: 15px; }

.pill {
  display: inline-block; padding: 2px 9px; border-radius: 999px;
  font-size: 12.5px; font-weight: 620; letter-spacing: .01em; white-space: nowrap;
  border: 1px solid currentColor;
}
.pill.key { color: var(--key); }
.pill.raw { color: var(--raw); }
.pill.personal { color: var(--personal); }

/* ---------------------------------------------------------------- buttons */
button.btn, a.btn {
  min-height: 52px; padding: 0 18px; border-radius: 11px; border: 1px solid var(--line);
  background: var(--card); color: var(--ink); font: inherit; font-weight: 560;
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  gap: 8px; text-decoration: none;
}
button.btn.primary { background: var(--accent); color: var(--accent-ink); border-color: transparent; }
button.btn.wide { width: 100%; }
button.btn.quiet { background: transparent; color: var(--muted); border-color: transparent; min-height: 44px; }
button.btn:disabled { opacity: .45; cursor: default; }
/* --bad-ink, not --bad: the saturated red measures 2.58:1 on a #585858 card. */
button.btn.danger { color: var(--bad-ink); }

/* --------------------------------------------------------------- the list */
ul.rows { list-style: none; margin: 0; padding: 0; }
ul.rows li {
  display: flex; align-items: center; gap: 12px; padding: 13px 16px;
  border-bottom: 1px solid var(--line); cursor: pointer;
}
ul.rows li:last-child { border-bottom: 0; }
ul.rows li .thumb {
  width: 44px; height: 56px; border-radius: 6px; object-fit: cover;
  background: var(--bg); border: 1px solid var(--line); flex: none;
}
ul.rows li .body { min-width: 0; flex: 1; }
ul.rows li .name { font-weight: 580; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
ul.rows li .meta { color: var(--muted); font-size: 14px; }
ul.rows li .amt { font-weight: 620; white-space: nowrap; font-variant-numeric: tabular-nums; }
ul.rows li.needs { background: var(--warn-bg); }
/* Still being read. Deliberately NOT the amber "needs something" colour: this
   is the tool working, not the user owing it an answer, and colouring the two
   the same is what makes a two-second wait feel like a problem. */
/* NOT dimmed any more. A row at 72% opacity reads as a faded row, not as one
   that is being worked on — it carries its own bar now instead. */
ul.rows li.reading { background: var(--surface, transparent); }
.spin.sm { width: 16px; height: 16px; border-width: 2px; display: inline-block; }

.needs-tag { color: var(--warn-ink); font-size: 13.5px; font-weight: 600; }

/* --------------------------------------------------------------- the form */
label.field { display: block; margin: 0 0 14px; }
label.field > span { display: block; font-size: 14.5px; color: var(--muted); margin: 0 0 5px; font-weight: 560; }
input[type="text"], input[type="date"], input[type="email"], textarea, select {
  width: 100%; min-height: 52px; padding: 12px 14px; border-radius: 11px;
  border: 1px solid var(--line); background: var(--bg); color: var(--ink);
  font: inherit; font-size: 17px; /* under 16px iOS zooms the whole page on focus */
}
textarea { min-height: 84px; resize: vertical; line-height: 1.4; }
input:focus, textarea:focus, select:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

.amount-row { display: flex; align-items: center; gap: 6px; }
.amount-row .cur { font-size: 30px; font-weight: 620; color: var(--muted); }
.amount-row input {
  font-size: 32px; font-weight: 640; min-height: 62px; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}

.two { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

/* --------------------------------------------------------- who pays / codes */
.whopays { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; }
.whopays button {
  min-height: 82px; border-radius: 12px; border: 2px solid var(--line);
  background: var(--card); font: inherit; cursor: pointer; padding: 8px 4px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  color: var(--ink);
}
.whopays button .t { font-weight: 640; font-size: 15.5px; }
.whopays button .s { font-size: 12.5px; color: var(--muted); line-height: 1.25; }
.whopays button[aria-pressed="true"] { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--card)); }

.chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 4px; }
.chips button {
  min-height: 46px; padding: 0 14px; border-radius: 23px; border: 1px solid var(--line);
  background: var(--card); color: var(--ink); font: inherit; font-size: 15.5px; cursor: pointer;
}
.chips button[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); border-color: transparent; font-weight: 580; }
.chips button.more { color: var(--muted); }

/* ONE-TAP DESCRIPTIONS, offered under the code he just chose — "CREW LUNCH",
   "AIRFARE", "FENCE REPAIR". Smaller and quieter than the code chips above
   them on purpose: the code is the question, these are a shortcut past typing
   the answer. They must not compete with it for the eye, and they must still
   clear the 44px Apple touch target — 38px plus the 8px gap does. */
.chips button.sm {
  min-height: 38px; padding: 0 12px; border-radius: 19px; font-size: 14px;
  color: var(--muted); background: transparent;
}
.chips button.sm[aria-pressed="true"] {
  background: color-mix(in srgb, var(--accent) 14%, var(--card));
  color: var(--ink); border-color: var(--accent); font-weight: 560;
}

/* ---------------------------------------------------------------- notices */
.note {
  border-radius: 11px; padding: 11px 14px; margin: 0 0 14px; font-size: 15px;
  background: var(--warn-bg); border: 1px solid var(--warn-line); color: var(--warn-ink);
}
.note.bad { background: var(--bad-bg); border-color: var(--bad); color: var(--bad-ink); }
.note.plain { background: var(--bg); border-color: var(--line); color: var(--muted); }
.note strong { font-weight: 660; }

.muted { color: var(--muted); }
.small { font-size: 14px; }
.center { text-align: center; }
.spread { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
/* A row of downloads that WRAPS. At 375px the three of them are wider than the
   card, so the last one used to sit off the right edge with no way to reach it
   and no sign it was there. */
.btnrow { display: flex; gap: 8px; flex-wrap: wrap; }
.stack { display: flex; flex-direction: column; gap: 10px; }

/* ---------------------------------------------------------------- overlay */
/* THE PHONE'S ADDRESS BAR IS NOT PART OF THE VIEWPORT, AND `vh` THINKS IT IS.
 *
 * Dave, 2026-08-28: "the web bar at the top of the phone is blocking the
 * loading bar and text at the top."
 *
 * The card is anchored to the BOTTOM and sized at 94vh. On iOS `vh` measures
 * the viewport as if the browser chrome were not there, so on a tall card the
 * missing height comes off the TOP — and the top is where the reading bar, the
 * heading and the close X live. Everything important, under the URL bar.
 *
 * `dvh` is the dynamic viewport: what is actually visible right now, shrinking
 * and growing as Safari's bar does. The `vh` line stays first as the fallback
 * for anything that does not know `dvh`, which is worse but not broken.
 *
 * The safe-area inset is separate and also needed: on a notched phone the top
 * of a fixed element sits under the status bar even with the address bar
 * hidden. */
.sheet {
  position: fixed; inset: 0; background: rgba(0, 0, 0, .45); z-index: 40;
  display: flex; align-items: flex-end; justify-content: center;
  padding-top: env(safe-area-inset-top);
  box-sizing: border-box;
}
.sheet > .inner {
  background: var(--bg); width: 100%; max-width: 640px; overflow-y: auto;
  max-height: 94vh;
  max-height: calc(100dvh - env(safe-area-inset-top) - 8px);
  border-radius: 18px 18px 0 0;
  padding: 8px 14px calc(env(safe-area-inset-bottom) + 20px);
  -webkit-overflow-scrolling: touch;
}
.grabber { width: 40px; height: 4px; border-radius: 2px; background: var(--line); margin: 8px auto 12px; }

/* THE PHOTO IS EVIDENCE, NOT THE POINT OF THE SCREEN.
   Uncapped, a portrait receipt fills 500+px of a 812px phone and pushes the
   amount — the field most likely to need correcting — below the fold. He has
   already seen the receipt; he is holding it. Capped to a third of the
   viewport, tap to enlarge. */
.shot {
  width: 100%; max-height: 33vh; object-fit: contain; object-position: top;
  border-radius: 10px; border: 1px solid var(--line); display: block;
  background: var(--bg); cursor: zoom-in;
}
.shot.big { max-height: none; cursor: zoom-out; }
.shot-hint { text-align: center; font-size: 13px; color: var(--muted); margin: 4px 0 0; }

.spin {
  width: 26px; height: 26px; border: 3px solid var(--line); border-top-color: var(--accent);
  border-radius: 50%; animation: spin .8s linear infinite; margin: 12px auto;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spin { animation-duration: 2.4s; } }

.queued-badge {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: calc(env(safe-area-inset-bottom) + 14px);
  background: var(--ink); color: var(--bg); padding: 9px 16px; border-radius: 999px;
  font-size: 14.5px; font-weight: 560; z-index: 30; box-shadow: var(--shadow);
}

table.mini { width: 100%; border-collapse: collapse; font-size: 15px; }
table.mini td { padding: 5px 0; vertical-align: top; }
table.mini td:last-child { text-align: right; font-variant-numeric: tabular-nums; }
table.mini td.k { color: var(--muted); padding-right: 14px; white-space: nowrap; }

/* The one-line "Books will say EXP FUEL · Change". Deliberately quiet: it is a
   confirmation, not a question — the code chips above already answered it. */
.ledgerline {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  margin: -6px 0 16px; font-size: 15px;
}
.ledgerline button.linkish {
  min-height: 44px; padding: 0 10px; border: 0; background: none; font: inherit;
  font-size: 15px; color: var(--accent-text); text-decoration: underline; cursor: pointer;
}

/* A rotated photo. The wrapper exists because a quarter turn swaps the image's
   axes while its layout box keeps the ORIGINAL shape — without this the page
   collapses around a tall image drawn wide, or leaves a huge gap. */
.shotwrap { display: flex; justify-content: center; overflow: hidden; }
.shotwrap.turn90, .shotwrap.turn270 { height: 33vh; }
.shotwrap.turn90 .shot, .shotwrap.turn270 .shot { max-height: none; max-width: 33vh; width: auto; }
.shotwrap.turn180 .shot { max-height: 33vh; }
ul.rows li .thumb { transition: none; }

/* ------------------------------------------------- "the AI is reading this"
   Dave, 2026-08-28: "when you upload a picture it eventually catches up and
   fills the vendor/amount etc but there is a time where you think that you
   should enter of all that yourself. maybe it should show a loading bar or
   something to indicate that AI is extrapolating all the info from the
   receipt."

   There WAS a spinner and a line of text, and it was not enough — it read as
   decoration next to a form full of empty boxes, and an empty box asks to be
   typed in. A bar spanning the full width of the card, directly above those
   boxes, is doing the one job that matters: saying "these are about to fill
   themselves in, wait a moment".

   It is INDETERMINATE on purpose. Nothing here knows how long the model will
   take, and a bar that pretends to know is worse than one that admits it does
   not — it would sit at 90% and be a lie. */
.reading-bar {
  /* 6px, not 4. Four was right on a desktop mock and too polite on a phone in
     daylight, which is where this is actually read. */
  height: 6px; border-radius: 999px; background: var(--line);
  overflow: hidden; margin: 0 0 10px;
}
.reading-bar > i {
  display: block; height: 100%; width: 40%; border-radius: 999px;
  background: var(--accent); animation: sweep 1.15s ease-in-out infinite;
}
@keyframes sweep {
  0%   { transform: translateX(-105%); }
  100% { transform: translateX(255%); }
}
/* Reduced motion still needs to say "working" — a pulse rather than a run. */
@media (prefers-reduced-motion: reduce) {
  .reading-bar > i { width: 100%; animation: pulse 1.8s ease-in-out infinite; }
  @keyframes pulse { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }
}

/* The fields it is about to fill. Not disabled — anything typed is kept and
   wins over what the model returns, so somebody in a hurry can get straight on
   with it — just visibly not waiting on him.
   Scoped to the form fields only: the bar and the notice above them must stay
   at full strength, or the thing explaining the wait fades along with it. */
.inner.reading label.field,
.inner.reading .chips,
.inner.reading .whopays { opacity: .5; transition: opacity .25s; }

/* ------------------------------------------------------------ on a desktop
   Dave, 2026-08-28: "this should also work on a pc. thats where jordan will
   work."

   It always worked — it is the same app and the same API — but it LOOKED like
   somebody's phone screen stretched onto a monitor: a 640px column, and a
   sheet welded to the bottom edge of a 1200px window with a drag handle on it
   that no mouse will ever use.

   Nothing here changes behaviour. It is the same markup reading as a desktop
   app rather than a phone app that got away. The breakpoint is 760px, above a
   landscape phone and below any real window.

   A/P is the reason this matters: Jordan spends her day in this on a monitor,
   reviewing lines and downloading the import file, and a screen that looks
   like it was meant for somebody else's pocket does not invite care. */
@media (min-width: 760px) {
  body { max-width: 880px; padding-top: 8px; }

  /* A dialog, not a bottom sheet. Centred, floating, with room around it. */
  .sheet { align-items: center; padding: 24px; }
  .sheet > .inner {
    max-width: 760px;
    max-height: 88vh;
    max-height: 88dvh;
    border-radius: 14px;
    padding: 10px 22px 22px;
    box-shadow: 0 24px 60px rgba(0, 0, 0, .28);
  }
  /* The grabber says "drag me up", which is a lie with a mouse. */
  .sheet > .inner > .grabber { display: none; }

  /* Rows have room to breathe, and a pointer gets something to aim at. */
  ul.rows li { padding: 14px 12px; }
  ul.rows li:hover { background: var(--surface, rgba(0, 0, 0, .02)); }

  /* Full-width buttons are a thumb affordance. A mouse does not need one. */
  .btn.wide { width: auto; min-width: 240px; }
  .stack > .btn.wide { align-self: flex-start; }

  /* The two capture buttons are a phone's whole purpose and a desk's
     afterthought — keep them, stop them dominating. */
  .capture-row { max-width: 560px; }
}

/* A real pointer, at any width: make the things that are tappable also look
   clickable. Touch devices are excluded so nothing gets a stuck hover state. */
@media (hover: hover) and (pointer: fine) {
  ul.rows li, .chips button, .btn, select, .linkish { cursor: pointer; }
  .chips button:hover { border-color: var(--accent); }
}

/* ------------------------------------------------------ closing a pop-out
   Dave, 2026-08-28: "at the top of jordans pop out windows add an X where she
   can close it instead of scrolling."

   The only way out was a Close button under everything else — fine on a card
   with one receipt, and a scroll to the bottom of twelve when she has already
   found what she came for. The X is sticky, so it is where she expects it
   whatever she has scrolled past.

   44px, because a target you have to aim at is not an escape. */
.sheet-head {
  position: sticky; top: 0; z-index: 3;
  display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
  background: var(--bg);
  padding: 4px 0 10px;
  margin: 0 0 2px;
}
.sheet-head > div { min-width: 0; }
.sheet-x {
  flex: 0 0 auto; width: 44px; height: 44px; margin: -6px -6px 0 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; background: transparent; border-radius: 999px;
  font-size: 24px; line-height: 1; color: var(--muted);
}
.sheet-x:hover { background: var(--line); color: var(--ink); }
.sheet-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The three-way ask when there is unsaved work. Dave: "if she does click the x
   if applicable ask if it shoul;d save her work." A browser confirm() offers
   two answers and the question has three — save, discard, carry on — and
   getting the wrong one throws away work she has already done. */
.leaving {
  position: sticky; bottom: 0; z-index: 3;
  background: var(--bg); border-top: 1px solid var(--line);
  padding: 12px 0 4px; margin-top: 12px;
}

/* --------------------------------------------------- A/P's review, as a grid
   Dave, 2026-08-28: "make the admin portal review more of a table style for
   each item rather than how it currenly looks so can look across items in rows
   with info in columns. also make columns sortable."

   The card layout was the phone's shape wearing A/P's hat. One receipt per
   card is how somebody CODES a receipt; a column per field is how somebody
   CHECKS twenty — because the job is spotting the one that does not look like
   its neighbours, and that is a comparison down a column.

   Every cell stays editable. The inputs lose their phone-sized padding and
   their borders until you touch them, so twenty rows read as a table rather
   than as sixty boxes. */
table.apgrid { border-collapse: collapse; width: 100%; font-size: 14px; }
table.apgrid th {
  position: sticky; top: 0; z-index: 2;
  background: var(--bg); text-align: left; white-space: nowrap;
  padding: 8px 8px; border-bottom: 1px solid var(--line);
  font-size: 12.5px; font-weight: 600; color: var(--muted);
  cursor: pointer; user-select: none;
}
table.apgrid th:hover { color: var(--ink); }
table.apgrid th.sorted { color: var(--accent-text); }
table.apgrid td { padding: 3px 6px; border-bottom: 1px solid var(--line); vertical-align: top; }
table.apgrid tr.edited td { background: var(--warn-bg, rgba(255, 193, 7, .08)); }
table.apgrid tr.edited td:first-child { box-shadow: inset 3px 0 0 var(--accent); }

/* Inputs in a grid are cells first and controls second. */
table.apgrid input[type="text"],
table.apgrid input[type="date"],
table.apgrid select {
  min-height: 34px; padding: 5px 7px; font-size: 14px;
  border-color: transparent; background: transparent; border-radius: 7px;
  width: 100%; min-width: 0;
}
table.apgrid input:hover, table.apgrid select:hover { border-color: var(--line); }
table.apgrid input:focus, table.apgrid select:focus {
  border-color: var(--accent); background: var(--bg);
}
table.apgrid td.posts { font-size: 13px; color: var(--muted); white-space: nowrap; padding-top: 11px; }
table.apgrid td.posts strong { color: var(--ink); }
/* An overhead line's account is already in the dropdown beside it, so this cell
   holds a dash rather than the same number again. Greyed right back: it is
   saying "nothing to see", and it must not read as a value. */
table.apgrid td.posts .same { color: var(--line); }
/* A half-coded row names what it still wants. It is an instruction, so it does
   not wear the ink of a value — nobody should read it as an account. */
table.apgrid td.posts .need { color: var(--accent-text); font-style: italic; }
/* The columns that need room, and the ones that do not. */
/* Done is column 1 now, so every other column shifted along one. */
table.apgrid th:nth-child(1), table.apgrid td:nth-child(1) { width: 54px; text-align: center; }
table.apgrid th:nth-child(2), table.apgrid td:nth-child(2) { width: 130px; }
table.apgrid td.tick input { width: 20px; height: 20px; min-height: 0; accent-color: var(--accent); cursor: pointer; }
/* A finished line steps back without disappearing — she can still read it and
   still untick it, but her eye goes to the ones still wanting her. */
table.apgrid tr.ready td { opacity: .55; }
table.apgrid tr.ready td.tick { opacity: 1; }
/*
 * MIN-width, not width. In an auto-layout table `width` is a suggestion the
 * browser is free to ignore, and it did: the select inside carries min-width:0
 * so the column offered no content minimum to defend itself with, and the text
 * columns either side took the space. 92px measured 53px on the screen, which
 * is why "759" — and then "Overhead" — showed as a single clipped letter and a
 * chevron. min-width is the one the layout has to honour.
 */
table.apgrid th:nth-child(5), table.apgrid td:nth-child(5) {
  width: 124px; min-width: 124px;
}
table.apgrid th:nth-child(8), table.apgrid td:nth-child(8) { width: 96px; }
table.apgrid th:nth-child(9), table.apgrid td:nth-child(9) { width: 28px; text-align: center; }
table.apgrid td:nth-child(9) a { text-decoration: none; opacity: .5; }
table.apgrid td:nth-child(9) a:hover { opacity: 1; }

/* Wide enough that the columns do not crush; the container scrolls instead. */
.sheet > .inner .tw { overflow-x: auto; }
/* The sum of the columns below, so they never crush each other; narrower than
   this and the container scrolls sideways instead. */
table.apgrid { min-width: 1194px; }

/* A/P's grid needs the room a form does not. Only on a screen that has it —
   on a phone this stays the ordinary bottom sheet and the table scrolls
   sideways inside it. */
@media (min-width: 1000px) {
  /*
   * Wide enough that all eight columns land inside it, because the amount is
   * the number she is checking and a column she has to scroll to reach is a
   * column she will not read. 96vw keeps it inside a laptop screen; below
   * about 1300px the table scrolls within the card, which is the honest
   * fallback rather than a crushed grid.
   */
  .sheet > .inner.wide { max-width: min(1340px, 96vw); }
}
/* Give the text columns the space and stop the amount hiding off the edge. */
table.apgrid th:nth-child(3), table.apgrid td:nth-child(3) { min-width: 150px; }
table.apgrid th:nth-child(4), table.apgrid td:nth-child(4) { min-width: 210px; }
/* The selection column carries the account on every overhead row, so it gets
   the width the echo column no longer needs. */
table.apgrid th:nth-child(6), table.apgrid td:nth-child(6) { min-width: 250px; }
table.apgrid th:nth-child(7), table.apgrid td:nth-child(7) { min-width: 150px; }

/* ───────────────────────────────────── the photograph, over everything else
   Dave: "it should pop out a window with the image of the receipt with a X at
   top to close." Its own layer above the sheet (z-index 40), because the sheet
   is holding A/P's grid and her unsaved edits and must survive underneath. */
#photo { display: none; }
#photo.on {
  display: flex; position: fixed; inset: 0; z-index: 60;
  align-items: center; justify-content: center;
  background: rgba(10, 10, 10, .88); padding: 16px;
  padding-top: max(16px, env(safe-area-inset-top));
  padding-bottom: max(16px, env(safe-area-inset-bottom));
}
.photo-inner {
  display: flex; flex-direction: column; gap: 10px;
  width: min(1100px, 100%); max-height: 100%;
  background: var(--card); border-radius: var(--radius); padding: 12px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, .5);
}
.photo-bar { display: flex; align-items: center; gap: 12px; justify-content: space-between; }
.photo-zoom { display: flex; align-items: center; gap: 4px; }
/* 44px, like every other tap target. These get a mouse at A/P's desk and a
   thumb on a phone, and the X is the only visible way out. */
.zoomb, .photo-x {
  min-width: 44px; min-height: 44px; display: inline-flex;
  align-items: center; justify-content: center;
  border: 0; background: transparent; color: var(--muted);
  border-radius: 10px; cursor: pointer; font-size: 20px; line-height: 1;
}
.photo-x { font-size: 26px; }
.zoomb:hover, .photo-x:hover { background: var(--card2); color: var(--ink); }
.zoomb .glass { width: 21px; height: 21px; }
/* Tabular so stepping 100 -> 200 -> 400 does not shuffle the buttons sideways. */
.photo-pct {
  min-width: 46px; text-align: center; font-size: 12.5px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.photo-shot {
  overflow: auto; display: flex; align-items: center; justify-content: center;
  max-height: 78vh; max-height: 78dvh; border-radius: 8px;
  /* The photograph is the one white thing on a charcoal page, so it gets a
     ground of its own rather than glowing straight off the card. */
  background: #f2f2f2;
}
.photo-shot img {
  max-width: 100%; max-height: 78vh; max-height: 78dvh;
  object-fit: contain; display: block;
}
/* Zoomed, it stops shrinking to fit and starts scrolling, and the cursor says
   the picture can be pulled around. */
.photo-shot.zoomed { align-items: flex-start; justify-content: flex-start; cursor: grab; }
/* flex: none is load-bearing. The wrapper is a flex container so it can centre
   the picture when it is fitted, and a flex item shrinks to its container by
   default — so `width: 4800px` was computing back to 1200px and the 200% and
   400% steps did nothing at all while the label happily said they had. */
.photo-shot.zoomed img { max-width: none; max-height: none; flex: none; }
.photo-shot.grabbing { cursor: grabbing; }
/* The stored file is never rewritten, so the turn the extractor reported is
   applied here - the same rule as the confirm card. */
.photo-shot.turn90 img { transform: rotate(90deg); }
.photo-shot.turn180 img { transform: rotate(180deg); }
.photo-shot.turn270 img { transform: rotate(270deg); }

/* ─────────────────────────────────────────────────── routing for approval */
/* A queried line is the one thing in the tracker she must act on, so it reads
   as a problem rather than as another grey line of history. */
.queried { color: var(--bad-ink); margin-top: 3px; }
.trail { margin-top: 6px; display: flex; flex-direction: column; gap: 3px; max-height: 240px; overflow: auto; }
/* Somebody moved a stage along who was not the approver. Marked, never hidden —
   it is the fact an auditor is looking for. */
.ovr {
  color: var(--warn-ink); border: 1px solid var(--warn-line);
  border-radius: 6px; padding: 0 5px; font-size: 11px; margin-right: 4px;
}
.pill {
  border: 1px solid var(--line2); border-radius: 999px; padding: 4px 11px;
  font-size: 12px; color: var(--muted); white-space: nowrap;
}
table.apgrid td.actions { white-space: nowrap; }
table.apgrid td.actions .btn { margin-left: 4px; }

/* Where a sent batch has got to. Amber while somebody still has to act, green
   once it is clear — the two states a person scanning the Sent list cares
   about, and neither of them is the same as "paid". */
.waiting { color: var(--warn-ink); }
.ok { color: var(--good-ink); }

/* ───────────────────────────────────────────── open work vs the paid record */
/* Two jobs, two lists. The toggle borrows the Scorecard's segmented control so
   it reads the same as the rest of Key's tools. */
.seg {
  display: inline-flex; background: var(--card); border: 1px solid var(--line);
  border-radius: 10px; padding: 5px 4px; gap: 3px;
}
.seg button {
  font-size: 13.5px; font-weight: 700; border: 0; background: transparent;
  color: var(--muted); padding: 9px 15px; border-radius: 7px; cursor: pointer;
  white-space: nowrap; min-height: 44px;
}
.seg button:hover { color: var(--ink); }
.seg button[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); }

/* Wraps, so the same row of filters works on a phone and at a desk. */
.filters { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; }
.filters .field { min-width: 0; }
.filters input { min-height: 44px; }

/* The paid table is a record to scan, not a form to edit — so its cells are
   plain text and it does not need the apgrid's minimum width. */
table.paidgrid { min-width: 720px; }
table.paidgrid td { padding: 9px 10px 9px 0; }
table.paidgrid td.muted { color: var(--faint); }

/* THE APPROVAL TABLE ON A PHONE.
   Eight columns do not fit in 375px. The table scrolled sideways and the
   Approve and Query buttons sat 700px past the right edge of the screen,
   where nobody looks - a manager opened "Look at these" and saw no way to
   answer. Below the dialog breakpoint each line becomes a card with its two
   buttons underneath, full width, thumb-sized. */
@media (max-width: 759px) {
  table.approvegrid, table.approvegrid tbody, table.approvegrid tr, table.approvegrid td { display: block; }
  table.approvegrid thead { display: none; }
  table.approvegrid tr { padding: 10px 0 12px; border-bottom: 1px solid var(--line); }
  table.approvegrid td { border: 0; padding: 1px 0; text-align: left !important; }
  /* The desktop column widths (54px date, 130px merchant, 1194px table)
     are what pushed the buttons off-screen; none of them apply to a card. */
  table.approvegrid { min-width: 0; width: 100%; }
  table.approvegrid td:nth-child(n) { width: auto; min-width: 0; white-space: normal; }
  table.approvegrid td.posts { color: var(--muted); }
  table.approvegrid td.actions { display: flex; gap: 8px; margin-top: 8px; }
  table.approvegrid td.actions .btn { flex: 1; text-align: center; padding: 10px 12px; }
}

/* ---- PDFs (3 Sep): a receipt that arrived as a file, not a photograph ---- */
ul.rows li .thumb.pdf {
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; letter-spacing: .06em; color: var(--muted);
}
.shot.pdf { height: 40vh; max-height: none; }
.shot.pdf.big { height: 85vh; }
.drophint { color: var(--muted); font-size: 13px; text-align: center; margin: 8px 0 0; }
@media (hover: none) { .drophint { display: none; } }
body.dropping { outline: 3px dashed var(--ink); outline-offset: -8px; }
body.dropping::after {
  content: "Drop the receipt here"; position: fixed; left: 50%; top: 18px; transform: translateX(-50%);
  background: var(--ink); color: var(--bg); padding: 8px 16px; border-radius: 999px;
  font-weight: 600; z-index: 99; pointer-events: none;
}

/* ---- approvals switched off (10 Sep): the coding table without the Done tick
   and the standing column; header and cells hide together ---- */
.no-approvals .apgrid th:nth-child(1), .no-approvals .apgrid td.tick,
.no-approvals .apgrid th:nth-child(9), .no-approvals .apgrid td.flow { display: none; }
