/* =========================================================================
   ROMOTANA — admin stylesheet
   Loaded after site.css: the tokens, buttons, forms, tables and alerts all
   come from there, and this file adds the application shell around them.
   ========================================================================= */

/* The admin shell's navy ramp.
 *
 * These four tokens were used throughout this file from the day it was written
 * and defined nowhere, so every var() referencing them resolved to nothing:
 * the sidebar had no background, the sign-in card had no gradient, and the
 * current page in the navigation was white text on white. Found on
 * 4 September 2026 while screenshotting the new Page content screen, whose
 * nav item was invisible for exactly this reason.
 *
 * Derived from site.css's --blue-deep (#072a63) so the admin reads as the same
 * publication as the public site rather than as a second product.
 */
:root {
  --navy-900: #061f4a;
  --navy-800: #072a63;   /* = --blue-deep, the public site's darkest blue */
  --navy-700: #0b3a8f;   /* = --blue */
  --navy-600: #1b4ea8;

  /* Same family of bug as --shadow-1 above: admin.css uses var(--muted) and
     var(--line) in twelve places — muted captions, panel borders, table rules
     — and neither was ever defined, here or in site.css, so all twelve
     resolved to nothing. Aliased to the real site tokens rather than renamed
     at each use, so the two vocabularies stay in step.
     Found and corrected 4 September 2026. */
  --muted: var(--ink-3);
  --line:  var(--rule);

  /* Four more of the same, found on 4 September by the design-token check in
     smoke-test.php rather than by eye — which is the point of writing it. Each
     is named by admin templates that predate the Spread rewrite: soft panel
     grounds, the small radius on a thumbnail, the hairline between rows of a
     mini list, and the gold rule on the "here is the password" panel. All four
     resolved to nothing, so those surfaces were flat white and square.
     Aliased, not renamed at each use, so the two vocabularies stay in step. */
  --bg-soft:   var(--paper-2);
  --line-soft: var(--rule);
  --radius-sm: var(--radius);
  --gold-500:  var(--yellow);
}

body.admin {
  background: var(--bg-soft);
  font-size: 1rem;
}

/* --------------------------------------------------------------- shell --- */
.admin-shell {
  display: grid;
  grid-template-columns: 16rem minmax(0, 1fr);
  min-height: 100vh;
}

.admin-side {
  background: var(--navy-900);
  color: #b9c7de;
  padding: 1.5rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}
.admin-side__brand {
  display: flex;
  align-items: center;
  gap: .7rem;
  color: #fff;
  text-decoration: none;
  padding-inline: .5rem;
}
.admin-side__brand img { height: 2.25rem; }
.admin-side__brand strong { font-size: .95rem; letter-spacing: .08em; text-transform: uppercase; }
.admin-side__brand span { display: block; font-size: .7rem; color: #8ea1c0; letter-spacing: .04em; }

.admin-nav { display: grid; gap: .15rem; }
.admin-nav__label {
  font-size: .68rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: #6d80a3;
  padding: 1rem .75rem .35rem;
}
.admin-nav a {
  display: flex;
  align-items: center;
  gap: .65rem;
  padding: .6rem .75rem;
  border-radius: var(--radius-sm);
  color: #cbd7ea;
  text-decoration: none;
  font-size: .94rem;
  font-weight: 550;
}
.admin-nav a:hover { background: rgba(255, 255, 255, .07); color: #fff; }
.admin-nav a[aria-current="page"] { background: var(--navy-700); color: #fff; }
.admin-nav svg { width: 1.05rem; height: 1.05rem; fill: currentColor; opacity: .85; flex: 0 0 auto; }
.admin-nav__count {
  margin-left: auto;
  background: var(--red-600);
  color: #fff;
  border-radius: 999px;
  font-size: .72rem;
  font-weight: 700;
  padding: .1rem .45rem;
}

.admin-side__foot { margin-top: auto; font-size: .8rem; color: #8ea1c0; padding: .75rem; }
.admin-side__foot a { color: #cbd7ea; }

.admin-main { display: flex; flex-direction: column; min-width: 0; }

.admin-top {
  background: #fff;
  border-bottom: 1px solid var(--line);
  padding: 1rem clamp(1.25rem, 3vw, 2.25rem);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  position: sticky;
  top: 0;
  z-index: 20;
}
.admin-top h1 { font-size: 1.35rem; margin: 0; }
.admin-top__meta { font-size: .85rem; color: var(--muted); margin-top: .15rem; }
.admin-top__actions { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; }
/* Close and Reopen are posts, not links, so they arrive in the page header
   wrapped in a form. The wrapper lays out as the button it holds, and nothing
   else — the same job .pay-head__form does on the payments screens. */
.admin-top__form { display: inline-flex; margin: 0; }

.admin-body { padding: clamp(1.25rem, 3vw, 2.25rem); display: grid; gap: 1.5rem; align-content: start; }

.admin-menu-toggle { display: none; }

/* ------------------------------------------------------------- widgets --- */
.kpi-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: 1rem; }
/* The admin stays utilitarian, but it uses the same three shading steps as the
   public site so the two halves read as one product. See design-spread.md §3a. */
.kpi {
  background: #fff;
  box-shadow: var(--shade-1);
  border: 1px solid var(--line);
  border-left: 4px solid var(--navy-700);
  border-radius: var(--radius);
  padding: 1.1rem 1.25rem;
  text-decoration: none;
  display: block;
  color: inherit;
}
.kpi:hover { box-shadow: var(--shade-2); color: inherit; }
.kpi--gold { border-left-color: var(--gold-500); }
.kpi--red  { border-left-color: var(--red-500); }
.kpi--green { border-left-color: #2e9e63; }
/* Block, so the number sits above its label whether the view wrote a <div> or
   a <span>. Two views wrote spans, and on those the figure and its label ran
   together on one line and wrapped raggedly — "927 Registered", and a fourth
   card two lines taller than the three beside it. A card should not depend on
   which element somebody reached for. */
.kpi__value { display: block; font-size: 1.9rem; font-weight: 800; color: var(--ink); line-height: 1.1; }
.kpi__label { display: block; font-size: .85rem; color: var(--muted); margin-top: .2rem; }

.panel {
  background: #fff;
  box-shadow: var(--shade-1);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.panel__head {
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.panel__head h2 { font-size: 1.05rem; margin: 0; }
.panel__body { padding: 1.25rem; }
.panel__body--flush { padding: 0; }
.panel__foot {
  padding: .85rem 1.25rem;
  border-top: 1px solid var(--line);
  background: var(--bg-soft);
  font-size: .9rem;
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  align-items: center;
}

.toolbar {
  display: flex;
  gap: .75rem;
  flex-wrap: wrap;
  align-items: center;
}
/* The search sits at the far end of the toolbar, away from the filter tabs.
   A class rather than an inline margin-left:auto, because an inline style
   cannot be overridden by a media query and on a phone it left the field and
   its button stranded against the right edge with the toolbar's whole width
   empty beside them. */
.toolbar__search { margin-left: auto; display: flex; gap: .5rem; align-items: center; flex: 1 1 auto; justify-content: flex-end; }
.toolbar form { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; }

/* The field grows into the space the toolbar already had.
   At a fixed 12rem it measured 192px while its placeholder needed 299 — and
   the placeholder is the only thing that says what can be searched, so
   "Company, contact, email, reference or USDOT" was cut off after "reference".
   Two of the three search screens were clipping the instruction. It still
   stops at 26rem: a search box the width of the page reads as the subject of
   the screen rather than a way through it. */
/* Sized by the search form as well as by the toolbar around it. Two screens
   (events, partners) put their form straight into .panel__head with no
   .toolbar wrapper, so the field there kept none of this and sat at the
   browser's default width beside boxes that were twice as wide. Matching on
   the search form itself means the field measures the same wherever a screen
   chooses to hang it. */
.toolbar input[type="search"],
.toolbar__search input[type="search"] { flex: 1 1 20rem; min-width: 12rem; max-width: 26rem; }
.toolbar select { min-width: 12rem; }

.filter-tabs { display: flex; gap: .3rem; flex-wrap: wrap; }
.filter-tabs a {
  padding: .4rem .8rem;
  border-radius: 999px;
  font-size: .88rem;
  font-weight: 600;
  text-decoration: none;
  color: var(--body);
  background: var(--bg-soft);
  border: 1px solid var(--line);
}
.filter-tabs a:hover { border-color: var(--navy-600); }
.filter-tabs a[aria-current="true"] { background: var(--navy-800); border-color: var(--navy-800); color: #fff; }

.status {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .18rem .55rem;
  border-radius: 999px;
  font-size: .75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  white-space: nowrap;
}
.status--new, .status--pending   { background: var(--warn-bg); color: var(--warn-fg); }
.status--approved, .status--active, .status--published, .status--closed { background: var(--ok-bg); color: var(--ok-fg); }
.status--draft, .status--lapsed, .status--in_progress { background: var(--bg-soft); color: var(--muted); }
.status--declined, .status--spam, .status--cancelled { background: var(--err-bg); color: var(--err-fg); }
/* The ELD sync log writes `status--ok` and `status--danger`, and neither
   existed — so a run that FAILED wore the same neutral pill as one that
   loaded, on the one screen whose job is to say whether the register copied
   cleanly. Matched to the same green and red as every other state here. */
.status--ok     { background: var(--ok-bg);  color: var(--ok-fg); }
.status--danger { background: var(--err-bg); color: var(--err-fg); }
.status--registered { background: var(--info-bg); color: var(--info-fg); }
.status--attended   { background: var(--ok-bg);   color: var(--ok-fg); }


.detail-list { display: grid; gap: .1rem; margin: 0; }
.detail-list div {
  display: grid;
  grid-template-columns: 11rem minmax(0, 1fr);
  gap: 1rem;
  padding: .6rem 0;
  border-bottom: 1px solid var(--line-soft);
}
.detail-list dt { color: var(--muted); font-size: .9rem; }
.detail-list dd { margin: 0; color: var(--ink); overflow-wrap: anywhere; }

.form-actions {
  display: flex;
  gap: .75rem;
  flex-wrap: wrap;
  align-items: center;
  padding-top: .5rem;
}
.form-actions--split { justify-content: space-between; }

.empty-state {
  text-align: center;
  padding: clamp(2rem, 5vw, 3.5rem) 1.5rem;
  color: var(--muted);
}
.empty-state h3 { color: var(--ink); }

.login-page {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 2rem 1.25rem;
  background:
    radial-gradient(60rem 30rem at 80% -10%, rgba(242, 193, 28, .12), transparent 60%),
    linear-gradient(160deg, var(--navy-900), var(--navy-700));
}
.login-card {
  width: 100%;
  max-width: 25rem;
  background: #fff;
  border-radius: var(--radius-lg);
  /* --shadow-1 and --shadow-3 were referenced here and on .kpi:hover but were
     never defined anywhere — leftovers from the stylesheet this replaced, so
     both shadows had silently been doing nothing. Now they use the real
     tokens. Corrected 4 September 2026. */
  box-shadow: var(--shade-2);
  padding: clamp(1.75rem, 4vw, 2.5rem);
}
/* The mark is the way back to the public site: these two screens are the only
   ones with no navigation at all, so without it a signed-out visitor who does
   not want to sign in has nowhere to go but the browser's back button. */
.login-card__brand {
  display: block;
  text-align: center;
  margin-bottom: 1.5rem;
  text-decoration: none;
  color: inherit;
  border-radius: var(--radius);
}
.login-card__brand:hover strong { color: var(--navy-600); }
.login-card__brand:focus-visible { outline: 2px solid var(--navy-700); outline-offset: 4px; }
.login-card__brand img { height: 3.5rem; margin: 0 auto .75rem; }
.login-card__brand strong { display: block; letter-spacing: .1em; text-transform: uppercase; color: var(--navy-800); }
.login-card__brand span { font-size: .85rem; color: var(--muted); }

/* The forced first-sign-in password change reuses this card as a dialog:
   nothing of the administration area is rendered behind it. */
.login-card--dialog { box-shadow: var(--shade-3); }
.login-card__title { font-size: 1.25rem; margin: 0 0 .75rem; text-align: center; }
.login-card__lede { margin: 0 0 1.5rem; color: var(--muted); font-size: .92rem; }
.login-card__aside {
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .5rem 1rem;
  font-size: .85rem;
  color: var(--muted);
}
/* A button that reads as a link but is still a button, so it still has to be
   hittable with a thumb. Left at its natural 22px it was the only control on
   the forced-password-change gate that a phone could not reliably hit — and
   that gate is the first screen eleven staff accounts will ever see. The
   padding is vertical only: horizontal padding would make the underline run
   wider than the words. */
.linkish {
  background: none;
  border: 0;
  padding: .5rem 0;
  min-height: 2.75rem;
  font: inherit;
  color: var(--navy-700);
  text-decoration: underline;
  cursor: pointer;
}

/* ---------------------------------------------------------------- data --- */
/* Every admin list — news, members, inquiries, events, partners, team, users,
   activity, newsletter, registrations, partner categories — writes
   `<table class="data">`, and until today `.data` matched nothing at all.
   Eleven screens were rendering as bare browser tables: `th` centred and bold
   because that is the user-agent default, no padding, no rules, columns sized
   by whatever the longest string happened to be. The news list looked the
   worst only because it has the most to say per row.

   The shape follows `.table` in site.css so the admin reads as the same
   publication as the public site: a firm rule under the head, hairlines
   between rows, small caps for the column names. */
.table-wrap { overflow-x: auto; max-width: 100%; }

.data {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--sans);
  font-size: var(--step--1);
}

.data th {
  text-align: left;
  font-weight: 700;
  color: var(--muted);
  font-size: var(--step--2);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
  padding: 0.55rem 0.9rem 0.55rem 0;
  border-bottom: 2px solid var(--ink);
}

.data td {
  padding: 0.7rem 0.9rem 0.7rem 0;
  border-bottom: 1px solid var(--line, var(--rule));
  /* middle, not baseline: a cell holding a title above a muted meta line sits
     ragged against a single-line cell beside it when baselines are aligned. */
  vertical-align: middle;
  color: var(--ink);
}

/* The row header carries the record's name. It is the thing being scanned, so
   it is left-aligned and normal weight — the browser's centred bold is what
   made these tables look like spreadsheets. */
.data tbody th {
  text-align: left;
  font-weight: 600;
  color: var(--ink);
  border-bottom: 1px solid var(--line, var(--rule));
  padding: 0.7rem 0.9rem 0.7rem 0;
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--step--1);
  white-space: normal;
  vertical-align: middle;
}

.data tbody tr:hover { background: var(--bg-soft); }
.data tbody tr:last-child th,
.data tbody tr:last-child td { border-bottom: 2px solid var(--ink); }

/* Dates and counts read as columns of figures, so they get tabular numerals
   and never wrap mid-date. */
.data .num,
.data .when { white-space: nowrap; font-variant-numeric: tabular-nums; }

/* The first and last columns keep the panel's own gutter.

   Every cell is padded on its right and not on its left, which lines the
   columns up beautifully and puts the first one hard against the panel border:
   a table always sits in a `--flush` body or straight in the panel, so there
   was nothing else to hold it off the edge, and on ten screens the dates and
   names in the first column were printed on the line. The last column had the
   same fault at the other end, 0.9rem from the border where every other
   element in the panel is 1.25rem in.

   1.25rem is the panel body's own padding, so a table now starts where the
   prose above it starts. The rules and the hover band still run the full width
   — the gutter is on the cell, not on the table — which is what keeps a table
   reading as a register rather than a stack of boxes. */
.data th:first-child,
.data td:first-child { padding-left: 1.25rem; }
.data th:last-child,
.data td:last-child { padding-right: 1.25rem; }

/* Actions sit at the right edge, on one line, and stay put. */
.data .row-actions {
  text-align: right;
  white-space: nowrap;
  width: 1%;
  padding-right: 1.25rem;
}
.data .row-actions form { display: inline; }
.data .row-actions > *, .data .row-actions form > * { margin-left: 0.3rem; }

/* The news row: a mark, a title, and one muted line that qualifies it. The
   mark is the category illustration the article already carries, at the size
   of a bullet — it gives the eye something to run down the left edge, and it
   is real information rather than an ornament. */
.row-title { display: flex; align-items: flex-start; gap: 0.7rem; }
.row-title__mark {
  flex: 0 0 auto;
  width: 2rem; height: 2rem;
  object-fit: contain;
  background: var(--bg-soft);
  border: 1px solid var(--line, var(--rule));
  border-radius: var(--radius);
  padding: 0.1rem;
  margin-top: 0.05rem;
}
.row-title__text { min-width: 0; }
.row-title__text > a {
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
  /* The whole row is a list of links; underlining every title turns the column
     into a thicket. The underline arrives on hover, where it means something. */
}
.row-title__text > a:hover,
.row-title__text > a:focus-visible { text-decoration: underline; }
.row-title__meta {
  margin: 0.15rem 0 0;
  font-size: var(--step--2);
  color: var(--muted);
  font-weight: 400;
}
.row-title__meta a { color: inherit; }

/* Said once, above the table, instead of on every row. */
.panel__note {
  margin: 0;
  padding: 0.85rem clamp(1rem, 2.5vw, 1.5rem);
  border-bottom: 1px solid var(--line, var(--rule));
  background: var(--info-bg);
  color: var(--info-fg);
  font-size: var(--step--2);
}

/* A draft close to being swept. Red, because it is the only thing on the
   screen that will happen without somebody deciding it. */
.row-flag {
  display: block;
  font-size: var(--step--2);
  color: var(--red);
  font-weight: 600;
}

/* A caption over a thing rather than under it — "Current image", "Current
   photograph", "Listing" — used in four admin forms and styled in none, so it
   arrived as ordinary body text next to the thumbnail it was labelling.
   `.field__hint` is its sibling and sits below; this one sits above and carries
   the weight of a label. */
.field__label {
  display: block;
  font-size: var(--step--2);
  font-weight: 700;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 0.3rem;
}

/* "You", beside your own row in the admin users list. The badge base gives it
   a shape and nothing else, so it read as plain text in a list where every
   other cell is plain text — which is the one place it needed to stand out. */
.badge--navy { background: var(--blue-deep); color: #fff; }

/* ------------------------------------------------------------- a chart --- */

/* Bars, drawn with boxes rather than with a library.
   The Content-Security-Policy on this site allows scripts from itself and
   nothing else, which rules out every charting package; and a bar chart is
   twelve divs and a percentage height, which is less code than loading one
   would have been.

   The markup existed on the dashboard from the day it was written and this
   stylesheet never had a rule for any of it — `.mini-chart` matched nothing, so
   the bars were inline spans with a height percentage and no box to have a
   height in. Nobody saw it because the chart only renders when there is data
   and there were no applications; the day the first one arrived, the dashboard
   would have shown a column of loose numbers. Found on 7 September 2026 while
   building the traffic screen, which wanted the same chart.

   `align-items: end` is what makes them bars: each column is as tall as the
   row, and the bar grows from the bottom of it. */
.mini-chart {
  display: flex;
  align-items: end;
  gap: .35rem;
  height: 9rem;
  padding-top: 1.25rem;         /* room for the figure above the tallest bar */
}
.mini-chart__col {
  flex: 1 1 0;
  min-width: 0;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: end;
  align-items: center;
  gap: .25rem;
  position: relative;
}
/* The figure sits above its own bar, however tall the bar is. */
.mini-chart__n {
  font-size: .7rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.mini-chart__bar {
  width: 100%;
  max-width: 2.5rem;
  min-height: 2px;
  background: var(--navy-700);
  border-radius: 2px 2px 0 0;
}
/* A day with nothing is a gap in the row, not a missing column: the shape of
   the week is the information, and a chart that silently drops its empty days
   draws a busy fortnight out of two busy days. */
.mini-chart__bar--empty { background: var(--line); min-height: 2px; }
.mini-chart__x {
  font-size: .65rem;
  color: var(--muted);
  white-space: nowrap;
  line-height: 1;
}

/* Past a fortnight the dates cannot all be printed, so they stop being
   printed. The bars still say the shape and the table underneath still has
   every figure. */
@media (max-width: 60rem) {
  .mini-chart { gap: .2rem; }
  .mini-chart__n, .mini-chart__x { display: none; }
}

/* ------------------------------------------------- two-column records --- */

/* A record screen with seven panels is a very long column on a desktop, and
   the right half of a 1440px window was empty while the reader scrolled 3,400
   pixels. The split puts what the record IS on the left and what you can DO
   with it on the right.

   It is one grid, not floats, so the two columns start level and neither
   stretches to match the other. Below 80rem it collapses back to the single
   column it was, in source order — the left column first, which is the order
   somebody reads the record in anyway. */
.admin-split { display: grid; gap: 1.5rem; align-items: start; }
.admin-stack { display: grid; gap: 1.5rem; margin: 1.5rem 0; }
.admin-split + .admin-split { margin-top: 1.5rem; }
.admin-split__main,
.admin-split__side { display: grid; gap: 1.5rem; align-content: start; min-width: 0; }

@media (min-width: 80rem) {
  .admin-split { grid-template-columns: minmax(0, 1.9fr) minmax(0, 1fr); }
}

/* ------------------------------------------------------ a folded form --- */

/* A form nobody has asked for yet, folded away behind its own button.
   `<details>` rather than a script: it opens without JavaScript, it is a
   button to a screen reader, and the open state survives the page not having
   loaded its scripts yet. The summary is styled as a button because that is
   what it is — the marker is removed so it does not sit inside one. */
.fold { display: block; }
.fold > summary {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  cursor: pointer;
  list-style: none;
}
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary::marker { content: ''; }
.fold[open] > summary { margin-bottom: 1rem; }

/* The chevron is the only thing that says this opens rather than submits. */
.fold > summary .fold__sign::before { content: '+'; font-weight: 800; }
.fold[open] > summary .fold__sign::before { content: '\2212'; }

/* -------------------------------------------------------------- a fact --- */

/* One number the screen exists to show: an account number, a reference. Bigger
   than body text, tabular so digits line up between records, and selectable
   without the surrounding words coming with it. */
.fact { display: grid; gap: .15rem; margin: 0 0 1rem; }
.fact__label { font-size: .8rem; color: var(--muted); }
.fact__value {
  font-size: 1.35rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: .01em;
  overflow-wrap: anywhere;
}

/* ---------------------------------------------------------- responsive --- */
@media (max-width: 900px) {
  .admin-shell { grid-template-columns: 1fr; }
  /* Drawer, not in-flow expand: the old is-open flexed the sidebar above the
     page and shoved the dashboard ~1500px down with no close control. */
  .admin-side {
    display: none;
    position: fixed;
    inset: 0 auto 0 0;
    width: min(20rem, 88vw);
    height: 100vh;
    z-index: 80;
    box-shadow: 8px 0 28px rgba(6, 31, 74, 0.35);
  }
  .admin-side.is-open { display: flex; }
  .admin-backdrop {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 70;
    border: 0;
    padding: 0;
    margin: 0;
    background: rgba(6, 31, 74, 0.45);
    cursor: pointer;
  }
  .admin-backdrop.is-open { display: block; }
  .admin-menu-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    min-width: 44px;
  }
  .admin-side__close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    align-self: flex-end;
    min-height: 44px;
    min-width: 44px;
    margin: -0.4rem 0.25rem 0.4rem auto;
    padding: 0.4rem 0.75rem;
    border: 1px solid #4a5f85;
    border-radius: var(--radius-sm);
    background: transparent;
    color: #cbd7ea;
    font: inherit;
    font-weight: 700;
    cursor: pointer;
  }
  .admin-side__close:hover { background: rgba(255,255,255,.07); color: #fff; }
  .detail-list div { grid-template-columns: 1fr; gap: .15rem; }
}
@media (min-width: 901px) {
  .admin-backdrop,
  .admin-side__close { display: none !important; }
}

/* Below 44rem an admin list stops being a table and becomes a list of records.
   Scrolling a 330px box sideways is what it did before, and it is a poor answer
   for the same reason the public register's is: by the time you have dragged
   across to the status you have lost the row you were reading. The same
   decision, and the same 44rem, as `.table--stack` on the public side.

   admin.js copies each column heading onto its cells as data-label, and the
   labels are printed here — so a date says which date it is. Without that
   script the tables scroll as they always did. */
@media (max-width: 44rem) {
  .data, .data tbody, .data tr, .data th, .data td { display: block; width: 100%; }

  /* Gone from the screen, kept in the accessibility tree: the headings are
     still the labels for cells that no longer sit beneath them. */
  .data thead {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
  }

  /* A card per record.
     
     The public register argues the other way — "the rules are what make it a
     register rather than a list of cards" — and it is right about a document
     somebody reads. This is not that. Here each record carries five labelled
     fields and up to three buttons, and a hairline is too quiet a boundary
     between one set of those and the next: on a phone the eye loses which
     Delete belongs to which headline. A card is the boundary.
     
     Kept cheap: one hairline border, the panel's own radius, no shadow. The
     table already sits inside a panel, and a shadow on every row inside a
     shadowed box is the sort of thing that makes an interface feel padded.
     
     A column, so the record's name leads whatever column it occupies. On the
     partner list the logo is the first cell and the name the second, and
     stacked in source order every partner opened with "LOGO" and a picture
     before it said whose it was. */
  .data tbody tr {
    display: flex;
    flex-direction: column;
    padding: 0.85rem 0.9rem;
    margin: 0 0 0.7rem;
    background: var(--white, #fff);
    border: 1px solid var(--line, var(--rule));
    border-radius: var(--radius);
    border-left: 3px solid var(--navy-700);
  }
  .data tbody th[scope="row"] { order: -1; }
  .data .row-actions { order: 99; }

  /* A group heading — the partner list's category rows — is not a record and
     should not look like one. Matched by class rather than :has(), which is
     well supported now but not universally, and a heading rendered as a card
     is a worse failure than the class costing one attribute. */
  .data tbody tr.data__group {
    background: none;
    border: 0;
    border-radius: 0;
    padding: 1.1rem 0 0.35rem;
    margin: 0;
  }
  .data tbody tr:last-child { margin-bottom: 0; }
  .data tbody tr:hover { background: var(--white, #fff); }   /* no hover target on a phone */

  /* The record's name leads, at the size it deserves. */
  .data tbody th[scope="row"] {
    border: 0;
    padding: 0 0 0.4rem;
    font-size: var(--step-0);
  }

  /* Label left, value beside it, both starting on a column of their own.

     This was flex with space-between, which pushed every value to the
     right edge and left a ragged gutter down the middle of the card. It
     also broke cells holding more than one element: the account number and
     the company name are a link and a div, and as two flex items they sat
     side by side instead of stacking as they do on a desktop.

     The label is taken out of flow and the cell indented to make room for it,
     so every value starts at the same x AND a cell holding several elements
     keeps all of them in that column. Floating the label was tried first and
     got the second part wrong: a float is only as tall as the label, so the
     second line of a cell, the company name under the account number, cleared
     it and jumped back to the card edge. */
  .data tbody td {
    border: 0;
    position: relative;
    padding: 0.3rem 0 0;
    display: block;
  }
  /* Only a cell that actually has a label gets the room for one. The actions
     cell has none — its column heading is a visually-hidden "Actions" that the
     script does not copy — so its buttons sit at the card's edge, which is
     where a hand reaching for Delete expects them. Only the news list had
     marked that cell with a class; this covers every table without asking
     eight views to remember. */
  .data tbody td[data-label]:not([data-label=""]) { padding-left: 7.5rem; }
  .data tbody td:empty { display: none; }

  /* The desktop gutter is undone here, explicitly. A card supplies its own
     padding, and `:first-child` outranks the `.data tbody td` reset above it,
     so without this the first field of every card was indented 20px past the
     rest and the actions sat a gutter short of the edge a thumb reaches for. */
  .data tbody th:first-child,
  .data tbody td:first-child { padding-left: 0; }
  .data tbody th:last-child,
  .data tbody td:last-child { padding-right: 0; }
  .data .row-actions { padding-right: 0; }

  .data tbody td[data-label]:not([data-label=""])::before {
    content: attr(data-label);
    position: absolute;
    left: 0;
    top: 0.3rem;
    width: 7.5rem;
    padding-right: 0.75rem;
    color: var(--muted);
    font-size: var(--step--2);
    line-height: 1.5;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-weight: 700;
  }

  /* Under 24rem there is no room for a label column and a value beside it,
     so the label goes above. */
  @media (max-width: 24rem) {
    .data tbody td[data-label]:not([data-label=""]) { padding-left: 0; }
    .data tbody td[data-label]:not([data-label=""])::before {
      position: static; display: block; width: auto; padding-right: 0;
    }
  }

  /* The rule that closes the last row of a table on a desktop. In card mode it
     drew a heavy 2px line under every field of the last card — and, because it
     names th as well as td, under the last record's title too. On a screen
     holding one record that is a line under everything. Both halves are reset;
     resetting only td left the title underlined on News, Events and Leadership. */
  .data tbody tr:last-child td,
  .data tbody tr:last-child th { border-bottom: 0; }

  /* Actions get the full width and sit together at the end of the record,
     which is where somebody reaching for them expects them. */
  .data .row-actions {
    width: 100%;
    text-align: left;
    padding: 0.6rem 0 0;          /* not indented: buttons start at the card edge */
    white-space: normal;
  }
  .data .row-actions > *, .data .row-actions form > * { margin: 0.3rem 0.3rem 0 0; }

  /* The news list's mark and title share a line as they do on a desktop. */
  .row-title { align-items: flex-start; }
}

@media (max-width: 640px) {
  .admin-top { flex-direction: column; align-items: flex-start; gap: .75rem; }
  .toolbar input[type="search"], .toolbar__search input[type="search"],
  .toolbar select { min-width: 0; width: 100%; }
  .toolbar__search { margin-left: 0; width: 100%; }
}

/* ------------------------------------------------------------- help note --- */
/* A question mark in the page header that keeps an explanation off the screen
   until somebody wants it.
   
   A <details>, not a scripted popover: it is keyboard-operable and focusable
   for free, it works with no JavaScript, and the browser owns the open/closed
   state. The explanation it holds can afford to be thorough precisely because
   it costs no space when closed — which is the whole reason it replaced a
   four-line banner sitting above every row of the table. */
.helpnote { position: relative; }
.helpnote > summary {
  list-style: none;
  cursor: pointer;
  width: 1.9rem;
  height: 1.9rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  background: #fff;
  color: var(--muted);
  font: 700 0.95rem/1 var(--sans);
  transition: color .12s ease, border-color .12s ease;
}
.helpnote > summary::-webkit-details-marker { display: none; }
.helpnote > summary::marker { content: ""; }
.helpnote > summary:hover,
.helpnote[open] > summary { border-color: var(--navy-700); color: var(--navy-700); }
.helpnote > summary:focus-visible { outline: 2px solid var(--navy-700); outline-offset: 2px; }

.helpnote__body {
  position: absolute;
  right: 0;
  top: calc(100% + 0.55rem);
  z-index: 40;
  width: min(32rem, calc(100vw - 2.5rem));
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shade-2);
  padding: 1.1rem 1.25rem;
  text-align: left;
  font-size: 0.9rem;
  line-height: 1.55;
  color: var(--ink-2);
}
.helpnote__body h3 { margin: 0 0 0.5rem; font-size: 1rem; }
.helpnote__body p { margin: 0 0 0.7rem; }
.helpnote__body > :last-child { margin-bottom: 0; }
.helpnote__body dl { margin: 0 0 0.7rem; }
.helpnote__body dt { font-weight: 700; color: var(--ink); margin-top: 0.5rem; }
.helpnote__body dt:first-child { margin-top: 0; }
.helpnote__body dd { margin: 0.1rem 0 0; }
.helpnote__body code {
  font-size: 0.88em;
  background: var(--bg-soft, #f4f2ed);
  padding: 0.05em 0.3em;
}

/* Next to a field label. The page-header ? hangs from the right; this one
   hangs from the left so the panel stays over the textarea, not off the page. */
.field__head {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 0.35rem;
}
.field__head > label {
  font-size: var(--step--1);
  font-weight: 700;
  color: var(--ink);
  margin: 0;
}
.helpnote--inline > summary {
  width: 1.45rem;
  height: 1.45rem;
  font-size: 0.8rem;
}
.helpnote--inline .helpnote__body {
  right: auto;
  left: 0;
  max-height: min(28rem, 70vh);
  overflow-y: auto;
}

/* The sheet is taller than the rest of the panel. overflow:hidden (the
   radius) would clip it, and the next panel would paint on top of what
   remained — which is how “When” cut the formatting note in half. */
.panel:has(.helpnote[open]) {
  overflow: visible;
  position: relative;
  z-index: 30;
}

/* Below a wide desktop the panel stops hanging off the icon and pins itself
   to the viewport instead.

   Anchoring to the icon cannot be made to work at every width, which the
   first attempt proved twice: left-anchored it ran off the right at 820px,
   and on Applications, where an Export CSV button sits before the icon, it
   ran off at 390px too. Where the icon happens to be is not something a
   stylesheet should have to predict. Pinned to the viewport with a cap and
   margin-left:auto, it hugs the right and always fits. */
@media (max-width: 60rem) {
  .helpnote__body {
    position: fixed;
    top: auto;
    left: 1rem;
    right: 1rem;
    width: auto;
    max-width: 32rem;
    margin-left: auto;
    max-height: 70vh;
    overflow-y: auto;
  }
}

/* ------------------------------------------------- the traffic screen --- */

/* The bars are links when the screen has data behind each day. A link needs
   to look like one only on hover: the shape of the chart is the point, and a
   row of underlined columns is noise. */
.mini-chart--live { position: relative; }
.mini-chart--live .mini-chart__col { text-decoration: none; color: inherit; border-radius: 3px; }
.mini-chart--live .mini-chart__col:hover .mini-chart__bar,
.mini-chart--live .mini-chart__col:focus-visible .mini-chart__bar { background: var(--navy-600); }
.mini-chart--live .mini-chart__col:focus-visible { outline: 2px solid var(--navy-600); outline-offset: 2px; }
.mini-chart--live .mini-chart__col.is-selected .mini-chart__bar { background: var(--gold-500); }
.mini-chart--live .mini-chart__col.is-selected .mini-chart__x,
.mini-chart--live .mini-chart__col.is-selected .mini-chart__n { color: var(--ink); font-weight: 700; }
.mini-chart--live .mini-chart__bar { transition: height .25s ease, background-color .15s ease; }
@media (prefers-reduced-motion: reduce) { .mini-chart--live .mini-chart__bar { transition: none; } }

/* Robots are drawn in the robots' colour, so a switch of measure is seen,
   not only read. */
.mini-chart[data-metric="robot_views"] .mini-chart__col:not(.is-selected) .mini-chart__bar:not(.mini-chart__bar--empty) { background: var(--gold-500); opacity: .75; }
.mini-chart[data-metric="staff_views"] .mini-chart__col:not(.is-selected) .mini-chart__bar:not(.mini-chart__bar--empty) { background: var(--muted); }

.chart-tools { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; margin-bottom: .25rem; }
.filter-tabs__btn {
  font: inherit; font-size: .85rem; padding: .3rem .7rem; border-radius: 999px;
  border: 1px solid var(--line); background: #fff; color: var(--ink); cursor: pointer;
}
.filter-tabs__btn:hover { border-color: var(--navy-600); }
.filter-tabs__btn[aria-pressed="true"] { background: var(--navy-800); border-color: var(--navy-800); color: #fff; }

.chart-tip {
  position: absolute; z-index: 3; pointer-events: none;
  background: var(--ink); color: #fff; font-size: .8rem; line-height: 1.35;
  padding: .45rem .65rem; border-radius: 4px; box-shadow: var(--shade-2);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.chart-tip strong { display: block; margin-bottom: .2rem; }
.chart-tip__current { font-weight: 700; }
/* The tooltip is positioned against the chart, so the chart's parent needs
   to be the frame. */
.panel__body { position: relative; }

/* Sortable headings are buttons that look like headings. */
.sort-btn {
  font: inherit; font-weight: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
  background: none; border: 0; padding: 0; cursor: pointer;
}
.sort-btn:hover { color: var(--navy-700); text-decoration: underline; }
th[aria-sort] .sort-btn::after { content: " ▾"; font-size: .75em; }
th[aria-sort="ascending"] .sort-btn::after { content: " ▴"; }

/* The two traffic tables must fit the main column at 1280 without a sideways
   scroll, which they did not: an address in code, a device, two dates and a
   count came to 692px in 596. Tighter cells and smaller type in the address. */
.visitors-table th, .visitors-table td, .journey th, .journey td { padding-left: .6rem; padding-right: .6rem; }
.visitors-table th:first-child, .visitors-table td:first-child, .journey th:first-child, .journey td:first-child { padding-left: 1.25rem; }
.visitors-table code { font-size: .85em; }
.visitors-table td[data-value] { white-space: nowrap; }
.data tr.is-selected > * { background: var(--paper-2); box-shadow: inset 3px 0 0 var(--gold-500); }
.nowrap { white-space: nowrap; }
.badge--muted { background: var(--paper-2); color: var(--ink-3); border: 1px solid var(--line); }
.badge--green { background: #e3f3ea; color: #1d6b42; }

/* A journey: half an hour of silence starts a new visit, marked with a rule
   above the row rather than a heading — the timeline stays one table. */
.journey__row--new-visit > * { border-top: 2px solid var(--navy-700); }
.journey__row--scanner td:nth-child(3) { color: var(--red-500); }

/* Thirty labels do not fit under thirty bars, and ninety zeros are not
   information. Past a fortnight the dates thin out and the figures go; the
   tooltip has both. */
.mini-chart--dense .mini-chart__n,
.mini-chart--sparse .mini-chart__n { visibility: hidden; }
.mini-chart--dense .mini-chart__col:hover .mini-chart__n,
.mini-chart--sparse .mini-chart__col:hover .mini-chart__n,
.mini-chart--dense .mini-chart__col.is-selected .mini-chart__n,
.mini-chart--sparse .mini-chart__col.is-selected .mini-chart__n { visibility: visible; }
.mini-chart--dense .mini-chart__col:not(:nth-child(5n+1)):not(.is-selected) .mini-chart__x,
.mini-chart--sparse .mini-chart__col:not(:nth-child(15n+1)):not(.is-selected) .mini-chart__x { visibility: hidden; }
.mini-chart--dense .mini-chart__col .mini-chart__x,
.mini-chart--sparse .mini-chart__col .mini-chart__x { overflow: visible; white-space: nowrap; }
/* With a day chosen, its date is the one that matters; the others make way. */
.mini-chart--dense:has(.is-selected) .mini-chart__col:not(.is-selected) .mini-chart__x,
.mini-chart--sparse:has(.is-selected) .mini-chart__col:not(.is-selected) .mini-chart__x { visibility: hidden; }

/* A panel that folds. <details> with the panel head as its <summary>: the
   browser's own control, so it works before the script and without it. The
   chevron is the only sign, and it is enough. */
.panel--fold > summary { list-style: none; cursor: pointer; user-select: none; }
.panel--fold > summary::-webkit-details-marker { display: none; }
.panel--fold > summary::after {
  content: ""; width: .5rem; height: .5rem; margin-left: .75rem; flex: 0 0 auto;
  border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
  transform: rotate(45deg); transition: transform .15s ease;
}
.panel--fold[open] > summary::after { transform: rotate(-135deg); }
.panel--fold:not([open]) > summary { border-bottom: 0; }
.panel--fold > summary:hover h2 { color: var(--navy-700); }
.panel--fold > summary:focus-visible { outline: 2px solid var(--navy-600); outline-offset: -2px; }
/* Some panel heads carry a heading and a count; the count sits before the chevron. */
.panel--fold > summary > .text-small { margin-left: auto; }

/* Controls that used to share the head, on a line of their own under it. */
.panel__toolbar { padding: .75rem 1.25rem; border-bottom: 1px solid var(--line); }

/* The line under a paged table. */
.pager { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; padding: .75rem 1.25rem; border-top: 1px solid var(--line); }
.pager__note { margin-right: auto; }

@media (max-width: 60rem) {
  /* Seven bars fit their dates on a phone; the rule that hides them is for
     the dashboard's twelve-month chart. Dense charts keep theirs hidden. */
  .mini-chart--live:not(.mini-chart--dense):not(.mini-chart--sparse) .mini-chart__x { display: block; }
}

@media (max-width: 44rem) {
  /* The visitors list as a compact card: address and kind on the first line,
     the rest inline beneath it. Five labelled fields per address made the
     list thirty screens long; two lines say the same. */
  /* The card is a flex column elsewhere, which blockifies its cells; here it
     is a wrapping row, so the cells sit inline after the full-width address. */
  .data--compact tbody tr { flex-direction: row; flex-wrap: wrap; align-items: baseline; column-gap: .75rem; row-gap: .15rem; padding: .65rem .9rem; margin-bottom: .5rem; }
  .data--compact tbody th[scope="row"] { flex: 0 0 100%; width: 100%; padding-bottom: .25rem; }
  .data--compact tbody td { display: inline; width: auto; padding: 0; font-size: var(--step--1); }
  .data--compact tbody td[data-label]:not([data-label=""]) { padding-left: 0; }
  .data--compact tbody td[data-label]:not([data-label=""])::before {
    position: static; display: inline; width: auto; padding: 0 .3rem 0 0; font-size: inherit;
    text-transform: none; letter-spacing: 0; font-weight: 400; content: attr(data-label) ":";
  }
  /* Scoped to the visitors list, not to the pattern. "First seen" is its third
     cell and last seen is the one that matters - but twelve other views borrow
     .data--compact, and on every one of them this was quietly deleting a
     different column on a phone: Amount off the subscriptions list, Method off
     the ledger, the bank's own reason off a customer's returned payments. The
     card was safe to share; this line never was. */
  .visitors-table tbody td:nth-of-type(3) { display: none; }
  .data--compact tbody td[data-label]:nth-of-type(1)::before,   /* the badge says what it is */
  .data--compact tbody td[data-label]:nth-of-type(2)::before { content: none; }
  .data--compact tbody td .text-small { display: inline; }
  .data--compact tbody td .text-small::before { content: " · "; }

  /* The rolodex uses the compact card, with one correction.
     That card gives its first line to a th[scope="row"]; this table's first
     cell is a td, so nothing claimed the first line and all seven cells flowed
     inline and wrapped - which is how "Bounced:" ended up beside "Last payment"
     with its amount on the line below.
     One rule, not a second card: the pattern was right, its assumption was not. */
  .data--rolodex tbody td:nth-of-type(1) {
    flex: 0 0 100%; width: 100%; padding-bottom: .2rem;
  }
  /* The markup writes its own " · " between the number, the email and the town.
     The inherited rule put one in front of the first as well. */
  .data--rolodex tbody td:nth-of-type(1) .text-small::before { content: none; }
  /* Level and standing sit together, not stacked: a badge and the word beside
     it are one fact about where this account stands. */
  .data--rolodex tbody td:nth-of-type(2) > div { display: inline; }
  .data--rolodex tbody td:nth-of-type(2) .text-small::before { content: " · "; }

  /* A row the script has hidden stays hidden: the card rule above says
     display:flex with more weight than the browser gives [hidden]. */
  .data tbody tr[hidden] { display: none; }

  /* A journey step as three lines: when and how long after; the page; the
     device and the answer. Labels go — the order says what each is. */
  .journey tbody tr { flex-direction: row; flex-wrap: wrap; align-items: baseline; column-gap: .5rem; row-gap: .1rem; padding: .55rem .9rem; margin-bottom: .45rem; }
  .journey tbody tr.journey__row--new-visit { border-top: 3px solid var(--navy-700); margin-top: .9rem; }
  .journey tbody td { display: inline; width: auto; padding: 0; font-size: var(--step--1); }
  .journey tbody td[data-label]:not([data-label=""]) { padding-left: 0; }
  .journey tbody td[data-label]:not([data-label=""])::before { content: none; }
  .journey tbody td:nth-of-type(1) { font-weight: 700; }
  .journey tbody td[data-label]:nth-of-type(2):not(:empty)::before { content: "· after "; position: static; display: inline; width: auto; font: inherit; text-transform: none; letter-spacing: 0; color: var(--muted); padding: 0; }
  .journey tbody td:has(.journey__first) { display: none; }   /* the first step is after nothing */
  .journey tbody td:nth-of-type(3) { flex: 0 0 100%; width: 100%; font-size: var(--step-0); }
  .journey tbody td:nth-of-type(3) .text-small { display: inline; margin-left: .35rem; }
  .journey tbody td:nth-of-type(4) { color: var(--muted); }
  .journey tbody td:nth-of-type(5) .badge { font-size: .7rem; }
}

/* ===========================================================================
   Payment Processing — a module, not a screen. Its own navigation, its own
   tables, and a statement that has to read like a bank's.
   =========================================================================== */
.pay-nav { display:flex; flex-wrap:wrap; gap:.25rem; margin:0 0 1.25rem;
  border-bottom:1px solid var(--admin-line, #e3e7ee); }
.pay-nav a { padding:.55rem .9rem; font-size:.9rem; text-decoration:none; color:inherit;
  border-bottom:2px solid transparent; margin-bottom:-1px; border-radius:6px 6px 0 0; }
.pay-nav a:hover { background:rgba(11,58,143,.05); }
.pay-nav a[aria-current="page"] { border-bottom-color:var(--navy,#0b3a8f); font-weight:600;
  color:var(--navy,#0b3a8f); }

.pay-alerts { display:grid; gap:.6rem; margin:0 0 1.1rem; }
.pay-alert { display:flex; align-items:center; gap:.6rem; flex-wrap:wrap;
  padding:.7rem .9rem; border-radius:8px; text-decoration:none; color:inherit;
  background:rgba(11,58,143,.06); border-left:3px solid var(--navy,#0b3a8f); font-size:.92rem; }
.pay-alert span { color:var(--muted,#6b7280); font-size:.85rem; margin-left:auto; }
.pay-alert--warn { background:rgba(195,18,42,.06); border-left-color:var(--red,#c3122a); }
.pay-alert:hover { filter:brightness(.98); }

.pay-filters { display:flex; flex-wrap:wrap; align-items:center; gap:.4rem;
  margin:0 0 1rem; padding:.55rem .7rem; background:var(--admin-panel,#fff);
  border:1px solid var(--admin-line,#e3e7ee); border-radius:9px; }
.pay-filters__label { font-size:.72rem; text-transform:uppercase; letter-spacing:.06em;
  color:var(--muted,#6b7280); margin-left:.4rem; }
.pay-filters__label:first-child { margin-left:0; }
.pay-filters input[type="date"], .pay-filters select { font:inherit; font-size:.85rem;
  padding:.3rem .45rem; border:1px solid var(--admin-line,#e3e7ee); border-radius:6px; }
.pay-chip { font-size:.8rem; padding:.28rem .6rem; border-radius:20px; text-decoration:none;
  color:inherit; border:1px solid var(--admin-line,#e3e7ee); }
.pay-chip:hover { background:rgba(0,0,0,.03); }
.pay-chip.is-on { background:var(--navy,#0b3a8f); border-color:var(--navy,#0b3a8f); color:#fff; }

.pay-head { display:flex; justify-content:space-between; align-items:flex-start;
  gap:1rem; flex-wrap:wrap; margin:0 0 1rem; }
.pay-head__name { margin:0; font-size:1.45rem; }
.pay-head__meta { margin:.25rem 0 0; color:var(--muted,#6b7280); font-size:.9rem; }
.pay-head__actions { display:flex; gap:.4rem; flex-wrap:wrap; }
/* Close and Reopen are posts, not links, so they arrive in the header wrapped in
   a form. The wrapper lays out as the button it holds, and nothing else. */
.pay-head__form { display:inline-flex; margin:0; }
.crumb { margin:0 0 .7rem; font-size:.85rem; }

.kpi-row--tight { margin-bottom:1rem; }
.kpi-row--tight .kpi { padding:.7rem .9rem; }
.kpi-row--tight .kpi__value { font-size:1.25rem; }

.pay-methods { list-style:none; margin:0; padding:0; display:grid; gap:.7rem; }
.pay-methods li { padding-bottom:.6rem; border-bottom:1px solid var(--admin-line,#e3e7ee); }
.pay-methods li:last-child { border-bottom:0; padding-bottom:0; }

.pay-audit { list-style:none; margin:0; padding:0; display:grid; gap:.65rem; font-size:.88rem; }
.pay-audit li { padding-bottom:.55rem; border-bottom:1px solid var(--admin-line,#e3e7ee); }
.pay-audit li:last-child { border-bottom:0; }

.kvs { display:grid; gap:.15rem .9rem; margin:0 0 1rem; }
.kvs .kv { display:grid; grid-template-columns:minmax(120px,34%) 1fr; gap:.9rem;
  padding:.25rem 0; border-bottom:1px dotted var(--admin-line,#e3e7ee); }
.kvs .kv:last-child { border-bottom:0; }
.kvs dt { color:var(--muted,#6b7280); font-size:.83rem; }
.kvs dd { margin:0; font-size:.9rem; word-break:break-word; }
.kvs dd.mono { font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:.85rem; }
.pay-record h4 { margin:1.1rem 0 .5rem; font-size:.74rem; text-transform:uppercase;
  letter-spacing:.08em; color:var(--navy,#0b3a8f); }
.pay-record h4:first-child { margin-top:0; }

.raw-json { border-top:1px solid var(--admin-line,#e3e7ee); padding:.7rem 1.25rem; }
.raw-json summary { cursor:pointer; font-size:.85rem; color:var(--navy,#0b3a8f); }
.raw-json pre { background:rgba(0,0,0,.035); padding:.8rem; border-radius:7px; overflow:auto;
  font-size:.75rem; max-height:420px; margin:.6rem 0 0; }

.pay-grid2 { display:grid; grid-template-columns:1fr 1fr; gap:.5rem; }

.badge--basic  { background:#eef2fb; color:#2f5bd0; }
.badge--elite  { background:#efe9fb; color:#5b34c0; }
.badge--silver { background:#eceff3; color:#4a5568; }
.badge--gold   { background:#fbf1dd; color:#8a6114; }
.badge--ok     { background:#e6f5ee; color:#0f7b4f; }
.badge--bad    { background:#fdecea; color:#b3261e; }
.badge--warn   { background:#fdf3e0; color:#96631a; }
.text-bad { color:#b3261e; }
tr.is-reversed td { background:rgba(195,18,42,.04); }

/* the statement and the ledger carry a lot of columns; let them scroll rather
   than squeeze, and become cards on a phone like the rest of the admin */
@media (max-width: 60rem) {
  .pay-head__actions { width:100%; }
  .pay-grid2 { grid-template-columns:1fr; }
}
@media (max-width: 44rem) {
  /* Wrap instead of a sideways strip that hides later tabs off-screen. */
  .pay-nav {
    overflow: visible;
    flex-wrap: wrap;
    gap: 0.2rem 0.15rem;
    border-bottom: 0;
  }
  .pay-nav a {
    white-space: normal;
    border: 1px solid var(--admin-line, #e3e7ee);
    border-radius: 999px;
    margin-bottom: 0;
    padding: 0.45rem 0.75rem;
    font-size: 0.82rem;
    line-height: 1.2;
  }
  .pay-nav a[aria-current="page"] {
    border-color: var(--navy, #0b3a8f);
    background: rgba(11, 58, 143, 0.08);
  }
  .kvs .kv { grid-template-columns:1fr; gap:0; }
  .kvs dt { font-size:.76rem; }
  .pay-filters { gap:.35rem; }
}

/* Forms inside the payment panels: a label above its field, not beside it. */
.pay-form label { display:block; margin:0 0 .7rem; font-size:.85rem; color:var(--muted,#6b7280); }
.pay-form input[type="text"], .pay-form input[type="email"], .pay-form input[type="date"],
.pay-form select, .pay-form textarea {
  display:block; width:100%; margin-top:.22rem; font:inherit; font-size:.9rem;
  padding:.42rem .55rem; border:1px solid var(--admin-line,#e3e7ee); border-radius:6px;
  background:#fff; color:inherit; }
.pay-form textarea { resize:vertical; }
.pay-form button { margin-top:.25rem; }
.pay-form .pay-grid2 label { margin-bottom:0; }

/* ===========================================================================
   Issues: a worklist. Each row states the problem, the reason the bank gave,
   and what can be done about it — nothing else.
   =========================================================================== */
.issues { list-style:none; margin:0; padding:0; }
.issue { display:flex; gap:1rem; justify-content:space-between; align-items:flex-start;
  padding:.9rem 1.25rem; border-bottom:1px solid var(--admin-line,#e3e7ee); flex-wrap:wrap; }
.issue:last-child { border-bottom:0; }
.issue--plan_bouncing { border-left:3px solid var(--red,#c3122a); }
.issue--reversal { border-left:3px solid #96631a; }
.issue__main { flex:1 1 22rem; min-width:0; }
.issue__head { display:flex; gap:.5rem; align-items:center; flex-wrap:wrap; margin-bottom:.2rem; }
.issue__detail { font-size:.92rem; }
.issue__reason { font-size:.88rem; color:var(--muted,#6b7280); font-style:italic; margin:.15rem 0; }
.issue__actions { display:flex; gap:.4rem; align-items:flex-start; flex-wrap:wrap; flex:0 0 auto; }
.issue__do { position:relative; }
.issue__do > summary { list-style:none; cursor:pointer; display:inline-block; }
.issue__do > summary::-webkit-details-marker { display:none; }
.issue__do[open] > summary { background:var(--navy,#0b3a8f); color:#fff; border-color:var(--navy,#0b3a8f); }
.issue__do > form { margin-top:.5rem; padding:.8rem; background:var(--admin-panel,#fff);
  border:1px solid var(--admin-line,#e3e7ee); border-radius:8px; min-width:16rem;
  box-shadow:0 6px 20px rgba(0,0,0,.07); }

.pay-check { display:flex; gap:.55rem; align-items:flex-start; margin:.4rem 0 .8rem; }
.pay-check input { width:auto; margin-top:.2rem; }
.pay-check span { font-size:.88rem; }
.pay-charge { border-left:3px solid var(--navy,#0b3a8f); padding-left:.9rem; }

@media (max-width: 44rem) {
  .issue { flex-direction:column; }
  .issue__actions { width:100%; }
  .issue__do > form { min-width:0; width:100%; }
}

/* ---------------------------------------------------------------------------
   Placing payments on accounts. One card per panel: what the payments say
   about who paid, where they sit now, and the choices — in that order, because
   that is the order the question gets answered in.
   --------------------------------------------------------------------------- */
.place__why { font-style: italic; color: var(--admin-muted, #6b7280); margin: 0 0 .9rem; }
.place__facts { margin: 0 0 1rem; }
.place__facts p { margin: 0 0 .3rem; font-size: .93rem; }
.place__label {
  display: inline-block; min-width: 11rem; font-size: .8rem; text-transform: uppercase;
  letter-spacing: .06em; color: var(--admin-muted, #6b7280);
}
.place__txns { margin: 0 0 1rem; }
.place__actions { display: flex; gap: .45rem; flex-wrap: wrap; align-items: flex-start; }
.place__actions .issue__do > form { min-width: 20rem; }
@media (max-width: 44rem) {
  .place__label { min-width: 0; display: block; }
  .place__actions { flex-direction: column; width: 100%; }
  .place__actions .issue__do, .place__actions .issue__do > form { width: 100%; min-width: 0; }
}

/* The waiting count on the "To place" tab. Reads as a quantity of work, not a
   decoration, so it is the colour of something that needs doing. */
.pay-nav__count {
  display: inline-block; margin-left: .4rem; padding: 0 .38rem;
  background: var(--red, #c3122a); color: #fff; border-radius: 999px;
  font-size: .72rem; font-weight: 600; line-height: 1.5; vertical-align: .1em;
  font-variant-numeric: tabular-nums;
}

/* Suggested payment accounts for a member login. Each one has to argue for
   itself — the reason it is being suggested matters more than the name. */
.link-cands { list-style: none; margin: .8rem 0 0; padding: 0; }
.link-cand {
  display: flex; gap: 1rem; justify-content: space-between; align-items: flex-start;
  padding: .75rem 0; border-bottom: 1px solid var(--admin-line, #e3e7ee);
}
.link-cand:last-child { border-bottom: 0; }
@media (max-width: 44rem) {
  .link-cand { flex-direction: column; }
  .link-cand .issue__do, .link-cand .issue__do > form { width: 100%; min-width: 0; }
}

/* Traffic: the cards open the rows behind their own number, so they have to
   look like something you can press. Same box as .kpi - only the affordance is
   added, because three cards that suddenly looked different would read as three
   different kinds of thing. */
.kpi--link { display: flex; flex-direction: column; text-decoration: none; color: inherit;
  transition: border-color .12s ease, box-shadow .12s ease, transform .12s ease; }
.kpi--link:hover, .kpi--link:focus-visible { border-color: var(--navy); box-shadow: 0 2px 10px rgb(0 0 0 / .09); transform: translateY(-1px); }
.kpi--link.is-on { border-color: var(--navy); }
.kpi__go { margin-top: auto; padding-top: .5rem; font-size: .78rem; font-weight: 600;
  color: var(--navy); display: flex; align-items: center; gap: .3rem; }
.kpi__go svg { width: .85em; height: .85em; fill: currentColor; }
.kpi--link:hover .kpi__go { text-decoration: underline; }
.kpi--gold .kpi__go { color: #8a6100; }

/* The marks beside an address: country, kind of machine, what it runs. Sized to
   the row's own text rather than a fixed pixel value, so they stay in proportion
   if the table's type ever changes. */
.ip-marks { display: inline-flex; align-items: center; gap: .3rem; margin-right: .45rem;
  vertical-align: -.15em; }
.ip-mark { display: inline-flex; align-items: center; justify-content: center;
  width: 1.15em; height: 1.15em; }
.ip-mark svg { width: 100%; height: 100%; fill: var(--muted, #61666f); }
.ip-mark--flag { font-size: 1.05em; line-height: 1; }
.ip-mark--none svg { opacity: .45; }

/* The communication journal's floating preview used to live here. It is gone:
   the journal uses the same in-place lookup as every other admin list, so the
   rows you are reading are the answer rather than something behind a panel. */

/* A sent message is a whole document with its own layout. It is framed rather
   than inlined so it cannot restyle the page around it. */
.comms-body { width: 100%; min-height: 30rem; border: 1px solid var(--line); border-radius: 6px;
  background: #fff; }

@media (max-width: 44rem) {
  /* The journal as a card, on its own terms.
     .data--compact's phone rules were written for the visitors list and are
     tuned to it - notably "td:nth-of-type(3) { display: none }", which drops
     that table's redundant "first seen". Borrowing the class borrowed that too,
     and the third cell here was the address the message went to: it simply was
     not on the phone. Reordering the columns would only have moved which one
     vanished. So this table states its own arrangement rather than inheriting a
     neighbour's.

     Who leads, on its own line, because the office scans for a carrier. The
     subject follows on its own line because it is the thing being looked for.
     The address, the date and the state sit inline beneath, labelled, because
     each answers a question somebody asks second. */
  .comms-table tbody td { display: block; width: 100%; }
  .comms-table tbody td:nth-of-type(3) { display: block; }   /* undo the visitors rule */

  .comms-table tbody td:nth-of-type(1) { font-size: var(--step-0); padding-bottom: .1rem; }
  .comms-table tbody td:nth-of-type(3) { padding-bottom: .35rem; }

  /* Who and Subject need no label - a name and a linked sentence say what they
     are. Everything after them does. */
  .comms-table tbody td[data-label]:nth-of-type(1)::before,
  .comms-table tbody td[data-label]:nth-of-type(3)::before { content: none; }
  .comms-table tbody td[data-label]:nth-of-type(4)::before,
  .comms-table tbody td[data-label]:nth-of-type(5)::before {
    content: attr(data-label) ": "; color: var(--muted, #61666f);
  }
  /* "When: 10 Sep 2026" - a date does not need telling that it is one. */
  .comms-table tbody td[data-label]:nth-of-type(2)::before { content: none; }
  .comms-table tbody td:nth-of-type(2) { color: var(--muted, #61666f); }

  /* The date and the state each take a line. They were meant to share one, and
     the rule that would have done it - display:inline on those two cells - was
     inert: .data--compact makes the row a flex container, and a flex item is
     blockified whatever its display says. Removed rather than left in place
     claiming to work. Six short lines read perfectly well. */

  /* The account number under the name, and the time under the date, are
     asides - they should not each claim a line of their own. */
  .comms-table tbody td .text-small { display: inline; }
  .comms-table tbody td:nth-of-type(1) .text-small::before { content: " · "; }

  .comms-table tbody td:last-child { padding-top: .5rem; }
}

/* The activity log's first level: what kind of thing happened. A row of cards
   rather than more chips, because these are a level above the chips beneath
   them and looking identical would say they were the same kind of choice. */
.act-groups { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0 0 1rem; }
.act-group { display: flex; flex-direction: column; gap: .1rem; min-width: 8.5rem;
  padding: .55rem .8rem; border: 1px solid var(--line); border-radius: 6px; background: #fff;
  text-decoration: none; color: inherit;
  transition: border-color .12s ease, box-shadow .12s ease; }
a.act-group:hover, a.act-group:focus-visible { border-color: var(--navy,#0b3a8f); box-shadow: 0 2px 8px rgb(0 0 0 / .07); }
/* var(--navy) with no fallback is what .pay-chip.is-on has always avoided, and
   why: the token does not exist - the palette defines --navy-900/800/700 - so
   the declaration is invalid, the background stays transparent, and the white
   text sits on white. The selected group was a ghost until this was noticed in
   a screenshot. */
.act-group.is-on { border-color: var(--navy,#0b3a8f); background: var(--navy,#0b3a8f); color: #fff; }
.act-group.is-on .act-group__n { color: rgb(255 255 255 / .75); }
.act-group__name { font-weight: 600; font-size: var(--step--1); }
.act-group__n { font-size: var(--step--1); color: var(--muted, #61666f); font-variant-numeric: tabular-nums; }
/* Nothing in it: still offered, because "none this month" is an answer, but it
   should not compete with the groups that do have something. */
.act-group.is-empty { opacity: .55; }
.pay-chip.is-empty { opacity: .55; }

@media (max-width: 44rem) {
  .act-groups { display: grid; grid-template-columns: 1fr 1fr; gap: .4rem; }
  .act-group { min-width: 0; }
}

@media (max-width: 44rem) {
  /* A link in a card is something a thumb has to hit.
     Below 44rem an admin table becomes a list of records, and the only way into
     a record is usually a link in one of its cells. Those links were rendering
     15px tall — the height of the text and nothing else — across every admin
     list: activity, communications, member accounts, payments, news. Not a
     regression, just never noticed, because layout-sweep.mjs measures buttons,
     summaries, .btn and form controls, and a plain <a> is none of those.
     A card is generous with space; the target inside it should be too. */
  .data tbody td a,
  .data tbody th a,
  .table tbody td a,
  .table tbody th a {
    display: inline-block;
    padding-block: .34rem;
  }
  /* The first link in a cell is usually the way in — the subject, the name, the
     action. It gets the width of the cell as well, so the tap does not have to
     find the word. */
  .data tbody td > a:first-child,
  .data tbody th > a:first-child,
  .table tbody td > a:first-child,
  .table tbody th > a:first-child { display: block; }

  /* Except where several sit on one line as a row of choices - "Read · Print" -
     which would otherwise stack into a column of full-width bars. */
  .data tbody td.nowrap a,
  .table tbody td.nowrap a { display: inline-block; }

  /* And except a figure that happens to link. The way into a customer is the
     name at the top of the card; the bounced amount is a value with a label in
     front of it, and making it a full-width bar put "Bounced:" on one line and
     its own figure on the next. */
  .data--rolodex tbody td > a.text-bad:first-child { display: inline-block; }
}

/* A record that opens. The pointer is the only hint on a desktop; on a phone
   the card is the target and there is no hover to hint with, which is why the
   row carries a real link as well. */
.data tbody tr[data-open] { cursor: pointer; }
@media (hover: hover) {
  .data tbody tr[data-open]:hover { background: #f6f7f9; }
}
@media (max-width: 44rem) {
  .data tbody tr[data-open]:active { background: #f1f3f5; }
}

/* ------------------------------------------------------------ confirm dialog --
   The site's own "are you sure?", replacing the browser's grey box (10 September).
   Built by askFirst() in admin.js from any form or button with data-confirm;
   data-confirm-tone="danger" for deletes, data-confirm-yes for the button's words. */
.confirm-dialog {
  border: 0;
  border-top: 4px solid var(--blue-deep);
  border-radius: var(--radius);
  padding: 1.35rem 1.5rem 1.15rem;
  width: 100%;
  max-width: min(30rem, calc(100vw - 2rem));
  background: var(--white);
  color: var(--ink);
  font-family: var(--sans);
  box-shadow: 0 18px 48px rgba(19, 21, 25, .28);
}
.confirm-dialog--danger { border-top-color: var(--red); }
.confirm-dialog::backdrop { background: rgba(19, 21, 25, .45); }
.confirm-dialog__title { margin: 0 0 .35rem; font-weight: 700; font-size: 1.05rem; }
.confirm-dialog__msg { margin: 0 0 1.25rem; line-height: 1.5; }
.confirm-dialog__actions { display: flex; justify-content: flex-end; gap: .5rem; flex-wrap: wrap; }
/* The question that needs an answer typed, not just clicked: closing an account
   asks why in the same box that asks whether. */
.confirm-dialog__ask { display: block; margin: -.5rem 0 1.15rem; font-size: .875rem; font-weight: 600; }
.confirm-dialog__ask input {
  display: block;
  width: 100%;
  margin-top: .3rem;
  padding: .5rem .6rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm, 4px);
  font: inherit;
  font-weight: 400;
  color: var(--ink);
  background: var(--white);
}
.confirm-dialog__ask input:focus { outline: 2px solid var(--blue-deep); outline-offset: 1px; }
.confirm-dialog__ask input.is-empty { border-color: var(--red); background: #fdf2f3; }
@media (prefers-reduced-motion: no-preference) {
  .confirm-dialog[open] { animation: confirm-in .14s ease-out; }
  .confirm-dialog[open]::backdrop { animation: confirm-fade .14s ease-out; }
}
@keyframes confirm-in { from { opacity: 0; transform: translateY(6px) scale(.98); } to { opacity: 1; transform: none; } }
@keyframes confirm-fade { from { opacity: 0; } to { opacity: 1; } }

/* An icon-only button - the trash can on an invoice row. */
.btn--icon { display: inline-flex; align-items: center; justify-content: center; padding-inline: .5rem; }
.btn--icon svg { width: 1.05em; height: 1.05em; }

/* Several pictures on an event or a news post. */
.image-picker {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
  gap: 0.85rem;
  margin-bottom: 1rem;
}
.image-picker__item {
  margin: 0;
  padding: 0.5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--white);
}
.image-picker__item a { display: block; cursor: zoom-in; }
.image-picker__item img {
  display: block;
  width: 100%;
  height: auto;
  margin-bottom: 0.5rem;
}
.image-picker__item .checkbox { margin-top: 0.25rem; }

/* FleetCare PRO link panel — search above a normal .data table. */
.fc-link { margin-top: 1.25rem; }
.fc-link__search { max-width: 28rem; margin: 0 0 .75rem; }
.fc-link__search .field { margin-bottom: 0; }
.fc-link__status { margin: 0 0 .75rem; min-height: 1.25em; }
.fc-link__table { margin-top: .25rem; }
.fc-link__table .data { font-size: var(--step--1); }


/* === portal-mobile-20260921 === */
/* Keep sign-in toasts clear of the sticky admin title + Menu control. */
body.admin .toast-stack {
  top: 4.75rem;
}

/* === live-list-20260921 === */
/* Lookup-as-you-type on the accounts list. The results fade rather than empty
   while a search is in flight: a list that blanks between keystrokes reads as
   "no matches" for a moment, which is the one thing it must never say by
   accident. Held at .55 so the rows underneath stay readable — somebody
   refining a search is usually still looking at the last answer. */
[data-live-list] { transition: opacity .12s ease-out; }
[data-live-list].is-searching { opacity: .55; }

@media (prefers-reduced-motion: reduce) {
  [data-live-list] { transition: none; }
}


/* === ledger-mobile-20260921 === */

/* Inline Method under the description on phone cards ??? the Method <td> was
   disappearing in the grid layout even when present in the HTML. */
.ledger__method-inline { display: none; }
.ledger__method-inline__label {
  display: inline-block;
  min-width: 6.25rem;
  margin-right: 0.5rem;
  color: var(--muted);
  font-size: var(--step--2);
  line-height: 1.5;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 700;
}
@media (max-width: 44rem) {
  .ledger__method-inline {
    display: block;
    margin-top: 0.35rem;
    padding-left: 0;
  }
  .data.ledger tbody td.ledger__method {
    display: none !important;
  }
}

/* Desktop: keep long ledger descriptions from blowing out the table. */
.ledger__desc {
  overflow-wrap: anywhere;
  max-width: 28rem;
}
@media (max-width: 44rem) {
  /* Organised ledger cards: same order on every row.
     Date + amount on one line, then id/description, then labelled facts. */
  .data.ledger tbody tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: 0.75rem;
    row-gap: 0.35rem;
    align-items: start;
  }
  .data.ledger tbody td {
    padding: 0;
  }
  /* Date */
  .data.ledger tbody td:nth-child(1) {
    grid-column: 1;
    grid-row: 1;
    padding-left: 0 !important;
  }
  .data.ledger tbody td:nth-child(1)::before { display: none !important; content: none !important; }
  .data.ledger tbody td:nth-child(1) .text-muted {
    display: inline;
    margin-left: 0.35rem;
  }
  /* Third time .data--compact has leaked a rule into a table it was not
     written for. Its ".text-small::before { content: ' · ' }" joins two facts
     on one line in the visitors list; in a ledger card each .text-small starts
     its OWN line, so the separator had nothing to separate and every card
     opened with a stray bullet - "· Tuda Freight LLC", "· METHOD ACH - 2178".
     (The other two leaks: the date hidden on the dashboard panel, and the same
     bullet on the comms table, both dealt with where they were found.) */
  .data.ledger tbody td .text-small::before { content: none; }

  /* Method reads as a label like the ones under it, not as a heading. The
     others are "Category:", "Account:", "Status:" in sentence case; this one
     was shouting in small caps because it comes from a different rule. */
  .ledger__method-inline__label {
    text-transform: none; letter-spacing: 0; font-weight: 400;
    color: var(--muted, #61666f);
  }
  .ledger__method-inline__label::after { content: ":"; }

  /* Amount — always top-right, never a labelled middle field */
  .data.ledger tbody td.ledger__amount,
  .data.ledger tbody td:nth-child(4) {
    grid-column: 2;
    grid-row: 1;
    padding-left: 0 !important;
    text-align: right;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    font-size: 1.05rem;
    line-height: 1.2;
  }
  .data.ledger tbody td.ledger__amount::before,
  .data.ledger tbody td:nth-child(4)::before { display: none !important; content: none !important; }
  /* Transaction id + description */
  .data.ledger tbody td:nth-child(2) {
    grid-column: 1 / -1;
    grid-row: 2;
    padding-left: 0 !important;
  }
  .data.ledger tbody td:nth-child(2)::before { display: none !important; content: none !important; }
  .data.ledger .ledger__desc {
    max-width: none;
    margin-top: 0.2rem;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  /* Method / Category / Account / Status — explicit rows so Method is not
     auto-placed onto the date/amount line (grid source order put it there). */
  .data.ledger tbody td.ledger__method,
  .data.ledger tbody td:nth-child(3) {
    grid-column: 1 / -1;
    grid-row: 3;
    padding-left: 6.5rem !important;
  }
  .data.ledger tbody td:nth-child(5) {
    grid-column: 1 / -1;
    grid-row: 4;
    padding-left: 6.5rem !important;
  }
  .data.ledger tbody td:nth-child(6) {
    grid-column: 1 / -1;
    grid-row: 5;
    padding-left: 6.5rem !important;
  }
  .data.ledger tbody td:nth-child(7) {
    grid-column: 1 / -1;
    grid-row: 6;
    padding-left: 6.5rem !important;
  }
  .data.ledger tbody td.ledger__method::before,
  .data.ledger tbody td:nth-child(3)::before,
  .data.ledger tbody td:nth-child(5)::before,
  .data.ledger tbody td:nth-child(6)::before,
  .data.ledger tbody td:nth-child(7)::before {
    width: 6.25rem;
  }
  .data.ledger tbody td.ledger__account a {
    display: inline;
    overflow-wrap: anywhere;
  }
  .data.ledger tbody td:nth-child(7) .badge {
    vertical-align: middle;
  }
}

/* The partner directory's logo cell. Injected per-page from the view until
   now, which stopped working when the rows moved into a partial for
   lookup-as-you-type: the style went with the page and the classes went with
   the rows. A rule here is where it should have been. */
.logo-cell {
  width: 4.5rem;
  height: 2.5rem;
  object-fit: contain;
  background: var(--bg-soft);
  border-radius: var(--radius-sm);
}
.logo-cell--empty {
  display: grid;
  place-items: center;
  font-size: .7rem;
  font-weight: 700;
  color: var(--muted);
}

/* A sponsorship account, said on the account itself. Amber rather than one of
   the membership colours: it is deliberately not a membership tier. */
.badge--sponsor { background: #f4e6c8; color: #6b4a12; border-color: #e0cb9e; }

/* ---------------------------------------------------------------------------
   The account story — every recorded fact about one account, in order.

   A rail down the left with a marker per event. The marker's colour is never
   the only signal: each line says in words what it is, because a page used to
   defend a debit has to survive being printed in black and white.
   --------------------------------------------------------------------------- */
.story { list-style: none; margin: 0; padding: 0; }

.story__row {
  display: grid;
  grid-template-columns: 7.5rem 1fr;
  gap: 0 1rem;
  padding: .7rem 0 .7rem 0;
  border-top: 1px solid #ecebe7;
}
.story__row:first-child { border-top: 0; }

.story__when {
  display: flex; flex-direction: column;
  font-size: .8rem; line-height: 1.35; color: #6b6a66;
  position: relative; padding-left: 1rem;
}
/* The marker sits on the date, not on the text, so the eye can run down the
   column and find the bad days without reading a word. */
.story__when::before {
  content: ""; position: absolute; left: 0; top: .35rem;
  width: .5rem; height: .5rem; border-radius: 50%;
  background: #c9c7c1;
}
.story__clock { color: #9a9892; font-variant-numeric: tabular-nums; }

/* The right-hand column. min-width:0 so a long transaction id wraps inside the
   grid cell instead of widening the track and pushing the date column out. */
.story__what { min-width: 0; }

.story__title { margin: 0; font-size: .92rem; line-height: 1.4; }
.story__actor { color: #8a8884; font-size: .78rem; margin-left: .45rem; white-space: nowrap; }
.story__detail { margin: .2rem 0 0; font-size: .85rem; line-height: 1.5; color: #55534f; }

.story__facts { margin: .35rem 0 0; display: flex; flex-wrap: wrap; gap: .3rem; }
.story__facts span {
  font-size: .74rem; line-height: 1.6; color: #55534f;
  background: #f4f3f0; border: 1px solid #e7e5e0; border-radius: 3px;
  padding: 0 .4rem; font-variant-numeric: tabular-nums;
}

.story__row--ok   .story__when::before { background: #0f7b4f; }
.story__row--warn .story__when::before { background: #b97309; }
.story__row--bad  .story__when::before { background: #b3261e; }
.story__row--bad  .story__title { font-weight: 600; color: #b3261e; }
.story__row--bad  .story__detail { color: #7a2019; }
.story__row--muted .story__title,
.story__row--muted .story__detail { color: #8a8884; }

/* On a phone the date stops being a column and becomes a first line, because
   7.5rem of gutter costs more than the alignment is worth at that width. */
@media (max-width: 40rem) {
  .story__row { grid-template-columns: 1fr; gap: .15rem; }
  .story__when { flex-direction: row; gap: .4rem; align-items: baseline; }
}

/* The per-plan payment list, folded away under its own row. A plan is what a
   bank quotes at us, so the payments have to be readable by plan and not only
   by date — but only when somebody asks for them. */
.plan-detail > td { background: #fafaf8; padding: .5rem .75rem; }
.plan-detail summary { cursor: pointer; color: #0b3a8f; }
.plan-detail .table-wrap { margin-top: .5rem; background: #fff; }
