/* ==========================================================================
   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.

   WHY THE `--color-*` ALIASES EXIST
   ---------------------------------
   Almost every rule in this file names a `--color-*` variable rather than a
   literal — `var(--color-surface)`, not `#ffffff`. The block below re-points
   those names at the Geode tokens, so every downstream rule themes for free and
   a colour change is a one-line edit in tokens_v2.css.

   That is also why the layout here is plain: this file is structure plus
   variable references, and the colour decisions live in the token sheet.
   ========================================================================== */

:root {
    /* ---- legacy variable names → Geode tokens -------------------------
       Kept as aliases, not as history: component rules across the app still
       name them, and re-pointing them here was cheaper and safer than a
       find-and-replace over every stylesheet rule that uses one. ---------- */
    --color-bg:               var(--bg);
    --color-surface:          var(--glass);
    --color-surface-2:        var(--glass-2);
    --color-text:             var(--text);
    --color-muted:            var(--text-muted);
    --color-border:           var(--stroke);
    --color-primary:          var(--brand-400);
    --color-primary-hover:    var(--brand-500);
    --color-primary-contrast: var(--on-brand);
    --color-accent:           var(--info-bg);
    --color-danger:           var(--danger);

    /* The rail is inked in BOTH themes — under light it keeps an edge against
       the white content column. Geode ships its own --rail-* set for exactly
       this, so these map across rather than following the page.

       Only the three names Geode does NOT already use are mapped here.
       `--rail-text` and `--rail-active-text` are declared by tokens_v2.css under
       those exact names, so v1's rules pick them up untouched — redeclaring them
       here as var(--rail-text) would be a circular reference and resolve to
       nothing. */
    --rail-bg:        var(--rail);
    --rail-active-bg: var(--rail-active);
    --rail-brand:     var(--rail-text);

    /* ---- geometry: unchanged from v1 ---------------------------------- */
    --radius:      12px;
    --radius-sm:   8px;
    --space:       16px;
    --header-h:    60px;
    --rail-w:      244px;   /* the example's rail width (request 131 §2) */
    --max-w:       var(--container);

    --shadow:     var(--shadow-sm);
    --shadow-pop: var(--shadow-lg);

    /* Type comes from the tokens now: the system stack, no self-hosted
       families (decision 2026-08-03). v2 does not load fonts.css. */
    --font-display: var(--font);
}

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }

body {
    font-family: var(--font);
    color: var(--color-text);
    background: var(--color-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-display);
    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(--max-w); 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(--color-primary); color: var(--color-primary-contrast);
    font-weight: 600; transition: top .15s ease;
}
.skip-link:focus { top: 8px; }
:focus-visible { outline: 3px solid var(--color-primary); 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(--color-primary); 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-bg);
    color: var(--rail-text);
    padding: 16px 12px;
    display: flex;
    flex-direction: column;
    gap: 22px;
}
.sidebar .brand { color: var(--rail-brand); 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: 10.5px; 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(--color-surface);
    border-block-end: 1px solid var(--color-border);
    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: 12.5px; }

/* 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(--color-border);
    border-radius: var(--radius-sm); background: var(--color-surface); color: var(--color-text);
}

.header-tools { display: flex; align-items: center; gap: 8px; }

/* --- 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(--color-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(--color-text); }
.crumbs strong { color: var(--color-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(--color-border); border-radius: var(--radius-sm);
    background: var(--color-surface); color: var(--color-text);
}
.btn-tool:hover { background: var(--color-surface-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(--color-surface); border: 1px solid var(--color-border);
    border-radius: var(--radius); box-shadow: var(--shadow-pop); 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(--color-surface-2); }
.dropdown a[aria-current="true"] { color: var(--color-primary); font-weight: 600; }

/* --- Secondary (in-section) sub-navigation ----------------------------- */
.secondary-menu { background: var(--color-surface); border-block-end: 1px solid var(--color-border); }
.secondary-menu__inner { display: flex; align-items: center; gap: var(--space); min-height: 52px; padding-inline: var(--space); }
.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(--color-muted); font-weight: 500; }
.secondary-nav a:hover { background: var(--color-surface-2); color: var(--color-text); }
.secondary-nav a.is-active { background: var(--color-accent); color: var(--color-primary); }
.secondary-menu__title a { color: inherit; text-decoration: none; }
.secondary-menu__title a:hover { color: var(--color-primary); }

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

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

.page-head { margin-block-end: 20px; }
.page-head h1 { margin: 0; font-size: 1.5rem; }
.page-head__sub { margin: 4px 0 0; color: var(--color-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(--color-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(--color-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(--color-surface); border: 1px solid var(--color-border);
    border-radius: var(--radius); box-shadow: var(--shadow); padding: 20px;
}
.feature-card__icon { font-size: 1.75rem; }
.feature-card__name { margin: 10px 0 6px; font-size: 1.05rem; }
.feature-card__desc { margin: 0; color: var(--color-muted); font-size: .95rem; }
.feature-card--link { display: block; color: inherit; transition: border-color .15s, transform .15s; }
.feature-card--link:hover { border-color: var(--color-primary); transform: translateY(-2px); }
.feature-card__open { display: inline-block; margin-block-start: 10px; color: var(--color-primary); 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(--color-muted); font-size: .9rem; }
.back-link:hover { color: var(--color-primary); }

/* 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(--color-surface); border: 1px solid var(--color-border);
    border-radius: var(--radius); box-shadow: var(--shadow); padding: 14px 16px;
}
.list-item__name { font-weight: 600; font-size: 1.05rem; color: var(--color-text); }
.list-item__name:hover { color: var(--color-primary); }
.list-item__date { margin-inline-start: auto; color: var(--color-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(--color-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(--color-border); }

/* Status-distinct rows */
.item-row--new .item-status-dot { background: var(--color-primary); }
.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(--color-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(--color-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(--color-surface); 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(--color-border); 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 `--color-surface`, which the v1→Geode alias block above maps to
   `--glass`. 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(--color-danger); font-size: .9rem; }
/* Required-field asterisk (task 0100 / UI amendments v1 §1). */
.req { color: var(--color-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(--color-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(--color-border); }
/* 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(--color-surface-2); color: var(--color-muted);
    border: 1px solid var(--color-border);
}

.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(--color-border);
}
.detail-row:last-child { border-block-end: 0; }
.detail-row dt { margin: 0; color: var(--color-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(--color-surface-2); color: var(--color-text); border: 1px solid var(--color-border);
}
/* 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(--color-muted); font-size: .85rem; }
.readonly-value { padding: 6px 0; }

/* Listings & interactions (task 0018) */
.muted { color: var(--color-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(--color-surface); border: 1px solid var(--color-border);
    color: var(--color-text); font-size: .85rem;
}
.chip:hover { border-color: var(--color-primary); }
.chip--on { background: var(--color-accent); border-color: var(--color-primary); color: var(--color-primary); 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(--color-border); }

.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(--color-border); }
.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(--color-surface);
    border: 1px solid var(--color-border); border-top: 3px solid var(--badge); color: var(--color-text);
}
.funnel__stage:hover { border-color: var(--color-primary); }
.funnel__count { font-size: 1.5rem; font-weight: 700; line-height: 1; }
.funnel__label { font-size: .8rem; color: var(--color-muted); }
.action-group { margin: 14px 0 6px; font-size: .9rem; color: var(--color-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(--color-border); }
.action-item:last-child { border-block-end: 0; }
.action-item__date { color: var(--color-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(--color-border);
}
.profile-row:last-child { border-block-end: 0; }
.profile-row dt { margin: 0; color: var(--color-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(--color-primary); color: var(--color-primary-contrast);
}
.btn:hover { background: var(--color-primary-hover); }
.btn--ghost { background: var(--color-surface); color: var(--color-text); border-color: var(--color-border); }
.btn--block { width: 100%; }

.input {
    font: inherit; padding: 9px 12px; border: 1px solid var(--color-border);
    border-radius: var(--radius-sm); background: var(--color-surface); color: var(--color-text);
}
.input:focus { outline: 2px solid var(--color-accent); outline-offset: 0; border-color: var(--color-primary); }

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

.table { width: 100%; border-collapse: collapse; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius); overflow: hidden; }
.table th, .table td { padding: 12px 14px; text-align: start; border-block-end: 1px solid var(--color-border); }
.table th { background: var(--color-surface-2); color: var(--color-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(--color-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(--color-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(--color-primary); font-weight: 600; }

/* --- Mobile: rail collapses to an off-canvas drawer -------------------- */
@media (max-width: 768px) {
    .sidebar-toggle { display: inline-flex; }
    .btn-tool__label { display: none; }

    .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(--color-border);
        border-radius: var(--radius-sm); background: var(--color-surface);
    }
    .secondary-nav {
        display: none; position: absolute; inset-inline: 0; inset-block-start: 100%;
        flex-direction: column; gap: 2px; margin: 0; padding: 8px;
        background: var(--color-surface); border-block-end: 1px solid var(--color-border);
        box-shadow: var(--shadow-pop); 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(--color-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(--color-border); 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(--color-border); 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(--color-surface-2); border: 1px solid var(--color-border); 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(--color-surface); border: 1px solid var(--color-border); }
.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(--color-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(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-sm); }
.exp-summary .sep { color: var(--color-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(--color-surface);
    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: 10.5px; 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(--color-surface); 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(--color-surface-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) */
.exp-table tbody tr[id^="exp-"] { scroll-margin-top: 80px; }
.exp-table tbody tr[id^="exp-"]:target { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.exp-table tbody tr[id^="exp-"]:target td:first-child { box-shadow: inset 3px 0 0 var(--focus-ring); }
.pay-cell { white-space: nowrap; }
.pay-status { font-weight: 700; }
.pay-badge { display: inline-block; font-size: 10px; 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(--color-border); border-radius: 999px; }
.input--color { width: 48px; height: 34px; padding: 2px; }
.input--mini { padding: 2px 4px; font-size: .8rem; width: auto; }
.exp-planned td { opacity: .72; font-style: italic; }
.linklike { background: none; border: 0; color: var(--color-primary); 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(--color-muted); font-size: .9rem; }
.share-lang__link.is-active { background: var(--color-surface-2); color: var(--color-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(--color-border); text-align: center; color: var(--color-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(--color-text); }
.trip-card__name:hover { color: var(--color-primary); }
.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(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius); text-decoration: none; color: inherit; transition: border-color .15s, transform .15s; }
.dash-card:hover { border-color: var(--color-primary); transform: translateY(-2px); }
.dash-card__count { font-size: 1.9rem; font-weight: 700; line-height: 1; color: var(--color-primary); }
.dash-card__count--view { color: var(--color-muted); }
.dash-card__label { font-size: .9rem; color: var(--color-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(--color-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(--color-primary); }
.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(--color-border);
}
.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(--color-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(--color-border); text-align: start; vertical-align: top; }
.plan-table thead th { background: var(--color-surface-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; opacity: .8; }
.plan-day__date { display: block; font-size: .8rem; opacity: .8; 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(--color-primary); background: var(--color-surface); border: 1px solid var(--color-border); border-radius: 50%; }
.plan-day__add:hover { background: var(--color-primary); color: var(--on-brand); border-color: var(--color-primary); }
.plan-row.is-dragging { opacity: .5; }
.plan-actions { white-space: nowrap; }
.drag-handle { cursor: grab; color: var(--color-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(--color-primary); }
/* 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(--color-surface-2); font-style: italic; color: var(--color-text); padding: 4px 10px; }

/* Travel — map (task 0034) */
#travel-map { height: 62vh; min-height: 360px; border-radius: var(--radius); border: 1px solid var(--color-border); }
.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(--color-primary); color: var(--on-brand); border-color: var(--color-primary); }

/* 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(--color-surface-2); color: var(--color-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); }
.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(--color-border); 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(--color-primary); box-shadow: 0 0 0 2px var(--focus-ring); }
.upload-label { display: block; margin-block-start: 8px; font-size: .85rem; color: var(--color-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(--color-surface-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(--color-surface-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(--color-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(--color-surface); border: 1px solid var(--color-border); 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(--color-muted); font-size: 0.85rem; overflow-wrap: anywhere; }
.seg-card__date { color: var(--color-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(--color-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(--color-border); }
.seg-card__plane { position: absolute; inset-inline-end: -2px; inset-block-start: -0.7em; font-size: 0.9rem; line-height: 1; color: var(--color-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(--color-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(--color-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(--color-border); 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(--color-muted); }
.fbk-total--booking { display: block; margin: 10px 0 0; font-size: .95rem; color: var(--color-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; }
}
/* ============================================================
   Domolytics — v2 stylesheet (request 130)

   The Geode restyle. Loaded after tokens_v2.css, which defines every
   colour for both themes; this file names NO colour literals — it
   only says var(--…), so flipping `data-theme` on <html> repaints
   everything without touching a rule here.

   Task 0130a lays the foundation: the theme switch and the base
   page. The rest arrives in:
     0130b  signed-in shell — rail, header, menus, buttons, forms, modals
     0130c  public + share surfaces
     0130d  section components — tables, cards, badges, lists, charts
   ============================================================ */

/* ---- base ---------------------------------------------------- */
* { box-sizing: border-box; }

html { block-size: 100%; }

body {
    margin: 0;
    min-block-size: 100%;
    background: var(--bg);
    color: var(--text);
    font-family: var(--font);
    font-size: 15px;
    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. Light is untouched — it must render exactly as v1 does.

   `!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(--color-primary);
    border-color: var(--color-primary);
    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. v1's
   `.auth-card { max-width: 380px }` and `.auth-wrap { padding: 24px }` above
   have no counterpart there, so they survived and squeezed the card to 380px
   where the example gives it 752px.

   Scoped to `.landing`, which is only the ported pages, so v1's centred
   single-card login 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(24px, 3vw, 32px); 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: 14px; 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(--color-muted); font-size: 14px; }

.profile-v2 .card { padding: 22px; }
.profile-v2 .card > h2 { font-size: 15.5px; 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; }

/* 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: 15px;
}
/* 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(--color-surface-2)); color: var(--danger); border: 1px solid var(--danger-border, var(--color-border)); }

/* ==========================================================================
   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: 14.5px; font-weight: 600; margin: 0; }
.row small, .row .meta { display: block; font-size: 12.5px; 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: 10.5px; 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: 13px; 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: 16px; font-weight: 700; margin: 0 0 6px; }
.empty p { color: var(--color-muted); font-size: 14px; 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: 17px; 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(--color-muted); font-size: 14px; }
.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: 13px;
}

/* 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(--color-muted); }
    .sl-panel .meta-cell::before { content: attr(data-label) ": "; color: var(--color-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: 15.5px; font-weight: 700; margin: 0; }
.count {
    font-size: 12px; 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: 13px; 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: 13px; 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: 14.5px; font-weight: 600; }
.sl-item .item-qty { color: var(--text-muted); font-size: 13.5px; 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: 11.5px; 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); }
/* The v1 rule sets a missing item's NAME 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. (v1 has the same rule and the same
   problem; fixing it there belongs with v1's own work, not silently here.) */
.sl-item.item-row--not-found .item-name { color: var(--text); }

/* 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(--color-muted); }
    .sl-item .item-qty:empty { display: none; }
    .sl-item .item-actions { justify-content: flex-start; }
    .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(--color-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: 11.5px;
    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: 10.5px; 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: 14px; 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: 14.5px; font-weight: 600; letter-spacing: -.01em; margin: 0; }
.trip-dates { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; font-size: 12.5px; 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: 11.5px; 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; }
.chips span {
    font-size: 11.5px; 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;
}

.stack { display: flex; }
.stack span:not(.sr-only) {
    inline-size: 26px; block-size: 26px;
    border-radius: 50%;
    display: grid; place-items: center;
    font-size: 11px; 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: 12px; 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 v1 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: 17px; 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(--color-muted); font-size: 14px; }

/* ---- 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: 13px; }
.btn.btn--xs { padding: 4px 10px; font-size: 12px; }

/* ---- 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: 10.5px; 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: 10.5px; 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

   `--color-surface` is `--glass`, which 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); }
}
