/* ==========================================================================
   Domolytics — the application stylesheet (request 130, the "Geode" design).

   Loaded after tokens_v2.css, which defines every colour for both themes, and
   before components_v2.css, which styles the generic component names and so
   wins on equal specificity.

   THIS FILE DECLARES NO COLOUR
   ----------------------------
   Every colour here is `var(--token)` naming a token from tokens_v2.css —
   never a literal, and never a local alias re-pointed at one. A colour change
   is therefore a one-line edit in the token sheet, and flipping `data-theme`
   on <html> repaints the app without touching a rule here.

   THERE IS NO v1
   --------------
   The `_v2` suffix is a name, not a variant (request 152). The v1 sheet
   (`main.css` + `fonts.css`) and the `UI_VERSION` fork were deleted in task
   0152b; its layout rules had already been folded into this file by task 0130b
   and recoloured through the tokens, and they are the current app shell — the
   sidebar, `.layout`, `.container`, `body.app`. Nothing here is deprecated, and
   nothing is waiting to be migrated. Do not add v1 rules, `--color-*` style
   aliases, or compatibility fallbacks: there is nothing left to be compatible
   with.
   ========================================================================== */

:root {
    /* Geometry. Real values, not aliases: tokens_v2.css ships no equivalent, so
       these are declared here and nowhere else. Every COLOUR, by contrast, comes
       straight from a tokens_v2.css token — this file declares none. */
    --radius:      12px;
    --radius-sm:   8px;
    --space:       16px;
    --header-h:    60px;
    --rail-w:      244px;   /* the example's rail width (request 131 §2) */
}

*, *::before, *::after { box-sizing: border-box; }
/* Text size (task 0162b).

   `100%`, never a px value: a px root would override whatever font size the
   user has set in their browser, which is the preference this task exists to
   honour. A percentage multiplies it instead, so the control and the browser
   setting compose rather than fight.

   Every `font-size` in this file and in components_v2.css is `rem`, so all of
   them follow. Default is the absence of the attribute, mirroring how the theme
   treats light — a page whose JS never runs stays at 100%. */
html { font-size: 100%; }
:root[data-text-size="lg"] { font-size: 112.5%; }   /* 18px at a 16px default */
:root[data-text-size="xl"] { font-size: 125%; }     /* 20px at a 16px default */

html, body { margin: 0; padding: 0; }

body {
    font-family: var(--font);
    color: var(--text);
    background: var(--bg);
    line-height: 1.5;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

h1, h2, h3, .brand, .secondary-menu__title, .page-title {
    font-family: var(--font);
    letter-spacing: -0.015em;
}

a { color: inherit; text-decoration: none; }

/* Full-width content column (change request 01): no wide unused side margins. */
.container { width: 100%; margin-inline: auto; padding-inline: var(--space); }

/* ---- Bug 0017: the signed-in shell's width and gutter ---------------------
   Two problems, one cause — the shell was borrowing the public pages' column.

   1. `components_v2.css` caps `.container` at `--container` (1240px). That is
      right for landing/register/login, which have no rail and would otherwise
      run edge to edge; it is wrong here, where the rail already bounds the
      content and the example's signed-in `.content` has no max-width at all. The
      page simply stopped growing past 1240px on a wide monitor.
   2. The bands disagreed on their gutter: content on `.container`'s 24px, the top
      bar and in-section bar on `--space` (16px), so the breadcrumb trail sat 8px
      inside the page beneath it.

   Both are fixed by giving the shell one gutter of its own and using it
   everywhere, so the bands cannot drift apart again — including at the 760px
   breakpoint where the components sheet narrows `.container` to 18px. Scoped to
   the app shell's body class, so the public shells keep the column they are
   designed around; the added specificity also makes sheet load order
   irrelevant. */
body.app { --shell-gutter: 24px; }
@media (max-width: 760px) { body.app { --shell-gutter: 18px; } }

body.app .container { max-width: none; padding-inline: var(--shell-gutter); }
body.app .site-header__inner,
body.app .secondary-menu__inner { padding-inline: var(--shell-gutter); }
/* Opt-in centered, comfortable-width column (e.g. the career dashboard). */
.centered { max-width: var(--container); margin-inline: auto; }
.spacer { flex: 1; }

.sr-only {
    position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    clip: rect(0 0 0 0); overflow: hidden; white-space: nowrap;
}

/* Accessibility (task 0024): skip link + visible focus (IS 5568 / WCAG 2.1 AA) */
.skip-link {
    position: absolute; inset-inline-start: 8px; top: -48px; z-index: 200;
    padding: 10px 16px; border-radius: var(--radius-sm);
    background: var(--brand-400); color: var(--on-brand);
    font-weight: 600; transition: top .15s ease;
}
.skip-link:focus { top: 8px; }
:focus-visible { outline: 3px solid var(--brand-400); outline-offset: 2px; }
#main-content:focus { outline: none; }

.prose { max-width: 720px; margin-inline: auto; padding-block: 24px; }
.prose h1 { margin: 0 0 12px; }
.prose h2 { margin: 24px 0 6px; font-size: 1.15rem; }
.prose p { margin: 8px 0; line-height: 1.6; }
.prose a { color: var(--brand-400); font-weight: 600; }
/* Task 0151a — the terms document is the first `.prose` page with three heading
   levels and lists. h3 must sit visibly *under* h2, and the list markers have to
   come back: `components_v2.css` strips them from every `ul` on the site, which is
   right for navigation and wrong for a document where the list is the content. */
.prose h3 { margin: 18px 0 4px; font-size: 1rem; }
.prose ul { margin: 8px 0; padding-inline-start: 24px; list-style: disc; }
.prose li { margin: 4px 0; line-height: 1.6; }

/* --- App shell: dark rail + content column ----------------------------- */
.layout { flex: 1; display: flex; align-items: stretch; min-height: 100vh; }

/* Rail geometry and the grouped nav inside it follow the example
   (`variant-8-geode/css/app.css` — request 131 §2). The example's foot block
   (user card + sign-out) is deliberately not ported: the header carries both. */
.sidebar {
    flex: 0 0 var(--rail-w);
    width: var(--rail-w);
    background: var(--rail);
    color: var(--rail-text);
    padding: 16px 12px;
    display: flex;
    flex-direction: column;
    gap: 22px;
}
.sidebar .brand { color: var(--rail-text); padding: 6px 10px 14px; border-block-end: 1px solid var(--rail-stroke); }

.sidebar__groups { display: flex; flex-direction: column; gap: 22px; }

/* The group title is a real button so it is operable by keyboard for free; it
   is styled back down to a label, and the chevron says which way it will go. */
.side-group__title {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    inline-size: 100%; margin: 0 0 8px; padding: 0 10px;
    background: none; border: 0; cursor: pointer;
    font: inherit; font-size: 0.6563rem; font-weight: 700;
    letter-spacing: .14em; text-transform: uppercase;
    /* The example dims these with `--rail-dim`, which measures 4.41:1 on the
       rail — under AA, and at 10.5px this is normal text, not large. One step up
       to `--rail-muted` clears it while keeping the label subordinate to the
       links (spec 04-accessibility). */
    color: var(--rail-muted); text-align: start;
}
.side-group__title:hover { color: var(--rail-text); }
.side-group__chev { inline-size: 14px; block-size: 14px; flex: none; transition: transform .18s ease; }
/* Collapsed: the chevron points at the list it would reveal. Mirrored in RTL by
   the logical rotation below. */
.side-group__title[aria-expanded="false"] .side-group__chev { transform: rotate(-90deg); }
[dir="rtl"] .side-group__title[aria-expanded="false"] .side-group__chev { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) { .side-group__chev { transition: none; } }

.sidebar__nav { display: grid; gap: 2px; margin: 0; padding: 0; list-style: none; }
/* `display: grid` above outranks the UA's `[hidden] { display: none }` — an
   author rule always beats the UA sheet — so without this the collapsed group
   stays on screen while reporting itself hidden. */
.sidebar__nav[hidden] { display: none; }
.sidebar__link {
    display: flex; align-items: center; gap: 11px;
    padding: 9px 11px; border: 1px solid transparent; border-radius: var(--r-md);
    color: var(--rail-text); font-weight: 500;
}
.sidebar__link:hover { background: var(--rail-hover); color: var(--rail-text); }
.sidebar__link.is-active {
    background: var(--rail-active); border-color: var(--rail-active-border);
    color: var(--rail-active-text); font-weight: 600;
}
.sidebar__icon { inline-size: 17px; block-size: 17px; flex: none; opacity: .85; }
.sidebar__link.is-active .sidebar__icon { opacity: 1; color: var(--rail-active-icon); }
/* Focus has to stay visible against the dark rail, which the page-level ring
   colour is not built for. */
.sidebar :focus-visible { outline: 2px solid var(--rail-focus); outline-offset: 2px; }

.main-col { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.sidebar-overlay { display: none; }

/* --- Header (top bar of the content column) ---------------------------- */
.site-header {
    background: var(--glass);
    border-block-end: 1px solid var(--stroke);
    position: sticky; inset-block-start: 0; z-index: 50;
}
.site-header__inner { display: flex; align-items: center; gap: 12px; min-height: var(--header-h); padding-inline: var(--space); }

.brand { font-weight: 800; font-size: 1.15rem; display: inline-flex; align-items: center; gap: .4em; white-space: nowrap; }

/* The mark carries the same tile everywhere, rail included (request 149
   Verification #1, task 0149c) — it is `.logo-mark` from components_v2.css, which
   v2 loads app-wide. The rail's own `.brand` sets a bigger font for the name, so
   pin the tile's letter back to the size the header uses; without this the letter
   grows and the 30px box crops it. */
.brand .logo-mark { font-size: 0.7813rem; }

/* The name: one line by default — page headers, footers, the auth pages. Only the
   left panel stacks (--stacked below), because only it is narrow enough to need
   it; the story is explicit that the travel home page keeps one line. */
.brand-name { display: inline-flex; align-items: baseline; gap: .35em; }
/* Two rows: "Travel" over "by Domolytics". A column flex rather than a <br>, so
   the suffix carries its own size and colour and the stack mirrors under RTL
   without a rule of its own. */
.brand-name--stacked { flex-direction: column; align-items: start; gap: 0; line-height: 1.15; }
/* --text-muted, NOT --text-dim. The suffix sets at ~13px/500, which is normal
   text, so it owes 4.5:1 (1-tech/4-ACCESSIBILITY.md). Sampled from the painted
   pixels, --text-dim gives 2.97:1 in light and 3.52:1 in dark — both fail;
   --text-muted gives 5.35:1 and 7.0:1. The old one-line `.logo .suffix` had the
   same problem and it shipped, because the suffix was hidden on mobile and easy
   to miss on desktop. */
.brand-name__suffix { font-size: .72em; font-weight: 500; color: var(--text-muted); }
/* The rail is dark in BOTH themes, so it has its own muted token — --text-muted
   is #515c7a in light and would sit on that dark ground at about 2:1. */
.sidebar .brand-name__suffix { color: var(--rail-muted); }

.sidebar-toggle {
    display: none; align-items: center; justify-content: center;
    font: inherit; font-size: 1.25rem; line-height: 1; cursor: pointer;
    padding: 6px 10px; border: 1px solid var(--stroke);
    border-radius: var(--radius-sm); background: var(--glass); color: var(--text);
}

/* Wraps: with a text-size control alongside the theme switch and the two
   dropdowns, this row cannot hold one line at 320px once the user picks the
   largest text — and a control for making text bigger must not be what breaks
   the layout when they use it (task 0162b). */
.header-tools { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.site-header__inner { flex-wrap: wrap; }

/* --- Breadcrumbs (request 131) ----------------------------------------
   The Geode top bar carries the trail at its start; values (13px, 8px gap,
   dimmed separators, current page at 600) come from the variant's app.css,
   re-expressed against our own tokens. Logical properties only, so the trail
   mirrors under dir="rtl" with no separate rule. */
.crumbs { min-width: 0; font-size: .8125rem; color: var(--text-muted); }
.crumbs ol { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0; padding: 0; list-style: none; }
.crumbs li { display: flex; align-items: center; gap: 8px; }
.crumbs .sep { opacity: .6; }
.crumbs a { color: inherit; }
.crumbs a:hover { color: var(--text); }
.crumbs strong { color: var(--text); font-weight: 600; }

/* Phones: the header already carries the drawer toggle and the tools, so the
   trail keeps only the current page rather than wrapping onto a second line
   (spec 03-ui: mobile adaptations scoped to ≤640px). */
@media (max-width: 640px) {
    .crumbs li:not(:last-child) { display: none; }
    .crumbs li:last-child .sep { display: none; }
}

/* Dropdowns (language / user) */
.dropdown-wrap { position: relative; }
.btn-tool {
    font: inherit; cursor: pointer; display: inline-flex; align-items: center; gap: .4em;
    padding: 7px 10px; border: 1px solid var(--stroke); border-radius: var(--radius-sm);
    background: var(--glass); color: var(--text);
}
.btn-tool:hover { background: var(--glass-2); }
.dropdown {
    position: absolute; inset-inline-end: 0; inset-block-start: calc(100% + 6px);
    min-width: 160px; margin: 0; padding: 6px; list-style: none;
    background: var(--glass); border: 1px solid var(--stroke);
    border-radius: var(--radius); box-shadow: var(--shadow-lg); display: none; z-index: 80;
}
.dropdown.open { display: block; }
.dropdown a { display: block; padding: 8px 10px; border-radius: var(--radius-sm); }
.dropdown a:hover { background: var(--glass-2); }
.dropdown a[aria-current="true"] { color: var(--brand-400); font-weight: 600; }

/* --- Secondary (in-section) sub-navigation ----------------------------- */
.secondary-menu { background: var(--glass); border-block-end: 1px solid var(--stroke); }
/* Scrolls within itself rather than widening the page. The trip menu has 9
   entries; past roughly 1100px of them it used to push the document wider than
   the viewport — visible at the default text size on a 1024px laptop, and at
   1280px once a user picks the largest text (task 0162b). WCAG 1.4.10 wants the
   page not to scroll sideways; a scroll container is the way to keep that true
   without dropping menu entries. */
.secondary-menu__inner { display: flex; align-items: center; gap: var(--space); min-height: 52px; padding-inline: var(--space); overflow-x: auto; }
.secondary-nav { flex: 0 0 auto; }
.secondary-nav a { white-space: nowrap; }
.secondary-menu__title { margin: 0; font-size: 1.05rem; font-weight: 700; }
.secondary-menu__toggle { display: none; }
.secondary-nav { display: flex; gap: 4px; margin-inline-start: auto; }
.secondary-nav a { padding: 6px 12px; border-radius: 999px; color: var(--text-muted); font-weight: 500; }
.secondary-nav a:hover { background: var(--glass-2); color: var(--text); }
/* The current page (wired up in task 0177 — this rule existed and nothing ever set the
   class). The tint alone is not enough: in monochrome `--info-bg` flattens onto the
   page ground and the pill becomes invisible, so which page you are on would be carried
   by colour alone, which is exactly what 4-ACCESSIBILITY forbids. The ring and the
   heavier weight survive every mode; `currentColor` keeps them token-free. */
.secondary-nav a.is-active {
    background: var(--info-bg);
    color: var(--brand-400);
    font-weight: 700;
    box-shadow: inset 0 0 0 1px currentColor;
}
.secondary-menu__title a { color: inherit; text-decoration: none; }
.secondary-menu__title a:hover { color: var(--brand-400); }

/* --- Content / footer -------------------------------------------------- */
.content { flex: 1; padding-block: 24px; }
.site-footer { color: var(--text-muted); border-block-start: 1px solid var(--stroke); padding-block: 16px; font-size: .9rem; }
.site-footer__sep { margin-inline: 6px; }

/* --- Generic components ------------------------------------------------- */
.card { background: var(--glass); border: 1px solid var(--stroke); border-radius: var(--radius); box-shadow: var(--shadow-sm); padding: 20px; }

.page-head { margin-block-end: 20px; }
.page-head h1 { margin: 0; font-size: 1.5rem; }
/* The section's icon beside the section title in the secondary menu bar (task 0183;
   moved off the page-head <h1> by operator correction, 2026-08-25).

   Sized against the heading, not in px, so it follows the text-size control
   (task 0162b). `vertical-align: middle` on both the span and the image is what
   keeps a drawn icon and a 1em emoji on the same baseline as the text.

   `margin-inline-end` — logical, so RTL mirrors the gap with no second rule. */
.page-head__icon {
    margin-inline-end: 6px;
    vertical-align: middle;
    line-height: 1;
    font-size: 1.1em;
}
.page-head__icon--img { display: inline-block; }
.page-head__icon--img img {
    inline-size: 1.5em;
    block-size: auto;
    vertical-align: middle;
}
/* In the bar the title sits on a fixed-height row beside the sub-nav, so the icon is
   held to the line rather than allowed to set its height. */
.secondary-menu__title .page-head__icon--img img { inline-size: 1.4em; }
.page-head__sub { margin: 4px 0 0; color: var(--text-muted); }
/* Third header line: trip dates + nights (request 127 §1). */
.page-head__dates { margin: 2px 0 0; }

/* Home landing (task-0009) */
.home-hero { padding: 56px 0 32px; text-align: center; }
.home-hero__brand { margin: 0 0 12px; font-weight: 700; color: var(--text-muted); }
.home-hero__title { margin: 0 auto 12px; font-size: 2.25rem; line-height: 1.15; max-width: 16ch; }
.home-hero__lead { margin: 0 auto; max-width: 52ch; color: var(--text-muted); font-size: 1.05rem; }
.home-hero__cta { margin-block-start: 24px; display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
.home-features { padding: 24px 0 56px; }
.home-features__title { margin: 0 0 20px; text-align: center; font-size: 1.25rem; }

/* Shared card grid for home features + dashboard section tiles */
.feature-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 16px;
}
.feature-card {
    background: var(--glass); border: 1px solid var(--stroke);
    border-radius: var(--radius); box-shadow: var(--shadow-sm); padding: 20px;
}
.feature-card__icon { font-size: 1.75rem; }
/* A drawn section icon in place of the emoji (task 0180). Every card in the sections
   grid reserves the same icon height, whether it holds a 64px drawing or an emoji, so
   the titles line up across the row and nothing moves as the remaining three icons
   arrive. Scoped to that grid — career's onboarding cards use `.feature-card` too and
   have no images to make room for. */
.feature-grid--sections .feature-card__icon {
    display: flex; align-items: center; justify-content: center;
    min-block-size: 72px;
    line-height: 1;
}

/* ---- The section cards: one size, centred content (task 0182a, request 182) ----
   `grid-auto-rows: 1fr` is what makes every card identical: each row takes the
   tallest card in it, and because the grid is `1fr` columns of equal width the
   cards end up the same size in both axes. The card must then stretch to fill the
   track it was given, which is what `block-size: 100%` does — without it the card
   keeps its content height and only the ROW is tall.

   `.feature-card--link` sets `display: block` and is declared after this, so these
   rules carry two classes to win on specificity rather than on order.

   Scoped to this grid throughout: `.feature-card` is also the career and travel
   dashboards' card and both onboarding pages', none of which are in this story. */
.feature-grid--sections { grid-auto-rows: 1fr; }
.feature-grid--sections .feature-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    block-size: 100%;
}
.feature-grid--sections .feature-card__desc { max-inline-size: 30ch; }
/* Pinned to the foot of the card, so a section WITH a count and one without still
   agree on where everything above them sits. `auto` on a flex item's start margin
   absorbs the free space the equal-height row created. */
.feature-grid--sections .feature-card__count {
    margin: 12px 0 0;
    padding-block-start: 10px;
    margin-block-start: auto;
    inline-size: 100%;
    border-block-start: 1px solid var(--hairline);
    color: var(--text-muted);
    font-size: .9rem;
}
.feature-grid--sections .feature-card__count-n {
    color: var(--text);
    font-weight: 700;
    font-size: 1.05rem;
}
.feature-card__icon--img img { inline-size: 64px; block-size: auto; }
.feature-card__name { margin: 10px 0 6px; font-size: 1.05rem; }
.feature-card__desc { margin: 0; color: var(--text-muted); font-size: .95rem; }
.feature-card--link { display: block; color: inherit; transition: border-color .15s, transform .15s; }
.feature-card--link:hover { border-color: var(--brand-400); transform: translateY(-2px); }
.feature-card__open { display: inline-block; margin-block-start: 10px; color: var(--brand-400); font-weight: 600; }
@media (max-width: 720px) {
    .home-hero { padding-block-start: 36px; }
    .home-hero__title { font-size: 1.8rem; }
}

/* ---- Shopping section (tasks 0012 / 0013) ---- */
.btn--sm { padding: 5px 10px; font-size: .85rem; }
.btn--xs { padding: 3px 8px; font-size: .78rem; }
.btn.btn--danger { background: var(--danger); color: var(--on-danger); border-color: transparent; }
.btn--danger:hover { background: var(--danger-hover); }

.page-head--row { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.back-link { display: inline-block; margin-block-end: 6px; color: var(--text-muted); font-size: .9rem; }
.back-link:hover { color: var(--brand-400); }

/* Lists collection */
.list-collection { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.list-item {
    display: flex; align-items: center; gap: 12px;
    background: var(--glass); border: 1px solid var(--stroke);
    border-radius: var(--radius); box-shadow: var(--shadow-sm); padding: 14px 16px;
}
.list-item__name { font-weight: 600; font-size: 1.05rem; color: var(--text); }
.list-item__name:hover { color: var(--brand-400); }
.list-item__date { margin-inline-start: auto; color: var(--text-muted); font-size: .85rem; }
.list-item__actions { display: flex; gap: 6px; }
/* Companion row: the note takes a full line of its own under the name (request 127 §10). */
.companion-item { flex-wrap: wrap; }
.companion-item__note { flex-basis: 100%; }

/* Items table */
.items-table td { vertical-align: top; }
.items-table__actions-col { width: 1%; white-space: nowrap; }
.item-cell { display: flex; align-items: flex-start; gap: 10px; }
.item-name { font-weight: 600; }
.item-notes { color: var(--text-muted); font-size: .85rem; margin-block-start: 2px; }
.item-qty { white-space: nowrap; }
.item-actions { display: flex; gap: 6px; justify-content: flex-end; white-space: nowrap; }
.item-status-dot { flex: 0 0 auto; width: 10px; height: 10px; border-radius: 50%; margin-block-start: 5px; background: var(--stroke); }

/* Status-distinct rows */
.item-row--new .item-status-dot { background: var(--brand-400); }
.item-row--taken { background: var(--success-bg, var(--tint-2)); }
.item-row--taken .item-status-dot { background: var(--success); }
.item-row--taken .item-name { text-decoration: line-through; color: var(--text-muted); }
.item-row--not-found { background: var(--danger-bg, var(--tint-2)); }
.item-row--not-found .item-status-dot { background: var(--danger); }
.item-row--not-found .item-name { color: var(--danger); }

.items-empty { margin-block-start: 12px; color: var(--text-muted); }

/* Modals / popups */
.modal { position: fixed; inset: 0; z-index: 50; display: flex; align-items: center; justify-content: center; padding: 16px; }
.modal[hidden] { display: none; }
.modal__backdrop { position: absolute; inset: 0; background: var(--backdrop); }
.modal__dialog {
    position: relative; width: 100%; max-width: 440px;
    max-height: calc(100vh - 48px); overflow-y: auto;
    background: var(--glass); border-radius: var(--radius);
    box-shadow: var(--shadow-lg); padding: 22px;
}
.modal__title { margin: 0 0 16px; font-size: 1.2rem; }
.modal__message { margin: 0 0 18px; }
.modal__actions { display: flex; justify-content: flex-end; gap: 10px; margin-block-start: 8px; }
/* Segment popup From/To groups (request 128 §3). */
.seg-group { border: 1px solid var(--stroke); border-radius: var(--radius-sm); padding: 10px 12px; margin-block-end: 12px; }
.seg-group > legend { font-weight: 600; padding-inline: 6px; }

/* Airport autocomplete (request 129). The list overlays the field, so the popup
   needs a stacking context above the rest of the form but below the modal chrome. */
.ac { position: relative; }
/* Autocomplete dropdown (airports and airlines). Request 139 verification §8:
   it was unreadable in dark mode.

   The cause was `--glass` (which this rule used to reach through a `--color-surface`
   alias, removed since). That token is #ffffff under light but rgba(255,255,255,0.04) under
   dark — 4% white. A panel *inside* the page can afford to be a translucent
   wash; this list is absolutely positioned OVER the form behind it, so at 4%
   the suggestions floated on top of whatever happened to be underneath.

   It is pinned to the opaque `--panel` instead, and the text to `--text`, so the
   list is legible whatever it covers. The row highlight stays translucent on
   purpose — it now composites over an opaque ground, which is what makes it
   read as a lift rather than a hole. */
.ac__list {
    position: absolute; inset-inline: 0; top: 100%; z-index: 5;
    margin: 2px 0 0; padding: 4px; list-style: none;
    background: var(--panel); color: var(--text);
    border: 1px solid var(--stroke);
    border-radius: var(--r-md); box-shadow: var(--shadow-lg);
    max-block-size: 240px; overflow-y: auto;
}
.ac__opt { padding: 8px 10px; border-radius: var(--r-sm); cursor: pointer; font-size: .92rem; color: var(--text); }
.ac__opt:hover, .ac__opt.is-active { background: var(--glass-2); }
/* --brand-400 lightens under dark (#4054d8 → #8093ff), so the active outline
   keeps its contrast against the panel in both themes. */
.ac__opt.is-active { outline: 2px solid var(--brand-400); outline-offset: -2px; }

.field-row { display: flex; gap: 12px; }
.field-row .field { flex: 1; }
/* Accommodation type + name share a line, the type kept narrow (request 126). */
.field-row--acc { flex-wrap: wrap; }
.field-row--acc .field--acc-type { flex: 0 0 170px; }
.field-row--acc .field--acc-name { flex: 1 1 220px; }
@media (max-width: 640px) { .field-row--acc .field--acc-type { flex: 1 1 100%; } }
textarea.input { resize: vertical; font: inherit; }
.form-error { margin: 0 0 10px; color: var(--danger); font-size: .9rem; }
/* Required-field asterisk (task 0100 / UI amendments v1 §1). */
.req { color: var(--danger); margin-inline-start: 2px; font-weight: 700; }

/* ---- Career section (tasks 0016+) ---- */
.head-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.list-item__meta { color: var(--text-muted); font-size: .9rem; }
.list-item .inline { display: inline; margin: 0; }
.subhead { margin: 28px 0 12px; font-size: 1.05rem; }

.stars { letter-spacing: 1px; white-space: nowrap; }
.stars .star { color: var(--stroke); }
/* Rating stars kept a brighter gold; they are supplementary to the text label,
   so 3:1 (UI) applies — #d97706 on white = 3.19:1 (task 0026). */
.stars .star--on { color: var(--warning); }

.notice {
    margin: 0 0 16px; padding: 10px 14px; border-radius: var(--radius-sm);
    background: var(--glass-2); color: var(--text-muted);
    border: 1px solid var(--stroke);
}

.detail-card { max-width: 640px; }
.detail-list { margin: 0; display: grid; gap: 2px; }
.detail-row {
    display: grid; grid-template-columns: 180px 1fr; gap: 12px;
    padding: 12px 0; border-block-end: 1px solid var(--stroke);
}
.detail-row:last-child { border-block-end: 0; }
.detail-row dt { margin: 0; color: var(--text-muted); }
.detail-row dd { margin: 0; }
.detail-notes { white-space: pre-line; }

.form-card { max-width: 560px; }
.form-actions { display: flex; justify-content: flex-end; gap: 10px; margin-block-start: 8px; }
@media (max-width: 560px) {
    .detail-row { grid-template-columns: 1fr; gap: 2px; }
}

/* Contacts (task 0017) */
.badge {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 3px 10px; border-radius: 999px; font-size: .85rem;
    background: var(--glass-2); color: var(--text); border: 1px solid var(--stroke);
}
/* The "Self-employed" chip in career — unrelated to `role-badge--self`, which is
   the orange "you are" identity pill.
   Doubled class so it outranks components_v2's generic `.badge`, which loads later
   and was flattening this to the plain chip. Once it applied, the teal text it
   used to ask for measured 2.91:1 on this blue wash — the two were never seen
   together while the rule was being overridden. Text now comes from the same blue
   family as the background and border. */
.badge.badge--self { background: var(--info-bg); color: var(--brand-400); border-color: var(--info-border); }
.list-item__spacer { flex: 1; }
.hint { margin: 0 0 14px; color: var(--text-muted); font-size: .85rem; }
.readonly-value { padding: 6px 0; }

/* Listings & interactions (task 0018) */
.muted { color: var(--text-muted); }
.small { font-size: .82rem; }
.head-sub { margin-block-start: 8px; }

.status-badge {
    display: inline-flex; align-items: center; gap: 4px; white-space: nowrap;
    padding: 3px 10px; border-radius: 999px; font-size: .82rem;
    background: var(--badge); color: var(--on-brand);
}

.filter-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-block-end: 18px; }
.chip {
    display: inline-flex; gap: 4px; padding: 5px 12px; border-radius: 999px;
    background: var(--glass); border: 1px solid var(--stroke);
    color: var(--text); font-size: .85rem;
}
.chip:hover { border-color: var(--brand-400); }
.chip--on { background: var(--info-bg); border-color: var(--brand-400); color: var(--brand-400); font-weight: 600; }

.career-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }
@media (max-width: 860px) { .career-cols { grid-template-columns: 1fr; } }
.interaction-form { margin-block-end: 16px; padding-block-end: 16px; border-block-end: 1px solid var(--stroke); }

.timeline { list-style: none; margin: 0; padding: 0; }
.timeline__item { display: flex; gap: 10px; align-items: flex-start; padding: 12px 0; border-block-end: 1px solid var(--stroke); }
.timeline__item:last-child { border-block-end: 0; }
.timeline__icon { flex: 0 0 auto; font-size: 1.1rem; }
.timeline__body { flex: 1; min-width: 0; }
.timeline__head { display: flex; gap: 10px; align-items: baseline; justify-content: space-between; }
.timeline__desc { margin-block-start: 2px; word-break: break-word; }

/* Career dashboard (task 0019) */
.funnel { display: grid; grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); gap: 8px; margin-block-end: 24px; }
.funnel__stage {
    display: flex; flex-direction: column; gap: 4px; padding: 12px;
    border-radius: var(--radius-sm); background: var(--glass);
    border: 1px solid var(--stroke); border-top: 3px solid var(--badge); color: var(--text);
}
.funnel__stage:hover { border-color: var(--brand-400); }
.funnel__count { font-size: 1.5rem; font-weight: 700; line-height: 1; }
.funnel__label { font-size: .8rem; color: var(--text-muted); }
.action-group { margin: 14px 0 6px; font-size: .9rem; color: var(--text-muted); }
.action-group--overdue { color: var(--danger); }
.action-item { display: flex; justify-content: space-between; gap: 10px; padding: 7px 0; border-block-end: 1px solid var(--stroke); }
.action-item:last-child { border-block-end: 0; }
.action-item__date { color: var(--text-muted); font-size: .85rem; white-space: nowrap; }
.action-item__date.overdue { color: var(--danger); font-weight: 600; }

.subhead-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 0 0 12px; }
.subhead-row .subhead { margin: 0; }

.profile-card { max-width: 560px; }
.profile-list { margin: 0; display: grid; gap: 2px; }
.profile-row {
    display: grid;
    grid-template-columns: 200px 1fr;
    gap: 12px;
    padding: 12px 0;
    border-block-end: 1px solid var(--stroke);
}
.profile-row:last-child { border-block-end: 0; }
.profile-row dt { margin: 0; color: var(--text-muted); }
.profile-row dd { margin: 0; font-weight: 600; word-break: break-word; }
@media (max-width: 560px) {
    .profile-row { grid-template-columns: 1fr; gap: 2px; }
}

.btn {
    font: inherit; font-weight: 600; cursor: pointer; padding: 9px 16px;
    border: 1px solid transparent; border-radius: var(--radius-sm);
    background: var(--brand-400); color: var(--on-brand);
}
.btn:hover { background: var(--brand-500); }
.btn--ghost { background: var(--glass); color: var(--text); border-color: var(--stroke); }
.btn--block { width: 100%; }

.input {
    font: inherit; padding: 9px 12px; border: 1px solid var(--stroke);
    border-radius: var(--radius-sm); background: var(--glass); color: var(--text);
}
.input:focus { outline: 2px solid var(--info-bg); outline-offset: 0; border-color: var(--brand-400); }

.toolbar { display: flex; gap: 8px; margin-block-end: 16px; }
.toolbar .input { flex: 1; }

.table { width: 100%; border-collapse: collapse; background: var(--glass); border: 1px solid var(--stroke); border-radius: var(--radius); overflow: hidden; }
.table th, .table td { padding: 12px 14px; text-align: start; border-block-end: 1px solid var(--stroke); }
.table th { background: var(--glass-2); color: var(--text-muted); font-size: .8rem; font-weight: 600; text-transform: uppercase; letter-spacing: .03em; }
.table tr:last-child td { border-block-end: none; }

/* --- Auth / error pages ------------------------------------------------ */
.auth-wrap { display: grid; place-items: center; padding: 24px; min-height: calc(100vh - var(--header-h) - 60px); }
.auth-card { width: 100%; max-width: 380px; }
.auth-card--center { text-align: center; }
.auth-card h1 { margin: 0 0 4px; font-size: 1.5rem; }
.auth-card .sub { margin: 0 0 20px; color: var(--text-muted); }
.auth-error {
    margin: 0 0 16px;
    padding: 10px 12px;
    border-radius: 8px;
    background: var(--danger-bg, var(--tint-2));
    border: 1px solid var(--danger-border);
    color: var(--danger);
    font-size: .9rem;
}
.error-code { font-size: 3rem; line-height: 1; }

.field { display: grid; gap: 6px; margin-block-end: 14px; }
.field label, .field .field-label { font-size: .9rem; color: var(--text-muted); }
/* A caption for a value that is shown but not editable — the "you are" badge in
   the companion popup (request 139 verification §3). Not a <label>: it labels no
   control. */
.field .field-label { display: block; }
.field .input { width: 100%; }
/* Truncate a long selected value with an ellipsis, keeping the box width fixed
   (origin-country dropdown matches the base-currency width, request 0111). */
.input--truncate { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.auth-alt { margin-block-start: 16px; text-align: center; font-size: .9rem; }
.auth-alt a { color: var(--brand-400); font-weight: 600; }

/* --- Mobile: rail collapses to an off-canvas drawer -------------------- */
@media (max-width: 768px) {
    .sidebar-toggle { display: inline-flex; }
    /* Hidden from sight, kept for assistive tech (task 0161c). This was
       `display: none`, which also removed it from the accessibility tree — and
       since the button's only other child is an aria-hidden emoji, the language
       and user menus were left with no accessible name at all below 768px. */
    .btn-tool__label {
        position: absolute; width: 1px; height: 1px;
        margin: -1px; padding: 0; border: 0;
        clip: rect(0 0 0 0); overflow: hidden; white-space: nowrap;
    }

    .sidebar {
        position: fixed;
        inset-block: 0 0;
        inset-inline-start: calc(-1 * var(--rail-w));
        z-index: 70;
        overflow-y: auto;
        transition: inset-inline-start .2s ease;
    }
    body.sidebar-open .sidebar { inset-inline-start: 0; }

    .sidebar-overlay {
        display: block; position: fixed; inset: 0;
        background: var(--backdrop);
        opacity: 0; visibility: hidden; transition: opacity .2s ease; z-index: 65;
    }
    body.sidebar-open .sidebar-overlay { opacity: 1; visibility: visible; }

    .secondary-menu__inner { position: relative; }
    .secondary-menu__toggle {
        display: inline-flex; align-items: center; margin-inline-start: auto;
        font: inherit; font-size: 1.1rem; cursor: pointer;
        padding: 6px 12px; border: 1px solid var(--stroke);
        border-radius: var(--radius-sm); background: var(--glass);
    }
    .secondary-nav {
        display: none; position: absolute; inset-inline: 0; inset-block-start: 100%;
        flex-direction: column; gap: 2px; margin: 0; padding: 8px;
        background: var(--glass); border-block-end: 1px solid var(--stroke);
        box-shadow: var(--shadow-lg); z-index: 40;
    }
    .secondary-nav.open { display: flex; }
}

/* Travel — onboarding & misc fixes (bug 0002 / task 0037) */
.no-spin { appearance: textfield; -moz-appearance: textfield; }
.no-spin::-webkit-outer-spin-button,
.no-spin::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.feature-grid--center { justify-content: center; grid-template-columns: repeat(auto-fit, minmax(220px, 280px)); }

/* Travel section (task 0028) */
.trip-switch { display: inline-flex; align-items: center; gap: 8px; }
.feature-card--soon { opacity: .65; }
.feature-card--soon .feature-card__name { color: var(--text-muted); }

/* Travel — flights (task 0030) */
.modal__dialog--wide { max-width: 640px; }
.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px 14px; }
.input-row { display: flex; gap: 8px; align-items: center; }
.input-row .input { flex: 1; }
.check-row { display: flex; flex-wrap: wrap; gap: 8px 16px; }
.check-list { display: flex; flex-direction: column; gap: 8px; margin: 10px 0; }
/* Scrollable country checkbox grid on the trip form (bug 0004 #3). */
.country-check-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 4px 12px; max-height: 220px; overflow-y: auto; border: 1px solid var(--stroke); border-radius: var(--radius-sm); padding: 8px; margin-block-start: 6px; }
/* Trip country picker: keep the popup height constant no matter how many
   countries are selected (label) or visible after filtering (list) — reserve a
   fixed height for each and scroll internally (request 0107). */
[data-country-picker] .country-check-list { height: 146px; }        /* ~5 rows (request 0109) */
[data-country-picker] [data-country-selected] { display: block; height: 70px; line-height: 1.4; overflow-y: auto; padding: 5px 8px; border: 1px solid var(--stroke); border-radius: var(--radius-sm); }  /* ~3 lines (request 0109) */
/* Faint placeholder shown while no country is selected (request 0108). */
[data-country-picker] [data-country-selected]:empty::before { content: attr(data-placeholder); font-style: italic; }
.check { display: inline-flex; align-items: center; gap: 6px; }
/* .check sets display, which would override the [hidden] attribute; make hidden
   win so the country filter can hide options (bug 0016). */
.check[hidden] { display: none; }
.hint--warn { color: var(--warning); }
/* Two bugs stacked here, and fixing either alone changes nothing on screen.
   1. `--warn-bg` was declared in no sheet, so this fell back to `--info-bg` — a
      blue wash behind amber text. The token it meant is `--warning-bg`, which
      tokens_v2 defines for both themes and which pairs with `--warning`.
   2. `components_v2.css` loads after this file and its generic `.badge` sets a
      background and colour of its own. On equal specificity the later sheet won,
      so the modifier was being flattened to the generic grey chip regardless of
      what it asked for. `.badge.badge--warn` outranks a bare `.badge`.
   The sibling modifiers below (--self, --on, --link) have the same collision and
   are still single-class; they are left as they are rather than widened here. */
.badge.badge--warn { background: var(--warning-bg); color: var(--warning-strong); border-color: var(--warning); }
.list-item--stack { flex-direction: column; align-items: stretch; gap: 6px; }
@media (max-width: 640px) { .form-grid { grid-template-columns: 1fr; } }

/* Travel — hotels (task 0031) */
.hotel-card { margin-block-end: 16px; }
.hotel-card__head { display: flex; justify-content: space-between; gap: 12px; align-items: flex-start; flex-wrap: wrap; }
.hotel-card__name { margin: 0 0 4px; font-size: 1.1rem; }
/* Kind of stay, trailing the booking name on the same line (request 127 §2). */
.hotel-card__type { font-size: .85rem; font-weight: 400; white-space: nowrap; }
/* Rooms block sits in a shaded panel to set it apart from the hotel details (bug 0003 §3). */
/* The inner card. Accommodation had it first as `.hotel-card__rooms`; flights
   reuses the identical look via `.subcard` (request 141), so the declarations
   live here once instead of being copied per page. */
.hotel-card__rooms,
.subcard { margin-block-start: 14px; background: var(--glass-2); border: 1px solid var(--stroke); border-radius: var(--radius-sm); padding: 10px 12px; }
.hotel-card__rooms-head,
.subcard__head { display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap; }
.room-list__item { padding: 6px 8px; border-radius: var(--radius-sm); background: var(--glass); border: 1px solid var(--stroke); }
.subhead--sm { font-size: .95rem; margin: 0; }
.room-list { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.room-list__item { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* Transparent on desktop; a compact inline action group on mobile (bug 0010 §2). */
.room-list__actions { display: contents; }

/* Travel — rental cars (spec rental_cars.md): key/value detail grid + add-line form. */
.kv-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 8px 20px; margin-top: 8px; }
.kv { display: flex; flex-direction: column; gap: 2px; }
.kv__k { font-size: .78rem; color: var(--text-muted); }
.kv__v { font-size: .95rem; }
.inline-form { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-top: 10px; }
.inline-form .input { width: auto; flex: 1 1 160px; }
/* Required field in an inline-form: keep the asterisk tight to the input (task 0100). */
.inline-form .req-field { display: flex; align-items: center; gap: 2px; flex: 1 1 160px; }
.inline-form .req-field .input { flex: 1 1 auto; width: auto; }

/* Travel — expenses (task 0032) */
.exp-summary { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-block-end: 12px; padding: 10px 14px; background: var(--glass); border: 1px solid var(--stroke); border-radius: var(--radius-sm); }
.exp-summary .sep { color: var(--text-muted); }
/* Transparent on desktop (identical layout); becomes a row on mobile (bug 0010 §1). */
.exp-summary__item { display: contents; }
.exp-filters { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-block-end: 16px; }
.exp-filters .input { width: auto; }
.table-wrap { overflow-x: auto; margin-block-end: 16px; }
/* Rounded corners on the expense tables (request 127 §4). The wrapper does the
   clipping — the table keeps `border-collapse: collapse`, which ignores a radius
   of its own. `overflow-x: auto` already makes this box a clipping context.
   The wrapper draws its own outline too: clipping a square cell border on the
   curve otherwise eats a few pixels at each corner (verification §4). In the
   by-date view every day is its own wrapper, so each one rounds separately. */
.table-wrap[data-exp-cat], .exp-day-wrap {
    border-radius: var(--radius);
    border: 1px solid var(--stroke);
}
.exp-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.exp-table th, .exp-table td { padding: 5px 8px; text-align: start; border: 1px solid var(--hairline); }
/* Slimmer category header + compact, inline row actions (bug 0003 §6).
   Since request 155 the header carries no category colour: it is the surface, like
   the all-trips table's list head, and identical for every category. The token, not
   `#fff` — this has to be the raised dark surface in dark mode, not white. */
.exp-cat-head th {
    background: var(--glass);
    color: var(--text);
    font-size: .9rem;
    padding: 4px 8px;
}
/* Keep the actions cell a real table cell (colspan works); right-align via an
   inner flex wrapper (bug 0007 §4b, bug 0008 §1a). */
.exp-cat-head__actions { text-align: end; white-space: nowrap; }
.exp-cat-head__actions-inner { display: flex; justify-content: flex-end; align-items: center; gap: 6px; flex-wrap: wrap; }
.exp-cat-head__actions-inner .inline { display: inline-flex; margin: 0; }
.exp-cat-head__actions .btn { padding: 2px 8px; font-size: .78rem; }
.exp-table .inline { display: inline; margin: 0; }
.exp-table .linklike { padding: 0 3px; font-size: .82rem; }
/* Column titles: the all-trips `.grid-head` treatment (request 155), so the two
   tables read as the same component. `--text-muted` rather than `--text-dim` for the
   reason `.grid-head` gives — dim fails AA at this size. */
.exp-table__cols th {
    background: var(--sunken);
    font-size: 0.6563rem; font-weight: 700;
    letter-spacing: .08em; text-transform: uppercase;
    color: var(--text-muted);
}
.exp-table .num { text-align: end; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Only the DATA rows still sit on a stored category colour, so only they resolve
   their text through the measured foreground. The header and subtotal left this
   rule with request 155 — they are on theme tokens now and take `--text`/`--text-muted`. */
.exp-table tbody td { color: var(--cat-fg, var(--text)); }
/* The subtotal takes the column titles' colours (story: "same color"), but keeps its
   own weight and size: it carries figures, and 10.5px uppercase would make the one
   number the row exists for harder to read than the data above it. */
.exp-subtotal td { background: var(--sunken); color: var(--text-muted); font-weight: 700; }
.exp-grand { font-size: 1.05rem; margin: 4px 0 12px; }
/* Collapsible category groups (request 0120 §1). The whole title is the control
   (request 127 §4): caret + label in one button, sized like the plan page's
   share-block summary, with a tap target that holds up on a phone. */
.exp-collapse {
    display: inline-flex; align-items: center; gap: 10px;
    background: none; border: 0; cursor: pointer; color: inherit;
    font: inherit; font-size: 1.05rem; text-align: start;
    padding: 10px 12px; margin-inline-start: -12px; border-radius: var(--radius-sm);
    min-block-size: 48px; /* comfortable target on a phone (verification §4/§5) */
}
.exp-collapse:hover { background: var(--tint); }
.exp-collapse__caret { font-size: 1.5rem; line-height: 1; transition: transform .15s; }
.exp-collapse[aria-expanded="false"] .exp-collapse__caret { transform: rotate(-90deg); }
/* Collapsed keeps the subtotal (tfoot) visible; only the rows + column header hide (request 0120 update) */
.table-wrap.is-collapsed tbody { display: none; }
.table-wrap.is-collapsed .exp-table__cols { display: none; }
/* Per-day subtotal rows (request 0120 §3/§5). No background of its own since
   request 155 — it also carries `.exp-subtotal`, so it takes that rule's fill and
   the two views cannot drift apart. */
.exp-day-subtotal td { font-weight: 700; }
.exp-day-subtotal--client td { border-top: 2px solid var(--stroke); }
/* Date-view day groups (request 0120 updates) */
/* The day's date is a full-width title row above that day's column titles, on
   desktop as well as mobile (request 127 §5). */
/* The same surface as a category's group header (request 155): a day and a category
   are the same kind of group, so they get the same chrome. */
.exp-day-head th { background: var(--glass); font-weight: 700; text-align: start; }
/* Each day is its own `.table-wrap`, so collapsing it reuses the category rules
   (`.table-wrap.is-collapsed` hides the tbody + column titles, keeping the
   subtotal). The old `.exp-day.is-collapsed` and `.exp-day-gap` rules went with
   the restructure — the gap between days is now the wrapper's own margin. */
/* Rental car detail (request 0121): 5px gaps between blocks, action rows, mileage table */
.car-detail { display: flex; flex-direction: column; gap: 5px; }
.car-detail > .card { margin: 0; }
.car-actions { margin-block-start: 10px; display: flex; gap: 8px; flex-wrap: wrap; }
.mileage-table .mileage-day td, .mileage-table .mileage-avg td { background: var(--glass-2); }
.badge--link { text-decoration: none; cursor: pointer; }
.badge--link:hover { text-decoration: underline; }
/* Expense row anchored from a fueling row: scroll offset + brief highlight (request 0121 update 1) */
/* The travellers under a flight or a stay on the user-level lists (request 186).
   `.chips` — one badge per person, the same treatment the single trip dashboard gives
   a trip's travellers (operator, 2026-08-25) and the same one the trips table gives a
   trip's countries. The pill, the border and the wrap all come from `.chips`; only the
   gap above the row's own value is this rule's. */
.list-travelers { margin-block-start: 4px; }

/* Landing on a linked flight or stay (request 186). `scroll-margin` keeps the card
   clear of the sticky top bar, and the outline says which one you were sent to —
   without it the page simply looks scrolled. Same treatment the expenses rows have
   carried since request 143; an outline and not a shadow, because a shadow is
   discarded under forced colours. */
.fbk-card[id^="booking-"],
.hotel-card[id^="stay-"] { scroll-margin-top: 80px; }
/* `--brand-400` and NOT `--focus-ring`: that token is a whole box-shadow
   (`0 0 0 3px rgba(...)`), so `outline: 2px solid var(--focus-ring)` expands to
   nonsense and the browser drops the declaration silently. The rule below had that
   bug since request 143 and drew nothing; it is fixed here because it is the same
   rule family and was landing on the same "nothing happened" symptom. */
.fbk-card[id^="booking-"]:target,
.hotel-card[id^="stay-"]:target {
    outline: 2px solid var(--brand-400);
    outline-offset: 2px;
}

.exp-table tbody tr[id^="exp-"] { scroll-margin-top: 80px; }
.exp-table tbody tr[id^="exp-"]:target { outline: 2px solid var(--brand-400); outline-offset: -2px; }
.exp-table tbody tr[id^="exp-"]:target td:first-child { box-shadow: inset 3px 0 0 var(--brand-400); }
.pay-cell { white-space: nowrap; }
.pay-status { font-weight: 700; }
.pay-badge { display: inline-block; font-size: 0.625rem; font-weight: 700; color: var(--on-brand); padding: 2px 6px; border-radius: 3px; margin-inline-start: 4px; }
.exp-rates-note { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rate-chip { padding: 1px 6px; border: 1px solid var(--stroke); border-radius: 999px; }
.input--color { width: 48px; height: 34px; padding: 2px; }
.input--mini { padding: 2px 4px; font-size: .8rem; width: auto; }
/* Italic carries "planned"; opacity used to drag the text under 4.5:1 (task 0161a). */
.exp-planned td { opacity: .9; font-style: italic; }
.linklike { background: none; border: 0; color: var(--brand-400); cursor: pointer; font: inherit; padding: 0 4px; text-decoration: underline; }
/* ----- Public share pages + owner share box (request 0006) ----- */
.share-page { max-width: 900px; margin: 0 auto; padding: 16px; min-height: 100vh; display: flex; flex-direction: column; }
.share-page__bar { display: flex; justify-content: flex-end; align-items: center; gap: 10px; padding-block: 8px; }
.share-lang { display: flex; gap: 4px; }
.share-lang__link { padding: 4px 8px; border-radius: var(--radius-sm); text-decoration: none; color: var(--text-muted); font-size: .9rem; }
.share-lang__link.is-active { background: var(--glass-2); color: var(--text); font-weight: 600; }
.share-page__content { flex: 1; }
.share-title { font-size: 1.5rem; margin-block: 12px 18px; }
.share-page__footer { margin-block-start: 24px; padding-block: 16px; border-block-start: 1px solid var(--stroke); text-align: center; color: var(--text-muted); font-size: .85rem; }
.share-box { margin-block: 20px 8px; }
.share-box__summary { cursor: pointer; font-weight: 600; }
.share-box__body { margin-block-start: 10px; }
.share-box__link { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-block-start: 8px; }
.share-box__link .input { flex: 1; min-width: 200px; font-family: monospace; font-size: .85rem; }
.share-box__link .inline { display: inline-flex; margin: 0; }
/* Plan action icons on one line (bug 0007 §2) */
.plan-actions { white-space: nowrap; }
.plan-actions .inline { display: inline-flex; margin: 0; }
/* ----- All-trips cards with inline management (bug 0008 §3) ----- */
.trip-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; margin-block: 12px; }
.trip-card { display: flex; flex-direction: column; gap: 6px; }
.trip-card__name { font-size: 1.15rem; font-weight: 700; text-decoration: none; color: var(--text); }
.trip-card__name:hover { color: var(--brand-400); }
.trip-card__meta { margin: 0; }
.trip-card__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-block-start: auto; padding-block-start: 6px; }
.trip-card__actions .inline { display: inline-flex; margin: 0; }
/* ----- Trip dashboard redesign (bug 0005 #4) ----- */
.dash-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; margin-block: 16px; }
.dash-card { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; padding: 16px; background: var(--glass); border: 1px solid var(--stroke); border-radius: var(--radius); text-decoration: none; color: inherit; transition: border-color .15s, transform .15s; }
.dash-card:hover { border-color: var(--brand-400); transform: translateY(-2px); }
.dash-card__count { font-size: 1.9rem; font-weight: 700; line-height: 1; color: var(--brand-400); }
.dash-card__count--view { color: var(--text-muted); }
.dash-card__label { font-size: .9rem; color: var(--text); text-transform: capitalize; }
.dash-expenses { margin-block: 16px; }
.dash-expenses__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-block-end: 10px; }
/* Travelers / countries cards (request 135 §3/§4): the expenses card's chrome.
   The names inside are `.chips`, shared with the trips list, so nothing about the
   chip itself is repeated here. */
.dash-list { margin-block: 16px; }
.dash-list__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-block-end: 10px; }
.dash-totals { display: flex; flex-wrap: wrap; gap: 10px 18px; margin-block-end: 8px; }
.dash-total { display: flex; align-items: center; gap: 6px; }
.dash-total__dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
.dash-total__label { color: var(--text-muted); }
.dash-total__val { font-weight: 600; font-variant-numeric: tabular-nums; }
.dash-total--grand { margin-inline-start: auto; font-size: 1.05rem; }
.dash-total--grand .dash-total__val { color: var(--brand-400); }
.dash-cats { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
/* Name + total only, no bar, with a rule under each one — including the last,
   where it also separates the list from the pie (request 127 §11 + verification). */
.dash-cat {
    display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 10px;
    padding-block-end: 6px; border-block-end: 1px solid var(--stroke);
}
.dash-cat__name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dash-cat__val { font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Share per category (request 127 §11). The disc is a conic-gradient — no chart
   library, per the framework-free stack — and the legend beside it carries the
   same information as text. */
.dash-pie { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; margin-block-start: 16px; }
.dash-pie__chart { inline-size: 160px; block-size: 160px; border-radius: 50%; flex: none; box-shadow: inset 0 0 0 1px var(--hairline); }
.dash-pie__legend { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.dash-pie__item { display: flex; align-items: center; gap: 8px; font-size: .9rem; }
.dash-pie__swatch { inline-size: 12px; block-size: 12px; border-radius: 3px; flex: none; border: 1px solid var(--stroke); }
.dash-pie__pct { color: var(--text-muted); font-variant-numeric: tabular-nums; }
.dash-meta { margin-block-start: 12px; }
/* Icon action buttons in tables (bug 0005 #2/#3) */
.icon-btn { background: none; border: 0; cursor: pointer; font-size: 1rem; line-height: 1; padding: 2px 4px; border-radius: var(--radius-sm); }
.icon-btn:hover { background: var(--hairline); }
.exp-actions { white-space: nowrap; text-align: center; }
.exp-note { margin-block-start: 2px; }

/* Travel — plan (task 0033) */
.plan-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.plan-table th, .plan-table td { padding: 7px 9px; border: 1px solid var(--stroke); text-align: start; vertical-align: top; }
.plan-table thead th { background: var(--glass-2); font-weight: 600; }
.plan-day { color: var(--cat-fg, var(--text)); font-weight: 600; white-space: nowrap; }
.plan-day__n { display: block; }
.plan-day__dow { display: block; font-size: .8rem; }
.plan-day__date { display: block; font-size: .8rem; font-variant-numeric: tabular-nums; }
/* Small "add item on this day" icon under the date. */
.plan-day__add { display: inline-flex; align-items: center; justify-content: center; margin-block-start: 4px; width: 22px; height: 22px; padding: 0; font-size: .95rem; line-height: 1; cursor: pointer; color: var(--brand-400); background: var(--glass); border: 1px solid var(--stroke); border-radius: 50%; }
.plan-day__add:hover { background: var(--brand-400); color: var(--on-brand); border-color: var(--brand-400); }
.plan-row.is-dragging { opacity: .5; }
.plan-actions { white-space: nowrap; }
.drag-handle { cursor: grab; color: var(--text-muted); margin-inline-end: 4px; }
/* "open map" link under the plan location text (request 0088). */
.plan-map-link { display: block; margin-block-start: 2px; font-size: .8rem; color: var(--brand-400); }
/* Preserve line breaks entered in the plan item's info/drive textarea (request 0082). */
.plan-info { white-space: pre-line; }
/* "Sleep in …" divider row between plan days (bug 0004 #4). */
.plan-sleep td { background: var(--glass-2); font-style: italic; color: var(--text); padding: 4px 10px; }

/* Travel — map (task 0034) */
#travel-map { height: 62vh; min-height: 360px; border-radius: var(--radius); border: 1px solid var(--stroke); }
.map-pin__dot { display: block; width: 16px; height: 16px; border-radius: 50%; border: 2px solid var(--panel); box-shadow: 0 0 0 1px var(--stroke-2); }
/* Map day-filter buttons (bug 0004 #2) */
.map-days { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-block-end: 10px; }
.map-day-btn.is-active { background: var(--brand-400); color: var(--on-brand); border-color: var(--brand-400); }

/* Travel — itinerary (task 0035) */
.itin-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.itin-table th, .itin-table td { padding: 8px 10px; border: 1px solid var(--stroke); text-align: start; vertical-align: top; color: var(--cat-fg, var(--text)); }
.itin-table thead th { background: var(--glass-2); color: var(--text); font-weight: 600; }
.itin-icon { text-align: center; font-size: 1.2rem; }
.itin-row--flight-0 td { background: #F0EFFE; }
.itin-row--flight-1 td { background: #E4E0FC; }
.itin-row--hotel-0 td { background: #FEF4EA; }
.itin-row--hotel-1 td { background: #FAE8D4; }

/* Travel — settings (task 0036) */
.settings-form { margin-block-end: 16px; }
.notice { padding: 10px 14px; border-radius: var(--radius-sm); margin-block-end: 14px; }
.notice--ok { background: var(--success-bg, var(--info-bg)); color: var(--success); border: 1px solid var(--success-border); }
/* A confirmation that takes itself away (task 0174d). `notice.js` adds the class
   three seconds after load and REMOVES the element when the fade ends, so the card it
   sits in — height-capped since task 0174c — is not left holding an empty box. Under
   reduced motion the script removes the element without ever adding the class, which
   is why there is no media query here. */
.notice.is-fading { opacity: 0; transition: opacity .3s ease; }
.icon-gallery { display: flex; flex-wrap: wrap; gap: 8px; margin: 6px 0; }
.icon-choice { display: inline-flex; align-items: center; justify-content: center; padding: 6px; border: 2px solid var(--stroke); border-radius: var(--radius-sm); cursor: pointer; background: var(--input-bg); }
.icon-choice input { position: absolute; opacity: 0; }
.icon-choice:has(input:checked) { border-color: var(--brand-400); box-shadow: 0 0 0 2px var(--focus-ring); }
.upload-label { display: block; margin-block-start: 8px; font-size: .85rem; color: var(--text-muted); }
.settings-actions { display: flex; gap: 10px; flex-wrap: wrap; }

/* Shared-plan view toggle: hidden on desktop; the mobile override below (inside
   the phone media query, which comes later in source order) reveals it. */
.plan-view-toggle { display: none; align-items: center; gap: 6px; margin-block-end: 10px; }

/* ============================================================================
   Bug 0010 — mobile-only responsive views (phones). Desktop is untouched.
   ========================================================================== */
@media (max-width: 640px) {
    /* Companions: the row's buttons drop to a line of their own — the second, or
       the third when the companion has a note (request 127 §10). */
    .companion-item .list-item__spacer { display: none; }
    .companion-item .list-item__actions { flex-basis: 100%; justify-content: flex-start; }
    .companion-item__note { order: 1; }
    .companion-item .list-item__actions { order: 2; }

    /* §1 Expenses: stacked totals, one payment type per row */
    .exp-summary { flex-direction: column; align-items: stretch; }
    .exp-summary .sep { display: none; }
    .exp-summary__item { display: flex; align-items: center; gap: 6px; }

    /* §1 Expenses: the WHOLE table becomes block so the header, the row cards and
       the footer all span the same width (no table-layout sizing at all). */
    .exp-table, .exp-table thead, .exp-table tbody, .exp-table tfoot, .exp-table tr { display: block; width: auto; }
    /* Higher specificity than `.exp-table tr` above so the column header stays hidden. */
    .exp-table tr.exp-table__cols { display: none; }
    /* Category header stacks: title on its own row, actions on the next row */
    .exp-cat-head th { display: block; width: auto; }
    .exp-cat-head__actions { text-align: start; }
    .exp-cat-head__actions-inner { justify-content: flex-start; flex-wrap: wrap; }
    /* Data rows become cards; each cell is label: value */
    .exp-table tbody tr { border: 1px solid var(--stroke); border-radius: var(--radius-sm); margin-block-end: 10px; }
    /* Same breathing room above the first item as below the last one, which gets
       it from the row margin (request 127 §7). */
    [data-exp-cat] .exp-table tbody tr:first-child { margin-block-start: 10px; }
    /* Price and currency read as one value, not two stacked lines (request 127 §8).
       They stay separate cells for the desktop table; the currency rides along on
       the price cell here and its own cell steps aside.
       The cell is a flex row, so `space-between` would fling the currency to the
       far edge away from the number (verification §8) — push the label out with an
       auto margin instead and the pair stays together at the end edge, one space
       apart (`pre` keeps that space from collapsing). */
    /* `gap: 0` matters: the cell's 12px flex gap would otherwise sit between the
       number and the currency, which is the gap the user reported. The label is
       held apart by its auto margin instead. */
    .exp-table tbody td[data-cur] { justify-content: flex-start; gap: 0; }
    .exp-table tbody td[data-cur]::before { margin-inline-end: auto; }
    .exp-table tbody td[data-cur]::after { content: ' ' attr(data-cur); white-space: pre; }
    .exp-table tbody td.exp-cur-cell { display: none; }
    .exp-table tbody td { display: flex; justify-content: space-between; gap: 12px; border: 0; border-block-end: 1px solid var(--hairline); padding: 6px 10px; text-align: end; white-space: normal; }
    .exp-table tbody td:last-child { border-block-end: 0; }
    .exp-table tbody td::before { content: attr(data-label); font-weight: 600; color: var(--cat-fg, var(--text)); text-align: start; margin-inline-end: 8px; }
    .exp-table tbody td:empty { display: none; }
    /* Expense note on its own full-width line within the row (request 0120 §6) */
    .exp-table tbody td.exp-desc-cell { flex-wrap: wrap; }
    .exp-desc-cell .exp-note { flex-basis: 100%; width: 100%; text-align: start; margin-block-start: 4px; }
    .exp-table .exp-actions { justify-content: flex-end; }
    /* Subtotal row: label and amount on one line, full width */
    .exp-table tr.exp-subtotal { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 6px 10px; }
    .exp-subtotal td { border: 0; padding: 0; }
    .exp-subtotal td:empty { display: none; }
    /* Date-view day header as a collapsible group header (request 0120 update).
       The day's own `.exp-day-wrap` now draws the border and the rounding, so the
       header is just the filled bar at the top of that card. */
    .exp-day-head { display: block; background: var(--glass-2); font-weight: 700; }
    /* Header + arrow start-aligned; specificity must beat `.exp-table tbody td` (request 0120 update 2) */
    .exp-table tr.exp-day-head th { display: block; border: 0; padding: 8px 10px; text-align: start; }
    /* Neither row carries a `data-label`, so neither should get the stacked view's
       "label:" prefix — an empty one still contributes its 8px margin and pushes
       the base-currency line out of line (request 127 §6). Both now sit outside
       `tbody`, but keep the override explicit so a future markup move cannot
       quietly bring the offset back. */
    .exp-table tr.exp-day-head th::before,
    .exp-table tr.exp-day-subtotal td::before { content: none; margin-inline-end: 0; }
    .exp-day-head .exp-collapse { margin-inline-end: 6px; }
    /* Day subtotal split into full-width lines: label · date / currency breakdown / base conversion (request 0120 update).
       Start-aligned, and the background fills the whole rounded row (request 0120 update 2). */
    .exp-table tr.exp-day-subtotal { display: block; background: var(--glass-2); overflow: hidden; padding-block: 4px; }
    .exp-table tr.exp-day-subtotal td { display: block; width: auto; text-align: start; padding: 2px 10px; border: 0; background: transparent; }
    .exp-table tr.exp-day-subtotal td:empty { display: none; }
    .exp-day-subtotal .exp-dsub__label, .exp-day-subtotal .exp-dsub__bycur { display: block; }
    .exp-day-subtotal.exp-dsub--samecur .exp-dsub__amount { display: none; }

    /* §2 Hotels: smaller room Edit/Delete buttons kept on one line */
    .room-list__actions { display: inline-flex; align-items: center; gap: 4px; margin-inline-start: auto; }
    .room-list__actions .inline { display: inline-flex; margin: 0; }
    .room-list__actions .btn { padding: 3px 8px; font-size: .72rem; }

    /* §3 Travel plan: day as a header row, then each event's details vertically.
       Gated behind .plan--stacked so the shared plan can toggle it (bug 0011 §2);
       the user's own plan table always has the class. */
    .plan-table.plan--stacked thead { display: none; }
    .plan-table.plan--stacked, .plan--stacked tbody, .plan--stacked tr, .plan--stacked td { display: block; width: auto; }
    .plan--stacked tr.plan-row { border: 1px solid var(--stroke); border-radius: var(--radius-sm); margin-block-end: 8px; overflow: hidden; }
    .plan--stacked td.plan-day { padding: 6px 10px; }
    .plan--stacked .plan-day__n { font-weight: 700; }
    .plan--stacked .plan-day__n, .plan--stacked .plan-day__dow, .plan--stacked .plan-day__date { display: inline; margin-inline-end: 8px; }
    .plan--stacked .plan-row td[data-label] { display: flex; justify-content: space-between; gap: 12px; padding: 4px 10px; text-align: end; white-space: normal; }
    .plan--stacked .plan-row td[data-label]::before { content: attr(data-label); font-weight: 600; color: var(--text-muted); text-align: start; }
    .plan--stacked .plan-row td:empty { display: none; }
    .plan--stacked .plan-actions { padding: 6px 10px; text-align: end; }
    .plan--stacked .plan-actions .drag-handle { display: none; }
    .plan--stacked .plan-actions .inline { display: inline-flex; margin: 0; }
    .plan--stacked .plan-sleep td { padding: 6px 10px; }

    /* Shared-plan view toggle: only visible on mobile (bug 0011 §2) */
    .plan-view-toggle { display: inline-flex; }
}

/* ----- Flight bookings editor + list (request 0113) ----- */
.fbk-list { display: grid; gap: 14px; }
.fbk-card__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.fbk-card__title { font-size: 1.05rem; margin: 0; }
/* Segments table (request 0115) */
/* ── Flight segments as boarding-pass cards (request 141 follow-up) ──────────
   A leg is a journey between two places, not a table row: origin on one side,
   destination on the other, an arrow between them. That reads without column
   headings to decode, which is why the table it replaces is gone. */
.fbk-fnum { font-weight: 600; }
.seg-list { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.seg-card { background: var(--glass); border: 1px solid var(--stroke); border-radius: var(--radius-sm); padding: 10px 12px; }
.seg-card--warn { border-color: var(--stroke-2); }
.seg-card__head { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; flex-wrap: wrap; }
.seg-card__ident { margin: 0; }
.seg-card__actions { display: inline-flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.seg-card__actions .inline { display: inline-flex; margin: 0; }
/* The route: two ends with the arrow taking whatever space is left. */
.seg-card__route { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 12px; margin-block: 10px 4px; }
.seg-card__end { display: flex; flex-direction: column; min-width: 0; }
.seg-card__end--arr { text-align: end; }
.seg-card__time { font-size: 1.25rem; font-weight: 600; font-variant-numeric: tabular-nums; line-height: 1.2; }
.seg-card__code { font-weight: 600; letter-spacing: 0.04em; }
.seg-card__place { color: var(--text-muted); font-size: 0.85rem; overflow-wrap: anywhere; }
.seg-card__date { color: var(--text-muted); font-size: 0.8rem; margin-block-start: 2px; }
.seg-card__mid { display: flex; flex-direction: column; align-items: center; gap: 2px; min-width: 92px; }
.seg-card__dur { color: var(--text-muted); font-size: 0.78rem; font-variant-numeric: tabular-nums; }
/* The rule carries the plane, so the two never drift apart. Mirrors under RTL
   because the glyph is flipped with the writing direction, not positioned. */
.seg-card__rule { position: relative; display: block; width: 100%; border-block-start: 1px solid var(--stroke); }
.seg-card__plane { position: absolute; inset-inline-end: -2px; inset-block-start: -0.7em; font-size: 0.9rem; line-height: 1; color: var(--text-muted); }
[dir="rtl"] .seg-card__plane { transform: scaleX(-1); }
/* Services live under the leg they belong to, separated by a hairline. */
.seg-card__svcs { border-block-start: 1px solid var(--hairline); padding-block-start: 8px; margin-block-start: 8px; }
.seg-svc { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 4px; font-size: 0.9rem; }
.seg-svc__name { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.seg-svc__amt { font-variant-numeric: tabular-nums; color: var(--text-muted); white-space: nowrap; }
.seg-svc__act { display: inline-flex; align-items: center; gap: 4px; }
.seg-svc__act .inline { display: inline-flex; margin: 0; }
.seg-card__foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin: 4px 0 0; }
.seg-card__total { color: var(--text-muted); font-size: 0.85rem; }
/* Hide number-input up/down spinners on price fields (request 0116) */
.no-spin { -moz-appearance: textfield; appearance: textfield; }
.no-spin::-webkit-outer-spin-button, .no-spin::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
/* Booking-level services: divider from the segment table above (request 0119) */
.fbk-card__svc { margin-block-start: 10px; padding-block-start: 10px; border-block-start: 1px solid var(--stroke); display: flex; flex-wrap: wrap; gap: 6px; }
/* Nested popup stacking (request 0114): booking 50 < sub 60 < detail 70 < service 80 */
[data-sub-modal] { z-index: 60; }
[data-detail-modal] { z-index: 70; }
#service-modal { z-index: 80; }
.form-grid--2 { grid-template-columns: 1fr 1fr; }
@media (max-width: 640px) {
    .form-grid--2 { grid-template-columns: 1fr; }
}

/* Flight booking passengers: one row each with its cost (request 128 §2). */
.fbk-pax__name { font-weight: 600; }

/* An editable service row: badge + its edit/remove buttons (request 128 §2). */
.fbk-svc-row { display: inline-flex; align-items: center; gap: 4px; margin-inline-end: 10px; }
.fbk-svc-row .inline { display: inline-flex; margin: 0; }

/* Flight totals, per currency (request 128 §2). */
.fbk-total { display: inline-block; margin-inline-start: 10px; font-size: .85rem; color: var(--text-muted); }
.fbk-total--booking { display: block; margin: 10px 0 0; font-size: .95rem; color: var(--text); }
@media (max-width: 640px) {
    /* Boarding-pass cards stack their route ends instead of sitting side by side. */
    .seg-card__route { grid-template-columns: 1fr; gap: 6px; }
    .seg-card__end--arr { text-align: start; }
    /* Narrow enough that the time and the code fit together, so they share a line
       and the airport name gets the next one to itself rather than being squeezed
       beside them (request 141 follow-up). */
    .seg-card__end { display: grid; grid-template-columns: auto auto; justify-content: start; align-items: baseline; column-gap: 8px; }
    .seg-card__place, .seg-card__date { grid-column: 1 / -1; }
    .seg-card__mid { flex-direction: row; align-items: center; min-width: 0; gap: 8px; }
    .seg-card__rule { width: 40px; }
    .fbk-total { margin-inline-start: 0; display: block; margin-block-start: 4px; }
}
/* ============================================================
   The Geode restyle — theme switch, base page, and the components
   introduced with it (request 130, tasks 0130a–0130d).

   A SECTION boundary, not a generation boundary. The rules above
   this line came from the v1 sheet when task 0130b folded it into
   this file and recoloured it through the tokens; the rules below
   were written for Geode. Both halves style the current app and
   neither is deprecated — see the file header.
   ============================================================ */

/* ---- base ---------------------------------------------------- */
/* box-sizing is reset once, at the top of this file. */

html { block-size: 100%; }

body {
    margin: 0;
    min-block-size: 100%;
    background: var(--bg);
    color: var(--text);
    font-family: var(--font);
    font-size: 0.9375rem;
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
}

/* Cross-fade the theme change, but never the first paint — theme_v2.js adds
   .theme-ready once the document is parsed. */
html.theme-ready body,
html.theme-ready .theme-switch,
html.theme-ready .theme-switch .ts-knob {
    transition: background-color .2s ease, color .2s ease, border-color .2s ease;
}
@media (prefers-reduced-motion: reduce) {
    html.theme-ready body,
    html.theme-ready .theme-switch,
    html.theme-ready .theme-switch .ts-knob { transition: none; }
}

a { color: var(--link, var(--brand-400)); }

/* Skip link — must stay usable in both themes. */
.skip-link {
    position: absolute; inset-inline-start: -9999px; inset-block-start: 0;
    background: var(--glass); color: var(--text);
    padding: 10px 14px; border-radius: var(--r-md); z-index: 100;
}
.skip-link:focus { inset-inline-start: 8px; inset-block-start: 8px; outline: 2px solid var(--focus-ring); }

/* ---- theme switch (request 130) ------------------------------
   A real switch: a track, a sliding knob and both icons visible, so
   it reads as two states rather than one ambiguous glyph.
   aria-checked="true" means dark is on. */
.theme-switch {
    position: relative;
    inline-size: 62px; block-size: 32px;
    padding: 0; flex: none;
    border: 1px solid var(--stroke);
    border-radius: 999px;
    background: var(--sunken);
    cursor: pointer;
}
.theme-switch:hover { border-color: var(--stroke-2); }
.theme-switch:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

.theme-switch .ts-knob {
    position: absolute; inset-block-start: 3px; inset-inline-start: 3px;
    inline-size: 24px; block-size: 24px;
    border-radius: 50%;
    background: var(--grad-brand, var(--brand-500));
    transition: translate .2s ease;
}
/* The knob slides toward the end of the line, so it reverses under RTL. */
:root[data-theme="dark"] .theme-switch .ts-knob { translate: 28px 0; }
html[dir="rtl"][data-theme="dark"] .theme-switch .ts-knob { translate: -28px 0; }

.theme-switch .ts-icons {
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: space-between;
    padding-inline: 7px;
    pointer-events: none;
}
.theme-switch svg { inline-size: 15px; block-size: 15px; }
.theme-switch .ts-sun { color: var(--on-brand, #fff); }
.theme-switch .ts-moon { color: var(--text-muted); }
:root[data-theme="dark"] .theme-switch .ts-sun { color: var(--text-muted); }
:root[data-theme="dark"] .theme-switch .ts-moon { color: var(--on-brand, #fff); }

/* ==========================================================================
   Stored colours on a dark page (request 130, task 0130d)

   Expense categories carry colours the user chose, and the itinerary stripes
   are fixed pastels. Both were picked against a WHITE page: left alone on a
   dark one they stop being an accent and become a bright slab floating on ink.

   The agreed treatment (2026-08-03) is to keep the hue and drop the intensity,
   so the colour still identifies the category but sits on the page instead of
   glowing on it. Only dark is touched; light keeps the flat category fill it
   already had.

   `!important` is not decoration here: the category colour arrives as an inline
   style on the row (the view also emits it as `--cat` so it can be mixed), and
   an inline declaration outranks a stylesheet unless the stylesheet insists.
   ========================================================================== */
/* Data rows only, since request 155. The group header and the subtotal stopped
   carrying a category colour, so they have no `--cat` to blend — and because this
   declaration is `!important`, leaving them in would have blended them to
   `--glass-2` and silently overridden their new theme fills in dark mode alone. */
:root[data-theme="dark"] .exp-table tbody tr[data-exp-row] {
    background: color-mix(in srgb, var(--cat, transparent) 16%, var(--glass-2)) !important;
}

/* Text on those fills follows the theme rather than the hard-coded navy that
   only ever suited a pastel. */
:root[data-theme="dark"] .exp-table tbody td { color: var(--text); }

/* The travel plan's day column — the same stored-pastel problem, missed by the
   0130e sweep (request 134). Its fill is an inline style like the expense rows',
   and with no `--cat-fg` set the text fell back to `--text`: near-white on a near
   -white pastel, about 1.05:1, i.e. invisible. Blended down, the day and date
   read against it, and the add button drops the light-surface colours it was
   hard-coded to. */
:root[data-theme="dark"] .plan-day {
    background: color-mix(in srgb, var(--cat, transparent) 16%, var(--glass-2)) !important;
    color: var(--text);
}
:root[data-theme="dark"] .plan-day__add {
    background: var(--glass-2);
    border-color: var(--stroke-2);
    color: var(--text);
}
:root[data-theme="dark"] .plan-day__add:hover {
    background: var(--brand-400);
    border-color: var(--brand-400);
    color: var(--on-brand);
}

/* Itinerary row stripes — the same problem, fixed palette rather than stored. */
:root[data-theme="dark"] .itin-row--flight-0 td { background: color-mix(in srgb, #F0EFFE 12%, var(--glass-2)); }
:root[data-theme="dark"] .itin-row--flight-1 td { background: color-mix(in srgb, #E4E0FC 16%, var(--glass-2)); }
:root[data-theme="dark"] .itin-row--hotel-0  td { background: color-mix(in srgb, #FEF4EA 12%, var(--glass-2)); }
:root[data-theme="dark"] .itin-row--hotel-1  td { background: color-mix(in srgb, #FAE8D4 16%, var(--glass-2)); }

/* The dashboard pie keeps FULL saturation in both themes: dimmed slices stop
   being distinguishable from each other, which is the only thing a pie has to
   do. Its swatches are inline styles and are deliberately left alone. */

/* ==========================================================================
   Landing shell: let the Geode pages own their layout (request 130, task 0130g)

   `components_v2.css` loads app-wide, so the ported pages get their own
   `.auth-*` rules — but only the ones the example actually declares. The
   `.auth-card { max-width: 380px }` and `.auth-wrap { padding: 24px }` rules
   earlier in this file have no counterpart there, so they win and squeeze the
   card to 380px where the example gives it 752px.

   Scoped to `.landing`, which is only the ported pages, so the centred
   single-card login used everywhere else is untouched.
   ========================================================================== */
.landing .auth-wrap { display: block; place-items: initial; padding-inline: 0; min-block-size: calc(100vh - 64px); }
.landing .auth-card { max-inline-size: none; width: auto; }
.landing .auth-card h1 { font-size: clamp(1.5rem, 3vw, 2rem); margin: 0 0 8px; }

/* Social sign-up, present but not yet wired (request 130, task 0130g).
   Reads as deliberately unavailable rather than broken. */
.landing .social-btn.is-pending { opacity: .55; cursor: not-allowed; }
.landing .social-note { margin: -14px 0 20px; text-align: center; font-size: .82rem; }

/* Login: a single centred card in a narrow container, as the example (request 130). */
.landing .auth-narrow { max-inline-size: 520px; }
.landing .auth-foot-alt { text-align: center; font-size: 0.875rem; color: var(--text-muted); margin-block-start: 4px; }
.landing .auth-foot-alt a { color: var(--brand-400); font-weight: 600; }

/* ==========================================================================
   Profile page, Geode design (request 131, task 0131b)

   The story asks for the example's card sizes and the margins between its
   elements, so the numbers below are lifted from `variant-8-geode/css/pages.css`
   rather than approximated: 1.6fr/1fr split at 18px, 22px card padding, a 16px
   form grid, and the example's own 1080px / 760px collapse points.

   `.split-2`, `.form-grid .full` and `.empty-note` are the example's shared
   primitives and are global — other Geode pages will want them. Everything that
   would change a control the rest of the app already uses (`.card` padding, the
   `.form-grid` gap) is scoped to `.profile-v2` so no existing page moves.
   ========================================================================== */
.split-2 { display: grid; grid-template-columns: 1.6fr 1fr; gap: 18px; align-items: start; }
@media (max-width: 1080px) { .split-2 { grid-template-columns: 1fr; } }

.form-grid .full { grid-column: 1 / -1; }
.empty-note { padding: 40px 20px; text-align: center; color: var(--text-muted); font-size: 0.875rem; }

.profile-v2 .card { padding: 22px; }
.profile-v2 .card > h2 { font-size: 0.9688rem; font-weight: 700; margin: 0 0 14px; }
.profile-v2 .form-grid { grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 760px) { .profile-v2 .form-grid { grid-template-columns: 1fr; } }
/* Task 0131k — grid items stretch to the tallest cell in their row, and `.field`
   is itself a grid, so its last row (the control) absorbs the slack. The currency
   cell carries a hint and the language cell does not, which left the language
   select about twice the height of the one beside it. Top-align the fields so
   each keeps its natural height. Scoped: `.form-grid` also serves the travel
   cars, expenses and flights forms, verified under their own tasks. */
.profile-v2 .form-grid .field { align-self: start; }
/* Request 150 — the profile itself is two cards "one under another", so the form
   column is a stack. That is the *left* column: the page keeps its `.split-2` shell
   and the workspace card keeps its aside (task 0150d — 0150b read the requirement as
   applying to the whole page and moved a card the story never mentions). The 18px
   gap matches `.split-2`, so the spacing reads the same in both directions. */
.profile-v2__form { display: grid; gap: 18px; align-content: start; }
.profile-v2__aside { display: grid; gap: 18px; }
.profile-v2__actions { display: flex; gap: 10px; justify-content: flex-end; }

/* The aside is EMPTY and reserved since task 0176e — the People card that filled it
   is gone (`2-business/contacts/contacts.md` §6) and what replaces it has not been
   decided.

   Nothing is needed to hold the column open: `.split-2` is `1.6fr 1fr`, and a grid
   track named in the template exists whether or not anything sits in it. So the cards
   beside it keep exactly the width they had, and this file needed no change for that —
   which is the whole reason the empty `<div>` is worth keeping.

   Task 0174c's height-capping rules went with the card. They existed to stop a growing
   list of groups from making the page taller than its left column; an empty div has no
   height to cap. */

/* Selects: the example styles them identically to its inputs, and our
   `.field input` already matches that rule — this is the missing half. */
.field select {
    width: 100%;
    padding: 11px 13px;
    border: 1px solid var(--stroke);
    border-radius: var(--r-md);
    background: var(--input-bg);
    color: var(--text);
    font: inherit;
    font-size: 0.9375rem;
}
/* The example suppresses the outline and leans on `--focus-ring` alone, but that
   token is a 22%-alpha wash measuring 1.3:1 against the card — under the 3:1 the
   spec requires of a focus indicator. A real outline is kept on top of the ring
   rather than replaced by it (spec 04-accessibility: never suppress focus). */
.field select:focus-visible {
    outline: 2px solid var(--brand-400);
    outline-offset: 2px;
    border-color: var(--brand-400);
    box-shadow: var(--focus-ring);
}
.field select[aria-invalid="true"] { border-color: var(--danger); box-shadow: var(--danger-ring); }

/* A disabled control has to read as deliberately unavailable, and the reason is
   always spelled out in adjacent text — the styling alone is not the message
   (spec 04-accessibility). */
.profile-v2 .field input:disabled,
.profile-v2 .field select:disabled { opacity: .6; cursor: not-allowed; }

/* Those explanations carry the meaning the disabled styling cannot, so they have
   to be readable: the shared `.hint` uses `--text-dim`, which measures 3.69:1 in
   light and 3.95:1 in dark — under AA for 12.5px body text. `--text-muted` is the
   same role one step darker and clears it at 6.64:1 / 7.22:1. Scoped rather than
   changed globally: the landing pages have the same failing token, but they were
   verified separately and fixing them belongs with their own task. */
.profile-v2 .hint { color: var(--text-muted); }

/* Phones: the actions stop hugging the end and take the full width, matching
   how the rest of the app's forms behave at this width (spec 03-ui). */
@media (max-width: 640px) {
    .profile-v2__actions { justify-content: stretch; }
    .profile-v2__actions .btn { flex: 1; }
}

/* The error variant of `.notice`; `--ok` already exists. Bordered and prefixed
   by its role, never colour alone. */
.notice--error { background: var(--danger-bg, var(--glass-2)); color: var(--danger); border: 1px solid var(--danger-border, var(--stroke)); }

/* "Shared with you by X" at the top of an object the reader does not own (request 195,
   task 0195e). Informational, not an error and not a success — it uses the neutral
   `.notice` ground with the shared badge's own token pair so it agrees with the badges
   on the list page it was reached from. The icon is decoration; the sentence carries
   the meaning, per spec 04. */
.notice--shared {
    display: flex; align-items: center; gap: 10px;
    background: var(--badge-shared-bg); color: var(--badge-shared-fg);
    border: 1px solid var(--stroke);
}
.notice--shared .notice__icon { display: inline-flex; flex: none; }
.notice--shared svg { width: 18px; height: 18px; }
.notice--shared strong { font-weight: 600; }

/* ==========================================================================
   Geode list + modal primitives (request 131 §3, task 0131d)

   Ported from the variant's `pages.css` / `app.css` at their own values, because
   the story asks for the example's exact styles. These are new class names —
   the existing `.modal` container and the whole `.modal__*` vocabulary are left
   untouched, so no modal already in the app moves.

   One rule is deliberately NOT copied: the example fades `.modal-card` in from
   `opacity: 0` via `.modal.open`. Our modals open by removing `hidden` and never
   get an `.open` class, so that rule would leave every card invisible.
   ========================================================================== */
.panel {
    background: var(--glass);
    border: 1px solid var(--stroke);
    border-radius: var(--r-lg);
    backdrop-filter: blur(10px);
    overflow: hidden;
}

.rows { display: block; }
/* `display: block` above is an author rule and outranks the UA's
   `[hidden] { display: none }`, so a filtered-out group would stay on screen —
   and in the tab order — while reporting itself hidden (request 131 §4). */
.rows[hidden] { display: none; }
.row {
    display: grid;
    gap: 14px;
    align-items: center;
    padding: 12px 16px;
    border-block-end: 1px solid var(--hairline);
    transition: background .14s;
}
/* `display: grid` above outranks the UA's `[hidden]` rule, so a filtered-out row
   would stay on screen without this (request 154; the same trap `.trip[hidden]`
   already covers on the trips table). */
.row[hidden] { display: none; }
.row:last-child { border-block-end: 0; }
.row:hover { background: var(--row-hover); }
.row h3 { font-size: 0.9063rem; font-weight: 600; margin: 0; }
.row small, .row .meta { display: block; font-size: 0.7813rem; color: var(--text-muted); margin-block-start: 2px; }
@media (prefers-reduced-motion: reduce) { .row { transition: none; } }

.row-head {
    display: grid;
    gap: 14px;
    padding: 9px 16px;
    background: var(--sunken);
    border-block-end: 1px solid var(--stroke);
    font-size: 0.6563rem; font-weight: 700;
    letter-spacing: .08em; text-transform: uppercase;
    /* The example dims this with `--text-dim`, which does not clear AA at
       10.5px; `--text-muted` is the same role one step darker. */
    color: var(--text-muted);
}
.row-head span:last-child, .row > .row-actions { justify-self: end; }
.row-actions { display: flex; gap: 6px; }

/* Column templates, so a page picks a shape instead of redefining one. */
.cols-name-2 { grid-template-columns: minmax(0,2fr) minmax(0,1fr) 92px; }
.cols-name-3 { grid-template-columns: minmax(0,1.8fr) minmax(0,1.1fr) minmax(0,1fr) 92px; }
.cols-name-4 { grid-template-columns: minmax(0,1.6fr) minmax(0,1fr) minmax(0,1fr) 110px 92px; }

.mark {
    inline-size: 34px; block-size: 34px; flex: none;
    border-radius: var(--r-md);
    display: grid; place-items: center;
    font-size: 0.8125rem; font-weight: 700;
    background: var(--info-bg);
    border: 1px solid var(--info-border);
    color: var(--brand-400);
}
.mark.round { border-radius: 50%; }
.with-mark { display: flex; align-items: center; gap: 11px; min-width: 0; }

.icon-action {
    inline-size: 30px; block-size: 30px;
    border: 1px solid var(--stroke);
    border-radius: var(--r-sm);
    background: transparent;
    color: var(--text-muted);
    display: grid; place-items: center;
    cursor: pointer;
    transition: color .14s, border-color .14s, background .14s;
}
.icon-action svg { inline-size: 15px; block-size: 15px; }
.icon-action:hover { color: var(--text); border-color: var(--stroke-2); background: var(--glass-2); }
/* Delete carries the fill at rest, not only under the pointer (request 134): it
   is the same red as the flight-booking button, which is the treatment the story
   names as the one every delete should match. `--on-danger` is picked per theme
   to sit on it — white on light's red, near-black on dark's salmon. */
.icon-action.danger {
    background: var(--danger);
    border-color: transparent;
    color: var(--on-danger);
}
.icon-action.danger:hover { background: var(--danger-hover); border-color: transparent; color: var(--on-danger); }
@media (prefers-reduced-motion: reduce) { .icon-action { transition: none; } }

.empty { padding: 56px 20px; text-align: center; }
.empty h3 { font-size: 1rem; font-weight: 700; margin: 0 0 6px; }
.empty p { color: var(--text-muted); font-size: 0.875rem; margin: 0; }

/* Modal interior, the example's. The container (`.modal`) already exists. */
.modal-card {
    position: relative;
    width: 100%;
    max-width: 520px;
    max-height: calc(100vh - 48px);
    overflow-y: auto;
    background: var(--panel);
    border: 1px solid var(--stroke-2);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-lg);
}
.modal-card.narrow { max-width: 420px; }
.modal-head {
    display: flex; align-items: flex-start; gap: 16px;
    padding: 20px 22px;
    border-block-end: 1px solid var(--stroke);
}
.modal-head h2 { font-size: 1.0625rem; font-weight: 700; margin: 0; }
.modal-close {
    margin-inline-start: auto;
    inline-size: 30px; block-size: 30px;
    border: 0; border-radius: var(--r-sm);
    background: transparent; color: var(--text-muted);
    display: grid; place-items: center; flex: none; cursor: pointer;
}
.modal-close:hover { background: var(--glass-2); color: var(--text); }
.modal-close svg { inline-size: 16px; block-size: 16px; }
.modal .form { padding: 22px; }
.modal-body { padding: 22px; }
.modal-body__text { margin: 0; color: var(--text-muted); font-size: 0.875rem; }
.modal-foot {
    display: flex; gap: 10px; justify-content: flex-end;
    padding: 16px 22px;
    background: var(--sunken-3);
    border-block-start: 1px solid var(--stroke);
    margin: 22px -22px -22px;
}
.modal-body + .modal-foot { margin: 0; }
.modal .form-error {
    padding: 10px 13px;
    border: 1px solid var(--danger);
    border-radius: var(--r-md);
    background: var(--danger-bg);
    color: var(--danger);
    font-size: 0.8125rem;
}

/* Page head with the actions pushed to the end, as the example's list pages. */
.page-head--geode { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 16px; }
.page-head--geode .head-actions { margin-inline-start: auto; }
.btn-primary svg, .btn-outline svg { inline-size: 16px; block-size: 16px; flex: none; }

/* Phones: a four-column grid cannot stay one. The header row goes, and each row
   becomes a stacked card with `label: value` per field (spec 03-ui). The labels
   are CSS-generated from the same strings as the header, set per language on the
   view's wrapper so they stay translated. */
@media (max-width: 640px) {
    .sl-panel .row-head { display: none; }
    .sl-panel .row { grid-template-columns: 1fr; gap: 8px; padding: 14px 16px; }
    .sl-panel .row > .row-actions { justify-self: start; }
    .sl-panel .sl-count::before { content: attr(data-label) ": "; color: var(--text-muted); }
    .sl-panel .meta-cell::before { content: attr(data-label) ": "; color: var(--text-muted); }
    .page-head--geode .head-actions { margin-inline-start: 0; width: 100%; }
    .page-head--geode .head-actions .btn { flex: 1; }
}

/* ==========================================================================
   Single shopping list, Geode design (request 131 §4, task 0131e)

   Reuses the panel/row primitives from task 0131d; what is added here is the
   list head (title, count, filters) and the item row's own three-column shape.
   The example's values, at their own numbers.
   ========================================================================== */
.list-head {
    display: flex; flex-wrap: wrap; align-items: center; gap: 14px;
    padding: 14px 18px;
    border-block-end: 1px solid var(--stroke);
}
.list-head h2 { font-size: 0.9688rem; font-weight: 700; margin: 0; }
.count {
    font-size: 0.75rem; font-weight: 600;
    color: var(--text-muted);
    background: var(--glass-2);
    border: 1px solid var(--stroke);
    border-radius: 999px;
    padding: 2px 9px;
    font-variant-numeric: tabular-nums;
}
.filters {
    display: flex; gap: 4px;
    margin-inline-start: auto;
    padding: 3px;
    background: var(--sunken-3);
    border: 1px solid var(--stroke);
    border-radius: var(--r-md);
}
.filters button {
    padding: 6px 14px;
    border: 1px solid transparent;
    border-radius: var(--r-sm);
    background: transparent;
    color: var(--text-muted);
    font: inherit; font-size: 0.8125rem; font-weight: 600;
    cursor: pointer;
}
.filters button:hover { color: var(--text); }
.filters button[aria-pressed="true"] {
    background: var(--grad-active);
    border-color: var(--grad-active-border);
    color: var(--text);
}

/* Request 136, verification §1 — the year is its own group, 10px from the status
   strip. The gap lives here rather than on `.list-head`, whose own 14px would
   otherwise be the spacing between them; `margin-inline-start: auto` moves off the
   individual strips and onto the wrapper, so the pair still sits at the end of the
   header and still mirrors under RTL. With no years to offer the wrapper holds the
   status strip alone and the layout is what it was before this task. */
.filter-groups {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: 10px;
    margin-inline-start: auto;
}
.filter-groups .filters { margin-inline-start: 0; }

/* The story asks the control to look like the buttons beside it "as much as
   possible", so it borrows their box wholesale and differs only where a select
   must: it keeps the native arrow, the affordance saying this one opens a list
   rather than toggling.

   Named for what it is, not for the first page that used it (request 154): the
   trips year filter and the companions role filter are the same control, and the
   rule was called `.filter-year` until the second one arrived. */
.filters select.filter-select {
    padding: 6px 10px 6px 14px;
    border: 1px solid transparent;
    border-radius: var(--r-sm);
    background: transparent;
    color: var(--text-muted);
    font: inherit; font-size: 0.8125rem; font-weight: 600;
    cursor: pointer;
}
.filters select.filter-select:hover { color: var(--text); }
/* Set by the change handler — the selected-year equivalent of aria-pressed. */
.filters select.filter-select[data-active="true"] {
    background: var(--grad-active);
    border-color: var(--grad-active-border);
    color: var(--text);
}
/* Never suppressed, and not left to the ring alone (spec 04-accessibility) — the
   same reasoning as `.field select:focus-visible` a few hundred lines down. */
.filters select.filter-select:focus-visible {
    outline: 2px solid var(--brand-400);
    outline-offset: 2px;
    color: var(--text);
}
/* Story §11. `.filters` sits on `--sunken-3`, a translucent token, and the option
   list is painted by the UA — it inherits neither that wash nor the transparent
   background above it, so an unstyled option lands on the UA's own white and, in
   dark mode, puts near-white text on it. Pinning both ends to opaque tokens is
   what makes the year readable: --text on --panel measures 17.29:1 light and
   14.61:1 dark, against the 4.5:1 the spec requires. */
.filters select.filter-select option {
    background-color: var(--panel);
    color: var(--text);
}

/* Item row: name (+ notes) · quantity · actions. The actions column is wider
   than the lists page's 92px because a to-buy row carries three controls. */
.sl-cols, .sl-item { grid-template-columns: minmax(0,1fr) minmax(0,140px) auto; }
.sl-items .row-head.sl-cols span:last-child { justify-self: end; }
.sl-item .item-cell { display: flex; align-items: flex-start; gap: 10px; min-width: 0; }
.sl-item .item-name { font-size: 0.9063rem; font-weight: 600; }
.sl-item .item-qty { color: var(--text-muted); font-size: 0.8438rem; font-variant-numeric: tabular-nums; }
.sl-item .item-actions { display: flex; gap: 6px; justify-content: flex-end; align-items: center; flex-wrap: wrap; }

/* Marked states. The strike-through and the tinted dot are the quick read; the
   `.item-flag` word beside them is what makes the state survive without colour
   (spec 04-accessibility), alongside the visually-hidden status already there. */
.item-flag {
    display: inline-block; margin-block-start: 4px;
    /* Not uppercased: the badge shows the very words the screen reader
       announces, and "NOT FOUND" shouts where "Not found" reads. */
    font-size: 0.7188rem; font-weight: 700;
    padding: 2px 8px; border-radius: 999px;
    border: 1px solid transparent;
}
/* The semantic colour lives in the tint and the border, not in the text. Setting
   the word itself in `--success` / `--danger` over their own tinted backgrounds
   measures 2.74:1 and 3.69:1 in light — well under AA, and this word is the
   accessible carrier of the state, so it is the last thing that may be hard to
   read. `--text` on the tint clears it comfortably in both themes; the hue still
   distinguishes the two at a glance. */
.item-flag--taken { background: var(--success-bg); border-color: var(--success-border); color: var(--text); }
.item-flag--not-found { background: var(--danger-bg); border-color: var(--danger-border); color: var(--text); }

.sl-item .item-status-dot { flex: 0 0 auto; inline-size: 10px; block-size: 10px; border-radius: 50%; margin-block-start: 6px; background: var(--stroke-2); }
.sl-item.item-row--new .item-status-dot { background: var(--brand-400); }
.sl-item.item-row--taken .item-status-dot { background: var(--success); }
.sl-item.item-row--taken .item-name { text-decoration: line-through; color: var(--text-muted); }
.sl-item.item-row--not-found .item-status-dot { background: var(--danger); }
/* A missing item's NAME was set in `--danger`, which measures 4.2:1 on the tinted
   row in light — under AA for 14.5px text. The state is already carried by the
   dot, the row tint and the "Not found" flag, so the red name was adding emphasis
   at the cost of legibility. It is pinned to `--text` instead. */
.sl-item.item-row--not-found .item-name { color: var(--text); }

/* The three-second mark on a row that was just saved (task 0175b). Two classes, so it
   beats `.item-row--taken` / `.item-row--not-found` (one class each); it sits after
   `.row:hover`, which has the same weight, so hovering a just-saved row does not wipe
   the mark off it. The mark is decoration ON TOP of the status — the dot, the flag and
   the strike-through all stay, and when it goes the row is back on whatever background
   its status gives it.

   `background-color` rather than the `background` shorthand, because the colour is the
   only thing being changed: the shorthand would also reset background-image, position,
   repeat and the rest on a row that may one day have them. */
.sl-item.is-saved { background-color: var(--row-saved-bg); }
/* Applied for the fade only. `.row` transitions background in .14s, which is a hover
   transition and far too quick to read as fading out; changing `.row` itself would slow
   every row hover in the app down with it. */
.sl-item.is-saved-fading { transition: background-color 1s ease; }

/* The second "Add item" button, under the table (task 0175a), and since task 0175d a
   SIBLING of the items panel rather than a strip inside it — "under the table" means
   under it. So no border and no row padding: those existed to separate it from the last
   row while it lived in the card. A top margin keeps it off the panel's edge.

   `justify-content: flex-end` mirrors what `margin-inline-start: auto` does for
   `.head-actions` in the page head: the story asks for a button identical to the one at
   the top, and identical includes where it sits (task 0175c). `flex-end` follows the
   writing direction, so RTL mirrors it without a second rule. Now that both buttons sit
   in the same container, the two line up exactly. */
.sl-foot {
    display: flex;
    justify-content: flex-end;
    margin-block-start: 16px;
}
/* `display: flex` above is an author rule and outranks the UA's `[hidden]`, exactly
   as `.rows` and `.row` document further up — without this the button would stay on
   screen, and in the tab order, on an empty list. */
.sl-foot[hidden] { display: none; }

/* Phones: the three columns stack, and the actions get a full row of their own
   rather than being squeezed beside the name (spec 03-ui). The quantity keeps
   its label, since the header row is hidden. */
@media (max-width: 640px) {
    .sl-items .row-head { display: none; }
    .sl-cols, .sl-item { grid-template-columns: 1fr; gap: 8px; }
    .sl-item .item-qty::before { content: attr(data-label) ": "; color: var(--text-muted); }
    .sl-item .item-qty:empty { display: none; }
    .sl-item .item-actions { justify-content: flex-start; }
    /* The header button goes full-width here (`.page-head--geode .head-actions .btn`
       gets `flex: 1`), so the foot one does too — the two are meant to be the same
       control in two places (task 0175c). */
    .sl-foot .btn { flex: 1; }
    .list-head { gap: 10px; }
    .filters { margin-inline-start: 0; width: 100%; }
    .filters button { flex: 1; padding-inline: 8px; }
}

/* ==========================================================================
   Travel companions, Geode design (request 131 §5, task 0131h)

   Reuses the panel/row primitives; only the name cell's stacking and the
   phone layout are specific to this page.
   ========================================================================== */
.cmp-name { min-width: 0; }
.cmp-name h3 { margin: 0; }
.cmp-name .meta { display: block; }
.cmp-note { font-style: italic; }
.cmp-trips { font-variant-numeric: tabular-nums; }
/* The dash is decorative; the reason sits beside it in a visually-hidden span. */
.cmp-email { color: var(--text-muted); }

/* Phones: the four columns stack into a card with `label: value` per field and
   the header row goes, as on the shopping lists page (spec 03-ui). */
@media (max-width: 640px) {
    .cmp-panel .row-head { display: none; }
    .cmp-panel .row { grid-template-columns: 1fr; gap: 8px; padding: 14px 16px; }
    .cmp-panel .row > .row-actions { justify-self: start; }
    .cmp-panel .cmp-trips::before,
    .cmp-panel .cmp-email::before { content: attr(data-label) ": "; color: var(--text-muted); }
    /* The role filter takes the full width rather than crowding the title, the
       same arrangement `.trips-panel` uses for its own strip (request 154). */
    .cmp-panel .filter-groups { margin-inline-start: 0; width: 100%; }
    .cmp-panel .filters { width: 100%; }
    .cmp-panel .filters select.filter-select { width: 100%; }
}

/* --- Companion role badge (request 131 §6, task 0131i) -------------------
   The example's trip-status pill — 3px 10px, fully rounded, 11.5px/600 — beside
   the name rather than on a line of its own. Its own class, NOT the global
   `.badge`, which several travel and career screens already use. Colours come
   from tokens_v2.css, one hue per role, measured for AA in both themes. */
.role-badge {
    display: inline-block;
    padding: 3px 10px;
    border-radius: 999px;
    font-size: 0.7188rem;
    font-weight: 600;
    line-height: 1.5;
    white-space: nowrap;
    vertical-align: middle;
}
.role-badge--friend    { background: var(--role-friend-bg);    color: var(--role-friend-fg); }
.role-badge--family    { background: var(--role-family-bg);    color: var(--role-family-fg); }
.role-badge--colleague { background: var(--role-colleague-bg); color: var(--role-colleague-fg); }
.role-badge--partner   { background: var(--role-partner-bg);   color: var(--role-partner-fg); }
.role-badge--coach     { background: var(--role-coach-bg);     color: var(--role-coach-fg); }
.role-badge--guide     { background: var(--role-guide-bg);     color: var(--role-guide-fg); }
.role-badge--customer  { background: var(--role-customer-bg);  color: var(--role-customer-fg); }
/* "you are" — the user's own companion row (request 139). Same pill as a role so
   the two sit together cleanly, in the one hue no role uses. */
.role-badge--self      { background: var(--role-self-bg);      color: var(--role-self-fg); }
.role-badge--other     { background: var(--role-other-bg);     color: var(--role-other-fg); }

/* Name and badge share a line and wrap together on narrow columns. */
.cmp-name h3 { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* ==========================================================================
   Travel list — the trips table (request 131 §8, task 0131l)

   The example's five-column grid at its own values. The example's stat tiles
   are NOT ported: the story defers the cards, so our existing `.funnel` strip
   stays above this table untouched.
   ========================================================================== */
.grid-head {
    display: grid;
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1.2fr) 132px 104px 92px;
    gap: 16px;
    padding: 10px 18px;
    background: var(--sunken);
    border-block-end: 1px solid var(--stroke);
    font-size: 0.6563rem; font-weight: 700;
    letter-spacing: .08em; text-transform: uppercase;
    /* `--text-dim` again fails AA at this size — see tasks 0131c/0131d/0131f. */
    color: var(--text-muted);
}
.grid-head span:last-child { text-align: end; }

.trips { display: block; }
.trips[hidden] { display: none; }
.trip {
    display: grid;
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1.2fr) 132px 104px 92px;
    gap: 16px;
    align-items: center;
    padding: 13px 18px;
    border-block-end: 1px solid var(--hairline);
    transition: background .14s;
}
.trip:hover { background: var(--row-hover); }
.trip:last-child { border-block-end: 0; }
/* `display: grid` outranks the UA's `[hidden]` rule — the trap from 0131c/0131e. */
.trip[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) { .trip { transition: none; } }

.trip-name { display: flex; align-items: center; gap: 11px; min-width: 0; }
.trip-name__text { min-width: 0; }
.trip-mark {
    inline-size: 34px; block-size: 34px; flex: none;
    border-radius: var(--r-md);
    display: grid; place-items: center;
    font-size: 0.875rem; font-weight: 700;
    border: 1px solid transparent;
    background: var(--glass-2); color: var(--text-muted); border-color: var(--stroke);
}
.trip[data-status="upcoming"] .trip-mark { background: var(--info-bg); color: var(--brand-400); border-color: var(--info-border); }
.trip[data-status="ongoing"] .trip-mark { background: var(--success-bg); color: var(--success); border-color: var(--success-border); }
.trip h3 { font-size: 0.9063rem; font-weight: 600; letter-spacing: -.01em; margin: 0; }
.trip-dates { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; font-size: 0.7813rem; color: var(--text-muted); margin-block-start: 2px; }
.trip-dates .dot { opacity: .6; }

/* Status pill. Its own colours rather than the global `.badge`, and the word
   is what carries the state — the tinted mark beside it is a second signal,
   never the only one. */
.trip-badge { display: inline-block; padding: 3px 10px; border-radius: 999px; font-size: 0.7188rem; font-weight: 600; }
.trip-badge--upcoming { background: var(--info-bg); color: var(--text); }
.trip-badge--ongoing { background: var(--success-bg); color: var(--text); }
.trip-badge--past { background: var(--glass-2); color: var(--text); }

.chips { display: flex; flex-wrap: wrap; gap: 5px; }
/* `.chip-code` rides on the SAME rule rather than repeating it (request 192): the
   story asks for airport and flight badges styled like a trip's countries and
   travellers, and two declarations of one pill are two things to keep in step. Only
   the colours differ, below. */
.chips span,
.chip-code {
    font-size: 0.7188rem; font-weight: 500;
    padding: 3px 9px; border-radius: var(--r-sm);
    background: var(--glass-2); border: 1px solid var(--stroke);
    color: var(--text-muted); white-space: nowrap;
}

/* The two hues. `inline-block` because these sit in running text and in flex
   columns, where a bare inline would not take the padding cleanly. */
.chip-code { display: inline-block; text-align: center; }
/* One size for every badge of a kind (request 192 follow-up), so a column of them
   lines up instead of jittering with the glyphs. Measured rather than guessed, in the
   chip's own font at 11.5px:

     airport  3-letter IATA codes run III 29.6px .. WWW 51.9px  -> 4.51em
     flight   2 letters + up to 4 digits, LY3 39.0 .. QR0001 62.4 -> 5.42em

   `min-inline-size`, not a fixed one, and in `em` not px: em follows the text-size
   control (task 0162b), and a minimum means an entry longer than anything real grows
   instead of being clipped — losing a character of a flight number would be worse
   than one wide badge. */
.chip-code--airport {
    background: var(--chip-airport-bg);
    color: var(--chip-airport-fg);
    min-inline-size: 4.6em;
    /* Breathing room above and below, asked for on the airport badge only. */
    margin-block: 3px;
}
.chip-code--flight {
    background: var(--chip-flight-bg);
    color: var(--chip-flight-fg);
    min-inline-size: 5.6em;
}
/* A chip in a route end must not stretch to the width of the half it sits in, and
   that end is a different kind of box at each breakpoint: a flex COLUMN on desktop,
   and at ≤640px a two-column grid so the time and the code can share a line
   (request 141 follow-up).

   Both properties, because they are the inline axis in different formatting contexts
   — `align-self` is the cross axis of a column flex, `justify-self` the inline axis
   of a grid, and setting only the first left the chip stretching to 125–152px on a
   phone. */
.seg-card__end .chip-code { align-self: flex-start; justify-self: start; }
/* The arrival end reads towards the end of the line only while the two ends sit side
   by side. Below the breakpoint they stack and `.seg-card__end--arr` is reset to
   `text-align: start`, so the chip follows the text rather than hanging off on its
   own. */
@media (min-width: 641px) {
    .seg-card__end--arr .chip-code { align-self: flex-end; justify-self: end; }
}
/* The arrow between two airport badges on the all-flights list. Decoration: the two
   codes either side already say the route, so it is `aria-hidden` in the markup and
   only has to sit on their line here.

   Mirrored under RTL, exactly as `.seg-card__plane` is. The badges follow the page
   direction, so in Hebrew the origin is the rightmost and the destination the
   leftmost — correct reading order — and an arrow still drawn pointing right would
   then be pointing from the destination back to the origin. */
.seg-arrow { color: var(--text-muted); margin-inline: 2px; display: inline-block; }
[dir="rtl"] .seg-arrow { transform: scaleX(-1); }

.stack { display: flex; }
.stack span:not(.sr-only) {
    inline-size: 26px; block-size: 26px;
    border-radius: 50%;
    display: grid; place-items: center;
    font-size: 0.6875rem; font-weight: 700;
    color: var(--on-accent);
    background: var(--brand-400);
    border: 2px solid var(--bg-2);
    margin-inline-start: -8px;
}
.stack span:not(.sr-only):first-child { margin-inline-start: 0; }
.stack span:not(.sr-only):nth-child(2n) { background: var(--teal-400); }
.stack span:not(.sr-only):nth-child(3n) { background: var(--violet); }

/* Scoped to `.trip`: `components_v2.css` already owns a `.metrics` — the landing
   page's four-column stat grid — and it loads after this sheet, so a bare
   `.metrics` here loses on equal specificity and the row would inherit a
   `grid-template-columns: repeat(4, 1fr)` it has no use for. The same collision
   that produced bug 0017; `.metrics` is the only name in this block that clashes. */
.trip .metrics { display: flex; gap: 14px; font-size: 0.75rem; color: var(--text-muted); }
.trip .metrics b { color: var(--text); font-weight: 700; font-variant-numeric: tabular-nums; margin-inline-end: 4px; }

.trip-actions { display: flex; gap: 6px; justify-content: flex-end; }

/* Phones: the five columns collapse. The head row goes and each trip becomes a
   stacked card, with the actions kept on the first row beside the name — the
   example's own 720px arrangement, at our ≤640px breakpoint (spec 03-ui). */
@media (max-width: 640px) {
    .trips-panel .grid-head { display: none; }
    .trip {
        grid-template-columns: 1fr auto;
        gap: 10px;
        padding: 14px;
        align-items: start;
    }
    .trip .trip-actions { grid-row: 1; grid-column: 2; }
    .trip .chips { grid-column: 1 / -1; }
    .trip .stack { grid-column: 1; }
    .trip .metrics { grid-column: 1 / -1; }
    .trips-panel .filters { margin-inline-start: 0; width: 100%; }
    .trips-panel .filters button { flex: 1; padding-inline: 8px; }
}

/* ==========================================================================
   The shared component vocabulary (request 131 §9, task 0131n)

   Twenty-one pages render the generic `.table`, `.btn--*` and `.modal__*`
   names. They need no structural change, so rather than 21 near-duplicate
   views the vocabulary itself is given the Geode look here.

   A page whose structure genuinely differs still earns its own view, as the
   profile, shopping, companions and travel-list pages did.
   ========================================================================== */

/* ---- Popups: the plain dialog gets the `.modal-card` chrome ------------- */
.modal__dialog {
    background: var(--panel);
    border: 1px solid var(--stroke-2);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-lg);
    max-width: 520px;
}
.modal__dialog--wide { max-width: 640px; }

/* The title becomes a ruled head. It sits inside the dialog's 22px padding, so
   the rule is pulled out to the edges with negative margins. */
.modal__title {
    margin: -22px -22px 20px;
    padding: 20px 22px;
    border-block-end: 1px solid var(--stroke);
    font-size: 1.0625rem; font-weight: 700;
}
/* Same for the action bar, which becomes the sunken footer. */
.modal__actions {
    display: flex; gap: 10px; justify-content: flex-end;
    margin: 22px -22px -22px;
    padding: 16px 22px;
    background: var(--sunken-3);
    border-block-start: 1px solid var(--stroke);
}
.modal__message { margin: 0 0 18px; color: var(--text-muted); font-size: 0.875rem; }

/* ---- Buttons ------------------------------------------------------------ */
.btn.btn--ghost {
    background: var(--glass);
    border-color: var(--stroke);
    color: var(--text);
}
.btn.btn--ghost:hover { background: var(--glass-2); border-color: var(--stroke-2); }
.btn.btn--danger { background: var(--danger); color: var(--on-danger); border-color: transparent; }
.btn.btn--danger:hover { filter: brightness(.94); }
.btn.btn--sm { padding: 6px 12px; font-size: 0.8125rem; }
.btn.btn--xs { padding: 4px 10px; font-size: 0.75rem; }

/* ---- Tables: the Geode list head and hairline rows ---------------------- */
.table {
    background: var(--glass);
    border: 1px solid var(--stroke);
    border-radius: var(--r-lg);
}
.table th {
    background: var(--sunken);
    border-block-end: 1px solid var(--stroke);
    font-size: 0.6563rem; font-weight: 700;
    letter-spacing: .08em; text-transform: uppercase;
    /* `--text-dim` fails AA at this size — measured four times in this project. */
    color: var(--text-muted);
    padding: 9px 16px;
}
.table td { border-block-end: 1px solid var(--hairline); padding: 12px 16px; }
.table tbody tr:hover td { background: var(--row-hover); }

/* Only two views actually use the shared `.table` class; the section tables carry
   their own. The plan and itinerary tables get the same head treatment — their
   rows are left alone, because both paint row backgrounds (day grouping, flight
   and stay stripes) that a blanket rule would fight. */
/* `thead th` (0,0,2,1) to outrank the earlier `.plan-table thead th` /
   `.itin-table thead th`, which set their own weight and surface. */
.plan-table thead th,
.itin-table thead th {
    background: var(--sunken);
    border-block-end: 1px solid var(--stroke);
    font-size: 0.6563rem; font-weight: 700;
    letter-spacing: .08em; text-transform: uppercase;
    color: var(--text-muted);
}

/* `.exp-table` — the expenses grid and the car mileage table that reuses it — is
   deliberately NOT swept. It paints stored category colours per row, carries its
   own subtotal and stacked-phone behaviour from task 0127d, and re-tuning it
   belongs with a structural pass, exactly as the story says of flights and
   hotels. Listed as pending in the report. */

/* Action cells keep their two icon buttons on one line — they stack otherwise,
   because `.exp-actions` has no direction of its own (task 0131o). Scoped to the
   mileage table then; request 134 brings the expenses grid into the same icon
   pattern, so it takes the same rule. */
.mileage-table .exp-actions,
.exp-table tbody .exp-actions { display: flex; gap: 6px; justify-content: flex-end; align-items: center; }

/* A category header is filled with the category's own colour, and some of those
   colours are red — a red delete on a red header stops reading as a button. A
   light ring keeps its edge on any fill (request 134). */
.exp-cat-head .btn--danger { box-shadow: 0 0 0 1.5px rgba(255, 255, 255, 0.75); }

/* ==========================================================================
   Bug 0018 — dark mode: the chrome must not be see-through

   `--glass` is opaque white under Quartz but
   rgba(255,255,255,0.04) under Obsidian, and `--rail` is rgba(0,0,0,0.28)
   there. So the top bar, the dropdown menus and the mobile drawer all let the
   page through in dark, and only in dark.

   The header takes the wash the example actually uses — `--nav-bg` with a
   backdrop blur, a token defined in 0130a and never wired up. Menus and the
   drawer go fully opaque: blur is not enough at menu size, and a drawer floats
   over content. The DESKTOP rail keeps its glass, which is intended.
   ========================================================================== */
/* Opaque, not frosted. The example's top bar is a wash plus a backdrop blur, and
   `--nav-bg` (0.78 in dark) with blur was the first fix here — but a probe showed
   content still bleeding 22% through it, and the operator asked for the chrome to
   stop showing the page. `--panel` is the opaque surface token in both themes,
   and in light it restores exactly the white bar that was there before.
   A blur would now be a compositing layer that does nothing, so there is none. */
.site-header,
.secondary-menu { background: var(--panel); }

/* A menu you can read the page through is unusable. */
.dropdown { background: var(--panel); }

@media (max-width: 768px) {
    /* Only as a drawer — on desktop the rail is glass over the page by design. */
    .sidebar { background: var(--rail-solid); }
}

/* ==========================================================================
   Travel — visited countries (request 172)
   ==========================================================================
   Built on the itinerary table's shape, which is the section's other read-only
   table, so the two read as one design. Colour comes only from tokens — the
   numbered mark reuses `.trip-mark`, so it already tracks the theme.
*/
.visited-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.visited-table th, .visited-table td {
    padding: 8px 10px; border: 1px solid var(--stroke);
    text-align: start; vertical-align: top;
}
.visited-table thead th { background: var(--glass-2); color: var(--text); font-weight: 600; }
/* The number column is exactly as wide as the mark it holds. */
.visited-no { inline-size: 1%; white-space: nowrap; }
.visited-country { font-weight: 600; }

.visited-trip-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
/* Each trip is its own row (story). Name and dates sit on one line and wrap
   together rather than the dates orphaning onto a line of their own. */
.visited-trip-list li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; }
.visited-when { white-space: nowrap; }

/* Phones: the stacked card layout of spec 03-ui — the header row goes, and each
   country becomes a card of `label: value` lines. The number keeps its mark and
   leads the card, so the ordering is still visible without a column. */
@media (max-width: 640px) {
    .visited-table thead { display: none; }
    .visited-table, .visited-table tbody, .visited-table tr, .visited-table td { display: block; }
    .visited-table tr {
        border: 1px solid var(--stroke); border-radius: var(--r-md);
        margin-block-end: 10px; overflow: hidden;
    }
    .visited-table td { border: 0; border-block-end: 1px solid var(--hairline); padding: 6px 10px; }
    .visited-table td:last-child { border-block-end: 0; }
    .visited-no { inline-size: auto; padding-block-end: 0; }
    /* `label: value` on one line, as the expenses table does it. The trips cell is
       excluded: its value is a list of rows, which cannot share a line with a
       label. */
    .visited-table td.visited-country { display: flex; justify-content: space-between; gap: 12px; text-align: end; }
    .visited-table td.visited-country::before {
        content: attr(data-label); font-weight: 600; color: var(--text-muted);
        text-align: start; margin-inline-end: 8px;
    }
    .visited-table td.visited-trips::before {
        content: attr(data-label); display: block; font-weight: 600;
        color: var(--text-muted); margin-block-end: 4px;
    }
    /* Every OTHER labelled cell gets its label too. The two rules above name the two
       cells the countries page has, so when the flights and stays pages arrived
       (task 0188b) their cells carried `data-label` and nothing rendered it — a card
       of bare values with nothing saying which was the route and which the date.
       `:not()` on the two above rather than a lower-specificity fallback, so neither
       of their layouts is disturbed. */
    .visited-table td[data-label]:not(.visited-country):not(.visited-trips)::before {
        content: attr(data-label); display: block; font-weight: 600;
        color: var(--text-muted); margin-block-end: 4px;
    }
}

/* A stat card that is a link (request 172): it must look no different from the
   cards beside it, so only the anchor's own defaults are undone. The hover
   border it inherits from `.funnel__stage:hover` is the affordance, and focus
   keeps the global visible ring — never suppressed (spec 04-accessibility). */
.funnel__stage--link { text-decoration: none; color: inherit; }
.funnel__stage--link:hover { text-decoration: none; }

/* The People card's styles (tasks 0173b, 0174b, 0174e) were removed by task 0176e
   along with the card. Nothing else used `.circles*` or `.ppl-*`: the contacts list
   was revised to a flat table on 2026-08-23 and never consumed the group row. */

