/* ---------------------------------------------------------------------------
   Star Travel NG — utility and component CSS (Tech Spec §8)

   Tokens live in views/tokens.css and are copied to assets/css/tokens.css by
   `php tools/build_assets.php`; this file assumes they are already loaded.

   No build step: Tailwind classes from a Stitch export are translated into the
   utilities below by hand. A component is extracted on its second use, not its
   first.
   --------------------------------------------------------------------------- */

/* --- Reset ---------------------------------------------------------------- */

*,
*::before,
*::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    font-family: var(--font-sans);
    font-size: var(--text-base);
    line-height: var(--leading-normal);
    color: var(--color-ink);
    background: var(--color-surface-sunken);
}

h1, h2, h3, h4, p, figure, blockquote { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
img, svg { max-width: 100%; display: block; }

/* Every icon has a size by default.

   components/icon emits an inline <svg> with a viewBox and no width/height,
   which a browser renders at its 300x150 default — so an icon in any context
   without an explicit rule came out enormous (the Issue policy arrow and the
   Emergency contact shield on the checkout, among others). Sizing here means a
   new icon is correct wherever it is dropped, instead of correct only where
   someone remembered to add a rule. Context rules are `.x svg` (specificity
   0,1,1) and still win over this `.icon` (0,1,0). */
.icon { width: 20px; height: 20px; flex: 0 0 auto; }
button, input, select, textarea { font: inherit; color: inherit; }

a { color: var(--color-primary-action); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Keyboard focus is always visible — WCAG AA. */
:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

.visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

.skip-link {
    position: absolute;
    left: var(--space-3);
    top: -60px;
    z-index: 100;
    padding: var(--space-2) var(--space-4);
    background: var(--color-surface);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    transition: top var(--transition-fast);
}
.skip-link:focus { top: var(--space-3); }

/* --- Typography ----------------------------------------------------------- */

.h1 {
    font-size: var(--headline-lg);
    font-weight: var(--weight-bold);
    line-height: var(--leading-headline-lg);
    letter-spacing: var(--tracking-headline);
}
.h2 { font-size: var(--headline-md); font-weight: var(--weight-semibold); line-height: var(--leading-headline-md); }
.h3 { font-size: var(--headline-sm); font-weight: var(--weight-semibold); line-height: var(--leading-headline-sm); }

/* The design drops the display size on phones rather than letting it wrap. */
@media (max-width: 767px) {
    .h1 { font-size: var(--headline-lg-mobile); line-height: 32px; letter-spacing: -0.01em; }
}

.text-sm { font-size: var(--text-sm); }
.text-xs { font-size: var(--text-xs); }
.text-muted { color: var(--color-ink-muted); }
.text-subtle { color: var(--color-ink-subtle); }
.text-danger { color: var(--color-danger); }
.text-primary { color: var(--color-primary-action); }
.text-center { text-align: center; }
.text-right { text-align: right; }
.font-medium { font-weight: var(--weight-medium); }
.font-semibold { font-weight: var(--weight-semibold); }

/* --- Layout utilities ----------------------------------------------------- */

.stack > * + * { margin-top: var(--space-4); }
.stack-sm > * + * { margin-top: var(--space-2); }
.stack-lg > * + * { margin-top: var(--space-5); }

.row { display: flex; align-items: center; gap: var(--space-3); }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
.row-wrap { flex-wrap: wrap; }
.row-center { display: flex; align-items: center; justify-content: center; gap: var(--space-3); }
.grow { flex: 1 1 auto; min-width: 0; }

.grid { display: grid; gap: var(--space-4); }
.grid-tiles { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }

.container {
    width: 100%;
    max-width: var(--content-max);
    margin-inline: auto;
    padding-inline: var(--space-4);
}

/* --- Page layout ---------------------------------------------------------- */

/* The design centres content at 1440px with a 16px gutter on phones and 48px
   from 768px up (design/stitch/agency-dashboard). */
.page {
    width: 100%;
    max-width: 1440px;
    margin-inline: auto;
    padding-inline: var(--margin-mobile);
    padding-block: var(--space-6);
}

@media (min-width: 768px) {
    .page { padding-inline: var(--margin-desktop); }
}

/* Twelve-column feel without a grid framework: the design uses a 3-column
   split with the main panel spanning 2. */
.split { display: grid; gap: var(--space-6); }
.split-main { min-width: 0; }
.split-rail { display: flex; flex-direction: column; gap: var(--space-5); min-width: 0; }

@media (min-width: 1080px) {
    .split { grid-template-columns: 2fr 1fr; }
}

/* --- Notice banner -------------------------------------------------------- */

/* Left red rule, hairline border, dismissible — the insurer notice the design
   puts above the KPI row. */
.notice-banner {
    display: flex;
    align-items: flex-start;
    gap: var(--space-4);
    padding: var(--space-4);
    margin-bottom: var(--space-6);
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-left: 4px solid var(--color-primary);
    border-radius: var(--radius-md);
}
.notice-banner-body { flex: 1 1 auto; min-width: 0; }
.notice-banner-title { font-weight: var(--weight-semibold); }
.notice-banner svg { color: var(--color-primary); flex: 0 0 auto; }
.notice-banner-dismiss {
    flex: 0 0 auto;
    width: 32px;
    height: 32px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--color-ink-subtle);
    cursor: pointer;
    font-size: 18px;
    line-height: 1;
}
.notice-banner-dismiss:hover { background: var(--color-surface-sunken); color: var(--color-ink); }

/* --- KPI cards ------------------------------------------------------------ */

.kpi-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--gutter);
    margin-bottom: var(--space-6);
}

@media (min-width: 768px) { .kpi-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1080px) { .kpi-grid { grid-template-columns: repeat(4, 1fr); } }

.kpi {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-4);
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-md);
    transition: box-shadow var(--transition-fast);
}
.kpi:hover { box-shadow: var(--shadow-md); }

.kpi-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
}
.kpi-label { font-size: var(--body-sm); color: var(--color-ink-muted); }
.kpi-icon {
    width: 32px;
    height: 32px;
    display: grid;
    place-items: center;
    border-radius: var(--radius-pill);
    background: var(--color-primary-light);
    color: var(--color-primary-action);
    flex: 0 0 auto;
}
.kpi-icon svg { width: 16px; height: 16px; }
.kpi-value {
    font-size: var(--headline-md);
    font-weight: var(--weight-bold);
    line-height: var(--leading-headline-md);
    font-variant-numeric: tabular-nums;
}
.kpi-value-alert { color: var(--color-danger); }
.kpi-empty { font-size: var(--headline-sm); color: var(--color-ink-subtle); }

/* --- Panels (a card with a flush header and edge-to-edge body) ------------ */

.panel {
    display: flex;
    flex-direction: column;
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-md);
    overflow: hidden;
}
.panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-5);
    border-bottom: 1px solid var(--color-line);
}
/* A panel title is a heading that may carry an icon. The reset makes every
   <svg> a block, so without this the icon dropped onto its own line above the
   words — visible on the Trip, Emergency contact and Payment panels. */
.panel-title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--headline-sm);
    font-weight: var(--weight-semibold);
}
.panel-body { padding: var(--space-5); }
.panel-body-flush { padding: 0; }

/* A quiet action in a panel header. */
.link-arrow {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    font-size: var(--label-md);
    font-weight: var(--weight-semibold);
    color: var(--color-primary-action);
}
.link-arrow svg { width: 14px; height: 14px; }

/* --- Data table ----------------------------------------------------------- */

/* The design flushes the table to the panel edge and tints the header row. */
.table-flush thead th {
    padding-inline: var(--space-5);
    background: var(--color-surface-sunken);
    font-size: var(--label-sm);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.table-flush tbody td { padding-inline: var(--space-5); }
.table-flush tbody tr:last-child td { border-bottom: 0; }

/* --- Ageing bars ---------------------------------------------------------- */

.ageing { display: flex; flex-direction: column; gap: var(--space-3); }
.ageing-row { display: flex; flex-direction: column; gap: var(--space-1); }
.ageing-head {
    display: flex;
    justify-content: space-between;
    gap: var(--space-2);
    font-size: var(--body-sm);
}
.ageing-amount { font-variant-numeric: tabular-nums; font-weight: var(--weight-medium); }
.ageing-track {
    height: 6px;
    border-radius: var(--radius-pill);
    background: var(--color-surface-sunken);
    overflow: hidden;
}
.ageing-fill { height: 100%; border-radius: var(--radius-pill); background: var(--color-primary); }
.ageing-fill-warn { background: var(--color-warning); }
.ageing-fill-danger { background: var(--color-danger); }

/* --- Action list (quick actions) ------------------------------------------ */

.action-list { display: flex; flex-direction: column; gap: var(--space-3); }
.action-list .btn { justify-content: flex-start; }
.action-list .btn svg { width: 18px; height: 18px; }

/* --- Cards ---------------------------------------------------------------- */

.card {
    background: var(--color-surface-raised);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
    padding: var(--space-5);
}

@media (max-width: 767px) {
    .card { padding: var(--space-4); }
}

.card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    margin-bottom: var(--space-4);
}

.tile { display: flex; flex-direction: column; gap: var(--space-2); }
.tile-label { font-size: var(--text-sm); color: var(--color-ink-muted); }
.tile-value { font-size: var(--text-2xl); font-weight: var(--weight-bold); line-height: var(--leading-tight); }
.tile-empty { font-size: var(--text-lg); color: var(--color-ink-subtle); }

/* --- Status chips (Tech Spec §8 — one component, used everywhere) ---------- */

.chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 2px var(--space-2);
    border-radius: var(--radius-md);
    font-size: var(--label-sm);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.02em;
    white-space: nowrap;
}
.chip-active   { color: var(--color-success); background: var(--color-success-bg); }
.chip-pending  { color: var(--color-warning); background: var(--color-warning-bg); }
.chip-expired  { color: var(--color-neutral); background: var(--color-neutral-bg); }
.chip-refunded,
.chip-void     { color: var(--color-danger);  background: var(--color-danger-bg); }
.chip-part     { color: var(--color-accent);  background: var(--color-accent-bg); }

/* --- Buttons -------------------------------------------------------------- */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: 44px;                 /* touch target */
    padding: var(--space-2) var(--space-5);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    background: var(--color-primary-action);
    color: var(--color-primary-contrast);
    font-weight: var(--weight-semibold);
    cursor: pointer;
    transition: background var(--transition-fast), border-color var(--transition-fast);
}
.btn:hover { background: var(--color-primary-dark); text-decoration: none; }
.btn:disabled { opacity: 0.55; cursor: not-allowed; }

/* Outlined in the action red — the design's secondary, not a grey box. */
.btn-secondary {
    background: transparent;
    color: var(--color-primary-action);
    border-color: var(--color-primary-action);
}
.btn-secondary:hover { background: var(--color-primary-light); }

/* Tertiary: oxblood text, no background. For Cancel and Go back. */
.btn-ghost { background: transparent; color: var(--color-primary-deep); }
.btn-ghost:hover { background: var(--color-surface-sunken); }

.btn-block { width: 100%; }
.btn-sm { min-height: 36px; padding: var(--space-1) var(--space-3); font-size: var(--text-sm); }

/* --- Forms ---------------------------------------------------------------- */

.field { display: block; }
.field + .field { margin-top: var(--space-4); }

.label {
    display: block;
    margin-bottom: var(--space-2);
    font-size: var(--label-md);
    font-weight: var(--weight-semibold);
    line-height: var(--leading-label);
    letter-spacing: var(--tracking-label);
    color: var(--color-ink);
}

.input,
.select,
.textarea {
    width: 100%;
    min-height: 44px;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-md);
    background: var(--color-surface);
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.input:focus,
.select:focus,
.textarea:focus {
    border-color: var(--color-primary);
    box-shadow: 0 0 0 3px var(--color-primary-light);
    outline: none;
}
.textarea { min-height: 120px; padding-top: var(--space-3); }

.input::placeholder,
.textarea::placeholder { color: var(--color-ink-subtle); }

/* A field with a control inside it — the password eye, a currency prefix, a
   clear button. The design places these within the input rather than beside
   it, which keeps the 44px row height and the field's full width. */
.input-affix { position: relative; }
.input-affix .input { padding-right: 48px; }
.input-affix-button {
    position: absolute;
    right: 0;
    top: 0;
    width: 44px;
    height: 44px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--color-ink-muted);
    cursor: pointer;
}
.input-affix-button:hover { color: var(--color-ink); }

.input-error { border-color: var(--color-danger); }
.field-error {
    display: block;
    margin-top: var(--space-1);
    font-size: var(--text-sm);
    color: var(--color-danger);
}
.field-hint {
    display: block;
    margin-top: var(--space-1);
    font-size: var(--text-sm);
    color: var(--color-ink-muted);
}

/* Two fields side by side on desktop, stacked on phones. */
.field-row { display: grid; gap: var(--space-4); }
.field-row .field + .field { margin-top: 0; }

@media (min-width: 768px) {
    .field-row { grid-template-columns: 1fr 1fr; }
}

/* Label/value pairs for read-only detail panels.

   Two shapes share this class and they size differently, which is worth stating
   because getting it wrong cut pages off:

     <dt>Label</dt><dd>Value</dd>          -> the columns are LABEL and VALUE
     <div><dt>..</dt><dd>..</dd></div>     -> the columns are whole PAIRS

   In the second shape a `max-content` first column is as wide as the widest
   PAIR — so a 50-character street address or a 46-character fax number pushed
   the grid past the panel and the page scrolled sideways. `.detail-grid-pairs`
   below is that shape, and it does not use max-content at all.

   `minmax(0, …)` and `min-width: 0` are the general fix: a grid track defaults
   to min-content and will not shrink past it, so one long unbroken value —
   a URL, an account number — overflows a container that has room for
   everything else. */

.detail-grid { display: grid; gap: var(--space-2) var(--space-4); }
.detail-grid > * { min-width: 0; }
.detail-grid dt { font-size: var(--label-sm); color: var(--color-ink-muted); }
.detail-grid dd {
    margin: 0;
    font-weight: var(--weight-medium);
    /* A long address or URL breaks rather than pushing the panel wider. */
    overflow-wrap: anywhere;
}
.mono { font-family: var(--font-mono); font-size: var(--body-sm); }

@media (min-width: 480px) {
    .detail-grid { grid-template-columns: minmax(0, max-content) minmax(0, 1fr); align-items: baseline; }
}

/* Pairs wrapped in a <div>: they are cards in a flexible grid, not a two-column
   table. auto-fit means one column on a phone, two on a tablet, three on a wide
   panel — decided by the space there is rather than by a breakpoint guess. */
.detail-grid-pairs { grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr)); }
.detail-grid-pairs > div { min-width: 0; }
.detail-grid-pairs dt { margin-bottom: var(--space-1); }

@media (min-width: 480px) {
    .detail-grid-pairs { grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr)); align-items: start; }
}

/* --- Choice list (radio cards) -------------------------------------------- */

.choice-list { display: grid; gap: var(--space-3); border: 0; padding: 0; margin: 0; }

.choice {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-4);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-md);
    cursor: pointer;
}
.choice:hover { background: var(--color-surface-sunken); }
.choice-selected { border-color: var(--color-primary); background: var(--color-primary-light); }
.choice input { margin-top: 3px; accent-color: var(--color-primary-action); }

/* Two currencies side by side on desktop, stacked on a phone — the pair is a
   comparison, so they belong on one line where there is room for one. */
.choice-row { display: grid; gap: var(--space-3); border: 0; padding: 0; margin: 0; }

@media (min-width: 768px) {
    .choice-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* The choice label holds a stacked amount-and-caption pair. */
.choice > span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.choice:has(input:checked) { border-color: var(--color-primary); background: var(--color-primary-light); }

.choice-body { display: flex; flex-direction: column; gap: var(--space-1); }
.choice-title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-weight: var(--weight-semibold);
}
.choice-note { font-size: var(--body-sm); color: var(--color-ink-muted); }
.choice-impact { font-size: var(--label-sm); color: var(--color-success); }
.choice-impact-warn { color: var(--color-warning); }

/* --- Stepper -------------------------------------------------------------- */

/* Trip Details -> Travellers -> Compare Plans -> Payment. Completed steps show
   a tick, the current one is filled, later ones are muted
   (design/stitch/compare-plans). */
.stepper {
    display: flex;
    align-items: center;
    width: 100%;
    max-width: 680px;
    margin-inline: auto;
    margin-bottom: var(--space-6);
}
.step { display: flex; flex-direction: column; align-items: center; flex: 0 0 auto; }
.step-marker {
    width: 32px;
    height: 32px;
    display: grid;
    place-items: center;
    border-radius: var(--radius-pill);
    background: var(--color-surface-sunken);
    color: var(--color-ink-subtle);
    font-size: var(--label-md);
    font-weight: var(--weight-semibold);
    margin-bottom: var(--space-1);
}
.step-marker svg { width: 16px; height: 16px; }
.step-label {
    font-size: var(--label-sm);
    color: var(--color-ink-muted);
    white-space: nowrap;
}

.step-done .step-marker { background: var(--color-primary-action); color: var(--color-primary-contrast); }
.step-current .step-marker { background: var(--color-primary-light); color: var(--color-primary-dark); }
.step-current .step-label { color: var(--color-primary-action); font-weight: var(--weight-bold); }

.step-line { flex: 1 1 auto; height: 1px; background: var(--color-line-strong); margin-inline: var(--space-2); }
.step-line-done { background: var(--color-primary-action); }

@media (max-width: 767px) {
    .step-label { display: none; }
}

/* --- Trip summary strip --------------------------------------------------- */

.trip-summary {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-4);
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-md);
    margin-bottom: var(--space-5);
}
.trip-summary-facts {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}
.trip-summary-facts svg { color: var(--color-primary); flex: 0 0 auto; width: 18px; height: 18px; }

@media (min-width: 768px) {
    .trip-summary { flex-direction: row; align-items: center; justify-content: space-between; }
}

/* --- Plan comparison cards ------------------------------------------------ */

.plan-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--gutter);
    align-items: start;
}

@media (min-width: 768px) { .plan-grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1080px) { .plan-grid { gap: var(--space-6); } }

.plan {
    position: relative;
    display: flex;
    flex-direction: column;
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    transition: box-shadow var(--transition-fast);
}
.plan:hover { box-shadow: var(--shadow-md); }

/* The recommended plan is bordered in the brand red and carries a tag. */
.plan-recommended { border-color: var(--color-primary); }
.plan-tag {
    position: absolute;
    top: -11px;
    left: 50%;
    transform: translateX(-50%);
    padding: 2px var(--space-3);
    border-radius: var(--radius-pill);
    background: var(--color-primary-action);
    color: var(--color-primary-contrast);
    font-size: var(--label-sm);
    font-weight: var(--weight-semibold);
    white-space: nowrap;
}

/* A plan the party cannot buy is de-emphasised rather than hidden, so the
   agent can see it exists and why it is unavailable. */
.plan-unavailable { opacity: 0.72; }
.plan-unavailable .plan-price { color: var(--color-ink-subtle); }

.plan-body {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    padding: var(--space-5);
    border-bottom: 1px solid var(--color-line);
}
.plan-name {
    font-size: var(--headline-sm);
    font-weight: var(--weight-semibold);
    margin-bottom: var(--space-1);
}
.plan-price {
    font-size: var(--headline-md);
    font-weight: var(--weight-bold);
    line-height: var(--leading-headline-md);
    font-variant-numeric: tabular-nums;
}
.plan-price-secondary {
    font-size: var(--body-sm);
    color: var(--color-ink-muted);
    font-variant-numeric: tabular-nums;
}

.plan-benefits { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-4); }
.plan-benefit { display: flex; align-items: flex-start; gap: var(--space-2); font-size: var(--body-sm); }
.plan-benefit svg { width: 16px; height: 16px; color: var(--color-success); flex: 0 0 auto; margin-top: 2px; }

.plan-warning {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    padding: var(--space-3);
    margin-top: var(--space-4);
    border-radius: var(--radius-md);
    background: var(--color-warning-bg);
    color: var(--color-warning);
    font-size: var(--body-sm);
}
.plan-warning svg { width: 16px; height: 16px; flex: 0 0 auto; margin-top: 1px; }

.plan-footer { padding: var(--space-4) var(--space-5); }

/* --- Quote form ----------------------------------------------------------- */

.traveller-row {
    display: grid;
    gap: var(--space-3);
    align-items: end;
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--color-line);
}
.traveller-row:last-child { border-bottom: 0; }

@media (min-width: 560px) {
    .traveller-row { grid-template-columns: 1fr 140px; }
}

/* --- Alerts / flash ------------------------------------------------------- */

.alert {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-md);
    border: 1px solid transparent;
    font-size: var(--body-sm);
    line-height: var(--leading-body-sm);
}
.alert svg { flex: 0 0 auto; width: 18px; height: 18px; margin-top: 1px; }
.alert-success { color: var(--color-success); background: var(--color-success-bg); border-color: var(--color-success-bg); }
.alert-error   { color: var(--color-danger);  background: var(--color-danger-bg);  border-color: var(--color-danger-bg); }
.alert-warning { color: var(--color-warning); background: var(--color-warning-bg); border-color: var(--color-warning-bg); }
.alert-info    { color: var(--color-accent);  background: var(--color-accent-bg);  border-color: var(--color-accent-bg); }

/* --- Tables ---------------------------------------------------------------
   A table folds to cards when the space IT has is too small — not when the
   VIEWPORT is. The two are not the same thing: the dashboard puts a six-column
   table in the 2fr side of a 2fr/1fr split, which on a 1366px laptop leaves it
   about 610px. The viewport is nowhere near the 768px card breakpoint, so the
   table stayed a table and three of its columns went past the right edge, with
   nothing to say so but a scrollbar at the foot of the panel.

   `container-type: inline-size` makes .table-wrap a query container, so the
   rules below can ask about the column rather than the window. The old 768px
   media query is kept as it was, further down, for browsers without container
   queries: they get exactly today's behaviour, never less. */

.table-wrap {
    overflow-x: auto;
    container-type: inline-size;

    /* Scrolling that is visible before you need it. The default overlay bar on
       Windows fades out and sits at the foot of a tall panel, so the one thing
       that said "there is more to the right" had to be hunted for. */
    scrollbar-width: thin;
    scrollbar-color: var(--color-line-strong) transparent;

    /* A sideways flick inside the table scrolls the table, not the history. */
    overscroll-behavior-x: contain;

    /* Shadows that appear only on the side that HAS more, and only while it
       has it. Two gradients scroll with the content (`local`) and two are
       pinned to the box (`scroll`); where a pair overlaps — at either end of
       the travel — the cover gradient hides the shadow behind it. No script,
       no resize observer, and it is right during the scroll as well as after.
       Kept off the collapsed card view, which has nothing to scroll. */
    background:
        linear-gradient(to right, var(--color-surface) 40%, transparent) 0 0 / 28px 100% no-repeat local,
        linear-gradient(to left, var(--color-surface) 40%, transparent) 100% 0 / 28px 100% no-repeat local,
        linear-gradient(to right, rgba(31, 26, 26, 0.13), transparent) 0 0 / 14px 100% no-repeat scroll,
        linear-gradient(to left, rgba(31, 26, 26, 0.13), transparent) 100% 0 / 14px 100% no-repeat scroll;
}

/* Made focusable by app.js only when it actually overflows, so a keyboard can
   scroll it. The ring is drawn inside: the panel clips its own corners, and an
   outline on the outside edge would be cut in half. */
.table-wrap:focus-visible {
    outline: none;
    box-shadow: inset 0 0 0 2px var(--color-primary);
}

/* The first column stays put while the rest scrolls under it, so a row never
   becomes anonymous halfway across. Opt-in, on the tables wide enough to need
   it — the cell needs its own background or the scrolling cells show through,
   and the header keeps the tinted one .table-flush gives it. */
.table-sticky-first tbody td:first-child,
.table-sticky-first thead th:first-child {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--color-surface);
}
.table-sticky-first.table-flush thead th:first-child { background: var(--color-surface-sunken); }
.table-sticky-first tbody tr:hover td:first-child { background: var(--color-surface-sunken); }

/* A hairline on the pinned column's trailing edge, so it reads as pinned
   rather than as a row that failed to scroll. */
.table-sticky-first tbody td:first-child::after,
.table-sticky-first thead th:first-child::after {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: 1px;
    background: var(--color-line);
}
/* No zebra striping: the design separates rows with whitespace and a
   hairline rule instead. */
.table { width: 100%; border-collapse: collapse; font-size: var(--body-sm); }
.table th,
.table td {
    padding: var(--space-4) var(--space-3);
    text-align: left;
    border-bottom: 1px solid var(--color-line);
}
.table th {
    font-weight: var(--weight-semibold);
    color: var(--color-ink-muted);
    white-space: nowrap;
}
.table tbody tr:hover { background: var(--color-surface-sunken); }
.table .numeric { text-align: right; font-variant-numeric: tabular-nums; }

/* --- Audit timeline ------------------------------------------------------- */

/* The policy history (FR-AUDIT.1-2): a connecting rule with a dot per event,
   each naming the actor and the moment. */
.timeline { position: relative; padding-left: var(--space-5); }
.timeline::before {
    content: "";
    position: absolute;
    left: 5px;
    top: 6px;
    bottom: 6px;
    width: 2px;
    background: var(--color-line);
}
.timeline-item { position: relative; padding-bottom: var(--space-4); }
.timeline-item:last-child { padding-bottom: 0; }
.timeline-dot {
    position: absolute;
    left: calc(-1 * var(--space-5) + 1px);
    top: 6px;
    width: 10px;
    height: 10px;
    border-radius: var(--radius-pill);
    background: var(--color-primary);
    border: 2px solid var(--color-surface);
}

/* --- Empty state ---------------------------------------------------------- */

.empty-state {
    padding: var(--space-6) var(--space-4);
    text-align: center;
    color: var(--color-ink-muted);
}
.empty-state .h3 { color: var(--color-ink); margin-bottom: var(--space-2); }

/* --- Auth screens --------------------------------------------------------- */

.auth-shell {
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: var(--space-5) var(--space-4);
    gap: var(--space-5);
}
.auth-card { width: 100%; max-width: 420px; }
/* Registration only: an agency plus its first contact does not fit a
   sign-in card, and squeezing it there makes it look longer than it is. */
.auth-card-wide { max-width: 760px; }
.auth-brand { display: flex; align-items: center; justify-content: center; }

/* The Star Assurance logo, rendered at a fixed height and left to scale. */
.brand-logo { width: auto; height: 40px; }
.brand-logo-compact { height: 30px; }

@media (max-width: 767px) {
    .brand-logo { height: 34px; }
    .brand-logo-compact { height: 26px; }
}



/* The design centres the header inside the card rather than above it. */
.auth-card-header {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--space-3);
}
.auth-footer {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-sm);
    color: var(--color-ink-muted);
    text-align: center;
}


/* --- State panels (sent, expired, not found) ------------------------------ */

/* The large circular glyph the design uses at the top of a result panel. */
.state-icon {
    width: 56px;
    height: 56px;
    display: grid;
    place-items: center;
    margin-inline: auto;
    border-radius: var(--radius-pill);
}
.state-icon svg { width: 26px; height: 26px; }
.state-icon-info    { background: var(--color-accent-bg);  color: var(--color-accent); }
.state-icon-success { background: var(--color-success-bg); color: var(--color-success); }
.state-icon-muted   { background: var(--color-neutral-bg); color: var(--color-neutral); }
.state-icon-danger  { background: var(--color-danger-bg);  color: var(--color-danger); }

/* --- Password requirements checklist -------------------------------------- */

.requirements {
    padding: var(--space-3) var(--space-4);
    background: var(--color-surface-sunken);
    border-radius: var(--radius-md);
}
.requirements-heading {
    margin-bottom: var(--space-2);
    font-size: var(--label-sm);
    font-weight: var(--weight-semibold);
    color: var(--color-ink-muted);
}
.requirements li {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 2px 0;
    font-size: var(--body-sm);
    color: var(--color-ink-muted);
}

/* Unmet: a hollow ring. Met: a filled tick. The state is carried on the list
   item so it reads correctly to a screen reader via aria-label too. */
.requirements-mark {
    width: 16px;
    height: 16px;
    flex: 0 0 auto;
    border-radius: var(--radius-pill);
    border: 2px solid var(--color-line-strong);
    position: relative;
}
.requirements li[data-met="1"] { color: var(--color-success); }
.requirements li[data-met="1"] .requirements-mark {
    background: var(--color-success);
    border-color: var(--color-success);
}
.requirements li[data-met="1"] .requirements-mark::after {
    content: "";
    position: absolute;
    left: 4px;
    top: 1px;
    width: 4px;
    height: 8px;
    border: solid #fff;
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

/* --- Portal shell --------------------------------------------------------- */

/* Three portals, one design system, one hue. They are told apart by DEPTH —
   agency on white, Star in brand oxblood, UITS in a warm near-black — not by
   introducing a second hue. Chrome colours live in views/tokens.css. */

.app { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; }

.topbar {
    position: sticky;
    top: 0;
    z-index: 30;
    height: var(--topbar-height);
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding-inline: var(--space-4);
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-line);
}

/* The Star top bar is brand red (client direction, 26 August 2026 — it was the
   deeper oxblood --color-primary-deep until then). UITS keeps the white bar it
   has always had (client direction, same day).

   The white-on-white rules that used to sit here are gone: `.portal-uits` was
   painting the portal name and the organisation under it white, on a bar with
   no background rule of its own and therefore --color-surface. "UITS
   administration" has been invisible on every UITS page. On a white bar those
   two want the ordinary ink tokens, which is what they now get by inheriting
   the base rules. */
.portal-star .topbar { background: var(--color-portal-header); border-bottom-color: transparent; color: #fff; }
.portal-star .topbar-portal-name { color: rgba(255, 255, 255, 0.72); }
.portal-star .menu-trigger-label { color: rgba(255, 255, 255, 0.92); }

.topbar-brand { display: flex; align-items: center; flex: 0 0 auto; }
.topbar-brand:hover { text-decoration: none; }

/* Centred on the bar itself, not in the flex flow: the brand on the left and
   the notification/user controls on the right are different widths, so a flex
   item between them centres against neither. Absolute positioning centres it
   against the VIEWPORT, which is what "centred" means to someone looking at
   it. Hidden below 768px, where there is no room beside the controls. */
.topbar-portal {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    display: none;
    flex-direction: column;
    align-items: center;
    gap: 1px;
    max-width: 42%;
    text-align: center;
    pointer-events: none;
}

@media (min-width: 768px) {
    .topbar-portal { display: flex; }
}

/* The label is absolutely centred, so on a bar that also carries a search it
   sits ON TOP of the field rather than beside it. It says which portal you are
   in — which the chrome colour and the navigation already say — and the search
   is the one that does something, so the label gives way.

   Scoped to a bar that HAS a search: Star and UITS carry none, and their label
   still appears from 768px as it always did. */
@media (max-width: 1079px) {
    .topbar-with-search .topbar-portal { display: none; }
}

/* Above 1080px it came back, and on a laptop it was still covered: the search
   sits right of the spacer, and at 1280–1440px its left edge reaches past the
   middle of the bar, which is where the label was pinned. So on a bar with a
   search the label leaves the centre and takes its place in the flow beside
   the logo. Flex can then give each its share, and a long agency name is
   truncated rather than hidden under the field. */
.topbar-with-search .topbar-portal {
    position: static;
    transform: none;
    flex: 0 1 auto;
    min-width: 0;
    max-width: 32%;
    align-items: flex-start;
    text-align: left;
    padding-left: var(--space-3);
    border-left: 1px solid var(--color-line);
}

.topbar-portal-name {
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-ink-subtle);
}

/* The agency's own name, which is the half that tells one signed-in user from
   another — so it is the larger of the two lines. */
.topbar-portal-org {
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--color-ink);
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.portal-star .topbar-portal-org { color: rgba(255, 255, 255, 0.92); }

.icon-button {
    display: grid; place-items: center;
    width: 38px; height: 38px;
    border: 0; border-radius: var(--radius-pill);
    background: transparent; cursor: pointer; color: inherit;
}
.icon-button svg,
.nav-toggle svg { width: 20px; height: 20px; }
.icon-button:hover { background: var(--color-surface-sunken); }
.portal-star .icon-button:hover { background: rgba(255, 255, 255, 0.14); }

.avatar {
    width: 32px; height: 32px;
    display: grid; place-items: center;
    border-radius: var(--radius-pill);
    background: var(--color-primary-light);
    color: var(--color-primary-dark);
    font-size: var(--text-xs);
    font-weight: var(--weight-bold);
    flex: 0 0 auto;
}

/* --- Agency top navigation with dropdowns --------------------------------- */

.navbar {
    position: sticky;
    top: var(--topbar-height);
    z-index: 25;
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-line);
    padding-inline: var(--space-4);
}

.navbar-items { display: flex; align-items: stretch; gap: var(--space-1); }

.navbar-link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-3) var(--space-4);
    border: 0;
    border-bottom: 2px solid transparent;
    background: transparent;
    color: var(--color-ink-muted);
    font: inherit;
    font-weight: var(--weight-medium);
    text-decoration: none;
    cursor: pointer;
    white-space: nowrap;
}
.navbar-link:hover { color: var(--color-ink); background: var(--color-surface-sunken); text-decoration: none; }

/* The current page, and a dropdown whose child is the current page. */
.navbar-link[aria-current="page"],
.navbar-link[data-current="1"] {
    color: var(--color-ink);
    border-bottom-color: var(--color-primary);
    font-weight: var(--weight-semibold);
}

.chevron { display: inline-flex; opacity: 0.7; }
.chevron svg { width: 14px; height: 14px; }

/* --- Dropdown menus (top nav and the user menu) --------------------------- */

.menu { position: relative; display: flex; align-items: center; }

.menu-trigger {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-1) var(--space-2);
    border: 0;
    border-radius: var(--radius-md);
    background: transparent;
    cursor: pointer;
    color: inherit;
    font: inherit;
}

.menu-trigger-label {
    max-width: 160px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--text-sm);
    color: var(--color-ink-muted);
}

.menu-panel {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    z-index: 40;
    min-width: 220px;
    padding: var(--space-2);
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
}
.menu-panel[hidden] { display: none; }
.menu-panel-right { left: auto; right: 0; }

.menu-panel a,
.menu-item-button {
    display: block;
    width: 100%;
    padding: var(--space-2) var(--space-3);
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-ink);
    font: inherit;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}
.menu-panel a:hover,
.menu-item-button:hover { background: var(--color-surface-sunken); text-decoration: none; }
.menu-panel a[aria-current="page"] { color: var(--color-primary-dark); font-weight: var(--weight-semibold); }

.menu-heading {
    display: block;
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-ink-subtle);
}

/* --- Body, sidebar, main -------------------------------------------------- */

.app-body { flex: 1; display: flex; min-height: 0; }

.nav-toggle {
    display: grid; place-items: center;
    width: 38px; height: 38px;
    border: 0; border-radius: var(--radius-md);
    background: transparent; cursor: pointer; color: inherit;
}

.sidebar { display: none; }
.sidebar-scrim { display: none; }

.main {
    flex: 1;
    min-width: 0;
    padding: var(--space-5) var(--space-4);
    padding-bottom: calc(var(--tabbar-height) + var(--space-5));
}

.page-header { margin-bottom: var(--space-5); text-align: center; }

/* Headers that also carry actions keep the title left and the buttons right —
   centring a title while buttons sit beside it just looks misaligned. Applies
   to Policies and Policy detail (client direction, 22 Aug 2026). */
.page-header.row-between { text-align: left; }

/* --- Bottom tab bar: phones only ------------------------------------------ */

.tabbar {
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 35;
    height: var(--tabbar-height);
    display: flex;
    background: var(--color-surface);
    border-top: 1px solid var(--color-line);
    padding-bottom: env(safe-area-inset-bottom);
}
.tabbar a {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    font-size: var(--text-xs);
    color: var(--color-ink-muted);
    text-decoration: none;
}
.tabbar-icon { display: inline-flex; }
.tabbar-icon svg { width: 20px; height: 20px; }
.tabbar a[aria-current="page"] { color: var(--color-primary); font-weight: var(--weight-semibold); }

/* --- Under 768px: the agency top nav gives way to the tab bar ------------- */

@media (max-width: 767px) {
    .navbar { display: none; }
    .menu-trigger-label { display: none; }
    .brand-name { display: none; }

    .app-sidebar .sidebar {
        display: block;
        position: fixed;
        inset-block: 0;
        left: 0;
        z-index: 50;
        width: min(var(--sidebar-width), 84vw);
        padding: var(--space-4);
        background: var(--color-surface);
        border-right: 1px solid var(--color-line);
        transform: translateX(-100%);
        transition: transform var(--transition-base);
        overflow-y: auto;
    }
    .app-sidebar.nav-open .sidebar { transform: translateX(0); }
    .app-sidebar.nav-open .sidebar-scrim {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 45;
        background: rgba(26, 29, 33, 0.45);
    }
    .portal-uits .sidebar { background: var(--color-portal-uits); }
    .portal-uits .sidebar a { color: rgba(255, 255, 255, 0.72); }
    .portal-uits .sidebar-heading { color: rgba(255, 255, 255, 0.45); }
}

/* --- 768px and up: sidebar portals show their sidebar --------------------- */

@media (min-width: 768px) {
    .tabbar { display: none; }
    .nav-toggle { display: none; }

    .main { padding: var(--space-6); padding-bottom: var(--space-6); }

    .app-sidebar .sidebar {
        display: block;
        flex: 0 0 var(--sidebar-width);
        width: var(--sidebar-width);
        padding: var(--space-4);
        background: var(--color-surface);
        border-right: 1px solid var(--color-line);
    }
    /* Two clearly different levels (the Stitch star-dashboard treatment):
       group rows carry an icon and a semibold 14px label; the items inside are
       13px regular, indented under the icon column, and muted until hover.
       Before this, headings and items were near-identical grey rows — the
       readability complaint of 21 Aug. */
    .sidebar a {
        display: flex;
        align-items: center;
        gap: var(--space-2);
        padding: var(--space-2) var(--space-3);
        padding-left: calc(var(--space-3) + 18px + var(--space-3));
        border-left: 3px solid transparent;
        border-radius: 0 var(--radius-md) var(--radius-md) 0;
        color: var(--color-ink-muted);
        font-size: var(--body-sm);
        text-decoration: none;
    }
    .sidebar a:hover { background: var(--color-surface-sunken); color: var(--color-ink); }
    .sidebar a[aria-current="page"] {
        background: var(--color-primary-light);
        border-left-color: var(--color-primary);
        color: var(--color-primary-dark);
        font-weight: var(--weight-semibold);
    }
    .sidebar-heading {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: var(--space-2);
        padding: var(--space-3);
        margin-top: var(--space-1);
        border-radius: var(--radius-md);
        font-size: var(--label-md);
        font-weight: var(--weight-semibold);
        letter-spacing: var(--tracking-label);
        color: var(--color-ink);
        cursor: pointer;
        list-style: none;                 /* hide the default disclosure marker */
        user-select: none;
    }
    .sidebar-heading-label {
        display: inline-flex;
        align-items: center;
        gap: var(--space-3);
        min-width: 0;
    }
    .sidebar-heading-label svg {
        width: 18px;
        height: 18px;
        flex: 0 0 auto;
        color: var(--color-ink-muted);
    }
    .sidebar-group[open] > .sidebar-heading .sidebar-heading-label svg { color: var(--color-primary-action); }
    .sidebar-heading::-webkit-details-marker { display: none; }
    .sidebar-heading:hover { background: var(--color-surface-sunken); }

    /* The chevron points right while collapsed, down when open. */
    .sidebar-group > .sidebar-heading .chevron {
        transition: transform var(--transition-fast), color var(--transition-fast);
        transform: rotate(-90deg);
        opacity: 0.5;
    }
    .sidebar-group[open] > .sidebar-heading .chevron { transform: rotate(0deg); }

    /* A collapsed group holding the current page turns its chevron red, so a
       user can see where they are without opening everything.

       This was a 6px dot in the free space to the right of the heading (client
       direction, 26 August 2026: the coloured arrow reads better). Colour is
       not the only signal either way — the group is still the one that opens to
       reveal an item carrying aria-current, which is what a screen reader
       follows. */
    .sidebar-group:not([open]) > .sidebar-heading.has-current .chevron {
        color: var(--color-primary);
        opacity: 1;
    }

    .portal-uits .sidebar-heading { color: rgba(255, 255, 255, 0.92); }
    .portal-uits .sidebar-heading:hover { background: rgba(255, 255, 255, 0.08); }
    .portal-uits .sidebar-heading-label svg { color: rgba(255, 255, 255, 0.55); }
    .portal-uits .sidebar-group[open] > .sidebar-heading .sidebar-heading-label svg { color: var(--color-primary-light); }

    /* UITS wears the dark slate sidebar. */
    .portal-uits .sidebar { background: var(--color-portal-uits); border-right-color: transparent; }
    .portal-uits .sidebar a { color: rgba(255, 255, 255, 0.72); }
    .portal-uits .sidebar a:hover { background: rgba(255, 255, 255, 0.08); color: #fff; }
    .portal-uits .sidebar a[aria-current="page"] {
        background: rgba(255, 255, 255, 0.12);
        border-left-color: var(--color-primary);
        color: #fff;
    }
    .portal-uits .sidebar-heading { color: rgba(255, 255, 255, 0.45); }
}

/* --- 768px to 1080px: the sidebar narrows to icon width ------------------- */

@media (min-width: 768px) and (max-width: 1079px) {
    .app-sidebar .sidebar { flex-basis: 72px; width: 72px; padding: var(--space-2); }
    .app-sidebar .sidebar a { justify-content: center; padding: var(--space-3) 0; }
    .app-sidebar .sidebar-heading { display: none; }
}

/* --- < 768px: tables become cards ----------------------------------------- */

@media (max-width: 767px) {
    .table-collapse thead { display: none; }
    .table-collapse tbody tr {
        display: block;
        margin-bottom: var(--space-3);
        padding: var(--space-3);
        background: var(--color-surface);
        border: 1px solid var(--color-line);
        border-radius: var(--radius-md);
    }
    .table-collapse tbody td {
        display: flex;
        justify-content: space-between;
        gap: var(--space-3);
        padding: var(--space-1) 0;
        border: 0;
    }
    .table-collapse tbody td::before {
        content: attr(data-label);
        font-weight: var(--weight-medium);
        color: var(--color-ink-muted);
    }
    .table-collapse tbody tr.row-status { border-left: 4px solid var(--row-status-colour); }
    .table-collapse tbody tr.row-status > td:first-child { box-shadow: none; }
}

/* --- Cards when the COLUMN is narrow, whatever the window is -------------- */

/* The same card treatment as above, asked of the table's own container rather
   than the viewport. A table in the 1fr rail of a split is narrow on a 27-inch
   monitor, and was getting the full desktop layout because the WINDOW was
   wide. The block above stays for browsers without container queries — they
   keep exactly the behaviour they have today.

   480px, and it matters that it is not higher. The dashboards put their table
   in the 2fr side of a 2fr/1fr split, which on a 1366px laptop measures 575px:
   at a 640px threshold — the first number tried — those tables skipped the
   fold-a-column-away step entirely and went straight to phone cards, which in
   a narrow panel reads exactly like the cut-off table it was meant to fix.
   Cards are for a container too narrow for ANY table. A laptop column is not
   that; it is a table with fewer columns, which is what the block below does.
   A phone gives these tables about 326px, so they still become cards there. */
@container (max-width: 480px) {
    .table-collapse thead { display: none; }
    .table-collapse tbody tr {
        display: block;
        margin-bottom: var(--space-3);
        padding: var(--space-3);
        background: var(--color-surface);
        border: 1px solid var(--color-line);
        border-radius: var(--radius-md);
    }
    .table-collapse tbody td {
        display: flex;
        justify-content: space-between;
        gap: var(--space-3);
        padding: var(--space-1) 0;
        border: 0;
    }
    .table-collapse tbody td::before {
        content: attr(data-label);
        font-weight: var(--weight-medium);
        color: var(--color-ink-muted);
    }
    .table-collapse tbody tr.row-status { border-left: 4px solid var(--row-status-colour); }
    .table-collapse tbody tr.row-status > td:first-child { box-shadow: none; }

    .cell-expander { width: auto; }
    .table-collapse tbody tr.row-detail {
        margin-top: calc(var(--space-3) * -1);
        padding-top: 0;
        border-top: 0;
        border-top-left-radius: 0;
        border-top-right-radius: 0;
    }
    .table-collapse tbody tr.row-detail > td { display: block; padding: 0; }
    .table-collapse tbody tr.row-detail > td::before { content: none; }

    /* A card has room to run downwards, so nothing needs to give way here —
       every column the narrow-table rules below hide comes back as a row.
       Specificity, not source order: the tier rules further down claim the
       same cells at the same weight, and they are written later. */
    .table-collapse.table-priority tbody td.col-secondary,
    .table-collapse.table-priority tbody td.col-status,
    .table-collapse.table-priority-wide tbody td.col-secondary,
    .table-collapse.table-priority-wide tbody td.col-status { display: flex; }

    /* The Status row is present in the card to say it out loud, so the hidden
       copy would be read twice. */
    .table-collapse.table-priority .row-status-label,
    .table-collapse.table-priority-wide .row-status-label { display: none; }
}

/* --- Columns that give way when the table has no room --------------------- */

/* A dense list on a laptop had columns past the right edge and nothing but a
   scrollbar at the foot of the panel to say so. Rather than scroll first, the
   columns whose value is repeated inside the row's own expander stand down,
   and the STATUS column stands down in favour of the coloured rule the row
   already wears — which is how the legacy list showed it too
   (ins_policylist.php:1408, a 5px bar per row).

   It happens in TWO steps, because Status is the cheap one: dropping it costs
   nothing that the rule does not say, where folding a column away costs a
   click on the expander. So Status goes first and alone, and the rest only
   when the table is still short of room without it.

   The thresholds are MEASURED, not guessed — each table was rendered in Chrome
   and asked how wide it wants to be. Guessing is how the first attempt put the
   card breakpoint above the dashboard's own column and turned a laptop table
   into phone cards.

                            all columns   without Status   folded
     dashboards                   920px            805px    450–685px
     /a/policies, /s/policies    1289px           1184px    709px

   Hence two tiers. One threshold cannot serve both: at 1120px for everything,
   the dashboards — whose column never exceeds 873px, even on a 1920px monitor
   — folded Destination and Dates away at EVERY width, while /s/policies showed
   all nine columns in a 1150px column that needed 1289 and overflowed. */

/* --- Compact tables: the dashboards' 2fr column ---------------------------- */

@container (max-width: 940px) {
    .table-priority th.col-status,
    .table-priority td.col-status { display: none; }
}

@container (max-width: 820px) {
    .table-priority th.col-secondary,
    .table-priority td.col-secondary { display: none; }
}

/* --- Wide tables: the full-width policy lists ------------------------------ */

@container (max-width: 1310px) {
    .table-priority-wide th.col-status,
    .table-priority-wide td.col-status { display: none; }
}

@container (max-width: 1200px) {
    .table-priority-wide th.col-secondary,
    .table-priority-wide td.col-secondary { display: none; }
}

/* Colour is never the only signal. Whenever the Status column has gone, this
   is what a screen reader announces in its place and what Ctrl+F still finds;
   it is display:none the rest of the time, where the chip in the Status column
   says the same thing out loud. */
@container (max-width: 940px) {
    .table-priority .row-status-label { display: inline; }
}

@container (max-width: 1310px) {
    .table-priority-wide .row-status-label { display: inline; }
}

.table-priority .row-status-label,
.table-priority-wide .row-status-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* --- The status rule on a row --------------------------------------------- */

/* One colour vocabulary with the chip (src/Core/Status.php): active green,
   pending amber, expired grey, refunded red, suspended/part mulberry. */
.row-status { --row-status-colour: var(--color-neutral); }
.row-status-active { --row-status-colour: var(--color-success); }
.row-status-pending { --row-status-colour: var(--color-warning); }
.row-status-expired { --row-status-colour: var(--color-neutral); }
.row-status-refunded { --row-status-colour: var(--color-danger); }
.row-status-part { --row-status-colour: var(--color-accent); }

/* Drawn INSIDE the leading cell rather than as a border, so it does not shift
   the column by 4px and does not fight the sticky-column background. */
.row-status > td:first-child { box-shadow: inset 4px 0 0 var(--row-status-colour); }

/* Hidden until the Status column stands down. */
.row-status-label { display: none; }

/* --- >= 1080px ------------------------------------------------------------ */

@media (min-width: 1080px) {
    .main { padding-inline: var(--space-7); }
}

/* --- Buy flow (checkout + issued) ----------------------------------------
   Adapted from design/stitch/buy-policy-checkout/ and .../policy-issued/.
   The checkout is a two-column layout on desktop with the summary sticky
   beside the form, and a single column on phones with the summary last. */

.buy-layout { display: grid; gap: var(--space-5); align-items: start; }

@media (min-width: 1024px) {
    .buy-layout { grid-template-columns: minmax(0, 1fr) 320px; }
    .buy-summary { position: sticky; top: var(--space-5); }
}

.buy-main { min-width: 0; }
.buy-summary { display: grid; gap: var(--space-3); }

/* One disclosure panel per traveller, so a party of six is still readable. */
.traveller-panel > .traveller-summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    cursor: pointer;
    list-style: none;
    user-select: none;
}
.traveller-panel > .traveller-summary::-webkit-details-marker { display: none; }
.traveller-summary-label { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.traveller-summary-label > span:last-child { display: grid; gap: 2px; }

.traveller-index {
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    flex: 0 0 auto;
    border-radius: var(--radius-pill);
    background: var(--color-primary-light);
    color: var(--color-primary-dark);
    font-size: var(--label-sm);
    font-weight: var(--weight-semibold);
}

.traveller-panel > .traveller-summary .chevron {
    transition: transform var(--transition-fast);
    transform: rotate(-90deg);
}
.traveller-panel[open] > .traveller-summary .chevron { transform: rotate(0deg); }

.required-mark { color: var(--color-primary-action); }
.field-narrow { max-width: 180px; }

.summary-row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
.summary-row > span:last-child { text-align: right; }

.summary-total {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    margin-top: var(--space-2);
    padding-top: var(--space-3);
    border-top: 1px solid var(--color-border);
    font-weight: var(--weight-semibold);
}
.summary-amount { font-size: var(--headline-sm); color: var(--color-primary-dark); }

/* The confirmation. */
.issued-hero { display: grid; justify-items: center; gap: var(--space-3); text-align: center; padding: var(--space-6) 0; }

.issued-mark {
    display: grid;
    place-items: center;
    width: 56px;
    height: 56px;
    border-radius: var(--radius-pill);
    background: var(--color-success-light, #E6F4EA);
    color: var(--color-success, #2C7A45);
}
.issued-mark svg { width: 28px; height: 28px; }

.issued-number {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-md);
    background: var(--color-surface-sunken);
}
.issued-number-value { font-family: var(--font-mono); font-weight: var(--weight-semibold); letter-spacing: 0.5px; }

.issued-actions { display: grid; gap: var(--space-3); margin-top: var(--space-5); }
.issued-actions > * { margin: 0; }

@media (min-width: 640px) {
    .issued-actions { grid-template-columns: repeat(3, 1fr); }
}

/* A field whose value is settled — a returning traveller's identity. It reads
   as fixed rather than broken: still legible, plainly not editable. */
.field-locked .input,
.field-locked .select,
.input[readonly] {
    background: var(--color-surface-sunken);
    color: var(--color-ink-muted);
    cursor: default;
}

.select.is-locked { pointer-events: none; }

.field-hint-found { color: var(--color-success, #2C7A45); font-weight: var(--weight-medium); }

/* Used when a hint is telling the agent something is incomplete rather than
   confirming something worked — a traveller on the form who is not yet in the
   premium, for one. Amber, and the wording says it too. */
.field-hint-warn { color: var(--color-warning); font-weight: var(--weight-medium); }

/* --- Searchable select (combobox) ----------------------------------------- */

/* Progressive enhancement over a native <select> — see searchableSelects() in
   app.js. The <select> stays in the DOM and stays focusable, so a `required`
   one can still report its own constraint violation; it is only moved out of
   sight. `display:none` would make the browser refuse to report it at all. */

.combo { position: relative; }

.combo-native {
    position: absolute;
    left: 0;
    bottom: 0;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

.combo-control {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-1);
    width: 100%;
    min-height: 44px;
    padding: var(--space-1) var(--space-3);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-md);
    background: var(--color-surface);
    cursor: text;
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.combo.is-open .combo-control,
.combo-control:focus-within {
    border-color: var(--color-primary);
    box-shadow: 0 0 0 3px var(--color-primary-light);
}

.combo-input {
    flex: 1 1 8ch;
    min-width: 8ch;
    border: 0;
    background: transparent;
    padding: var(--space-1) 0;
    outline: none;
}

.combo-caret {
    flex: 0 0 auto;
    width: 0;
    height: 0;
    margin-left: var(--space-2);
    border-left: 5px solid transparent;
    border-right: 5px solid transparent;
    border-top: 6px solid var(--color-ink-subtle);
}

.combo-chips { display: contents; }

.combo-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 2px var(--space-1) 2px var(--space-2);
    border-radius: var(--radius-sm);
    background: var(--color-primary-light);
    color: var(--color-primary-deep);
    font-size: var(--text-sm);
    max-width: 100%;
}

.combo-chip > span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.combo-chip-remove {
    border: 0;
    background: transparent;
    color: inherit;
    cursor: pointer;
    line-height: 1;
    padding: 0 2px;
    font-size: var(--text-base);
}
.combo-chip-remove:hover { color: var(--color-primary-action); }

.combo-list {
    position: absolute;
    z-index: 40;
    inset-inline: 0;
    top: calc(100% + 2px);
    max-height: 260px;
    overflow-y: auto;
    padding: var(--space-1);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-md);
    background: var(--color-surface);
    /* Level 2 is too faint for a panel that floats over form fields — the
       list needs to read as being above the page, not printed on it. */
    box-shadow: 0 8px 24px rgba(31, 26, 26, 0.16);
}

.combo-option {
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    cursor: pointer;
}
.combo-option.is-active { background: var(--color-surface-sunken); }
.combo-option.is-selected { color: var(--color-primary-action); font-weight: var(--weight-semibold); }
.combo-option.is-selected::after { content: " ✓"; }

.combo-empty {
    padding: var(--space-2) var(--space-3);
    color: var(--color-ink-subtle);
    font-size: var(--text-sm);
}

/* Matches .input-error on the plain fields. */
.field-error ~ .combo .combo-control,
.combo-error .combo-control { border-color: var(--color-danger, #B3161D); }

/* --- Expandable table rows ------------------------------------------------ */

/* The list expands in place rather than sending the reader to another screen
   and back. The toggle is rendered `hidden` and revealed by app.js, so with no
   script there is no dead control — the "View" link is the route instead. */

.cell-expander { width: 40px; padding-right: 0; }

/* The identifier and the control that opens it, in one cell. The expander used
   to hold a column of its own, which cost 40px of a table that had none to
   spare and put the control a column away from the row it belonged to. The
   negative margin pulls the button's own padding back so the numbers below the
   header still line up under it. */
.cell-lead { display: flex; align-items: center; gap: var(--space-1); }
.cell-lead > .row-expander { flex: 0 0 auto; margin-left: calc(var(--space-3) * -1); }

.row-expander {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-ink-muted);
    cursor: pointer;
}
/* Author styles beat the UA sheet whatever the specificity, so `display:
   inline-flex` above would show the button even while it carries `hidden` —
   which is how it is rendered, to be revealed by app.js. Without this the
   no-JavaScript fallback the markup describes does not happen. */
.row-expander[hidden] { display: none; }
.row-expander:hover { background: var(--color-surface-sunken); color: var(--color-ink); }
.row-expander svg { width: 16px; height: 16px; transition: transform var(--transition-fast); }
.row-expander[aria-expanded="true"] svg { transform: rotate(180deg); }

.row-detail > td { background: var(--color-surface-sunken); padding: 0; }

.row-detail-body {
    display: grid;
    gap: var(--space-4);
    padding: var(--space-4) var(--space-5);
    border-left: 3px solid var(--color-primary);
}

.row-detail-party { display: grid; gap: var(--space-2); }

/* The commission derivation inside an expanded agency row. It is a table
   nested in a table cell, so it gets tighter rows than the one around it —
   otherwise the workings look like a second report rather than a footnote to
   the line above them. */
.table-tight th,
.table-tight td { padding-top: var(--space-1); padding-bottom: var(--space-1); }
.workings { background: transparent; font-size: var(--body-sm); }
.workings thead th { border-bottom: 1px solid var(--color-border); }
.workings tbody tr:last-child > td { border-bottom: 0; }
.workings-total > td {
    border-top: 1px solid var(--color-border);
    padding-top: var(--space-2);
}

/* Below 768px the table is already stacked into cards, where a second row
   holding a panel has nothing to span. The panel becomes a block in the flow
   and the expander sits with the rest of the card. */
@media (max-width: 767px) {
    .cell-expander { width: auto; }

    /* The table is stacked into cards here, so the detail row would otherwise
       float as a second card detached from the policy it belongs to. Pulled up
       and squared off at the top, the pair reads as one card that grew. */
    .table-collapse tbody tr.row-detail {
        margin-top: calc(var(--space-3) * -1);
        padding-top: 0;
        border-top: 0;
        border-top-left-radius: 0;
        border-top-right-radius: 0;
    }

    /* The collapsed table turns every cell into a label/value flex row; the
       detail panel is neither, so it opts out. */
    .table-collapse tbody tr.row-detail > td { display: block; padding: 0; }
    .table-collapse tbody tr.row-detail > td::before { content: none; }

    .row-detail-body {
        padding: var(--space-3) var(--space-3) var(--space-3);
        border-left: 0;
        border-top: 1px solid var(--color-line);
    }
}

/* --- Adding and removing travellers --------------------------------------- */

.add-traveller {
    padding: var(--space-3) var(--space-4);
    border: 1px dashed var(--color-line);
    border-radius: var(--radius-md);
    background: var(--color-surface);
}

/* Sits in the panel summary beside the chevron. */
.traveller-summary-actions {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex: 0 0 auto;
}

/* Removing a traveller is destructive but ordinary — a text action in the
   refund red, not a filled button competing with "Issue policy". */
.btn-link-danger {
    border: 0;
    background: transparent;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-sm);
    color: var(--color-danger);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    cursor: pointer;
}
.btn-link-danger:hover { background: var(--color-danger-bg); }

/* --- Plan benefits -------------------------------------------------------- */

/* Every plan covers the same named benefits; the AMOUNT is what tells them
   apart, so the name and the figure stack rather than the figure being dropped
   for space. */
.plan-benefit-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.plan-benefit-name { line-height: 1.35; }
.plan-benefit-amount {
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--color-primary-deep);
}

/* An exclusion is stated, not hidden — with a cross and muted tone as well as
   the words, so colour is never the only signal. */
.plan-benefit-excluded { color: var(--color-ink-subtle); }
.plan-benefit-excluded svg { color: var(--color-ink-subtle); }
.plan-benefit-excluded .plan-benefit-amount { color: var(--color-ink-subtle); font-weight: var(--weight-medium); }

.plan-benefits-more { margin-top: var(--space-2); }

.plan-benefits-toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    padding: var(--space-2) 0;
    cursor: pointer;
    color: var(--color-primary-action);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    list-style: none;
}
.plan-benefits-toggle::-webkit-details-marker { display: none; }
.plan-benefits-toggle:hover { text-decoration: underline; }
.plan-benefits-toggle .chevron { transition: transform var(--transition-fast); }
.plan-benefits-more[open] .plan-benefits-toggle .chevron { transform: rotate(180deg); }

/* The full list is long — 31 benefits on most plans — so it scrolls inside the
   card instead of making one card four screens taller than its neighbours. */
.plan-benefits-more[open] > .plan-benefits {
    max-height: 320px;
    overflow-y: auto;
    padding-top: var(--space-2);
    border-top: 1px solid var(--color-line);
}

.plan-benefits-more[open] > .plan-benefits::after {
    content: "";
    display: block;
}

/* --- Combobox: escaping the card ------------------------------------------ */

/* A panel hides its overflow so its corners stay rounded, which also clips any
   dropdown reaching past the card's lower border — the list vanished at the
   edge. app.js releases the clip only while a list is open, so the corners are
   intact the rest of the time. `position: relative` would re-clip via a new
   stacking context, so it is deliberately not set here. */
.panel.has-combo-open,
.card.has-combo-open,
.table-wrap.has-combo-open { overflow: visible; }

/* Near the bottom of the window the list opens upwards instead. */
.combo.combo-up .combo-list { top: auto; bottom: calc(100% + 2px); }

/* A single-purpose form does not need the full content width — a 6-line
   textarea stretched across 1400px is harder to read, not easier. */
.page-narrow { max-width: 640px; }

/* Forms that carry PANELS rather than one field need more room: at 640px the
   extend screen's detail grids wrapped every label onto its own line and the
   currency choices stacked when there was space for both. Still capped, so the
   text does not run the full width of a 1440px display. */
.page-form { max-width: 760px; }

@media (min-width: 1080px) {
    .page-form { max-width: 1000px; }
}

/* And wider still on a large monitor. The agency record and the agency-user
   form are panels of PAIRED fields — name beside code, start beside end — and
   at 1000px those pairs were still stacking on a 1440px display while a third
   of the window sat empty (client direction, 26 August 2026). */
@media (min-width: 1400px) {
    .page-form { max-width: 1180px; }
}

/* --- Traveller policy history --------------------------------------------- */

/* Each entry is three lines — number and status, what the cover was, when and
   through whom — so a rule between them is what keeps them from running
   together into one block of text. */
.history-row { display: grid; gap: 2px; padding-bottom: var(--space-3); }
.history-row + .history-row {
    border-top: 1px solid var(--color-line);
    padding-top: var(--space-3);
}
.history-row:last-child { padding-bottom: 0; }

/* --- State tabs ----------------------------------------------------------- */

/* Star works THROUGH policy states rather than picking one, so they are tabs
   with their counts on them: the size of each job is visible before it is
   opened. Scrolls horizontally on a phone rather than wrapping into a block
   that pushes the table off the screen. */
.tabs {
    display: flex;
    gap: var(--space-1);
    margin-bottom: var(--space-4);
    padding-bottom: var(--space-1);
    overflow-x: auto;
    border-bottom: 1px solid var(--color-line);
    scrollbar-width: thin;
}

.tab {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    flex: 0 0 auto;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    color: var(--color-ink-muted);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    white-space: nowrap;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}
.tab:hover { color: var(--color-ink); background: var(--color-surface-sunken); text-decoration: none; }

/* The current tab is marked by the rule under it AND by weight and colour —
   never by colour alone. */
.tab-current {
    color: var(--color-primary-action);
    border-bottom-color: var(--color-primary-action);
    font-weight: var(--weight-semibold);
}

.tab-count {
    display: inline-block;
    padding: 1px var(--space-2);
    border-radius: 999px;
    background: var(--color-surface-sunken);
    color: var(--color-ink-muted);
    font-size: var(--text-xs);
    font-variant-numeric: tabular-nums;
}
.tab-current .tab-count { background: var(--color-primary-light); color: var(--color-primary-deep); }

/* --- Modal dialog --------------------------------------------------------- */

/* A native <dialog>: the browser gives focus trapping, Escape-to-close, the
   backdrop and inert-behind for free. Only the appearance is ours. */
.dialog {
    width: min(92vw, 680px);
    max-height: 85vh;
    padding: 0;
    border: 1px solid var(--color-line);
    border-radius: var(--radius-md);
    background: var(--color-surface);
    color: var(--color-ink);
    box-shadow: 0 16px 48px rgba(31, 26, 26, 0.22);
    overflow: hidden;
}

.dialog::backdrop { background: rgba(31, 26, 26, 0.45); }

/* The header stays put while a long benefit list scrolls under it — 31 rows is
   past a screen, and losing the close button off the top is how people end up
   pressing Escape and hoping. */
.dialog > .panel-header {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--color-surface);
}

.dialog > .panel-body { max-height: calc(85vh - 72px); overflow-y: auto; }

/* A reversed payment stays on the ledger, struck through — the record of a
   correction is the point, so it is dimmed rather than removed. */
.history-row.is-reversed { opacity: 0.65; }
.history-row.is-reversed .font-medium { text-decoration: line-through; }

/* A JSON body shown for inspection. It scrolls inside its own box rather than
   widening the page — an underwriting request is 60-odd fields on one line
   apiece, and several of them are long. */
.code-block {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: 1.55;
    white-space: pre;
    overflow-x: auto;
    max-height: 32rem;
    overflow-y: auto;
    padding: var(--space-4);
    margin: 0;
    background: var(--color-surface-sunken);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-md);
    color: var(--color-ink);
}

/* A small count on the plan catalogue. Zero is coloured, because on this
   screen zero is the whole point: a plan with no benefits renders an empty
   comparison card, and that is invisible on legacy's list. */
.count-pill {
    display: inline-flex;
    align-items: center;
    padding: 2px var(--space-2);
    border-radius: var(--radius-sm);
    background: var(--color-surface-sunken);
    color: var(--color-ink-muted);
    font-size: var(--text-xs);
    white-space: nowrap;
}

.count-pill.count-zero {
    background: var(--color-danger-bg);
    color: var(--color-danger);
    font-weight: var(--weight-medium);
}

/* A grid cell whose value is missing or inconsistent. On the premium table
   this is not decoration: an empty multiplier cell means that age group is
   charged the base price with no uplift, silently. */
.cell-alert {
    background: var(--color-danger-bg);
    color: var(--color-danger);
    font-weight: var(--weight-medium);
}

/* A checkbox and its label on one line, with the hit area covering both —
   a 13px checkbox on its own is a hard target on a phone. */
.check-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) 0;
    cursor: pointer;
    font-size: var(--body-sm);
}

.check-row input[type="checkbox"] {
    width: 18px;
    height: 18px;
    flex: 0 0 auto;
    accent-color: var(--color-primary);
}

/* A field sized to sit inside a table cell. The setup grids edit in place —
   an age band or a time period is three short values, and pushing that
   through a separate edit page for each row would be six clicks to change
   one number. */
.input-sm {
    padding: var(--space-2) var(--space-3);
    font-size: var(--body-sm);
    min-width: 6rem;
}

/* --- Invoice document ------------------------------------------------------

   /a/invoice/{id} and /s/invoice/{id} are DOCUMENTS, not screens about a
   document: an agency reconciles a line at a time against its own book, so the
   layout follows legacy's `render_agent_invoice_html()` — a title, a meta
   block, a ruled table of policies, totals, and where to send the money.

   Ruled rather than flush, unlike every other table in the application. That
   is deliberate: eleven money columns need vertical rules to be read across,
   and this is the one place a person traces a row with a finger. */

.invoice-doc { padding: var(--space-5); }

.invoice-doc-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--space-3);
    padding-bottom: var(--space-4);
    border-bottom: 2px solid var(--color-ink);
    margin-bottom: var(--space-5);
}
.invoice-doc-title {
    margin: 0;
    font-size: var(--headline-md);
    font-weight: var(--weight-bold);
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.invoice-doc-meta {
    display: grid;
    gap: var(--space-3) var(--space-6);
    margin-bottom: var(--space-5);
}
.invoice-doc-meta dt {
    font-size: var(--label-sm);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-ink-muted);
}
.invoice-doc-meta dd { margin: 0; font-weight: var(--weight-semibold); }

@media (min-width: 640px) {
    .invoice-doc-meta { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 960px) {
    .invoice-doc-meta { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* Ruled table, as the printed invoice is ruled. */
.table-bordered th,
.table-bordered td { border: 1px solid var(--color-line); }
.table-bordered thead th {
    background: var(--color-surface-sunken);
    text-transform: uppercase;
    font-size: var(--label-sm);
    letter-spacing: 0.03em;
}
.table-bordered tbody tr:hover { background: transparent; }

.table-compact th,
.table-compact td { padding: var(--space-2) var(--space-3); }

.table-bordered tfoot th,
.table-bordered tfoot td {
    font-weight: var(--weight-semibold);
    background: var(--color-surface);
}
.table-bordered tfoot .numeric { font-variant-numeric: tabular-nums; text-align: right; }

.invoice-doc-net th,
.invoice-doc-net td {
    background: var(--color-surface-sunken);
    font-size: var(--body-md);
    font-weight: var(--weight-bold);
}

.invoice-doc-note { margin-top: var(--space-4); }

.invoice-doc-remit {
    margin-top: var(--space-6);
    padding: var(--space-5);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-md);
    background: var(--color-surface-sunken);
}
.invoice-doc-remit h2 { margin: 0 0 var(--space-2); }
.invoice-doc-remit-grid {
    display: grid;
    gap: var(--space-5);
    margin-top: var(--space-4);
}
@media (min-width: 720px) {
    .invoice-doc-remit-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* --- Print -----------------------------------------------------------------

   The invoice is printed and filed. Legacy opens a popup and writes the
   invoice div into it; a print stylesheet does the same job without a popup
   blocker in the way, and without JavaScript. */

@media print {
    /* .no-print marks chrome around a document rather than part of it. It has
       no screen rule on purpose — it only means anything here. */
    .no-print,
    .portal-topbar,
    .portal-sidebar,
    .portal-header,
    .skip-link,
    .link-arrow,
    .btn,
    .flash,
    .alert-info { display: none !important; }

    body { background: #fff; }

    .page { max-width: none; padding: 0; }

    .panel,
    .invoice-doc {
        border: 0;
        box-shadow: none;
        padding: 0;
        break-inside: auto;
    }

    .invoice-doc + .invoice-doc { margin-top: var(--space-6); }

    /* Paper does not scroll, so nothing here is standing in for a scrollbar:
       no container to query, no shadow at the edges, no pinned column, and
       every column the screen folds away is printed. */
    .table-wrap {
        overflow: visible;
        container-type: normal;
        background: none;
    }

    .table-sticky-first tbody td:first-child,
    .table-sticky-first thead th:first-child { position: static; background: transparent; }
    .table-sticky-first tbody td:first-child::after,
    .table-sticky-first thead th:first-child::after { content: none; }

    .table-priority th.col-secondary,
    .table-priority td.col-secondary,
    .table-priority th.col-status,
    .table-priority td.col-status { display: table-cell; }

    .row-status > td:first-child { box-shadow: none; }
    .row-status-label { display: none; }

    .table-bordered th,
    .table-bordered td { border: 1px solid #999; }

    /* A policy row must not be split across a page break. */
    .table-bordered tbody tr { break-inside: avoid; }

    thead { display: table-header-group; }
}

/* --- Notice creative -------------------------------------------------------

   The image on an advert (FR-NOTICE.1). Its dimensions are not known ahead of
   time — it is a file staff uploaded — so the box constrains it here rather
   than through width/height attributes that would be a guess. Everything else
   in the product sizes its images, and this is the exception that says why. */

.notice-creative {
    display: block;
    width: 100%;
    max-height: 320px;
    object-fit: cover;
    border-top-left-radius: var(--radius-md);
    border-top-right-radius: var(--radius-md);
    background: var(--color-surface-sunken);
}

/* --- Top-bar policy search -------------------------------------------------

   On every page of the agency portal (client direction, 25 August 2026), so it
   lives in the chrome. The field is an ordinary GET form; the pop-up below it is
   the type-ahead app.js layers on, and everything here degrades to a plain
   search box when that does not run. */

.topbar-search {
    position: relative;
    /* Shrinks with the bar rather than disappearing off it. The first version
       hid the field below 900px, which meant the one search that is supposed to
       be on every page was on none of the small ones. */
    flex: 1 1 200px;
    min-width: 0;
    max-width: 360px;
    margin-right: var(--space-3);
}

.topbar-search-icon {
    position: absolute;
    left: var(--space-3);
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    color: var(--color-ink-subtle);
    pointer-events: none;
}
.topbar-search-icon svg { width: 16px; height: 16px; }

.topbar-search-input {
    padding-left: calc(var(--space-3) * 2 + 16px);
    height: 38px;
}

/* The Star bar is oxblood; a white field on it would glare. */
.portal-star .topbar-search-input {
    background: rgba(255, 255, 255, 0.12);
    border-color: rgba(255, 255, 255, 0.24);
    color: #fff;
}
.portal-star .topbar-search-input::placeholder { color: rgba(255, 255, 255, 0.7); }
.portal-star .topbar-search-icon { color: rgba(255, 255, 255, 0.7); }

@media (max-width: 767px) {
    /* Below 768px the bar has already dropped the brand name, the portal label
       and the user's name, so what is left — logo mark, search, bell, avatar —
       leaves the field the room it needs. */
    .topbar-search {
        flex: 1 1 auto;
        max-width: none;
        margin-right: var(--space-2);
    }

    /* The pop-up hangs off the field, and on a phone the field is narrow. Two
       lines of a traveller's name in a 150px box is not a result anybody can
       read, so the panel breaks out to the viewport instead. */
    .topbar-search-results {
        position: fixed;
        top: calc(var(--topbar-height) - var(--space-1));
        left: var(--space-3);
        right: var(--space-3);
        max-height: 60vh;
    }
}

.topbar-search-results {
    position: absolute;
    top: calc(100% + var(--space-2));
    left: 0;
    right: 0;
    z-index: 40;
    max-height: 380px;
    overflow-y: auto;
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    padding: var(--space-2);
}

.search-result {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 0 var(--space-3);
    padding: var(--space-3);
    border-radius: var(--radius-sm);
    color: var(--color-ink);
    text-decoration: none;
}
.search-result:hover,
.search-result.is-active { background: var(--color-surface-sunken); }

.search-result-name {
    font-weight: var(--weight-semibold);
    font-size: var(--body-sm);
}
.search-result-number {
    font-family: var(--font-mono);
    font-size: var(--label-sm);
    color: var(--color-ink-muted);
    text-align: right;
}
.search-result-meta {
    grid-column: 1 / -1;
    font-size: var(--label-sm);
    color: var(--color-ink-subtle);
}

.search-result-empty {
    margin: 0;
    padding: var(--space-4);
    font-size: var(--body-sm);
    color: var(--color-ink-muted);
    text-align: center;
}

.search-result-all {
    display: block;
    margin-top: var(--space-2);
    padding: var(--space-3);
    border-top: 1px solid var(--color-line);
    font-size: var(--body-sm);
    font-weight: var(--weight-semibold);
    color: var(--color-primary-action);
    text-align: center;
    text-decoration: none;
}
.search-result-all:hover { background: var(--color-surface-sunken); }

@media print {
    .topbar-search { display: none !important; }
}

/* --- The policy-letter editor -------------------------------------------- */

/* A contenteditable with legacy TinyMCE's first two toolbar rows over it, and
   an HTML source view one click away. See htmlEditor() in app.js for why it is
   not TinyMCE itself. */

.editor-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2);
    border: 1px solid var(--color-line);
    border-bottom: 0;
    border-radius: var(--radius-md) var(--radius-md) 0 0;
    background: var(--color-surface-sunken);
}

.editor-group { display: inline-flex; gap: 2px; }
.editor-group + .editor-group {
    padding-left: var(--space-2);
    border-left: 1px solid var(--color-line);
}

.editor-button {
    min-width: 30px;
    height: 30px;
    padding: 0 var(--space-2);
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-ink);
    font-size: var(--text-sm);
    line-height: 1;
    cursor: pointer;
}
.editor-button:hover { background: var(--color-surface); border-color: var(--color-line); }
.editor-button:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }

.editor-format { height: 30px; padding-block: 0; font-size: var(--text-sm); max-width: 10rem; }

.editor-paste {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin-left: auto;
    font-size: var(--text-xs);
    color: var(--color-ink-muted);
}

/* The surface is the letter, so it reads like the letter rather than like a
   form field: white, serif-neutral, and roomy enough to see a page. */
.editor-surface {
    min-height: 420px;
    max-height: 70vh;
    overflow-y: auto;
    padding: var(--space-5);
    border: 1px solid var(--color-line);
    border-radius: 0 0 var(--radius-md) var(--radius-md);
    background: var(--color-surface);
    color: var(--color-ink);
}
.editor-surface:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -2px; }
.editor-surface table { border-collapse: collapse; }
.editor-surface td, .editor-surface th { border: 1px solid var(--color-line-strong); padding: 4px; }
.editor-surface img { max-width: 100%; height: auto; }

/* Which of Rich text / HTML source is showing. */
.is-current-mode {
    background: var(--color-primary-light);
    color: var(--color-primary-dark);
    font-weight: var(--weight-semibold);
}

/* A fillable PDF is shown, never edited. */
.pdf-preview {
    display: block;
    width: 100%;
    height: 70vh;
    min-height: 420px;
    border: 1px solid var(--color-line);
    border-radius: var(--radius-md);
    background: var(--color-surface-sunken);
}

/* --- Grid children must be allowed to shrink -------------------------------

   A grid track sized `1fr` is `minmax(auto, 1fr)`, and `auto` bottoms out at
   min-content: the track will not go narrower than the longest unbreakable
   thing inside it. One long address, account number or URL therefore pushes a
   whole layout wider than its container and the page scrolls sideways, with the
   right-hand edge cut off — which is exactly what happened to the insurer cards
   and the agency panel on /a/profile.

   `min-width: 0` opts each child out of that floor, so the track can shrink and
   the content wraps instead. Applied to the containers whose children hold free
   text rather than blanket-applied, because a track that genuinely must not
   shrink — a fixed action column — should keep its floor. */

.split > *,
.kpi-grid > *,
.plan-grid > *,
.field-row > *,
.issued-actions > *,
.invoice-doc-remit-grid > *,
.invoice-doc-meta > * { min-width: 0; }

/* The policy number beside a suggestion: it is one unbreakable token, so it is
   the thing that would push the pop-up wider than the field it hangs under. */
.search-result-number {
    min-width: 0;
    overflow-wrap: anywhere;
}
