/* Self-hosted Inter and Fraunces were removed: the guidelines mandate
   Helvetica Neue with Arial as the substitute, both system faces. Dropping them
   also removes seven render-blocking font downloads from every first visit,
   which matters most on exactly the connections that could least afford them.
   The .woff2 files are still on disk and are now unreferenced. */
/* ============================================================
   Abia State Government Design System — first-party, self-contained (CSP-safe).
   One system, four channel skins via [data-channel] accent tokens.
   Self-hosted fonts (SIL OFL): Inter (text) + Fraunces (display).
   ============================================================ */

/* ── Self-hosted webfonts (CSP-safe: font-src 'self') ──────── */

:root {
  /* Brand — Abia Red.
     These names are kept because roughly 150KB of rules below reference them;
     only their VALUES move. Every one now resolves through brand.css, so this
     stylesheet and the MDA console cannot drift a shade apart from each other
     or from the state's guidelines. Change a colour there, not here. */
  --brand-950: var(--abia-red-950);
  --brand-900: var(--abia-red-900);
  --brand-800: var(--abia-red-800);
  --brand-700: var(--abia-red-700);   /* Abia Red #AA182C — MANDATED */
  --brand-600: var(--abia-red-600);
  --brand-500: var(--abia-red-500);
  --brand-400: var(--abia-red-400);
  --brand-100: var(--abia-red-100);
  --brand-50:  var(--abia-red-50);

  /* Neutrals — re-anchored from slate blue onto Abia Cool Grey. */
  --ink-900: var(--abia-neutral-950);
  --ink-700: var(--abia-neutral-700);
  --ink-500: var(--abia-neutral-600);
  --ink-400: var(--abia-neutral-500);
  --ink-300: var(--abia-neutral-300);
  --ink-200: var(--abia-neutral-200);
  --ink-100: var(--abia-neutral-100);
  --ink-50:  var(--abia-neutral-50);

  /* Status. Deliberately NOT red — see the reasoning in brand.css. With the
     brand itself red, an error rendered in red is indistinguishable from the
     furniture, and a destructive button from a primary one beside it. */
  --ok-600: var(--success-fg);   --ok-100: var(--success-surface);
  --warn-600: var(--warning-fg); --warn-100: var(--warning-surface);
  --bad-600: var(--danger-fg);   --bad-100: var(--danger-surface);
  --info-600: var(--info-fg);    --info-100: var(--info-surface);

  /* Channel accent (defaults to brand; overridden per channel) */
  --accent-700: var(--brand-700);
  --accent-600: var(--brand-600);
  --accent-100: var(--brand-100);
  --accent-50:  var(--brand-50);

  /* Elevation — FOUR RUNGS, each meaning exactly one thing, and there is never
     a fifth. A fifth rung is how this file ended up with six hover dialects and
     a bespoke `0 22px 46px` literal on the homepage's quick tools.

       flat  (none + 1px border) an in-column PANEL. Information, not an object.
       rest  --shadow-1          a card sitting in a grid.
       raised--shadow-2          a card that laps another surface at rest, and
                                 the hover of every rest card.
       overlay --shadow-3        floats above the page: a dropdown, the chat.

     Each token is a two-layer contact+ambient pair. The old --shadow-1 was two
     near-identical 1-3px drops, which renders as a muddy grey outline rather
     than a lift; separating the contact shadow from the ambient one is what
     makes a 1px difference read as height. The alphas stay low because the page
     ground is already a light grey — a heavy shadow on --ink-50 reads as dirt.
     No component may write a box-shadow literal; it references a rung. */
  --shadow-1: 0 1px 2px rgb(26 22 23 / .05), 0 2px 6px  rgb(26 22 23 / .05);
  --shadow-2: 0 2px 4px rgb(26 22 23 / .05), 0 8px 20px rgb(26 22 23 / .07);
  --shadow-3: 0 4px 8px rgb(26 22 23 / .05), 0 20px 48px rgb(26 22 23 / .12);
  --r-sm: 8px; --r-md: 12px; --r-lg: 16px; --r-xl: 24px; --r-2xl: 32px; --r-pill: 999px;

  /* ── The card system ────────────────────────────────────────────────────
     Tokens only; the rules are in the CARD SYSTEM section at the foot of this
     file, which is the single place a card's appearance is decided.

     TYPE. Thirteen card families were using NINE title sizes spanning
     15.68px-17.92px — a 2.24px spread, so the ramp encoded nothing at all. Two
     sizes replace all nine, and every step lands on a whole pixel at the 16px
     root. Title:body is 17:14 = 1.21, which is the smallest ratio that still
     reads as a hierarchy at a glance on a 390px screen.

     CONTRAST FLOOR, and it is not negotiable: the eyebrow and the meta row are
     --ink-500 (6.74:1 on white), never --ink-400. --ink-400 resolves to
     neutral-500 at 4.46:1, which brand.css itself labels "large text and UI
     components only" — and five card rules were spending it on 11-12.8px text. */
  --card-eyebrow:  .6875rem;   /* 11px  — uppercase, .08em, 800 */
  --card-title:    1.0625rem;  /* 17px  — every card in a grid */
  --card-title-lg: 1.1875rem;  /* 19px  — action cards and panel headings ONLY */
  --card-body:     .875rem;    /* 14px */
  --card-note:     .8125rem;   /* 13px  — callout, disclosure */
  --card-meta:     .75rem;     /* 12px */
  --card-action:   .8125rem;   /* 13px  — the footer action */

  /* Surface, border, radius, spacing. One padding value per breakpoint applied
     to every family, replacing six hand-typed values (16/18/20/22/24/26px)
     spread across fifteen rules. The rest of the ladder is in the 1024 and 640
     blocks of the card section. */
  --card-bg:       #fff;              /* NEVER --ink-50: that is the page */
  --card-bd:       var(--ink-200);
  --card-bd-hover: var(--abia-red-200);
  --card-r:        var(--r-lg);       /* 16px */
  --card-r-row:    var(--r-md);       /* 12px — compact row */
  --card-pad:      24px;
  --card-pad-row:  16px 18px;
  --card-gap:      12px;
  --card-lift:     -2px;

  /* The icon chip. Eighteen hand-written chips at eleven sizes and nine radii
     collapse to two sizes; the glyph is always half the chip. */
  --ic-md: 48px; --ic-sm: 40px;

  /* Text on a flat dark-red surface. --brand-400 was doing this job and
     measures 2.04:1 on brand-700 and 4.45:1 on brand-950 — it is a GRAPHIC
     mark (3:1), not a text colour. red-200 clears 4.5:1 on every red the site
     paints: 4.67:1 on brand-700, 6.11:1 on brand-800, 10.18:1 on brand-950.
     Over PHOTOGRAPHY no fixed colour can be guaranteed, so there the answer is
     white at 92% over a strengthened scrim, not a palette step. */
  --accent-on-dark: var(--abia-red-200);
  /* MANDATED: Helvetica Neue, with Arial as the sanctioned substitute. Inter
     and Fraunces were neither, and a display serif is not in the guidelines at
     all — the type system there is one sans family with weight and size doing
     the hierarchy work. --font-display is kept as a name so existing rules
     resolve, but it now points at the same mandated stack. */
  --font: var(--abia-font);
  --font-display: var(--abia-font);
  --mono: 'Cascadia Code', 'SF Mono', Consolas, monospace;
  --ease: cubic-bezier(.22, .8, .36, 1);

  /* Abia Gold. Note these are DELIBERATELY not the raw #FFBF3C at 500/600:
     white text on Abia Gold measures 1.64:1 and is unreadable, so anything that
     might carry white text resolves to a darkened step. Tints keep the bright
     mandated hue where only dark text sits on them. */
  --gold-600: var(--abia-gold-700); --gold-500: var(--abia-gold-600);
  --gold-100: var(--abia-gold-100); --gold-50:  var(--abia-gold-50);

  /* Spacing scale */
  --space-1:4px; --space-2:8px; --space-3:12px; --space-4:16px; --space-5:24px;
  --space-6:32px; --space-8:48px; --space-10:64px; --space-12:96px; --space-16:128px;

  /* Fluid type scale */
  --fs-display: clamp(2.6rem, 3.4vw + 1rem, 4.4rem);
  --fs-h1: clamp(2rem, 2.2vw + 1rem, 3rem);
  --fs-h2: clamp(1.7rem, 1.6vw + 1rem, 2.6rem);
  --fs-h3: 1.25rem;
  --fs-lead: clamp(1.05rem, .5vw + 1rem, 1.25rem);

  /* Wider tier for full-bleed section inner content */
  --page-max: 1240px;

  /* Header height.
     Set BY the logo rather than the logo being squeezed into it. A compliant
     lockup is the emblem (57px minimum, mandated) plus three cap-A widths of
     clear space above and below (25.2px each, also mandated) — 107.4px, which
     is why a 64px bar could never hold one without crowding it. Government
     identities generally do carry a tall masthead for exactly this reason.
     Every offset that used to hard-code 64px now reads this. */
  --topbar-h: 112px;
}

/* Mobile: the wordmark-only lockup needs 85.3px, so 96px carries it with room. */
@media (max-width: 640px) {
  :root { --topbar-h: 96px; }
}

/* Channel and domain accents.
   These used to be six unrelated hues — green, indigo, amber, cyan, rose,
   orange — which is exactly what the guidelines forbid: "Remember to use only
   one of the three colour palettes—never mix!" A citizen moving between the
   taxpayer portal and an agent screen was seeing what looked like two different
   organisations.

   They now resolve to the one palette. Where a surface genuinely benefits from
   being told apart, the distinction is carried by gold against red rather than
   by importing a hue the state does not use — and, more durably, by the icons
   and labels the information design already provides. Colour was never doing
   that work alone, which is also why removing it costs nothing in usability. */
[data-channel="individual"],
[data-channel="corporate"],
[data-channel="mda"],
[data-domain="individuals"],
[data-domain="business"],
[data-domain="property"],
[data-domain="health"],
[data-domain="mobility"] {
  --accent-700: var(--abia-red-800); --accent-600: var(--abia-red-700);
  --accent-100: var(--abia-red-100); --accent-50: var(--abia-red-50);
}

/* Agent surfaces take the gold accent — the guidelines' own ~5% emphasis
   colour — so the most operational surface reads as distinct without leaving
   the palette. Note the darkened steps: white text on Abia Gold measures
   1.64:1 and is unreadable.

   SCOPE. This is FieldOps' identity: layouts/agent.php puts data-channel on
   <body>, and the whole app is gold. It is NOT a card treatment. On the public
   site the same attributes sat on individual tiles, which is why "Your account"
   was the one quick tool with a cream chip and an olive-brown CTA and
   "Agents & Coordinators" the one gold card on /services and /about — one
   card in a row of five wearing a different palette for no reason a citizen
   could read. The CARD SYSTEM section re-points every public card family at
   the red block, so a card is one red whatever attribute its view carries. */
[data-channel="agent"],
[data-domain="revenue"] {
  --accent-700: var(--abia-gold-800); --accent-600: var(--abia-gold-700);
  --accent-100: var(--abia-gold-100); --accent-50: var(--abia-gold-50);
}
[data-domain="permits"],
[data-domain="community"] {
  --accent-700: var(--abia-red-800); --accent-600: var(--abia-red-700);
  --accent-100: var(--abia-red-100); --accent-50: var(--abia-red-50);
}

/* SVG icon base — inherits text color, one line language across the site. */
.svg-icon { display: inline-block; vertical-align: middle; flex: none; }

/* ── Reset & base ─────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; overflow-x: hidden; }
body {
  margin: 0; font-family: var(--font); font-size: 15.5px; line-height: 1.55;
  color: var(--ink-900); background: var(--ink-50);
  overflow-x: hidden; max-width: 100%;
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
}
img, svg { max-width: 100%; vertical-align: middle; }
a { color: var(--accent-700); text-decoration: none; }
a:hover { text-decoration: underline; }
h1, h2, h3, h4 { margin: 0 0 .5rem; line-height: 1.2; letter-spacing: -.015em; }
h1 { font-size: 1.75rem; font-weight: 800; }
h2 { font-size: 1.35rem; font-weight: 700; }
h3 { font-size: 1.1rem;  font-weight: 700; }
/* Display face for hero + section titles (opt-in via .u-display or components). */
.u-display { font-family: var(--font-display); font-weight: 700; letter-spacing: -.02em; }
p  { margin: 0 0 1rem; }
/* The focus ring. Two defects fixed at once, both measured.

   ONE: the ring was the accent at 45% ALPHA, which composites to about 2.3:1
   against every surface it lands on — under the 3:1 WCAG 2.2 floor, on every
   page, for every control. Meanwhile contrast.php asserts a SOLID --abia-red-700
   ring at 7.30:1, so the gate was green for a colour this sheet never painted.
   Solid red-700 makes that assertion true.

   TWO: `border-radius: 4px` is gone. It rewrote the focused element's OWN
   radius and survived only by luck of source order; it already bit .sitem__go,
   the block <a> wrapping every service card, giving keyboard users a
   near-square ring across a 16px rounded corner. An element that wants a
   softened ring sets the radius on itself.

   The white halo is what lets ONE token work on every light surface without
   per-surface tuning: the ring lands on white cards, on the red-50 chip tint
   and on the grey page, and the halo separates it from all three. Dark and
   photographic surfaces invert it below — white ring, red-950 halo — because a
   focus indicator must be unambiguous, and gold on a gold-ish sky is not. */
:focus-visible {
  outline: 3px solid var(--abia-red-700);
  outline-offset: 2px;
  box-shadow: 0 0 0 5px var(--abia-white);
}
.phero :focus-visible, .page-hero :focus-visible, .pbanner :focus-visible,
.pcta :focus-visible, .pband :focus-visible, .ptrust :focus-visible,
.psection--dark :focus-visible, .site-footer :focus-visible,
.svc-cta :focus-visible, .meganav__feature:focus-visible {
  outline-color: var(--abia-white);
  box-shadow: 0 0 0 5px var(--abia-red-950);
}

.container { max-width: 1160px; margin: 0 auto; padding: 0 20px; }
.muted { color: var(--ink-500); }
.small { font-size: .85rem; }
.mono  { font-family: var(--mono); font-size: .92em; }
.text-right { text-align: right; }
.nowrap { white-space: nowrap; }

/* ── Top bar ──────────────────────────────────────────────── */
.topbar {
  position: sticky; top: 0; z-index: 50;
  background: rgb(255 255 255 / .92); backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--ink-200);
}
.topbar-inner { display: flex; align-items: center; gap: 16px; height: var(--topbar-h); }
.topbar .brand { display: flex; align-items: center; gap: 10px; font-weight: 800; font-size: 1.1rem; color: var(--ink-900); }
.brand-mark {
  width: 36px; height: 36px; border-radius: 10px; display: grid; place-items: center;
  background: linear-gradient(135deg, var(--brand-700), var(--brand-500));
  color: #fff; font-weight: 800; font-size: .95rem; letter-spacing: -.03em;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .25), var(--shadow-1);
}
.topbar nav { display: flex; gap: 4px; margin-left: auto; align-items: center; }
.topbar nav a {
  padding: 8px 14px; border-radius: 999px; color: var(--ink-700); font-weight: 600; font-size: .92rem;
}
.topbar nav a:hover { background: var(--ink-100); text-decoration: none; }
.topbar nav a.active { background: var(--accent-50); color: var(--accent-700); }
/* `.topbar nav a` (0,1,2) outranks `.btn-primary` (0,1,0): the header Create account
   button rendered grey #414042 on the red gradient (about 2:1) and greyed on hover. */
.topbar nav a.btn-primary { color: #fff; }
.topbar nav a.btn-primary:hover { background: linear-gradient(180deg, var(--accent-600), var(--accent-700)); color: #fff; }

/* CSS-only mobile nav disclosure (hamburger). Hidden on desktop. */
.nav-toggle { display: none; }
.nav-toggle-btn {
  display: none; margin-left: auto; width: 42px; height: 42px; border: 1px solid var(--ink-200);
  border-radius: 10px; background: #fff; cursor: pointer; align-items: center; justify-content: center;
}
.nav-toggle-btn span, .nav-toggle-btn span::before, .nav-toggle-btn span::after {
  content: ""; display: block; width: 20px; height: 2px; background: var(--ink-700); position: relative;
}
.nav-toggle-btn span::before { position: absolute; top: -6px; }
.nav-toggle-btn span::after { position: absolute; top: 6px; }

/* Accessibility: skip-to-content link + screen-reader-only utility. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: 8px; top: -48px; z-index: 100; background: var(--accent-700); color: #fff;
  padding: 10px 14px; border-radius: 8px; font-weight: 700; transition: top .15s;
}
.skip-link:focus { top: 8px; text-decoration: none; }

/* Layout/spacing utilities — reduce inline-style drift. */
.w-full { width: 100%; }
.text-center { text-align: center; }
.stack { display: grid; gap: 12px; }
.btn-block { width: 100%; }

/* Busy button: on submit the button is disabled and shows an inline spinner so
   a slow round-trip (gateway account issue, OTP send) never looks unresponsive
   or gets double-submitted. Applied globally by a small delegated script. */
.is-busy { position: relative; pointer-events: none; opacity: .85; }
.is-busy > * { visibility: hidden; }
.is-busy::after {
  content: ""; position: absolute; inset: 0; margin: auto; width: 18px; height: 18px;
  border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%;
  animation: spin .6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .is-busy::after { animation: none; } }

/* ── Buttons ──────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 10px 18px; border-radius: var(--r-sm); border: 1px solid transparent;
  font: 600 .95rem var(--font); cursor: pointer; text-decoration: none;
  transition: transform .12s var(--ease), box-shadow .12s var(--ease), background .12s var(--ease);
  white-space: nowrap;
}
.btn:hover { text-decoration: none; }
.btn:active { transform: translateY(1px); }
/* A <summary> styled as a button is the script-free confirm step on portal
   pages (security "Turn off…", identity and photo "Withdraw…", family
   "Remove"): it opens the consequence and the real submit. Inline list-style
   is not enough. Older Safari ignores list-style on summary and draws its
   disclosure triangle inside the button unless the marker is removed by its
   prefixed name too, and a view that forgets the inline style (fines "Contest
   this fine") would get the triangle everywhere. Same rule as mda.css. */
summary.btn { list-style: none; }
summary.btn::-webkit-details-marker { display: none; }
summary.btn::marker { content: ""; }
.btn-primary { background: linear-gradient(180deg, var(--accent-600), var(--accent-700)); color: #fff; box-shadow: var(--shadow-1); }
.btn-primary:hover { box-shadow: var(--shadow-2); filter: brightness(1.05); }
.btn-secondary { background: #fff; color: var(--ink-700); border-color: var(--ink-300); }
.btn-secondary:hover { border-color: var(--ink-400); background: var(--ink-50); }
.btn-ghost { background: transparent; color: var(--accent-700); }
.btn-ghost:hover { background: var(--accent-50); }
.btn-danger { background: var(--bad-600); color: #fff; }
.btn-lg { padding: 13px 26px; font-size: 1.02rem; border-radius: var(--r-md); }
.btn-sm { padding: 6px 12px; font-size: .85rem; }
.btn[disabled] { opacity: .55; pointer-events: none; }

/* ── Cards ──────────────────────────────────────────────────
   .card is the platform's generic surface: 196 files tree-wide use it, 42 of
   them public. It is NEVER renamed — it is extended in place onto the card
   system's tokens, which is the whole reason those tokens exist as tokens.
   A .card is the same object as .ecard--panel; the difference is that .card
   also appears inside dashboard GRIDS, where a resting --shadow-1 is correct,
   so it keeps its elevation while .ecard--panel (an in-column information
   panel) is flat. See the CARD SYSTEM section at the foot of this file.
   .card-header and .stat-grid were deleted here: no view and no component in
   resources/ renders either, and the legacy tree that uses those names never
   loads this stylesheet. */
.card {
  background: var(--card-bg); border: 1px solid var(--card-bd); border-radius: var(--card-r);
  box-shadow: var(--shadow-1); min-width: 0;
}
.card-pad { padding: var(--card-pad); }
/* A .card that is a link gets the system's one hover gesture, gated to fine
   pointers for the reason spelled out on .ecard--link. Without this a card
   wrapped in an <a> was the one surface on the platform that told a citizen
   nothing when they pointed at it. */
a.card { display: block; color: inherit; }
a.card:hover { text-decoration: none; }
@media (hover: hover) and (pointer: fine) {
  a.card { transition: transform .16s var(--ease), box-shadow .16s var(--ease), border-color .16s var(--ease); }
  a.card:hover, a.card:focus-visible { transform: translateY(var(--card-lift)); box-shadow: var(--shadow-2); border-color: var(--card-bd-hover); }
}
.card__cta {
  display: inline-flex; align-items: center; gap: 6px; min-height: 44px;
  font-size: var(--card-action); font-weight: 700; color: var(--accent-700);
}

/* Stat card */
.stat {
  background: #fff; border: 1px solid var(--ink-200); border-radius: var(--r-lg);
  padding: 18px 20px; box-shadow: var(--shadow-1); position: relative; overflow: hidden;
}
.stat::before {
  content: ''; position: absolute; inset: 0 auto 0 0; width: 4px;
  background: linear-gradient(180deg, var(--accent-600), var(--accent-700));
}
.stat .stat-label { font-size: .8rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-500); }
.stat .stat-value { font-size: 1.65rem; font-weight: 800; letter-spacing: -.02em; margin-top: 4px; }
.stat .stat-sub { font-size: .84rem; color: var(--ink-500); margin-top: 2px; }
.stat .stat-delta-up { color: var(--ok-600); font-weight: 700; }
.stat .stat-delta-down { color: var(--bad-600); font-weight: 700; }
a.stat { display: block; color: inherit; }
a.stat:hover { text-decoration: none; box-shadow: var(--shadow-2); border-color: var(--accent-600); }

/* ── Badges ───────────────────────────────────────────────── */
.badge {
  display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px;
  border-radius: 999px; font-size: .78rem; font-weight: 700; letter-spacing: .01em;
}
.badge::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.badge-ok   { background: var(--ok-100);   color: var(--ok-600); }
.badge-warn { background: var(--warn-100); color: var(--warn-600); }
.badge-bad  { background: var(--bad-100);  color: var(--bad-600); }
.badge-info { background: var(--info-100); color: var(--info-600); }
.badge-muted{ background: var(--ink-100);  color: var(--ink-500); }

/* ── Tables ───────────────────────────────────────────────── */
.table-wrap { overflow-x: auto; border: 1px solid var(--ink-200); border-radius: var(--r-lg); background: #fff; box-shadow: var(--shadow-1); }
table.data { width: 100%; border-collapse: collapse; font-size: .92rem; }
table.data th {
  text-align: left; padding: 12px 16px; background: var(--ink-50);
  font-size: .76rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ink-500); border-bottom: 1px solid var(--ink-200); white-space: nowrap;
}
table.data td { padding: 12px 16px; border-bottom: 1px solid var(--ink-100); vertical-align: middle; }
table.data tbody tr:last-child td { border-bottom: 0; }
table.data tbody tr:hover { background: var(--ink-50); }
table.data td.num, table.data th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* ── Forms ────────────────────────────────────────────────── */
.field { margin-bottom: 16px; }
.field label { display: block; font-weight: 600; font-size: .9rem; margin-bottom: 6px; color: var(--ink-700); }
.input, select.input, textarea.input {
  width: 100%; padding: 11px 14px; border: 1.5px solid var(--control-border); border-radius: var(--r-sm);
  font: 400 .97rem var(--font); color: var(--ink-900); background: #fff;
  transition: border-color .12s var(--ease), box-shadow .12s var(--ease);
}
.input:focus { border-color: var(--accent-600); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-600) 18%, transparent); outline: none; }
/* `outline: none` above is (0,2,0) and sits after the global :focus-visible
   (0,1,0), so a text input answered KEYBOARD focus with an 18%-alpha glow while
   the card system gave everything else a solid ring. A pointer click keeps the
   soft glow — it is the right weight for a field you just clicked into — and a
   keyboard lands the same ring the rest of the page uses. */
.input:focus-visible { outline: 3px solid var(--accent-700); outline-offset: 1px; }
.input[aria-invalid="true"] { border-color: var(--bad-600); }
.field .error-text { color: var(--bad-600); font-size: .84rem; margin-top: 5px; }
.field .hint { color: var(--ink-500); font-size: .84rem; margin-top: 5px; }
.input-group { display: flex; gap: 10px; }
.input-group .input { flex: 1; }

/* ── Alerts & toasts ──────────────────────────────────────── */
.alert { display: flex; gap: 10px; padding: 13px 16px; border-radius: var(--r-md); font-size: .93rem; margin-bottom: 16px; border: 1px solid; }
.alert-ok   { background: var(--ok-100);   border-color: color-mix(in srgb, var(--ok-600) 30%, transparent);   color: var(--success-fg); }
.alert-error{ background: var(--bad-100);  border-color: color-mix(in srgb, var(--bad-600) 30%, transparent);  color: #991b1b; }
.alert-info { background: var(--info-100); border-color: color-mix(in srgb, var(--info-600) 30%, transparent); color: var(--abia-neutral-800); }
.alert-warn { background: var(--warn-100); border-color: color-mix(in srgb, var(--warn-600) 30%, transparent); color: #92400e; }

.toast-region { position: fixed; right: 16px; bottom: 16px; z-index: 100; display: grid; gap: 10px; max-width: 380px; }
.toast { background: var(--ink-900); color: #fff; padding: 13px 18px; border-radius: var(--r-md); box-shadow: var(--shadow-3); font-size: .93rem; animation: toast-in .3s var(--ease); }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* ── Empty state, skeleton ────────────────────────────────── */
.empty {
  text-align: center; padding: 48px 24px; color: var(--ink-500);
}
.empty .empty-icon { font-size: 2.2rem; margin-bottom: 8px; }
/* Element-specific, and the public views have since promoted these headings to
   the <h1> each page was missing — which dropped "Service not found" and "No
   payment found" to .empty's inherited --ink-500. The heading of an otherwise
   empty page is the one thing on it; it does not get quieter for being an h1. */
.empty :is(h1, h2, h3) { color: var(--ink-700); }
.skeleton {
  border-radius: var(--r-sm); background: linear-gradient(90deg, var(--ink-100) 25%, var(--ink-200) 50%, var(--ink-100) 75%);
  background-size: 200% 100%; animation: shimmer 1.4s infinite;
}
@keyframes shimmer { to { background-position: -200% 0; } }

/* ── Auth split-screen (adapted from the AbiaPay .lp-* design) ── */
.lp { display: flex; min-height: 100vh; }
.lp-brand {
  flex: 0 0 52%; position: relative; overflow: hidden; color: #fff;
  background:
    radial-gradient(1000px 600px at 15% -10%, rgb(201 48 73 / .35), transparent 60%),
    radial-gradient(800px 500px at 110% 40%, rgb(206 132 7 / .30), transparent 55%),
    linear-gradient(160deg, var(--brand-950) 0%, var(--brand-900) 55%, var(--abia-red-950) 100%);
  display: flex; flex-direction: column; justify-content: center; padding: 64px;
  /* A landscape iPhone puts its notch on this side. */
  padding-left: max(64px, calc(env(safe-area-inset-left, 0px) + 32px));
}
.lp-brand::after {
  content: ''; position: absolute; inset: 0;
  background-image: radial-gradient(rgb(255 255 255 / .07) 1px, transparent 1px);
  background-size: 26px 26px; pointer-events: none;
}
.lp-eyebrow {
  display: inline-flex; align-items: center; gap: 8px; align-self: flex-start;
  padding: 6px 14px; border-radius: 999px; background: rgb(255 255 255 / .1);
  border: 1px solid rgb(255 255 255 / .18); font-size: .8rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
}
.lp-brand h1 {
  font-size: 2.3rem; margin: 22px 0 10px; line-height: 1.15;
  background: linear-gradient(90deg, #fff, var(--abia-red-100)); -webkit-background-clip: text; background-clip: text; color: transparent;
}
.lp-brand .lead { color: rgb(255 255 255 / .78); font-size: 1.05rem; max-width: 46ch; }
.lp-feats { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 34px; max-width: 520px; }
.lp-feat { background: rgb(255 255 255 / .07); border: 1px solid rgb(255 255 255 / .12); border-radius: var(--r-md); padding: 14px 16px; }
.lp-feat b { display: block; margin-bottom: 3px; }
.lp-feat span { color: rgb(255 255 255 / .7); font-size: .87rem; }
/* The sign-in layout asks for viewport-fit=cover and a black-translucent status
   bar, so the page is drawn under the notch, the home indicator and, installed
   on iOS, the status bar itself. The card keeps clear of all three. Every inset
   is 0 in an ordinary browser tab, where this is exactly 40px 24px. */
.lp-form {
  flex: 1; display: flex; align-items: center; justify-content: center; background: var(--ink-50);
  padding: max(40px, calc(env(safe-area-inset-top, 0px) + 24px)) max(24px, calc(env(safe-area-inset-right, 0px) + 16px))
    max(40px, calc(env(safe-area-inset-bottom, 0px) + 24px)) max(24px, calc(env(safe-area-inset-left, 0px) + 16px));
}
/* The installed status bar's text is white and the page behind it is the pale
   form panel (the brand panel is gone below 960px), so the time and battery
   would be unreadable. A strip exactly the inset's height gives them a ground
   in the app's own theme colour, matching each app's theme-color meta: the
   portal's masthead red-950, the console's and FieldOps' Abia Red. The strip
   is 0px tall wherever there is no inset. */
.lp::before {
  content: ""; position: fixed; top: 0; left: 0; right: 0; z-index: 80;
  height: env(safe-area-inset-top, 0px); background: #470A12; pointer-events: none;
}
html:is([data-app="console"], [data-app="fieldops"]) .lp::before { background: #AA182C; }
.lp-card {
  width: 100%; max-width: 430px; background: #fff; border: 1px solid var(--ink-200);
  border-radius: var(--r-xl); box-shadow: var(--shadow-3); padding: 34px; position: relative; overflow: hidden;
}
.lp-card::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0; height: 4px;
  background: linear-gradient(90deg, var(--accent-700), var(--accent-600), var(--brand-400));
}
.lp-card .lp-title { font-size: 1.4rem; font-weight: 800; margin-bottom: 4px; }
.lp-card .lp-sub { color: var(--ink-500); font-size: .93rem; margin-bottom: 24px; }
.lp-security { display: flex; align-items: center; gap: 8px; justify-content: center; margin-top: 20px; color: var(--ink-400); font-size: .8rem; }

/* ── Page scaffolding ─────────────────────────────────────── */
.page-head { display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between; gap: 14px; margin: 28px 0 20px; }
.page-head .crumbs { font-size: .84rem; color: var(--ink-500); margin-bottom: 6px; }
.page-head .crumbs a { color: var(--ink-500); }
.section-gap { margin-bottom: 22px; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
.split { display: grid; grid-template-columns: 2fr 1fr; gap: 18px; }

/* .filter-bar was deleted: no view in resources/ renders it. */

/* Pagination */
.pager { display: flex; gap: 6px; align-items: center; justify-content: flex-end; margin-top: 14px; font-size: .9rem; }
.pager a, .pager span.cur { padding: 6px 12px; border-radius: var(--r-sm); border: 1px solid var(--control-border); background: #fff; color: var(--ink-700); }
.pager span.cur { background: var(--accent-700); color: #fff; border-color: var(--accent-700); font-weight: 700; }
.pager a:hover { text-decoration: none; border-color: var(--accent-600); color: var(--accent-700); }

/* ── Public landing ─────────────────────────────────────────
   The first-generation public layer — .hero, .hero-actions, .channel-grid,
   .channel-card, .ch-icon, .ch-cta — was deleted. It was superseded by .phero
   and the card system below, and a grep over resources/ and public_html/
   found no view, component or script that renders any of those names.

   It had exactly one lesson worth keeping, and it has been carried over onto
   .phero__title: a ch-based max-width measures the FONT, not the space
   available, so at 390px a 22ch heading came out ~396px wide against 350px of
   container and ran off the right edge. min(…, 100%) keeps the reading measure
   on wide screens and yields to the container on narrow ones, and
   overflow-wrap: break-word catches the unbreakable word that min() cannot. */

/* ── Footer ───────────────────────────────────────────────── */
.footer { margin-top: 64px; padding: 34px 0; border-top: 1px solid var(--ink-200); color: var(--ink-500); font-size: .88rem; background: #fff; }
.footer .container { display: flex; flex-wrap: wrap; gap: 12px; justify-content: space-between; }

/* ============================================================
   PUBLIC SITE — premium government-portal layer (TAMM-aligned).
   Search hero, life-domain Spaces, audience switch, popular
   services, journeys, app band, refined rhythm & rich footer.
   First-party & CSP-safe (self-hosted fonts/photos, inline SVG).
   ============================================================ */

/* ── Enhanced top bar (brand lockup + sign-in actions) ──────── */
.brand-logo { width: 34px; height: 34px; border-radius: 9px; object-fit: cover; box-shadow: var(--shadow-1); }
.brand-lock { display: flex; flex-direction: column; line-height: 1.05; }
.brand-lock .bl-name { font-weight: 800; font-size: 1.05rem; color: var(--ink-900); letter-spacing: -.02em; }
.brand-lock .bl-sub { font-size: .68rem; font-weight: 600; text-transform: uppercase; letter-spacing: .09em; color: var(--ink-400); }
.nav-actions { display: flex; align-items: center; gap: 8px; margin-left: 8px; }
.topbar nav a { min-height: 40px; display: inline-flex; align-items: center; }

/* ── Multi-level mega-menu (public header) ──────────────────── */
.meganav { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.meganav__list { display: flex; align-items: center; gap: 2px; list-style: none; margin: 0; padding: 0; }
.meganav__item { position: relative; }
.meganav__top { display: inline-flex; align-items: center; gap: 5px; padding: 9px 14px; border-radius: 999px;
  color: var(--ink-700); font-weight: 600; font-size: .92rem; min-height: 40px; cursor: pointer; white-space: nowrap; }
.meganav__top:hover { background: var(--ink-100); text-decoration: none; }
.meganav__top.active { background: var(--accent-50); color: var(--accent-700); }
.meganav__top .svg-icon { color: var(--ink-400); transition: transform .18s var(--ease); }
.meganav__cta { display: flex; align-items: center; gap: 8px; margin-left: 6px; }

/* Dropdown surface */
.meganav__drop { position: absolute; top: calc(100% + 10px); left: 50%; transform: translateX(-50%) translateY(8px);
  background: #fff; border: 1px solid var(--ink-200); border-radius: var(--r-lg); box-shadow: var(--shadow-3);
  padding: 14px; opacity: 0; visibility: hidden; transition: opacity .16s var(--ease), transform .16s var(--ease); z-index: 70; }
.meganav__drop::before { content: ''; position: absolute; top: -8px; left: 0; right: 0; height: 8px; } /* hover bridge */
/* The wide drop was RIGHT-aligned (left:auto; right:0) to a trigger that sits
   about 109px wide in the middle of the bar, while being 720px wide — so it
   hung off the LEFT edge of the viewport at every laptop width, and
   overflow-x:hidden cut it off rather than scrolled it: 318px at 961,
   233px at 1100, 33px at 1440. The first column of "Services" was sliced in
   half, and it only stopped overflowing above 1473px.
   It now uses the same centre-on-the-trigger positioning as every other drop,
   which is why the two overrides below it are gone as well — one anchor, one
   open-state transform, nothing to drift. Centred, nothing is clipped at any
   width this rule applies to (it collapses into the hamburger sheet at 960). */
.meganav__drop--wide { width: min(720px, calc(100vw - 24px)); }
.meganav__item.has-drop:hover > .meganav__top .svg-icon,
.meganav__item.has-drop:focus-within > .meganav__top .svg-icon { transform: rotate(180deg); }
.meganav__item.has-drop:hover > .meganav__drop,
.meganav__item.has-drop:focus-within > .meganav__drop,
.meganav__item.has-drop.is-open > .meganav__drop {
  opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
.meganav__item.has-drop.is-open > .meganav__top .svg-icon { transform: rotate(180deg); }

.meganav__panel { display: grid; gap: 2px; width: 300px; }
.meganav__cols { display: grid; grid-template-columns: 1fr 1fr auto; gap: 6px 18px; }
.meganav__col { min-width: 200px; }
/* .72rem (11.5px) at --ink-400 is 4.46:1, which brand.css labels large-text
   only. Same size, a step darker, 6.74:1. */
.meganav__coltitle { font-size: .72rem; font-weight: 800; text-transform: uppercase; letter-spacing: .08em;
  color: var(--ink-500); padding: 6px 10px 8px; }
.meganav__feature { grid-row: span 1; align-self: stretch; width: 220px; }
/* .meganav__link, .meganav__ic and the rest of .meganav__feature are the card
   system's --row and --invert variants; see the CARD SYSTEM section. */

/* Wider inner column for full-bleed sections on large screens. */
.pshell { max-width: var(--page-max); margin: 0 auto; padding: 0 24px; }

/* ── Section scaffolding + rhythm ───────────────────────────── */
/* padding-BLOCK, not the `96px 0` shorthand. Two views put .psection on the
   same element as .container (support.php, developers/shell.php), and the
   shorthand's `0` zeroed that element's 20px side gutter — so on a 390px phone
   those pages ran their cards edge to edge with no margin at all. The section
   only ever wanted to set its vertical rhythm; saying so leaves the gutter
   alone. Measured: support.php's contact cards were 390px wide in a 390px
   viewport. */
.psection { padding-block: var(--space-12); }
.psection--tight { padding-block: var(--space-10); }
/* Continues the preceding section — no top padding, so a strip reads as part
   of the block above it rather than as its own free-floating band. */
.psection--joined { padding-top: 0; }
/* Hairline rule between two consecutive plain-background sections, which
   otherwise merge into one long undifferentiated column. */
.psection--ruled > .pshell { border-top: 1px solid var(--ink-200); padding-top: var(--space-12); }
.psection--ruled { padding-top: 0; }
.psection--alt { background: #fff; border-top: 1px solid var(--ink-200); border-bottom: 1px solid var(--ink-200); }
.psection--dark { background: linear-gradient(160deg, var(--brand-950), var(--brand-800)); color: #fff; }
.pkicker { display: inline-flex; align-items: center; gap: 8px; font-size: .78rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .12em; color: var(--accent-700); margin-bottom: 10px; }
.pkicker::before { content: ''; width: 18px; height: 2px; border-radius: 2px; background: currentColor; }
/* .psection__kicker is the same thing under a name that is defined in NO
   stylesheet — verified across app.css, brand.css, mda.css, shell.css and
   fieldops.css — so every category page rendered its section kicker as plain
   body text. It is carried here rather than left broken while the three views
   that use it are migrated to .pkicker. It does NOT take the ::before rule:
   those views already print an inline glyph, and two marks on one line is one
   too many. */
.psection__kicker { display: inline-flex; align-items: center; gap: 8px; font-size: .78rem;
  font-weight: 800; text-transform: uppercase; letter-spacing: .12em; color: var(--accent-700);
  margin-bottom: 10px; }
/* --brand-400 measures 2.70:1 here. It is a graphic mark, not a text colour. */
.psection--dark .pkicker { color: var(--accent-on-dark); }
/* .psection__head rides along for the same reason as .psection__kicker: it is
   used by service_category.php (twice) and hospitality.php and defined nowhere,
   so the <h2> fell back to the global 1.35rem (21.6px) instead of --fs-h2
   (~41.6px). The page a citizen uses to actually PICK a service was the one
   page on the site with no hierarchy at all. */
.phead, .psection__head { max-width: 680px; margin-bottom: var(--space-8); }
.phead.center { margin-left: auto; margin-right: auto; text-align: center; }
.phead.center .pkicker::before { display: none; }
.phead h2, .psection__head h2 { font-family: var(--font-display); font-size: var(--fs-h2); font-weight: 700; letter-spacing: -.02em; line-height: 1.1; }
.phead p, .psection__head p { color: var(--ink-500); font-size: var(--fs-lead); margin: 0; }
.psection--dark .phead p { color: rgb(255 255 255 / .78); }
.psec-foot { margin-top: var(--space-6); }

/* ── Search-led cover hero ──────────────────────────────────── */
.phero { position: relative; overflow: hidden; background: linear-gradient(155deg, var(--brand-950), var(--brand-800)); color: #fff; }
.phero__bg { position: absolute; inset: 0; z-index: 0; }
.phero__bg img { width: 100%; height: 100%; object-fit: cover; object-position: center 30%; opacity: .5;
  animation: phero-in 1.1s var(--ease) both; }
.phero__bg::after { content: ''; position: absolute; inset: 0;
  background: linear-gradient(120deg, rgb(71 10 18 / .93) 0%, rgb(107 15 27 / .78) 42%, rgb(107 15 27 / .55) 100%); }
.phero__bg::before { content: ''; position: absolute; inset: 0; z-index: 1;
  background-image: radial-gradient(rgb(255 255 255 / .05) 1px, transparent 1px); background-size: 26px 26px; }
.phero__inner { position: relative; z-index: 2; max-width: var(--page-max); margin: 0 auto;
  padding: var(--space-12) 24px calc(var(--space-10) + 60px); }
.phero__eyebrow { display: inline-flex; align-items: center; gap: 8px; font-size: .82rem; font-weight: 700;
  background: rgb(255 255 255 / .1); border: 1px solid rgb(255 255 255 / .2); padding: 7px 14px; border-radius: var(--r-pill); }
.phero__eyebrow .svg-icon { color: var(--gold-500); }
/* max-width in ch measures the FONT, not the space available — see the note
   where .hero was deleted. min() keeps the 16ch reading measure wherever there
   is room for it and yields to the container everywhere else. */
.phero__title { font-family: var(--font-display); font-size: var(--fs-display); font-weight: 700;
  letter-spacing: -.025em; line-height: 1.02; margin: 20px 0 16px;
  max-width: min(16ch, 100%); overflow-wrap: break-word; text-wrap: balance; }
.phero__title em { font-style: normal; color: var(--brand-400); }
.phero__sub { color: rgb(255 255 255 / .9); font-size: var(--fs-lead); max-width: 52ch; line-height: 1.5; }
.phero__meta { display: flex; flex-wrap: wrap; gap: var(--space-3) var(--space-8); margin-top: var(--space-6); }
.phero__stat { display: flex; align-items: center; gap: 10px; }
.phero__stat .svg-icon { color: var(--brand-400); }
.phero__stat b { font-weight: 800; }
.phero__stat span { font-size: .9rem; color: rgb(255 255 255 / .75); }

/* Hero search bar */
.phero__search { margin-top: var(--space-6); max-width: 620px; }
.psearch { display: flex; align-items: center; gap: 8px; background: #fff; border-radius: var(--r-pill);
  padding: 7px 7px 7px 18px; box-shadow: var(--shadow-3); }
.psearch .svg-icon { color: var(--ink-400); }
.psearch input { flex: 1; border: 0; outline: 0; font: inherit; font-size: 1rem; color: var(--ink-900); background: transparent; min-width: 0; }
.psearch input::placeholder { color: var(--ink-400); }
.psearch button { flex: none; }
.phero__hints { margin-top: 12px; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; font-size: .85rem; color: rgb(255 255 255 / .7); }
.phero__hints a { color: #fff; background: rgb(255 255 255 / .12); border: 1px solid rgb(255 255 255 / .18);
  padding: 5px 12px; border-radius: var(--r-pill); }
.phero__hints a:hover { background: rgb(255 255 255 / .22); text-decoration: none; }

.btn-onhero { background: rgb(255 255 255 / .16); color: #fff; border: 1px solid rgb(255 255 255 / .45); backdrop-filter: blur(4px); }
.btn-onhero:hover { background: rgb(255 255 255 / .26); }
.btn-xl { padding: 15px 30px; font-size: 1.05rem; border-radius: var(--r-md); }
@keyframes phero-in { from { opacity: 0; } to { opacity: .5; } }
@media (prefers-reduced-motion: reduce) { .phero__bg img { animation: none; } }

/* ── Floating quick-tool cards (lap over the hero) ────────────
   Only the band's placement lives here now; the tile itself is the card
   system's .ecard--action. See the CARD SYSTEM section.

   What was deleted with it: `.ptool span`, which was the single most expensive
   bug on the homepage. `.ptool span` (0,1,1) beats `.ptool__ic` (0,1,0), and
   the icon plate IS a <span> — so `color: var(--ink-500)` greyed the glyph and
   `flex: 1` (basis 0%) collapsed the plate's height inside the column flex
   container. The five flagship quick tools rendered with squashed grey plates,
   at 1440px and at 390px alike. The card system fixes it structurally: the
   description gets a class of its own, and .ic-chip carries flex: none so no
   future flex rule can shrink a chip again.
   The bespoke `0 22px 46px rgb(26 22 23/.16)` hover went with it — see the
   elevation ladder at the top of this file. */
.ptools { position: relative; z-index: 3; margin-top: clamp(-72px, -6vw, -36px); }

/* ── Section photo banner (cross-fading real photography) ───── */
/* Four shots dissolve on a shared timeline: each is visible for a quarter of
   the cycle with a soft overlap. Pure CSS (no JS, CSP-safe); the images are
   decorative, so the markup is aria-hidden and the captions are cosmetic. */
.pbanner { position: relative; min-height: clamp(260px, 30vw, 360px); margin-bottom: var(--space-8);
  border-radius: var(--r-xl); overflow: hidden; background: var(--brand-900); box-shadow: var(--shadow-3);
  isolation: isolate; display: flex; align-items: flex-end; }
.pbanner__stage { position: absolute; inset: 0; }
/* The heading rides ON the photography — one composed block, not a floating
   image followed by a detached title. */
.pbanner__body { position: relative; z-index: 2; padding: clamp(26px, 3.4vw, 44px); max-width: 720px; color: #fff; }
/* Over PHOTOGRAPHY no fixed palette step can be guaranteed — the frame behind
   this line changes four times a minute, from a bright sky to a dark facade.
   --brand-400 measured 2.70:1 against the darkest frame and worse against the
   brightest. White at 92% plus the strengthened scrim below is the only pairing
   that holds across all four shots; a card token cannot fix text on a photo. */
.pbanner__body .pkicker { color: rgb(255 255 255 / .92); }
.pbanner__body h2 { font-family: var(--font-display); font-size: var(--fs-h2); font-weight: 700;
  letter-spacing: -.02em; line-height: 1.1; margin: 0 0 10px; color: #fff; }
.pbanner__body p { font-size: var(--fs-lead); color: rgb(255 255 255 / .88); margin: 0; max-width: 60ch; }
.pbanner__shot { position: absolute; inset: 0; margin: 0; opacity: 0;
  animation: pbanner-fade 24s var(--ease) infinite; animation-delay: calc(var(--i) * 6s); }
.pbanner__shot img { width: 100%; height: 100%; object-fit: cover; object-position: center 42%;
  /* Slow drift adds life without ever reading as motion sickness. */
  animation: pbanner-drift 24s ease-in-out infinite; animation-delay: calc(var(--i) * 6s); }
/* Photo credit sits top-right, clear of the overlaid heading (bottom-left).
   Given its own soft chip so it stays readable over a bright sky, where a
   plain text-shadow disappears. */
.pbanner__shot figcaption { position: absolute; right: 16px; top: 16px; z-index: 2;
  display: inline-flex; align-items: center; gap: 7px; padding: 6px 12px;
  border-radius: var(--r-pill); background: rgb(71 10 18 / .42);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  font-size: .74rem; font-weight: 600; letter-spacing: .02em; color: rgb(255 255 255 / .95); }
.pbanner__shot figcaption::before { content: ''; width: 12px; height: 2px; border-radius: 2px; background: var(--brand-400); }
/* Veil: a bottom scrim for the heading plus a directional brand wash, so the
   text stays legible over a bright sky as well as a dark facade. */
/* Strengthened: the old scrim held the heading over the Abia Tower frame and
   lost it over the Azumini river frame, where the water is nearly white exactly
   where the kicker sits. The bottom stop goes .88 -> .92 and the half-way stop
   .55 -> .82, which is what carries 92% white through all four shots. */
.pbanner__veil { position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    linear-gradient(to top, rgb(71 10 18 / .92) 0%, rgb(71 10 18 / .82) 46%, rgb(71 10 18 / .12) 86%),
    linear-gradient(105deg, rgb(71 10 18 / .55) 0%, rgb(71 10 18 / .2) 60%, rgb(71 10 18 / 0) 100%); }
/* 0-4% fade in · hold to 25% · fade out by 29% — a 4% overlap per shot. */
@keyframes pbanner-fade {
  0%   { opacity: 0; }
  4%   { opacity: 1; }
  25%  { opacity: 1; }
  29%  { opacity: 0; }
  100% { opacity: 0; }
}
@keyframes pbanner-drift {
  0%   { transform: scale(1.06); }
  29%  { transform: scale(1.01); }
  100% { transform: scale(1.01); }
}
/* Reduced motion: hold the first frame, no fade, no drift. */
@media (prefers-reduced-motion: reduce) {
  .pbanner__shot { animation: none; opacity: 0; }
  .pbanner__shot[data-first] { opacity: 1; }
  .pbanner__shot img { animation: none; transform: none; }
}
/* Never leave an empty band: if animations don't run at all, frame one shows.
   The running animation overrides this from its 0% keyframe onward. */
.pbanner__shot[data-first] { opacity: 1; }
.pbanner__shot[data-first] { animation-name: pbanner-fade-first; }
@keyframes pbanner-fade-first {
  0%   { opacity: 1; }
  25%  { opacity: 1; }
  29%  { opacity: 0; }
  96%  { opacity: 0; }
  100% { opacity: 1; }
}

/* ── Audience switch (CSS-only segmented control + tile filter) ── */
.paud-radio { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.paud { display: inline-flex; background: var(--ink-100); border: 1px solid var(--ink-200); border-radius: var(--r-pill); padding: 4px; gap: 2px; }
.paud label { padding: 9px 20px; border-radius: var(--r-pill); font-weight: 700; font-size: .92rem; color: var(--ink-500); cursor: pointer; min-height: 40px; display: inline-flex; align-items: center; justify-content: center; }
.paud label:hover { color: var(--ink-900); }
/* Anchor variant (server-rendered tabs, e.g. the bills/invoices ledger switch)
   — same pill chrome as the CSS-only radio version above. */
.paud a { padding: 9px 20px; border-radius: var(--r-pill); font-weight: 700; font-size: .92rem;
  color: var(--ink-500); min-height: 40px; display: inline-flex; align-items: center; justify-content: center; }
.paud a:hover { color: var(--ink-900); text-decoration: none; }
.paud a.is-on { background: #fff; color: var(--brand-800); box-shadow: var(--shadow-1); }
/* Active pill — the checked radio precedes .paud, so target by state. */
#aud-all:checked ~ .paud label[for="aud-all"],
#aud-ind:checked ~ .paud label[for="aud-ind"],
#aud-biz:checked ~ .paud label[for="aud-biz"] { background: #fff; color: var(--brand-800); box-shadow: var(--shadow-1); }
/* Filter the domain tiles by audience. "All" shows everything. */
#aud-ind:checked ~ .pspaces .pspace[data-aud="biz"] { display: none; }
#aud-biz:checked ~ .pspaces .pspace[data-aud="ind"] { display: none; }

/* .pspace, .ppop__item, .pservice and .pjourney are the card system's
   --category and --row variants; their grids and their rules are in the CARD
   SYSTEM section at the foot of this file, together with the other eleven
   families they used to differ from for no stated reason. */

/* ── Photo gallery (districts / in-focus) ───────────────────── */
.pgallery { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; }
.ptile { position: relative; border-radius: var(--r-lg); overflow: hidden; aspect-ratio: 4 / 3; box-shadow: var(--shadow-2); display: block; }
.ptile img { width: 100%; height: 100%; object-fit: cover; transition: transform .45s var(--ease); }
.ptile:hover img { transform: scale(1.06); }
.ptile__cap { position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: flex-end;
  padding: 16px; color: #fff; background: linear-gradient(to top, rgb(71 10 18 / .85), rgb(71 10 18 / 0) 62%); }
.ptile__cap b { font-size: 1rem; }
.ptile__cap span { font-size: .8rem; color: rgb(255 255 255 / .82); }

/* ── Feature band (image + text promo) ──────────────────────── */
.pband { display: grid; grid-template-columns: 1.05fr .95fr; border-radius: var(--r-2xl); overflow: hidden;
  background: linear-gradient(150deg, var(--brand-900), var(--brand-700)); color: #fff; box-shadow: var(--shadow-3); }
/* The source photo is PORTRAIT (800x1100). Forcing it into a 4:3 landscape
   slot cropped it to a narrow slice of masonry. Letting the media column take
   the band's natural height shows the landmark whole and keeps the two halves
   the same height. */
.pband__media { position: relative; min-height: 340px; }
.pband__media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: center 30%; }
.pband__body { padding: clamp(28px, 4vw, 52px); display: flex; flex-direction: column; justify-content: center; }
.pband__body h2 { font-family: var(--font-display); font-size: var(--fs-h2); }
.pband__body p { color: rgb(255 255 255 / .85); font-size: 1.02rem; }
.pband__cta { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 22px; }

/* ── App-download band (CSS device frame) ───────────────────── */
/* The device mock is a fixed 200px, so a .9fr track reserved far more space
   than it uses and left a dead gap beside the copy. Size the second column to
   the artwork and let the text take the rest. */
.papp { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: clamp(24px, 5vw, 72px); align-items: center;
  background: #fff; border: 1px solid var(--ink-200); border-radius: var(--r-2xl); padding: clamp(28px, 4vw, 52px); box-shadow: var(--shadow-2); }
.papp h2 { font-family: var(--font-display); font-size: var(--fs-h2); }
.papp p { color: var(--ink-500); font-size: 1.02rem; max-width: 52ch; }
.papp__badges { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 20px; }
.store-badge { display: inline-flex; align-items: center; gap: 10px; background: var(--ink-900); color: #fff;
  border-radius: var(--r-md); padding: 10px 18px; }
.store-badge:hover { text-decoration: none; background: #000; }
.store-badge small { display: block; font-size: .66rem; opacity: .8; line-height: 1; }
.store-badge b { font-size: 1rem; line-height: 1.2; }
/* The frame matches the SCREENSHOT's own 720x1560, not a rounded 9/18, so the
   capture sits in it whole. At 9/18 the screen was proportionally shorter than
   the image and object-fit: cover silently cropped the bottom — taking the tab
   bar, which is the part that makes it read as an app at all. */
.papp__device { justify-self: center; width: 200px; aspect-ratio: 720 / 1560; border-radius: 30px;
  background: linear-gradient(160deg, var(--brand-900), var(--brand-700)); padding: 10px; box-shadow: var(--shadow-3); }
.papp__device .scr { width: 100%; height: 100%; border-radius: 22px; overflow: hidden; position: relative; background: var(--ink-100); }
/* height:auto with the matching ratio above, so the intrinsic 720x1560 on the
   tag reserves the right box before the image arrives and nothing shifts. */
.papp__device .scr img { display: block; width: 100%; height: auto; }

/* ── How-it-works steps (CSS counters) ────────────────────────
   .psteps / .pstep are in the CARD SYSTEM section: a .pstep is a card whose
   icon chip happens to be a number. It shares one step marker with
   .svc-steps li::before, which was a different size, a different radius and a
   different fill for the same idea. */

/* ── Stats band ─────────────────────────────────────────────── */
.pstats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
.pstat { display: flex; flex-direction: column; gap: 4px; padding-left: 18px; border-left: 3px solid var(--brand-100); }
.pstat b { font-family: var(--font-display); font-size: clamp(2rem, 3.4vw, 2.8rem); font-weight: 700;
  letter-spacing: -.02em; color: var(--brand-700); font-variant-numeric: tabular-nums; }
.psection--dark .pstat { border-left-color: rgb(255 255 255 / .2); }
.psection--dark .pstat b { color: #fff; }
.pstat span { font-size: .92rem; color: var(--ink-500); font-weight: 600; }
.psection--dark .pstat span { color: rgb(255 255 255 / .75); }

/* ── Browse-by directory (ministries / LGAs) ────────────────── */
/* Columns are sized to their CONTENT: the service-group list has 6 entries and
   the LGA list 17, so an even 1fr 1fr split left a large void beside the short
   column. 2fr/3fr plus per-column counts keeps both halves visually dense. */
.pbrowse { display: grid; grid-template-columns: 2fr 3fr; gap: var(--space-6) var(--space-8); align-items: start; }
.pbrowse__col h3 { font-size: 1.05rem; margin-bottom: 14px; }
.pbrowse__col--wide .pbrowse__links { columns: 3; }
.pbrowse__links { columns: 1; column-gap: 24px; }
.pbrowse__links a { display: block; padding: 7px 0; font-size: .9rem; color: var(--ink-700); break-inside: avoid; }
.pbrowse__links a:hover { color: var(--accent-700); }
@media (max-width: 900px) {
  .pbrowse { grid-template-columns: 1fr; }
  .pbrowse__links, .pbrowse__col--wide .pbrowse__links { columns: 2; }
}

/* ── Trust strip ────────────────────────────────────────────── */
/* Even 4-up grid: flex-wrap left an orphan item alone on a second row at some
   widths. A grid keeps the four assurances balanced, then halves cleanly. */
.ptrust { background: var(--brand-950); color: #fff; border-radius: var(--r-lg); padding: 24px 28px;
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px 28px; align-items: center; }
.ptrust__item { display: flex; align-items: center; gap: 10px; font-size: .9rem; font-weight: 600; line-height: 1.35; }
.ptrust__item .svg-icon { flex: none; }
@media (max-width: 1024px) { .ptrust { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .ptrust { grid-template-columns: 1fr; } }
.ptrust__item .svg-icon { color: var(--gold-500); }

/* ── FAQ (native disclosure) ────────────────────────────────── */
.pfaq { max-width: 820px; margin: 0 auto; }
.pfaq details { background: #fff; border: 1px solid var(--ink-200); border-radius: var(--r-md); margin-bottom: 10px; overflow: hidden; }
.pfaq summary { cursor: pointer; padding: 18px 20px; font-weight: 700; list-style: none; display: flex; justify-content: space-between; align-items: center; gap: 16px; min-height: 44px; }
.pfaq summary::-webkit-details-marker { display: none; }
.pfaq summary .svg-icon { color: var(--accent-700); flex: none; transition: transform .2s var(--ease); }
.pfaq details[open] summary .svg-icon { transform: rotate(180deg); }
.pfaq details[open] summary { border-bottom: 1px solid var(--ink-200); }
.pfaq .pfaq__a { padding: 16px 20px; color: var(--ink-700); font-size: .95rem; }

/* ── Big CTA banner ─────────────────────────────────────────── */
.pcta { position: relative; overflow: hidden; background: linear-gradient(150deg, var(--brand-800), var(--brand-600));
  color: #fff; border-radius: var(--r-2xl); padding: clamp(36px, 5vw, 60px); text-align: center; box-shadow: var(--shadow-3); }
.pcta::before { content: ''; position: absolute; inset: 0; background-image: radial-gradient(rgb(255 255 255 / .06) 1px, transparent 1px); background-size: 24px 24px; }
.pcta > * { position: relative; }
.pcta h2 { font-family: var(--font-display); font-size: var(--fs-h2); }
.pcta p { color: rgb(255 255 255 / .88); max-width: 52ch; margin: 0 auto 24px; font-size: var(--fs-lead); }
.pcta__actions { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; }

/* ── Content pages (about / info long-form) ─────────────────── */
.pprose { max-width: 760px; }
.pprose h2 { font-family: var(--font-display); margin-top: 34px; font-size: 1.5rem; }
.pprose h3 { margin-top: 24px; font-size: 1.12rem; }
.pprose p, .pprose li { color: var(--ink-700); }
.pprose ul { padding-left: 20px; }
.page-hero { position: relative; overflow: hidden; background: linear-gradient(155deg, var(--brand-900), var(--brand-700)); color: #fff; padding: var(--space-10) 0; }
.page-hero::before { content: ''; position: absolute; inset: 0; background-image: radial-gradient(rgb(255 255 255 / .05) 1px, transparent 1px); background-size: 24px 24px; }
.page-hero .container { position: relative; }
.page-hero h1 { font-family: var(--font-display); font-size: var(--fs-h1); font-weight: 700; }
.page-hero p { color: rgb(255 255 255 / .84); font-size: var(--fs-lead); max-width: 60ch; margin: 0; }
.page-hero .crumbs { color: rgb(255 255 255 / .72); font-size: .85rem; margin-bottom: 12px; }
.page-hero .crumbs a { color: rgb(255 255 255 / .92); }
.page-hero--lg { padding: var(--space-12) 0; }

/* Photo hero: a background image behind the gradient scrim. Set the image on a
   .page-hero__bg child so it can carry loading hints; the ::after keeps text AA. */
.page-hero__bg { position: absolute; inset: 0; z-index: 0; }
.page-hero__bg img { width: 100%; height: 100%; object-fit: cover; object-position: center 35%; opacity: .5; }
.page-hero--photo::after { content: ''; position: absolute; inset: 0; z-index: 0;
  background: linear-gradient(115deg, rgb(71 10 18 / .92) 0%, rgb(107 15 27 / .78) 48%, rgb(138 19 36 / .55) 100%); }
.page-hero--photo .container { z-index: 1; }
.page-hero__eyebrow { display: inline-flex; align-items: center; gap: 8px; font-size: .8rem; font-weight: 700;
  background: rgb(255 255 255 / .12); border: 1px solid rgb(255 255 255 / .2); padding: 6px 13px; border-radius: var(--r-pill); margin-bottom: 14px; }
.page-hero__meta { display: flex; flex-wrap: wrap; gap: 10px 22px; margin-top: 18px; font-size: .9rem; color: rgb(255 255 255 / .85); }
.page-hero__meta span { display: inline-flex; align-items: center; gap: 7px; }
.page-hero__meta .svg-icon { color: var(--brand-400); }

/* ── Service-detail layout (content + hints sidebar) ────────── */
.svc-layout { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: var(--space-8); align-items: start; }
.svc-main { display: grid; gap: 18px; }
.svc-steps { list-style: none; counter-reset: sstep; margin: 0; padding: 0; display: grid; gap: 14px; }
.svc-steps li { counter-increment: sstep; display: grid; grid-template-columns: var(--ic-sm) 1fr; gap: 14px; align-items: start; }
.svc-docs { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.svc-docs li { display: flex; align-items: flex-start; gap: 10px; }
.svc-docs .svg-icon { color: var(--accent-600); flex: none; margin-top: 2px; }
.svc-chips { display: flex; flex-wrap: wrap; gap: 8px; }

/* Sticky hints / how-to sidebar.
   The offset was hard-coded at 84px against a --topbar-h of 112px, so the top
   28px of the "Responsible agency" panel sat permanently behind the translucent
   masthead. The comment on --topbar-h says every 64px offset was moved onto the
   token; this one was missed. */
.svc-side { display: grid; gap: 16px; position: sticky; top: calc(var(--topbar-h) + var(--space-5)); }
#main, .psection[id] { scroll-margin-top: calc(var(--topbar-h) + var(--space-4)); }
/* .svc-block, .svc-cta, .svc-hints and .svc-glance are the card system's
   --panel and --invert variants; see the CARD SYSTEM section. */
.svc-glance dl { margin: 0; display: grid; gap: 10px; }
.svc-glance dl > div { display: flex; justify-content: space-between; gap: 12px; font-size: .9rem; border-bottom: 1px dashed var(--ink-200); padding-bottom: 8px; }
.svc-glance dl > div:last-child { border-bottom: 0; padding-bottom: 0; }
.svc-glance dt { color: var(--ink-500); }
.svc-glance dd { margin: 0; font-weight: 700; text-align: right; }

/* ── Rich footer ────────────────────────────────────────────── */
.site-footer { background: var(--brand-950); color: rgb(255 255 255 / .72); margin-top: var(--space-12); }
.site-footer a { color: rgb(255 255 255 / .72); }
.site-footer a:hover { color: #fff; }
.sf-top { display: grid; grid-template-columns: 2fr 1fr 1fr 1.2fr; gap: 36px; padding: var(--space-10) 0 var(--space-6); }
/* The footer lockup is white because the component is asked for tone=inverse,
   which is what the brand guidelines govern — not because of a rule here. An
   earlier rule targeted .brand-lock .bl-name, the PREVIOUS lockup's class
   names; when the component was replaced by .lockup/.lockup__wordmark the
   selector stopped matching anything and the wordmark quietly went back to
   dark ink on a near-black bar. A colour override here would hide that from
   the contrast gate, so there deliberately is none. */
.sf-brand p { margin-top: 14px; font-size: .9rem; max-width: 38ch; }
.sf-social { display: flex; gap: 10px; margin-top: 18px; }
.sf-social a { width: 42px; height: 42px; border-radius: 11px; display: grid; place-items: center;
  background: rgb(255 255 255 / .08); border: 1px solid rgb(255 255 255 / .12); color: #fff; }
.sf-social a:hover { background: rgb(255 255 255 / .16); text-decoration: none; }
.sf-col h4 { color: #fff; font-size: .82rem; text-transform: uppercase; letter-spacing: .08em; margin-bottom: 14px; }
.sf-col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; font-size: .9rem; }
.sf-col ul a { display: inline-flex; padding: 5px 0; }
.sf-contact li { display: flex; gap: 10px; font-size: .9rem; margin-bottom: 12px; align-items: flex-start; }
.sf-contact .svg-icon { color: var(--brand-400); flex: none; margin-top: 2px; }
.sf-legal { border-top: 1px solid rgb(255 255 255 / .1); padding: 20px 0; display: flex; flex-wrap: wrap;
  gap: 10px 20px; justify-content: space-between; align-items: center; font-size: .84rem; }
.sf-legal nav { display: flex; flex-wrap: wrap; gap: 8px 18px; }
.sf-staff { color: rgb(255 255 255 / .55); font-size: .8rem; }

/* ── Notice / receipt documents ───────────────────────────── */
.doc-panel { max-width: 760px; margin: 0 auto; }
.doc-head { display: flex; justify-content: space-between; align-items: start; gap: 16px; padding-bottom: 16px; border-bottom: 2px solid var(--ink-900); margin-bottom: 18px; }
.kv { display: grid; grid-template-columns: auto 1fr; gap: 6px 18px; font-size: .93rem; }
.kv dt { color: var(--ink-500); font-weight: 600; }
.kv dd { margin: 0; font-weight: 600; color: var(--ink-900); }
.amount-hero { font-size: 2rem; font-weight: 800; letter-spacing: -.02em; }

/* ── Bottom nav (mobile, authed channels) ─────────────────── */
.bottom-nav {
  display: none; position: fixed; bottom: 0; left: 0; right: 0; z-index: 60;
  background: #fff; border-top: 1px solid var(--ink-200);
  padding: 6px env(safe-area-inset-right) calc(6px + env(safe-area-inset-bottom)) env(safe-area-inset-left);
}
/* WCAG 2.5.8 asks 24x24 CSS px for a target; 44x44 is the practical floor on a
   phone. 7px of padding round a 0.72rem label did not reach it, and this is the
   primary navigation for somebody paying a bill one-handed on a bus. */
.bottom-nav a {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; min-height: 52px; padding: 6px 2px; text-align: center;
  color: var(--ink-500); font-size: .7rem; font-weight: 600; line-height: 1.15;
  border-radius: var(--r-sm); text-decoration: none;
}
.bottom-nav a .bn-icon { display: block; font-size: 1.25rem; }
.bottom-nav a.active { color: var(--accent-700); background: var(--accent-50); }
.bottom-nav a:hover { text-decoration: none; }
.bottom-nav a:focus-visible { outline: 3px solid var(--accent-600); outline-offset: -3px; }

/* The support-chat button sat ON TOP of the last nav item, so "Account" was
   unreachable on every phone. The FAB now stands clear of the bar, and clears
   the iOS home indicator too. */
body.has-bottom-nav .scw { bottom: calc(72px + env(safe-area-inset-bottom)); }

/* ── Responsive ───────────────────────────────────────────── */

/* Tablet & down: 4-up grids become 2-up, collapse chrome.
   The card grids (.ptools__grid, .pspaces, .ppop, .pservices, .pjourneys,
   .psteps, .svc-cards, .sitems, .hspub-types) moved to the CARD SYSTEM
   section, which carries the whole ladder — 5/4/3 · 3 · 2 · 1 — in one place
   rather than in three media blocks a screen apart. */
@media (max-width: 1024px) {
  .pgallery { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 960px) {
  .lp-brand { display: none; }
  .grid-2, .grid-3, .split { grid-template-columns: 1fr; }

  /* Hamburger disclosure: the button appears, and the nav links become a
     full-width dropdown panel toggled by the #nav-toggle checkbox. */
  .nav-toggle-btn { display: inline-flex; }
  .topbar nav { position: static; }
  .topbar nav .nav-links {
    display: none; position: absolute; top: var(--topbar-h); left: 0; right: 0;
    background: #fff; border-bottom: 1px solid var(--ink-200); box-shadow: var(--shadow-2);
    flex-direction: column; padding: 8px; gap: 2px; z-index: 60;
  }
  .nav-toggle:checked ~ nav .nav-links { display: flex; }
  .topbar nav .nav-links a { display: block; padding: 12px 14px; border-radius: 10px; }
  .nav-signout { margin-top: 6px; text-align: center; }
  .nav-actions { margin-left: 0; margin-top: 4px; }
  .nav-actions .btn { flex: 1; justify-content: center; }

  /* Mega-nav collapses into the hamburger sheet as a stacked accordion.
     The topbar nav override (position:static above) is undone here so the
     sheet drops from under the bar; everything is forced full-width so no
     desktop dropdown width can leak past the viewport. */
  .topbar nav.meganav { position: absolute; top: var(--topbar-h); left: 0; right: 0; display: none;
    flex-direction: column; align-items: stretch; margin: 0; gap: 0; width: 100%; max-width: 100vw;
    background: #fff; border-bottom: 1px solid var(--ink-200); box-shadow: var(--shadow-2);
    max-height: calc(100vh - 64px); overflow-y: auto; overflow-x: hidden; padding: 8px; z-index: 60; }
  .nav-toggle:checked ~ nav.meganav { display: flex; }
  .meganav__list { flex-direction: column; align-items: stretch; gap: 2px; width: 100%; }
  .meganav__item { position: static; width: 100%; }
  .meganav__top { justify-content: space-between; width: 100%; border-radius: 10px; }
  /* On mobile the desktop absolute dropdowns become inline expandable blocks —
     hidden until the item carries .is-open (JS tap-to-expand). Neutralise the
     desktop hover/focus opens so nothing shows before the sheet is opened. */
  .meganav__drop, .meganav__drop--wide {
    position: static !important; transform: none !important; opacity: 1; visibility: visible;
    width: auto !important; max-width: 100%; box-shadow: none; border: 0; border-radius: 0;
    padding: 2px 6px 10px 12px; display: none; }
  .meganav__item.has-drop:hover > .meganav__drop,
  .meganav__item.has-drop:focus-within > .meganav__drop { display: none; }
  .meganav__item.has-drop.is-open > .meganav__drop { display: block; }
  .meganav__panel { width: auto; }
  .meganav__cols { grid-template-columns: 1fr; gap: 0; }
  .meganav__col { min-width: 0; }
  .meganav__feature { width: auto; margin-top: 8px; }
  .meganav__cta { flex-direction: column; margin: 8px 6px 4px; width: 100%; }
  .meganav__cta .btn { width: 100%; justify-content: center; }
  .meganav__item.has-drop > .meganav__top .svg-icon { transition: none; }

  /* Bottom-nav is only shown when a controller actually populates it. */
  body.has-bottom-nav .bottom-nav { display: flex; }
  body.has-bottom-nav { padding-bottom: 72px; }

  /* The negative overlap is clamp()-based so it never collides with the hero
     stats; the tool GRID is in the CARD SYSTEM section. */
  .ptools { margin-top: -32px; }
  .pband { grid-template-columns: 1fr; }
  .papp { grid-template-columns: 1fr; text-align: center; gap: var(--space-6); }
  .papp p { margin-left: auto; margin-right: auto; }
  .papp__badges { justify-content: center; }
  .pbrowse { grid-template-columns: 1fr; gap: var(--space-6); }
  .pstats { grid-template-columns: repeat(3, 1fr); gap: 20px; }
  .svc-layout { grid-template-columns: 1fr; }
  .svc-side { position: static; order: -1; }
  .sf-top { grid-template-columns: 1fr 1fr; gap: 28px; }
  .sf-brand { grid-column: 1 / -1; }
  .brand-lock .bl-sub { display: none; }
}

/* Phone: everything stacks to one column. */
@media (max-width: 640px) {
  .pgallery { grid-template-columns: 1fr; }
  /* Banner: shorter, tighter padding, and the photo credit steps aside so it
     can never crowd the heading on a narrow screen. */
  .pbanner { min-height: 300px; border-radius: var(--r-lg); }
  .pbanner__body { padding: 22px 20px 24px; }
  .pbanner__shot figcaption { right: 12px; top: 12px; font-size: .68rem; padding: 5px 10px; }
  .pstats { grid-template-columns: 1fr; gap: 18px; }
  .phero__inner { padding: var(--space-10) 20px var(--space-8); }
  .phero__title { max-width: 100%; }
  .phero__meta { gap: 12px 24px; }
  .pshell { padding: 0 20px; }
  .sf-top { grid-template-columns: 1fr; }
  .sf-legal { justify-content: flex-start; gap: 8px 16px; }
  .paud { display: flex; width: 100%; }
  .paud label { flex: 1; justify-content: center; }
  .papp__device { width: 168px; }
  .pbrowse__links { columns: 1; }
}

/* Small phones: tighten hero type + padding. */
@media (max-width: 380px) {
  .phero__inner { padding: var(--space-8) 16px var(--space-6); }
  .pshell, .container { padding: 0 16px; }
  .psearch { padding: 6px 6px 6px 14px; }
}

/* Large desktop: keep hero/band content aligned to the wider page column. */
@media (min-width: 1360px) {
  .phero__inner, .pshell { padding-left: 32px; padding-right: 32px; }
}

/* ============================================================
   DASHBOARD component layer — premium, CSP-safe (no Chart.js CDN).
   Stat cards w/ icon+trend, CSS donut gauge, bar chart, sparkline,
   ATM wallet card, KPI tiles, progress bars, alert feed, quick-tiles.
   ============================================================ */

/* Page scaffolding */
.dash { padding: 28px 0 64px; }
.dash-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 16px; flex-wrap: wrap; margin-bottom: 22px; }
.dash-head h1 { font-family: var(--font-display); font-size: 1.7rem; margin: 0 0 4px; }
.dash-head .muted { margin: 0; }
.dash-grid { display: grid; gap: 18px; }
.dash-cols-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
.dash-cols-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; }
.dash-split { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); gap: 18px; align-items: start; }
.dash-cols-2 > *, .dash-cols-3 > *, .dash-split > * { min-width: 0; }

/* Rich stat cards (icon chip + value + trend) */
.dstats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; }
.dstat { background: #fff; border: 1px solid var(--ink-200); border-radius: var(--r-lg); padding: 20px; box-shadow: var(--shadow-1);
  position: relative; overflow: hidden; }
.dstat__ic { width: 44px; height: 44px; border-radius: 12px; display: grid; place-items: center;
  background: var(--accent-50); color: var(--accent-700); margin-bottom: 14px; }
.dstat__label { font-size: .78rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-500); }
.dstat__value { font-family: var(--font-display); font-size: 1.7rem; font-weight: 700; letter-spacing: -.02em; margin-top: 3px; font-variant-numeric: tabular-nums; }
.dstat__sub { font-size: .84rem; color: var(--ink-500); margin-top: 3px; }
.dstat__trend { display: inline-flex; align-items: center; gap: 4px; font-size: .8rem; font-weight: 700; margin-top: 8px; }
.dstat__trend--up { color: var(--ok-600); }
.dstat__trend--down { color: var(--bad-600); }
.dstat__trend--flat { color: var(--ink-500); }
.dstat--accent .dstat__ic { background: var(--accent-600); color: #fff; }

/* KPI tiles (compact, colored) */
.kpi-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; }
.kpi { background: #fff; border: 1px solid var(--ink-200); border-left: 4px solid var(--accent-600); border-radius: var(--r-md); padding: 16px 18px; }
.kpi b { display: block; font-family: var(--font-display); font-size: 1.5rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.kpi span { font-size: .82rem; color: var(--ink-500); font-weight: 600; }

/* The collapse comes AFTER the base rules it overrides. It used to sit in the
   960px and 640px blocks above them, and a media query adds no specificity,
   so the later base rule won at every width: thirteen portal pages kept two
   squeezed columns on a phone and pushed the page sideways. minmax(0, …)
   lets a track shrink below its longest word or nowrap button. */
@media (max-width: 960px) {
  .dstats, .kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dash-cols-3, .dash-cols-2, .dash-split { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 640px) {
  .dstats, .kpi-grid { grid-template-columns: minmax(0, 1fr); }
}

/* Panels */
.panel { background: #fff; border: 1px solid var(--ink-200); border-radius: var(--r-lg); box-shadow: var(--shadow-1); }
.panel__head { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 16px 20px; border-bottom: 1px solid var(--ink-200); }
.panel__head h2, .panel__head h3 { margin: 0; font-size: .97rem; }
.panel__body { padding: 20px; }
.panel__body--flush { padding: 0; }

/* CSS donut gauge (conic-gradient; --val is 0-100, --col the ring color) */
.donut { --val: 0; --col: var(--brand-600); --track: var(--ink-100); position: relative; width: 150px; height: 150px; margin: 0 auto; }
.donut__ring { width: 100%; height: 100%; border-radius: 50%;
  background: conic-gradient(var(--col) calc(var(--val) * 1%), var(--track) 0); }
.donut__ring::after { content: ''; position: absolute; inset: 16px; border-radius: 50%; background: #fff; box-shadow: inset 0 1px 3px rgb(26 22 23 / .06); }
.donut__c { position: absolute; inset: 0; display: grid; place-items: center; text-align: center; }
.donut__c b { font-family: var(--font-display); font-size: 1.7rem; line-height: 1; }
.donut__c span { font-size: .74rem; color: var(--ink-500); }

/* Multi-segment donut via layered conic-gradient (set inline --seg) */
.donut--multi .donut__ring { background: conic-gradient(var(--seg, var(--brand-600) 0 100%)); }

/* Legend for charts */
.chart-legend { display: grid; gap: 8px; margin-top: 4px; }
.chart-legend li { display: flex; align-items: center; gap: 9px; font-size: .88rem; list-style: none; }
.chart-legend .dot { width: 11px; height: 11px; border-radius: 3px; flex: none; }
.chart-legend .lg-val { margin-left: auto; font-weight: 700; font-variant-numeric: tabular-nums; }

/* Bar chart (pure CSS; each .bar has --h 0-100) */
.barchart { display: flex; align-items: flex-end; gap: 8px; height: 160px; padding-top: 8px; }
.barchart .bar { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 6px; height: 100%; }
.barchart .bar > i { width: 100%; max-width: 34px; border-radius: 6px 6px 0 0; background: linear-gradient(180deg, var(--accent-500, var(--brand-500)), var(--accent-700, var(--brand-700)));
  height: calc(var(--h) * 1%); min-height: 3px; transition: height .4s var(--ease); }
.barchart .bar small { font-size: .72rem; color: var(--ink-500); }

/* Sparkline / trend line (SVG-friendly wrapper) */
.spark { width: 100%; height: 56px; display: block; }
.spark path { fill: none; stroke: var(--accent-600); stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
.spark .area { fill: var(--accent-50); stroke: none; }

/* Progress bar */
.progress { height: 12px; border-radius: 999px; background: var(--ink-100); overflow: hidden; }
.progress > i { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--brand-500), var(--brand-700)); width: calc(var(--val) * 1%); transition: width .5s var(--ease); }
.progress-row { display: flex; justify-content: space-between; font-size: .85rem; margin-bottom: 6px; }
.progress-row b { font-variant-numeric: tabular-nums; }

/* ATM-style wallet card */
.wallet { border-radius: var(--r-xl); padding: 22px; color: #fff; position: relative; overflow: hidden;
  background: linear-gradient(135deg, var(--brand-800), var(--brand-600)); box-shadow: var(--shadow-3); }
.wallet::after { content: ''; position: absolute; right: -40px; top: -40px; width: 160px; height: 160px; border-radius: 50%; background: rgb(255 255 255 / .08); }
.wallet__top { display: flex; justify-content: space-between; align-items: center; position: relative; }
.wallet__brand { font-weight: 800; letter-spacing: .02em; }
.wallet__chip { width: 38px; height: 28px; border-radius: 6px; background: linear-gradient(135deg, var(--abia-gold-200), var(--abia-gold-400)); }
.wallet__bal { font-family: var(--font-display); font-size: 2rem; font-weight: 700; margin: 18px 0 2px; font-variant-numeric: tabular-nums; }
.wallet__label { font-size: .78rem; color: rgb(255 255 255 / .78); text-transform: uppercase; letter-spacing: .06em; }
.wallet__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 16px; }
.wallet__mini { background: rgb(255 255 255 / .12); border-radius: var(--r-md); padding: 12px 14px; }
.wallet__mini span { font-size: .72rem; color: rgb(255 255 255 / .78); text-transform: uppercase; letter-spacing: .05em; }
.wallet__mini b { display: block; font-size: 1.1rem; font-weight: 800; margin-top: 2px; font-variant-numeric: tabular-nums; }
.wallet__no { margin-top: 16px; font-family: var(--mono); letter-spacing: .12em; font-size: .95rem; }

/* .qtiles / .qtile / .qtile__ic were deleted: no view, component or script in
   resources/ or public_html/ renders them. The portal's own .pmod does this
   job and is the one that ships. */

/* Alert / notification feed */
.feed { display: grid; gap: 2px; }
.feed__item { display: flex; gap: 12px; padding: 14px 20px; border-bottom: 1px solid var(--ink-100); }
.feed__item:last-child { border-bottom: 0; }
.feed__ic { width: 36px; height: 36px; border-radius: 10px; flex: none; display: grid; place-items: center; background: var(--accent-50); color: var(--accent-700); }
.feed__ic--warn { background: var(--warn-100); color: var(--warn-600); }
.feed__ic--ok { background: var(--ok-100); color: var(--ok-600); }
.feed__ic--bad { background: var(--bad-100); color: var(--bad-600); }
.feed__b b { display: block; font-size: .92rem; }
.feed__b span { font-size: .84rem; color: var(--ink-500); }
.feed__b time { font-size: .76rem; color: var(--ink-400); }

/* Chip row */
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chip { display: inline-flex; align-items: center; gap: 6px; background: var(--ink-100); color: var(--ink-700); font-size: .82rem; font-weight: 600; padding: 6px 12px; border-radius: 999px; }
.chip--star { background: var(--accent-50); color: var(--accent-700); }

/* Data table refinements for dashboards */
.dtable { width: 100%; border-collapse: collapse; }
.dtable th, .dtable td { text-align: left; padding: 11px 20px; font-size: .9rem; border-bottom: 1px solid var(--ink-100); }
.dtable th { font-size: .74rem; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-500); font-weight: 700; background: var(--ink-50); }
.dtable tbody tr:hover { background: var(--ink-50); }
.dtable .num { text-align: right; font-variant-numeric: tabular-nums; }

/* ── AI support chat widget (scw-*) ─────────────────────────── */
.scw { position: fixed; right: max(20px, calc(env(safe-area-inset-right, 0px) + 12px)); bottom: max(20px, calc(env(safe-area-inset-bottom, 0px) + 12px)); z-index: 900; }
.scw-fab { position: relative; width: 60px; height: 60px; border-radius: 50%; border: 0; cursor: pointer;
  background: linear-gradient(160deg, var(--brand-600), var(--brand-800)); color: #fff;
  box-shadow: 0 12px 30px rgb(71 10 18 / .4); display: grid; place-items: center;
  transition: transform .18s var(--ease), box-shadow .18s var(--ease); }
@media (hover: hover) {
  .scw-fab:hover { transform: translateY(-2px); box-shadow: 0 16px 36px rgb(71 10 18 / .5); }
}
.scw-fab__close { display: none; }
.scw.is-open .scw-fab__open { display: none; }
.scw.is-open .scw-fab__close { display: block; }
.scw-fab__badge { position: absolute; top: -3px; right: -3px; min-width: 20px; height: 20px; padding: 0 5px;
  border-radius: 10px; background: var(--bad-600); color: #fff; font-size: .72rem; font-weight: 800;
  display: grid; place-items: center; box-shadow: 0 0 0 2px #fff; }

.scw-panel { position: absolute; right: 0; bottom: 74px; width: 380px; max-width: calc(100vw - 40px);
  height: 560px; max-height: calc(100vh - 110px); background: #fff; border-radius: var(--r-lg);
  box-shadow: 0 24px 60px rgb(71 10 18 / .4); display: flex; flex-direction: column; overflow: hidden;
  border: 1px solid var(--ink-200); animation: scw-in .2s var(--ease) both; }
@keyframes scw-in { from { opacity: 0; transform: translateY(12px) scale(.98); } to { opacity: 1; transform: none; } }
.scw-panel[hidden] { display: none; }

.scw-head { display: flex; align-items: center; gap: 12px; padding: 14px 16px; color: #fff;
  background: linear-gradient(120deg, var(--brand-900), var(--brand-700)); }
.scw-head__ava { width: 38px; height: 38px; border-radius: 11px; display: grid; place-items: center;
  background: rgb(255 255 255 / .16); color: #fff; }
.scw-head__t { flex: 1; line-height: 1.2; }
.scw-head__t b { display: block; font-size: .98rem; }
.scw-head__t small { font-size: .78rem; color: rgb(255 255 255 / .8); }
.scw-head__x { background: transparent; border: 0; color: #fff; cursor: pointer; padding: 4px; border-radius: 8px; }
.scw-head__x:hover { background: rgb(255 255 255 / .14); }

.scw-log { flex: 1; overflow-y: auto; padding: 16px; background: var(--ink-50); display: flex; flex-direction: column; gap: 10px; }
.scw-msg { max-width: 84%; padding: 10px 14px; border-radius: 14px; font-size: .9rem; line-height: 1.5; white-space: pre-wrap; word-wrap: break-word; }
.scw-msg__who { font-size: .68rem; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-400); margin-bottom: 3px; }
.scw-msg--me { align-self: flex-end; background: var(--brand-700); color: #fff; border-bottom-right-radius: 4px; }
.scw-msg--them { align-self: flex-start; background: #fff; border: 1px solid var(--ink-200); color: var(--ink-900); border-bottom-left-radius: 4px; }
.scw-msg--staff { align-self: flex-start; background: var(--brand-50); border: 1px solid var(--brand-100); color: var(--brand-900); }
.scw-msg a { color: inherit; text-decoration: underline; }
.scw-msg--me a { color: #fff; }

.scw-typing { display: flex; gap: 4px; padding: 4px 20px 8px; }
.scw-typing[hidden] { display: none; }
.scw-typing span { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-300); animation: scw-bounce 1.2s infinite; }
.scw-typing span:nth-child(2) { animation-delay: .2s; }
.scw-typing span:nth-child(3) { animation-delay: .4s; }
@keyframes scw-bounce { 0%, 60%, 100% { transform: translateY(0); opacity: .5; } 30% { transform: translateY(-5px); opacity: 1; } }

.scw-quick { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 12px 4px; border-top: 1px solid var(--ink-200); }
.scw-quick[hidden] { display: none; }
.scw-quick button { border: 1px solid var(--ink-200); background: #fff; color: var(--brand-700); font-weight: 600;
  font-size: .8rem; padding: 6px 11px; border-radius: 999px; cursor: pointer; }
.scw-quick button:hover { background: var(--brand-50); border-color: var(--brand-100); }

.scw-form { display: flex; align-items: center; gap: 6px; padding: 10px 12px; border-top: 1px solid var(--ink-200); }
.scw-human, .scw-send { flex: none; width: 38px; height: 38px; border-radius: 10px; border: 0; cursor: pointer; display: grid; place-items: center; }
/* --ink-600 was referenced here and defined in NO stylesheet, so this glyph
   inherited whatever colour it happened to sit in. --ink-700 is the step this
   button wants — 8.97:1 on --ink-100 — and it is a real token. */
.scw-human { background: var(--ink-100); color: var(--ink-700); }
.scw-human:hover { background: var(--ink-200); }
.scw-send { background: var(--brand-700); color: #fff; }
.scw-send:hover { background: var(--brand-800); }
.scw-form input { flex: 1; min-width: 0; border: 1px solid var(--ink-200); border-radius: 10px; padding: 9px 12px; font: inherit; font-size: .9rem; outline: 0; }
.scw-form input:focus { border-color: var(--brand-400); }
.scw-legal { margin: 0; padding: 0 14px 10px; font-size: .7rem; color: var(--ink-400); text-align: center; }

@media (max-width: 480px) {
  .scw { right: max(14px, env(safe-area-inset-right, 0px)); bottom: max(14px, calc(env(safe-area-inset-bottom, 0px) + 8px)); }
  .scw-panel { width: calc(100vw - 24px); height: calc(100vh - 90px); bottom: 70px; }
}

/* ============================================================
   PORTAL SHELL — the signed-in taxpayer app chrome (masthead, app
   bar, footer) and the portal module kit. Distinct from the public
   site. The tab bar, More sheet, panels and toasts are shell.css
   primitives; this block only places the portal's own pieces around
   them (contract 1.2).
   ============================================================ */
/* overflow-x: clip, not the global hidden. `hidden` makes <body> a scroll
   container that never scrolls, and a sticky element sticks to its nearest
   scroll container — so the masthead scrolled away with the page. `clip` cuts
   off stray horizontal overflow without creating one. Measured: the header sat
   at -300px after a 300px scroll; with clip it holds at 0. A browser without
   clip keeps `visible` — sticky still works, and html's own overflow-x:hidden
   still stops the page panning sideways. */
.pbody { background: var(--ink-50); display: flex; flex-direction: column; min-height: 100vh; min-height: 100dvh; overflow-x: visible; overflow-x: clip; }

/* ── Masthead and app bar ──────────────────────────────────────
   Sticky, dark red, and never the ancestor of anything fixed: the layout
   keeps every fixed surface a direct child of <body>. The top inset applies
   always — it is 0 in a browser tab, and installed it keeps the white status
   bar text off the wordmark. view-transition-name lets the bar hold still
   while the page changes beneath it; the bar has no fixed descendants. */
.phdr { position: sticky; top: 0; z-index: 80; padding-top: env(safe-area-inset-top, 0px); background: rgb(71 10 18 / .96); -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); border-bottom: 1px solid rgb(255 255 255 / .08); color: var(--abia-red-50); view-transition-name: apx-appbar; }
.phdr__row { max-width: var(--pcol-max, 1164px); margin: 0 auto; padding: 0 max(22px, env(safe-area-inset-right, 0px)) 0 max(22px, env(safe-area-inset-left, 0px)); height: var(--topbar-h); display: flex; align-items: center; gap: 10px; }
.phdr .lockup { flex: none; }
.phdr__nav { display: flex; align-items: center; gap: 2px; margin-left: 4px; }
.phdr__dd { position: relative; }
.phdr__dd > summary { list-style: none; cursor: pointer; }
.phdr__dd > summary::-webkit-details-marker { display: none; }
.phdr__link { display: inline-flex; align-items: center; gap: 5px; padding: 8px 13px; border-radius: var(--r-pill); color: var(--abia-red-100); text-decoration: none; font-size: .875rem; font-weight: 600; white-space: nowrap; transition: background .15s var(--ease), color .15s var(--ease); }
.phdr__link:hover, .phdr__dd[open] > .phdr__link { background: rgb(255 255 255 / .1); color: #fff; }
.phdr__link.is-active { background: rgb(255 255 255 / .14); color: #fff; box-shadow: inset 0 -2px 0 var(--gold-500); }
.phdr__spacer { flex: 1; }
.phdr__iconbtn { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; border-radius: 50%; color: var(--abia-red-100); position: relative; cursor: pointer; }
.phdr__iconbtn:hover, .phdr__bell[open] .phdr__iconbtn { background: rgb(255 255 255 / .1); color: #fff; }
/* Gold on the masthead, not red: a red pill on dark red is a 1.9:1 smudge, and
   gold is the palette's own emphasis colour. Dark ink on it reads 10.9:1, and
   the ring separates the pill from the bell beneath it. */
.phdr .apx-badge { background: var(--abia-gold-400); color: var(--abia-neutral-950); box-shadow: 0 0 0 2px var(--abia-red-950); }
.phdr__iconbtn > .apx-badge { position: absolute; top: 3px; right: 1px; }
.phdr__who { display: inline-flex; align-items: center; gap: 9px; padding: 5px 10px 5px 5px; border-radius: var(--r-pill); color: var(--abia-red-50); cursor: pointer; }
.phdr__who:hover, .phdr__me[open] .phdr__who { background: rgb(255 255 255 / .1); }
.phdr__ava { position: relative; overflow: hidden; width: 32px; height: 32px; border-radius: 50%; background: var(--gold-500); color: var(--abia-gold-950); display: inline-flex; align-items: center; justify-content: center; font-weight: 700; font-size: .8rem; flex: none; }
/* Layered over the initials: an image that never arrives leaves them showing. */
.phdr__ava-photo { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.phdr__whot { display: flex; flex-direction: column; line-height: 1.15; text-align: left; }
.phdr__whot b { font-size: .82rem; max-width: 170px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.phdr__whot small { font-size: .66rem; color: var(--abia-red-300); letter-spacing: .04em; }
.phdr__panel { position: absolute; left: 0; top: calc(100% + 10px); min-width: 250px; background: #fff; border: 1px solid var(--ink-200); border-radius: var(--r-md); box-shadow: var(--shadow-3); padding: 6px; z-index: 90; }
.phdr__panel--right { left: auto; right: 0; }
/* dvh after vh: Safari measures vh against the viewport WITHOUT its toolbar, so
   on an iPad the last notifications sat under the toolbar, below where the
   list could scroll them into view. */
.phdr__panel--wide { width: min(360px, calc(100vw - 28px)); max-height: min(520px, calc(100vh - var(--topbar-h) - 32px)); max-height: min(520px, calc(100dvh - var(--topbar-h) - 32px)); padding: 0; overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; }
.phdr__panelhead { padding: 11px 14px; font-size: .74rem; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: var(--ink-500); border-bottom: 1px solid var(--ink-100); }
.phdr__mi { display: flex; align-items: center; gap: 10px; padding: 9px 11px; border-radius: 8px; text-decoration: none; color: var(--ink-900); font-size: .86rem; font-weight: 500; }
.phdr__mi .svg-icon { color: var(--accent-700); }
.phdr__mi:hover { background: var(--ink-100); text-decoration: none; }
.phdr__mi.is-active { background: var(--accent-50); color: var(--accent-700); font-weight: 600; }
/* The global focus ring is the accent at 45% — red on this dark red bar, where
   a keyboard user could not see which link they were on. Gold is the palette's
   emphasis colour and stands out on the masthead; inside the white dropdowns
   the ring turns red again and is drawn inward, so the panel's own clipping
   cannot cut it off. (.apx-iconbtn keeps shell.css's currentColor ring.) */
.phdr :focus-visible { outline: 3px solid var(--abia-gold-400); outline-offset: 2px; }
.phdr__panel :focus-visible { outline-color: var(--abia-red-800); outline-offset: -3px; }
/* An iPad held landscape is 1024px or wider and gets the desktop header, but it
   is still driven by a finger: the menu links and rows measured 37px. */
@media (pointer: coarse) {
  .phdr__link, .phdr__mi, .phdr__who { min-height: 44px; }
}
.phdr__mi--out { color: var(--bad-600); }
.phdr__mi--out .svg-icon { color: var(--bad-600); }
/* Phone pieces, off until a breakpoint turns them on: the page title of the
   compact bar and the bell that opens the bottom panel. The Back arrow and the
   drawer button are shell.css's (.apx-back, [data-apx-open="apx-more"]). */
/* (0,2,0): shell.css loads later and gives every .apx-iconbtn display:inline-flex. */
.phdr__row > .phdr__title, .phdr__row > .phdr__bellbtn { display: none; }
.phdr__row > .phdr__title { margin: 0 0 0 8px; color: #fff; }

/* Notifications — one list, in the dropdown and in the phone panel. */
.pnote { display: flex; gap: 11px; padding: 11px 14px; text-decoration: none; color: var(--ink-900); border-bottom: 1px solid var(--ink-100); }
.pnote:last-child { border-bottom: 0; }
.pnote:hover { background: var(--ink-50); text-decoration: none; }
.pnote__ic { width: 32px; height: 32px; flex: none; border-radius: 9px; display: inline-flex; align-items: center; justify-content: center; background: var(--info-100); color: var(--info-600); }
.pnote--warn .pnote__ic { background: var(--warn-100); color: var(--warn-600); }
.pnote--ok .pnote__ic { background: var(--ok-100); color: var(--ok-600); }
.pnote--bad .pnote__ic { background: var(--bad-100); color: var(--bad-600); }
.pnote b { display: block; font-size: .86rem; }
.pnote small { color: var(--ink-500); font-size: .78rem; line-height: 1.35; display: block; margin-top: 2px; }
/* In the panel each row bleeds to the edge, so the whole width is the target. */
.pnotes { margin: 0 -16px; border-top: 1px solid var(--ink-100); }
.pnotes .pnote { min-height: 64px; padding: 12px 16px; align-items: center; }
.pnotes__empty { display: flex; align-items: center; gap: 12px; margin: 0; padding: 16px 14px; color: var(--ink-700); }
.pnotes__empty .svg-icon { flex: none; color: var(--success-fg); }
.pnotes__empty b { display: block; font-size: .92rem; color: var(--ink-900); }
.pnotes__empty small { display: block; margin-top: 2px; font-size: .8rem; color: var(--ink-500); }
.pnotes .pnotes__empty { padding: 22px 16px; }

/* Main area */
.pmain { flex: 1; width: 100%; max-width: var(--pcol-max, 1164px); margin: 0 auto; padding: 26px max(22px, env(safe-area-inset-right, 0px)) 48px max(22px, env(safe-area-inset-left, 0px)); }
/* SCOPED to .pmain: `.phead` is also the PUBLIC-SITE section header (see the
   section-scaffolding block above). Unscoped, this two-column grid won rules
   by source order and broke every landing-page heading into a narrow column
   with the h2 floated beside it. Portal pages always render inside .pmain. */
.pmain .phead { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: end; gap: 14px 18px; margin: 2px 0 20px; }
.pmain .phead h1 { margin: 0 0 4px; font-size: 1.5rem; letter-spacing: -.01em; }
.pmain .phead .muted { margin: 0; }
.phead__actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: flex-end; }
@media (max-width: 700px) { .pmain .phead { grid-template-columns: minmax(0, 1fr); } .phead__actions { justify-content: flex-start; } }

/* Portal footer — one slim bottom bar for signed-in pages */
.pfoot { border-top: 1px solid var(--ink-200); background: #fff; }
.pfoot__inner { max-width: var(--pcol-max, 1164px); margin: 0 auto; padding: 12px max(22px, env(safe-area-inset-right, 0px)) calc(12px + env(safe-area-inset-bottom, 0px)) max(22px, env(safe-area-inset-left, 0px)); display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 22px; font-size: .78rem; color: var(--ink-500); }
.pfoot__links { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 18px; }
.pfoot__links a { color: var(--ink-500); text-decoration: none; }
.pfoot__links a:hover { color: var(--accent-700); text-decoration: underline; }
/* Footer Sign out. Below 1024px the account menu is hidden and Sign out lives
   in the More sheet. A page with no sheet gets this footer copy as its only
   way out, so there it always shows. Where a sheet exists the layout marks the
   copy --fallback, and the sheet's own Sign out is then always on offer: with
   shell.js running (html.apx-js) it is pinned in the foot of the sheet that
   More opens; without it (scripting off, or the file blocked, lost on a weak
   signal or bailing on an old browser) shell.css lays the sheet out in flow at
   the page foot. Hiding the copy only under (scripting: none) missed every case
   but the first, and a blocked shell.js stacked two Sign outs at every width
   below 1024px and on landscape phones. The html:not(.apx-js) rule repeats
   shell.css's in-flow query exactly, so the two files cannot disagree about
   which Sign out is on screen. */
.pfoot__links .pfoot__out { display: none; align-items: center; gap: 6px; min-height: 44px; color: var(--bad-600); font-weight: 700; }
.pfoot__links a.pfoot__out:hover { color: var(--bad-600); }
@media (max-width: 1023.98px) {
  .pfoot__links .pfoot__out { display: inline-flex; }
  html.apx-js .pfoot__links .pfoot__out--fallback { display: none; }
}
@media (max-width: 1023.98px), (max-height: 500px) and (orientation: landscape) {
  html:not(.apx-js) .pfoot__links .pfoot__out--fallback { display: none; }
}

/* Module tiles (dashboard services grid) — uniform height, corner count badge */
.pmods { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); grid-auto-rows: 1fr; gap: 12px; }
.pmod { position: relative; display: flex; flex-direction: column; gap: 8px; padding: 16px; background: #fff; border: 1px solid var(--ink-200); border-radius: var(--r-md); text-decoration: none; color: var(--ink-900); box-shadow: var(--shadow-1); transition: transform .16s var(--ease), box-shadow .16s var(--ease), border-color .16s var(--ease); }
/* Lift only where a pointer can hover. On touch, :hover sticks after a tap, so
   the tile a citizen just pressed stayed raised and outlined on the next page. */
@media (hover: hover) {
  .pmod:hover { transform: translateY(-2px); box-shadow: var(--shadow-2); border-color: var(--accent-600); }
}
.pmod__ic { width: 40px; height: 40px; border-radius: 11px; display: inline-flex; align-items: center; justify-content: center; background: var(--accent-50); color: var(--accent-700); flex: none; }
.pmod b { font-size: .92rem; line-height: 1.3; }
.pmod small { color: var(--ink-500); font-size: .78rem; line-height: 1.45; }
.pmod__count { position: absolute; top: 12px; right: 12px; font-size: .72rem; font-weight: 700; color: var(--accent-700); background: var(--accent-50); border: 1px solid var(--accent-100); border-radius: var(--r-pill); padding: 1px 8px; }

/* Forms (module wizards). The portal form kit styles RAW input/select/textarea
   inside .pform / .pform-stack / .pfieldset so views never need per-field
   classes. One field language: 44px control, soft border, accent focus ring. */
.pform { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 18px; }
.pform .full { grid-column: 1 / -1; }
.pform-stack { display: grid; gap: 14px; }
.pform label, .pform-stack label { display: block; font-size: .82rem; font-weight: 600; color: var(--ink-700); letter-spacing: .01em; }
.pform label > input, .pform label > select, .pform label > textarea,
.pform-stack label > input, .pform-stack label > select, .pform-stack label > textarea { margin-top: 7px; }
.pform .hint, .pform-stack .hint { font-weight: 400; color: var(--ink-500); font-size: .76rem; }

.pform input:not([type="checkbox"]):not([type="radio"]),
.pform select, .pform textarea,
.pform-stack input:not([type="checkbox"]):not([type="radio"]),
.pform-stack select, .pform-stack textarea,
.pfieldset input:not([type="checkbox"]):not([type="radio"]),
.pfieldset select, .pfieldset textarea {
  width: 100%; min-height: 44px; padding: 10px 14px;
  border: 1.5px solid var(--ink-300); border-radius: 10px;
  font: 400 .92rem/1.45 var(--font); color: var(--ink-900); background: #fff;
  transition: border-color .13s var(--ease), box-shadow .13s var(--ease), background .13s var(--ease);
}
.pform select, .pform-stack select, .pfieldset select {
  appearance: none; -webkit-appearance: none; padding-right: 40px; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 14px center;
}
.pform textarea, .pform-stack textarea, .pfieldset textarea { min-height: 88px; resize: vertical; }
.pform input:hover, .pform select:hover, .pform textarea:hover,
.pform-stack input:hover, .pform-stack select:hover, .pform-stack textarea:hover,
.pfieldset input:hover, .pfieldset select:hover, .pfieldset textarea:hover { border-color: var(--ink-400); }
.pform input:focus, .pform select:focus, .pform textarea:focus,
.pform-stack input:focus, .pform-stack select:focus, .pform-stack textarea:focus,
.pfieldset input:focus, .pfieldset select:focus, .pfieldset textarea:focus {
  outline: none; border-color: var(--accent-600);
  box-shadow: 0 0 0 3.5px color-mix(in srgb, var(--accent-600) 16%, transparent);
}
.pform input::placeholder, .pform textarea::placeholder,
.pform-stack input::placeholder, .pform-stack textarea::placeholder,
.pfieldset input::placeholder, .pfieldset textarea::placeholder { color: var(--ink-400); }
.pform input:disabled, .pform select:disabled,
.pform-stack input:disabled, .pform-stack select:disabled { background: var(--ink-50); color: var(--ink-500); cursor: not-allowed; }
.pform input[aria-invalid="true"], .pform-stack input[aria-invalid="true"] { border-color: var(--bad-600); }
.pform input[type="checkbox"], .pform-stack input[type="checkbox"],
.pform input[type="radio"], .pform-stack input[type="radio"],
.pcheck input[type="checkbox"] { width: 17px; height: 17px; accent-color: var(--accent-600); flex: none; cursor: pointer; }
.pform input[type="date"], .pform-stack input[type="date"] { color-scheme: light; }

/* Inline checkbox row (preferences, consents) */
.pcheck { display: flex; align-items: center; gap: 10px; padding: 8px 0; font-size: .88rem; color: var(--ink-900); cursor: pointer; }
.pcheck:hover { color: var(--accent-700); }

/* Fieldset cards — the wizard "step" surfaces */
.pfieldset { border: 1px solid var(--ink-200); border-radius: var(--r-lg); background: #fff; box-shadow: var(--shadow-1); padding: 22px; margin: 0 0 18px; min-width: 0; }
.pfieldset > legend { font-weight: 700; font-size: .95rem; padding: 2px 10px; margin-left: -10px; color: var(--ink-900); background: #fff; border-radius: 6px; }
.pfieldset > legend .hint { font-weight: 400; color: var(--ink-500); font-size: .78rem; }
.pfieldset__sub { margin: 2px 0 14px; color: var(--ink-500); font-size: .82rem; }
.pform-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 4px; }

/* Geolocation capture + mini map */
.pgeo { border: 1px dashed var(--ink-300); border-radius: var(--r-md); padding: 14px; background: var(--ink-50); }
.pgeo__row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.pgeo__status { font-size: .82rem; color: var(--ink-500); }
.pgeo__status.ok { color: var(--ok-600); font-weight: 600; }
.pgeo__map { margin-top: 12px; background: #fff; border: 1px solid var(--ink-200); border-radius: var(--r-md); overflow: hidden; }
.pgeo__map svg { display: block; width: 100%; height: auto; }
.pgeo__map .lga { fill: var(--accent-50); stroke: var(--accent-600); stroke-width: .6; }
.pgeo__map .lga.hit { fill: var(--accent-100); }
.pgeo__map .marker { fill: var(--bad-600); stroke: #fff; stroke-width: 1.2; }
/* portal.js draws the map from a geojson fetch. While that loads the frame is
   an .apx-skel square the size of the finished map, so nothing below it jumps
   when the drawing lands; without script an empty frame is not shown at all. */
.pgeo__map.apx-skel { aspect-ratio: 1 / 1; border-color: transparent; }
.pgeo__map:empty:not(.apx-skel) { display: none; }

/* Vertical stepper (permit pipeline) */
.pstepper { list-style: none; margin: 0; padding: 0; }
.pstepper li { position: relative; padding: 0 0 18px 34px; }
.pstepper li::before { content: ''; position: absolute; left: 11px; top: 22px; bottom: -2px; width: 2px; background: var(--ink-200); }
.pstepper li:last-child::before { display: none; }
/* The pending dot renders nothing today, so its white-on-white was harmless —
   but it is a trap: the next person to put a glyph in a pending step gets an
   invisible one, and would have no reason to suspect the stylesheet. The white
   belongs to the states that actually have a dark fill behind it. */
.pstepper .dot { position: absolute; left: 2px; top: 2px; width: 20px; height: 20px; border-radius: 50%; border: 2px solid var(--ink-300); background: #fff; display: inline-flex; align-items: center; justify-content: center; color: var(--ink-500); }
.pstepper .done .dot { background: var(--ok-600); border-color: var(--ok-600); color: #fff; }
.pstepper .active .dot { border-color: var(--accent-600); box-shadow: 0 0 0 4px var(--accent-100); }
.pstepper .bad .dot { background: var(--bad-600); border-color: var(--bad-600); color: #fff; }
.pstepper b { display: block; font-size: .88rem; }
.pstepper small { color: var(--ink-500); font-size: .78rem; }
.pstepper .active b { color: var(--accent-700); }

/* Activity timeline */
.ptl { list-style: none; margin: 0; padding: 0; }
.ptl li { position: relative; padding: 0 0 16px 26px; border-left: 2px solid var(--ink-200); margin-left: 8px; }
.ptl li:last-child { padding-bottom: 2px; }
.ptl li::before { content: ''; position: absolute; left: -6px; top: 3px; width: 10px; height: 10px; border-radius: 50%; background: var(--accent-600); box-shadow: 0 0 0 3px var(--accent-100); }
.ptl b { font-size: .86rem; }
.ptl small { display: block; color: var(--ink-500); font-size: .78rem; margin-top: 2px; line-height: 1.45; }
.ptl time { font-size: .72rem; color: var(--ink-400); }

/* E-ticket stub */
.ptkt { max-width: 460px; background: #fff; border-radius: var(--r-lg); box-shadow: var(--shadow-2); overflow: hidden; border: 1px solid var(--ink-200); }
.ptkt__head { background: linear-gradient(120deg, var(--brand-900), var(--brand-700)); color: #fff; padding: 16px 18px; }
.ptkt__head b { font-size: 1.05rem; display: block; }
.ptkt__head small { color: var(--abia-red-200); }
.ptkt__body { display: flex; gap: 16px; padding: 16px 18px; align-items: center; }
.ptkt__qr { flex: none; width: 132px; height: 132px; border: 1px solid var(--ink-200); border-radius: 10px; padding: 6px; background: #fff; }
.ptkt__qr svg, .ptkt__qr img { width: 100%; height: 100%; display: block; }
.ptkt__meta { font-size: .84rem; color: var(--ink-700); display: grid; gap: 6px; min-width: 0; }
.ptkt__meta b { color: var(--ink-900); }
.ptkt__tear { border-top: 2px dashed var(--ink-300); position: relative; }
.ptkt__tear::before, .ptkt__tear::after { content: ''; position: absolute; top: -9px; width: 18px; height: 18px; border-radius: 50%; background: var(--ink-50); border: 1px solid var(--ink-200); }
.ptkt__tear::before { left: -10px; }
.ptkt__tear::after { right: -10px; }
.ptkt__foot { padding: 12px 18px; font-size: .78rem; color: var(--ink-500); display: flex; justify-content: space-between; align-items: center; }

/* Certificate document */
.pcert { max-width: 760px; margin: 0 auto; background: #fff; border: 1px solid var(--ink-200); border-radius: var(--r-lg); box-shadow: var(--shadow-2); padding: 40px 44px; position: relative; }
.pcert::before { content: ''; position: absolute; inset: 10px; border: 2px solid var(--gold-100); border-radius: 12px; pointer-events: none; }
/* The certificate's 128px lockup is ~450px wide with its clear space, so on a
   phone it pushed the whole page sideways. It scales with the screen instead,
   never below the mandated 57px emblem. !important because the component sets
   the size inline, and an inline custom property outranks any selector. */
.pcert .lockup { --lockup-emblem: clamp(57px, 18vw, 128px) !important; max-width: 100%; }
.pcert__seal { position: absolute; top: 26px; right: 30px; width: 74px; opacity: .95; }
.pcert__head { text-align: center; margin-bottom: 22px; }
.pcert__head img { width: 58px; height: 58px; }
.pcert__head h2 { font-family: var(--font-display); margin: 10px 0 2px; font-size: 1.6rem; color: var(--brand-900); }
.pcert__head p { margin: 0; color: var(--ink-500); font-size: .84rem; text-transform: uppercase; letter-spacing: .14em; }
.pcert__body { font-size: .95rem; line-height: 1.7; color: var(--ink-700); text-align: center; }
.pcert__body .holder { font-family: var(--font-display); font-size: 1.5rem; color: var(--ink-900); display: block; margin: 8px 0; }
.pcert__facts { margin: 22px auto 0; max-width: 480px; text-align: left; font-size: .86rem; }
.pcert__facts div { display: flex; justify-content: space-between; gap: 16px; padding: 6px 0; border-bottom: 1px dashed var(--ink-200); }
.pcert__facts dt { color: var(--ink-500); }
.pcert__facts dd { margin: 0; font-weight: 600; text-align: right; overflow-wrap: anywhere; }
.pcert__verify { margin-top: 26px; display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 14px; font-size: .8rem; color: var(--ink-500); }
.pcert__code { font-family: var(--mono); font-weight: 700; font-size: 1rem; color: var(--brand-800); background: var(--brand-50); border: 1px solid var(--brand-100); border-radius: 8px; padding: 6px 12px; overflow-wrap: anywhere; }

/* Detail fact lists */
.plist { margin: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 26px; }
.plist div { display: flex; justify-content: space-between; gap: 14px; padding: 8px 0; border-bottom: 1px solid var(--ink-100); font-size: .87rem; }
.plist dt { color: var(--ink-500); flex: none; }
.plist dd { margin: 0; font-weight: 600; text-align: right; overflow-wrap: anywhere; }
.plist--one { grid-template-columns: minmax(0, 1fr); }

/* Progress bar (project completion) */
.pprog { height: 10px; background: var(--ink-100); border-radius: var(--r-pill); overflow: hidden; }
.pprog i { display: block; height: 100%; background: linear-gradient(90deg, var(--accent-600), var(--brand-500)); border-radius: var(--r-pill); }

/* ── Dashboard (ibjx-structured): typographic tiles, quick pills,
      7/5 bands, action rows. Spacing scale: 14 / 22 / 26. ─────── */
.num { font-variant-numeric: tabular-nums; }
.dsh-block { margin-bottom: 22px; }
.pmain .phead { margin-bottom: 26px; }

.dsh-tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; margin-bottom: 26px; }
.dsh-tile { background: #fff; border: 1px solid var(--ink-200); border-radius: var(--r-lg); padding: 18px 18px 16px; box-shadow: var(--shadow-1); min-width: 0; }
.dsh-tile__label { font-size: .74rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-500); margin-bottom: 8px; }
.dsh-tile__value { font-size: 1.5rem; font-weight: 800; letter-spacing: -.02em; line-height: 1.15; overflow-wrap: anywhere; }
.dsh-tile__note { font-size: .8rem; color: var(--ink-500); margin-top: 5px; }
.dsh-tile--brand { background: linear-gradient(150deg, var(--brand-950), var(--brand-800)); border: 0; color: #fff; }
.dsh-tile--brand .dsh-tile__label { color: var(--abia-red-300); }
.dsh-tile--brand .dsh-tile__note { color: var(--abia-red-200); }
.dsh-track { height: 8px; border-radius: var(--r-pill); background: var(--ink-100); overflow: hidden; margin-top: 10px; }
.dsh-fill { display: block; height: 100%; border-radius: var(--r-pill); background: linear-gradient(90deg, var(--accent-700), var(--brand-500)); }

.dsh-quick { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(205px, 100%), 1fr)); gap: 10px; }
.dsh-quick__item { display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 12px 14px; border: 1px solid var(--ink-200); border-radius: 10px; background: #fff; color: var(--ink-900); text-decoration: none; font-weight: 600; font-size: .85rem; transition: border-color .15s var(--ease), transform .12s var(--ease); }
.dsh-quick__item:hover { border-color: var(--accent-600); color: var(--accent-700); text-decoration: none; }
@media (hover: hover) {
  .dsh-quick__item:hover { transform: translateY(-1px); }
}
.dsh-quick__item .svg-icon { color: var(--accent-700); flex: none; }
.dsh-quick__item span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dsh-quick__count { margin-left: auto; flex: none; font-size: .72rem; font-weight: 700; color: var(--accent-700); background: var(--accent-50); border: 1px solid var(--accent-100); border-radius: var(--r-pill); padding: 1px 8px; }

.dsh-2 { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: 22px; align-items: start; }
.dsh-2 > * { min-width: 0; }
.dsh-stack { display: grid; gap: 22px; }

.panel__body--list { padding: 6px 20px; }
.dsh-act { display: flex; align-items: center; gap: 12px; padding: 12px 0; text-decoration: none; color: var(--ink-900); border-bottom: 1px solid var(--ink-100); }
.dsh-act:hover { text-decoration: none; }
.panel__body--list .dsh-act:last-child { border-bottom: 0; }
.dsh-act__ic { width: 34px; height: 34px; flex: none; border-radius: 9px; display: inline-flex; align-items: center; justify-content: center; background: var(--info-100); color: var(--info-600); }
.dsh-act__ic--warn { background: var(--warn-100); color: var(--warn-600); }
.dsh-act__ic--bad { background: var(--bad-100); color: var(--bad-600); }
.dsh-act__ic--ok { background: var(--ok-100); color: var(--ok-600); }
.dsh-act__b { min-width: 0; flex: 1; }
.dsh-act__b b { display: block; font-size: .86rem; line-height: 1.3; }
.dsh-act__b small { display: block; color: var(--ink-500); font-size: .78rem; line-height: 1.45; margin-top: 1px; }
.dsh-act__cta { flex: none; font-size: .76rem; font-weight: 700; color: var(--accent-700); background: var(--accent-50); border: 1px solid var(--accent-100); border-radius: var(--r-pill); padding: 3px 11px; }
.dsh-act:hover .dsh-act__cta { background: var(--accent-100); }

.dsh-flush { border: 0; box-shadow: none; border-radius: 0 0 var(--r-lg) var(--r-lg); }
.dsh-empty { text-align: center; padding: 42px 20px; color: var(--ink-500); font-size: .86rem; }
.dsh-empty b { display: block; font-size: 1rem; color: var(--ink-900); margin-bottom: 5px; }
.dsh-donut { flex: none; }
.dsh-donut .donut { width: 108px; height: 108px; margin: 0; }
.dsh-donut .donut__ring::after { inset: 11px; }
.dsh-donut .donut__c b { font-size: 1.25rem; }

@media (max-width: 1000px) {
  .dsh-2 { grid-template-columns: minmax(0, 1fr); }
  .dsh-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 430px) {
  .dsh-tiles { grid-template-columns: minmax(0, 1fr); }
}

/* ── Support chat inside the portal shell ──────────────────────
   The widget is shared with the public site; these rules place it around the
   shell's bars. */
/* A long form's sticky action bar (Register property, Save vehicle, Submit for
   approval) rides the bottom edge at every width, with its buttons at the
   right-hand end from 600px up: exactly where the chat button stands. Measured
   before this rule, the button sat on Cancel at 360, 768 and 1023px, and on a
   320px phone, where the two buttons wrap, on Register too. So on those pages
   the button stands on the bar instead, and snackbars keep rising above it.
   The lift is --apx-actionbar-h, the bar's reach above the bottom chrome, which
   shell.css and shell.js already keep for the snackbars and the update bar.
   shell.css seeds it with one row of 44px controls from 600px and two wrapped
   rows on phones; shell.js replaces that with the measured reach, because no
   constant is right at every width (the same bar is 64px at 768, 69px at 412,
   121px at 360). The chat button reads the same number rather than keeping a
   second measurement of its own, so the button, the snackbars and the update
   bar can never disagree about where the bar ends. The 0px fallbacks keep each
   calc() valid should shell.css ever fail to load: an unset custom property
   would void the whole declaration and drop the button to `bottom: auto`. */
body:has(.apx-actionbar) .scw { bottom: calc(var(--apx-bottom-chrome, 0px) + var(--apx-actionbar-h, 0px) + 12px); }
/* The floating card opens 74px above the raised button, so it gives up the
   same height: at its full 560px it would otherwise start above the top of a
   short tablet screen, taking its close button with it. Scoped away from the
   full-screen chat below, which has no height limit. */
@media (min-width: 600px) and (min-height: 500.02px), (min-width: 600px) and (orientation: portrait) {
  body:has(.apx-actionbar) .scw-panel { max-height: calc(100vh - 102px - var(--apx-actionbar-h, 0px)); }
}
/* From 600px snackbars sit bottom-right, where the chat button is. They are
   above it in the stack (toast layer 1150, chat 900), so a sticky error hid the
   button until dismissed. They stand 12px above it instead. */
@media (min-width: 600px) {
  body:has(> .scw:not(.is-open)) .apx-toasts { bottom: calc(max(20px, calc(env(safe-area-inset-bottom, 0px) + 12px)) + 72px); }
  body:has(.apx-actionbar):has(> .scw:not(.is-open)) .apx-toasts { bottom: calc(var(--apx-actionbar-h, 0px) + 84px); }
}
/* On a phone the open chat IS the screen. As a floating card it was taller than
   the space above the bottom bar, so its header — the title and the close
   button — sat above the top of the screen. Full-bleed, it takes the sheet
   layer above every bar, pads itself clear of the notch and home indicator,
   and the page behind stops scrolling. Short landscape screens get the same. */
@media (max-width: 599.98px), (max-height: 500px) and (orientation: landscape) {
  .scw.is-open { z-index: 1100; }
  .scw.is-open .scw-fab { display: none; }
  .scw-panel { position: fixed; inset: 0; width: auto; max-width: none; height: auto; max-height: none; border: 0; border-radius: 0; box-shadow: none; }
  .scw-head { padding-top: calc(14px + env(safe-area-inset-top, 0px)); padding-left: max(16px, env(safe-area-inset-left, 0px)); padding-right: max(8px, env(safe-area-inset-right, 0px)); }
  .scw-head__x { display: grid; place-items: center; width: 44px; height: 44px; }
  .scw-log { overscroll-behavior: contain; }
  .scw-form { padding-left: max(12px, env(safe-area-inset-left, 0px)); padding-right: max(12px, env(safe-area-inset-right, 0px)); }
  .scw-legal { padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px)); }
  html:has(.scw.is-open) { overflow: hidden; }
}
@media (max-width: 599.98px) {
  /* The button stands on the tab bar, not on top of it, and steps aside while
     the More sheet or a panel is open. */
  body.has-apx-tabbar .scw { right: max(14px, env(safe-area-inset-right, 0px)); bottom: calc(max(var(--apx-bottom-chrome, 0px), env(safe-area-inset-bottom, 0px)) + var(--apx-actionbar-h, 0px) + 12px); }
  body.apx-lock .scw { display: none; }
  /* Snackbars and the update bar rise above the chat button rather than
     covering it — the arrangement a thumb expects from every other app. */
  body.has-apx-tabbar:has(> .scw:not(.is-open)) :is(.apx-toasts, .pwa-update) { bottom: calc(max(var(--apx-bottom-chrome, 0px), env(safe-area-inset-bottom, 0px)) + var(--apx-actionbar-h, 0px) + 84px); }
}

/* ── Toasts without shell.js ───────────────────────────────────
   Nothing arms the timer or adds a dismiss button without the script, so a
   server-rendered toast would sit over the page for good. It expires on its
   own instead, an error later than a confirmation. */
html:not(.apx-js) .apx-toast { animation: ptoast-expire 1ms linear 8s forwards; }
html:not(.apx-js) .apx-toast[data-sticky] { animation-delay: 20s; }
@keyframes ptoast-expire { to { visibility: hidden; } }

/* Reduced motion: the tile lifts, the chat button's rise and the chat's entrance
   go. The toast expiry above is not motion — it only ends a message — so it
   stays. */
@media (prefers-reduced-motion: reduce) {
  .pmod, .dsh-quick__item, .scw-fab { transition: none; }
  .pmod:hover, .dsh-quick__item:hover, .scw-fab:hover { transform: none; }
  .scw-panel, .scw-typing span { animation: none; }
}

/* ── Portal responsive ─────────────────────────────────────── */
@media (max-width: 1279.98px) {
  .phdr__whot { display: none; }
}
@media (max-width: 1120px) {
  .pmods { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
/* Below 1024px the dropdown row no longer fits beside the lockup. The menu
   button opens the More sheet as a drawer instead, and the account menu's
   destinations are that sheet's Account group. */
@media (max-width: 1023.98px) {
  .phdr__nav, .phdr__me { display: none; }
}
/* The desktop navigation is back, so the drawer button goes — including in a
   short landscape desktop window, where shell.css would otherwise offer it. */
@media (min-width: 1024px) {
  .phdr .phdr__menu[data-apx-open] { display: none; }
}
/* From 600px the bell is a dropdown. Anchored to the masthead rather than the
   44px bell, it lines up with the content column's right edge and can never
   open off the left of the screen, as it once did by 123px on a 390px phone. */
@media (min-width: 600px) {
  .phdr__bell { position: static; }
  .phdr__bell > .phdr__panel { top: calc(100% + 8px); right: max(22px, env(safe-area-inset-right, 0px), calc((100% - var(--pcol-max, 1164px)) / 2 + 22px)); }
}
@media (max-width: 760px) {
  .pmods { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pform { grid-template-columns: minmax(0, 1fr); }
  .plist { grid-template-columns: minmax(0, 1fr); }
  .pmain { padding: 18px max(16px, env(safe-area-inset-right, 0px)) 32px max(16px, env(safe-area-inset-left, 0px)); }
  .pcert { padding: 26px 18px; }
}
/* Two tiles side by side under 400px leave each about 150px — the label wraps
   to three lines and the count badge sits on it. */
@media (max-width: 399.98px) {
  .pmods { grid-template-columns: minmax(0, 1fr); }
}

/* ── Phones (<600) ─────────────────────────────────────────────
   The bar is opaque and filter-free here. At 56px over scrolling content a blur
   buys nothing, and a backdrop-filter would make the masthead the containing
   block for anything fixed that ever strayed inside it. */
@media (max-width: 599.98px) {
  .phdr { background: var(--abia-red-950); -webkit-backdrop-filter: none; backdrop-filter: none; }
  .phdr__row { gap: 4px; padding: 0 max(8px, env(safe-area-inset-right, 0px)) 0 max(8px, env(safe-area-inset-left, 0px)); }
  .pbody.pbody--compact, html.pwa-standalone .pbody.pbody--compact { --topbar-h: 56px; }
  /* Inner pages: a 56px app bar — Back (installed), the page title, the bell.
     The lockup cannot keep its mandated clear space in 56px, so it does not
     appear in a squeezed form; Home carries it. */
  .pbody--compact .phdr .lockup { display: none; }
  .pbody--compact .phdr__row > .phdr__title { display: block; }
  /* Once shell.js runs the bell opens the bottom panel. Until then, or without
     it, the dropdown serves — spanning the bar, so it cannot clip. */
  html.apx-js .phdr__row > .phdr__bellbtn { display: inline-flex; }
  html.apx-js .phdr__bellbtn ~ .phdr__bell { display: none; }
  .phdr__bell { position: static; }
  .phdr__bell > .phdr__panel { top: calc(100% + 6px); left: 8px; right: 8px; width: auto; }
  /* The footer's links are in the More sheet — but only once the sheet can
     actually open, so without shell.js the footer stays. */
  html.apx-js .has-apx-tabbar .pfoot { display: none; }
  .pmain .phead { margin-bottom: 18px; }
}
/* Installed, the masthead knows it before paint. pwa.js's class arrives after
   it, and the Home masthead would visibly drop from 96px to 84px. */
@media (max-width: 599.98px) and (display-mode: standalone) {
  .pbody { --topbar-h: 84px; }
}
/* Landscape phones have no height for a tab bar or a masthead: every page gets
   the compact bar and the drawer. Capped below 1024px so a short desktop window
   keeps the desktop header. */
@media (max-width: 1023.98px) and (max-height: 500px) and (orientation: landscape) {
  .pbody, html.pwa-standalone .pbody { --topbar-h: 56px; }
  .phdr { background: var(--abia-red-950); -webkit-backdrop-filter: none; backdrop-filter: none; }
  .phdr .lockup { display: none; }
  .phdr__row > .phdr__title { display: block; }
}

/* ── Print ────────────────────────────────────────────────── */
@media print {
  .topbar, .bottom-nav, .footer, .site-footer, .phero, .ptools, .ptrust, .pcta, .papp, .scw, .no-print { display: none !important; }
  body { background: #fff; }
  .card, .table-wrap { box-shadow: none; border-color: #999; }
  /* A certificate printed with the masthead, a tab bar or an install card
     across it is not a certificate. shell.css hides the same chrome; this copy
     holds if that stylesheet did not load. */
  .phdr, .pfoot, .phead__actions, .skip-link, .toast-region,
  .apx-tabbar, .apx-sheet, .apx-panel, .apx-toasts, .apx-progress,
  .pwa-card, .pwa-update, .pwa-offline, .pwa-ios { display: none !important; }
  body.has-apx-tabbar, .is-offline body { padding: 0; }
  .pmain { max-width: none; padding: 0; }
  .pcert, .ptkt { box-shadow: none; border-color: #999; }
}

/* ── ABSSIN liveness capture ───────────────────────────────── */
.lv { padding: 18px; }
.lv-stage {
  position: relative; width: 100%; aspect-ratio: 3 / 4; max-height: 460px;
  margin: 0 auto 14px; border-radius: var(--r-lg); overflow: hidden;
  background: var(--abia-red-950); display: grid; place-items: center;
}
.lv-video {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  transform: scaleX(-1);            /* mirror: people expect a mirror, not a camera */
  opacity: 0; transition: opacity .3s;
}
.lv.is-live .lv-video { opacity: 1; }
.lv-canvas { display: none; }
.lv-oval {
  position: absolute; left: 50%; top: 46%; transform: translate(-50%, -50%);
  width: 62%; aspect-ratio: 3 / 4; border: 3px dashed rgba(255,255,255,.85);
  /* Two-tone: a single light dashed ring vanishes against a bright window or a
     pale wall, which is precisely where people stand to get good light. The
     dark outer shadow keeps the guide readable on any background. */
  box-shadow: 0 0 0 2px rgba(0,0,0,.45), inset 0 0 0 2px rgba(0,0,0,.25);
  border-radius: 50%; pointer-events: none; opacity: 0; transition: opacity .3s, border-color .3s;
}
.lv.is-live .lv-oval { opacity: 1; }
/* The direction arrow, on the picture itself. Hidden until the page sets a
   direction for the step on screen; it sits beside the oval on the side the
   citizen must turn towards (as they see it in the mirror), or under it for
   a nod. White on a dark halo, like the oval, so it reads against a window
   as well as a dark wall; the gentle nudge is dropped for reduced motion. */
.lv-arrow {
  position: absolute; z-index: 2; display: none; align-items: center; justify-content: center;
  width: 64px; height: 64px; border-radius: 50%; color: #fff;
  background: rgba(0,0,0,.42); box-shadow: 0 0 0 2px rgba(255,255,255,.85);
  pointer-events: none;
}
.lv-arrow svg { display: block; }
.lv-arrow[data-lv-arrow="right"] { display: flex; right: 4%; top: 46%; transform: translateY(-50%); animation: lv-nudge-x .9s ease-in-out infinite alternate; }
.lv-arrow[data-lv-arrow="left"]  { display: flex; left: 4%; top: 46%; transform: translateY(-50%) rotate(180deg); animation: lv-nudge-x-l .9s ease-in-out infinite alternate; }
.lv-arrow[data-lv-arrow="down"]  { display: flex; left: 50%; bottom: 4%; transform: translateX(-50%) rotate(90deg); animation: lv-nudge-y .9s ease-in-out infinite alternate; }
@keyframes lv-nudge-x   { from { transform: translateY(-50%) translateX(0); } to { transform: translateY(-50%) translateX(8px); } }
@keyframes lv-nudge-x-l { from { transform: translateY(-50%) rotate(180deg) translateX(0); } to { transform: translateY(-50%) rotate(180deg) translateX(8px); } }
@keyframes lv-nudge-y   { from { transform: translateX(-50%) rotate(90deg) translateX(0); } to { transform: translateX(-50%) rotate(90deg) translateX(8px); } }
.lv-idle { position: relative; z-index: 1; text-align: center; color: rgba(255,255,255,.82); padding: 24px; }
/* An inline SVG, so the box is the icon's own size rather than a line box.
   The stage behind it is dark maroon, so the stroke takes the idle text's
   colour rather than the brand red it would inherit on a light card. */
.lv-idle__icon { display: block; margin: 0 auto 8px; line-height: 0; color: inherit; }
.lv-idle__icon svg { display: block; margin: 0 auto; }
.lv-idle p { margin: 0; font-size: .92rem; }
/* The one line of advice that would have saved eleven of the eighteen runs
   on the round-3 engine (2026-09-25): every one of them sat with a warm wall
   or a window BEHIND them, and the face detector that replaced the skin
   box cannot see a face at luma 40 or 250 any more than the box could.
   Quieter than the idle text so it reads as advice, not as the state. */
.lv-idle__light { margin-top: 8px; font-size: .82rem; color: rgba(255,255,255,.7); line-height: 1.4; }

.lv-steps { list-style: none; display: flex; gap: 6px; margin: 0 0 12px; padding: 0; }
.lv-steps li {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 5px;
  font-size: .68rem; text-align: center; color: var(--ink-400); line-height: 1.25;
}
.lv-steps__dot {
  width: 100%; height: 4px; border-radius: 99px; background: var(--ink-200);
  transition: background .25s;
}
.lv-steps li.is-active { color: var(--brand-700); font-weight: 600; }
.lv-steps li.is-active .lv-steps__dot { background: var(--brand-400); }
.lv-steps li.is-done { color: var(--ok-600); }
.lv-steps li.is-done .lv-steps__dot { background: var(--ok-600); }

.lv-prompt {
  min-height: 46px; margin: 0 0 14px; padding: 11px 14px; border-radius: var(--r-sm);
  background: var(--ink-50); border: 1px solid var(--ink-200);
  font-size: .95rem; font-weight: 600; text-align: center; color: var(--ink-700);
}
.lv-prompt--action { background: var(--brand-50); border-color: var(--brand-100); color: var(--brand-900); font-size: 1.02rem; }
.lv-prompt--good { background: var(--ok-100); border-color: var(--ok-600); color: var(--success-fg); }
.lv-prompt--hint { background: var(--warn-100); border-color: var(--warn-600); color: #7c2d12; }
.lv-prompt--error { background: var(--bad-100); border-color: var(--bad-600); color: #7f1d1d; }
/* A movement step: the direction in large type, because the prompt is the
   one line a citizen reads. OTHER WAY is the same size on the warning colours,
   and never the same colour as "hold it…" — green meant "keep going" in the
   real run, and the wrong way must not look like it. */
.lv-prompt--turn { font-size: 1.32rem; line-height: 1.25; padding: 13px 14px; }
.lv-prompt--wrong { background: var(--warn-100); border-color: var(--warn-600); color: #7c2d12; }

/* Coaching sits below the instruction and is visually quieter, matching the
   polite live region it is announced through. */
.lv-coach {
  min-height: 20px; margin: -6px 0 12px; text-align: center;
  font-size: .86rem; color: var(--ink-500);
}
.lv-coach:empty { min-height: 0; margin-bottom: 0; }

.lv-option {
  display: flex; align-items: center; gap: 8px; justify-content: center;
  margin: 12px 0 0; font-size: .85rem; color: var(--ink-500); cursor: pointer;
}
.lv-option input { width: 16px; height: 16px; }

.lv-recovery {
  margin: 14px 0 0; padding: 14px 16px; border-radius: var(--r-sm);
  background: var(--warn-100); border: 1px solid var(--warn-600); color: #7c2d12;
}
.lv-recovery h3 { margin: 0 0 6px; font-size: .98rem; }
.lv-recovery h4 { margin: 10px 0 4px; font-size: .88rem; }
.lv-recovery p { margin: 0 0 8px; font-size: .88rem; }
.lv-recovery__steps { margin: 0; padding-left: 20px; font-size: .88rem; line-height: 1.55; }
.lv-recovery__steps li { margin-bottom: 4px; }

@media (max-width: 480px) {
  .lv { padding: 12px; }
  .lv-stage { aspect-ratio: 1 / 1; max-height: 340px; }
  .lv-steps li { font-size: 0; gap: 0; }      /* dots only on small screens */
  .lv-steps__dot { height: 5px; }
}
@media (prefers-reduced-motion: reduce) {
  .lv-video, .lv-oval, .lv-steps__dot { transition: none; }
  .lv-arrow[data-lv-arrow] { animation: none; }
}
/* Windows high-contrast and forced-colours modes strip the oval's colours
   entirely; without an explicit border it disappears and the citizen has no
   idea where to put their face. */
@media (prefers-contrast: more) {
  .lv-oval { border-color: #fff; border-width: 4px; box-shadow: 0 0 0 3px #000; }
  .lv-prompt { border-width: 2px; }
}
@media (forced-colors: active) {
  .lv-oval { border: 4px dashed CanvasText; box-shadow: none; }
  .lv-arrow { color: CanvasText; background: Canvas; box-shadow: 0 0 0 2px CanvasText; }
  .lv-steps__dot { background: CanvasText; }
  .lv-recovery { border: 2px solid CanvasText; }
}

/* ── FieldOps PWA chrome (agent channel) ───────────────────── */
.fops-install {
  position: fixed; left: 12px; right: 12px; bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  z-index: 900; display: flex; align-items: center; gap: 10px;
  padding: 12px 14px; border-radius: var(--r-lg);
  background: var(--brand-900); color: #fff; box-shadow: 0 12px 30px rgba(107,15,27,.32);
}
body.has-bottom-nav .fops-install { bottom: calc(76px + env(safe-area-inset-bottom, 0px)); }
.fops-install__txt { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: 1.3; }
.fops-install__txt strong { font-size: .95rem; }
.fops-install__txt small { font-size: .78rem; opacity: .78; }
.fops-install__go {
  flex: none; border: none; border-radius: 8px; padding: 9px 16px;
  background: #fff; color: var(--brand-900); font: 700 .88rem var(--font); cursor: pointer;
}
.fops-install__x {
  flex: none; width: 30px; height: 30px; border: none; border-radius: 50%;
  background: rgba(255,255,255,.16); color: #fff; font-size: 1.1rem; line-height: 1; cursor: pointer;
}
.fops-offline {
  position: fixed; left: 0; right: 0; top: 0; z-index: 950;
  padding: 9px 14px; text-align: center;
  background: var(--warn-600); color: #fff; font-size: .85rem; font-weight: 600;
}
@media (min-width: 821px) { .fops-install { display: none; } }

/* ── Service application wizard ─────────────────────────────── */
.wiz-crumb { font-size: .84rem; color: var(--ink-500); display: flex; gap: 8px; align-items: center; }
.wiz-crumb a { color: var(--ink-500); }
.wiz-crumb a:hover { color: var(--brand-700); }

.wiz-progress {
  height: 6px; border-radius: 99px; background: var(--ink-200);
  overflow: hidden; margin: 0 0 12px;
}
.wiz-progress__bar {
  display: block; height: 100%; border-radius: 99px;
  background: linear-gradient(90deg, var(--brand-600), var(--brand-400));
  transition: width .35s cubic-bezier(.4,0,.2,1);
}

.wiz-steps { list-style: none; display: flex; gap: 4px; margin: 0 0 20px; padding: 0; flex-wrap: wrap; }
.wiz-steps li {
  display: flex; align-items: center; gap: 6px; flex: 1; min-width: 0;
  font-size: .74rem; color: var(--ink-400);
}
.wiz-steps__n {
  width: 20px; height: 20px; border-radius: 50%; flex-shrink: 0;
  display: grid; place-items: center; font-size: .68rem; font-weight: 700;
  background: var(--ink-200); color: var(--ink-500);
}
.wiz-steps li.is-active { color: var(--brand-800); font-weight: 600; }
.wiz-steps li.is-active .wiz-steps__n { background: var(--brand-600); color: #fff; }
.wiz-steps li.is-done { color: var(--ok-600); }
.wiz-steps li.is-done .wiz-steps__n { background: var(--ok-600); color: #fff; }
.wiz-steps__t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.wiz-actions { display: flex; gap: 10px; align-items: stretch; margin-top: 18px; }
.wiz-actions .btn { display: inline-flex; align-items: center; justify-content: center; }

.field.has-error .input { border-color: var(--bad-600); }
.field-error { display: block; margin-top: 5px; color: var(--bad-600); font-size: .83rem; font-weight: 500; }

.opt-group { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; }
.opt {
  display: flex; align-items: center; gap: 10px; padding: 11px 13px;
  border: 1px solid var(--ink-200); border-radius: var(--r-sm);
  cursor: pointer; transition: border-color .15s, background .15s;
}
.opt:hover { border-color: var(--brand-400); background: var(--brand-50); }
.opt input { accent-color: var(--brand-600); flex-shrink: 0; }
.opt:has(input:checked) { border-color: var(--brand-600); background: var(--brand-50); }

/* Review list */
.rev-list { margin: 0; }
.rev-list__row {
  display: grid; grid-template-columns: 40% 1fr; gap: 10px;
  padding: 10px 0; border-bottom: 1px solid var(--ink-100);
}
.rev-list__row:last-child { border-bottom: 0; }
.rev-list dt { font-size: .86rem; color: var(--ink-500); margin: 0; }
.rev-list dd { margin: 0; font-size: .93rem; font-weight: 500; display: flex; gap: 10px; justify-content: space-between; }
.rev-list__edit { font-size: .82rem; font-weight: 500; flex-shrink: 0; }

/* Documents */
.doc-list { list-style: none; padding: 0; margin: 8px 0 0; display: flex; flex-direction: column; gap: 6px; }
.doc-list li {
  display: flex; align-items: center; gap: 9px; padding: 9px 12px;
  background: var(--ink-50); border-radius: var(--r-sm); font-size: .89rem;
}
.doc-list__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.doc-list__meta { color: var(--ink-400); font-size: .82rem; flex-shrink: 0; }

.up-row { padding: 13px 0; border-bottom: 1px solid var(--ink-100); }
.up-row:last-child { border-bottom: 0; padding-bottom: 0; }
.up-row__head { display: flex; align-items: center; gap: 8px; margin-bottom: 3px; }
.up-row__form { display: flex; gap: 8px; align-items: center; margin-top: 8px; flex-wrap: wrap; }
.up-row__form .input { flex: 1; min-width: 180px; padding: 7px 9px; font-size: .86rem; }
.up-row__have {
  display: flex; align-items: center; gap: 9px; margin-top: 8px;
  padding: 9px 12px; background: var(--ok-100); border-radius: var(--r-sm); font-size: .88rem;
}
.up-row__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
.pill--req {
  background: var(--warn-100); color: #7c2d12; font-size: .7rem;
  padding: 2px 8px; border-radius: 99px; font-weight: 600;
}

@media (max-width: 560px) {
  .wiz-steps__t { display: none; }
  .wiz-steps li { flex: 0 0 auto; }
  .rev-list__row { grid-template-columns: 1fr; gap: 2px; }
  .wiz-actions { flex-direction: column-reverse; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   SERVICES HIERARCHY — the service cards that sit under a category, and the
   responsible-agency panel. Added with the service-directory release.
   Reuses the existing token set (--ink-*, --accent-*, --r-lg, --shadow-*).
   ═══════════════════════════════════════════════════════════════════════════ */

/* .svc-cards / .svc-card are the card system's --service variant, and
   .svc-card__meta is its meta row; see the CARD SYSTEM section.
   `.pservice--svc .pservice__tag { color: var(--accent-700) }` was deleted: it
   restated the colour .pservice__tag already has, so it could only ever drift
   away from it. */

/* ── Responsible-agency panel ─────────────────────────────────────────────── */
.mdapanel__head { display: flex; gap: 13px; align-items: center; }
.mdapanel__logo {
  width: var(--ic-md); height: var(--ic-md); border-radius: calc(var(--ic-md) * .29);
  object-fit: contain; background: var(--accent-50); flex: none;
}
.mdapanel__logo--ph { display: grid; place-items: center; color: var(--accent-700); }
/* .7rem and .71rem are 11.2px and 11.4px — body sizes, not large text — so
   --ink-400 (4.46:1) was a WCAG 1.4.3 failure on all three of these. Same
   sizes, --ink-500, 6.74:1. The glyph beside the label may stay at --ink-400:
   it is decoration beside a word that already says the same thing. */
.mdapanel__kicker {
  display: block; font-size: var(--card-eyebrow); font-weight: 800; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-500);
}
.mdapanel__name { display: block; font-size: var(--card-title); font-weight: 700; line-height: 1.3;
  letter-spacing: -.01em; margin-top: 2px; color: var(--ink-900); }
.mdapanel__about { margin: 14px 0 0; font-size: var(--card-body); line-height: 1.6; color: var(--ink-500); }
.mdapanel__rows { margin: 15px 0 0; display: grid; gap: 11px; }
.mdapanel__rows > div { display: grid; gap: 2px; }
.mdapanel__rows dt {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--card-eyebrow); font-weight: 800; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-500);
}
.mdapanel__rows dt .svg-icon { color: var(--ink-400); flex: none; }
.mdapanel__rows dd { margin: 0; font-size: var(--card-body); line-height: 1.5; color: var(--ink-700); }
.mdapanel__contact {
  display: grid; gap: 8px; margin-top: 16px; padding-top: 15px;
  border-top: 1px solid var(--ink-200);
}
.mdapanel__contact a {
  display: inline-flex; align-items: center; gap: 8px; min-height: 44px;
  font-size: var(--card-body); font-weight: 700; color: var(--accent-700); overflow-wrap: anywhere;
}
.mdapanel__contact a .svg-icon { flex: none; }
.mdapanel__note { margin: 14px 0 0; font-size: var(--card-meta); line-height: 1.5; color: var(--ink-500); }

/* ── Mega-menu: a column may stack several catalogue groups ───────────────── */
/* Every heading after the first in a column needs air above it, or the groups
   read as one long list and the grouping stops meaning anything. */
.meganav__coltitle + .meganav__link + .meganav__coltitle,
.meganav__link + .meganav__coltitle { margin-top: 14px; }

/* ═══════════════════════════════════════════════════════════════════════════
   SERVICE ITEM CARDS — the individually-applicable services under a category
   ("What's included", made actionable). Reuses the existing token set.
   ═══════════════════════════════════════════════════════════════════════════ */

/* .sitems / .sitem are the card system's --service variant and .sitem--info is
   its --quiet flag; the hint is now the shared .callout. See the CARD SYSTEM
   section. Only the pieces with no counterpart in the system stay here.

   What went, and why: `.sitem--info { background: var(--ink-50) }` put an
   --ink-50 card on an --ink-50 page, so the one card that is a GUIDE rather
   than an application read as a hole in the grid with a hairline round it — and
   on Market & Trading that is the headline service of the category. The
   distinction moves into a word, which is where GOV.UK puts it. */
.sitem__go { display: block; color: inherit; text-decoration: none; }
.sitem__go:hover { text-decoration: none; }

.sitem__docs { margin: 0; }
.sitem__docs > summary {
  cursor: pointer; font-size: var(--card-note); font-weight: 700; color: var(--ink-700);
  padding: 6px 0; min-height: 44px; display: flex; align-items: center; list-style: none;
}
.sitem__docs > summary::-webkit-details-marker { display: none; }
/* A +/- in the accent is a fourth red mark on a card that has already spent
   its three. The glyph carries the state; --ink-500 carries the glyph. */
.sitem__docs > summary::before {
  content: '+'; display: inline-block; width: 1.1em; font-weight: 800; color: var(--ink-500);
}
.sitem__docs[open] > summary::before { content: '−'; }
.sitem__docs > summary:hover { text-decoration: underline; }
.sitem__docs ul { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 7px; }
.sitem__docs li {
  display: flex; gap: 8px; align-items: flex-start;
  font-size: var(--card-note); line-height: 1.5; color: var(--ink-700);
}
.sitem__docs li .svg-icon { color: var(--ink-400); flex: none; margin-top: 2px; }

/* Screen-reader-only helper (the public stylesheet had no equivalent). */
.u-sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════
   HOSPITALITY SELF-SERVICE (public site). Reuses the existing token set
   (--ink-*, --accent-*, --r-lg, --shadow-*).
   ═══════════════════════════════════════════════════════════════════════════ */

/* .hspub-types / .hspub-type are the card system's --service variant; see the
   CARD SYSTEM section. */

/* Public form */
.hsp-form--public .card { border-radius: var(--r-lg); }
.hsp-step { display: flex; align-items: center; gap: 10px; font-size: 1.05rem; margin: 0 0 16px; }
.hsp-step > span {
  width: 26px; height: 26px; border-radius: 99px; display: grid; place-items: center;
  background: var(--accent-700); color: #fff; font-size: .82rem; font-weight: 800; flex: none;
}

.hsp-form .form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 15px; }
.hsp-form .field { display: flex; flex-direction: column; gap: 5px; }
.hsp-form .field > span:first-child { font-size: .85rem; font-weight: 600; color: var(--ink-900); }
/* --ink-500, not --ink-400. At .78rem this is 12.5px and --ink-400 measures
   4.49:1 on white — a WCAG 1.4.3 failure, on the line that says "This decides
   your rate band". brand.css labels --ink-400 large-text-only and §3.2 of the
   card system already outlawed it on 11–13px card text; the form layer was
   simply not in that pass. */
.hsp-form .field small { font-size: .78rem; color: var(--ink-500); line-height: 1.45; }
.hsp-form .field[hidden] { display: none; }
@media (max-width: 760px) { .hsp-form .form-grid { grid-template-columns: 1fr; } }

/* Type chooser (public) */
.hsp-form--public .hsp-types { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; }
.hsp-form--public .hsp-type input { position: absolute; opacity: 0; width: 0; height: 0; }
.hsp-form--public .hsp-type__box {
  display: grid; gap: 4px; padding: 13px; height: 100%; cursor: pointer;
  background: #fff; border: 1.5px solid var(--ink-200); border-radius: var(--r-lg);
  transition: border-color .15s, background .15s;
}
.hsp-form--public .hsp-type:hover .hsp-type__box { border-color: var(--accent-600); }
.hsp-form--public .hsp-type input:checked + .hsp-type__box { border-color: var(--accent-700); background: var(--accent-50); }
.hsp-form--public .hsp-type input:focus-visible + .hsp-type__box { outline: 2px solid var(--accent-700); outline-offset: 2px; }
.hsp-form--public .hsp-type__ic { color: var(--accent-700); }
.hsp-form--public .hsp-type__box b { font-size: .88rem; }
.hsp-form--public .hsp-type__box small { font-size: .76rem; color: var(--ink-500); line-height: 1.4; }

/* Hint under the chosen type — the shared .callout; see the CARD SYSTEM
   section. Only the margin and the hidden state are specific to this one. */
.hsp-hint { margin: 16px 0 0; }
.hsp-hint[hidden] { display: none; }

/* Per-type fieldsets + band preview (public skin) */
.hsp-form--public .hsp-catfields { border: 1px solid var(--ink-200); border-radius: var(--r-lg); padding: 16px; margin-top: 18px; }
.hsp-form--public .hsp-catfields > legend { font-size: .74rem; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-400); padding: 0 6px; }
.hsp-form--public .hsp-preview { background: var(--accent-50); border: 1px solid var(--accent-600); border-radius: var(--r-lg); padding: 16px; }
.hsp-form--public .hsp-preview--warn { background: var(--warning-surface); border-color: var(--gold-600, #d97706); }
.hsp-form--public .hsp-preview__label { color: var(--ink-400); }
.hsp-form--public .hsp-preview__amt { color: var(--ink-900); }
.hsp-form--public .hsp-preview__note { color: var(--ink-500); }

/* Declaration */
.hsp-declare {
  display: flex; gap: 10px; align-items: flex-start;
  margin: 20px 0 0; padding: 14px; border-radius: var(--r-lg);
  background: var(--ink-50); font-size: .82rem; line-height: 1.6; color: var(--ink-500);
}
.hsp-declare .svg-icon { color: var(--accent-700); flex: none; margin-top: 1px; }

/* Confirmation */
.hsp-done__mark {
  width: 58px; height: 58px; border-radius: 99px; display: grid; place-items: center;
  background: var(--accent-50); color: var(--accent-700);
}
.hsp-refbox {
  background: var(--ink-50); border: 1px dashed var(--ink-300); border-radius: var(--r-lg);
  padding: 16px; display: grid; gap: 4px;
}
/* Same 11.8px / 4.49:1 failure as .hsp-form .field small — this one on the
   label above the reference number a citizen has to write down. */
.hsp-refbox > span { font-size: .74rem; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-500); }
.hsp-refbox > b { font-size: 1.15rem; letter-spacing: .04em; }
.hsp-refbox > small { font-size: .8rem; color: var(--ink-500); line-height: 1.5; margin-top: 4px; }

/* Tracking */
.kv--public { display: grid; gap: 12px; margin: 0; }
.kv--public > div { display: grid; grid-template-columns: 190px minmax(0, 1fr); gap: 12px; align-items: baseline; }
/* And again on the registration facts an operator actually reads: the market-shop
   details, review, done and track screens, the shop bank-account panel and
   hospitality_track. 11.8px at --ink-400 is 4.49:1. */
.kv--public dt { font-size: .74rem; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-500); }
.kv--public dd { margin: 0; font-size: .92rem; overflow-wrap: anywhere; }
/* The register's new number for the shop, on the review (0071): the figure to check. */
dl dd.msh-newno { font-size: 1.25rem; font-weight: 800; color: var(--ink-900); }
@media (max-width: 640px) { .kv--public > div { grid-template-columns: 1fr; gap: 2px; } }

/* .hsp-status is the shared .callout with a tone; see the CARD SYSTEM section.
   The three bespoke hexes (#d97706, #dc2626, #fef2f2) went with it — they were
   a second, unmandated palette hiding inside a rule nobody read. */
.hsp-status { margin: 0 0 14px; }

.input--code { font-size: 1.4rem; letter-spacing: .35em; text-align: center; font-family: var(--mono); }

/* ── ABSSIN tier ladder (taxpayer portal) ──────────────────── */
.tier-ladder { list-style: none; margin: 0; padding: 0; }
.tier-ladder__rung {
  display: flex; gap: 12px; padding: 12px 0;
  border-bottom: 1px solid var(--ink-100, var(--abia-neutral-100));
}
.tier-ladder__rung:last-child { border-bottom: 0; }
.tier-ladder__mark {
  flex: 0 0 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--ink-100, var(--abia-neutral-100)); color: var(--ink-400, #7a8590);
  font-size: .82rem; font-weight: 700;
}
.tier-ladder__rung.is-reached .tier-ladder__mark { background: var(--ok-100, var(--success-surface)); color: var(--ok-600, var(--success-fg)); }
.tier-ladder__rung.is-current .tier-ladder__mark { background: var(--brand-400, var(--success-fg)); color: #fff; }
.tier-ladder__body { flex: 1; min-width: 0; }
.tier-ladder__title { display: block; font-weight: 600; font-size: .95rem; margin-bottom: 4px; }
.tier-ladder__title em { font-weight: 400; font-style: normal; font-size: .85rem; margin-left: 4px; }
.tier-ladder__unlocks { margin: 0; padding-left: 18px; color: var(--ink-500, #5b6672); font-size: .88rem; line-height: 1.5; }
.tier-ladder__rung:not(.is-reached) .tier-ladder__unlocks { opacity: .65; }

.evidence-list { list-style: none; margin: 0; padding: 0; }
.evidence-list__item {
  display: grid; grid-template-columns: 24px 1fr auto; align-items: center; gap: 10px;
  padding: 9px 0; border-bottom: 1px solid var(--ink-100, var(--abia-neutral-100)); font-size: .92rem;
}
.evidence-list__item:last-child { border-bottom: 0; }
.evidence-list__mark { color: var(--ink-300, #a8b1ba); font-weight: 700; }
.evidence-list__item.is-present .evidence-list__mark { color: var(--ok-600, var(--success-fg)); }
.evidence-list__item .muted { font-size: .82rem; }

/* Side-by-side "what the registry has" vs "what you are proposing". */
.tier-diff { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.tier-diff__col { min-width: 0; }
.tier-diff__label { font-size: .76rem; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-400, #7a8590); }
@media (max-width: 640px) { .tier-diff { grid-template-columns: 1fr; } }

/* ── Identity photograph ───────────────────────────────────── */
.photo-row { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
.photo-row__shot {
  width: 140px; aspect-ratio: 7 / 9; object-fit: cover;
  border-radius: var(--r-sm); border: 1px solid var(--ink-200, var(--abia-neutral-200)); background: var(--ink-50, var(--abia-neutral-50));
}
.photo-row__empty {
  width: 140px; aspect-ratio: 7 / 9; border-radius: var(--r-sm);
  border: 1px dashed var(--ink-300, #a8b1ba); display: grid; place-items: center;
  color: var(--ink-400, #7a8590); font-size: .82rem; background: var(--ink-50, var(--abia-neutral-50));
}
.photo-row__body { flex: 1; min-width: 220px; }
.photo-row__body p { margin-top: 0; }

.photo-tips { list-style: none; margin: 0; padding: 0; font-size: .92rem; }
.photo-tips li { display: flex; gap: 10px; padding: 6px 0; align-items: flex-start; }
.photo-tips li span { color: var(--ok-600, var(--success-fg)); font-weight: 700; }
.photo-tips li.is-dont span { color: var(--bad-600, #dc2626); }

.photo-preview { margin: 14px 0; }
.photo-preview__frame {
  position: relative; width: 200px; aspect-ratio: 7 / 9; overflow: hidden;
  border-radius: var(--r-sm); background: var(--abia-red-950);
}
.photo-preview__frame img { width: 100%; height: 100%; object-fit: cover; }
.photo-preview__guide {
  position: absolute; left: 50%; top: 46%; transform: translate(-50%, -50%);
  width: 66%; aspect-ratio: 3 / 4; border: 2px dashed rgba(255,255,255,.85);
  box-shadow: 0 0 0 2px rgba(0,0,0,.45); border-radius: 50%; pointer-events: none;
}

/* Side-by-side old and new, for the reviewer. */
.photo-compare { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.photo-compare__col { min-width: 0; text-align: center; }
.photo-compare__label {
  font-size: .76rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-400, #7a8590); margin-bottom: 6px;
}
.photo-compare__shot {
  width: 100%; max-width: 220px; aspect-ratio: 7 / 9; object-fit: cover;
  border-radius: var(--r-sm); border: 1px solid var(--ink-200, var(--abia-neutral-200));
}
.photo-compare__none {
  width: 100%; max-width: 220px; aspect-ratio: 7 / 9; margin: 0 auto;
  border: 1px dashed var(--ink-300, #a8b1ba); border-radius: var(--r-sm);
  display: grid; place-items: center; color: var(--ink-400, #7a8590); font-size: .82rem;
}
@media (max-width: 560px) { .photo-compare { grid-template-columns: 1fr; } }

/* The automated quality report, shown to citizen and reviewer alike. */
.quality-list { list-style: none; margin: 0; padding: 0; font-size: .9rem; }
.quality-list li {
  display: grid; grid-template-columns: 22px 1fr; gap: 8px; padding: 7px 0;
  border-bottom: 1px solid var(--ink-100, var(--abia-neutral-100));
}
.quality-list li:last-child { border-bottom: 0; }
.quality-list__mark { font-weight: 700; }
.quality-list .is-pass .quality-list__mark { color: var(--ok-600, var(--success-fg)); }
.quality-list .is-warn .quality-list__mark { color: var(--warn-600, #d97706); }
.quality-list .is-fail .quality-list__mark { color: var(--bad-600, #dc2626); }
.quality-list .is-unknown .quality-list__mark { color: var(--ink-400, #7a8590); }

/* ── Touch input sizing ────────────────────────────────────────────────────
   iOS Safari zooms the page whenever a form control smaller than 16px takes
   focus, and does not zoom back out. Our controls were 15.5px, so every tap on
   every field on every iPhone left the citizen pinching to see the rest of the
   form — on a tax portal, where the forms are the point.

   16px exactly, and only on touch pointers, so desktop density is unchanged.
   `pointer: coarse` rather than a width query because a small window on a
   laptop is not a finger. */
@media (pointer: coarse) {
  .input, select.input, textarea.input,
  input[type="text"], input[type="email"], input[type="tel"], input[type="number"],
  input[type="password"], input[type="search"], input[type="date"], select, textarea {
    font-size: 16px;
  }
  /* A control must also be big enough to hit. WCAG 2.5.8 asks 24x24; 44 is the
     figure that actually works for a thumb on a moving bus. */
  .input, select.input, textarea.input, .btn, button, [role="button"] {
    min-height: 44px;
  }
}

/* ── Register a shop in a market (/market-shop) ──────────────────────────── */
.msh-chosen {
  display: flex; align-items: center; gap: 12px; margin: 0 0 16px; padding: 12px 14px;
  background: var(--accent-50); border: 1px solid var(--ink-200); border-radius: var(--r-lg);
}
.msh-chosen__ic { flex: none; display: inline-grid; place-items: center; width: 36px; height: 36px; border-radius: 10px; background: #fff; color: var(--accent-700); }
.msh-chosen__text { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.msh-chosen__text b { font-size: .92rem; overflow-wrap: anywhere; }
.msh-chosen__text small { font-size: .8rem; color: var(--ink-500); }
.msh-chosen__edit { flex: none; font-size: .86rem; font-weight: 600; }
.msh-abssin { max-width: 320px; }
/* The line a shop number stands in (0061): shown, never chosen. */
.msh-derived { display: flex; align-items: center; background: var(--ink-50); color: var(--ink-700); font-weight: 600; cursor: default; }
/* The NEW shop number a typed legacy number becomes (0071): the one answer on the step, so it reads as one. */
.msh-converted { min-height: 48px; height: auto; padding-top: 10px; padding-bottom: 10px; color: var(--ink-900); font-size: 17px; font-weight: 800; line-height: 1.3; }
.msh-claim { display: flex; gap: 10px; align-items: flex-start; }
.msh-claim textarea.input { min-height: 72px; }
.msh .hsp-form .field > label { font-size: .85rem; font-weight: 600; color: var(--ink-900); }
.msh .hsp-form select.input:disabled { background: var(--ink-50); color: var(--ink-400); }
.msh-fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
.msh-fieldset legend { font-size: .85rem; font-weight: 600; color: var(--ink-900); margin-bottom: 8px; padding: 0; }
.msh-fieldset.has-error .hsp-type__box { border-color: var(--bad-600); }
.msh-own.hsp-types { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
.msh-own .hsp-type { position: relative; }
.msh-declare { display: flex; gap: 10px; align-items: flex-start; margin: 18px 0 0; padding: 12px 14px; border: 1px solid var(--ink-200); border-radius: var(--r-md); cursor: pointer; font-size: .9rem; line-height: 1.5; }
.msh-declare input { width: 20px; height: 20px; margin: 1px 0 0; flex: none; accent-color: var(--brand-700); }
.msh-declare.has-error { border-color: var(--bad-600); }
.msh-person { background: var(--ink-50); }
.msh-person .hsp-step > span { background: var(--ok-600); }
.msh-edit-links { margin: 16px 0 0; font-size: .88rem; }
.msh-restart { margin: 18px 0 0; text-align: center; }
.msh-restart .btn { font-size: .86rem; }
.msh-done { text-align: center; }
.msh-done__ic { display: inline-grid; place-items: center; width: 60px; height: 60px; margin: 4px auto 12px; border-radius: 50%; background: var(--ok-100); color: var(--ok-600); }
.msh-done__ref { font-size: 1.6rem; font-weight: 700; letter-spacing: .04em; margin: 2px 0 10px; color: var(--ink-900); }
.msh-next { list-style: none; counter-reset: msh; margin: 0 auto 8px; padding: 0; max-width: 460px; text-align: left; display: grid; gap: 10px; }
.msh-next li { counter-increment: msh; display: grid; grid-template-columns: 30px 1fr; column-gap: 12px; align-items: start; }
.msh-next li::before { content: counter(msh); grid-row: span 2; display: inline-grid; place-items: center; width: 28px; height: 28px; border-radius: 50%; background: var(--brand-50); color: var(--brand-700); font-weight: 800; font-size: .8rem; }
.msh-next b { font-size: .92rem; }
.msh-next span { font-size: .84rem; color: var(--ink-500); }
@media (max-width: 560px) {
  .msh .wiz-actions { flex-direction: column-reverse; }
  .msh .wiz-actions .btn { width: 100%; justify-content: center; }
  .msh-chosen { flex-wrap: wrap; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE CARD SYSTEM — the public site's repeated surfaces, in one place.

   WHAT A CARD IS HERE. One self-contained doorway to one government task: one
   surface, one destination, one action pinned at its foot. It earns attention
   through type, spacing and elevation, and it spends the one Abia Red at most
   three times — on its icon, on its action, and on the mark that says "read
   this". Never on its background, never on its border at rest, never on its
   body copy. If a surface has no single destination it is a PANEL: it does not
   lift, it does not glow, and it does not pretend to be pressable.

   WHY ONE SECTION. Fifteen families had grown up here — .ptool, .pspace,
   .pservice, .pjourney, .ppop__item, .svc-card, .sitem, .hspub-type, .pstep,
   .meganav__link, .svc-block, .svc-glance, .mdapanel, .svc-hints, .svc-cta —
   each written where it was needed, each a plausible card, and between them
   they used nine title sizes across a 2.24px range, six padding values, eleven
   icon-chip sizes, nine chip radii, four hover dialects and a bespoke shadow
   literal. None of that encodes anything a citizen can read. One base, six
   modifiers and two flags cover every real use on the site.

   THE MIGRATION CONTRACT: no view breaks in this pass. Plain CSS has no
   @extend, so every old class name is carried literally in the same selector
   list as its .ecard variant. Views migrate to .ecard afterwards, one at a
   time, and nothing has to move at once.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 0. Scope: one red per card ────────────────────────────────────────────
   [data-channel="agent"] and [data-domain="revenue"] resolve --accent-* to the
   gold ramp. On <body> that is FieldOps' identity and it stays (see the channel
   block near the top of this file). On a CARD it produced exactly one cream
   chip and one olive-brown CTA in a row of five reds, for a distinction a
   citizen has no way to decode. Four of the five public tools already resolved
   to red, so the attributes bought nothing and could only go wrong again.
   Same specificity as the attribute selectors, later in the file, so a card
   wins whatever its view carries. */
.ecard, .ptool, .pspace, .pservice, .pjourney, .ppop__item, .svc-card, .sitem,
.hspub-type, .pstep, .meganav__link, .svc-block, .svc-glance, .mdapanel,
.svc-hints, .svc-cta, .meganav__feature {
  --accent-700: var(--abia-red-800);
  --accent-600: var(--abia-red-700);
  --accent-100: var(--abia-red-100);
  --accent-50:  var(--abia-red-50);
}

/* ── 1. The base ───────────────────────────────────────────────────────────
   Surface #fff — never --ink-50, which is the PAGE. A card the colour of the
   page is a hole with a hairline round it, which is what .sitem--info was. */
.ecard,
.ptool, .pspace, .pservice, .pjourney, .svc-card, .sitem, .hspub-type, .pstep,
.ppop__item, .meganav__link,
.svc-block, .svc-glance, .mdapanel, .svc-hints, .svc-cta, .meganav__feature {
  position: relative;
  display: flex; flex-direction: column; gap: var(--card-gap);
  min-width: 0;                       /* see the grid note in section 7 */
  padding: var(--card-pad) var(--card-pad) 18px;
  background: var(--card-bg);
  border: 1px solid var(--card-bd);
  border-radius: var(--card-r);
  box-shadow: var(--shadow-1);
  color: inherit;
}
:is(.ecard, .ptool, .pspace, .pservice, .pjourney, .svc-card, .sitem,
    .hspub-type, .ppop__item, .meganav__link):hover { text-decoration: none; }

/* ── 2. The icon chip ──────────────────────────────────────────────────────
   Two sizes, nothing between them, and the glyph is always half the chip.
   Every chip on the public site was a different size (38-56px) at a different
   radius (10-15px) with a glyph at whatever the view happened to pass, so the
   chip-to-glyph ratio ranged from 2:1 to 3:1 — and on /services, where the
   glyph is an emoji inheriting 15.5px body type inside a 48px tile, it was a
   speck in an empty square. The CSS sets the glyph size so a view cannot get
   it wrong; width and height on an inline <svg> are presentation attributes
   and CSS outranks them.

   The inset hairline exists because --abia-red-50 (#FDF2F4) is so faint that
   at 48px the chip reads as an off-white square rather than a tinted one. It
   gives the tint an edge without introducing a second colour.

   flex: none is load-bearing. `.ptool span { flex: 1 }` matched the chip and
   collapsed it to nothing; no future flex rule can do that again. */
.ic-chip,
.ptool__ic, .pspace__ic, .pservice__ic, .pjourney__ic, .svc-card__ic,
.sitem__ic, .hspub-type__ic, .ppop__ic, .meganav__ic {
  --chip: var(--ic-md);
  width: var(--chip); height: var(--chip); flex: none;
  border-radius: calc(var(--chip) * .29);
  display: grid; place-items: center;
  background: var(--accent-50); color: var(--accent-700);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-600) 12%, transparent);
  font-size: calc(var(--chip) * .5);   /* for the views still passing an emoji */
  line-height: 1;
}
.ic-chip--sm, .ppop__ic, .meganav__ic { --chip: var(--ic-sm); }
.ic-chip--quiet {
  background: var(--ink-100); color: var(--ink-700);
  box-shadow: inset 0 0 0 1px var(--ink-200);
}
:is(.ic-chip, .ptool__ic, .pspace__ic, .pservice__ic, .pjourney__ic,
    .svc-card__ic, .sitem__ic, .hspub-type__ic) > .svg-icon { width: 24px; height: 24px; }
:is(.ic-chip--sm, .ppop__ic, .meganav__ic) > .svg-icon { width: 20px; height: 20px; }

/* ── 3. Anatomy ────────────────────────────────────────────────────────────
   Flag · chip · eyebrow · TITLE · description · callout · disclosure · meta ·
   FOOTER ACTION. A card may omit any optional part; it may never reorder them. */

/* 3.1 Flag — absolute, top right. */
.ecard__flag, .svc-card__flag {
  position: absolute; top: 14px; right: 14px; z-index: 1;
  background: var(--accent-700); color: #fff;
  font-size: var(--card-eyebrow); font-weight: 800; line-height: 1;
  letter-spacing: .04em; text-transform: uppercase;
  padding: 5px 9px; border-radius: var(--r-pill);
}
/* "Guide", not "unavailable": a neutral word, because a greyed-out link that
   still navigates is worse than either a live one or no link at all. */
.ecard__flag--quiet { background: var(--ink-100); color: var(--ink-700); }

/* 3.2 Eyebrow. --ink-500, not --ink-400: at 11px this is body text, and
   --ink-400 is 4.46:1, which brand.css itself labels large-text-only. */
.ecard__eyebrow {
  font-size: var(--card-eyebrow); font-weight: 800; line-height: 1.2;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-500);
}

/* 3.3 Title — the only required part besides the action. */
.ecard__title, .sitem__title,
:is(.pspace, .pservice, .pjourney, .svc-card, .hspub-type, .pstep) h3,
.ptool > b, .ppop__item b, .meganav__link b {
  display: block; margin: 0;
  font-size: var(--card-title); font-weight: 700; line-height: 1.3;
  letter-spacing: -.01em; color: var(--ink-900);
  overflow-wrap: anywhere;
}
/* 19px is for the cards that lap the hero and for panel headings. Nothing
   else; two sizes is the whole ramp. */
.ecard--action .ecard__title, .ptool > b { font-size: var(--card-title-lg); }
.ecard--panel > :is(h2, h3), .svc-block > h2, .svc-glance > h3,
.svc-cta > h3, .svc-hints__head b, .meganav__feature b {
  margin: 0; font-size: var(--card-title-lg); font-weight: 700;
  line-height: 1.3; letter-spacing: -.01em;
}

/* 3.4 Description. Clamped, because equal-height boxes with unequal content
   are not equal-height cards: without this one long summary took the quick-tool
   row from 215px to 704px, and nothing else on the page had changed.

   ONE clamp value, three lines, for every family. The system originally set two
   for --service, and the rendered page settled it: at two lines the headline
   service of the Market & Trading category read "…choose the market, zone and
   lin…", which is a sentence cut in half on the card a citizen came to the page
   for. The clamp is a CEILING against one runaway string, not a design
   statement, and three lines is the ceiling that does not truncate ordinary
   copy. Equal height is delivered by the pinned footer below, not by this. */
.ecard__sum, .sitem__sum,
:is(.pspace, .pservice, .pjourney, .svc-card, .hspub-type, .pstep) > p,
.ptool > span:not(.ptool__ic) {
  margin: 0; font-size: var(--card-body); line-height: 1.6; color: var(--ink-500);
  overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden;
  -webkit-line-clamp: 3; line-clamp: 3;
}

/* 3.5 Callout — one component, four tones, replacing .sitem__hint, .hsp-hint
   and .hsp-status, which were three copies of one rule.

   The treatment was also wrong for what it carries. A red-tinted slab with a
   heavy red left bar is the universal ALERT pattern, so "Have your ABSSIN ready
   and the phone on its record to hand" was drawn louder than the card title
   above it — in a palette where brand.css went to deliberate lengths to stop
   red meaning "something went wrong". The rail IS the alert signal, so the rail
   goes and a full neutral border takes its place. The red info glyph stays:
   that is accent mark number three, and it is the whole point of the block. */
.callout, .sitem__hint, .hsp-hint, .hsp-status {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 12px 14px;
  background: var(--info-100);
  border: 1px solid var(--ink-200);
  border-radius: var(--r-md);
  font-size: var(--card-note); line-height: 1.55; color: var(--ink-700);
}
/* A callout is usually a <p>, and `p { margin: 0 0 1rem }` would add a phantom
   line to the card's gap. .hsp-hint and .hsp-status keep the margins set where
   they are declared — they sit in forms, not in cards. */
.callout, .sitem__hint { margin: 0; }
:is(.callout, .sitem__hint, .hsp-hint, .hsp-status) > .svg-icon,
.sitem__hint-ic, .hsp-hint__ic {
  flex: none; margin-top: 2px; display: flex; color: var(--accent-700);
}
/* Tones are for STATUS — an application rejected, a payment overdue — never
   for a helpful tip. They take the semantic palette brand.css mandates, which
   is the one place a second hue is correct. */
.callout--warn, .hsp-status--warning { background: var(--warn-100); border-color: color-mix(in srgb, var(--warn-600) 30%, transparent); }
.callout--bad,  .hsp-status--danger  { background: var(--bad-100);  border-color: color-mix(in srgb, var(--bad-600)  30%, transparent); }
.callout--ok,   .hsp-status--success { background: var(--ok-100);   border-color: color-mix(in srgb, var(--ok-600)   30%, transparent); }
:is(.callout--warn, .hsp-status--warning) > .svg-icon { color: var(--warn-600); }
:is(.callout--bad,  .hsp-status--danger)  > .svg-icon { color: var(--bad-600); }
:is(.callout--ok,   .hsp-status--success) > .svg-icon { color: var(--ok-600); }

/* 3.6 Meta row — facts, at 12px, in --ink-500. */
.ecard__meta, .svc-card__meta, .sitem__fee {
  margin: 0; display: flex; flex-wrap: wrap; gap: 4px 14px;
  font-size: var(--card-meta); font-weight: 600; line-height: 1.4; color: var(--ink-500);
}
:is(.ecard__meta, .svc-card__meta, .sitem__fee) > span { display: inline-flex; align-items: center; gap: 5px; min-width: 0; }
:is(.ecard__meta, .svc-card__meta, .sitem__fee) .svg-icon { color: var(--ink-400); flex: none; }
/* The responsible agency is a fact, not an action. It was the only meta row on
   the site printed in the accent, which made three service cards look as
   though they had two buttons each. */
.svc-card__mda { font-weight: 700; color: var(--ink-700); }
.svc-card__mda .svg-icon { color: var(--ink-400); }

/* 3.7 Count chip. Not a status badge: /services printed its count TWICE, once
   as .badge.badge-ok — the SUCCESS component, green with a ::before status dot
   — and again as "5 services →" at the card foot. On a portal where green means
   paid and verified, a green dotted pill reading "5" beside a category name is
   a false signal, and it was the only non-red, non-grey colour on the page.
   The element itself belongs in the bin (the count is already in the footer);
   until the view drops it, it is at least not a state. */
.ecard__count, .hspub-type__rate, .pservice .badge, .pspace .badge {
  font-size: var(--card-meta); font-weight: 700; line-height: 1.5;
  color: var(--accent-700); background: var(--accent-50);
  border: 1px solid var(--accent-100); border-radius: var(--r-pill);
  padding: 2px 9px; flex: none; align-self: flex-start;
  font-variant-numeric: tabular-nums;
}
:is(.pservice, .pspace) .badge::before { display: none; }

/* 3.8 The footer action, and the hairline that explains the gap above it.
   `margin-top: auto` alone is what produced the defect on every category page:
   roughly 80px of undifferentiated white between the last line of one card and
   its action, against 38px in the card beside it — three cards in a row with
   three different-sized holes and nothing to say why. The full-bleed hairline
   turns that space into a card FOOTER: above it is the body, below it is the
   action zone, and three ragged cards read as three aligned ones.
   44px minimum, because not one in-card action on this site reached it. */
.ecard__cta, .ptool > i, .pspace__count, .pservice__tag, .svc-card__go,
.sitem__cta, .hspub-type__go, .pjourney__steps {
  margin-top: auto;
  margin-inline: calc(var(--card-pad) * -1);
  padding: 14px var(--card-pad) 0;
  border-top: 1px solid var(--ink-100);
  display: flex; align-items: center; gap: 6px;
  min-height: 44px;
  font-style: normal;                            /* .ptool uses an <i> */
  font-size: var(--card-action); font-weight: 700; color: var(--accent-700);
}
:is(.ecard__cta, .ptool > i, .pspace__count, .pservice__tag, .svc-card__go,
    .sitem__cta, .hspub-type__go) .svg-icon { flex: none; }
/* A journey's foot is its route, not a button: same pinning, meta voice. */
.pjourney__steps { font-size: var(--card-meta); color: var(--ink-500); }

/* ── 4. Variants ───────────────────────────────────────────────────────────

   --action · the five homepage quick tools. The most important cards on the
   site, and the only ones that physically lap the hero — which is the entire
   reason they sit a rung higher at rest. */
.ecard--action, .ptool { box-shadow: var(--shadow-2); }

/* --row · a compact horizontal item: title plus one meta line, no description.
   The .ppop wrapper span becomes the column so the two lines stack. */
.ecard--row, .ppop__item, .meganav__link {
  flex-direction: row; align-items: center; gap: 14px;
  padding: var(--card-pad-row);
  border-radius: var(--card-r-row);
}
.ppop__item > span:not(.ppop__ic), .meganav__link > :is(span, div):not(.meganav__ic) {
  display: flex; flex-direction: column; gap: 2px; min-width: 0;
}
.ppop__item > span:not(.ppop__ic) > span, .meganav__link small {
  display: block; font-size: var(--card-meta); font-weight: 600;
  line-height: 1.4; color: var(--ink-500); overflow-wrap: anywhere;
}
/* A menu row is not an object floating inside a dropdown that is already
   floating: it keeps the row's chip and spacing and gives up the chrome.
   Its LABEL is deliberately not a card title either. A mega-menu column is
   200-300px wide and holds fourteen of these; rendered at --card-title every
   label wrapped to two lines and the panel grew ~300px, past the bottom of a
   laptop screen. It is a 14px bold label — an existing step in the ramp used
   for what it is, which is the point of having a ramp. */
.meganav__link {
  background: transparent; border-color: transparent; box-shadow: none;
  padding: 9px 10px; gap: 12px; align-items: flex-start;
}
.meganav__link b { font-size: var(--card-body); line-height: 1.35; }
.meganav__link small { line-height: 1.35; }
.meganav__link:hover { background: var(--ink-50); }

/* --panel · an in-column information panel. NOT pressable, so it does not lift
   and it does not float: a column of information is not a stack of objects.
   display: block, because a panel holds prose whose own margins set its rhythm.
   (.card keeps --shadow-1: it is the platform's generic surface and appears
   inside dashboard grids as well as in columns.) */
.ecard--panel, .svc-block, .svc-glance, .mdapanel, .svc-hints {
  display: block; box-shadow: none; padding: var(--card-pad);
}
.svc-hints__head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.svc-hints__head .svg-icon { color: var(--accent-700); flex: none; }
.svc-hints ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.svc-hints li {
  display: grid; grid-template-columns: 20px 1fr; gap: 10px;
  font-size: var(--card-body); line-height: 1.55; color: var(--ink-700);
}
.svc-hints li .svg-icon { color: var(--ink-400); margin-top: 2px; }
/* The aliases got this and --panel itself did not, so a panel heading sat flush
   against the first paragraph under it. Its specificity (0,1,1) outranks
   .section-gap, so two view workstreams each restored the rhythm with about
   twenty inline `margin` attributes rather than use the variant as intended.
   One declaration retires the reason for that. Inline margins already written
   still win, so nothing shifts until they are removed. */
.ecard--panel > :is(h2, h3), .svc-block > h2, .svc-glance > h3 { margin-bottom: 14px; }

/* --invert · a dark call-to-action panel. The single gradient in the system,
   and it is a panel rather than a card in a grid. Its CTA takes
   --accent-on-dark: --brand-400 measured 2.04:1 on this surface. */
.ecard--invert, .svc-cta, .meganav__feature {
  background: linear-gradient(160deg, var(--brand-900), var(--brand-700));
  border-color: transparent; box-shadow: var(--shadow-2); color: #fff;
}
.svc-cta { display: block; }
.svc-cta > h3, .meganav__feature b { color: #fff; }
.svc-cta p { color: rgb(255 255 255 / .86); font-size: var(--card-body); }
.meganav__feature small { font-size: var(--card-note); color: rgb(255 255 255 / .86); flex: 1; }
.meganav__feature .meganav__ic { background: rgb(255 255 255 / .16); color: #fff; box-shadow: none; }
.ecard--invert .ecard__cta, .meganav__featcta {
  display: inline-flex; align-items: center; gap: 6px; min-height: 44px;
  border: 0; margin: 0; padding: 0;
  font-size: var(--card-action); font-weight: 700; color: var(--accent-on-dark);
}

/* --featured · the one sanctioned use of gold on a card: a 3px rule along the
   top edge. Gold never carries text and never tints a surface (white on Abia
   Gold is 1.64:1, which brand.php asserts must keep failing). The rule itself
   is 1.64:1 against white and is therefore DECORATION — the word in the flag
   is what actually says "popular", which is how this stays inside 1.4.1. */
.ecard--featured::before, .svc-card--featured::before {
  content: ''; position: absolute; top: -1px; left: -1px; right: -1px; height: 3px;
  background: var(--abia-gold-400);
  border-radius: var(--card-r) var(--card-r) 0 0;
}

/* --quiet · informational, not applicable. It keeps its white surface and its
   elevation; the distinction is carried by the chip tone and by a word. */
.ecard--quiet .ic-chip, .sitem--info .sitem__ic {
  background: var(--ink-100); color: var(--ink-700);
  box-shadow: inset 0 0 0 1px var(--ink-200);
}
.ecard--quiet .ecard__cta, .sitem--info .sitem__cta { color: var(--ink-700); }

/* The step marker — .pstep and .svc-steps li::before were the same idea at two
   sizes, two radii and two fills, one of them a gradient with an inset
   highlight and decimal-leading-zero numbering. One marker. */
.psteps { counter-reset: step; }
.pstep::before {
  counter-increment: step; content: counter(step);
  width: var(--ic-sm); height: var(--ic-sm); flex: none;
  border-radius: calc(var(--ic-sm) * .29);
  display: grid; place-items: center;
  background: var(--accent-50); color: var(--accent-700);
  font-weight: 800; font-size: 1.05rem;
}
.svc-steps li::before {
  content: counter(sstep);
  width: var(--ic-sm); height: var(--ic-sm); flex: none;
  border-radius: calc(var(--ic-sm) * .29);
  display: grid; place-items: center;
  background: var(--accent-50); color: var(--accent-700); font-weight: 800;
}

/* ── 5. States ─────────────────────────────────────────────────────────────
   One gesture for every family: a 2px lift, one shadow step, one light border
   tint, 160ms on the house easing. Six hover dialects — -3px, -4px, border to
   accent-600, border to accent-100, a bespoke 46px shadow, a sliding rail —
   become one. The hover border is red-200, not a full-saturation red: a 1px
   saturated edge on a five-card grid flickers the whole page red as the mouse
   travels across it, where red-200 just reads as lit. */
.ecard--link, a.ecard, a.ptool, a.pspace, a.pservice, a.pjourney, a.ppop__item,
a.svc-card, .sitem, a.hspub-type, a.meganav__link, a.pstep, a.card {
  transition: transform .16s var(--ease), box-shadow .16s var(--ease), border-color .16s var(--ease);
}

/* THE GATE IS MANDATORY. On Android Chrome and iOS Safari :hover latches after
   a tap, so the card a citizen just pressed stays raised and outlined on the
   page they land on. .pmod in the portal layer was already gated and carries
   the comment explaining why; none of the nine public families had copied it. */
@media (hover: hover) and (pointer: fine) {
  :is(.ecard--link, a.ecard, a.ptool, a.pspace, a.pservice, a.pjourney,
      a.ppop__item, a.svc-card, .sitem, a.hspub-type, a.pstep):hover {
    transform: translateY(var(--card-lift));
    box-shadow: var(--shadow-2);
    border-color: var(--card-bd-hover);
  }
  /* …except on a quiet card. Its chip is grey ON PURPOSE — that and the word
     "Guide" are the entire distinction between "you can apply for this" and
     "this is something to read" — and the shared rule was brightening it to
     --abia-red-100 the moment a mouse touched it. The exclusion lives in the
     subject rather than in a later override because the subject list carries
     element-qualified selectors (a.ptool), which push this rule to (0,3,1):
     any override written in plain classes is (0,3,0) and silently loses. */
  :is(.ecard--link, a.ecard, a.ptool, a.pspace, a.pservice, a.pjourney,
      a.ppop__item, a.svc-card, .sitem, a.hspub-type):not(.ecard--quiet, .sitem--info):hover
    :is(.ic-chip, .ptool__ic, .pspace__ic, .pservice__ic, .pjourney__ic,
        .svc-card__ic, .sitem__ic, .hspub-type__ic, .ppop__ic) {
    background: var(--abia-red-100);
  }
  /* The one card already at --shadow-2 steps up to the overlay rung. */
  :is(.ecard--action, a.ptool):hover { box-shadow: var(--shadow-3); }
}

/* Focus answers on the CARD as well as on the control, so a keyboard user can
   see which card they are inside — but it does NOT lift. Displacing the thing
   you just focused is disorienting, and the ring already says where you are.
   Written beside :hover so the two can never drift apart again; before this,
   :focus-within existed on exactly one of seven card families. */
.ecard--link:focus-within, a.ecard:focus-visible, a.ptool:focus-visible,
a.pspace:focus-visible, a.pservice:focus-visible, a.pjourney:focus-visible,
a.ppop__item:focus-visible, a.svc-card:focus-visible, .sitem:focus-within,
a.hspub-type:focus-visible, a.meganav__link:focus-visible {
  border-color: var(--card-bd-hover); box-shadow: var(--shadow-2);
}
/* A card's own corner, not the global rule's — which is why `border-radius: 4px`
   came off the global :focus-visible. */
:is(a.ecard, a.ptool, a.pspace, a.pservice, a.pjourney, a.svc-card,
    a.hspub-type):focus-visible { border-radius: var(--card-r); }
:is(a.ppop__item, a.meganav__link):focus-visible { border-radius: var(--card-r-row); }

/* The only feedback a finger gets, and the reason the transition above is not
   itself inside the hover gate. */
:is(.ecard--link, a.ecard, a.ptool, a.pspace, a.pservice, a.pjourney,
    a.ppop__item, a.svc-card, a.hspub-type, a.meganav__link):active {
  transform: scale(.985); transition-duration: .08s;
}

/* Reduced motion. `transition: none` alone is NOT the fallback — it removes the
   easing and leaves the displacement, so the card still jumps 2px, now
   instantly. `transform: none` is the fix. The shadow and the border tint stay:
   they are legitimate non-motion signals, and a citizen who asked for less
   motion has not asked for less feedback. This one block replaces three
   partial ones that each covered a single family. */
@media (prefers-reduced-motion: reduce) {
  .ecard, .ecard--link, .ptool, .pspace, .pservice, .pjourney, .ppop__item,
  .svc-card, .sitem, .hspub-type, .pstep, .meganav__link, .card,
  .ptile img { transition: none; }
  :is(.ecard, .ecard--link, .ptool, .pspace, .pservice, .pjourney, .ppop__item,
      .svc-card, .sitem, .hspub-type, .pstep, .meganav__link,
      .card):is(:hover, :active, :focus-within, :focus-visible),
  .ptile:hover img { transform: none; }
}

/* ── 6. Composition inside the legacy families ─────────────────────────────
   .sitem wraps its chip, title and summary in a block <a>, so those three are
   not flex items of the card and never saw its gap. */
.sitem__go { display: flex; flex-direction: column; gap: var(--card-gap); }

/* ── 7. Layout ─────────────────────────────────────────────────────────────
   EXPLICIT column counts, never auto-fit or auto-fill. auto-fill keeps the
   empty track, which is what left three large voids down the right of
   /services — "Property & Development" is one category rendered as a single
   third-width card with two thirds of the row blank. auto-fit collapses them
   but then stretches that lone card to the full 1160px, which is grotesque for
   a service card. Explicit counts are deterministic, two engineers reproduce
   them identically, and .ecards--short (computed in PHP from count(), so it
   needs no :has() and works with JS off) handles the short group properly.

   minmax(0, 1fr) on every track is mandatory: a grid item's default min-width
   is auto, so a track cannot shrink below its longest unbreakable word, and a
   single 39-character reference number — the shape of an ABSSIN or a notice
   number — pushed .ppop__item 61px past the viewport edge. The dashboard layer
   has done this since it was written; the public layer never picked it up. */
.ecards { display: grid; gap: 18px; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.ecards--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.ecards--5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.ecards--short { grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: 776px; }
.ptools__grid { display: grid; gap: 18px; grid-template-columns: repeat(5, minmax(0, 1fr)); }
.pspaces, .ppop { display: grid; gap: 18px; grid-template-columns: repeat(4, minmax(0, 1fr)); }
.pservices, .pjourneys, .psteps, .svc-cards, .sitems, .hspub-types {
  display: grid; gap: 18px; grid-template-columns: repeat(3, minmax(0, 1fr));
}
.svc-cards, .sitems, .hspub-types { margin-top: 24px; }
:is(.ecards, .ptools__grid, .pspaces, .ppop, .pservices, .pjourneys, .psteps,
    .svc-cards, .sitems, .hspub-types) > * { min-width: 0; }

/* One page column. Exactly one public view uses .pshell (1240/24px); the other
   forty-four use .container (1160/20px), so the content column narrowed by
   80px and the left edge jumped 36px inward the moment a citizen clicked from
   the homepage into Services. .container is shared with the staff consoles, so
   it is NOT changed globally — .page-hero and .psection are public-only, which
   gets one column at zero console risk. */
.page-hero > .container, .psection > .container {
  max-width: var(--page-max); padding-inline: 24px;
}

/* ── 8. Breakpoints ────────────────────────────────────────────────────────
   One ladder, in one place, rather than in three media blocks a screen apart.

   The action row breaks at 1200, not 1024. The comment on .ptools__grid
   correctly computed that five tools need 1214px and then set the breakpoint
   190px below it: at 1100px each card was 198px wide with 152px of usable text
   and the description wrapped to six lines. */
@media (max-width: 1200px) {
  .ptools__grid, .ecards--5 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .pspaces, .ppop, .ecards--4 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 1024px) {
  :root { --card-pad: 20px; --card-pad-row: 14px 16px; }
  .ecards, .ecards--4, .ecards--5, .ecards--short,
  .ptools__grid, .pspaces, .ppop, .pservices, .pjourneys, .psteps,
  .svc-cards, .sitems, .hspub-types { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
}
@media (max-width: 640px) {
  :root { --card-pad: 16px; --card-pad-row: 12px 14px; }
  .ecards, .ecards--4, .ecards--5, .ecards--short,
  .ptools__grid, .pspaces, .ppop, .pservices, .pjourneys, .psteps,
  .svc-cards, .sitems, .hspub-types { grid-template-columns: minmax(0, 1fr); gap: 14px; }

  /* Four public views set grid-template-columns INLINE, and an inline style
     beats every media query — so those grids stayed three and four across at
     390px, each card about 80px wide. about.php even documents this exact trap
     in a comment and then leaves three instances standing. The views are being
     cleaned up; until they are, the phone wins. */
  .pservices, .pstats { grid-template-columns: minmax(0, 1fr) !important; }

  /* --action and --category become a media object: the chip beside the title
     rather than stacked above it. Worth about 60px a card, and the homepage is
     15,323px tall at 390px — around twenty phone screens. */
  .ecard--action, .ecard--category, .ptool, .pspace, .pservice, .pjourney {
    display: grid; column-gap: 14px; row-gap: 6px; align-items: start;
    grid-template-columns: var(--ic-sm) minmax(0, 1fr);
    grid-template-areas: "ic title" ". body" "cta cta";
  }
  :is(.ecard--action, .ecard--category) > .ic-chip,
  .ptool > .ptool__ic, .pspace > .pspace__ic,
  .pservice > .pservice__ic, .pjourney > .pjourney__ic {
    --chip: var(--ic-sm); grid-area: ic; align-self: center;
  }
  :is(.ecard--action, .ecard--category) > .ecard__title,
  .ptool > b, .pspace > h3, .pservice > div, .pjourney > h3 { grid-area: title; align-self: center; }
  :is(.ecard--action, .ecard--category) > .ecard__sum,
  .ptool > span:not(.ptool__ic), .pspace > p, .pservice > p, .pjourney > p { grid-area: body; }
  :is(.ecard--action, .ecard--category) > .ecard__cta,
  .ptool > i, .pspace__count, .pservice__tag, .pjourney__steps { grid-area: cta; }
  :is(.ecard--action, .ecard--category) > .ic-chip > .svg-icon,
  :is(.ptool__ic, .pspace__ic, .pservice__ic, .pjourney__ic) > .svg-icon { width: 20px; height: 20px; }

  /* The gold rule is the one thing a 390px card can lose, because it is the
     one thing on a card that carries no information — the flag beside it says
     "Popular" in words and stays. Nothing else is dropped: a citizen on a phone
     gets exactly what a citizen on a laptop gets. */
  .ecard--featured::before, .svc-card--featured::before { display: none; }

  /* The public page column. --card-pad is 16px here and the gutter is 20, so a
     card's left edge sits just inside the heading above it. */
  .page-hero > .container, .psection > .container { padding-inline: 20px; }

  /* 96px of band between every section is about 1,500px of empty homepage on a
     phone. 48px still reads as a section break at this width. */
  .psection { padding-block: var(--space-8); }
  .psection--tight { padding-block: var(--space-6); }
  /* Repeated here because this block is the last word in the file: without
     them, .psection would win by source order and a "joined" strip would grow
     a 48px gap from the section it is meant to continue. */
  .psection--joined, .psection--ruled { padding-top: 0; }
  .psection--ruled > .pshell { padding-top: var(--space-8); }

  /* Both audience labels wrapped to two lines at 390px, taking the control from
     51px to 74px, because the 20px horizontal padding survived the breakpoint.
     Shortening them is a view change; this at least makes each one a target. */
  .paud label { padding: 10px 6px; font-size: .84rem; min-height: 44px; }
}
@media (max-width: 380px) {
  .page-hero > .container, .psection > .container { padding-inline: 16px; }
}
@media (min-width: 1360px) {
  .page-hero > .container, .psection > .container { padding-inline: 32px; }
}

/* ── 9. Two things the public sheet was simply missing ─────────────────────
   .req marks a required field in market_shop.php and hospitality_register.php
   — seventeen of them — and was defined only in mda.css, which the public
   layout never loads. Every asterisk rendered as plain bold black. */
.req { color: var(--bad-600); font-weight: 700; }
/* The hero search input measured 26.8px. The touch block covers it on a phone;
   it was short for a mouse too, and the pill around it is already tall enough
   to hold a proper target. */
.psearch input { min-height: 44px; }

/* ── My market shops (/me/shops): the shop's own bank account ───────────── */
.shop-acct { margin: 12px 0 0; padding: 12px 14px; border-radius: 12px; background: var(--ink-50); border: 1px solid var(--ink-100); }
.shop-acct__title { display: flex; align-items: center; gap: 6px; margin: 0 0 4px; font-weight: 700; font-size: .88rem; }
.shop-acct__num { margin: 0; font-size: 1.45rem; font-weight: 800; letter-spacing: .06em; }
.shop-acct__meta { margin: 2px 0 0; font-size: .86rem; color: var(--ink-700); }

/* ── The camera (assets/js/abia-camera.js) ─────────────────────────────────
   Every photo capture page opens it: an agent photographing a citizen
   (FieldOps), and a citizen photographing themselves (ABSSIN registration,
   the photo update, /me/photo/new). Here rather than in fieldops.css since
   2026-10-21, because the public and portal layouts load app.css and not
   that sheet. Built by the script only while it is open, over everything,
   with the phone's insets honoured — the --apx-* insets and stack exist only
   under the app shell, so each has its fallback. Dark on purpose: a
   photographer judges light against black, and a white frame around the
   preview reads as glare. The mirror is on the front camera's PREVIEW only;
   the photo is the raw frame. */

html.acam-open { overflow: hidden; }
.acam {
  position: fixed; inset: 0; z-index: var(--apx-z-modal, 1200);
  display: flex; flex-direction: column;
  background: #0B0B0C; color: #FFFFFF;
}
.acam__top {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: max(10px, var(--apx-in-t, env(safe-area-inset-top))) max(12px, var(--apx-in-r, env(safe-area-inset-right))) 10px max(16px, var(--apx-in-l, env(safe-area-inset-left)));
}
.acam__title { margin: 0; min-width: 0; font-size: 16px; font-weight: 700; line-height: 1.3; color: #FFFFFF; overflow-wrap: anywhere; }
.acam__cancel {
  flex: none; min-height: 44px; padding: 0 16px; border: 1px solid rgba(255, 255, 255, .45); border-radius: 10px;
  background: transparent; color: #FFFFFF; font: inherit; font-size: 15px; font-weight: 700; cursor: pointer;
}
.acam__stage { position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden; background: #000000; }
.acam__video, .acam__still { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; background: #000000; }
.acam.is-mirrored .acam__video { transform: scaleX(-1); }
.acam__guide {
  position: absolute; left: 50%; top: 50%; width: min(62vw, 46vh); aspect-ratio: 3 / 4;
  transform: translate(-50%, -50%); border: 3px dashed rgba(255, 255, 255, .92); border-radius: 50%;
  box-shadow: 0 0 0 200vmax rgba(0, 0, 0, .32); pointer-events: none;
  transition: border-color .2s ease, box-shadow .2s ease;
}
/* The positioning, on the oval itself: amber while something needs to change,
   a solid green ring when the face is where the photo wants it. Colour is not
   the only signal — the coaching line says the same thing in words. */
.acam__guide.is-adjust { border-color: #FFC24A; }
.acam__guide.is-good { border-style: solid; border-color: #3DDC84; box-shadow: 0 0 0 200vmax rgba(0, 0, 0, .32), 0 0 0 4px rgba(61, 220, 132, .35); }
.acam__live {
  position: absolute; top: 12px; left: 12px; padding: 4px 8px; border-radius: 6px;
  background: var(--danger-fg); color: #FFFFFF; font-size: 11px; font-weight: 800; letter-spacing: .08em;
}
.acam__coach {
  position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%);
  width: max-content; max-width: calc(100% - 24px); margin: 0; padding: 9px 16px; border-radius: 999px;
  background: rgba(11, 11, 12, .82); color: #FFFFFF; font-size: 16px; font-weight: 700; line-height: 1.3; text-align: center;
  pointer-events: none;
}
.acam__coach--good { background: rgba(1, 92, 44, .92); }
.acam__hint { margin: 0; padding: 10px 16px 0; font-size: 14px; line-height: 1.4; text-align: center; color: rgba(255, 255, 255, .86); }
.acam__status { margin: 0; min-height: 1.4em; padding: 6px 16px 0; font-size: 15px; font-weight: 600; line-height: 1.4; text-align: center; color: #FFFFFF; }
.acam__status--error { color: #FFB8A6; }
.acam__status--ok { color: #A6E8BF; }
.acam__controls {
  display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 12px;
  padding: 12px max(16px, var(--apx-in-r, env(safe-area-inset-right))) calc(16px + var(--apx-in-b, env(safe-area-inset-bottom))) max(16px, var(--apx-in-l, env(safe-area-inset-left)));
}
.acam__controls--review { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
/* Each live control owns its column, so a hidden Switch camera (a phone with
   one camera) leaves the shutter in the middle instead of sliding it left. */
.acam__controls:not(.acam__controls--review) > .acam__flip { grid-column: 1; }
.acam__controls:not(.acam__controls--review) > .acam__shutter { grid-column: 2; }
.acam__controls:not(.acam__controls--review) > .acam__spacer { grid-column: 3; }
.acam__flip {
  justify-self: start; min-height: 44px; max-width: 100%; padding: 0 14px; border: 1px solid rgba(255, 255, 255, .4); border-radius: 22px;
  background: rgba(255, 255, 255, .14); color: #FFFFFF; font: inherit; font-size: 14px; font-weight: 700; cursor: pointer;
}
.acam__shutter {
  width: 74px; height: 74px; padding: 0; border: 5px solid #FFFFFF; border-radius: 50%;
  background: rgba(255, 255, 255, .28); box-shadow: inset 0 0 0 4px #0B0B0C; cursor: pointer;
}
.acam__shutter:disabled { opacity: .4; cursor: default; }
.acam__spacer { display: block; }
.acam__btn { width: 100%; min-height: 52px; }
.acam button:focus-visible { outline: 3px solid #FFFFFF; outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .acam__guide { transition: none; } }

/* ── The citizen's photo page (components/photo_capture.php) ──────────────
   Before the camera: what makes a photo that is accepted, beside a drawing
   of the oval. After it: the photograph itself, Retake, and the button that
   sends it. */
.pcap { display: grid; gap: 14px; }
.pcap__intro { display: flex; gap: 18px; align-items: flex-start; }
.pcap__figure { flex: none; padding: 6px; border-radius: 16px; background: var(--ink-50); }
.pcap__figure svg { display: block; }
.pcap__figure-oval { fill: none; stroke: var(--ok-600); stroke-width: 3; stroke-dasharray: 7 5; }
.pcap__figure-head, .pcap__figure-body { fill: var(--abia-neutral-200); stroke: none; }
.pcap__tipsbox { min-width: 0; }
.pcap__tips-title { margin: 0 0 8px; font-size: 1.02rem; }
.pcap__tips { display: grid; gap: 5px; margin: 0 0 8px; padding-left: 1.1em; font-size: .92rem; line-height: 1.5; }
.pcap__how { margin: 0; font-size: .86rem; line-height: 1.5; }
.pcap__open { width: 100%; justify-content: center; gap: 8px; min-height: 52px; }
.pcap__help h3 { margin: 0 0 6px; font-size: 1.02rem; }
.pcap__help h4 { margin: 12px 0 4px; font-size: .92rem; }
.pcap__steps { margin: 6px 0 0; padding-left: 1.3em; font-size: .9rem; line-height: 1.6; }
.pcap__result { display: grid; justify-items: center; gap: 12px; }
.pcap__result-title { justify-self: start; margin: 0; font-size: 1.05rem; }
.pcap__preview {
  display: block; width: min(100%, 300px); height: auto; aspect-ratio: 3 / 4; object-fit: cover;
  border: 1px solid var(--abia-neutral-200); border-radius: 14px; background: var(--ink-100);
}
.pcap__meta { margin: 0; font-size: .82rem; color: var(--ink-700); }
.pcap__attest { display: flex; gap: 10px; align-items: flex-start; width: 100%; font-size: .9rem; line-height: 1.45; text-align: left; cursor: pointer; }
.pcap__attest input { flex: none; width: 20px; height: 20px; margin-top: 2px; }
.pcap__actions { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); gap: 10px; width: 100%; }
.pcap__actions .btn { min-height: 52px; gap: 8px; white-space: normal; justify-content: center; }
.pcap__status { margin: 0; min-height: 1.4em; font-size: .92rem; font-weight: 600; text-align: center; color: var(--ink-700); }
.pcap__note, .pcap__alt { margin: 0; font-size: .84rem; text-align: center; }
@media (max-width: 420px) {
  .pcap__intro { flex-direction: column; align-items: center; text-align: left; }
}
