/* Wander Window — the shared component floor (UI overhaul Wave 1, Sep 2026).
 *
 * base.css is deliberately tokens-only; this file is the next layer up: the
 * HANDFUL of components every page kept re-inventing. The owner's audit found
 * 16+ page-local `.btn` definitions, ~40 border-radius values, six modal scrim
 * colours and the brand action teal #268183 hardcoded in ~32 files with no
 * token. Every rule here exists to make that drift structurally impossible,
 * not to restyle pages that already work.
 *
 * THE RULES OF THIS FILE:
 *   · NEW class names only (ww-*), plus new tokens no page declares. Nothing
 *     here can collide with a page's existing cascade, so linking it is safe
 *     on any page in any order after base.css.
 *   · No base.css token is re-declared here (smoke-test fails same-value
 *     re-declarations, and it is right to).
 *   · Pages migrate TO these classes deliberately, one surface at a time —
 *     aliasing a page's old class to these rules from here would restyle
 *     pages nobody was looking at.
 *   · Load order: after base.css, before the page's own <style>, so a page
 *     can still override a component the way base.css already allows for
 *     tokens.
 */

:root {
  /* ---- the brand action colour, FINALLY a token -------------------------
     #268183 is what nearly every primary button on the site already computes
     to — 15+ pages converged on it through hand-copied "Wanderology brand"
     override blocks. Equal-value substitution: swapping a page's literal for
     this token changes nothing on screen, and that is the point. The hover is
     the companion literal those same blocks already use. NOT the same colour
     family as --gold/#2f9c9e or --copper-text/#297678 — those stay for what
     they already mean; this is the ACTION teal. White on #268183 measures
     4.55:1 (passes 4.5 for the 13px button labels it carries); white on the
     hover #1f6b6d measures 6.13:1. */
  --brand-action: #268183;
  --brand-action-hover: #1f6b6d;

  /* ---- the radius scale: ~40 values become three -----------------------
     999px (231 uses), 10px (123) and 16px (42) are the measured majorities
     for pills, controls and cards. base.css deliberately refused a single
     --radius because 14px-vs-16px was a genuine split; a three-step scale is
     the settlement — values within 2px of a step migrate to the step, 50%
     stays for true circles. */
  --radius-pill: 999px;
  --radius-control: 10px;
  --radius-card: 16px;

  /* ---- one scrim, one z-scale ------------------------------------------
     Six scrim colours and six z tiers shipped across ~10 overlay
     implementations. New overlays use these; old ones migrate as they are
     touched. Toast stays above everything (ww.js already uses 200). */
  --scrim: rgba(41,54,74,.5);
  --z-nav: 60;
  --z-overlay: 80;
  --z-toast: 200;

  /* ---- the motion system: two durations, one ease ----------------------
     The audit measured ten distinct transition durations on one page — the
     absence of a system, not the presence of taste. Fast is hover/press/
     menus; standard is drawers, dialogs and accordion height. Everything
     using them must sit inside a prefers-reduced-motion guard. */
  --ww-fast: 120ms;
  --ww-standard: 220ms;
  --ww-ease: cubic-bezier(.2, .7, .3, 1);
}

/* ---- the compact page head --------------------------------------------
   What a working page shows under the sticky header instead of a ~400px
   marketing hero: the page's name at a working size, an optional one-line
   sub. The marketing hero survives only on the signed-out home page. The h1
   inherits each page's own serif rule (every page declares h1,h2 serif in
   its reset — deliberately not repeated here, see base.css's cascade note on
   why moved resets went red). */
.ww-page-head {
  max-width: var(--container, 1080px);
  margin: 0 auto;
  padding: 26px var(--gutter, 24px) 2px;
}
.ww-page-head h1 {
  font-size: clamp(26px, 3vw, 34px);
  font-weight: 500;
  margin: 0;
  color: var(--ink, #29364a);
  text-wrap: balance;
}
.ww-page-head p {
  font-size: 14px;
  color: var(--muted-ink, #6b7171);
  margin: 6px 0 0;
  max-width: 62ch;
  line-height: 1.55;
}

/* ---- the button trio ---------------------------------------------------
   ONE filled primary per container; secondaries are quiet outlines;
   borderless quiet buttons for tertiary acts. 44px minimum target with the
   inline-flex + center idiom the newest picker already proved (a min-height
   alone top-aligns the label and reads as a 31px control). Focus ring is the
   3px ink outline the site's newest controls already use. */
.ww-btn,
.ww-btn-secondary,
.ww-btn-quiet,
.ww-btn-danger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  box-sizing: border-box;
  min-height: 44px;
  padding: 10px 18px;
  border-radius: var(--radius-pill);
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 700;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  border: none;
}
.ww-btn {
  background: var(--brand-action);
  color: #fff;
}
.ww-btn:hover { background: var(--brand-action-hover); color: #fff; }
.ww-btn-secondary {
  background: var(--card, #fff);
  color: var(--copper-text, #297678);
  border: 1px solid var(--copper-text, #297678);
}
.ww-btn-secondary:hover { background: var(--paper, #f7f9f9); }
.ww-btn-quiet {
  background: none;
  color: var(--copper-text, #297678);
  padding: 10px 10px;
}
.ww-btn-quiet:hover { color: var(--ink, #29364a); }
/* Destructive is its own colour and never sits flush against a harmless
   neighbour — the layout rule (≥8px gap) is the container's job; the colour
   is this one's. --brick-text is the measured 4.5:1-safe red the tokens
   already carry. It shares the box rule above: it shipped without one and
   measured 19px tall the first time anything rendered it (the dialog probe,
   Wave 3), with no consumer to notice before that. */
.ww-btn-danger {
  background: var(--card, #fff);
  color: var(--brick-text, #ad4569);
  border: 1px solid var(--brick-text, #ad4569);
}
.ww-btn-danger:hover { background: var(--paper, #f7f9f9); }
.ww-btn:focus-visible,
.ww-btn-secondary:focus-visible,
.ww-btn-quiet:focus-visible,
.ww-btn-danger:focus-visible {
  outline: 3px solid var(--ink, #29364a);
  outline-offset: 2px;
}
.ww-btn[disabled], .ww-btn-secondary[disabled], .ww-btn-danger[disabled] {
  opacity: .55;
  cursor: default;
}
/* [hidden] restated for the trio and the icon button: an author `display`
   beats the UA sheet's [hidden]{display:none}, so a button shipped hidden
   until a server offers its act ("Clear all", Wave 2b) painted anyway —
   measured on the bell, the home card and the trip tab. One rule, here,
   where the display is set; not one per page that noticed. */
.ww-btn[hidden], .ww-btn-secondary[hidden], .ww-btn-quiet[hidden],
.ww-btn-danger[hidden], .ww-btn-icon[hidden] { display: none; }

/* ---- the icon-only button ----------------------------------------------
   Allowed ONLY for the universal set (edit, delete, close, search, photo,
   more) and only with BOTH a title (the sighted tooltip) and an aria-label;
   the control-hierarchy probe measures that. 44px round, with a visible
   edge at rest — N222 reverted a borderless version because "what is
   reverted is the invisibility": a control you cannot see is not there. */
.ww-btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--muted-ink, #6b7171);
  background: var(--card, #fff);
  color: var(--ink, #29364a);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}
.ww-btn-icon:hover { background: var(--paper, #f7f9f9); border-color: var(--copper-text, #297678); color: var(--copper-text, #297678); }
.ww-btn-icon:focus-visible { outline: 3px solid var(--ink, #29364a); outline-offset: 2px; }
.ww-btn-icon[aria-expanded="true"] { background: var(--paper-dim, #e0e6e5); }

/* ---- the action row ----------------------------------------------------
   Where a card or row keeps its controls. gap:8px is the floor the sweep
   measures between interactive neighbours; wrap keeps 44px targets honest on
   a phone instead of shrinking them. `end` right-aligns (list rows);
   default left-aligns (card footers). */
.ww-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.ww-actions.end { justify-content: flex-end; }

/* ---- the overflow menu (assets/ui.js) ----------------------------------
   position:fixed against the viewport so no sticky bar or overflow:hidden
   card can clip it; z above the sticky header (60) and the page's own
   overlays (80–90) so a menu opened inside a dialog still shows, below the
   toast (200). Rows are 44px for the same reason every button here is. */
.ww-popmenu {
  position: fixed;
  z-index: 110;
  min-width: 208px;
  max-width: min(320px, calc(100vw - 16px));
  background: var(--card, #fff);
  border: 1px solid var(--line, rgba(41,54,74,.12));
  border-radius: var(--radius-control);
  box-shadow: 0 12px 34px rgba(41,54,74,.18);
  padding: 6px;
  box-sizing: border-box;
}
.ww-popmenu button {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  box-sizing: border-box;
  min-height: 44px;
  padding: 9px 12px;
  border: none;
  border-radius: 8px;
  background: none;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink, #29364a);
  text-align: left;
  cursor: pointer;
}
.ww-popmenu button .ti { font-size: 16px; width: 18px; text-align: center; color: var(--muted-ink, #6b7171); }
.ww-popmenu button span { flex: 1; min-width: 0; }
.ww-popmenu button .ww-popmenu-check { color: var(--copper-text, #297678); }
.ww-popmenu button:hover, .ww-popmenu button:focus-visible { background: var(--paper-dim, #e0e6e5); outline: none; }
.ww-popmenu button.danger { color: var(--brick-text, #ad4569); }
.ww-popmenu button.danger .ti { color: var(--brick-text, #ad4569); }
.ww-popmenu button[disabled] { opacity: .5; cursor: default; }
.ww-popmenu-sep { height: 1px; background: var(--line, rgba(41,54,74,.12)); margin: 5px 4px; }

/* ---- the banner --------------------------------------------------------
   One sentence and one action above a list, in place of the same disabled
   control repeated on every card ("Choose trip first" ×25 on the dining
   catalogue). Teaching, not reporting. */
.ww-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  border: 1px solid var(--line, rgba(41,54,74,.12));
  border-radius: var(--radius-control);
  background: var(--card, #fff);
  color: var(--ink, #29364a);
  font-size: 13.5px;
}
.ww-banner .ti { color: var(--copper-text, #297678); font-size: 18px; }
.ww-banner p { margin: 0; flex: 1; min-width: 16ch; }

/* ---- the status chip ---------------------------------------------------
   WWTrip.statusChips() / stateChip() render into these (Wave 2a). One glyph,
   one number, one noun; the tone is the meaning: needs = the advisor has
   work, wait = someone else does, ok = settled, muted = a fact. The tone
   colours are the pairs the site already measures elsewhere (--brick-text on
   its wash for "needs", the trip page's waiting/liked pills for wait/ok), so
   nothing new has to clear 4.5:1. The noun hides on a phone so a row of six
   reads as a strip, not a sentence; the title carries the whole phrase. */
.ww-chips {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  vertical-align: middle;
}
.ww-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 28px;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
  border: 1px solid transparent;
}
.ww-chip .ti { font-size: 14px; line-height: 1; }
.ww-chip b { font-weight: 700; font-variant-numeric: tabular-nums; }
.ww-chip-needs { background: #f7e9ec; color: var(--brick-text, #ad4569); border-color: #eccfd6; }
.ww-chip-wait { background: #f8f0dc; color: #6d5410; border-color: #eadfbf; }
.ww-chip-ok { background: #e6f2e8; color: #25603a; border-color: #cfe3d5; }
.ww-chip-muted { background: var(--paper-dim, #e0e6e5); color: var(--muted-ink, #6b7171); }
/* On a phone the noun is CLIPPED, never display:none — it stays the chip's
   accessible name (the same idiom nav.js uses for tab labels), and the title
   still spells the phrase for a long press. */
@media (max-width: 640px) {
  .ww-chip { padding: 3px 8px; }
  .ww-chip .ww-chip-noun { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
}

/* ---- the status line ---------------------------------------------------
   Eleven modules ship their own *-msg idiom. New inline statuses use this;
   transient successes belong to WW.toast instead. */
.ww-note {
  font-size: 13px;
  color: var(--muted-ink, #6b7171);
  line-height: 1.5;
}
.ww-note.err { color: var(--brick-text, #ad4569); }

/* ---- the empty state ---------------------------------------------------
   Icon, one sentence, one action — teaching, not reporting. */
.ww-empty {
  text-align: center;
  padding: 36px 20px;
  color: var(--muted-ink, #6b7171);
}
.ww-empty .ti { font-size: 30px; display: block; margin-bottom: 10px; color: var(--copper-text, #297678); }
.ww-empty h3 { font-size: 16px; font-weight: 600; margin: 0 0 4px; color: var(--ink, #29364a); font-family: inherit; }
.ww-empty p { font-size: 13.5px; margin: 0 auto 14px; max-width: 44ch; line-height: 1.55; }

/* ---- the modal primitive ------------------------------------------------
   One scrim, one z tier, the dvh + sticky-header pattern the dining panel
   already proved on iOS. New dialogs build on this instead of shipping a
   seventh scrim; the sheet variant is the under-640px form.

   THE CARD IS A NATIVE <dialog> (UI overhaul Wave 3, Release A). The
   `.ww-scrim > .ww-modal` div form shipped in Wave 1 with no consumer, and
   the Wave 3 critic found why it could not have one where it mattered:
   trip-work's editor is a <dialog> opened with showModal() — the TOP LAYER —
   and a fixed div at z-index 80 paints under the top layer whatever its
   number. So the card rules moved onto `dialog.ww-dialog` (assets/ui.js
   WW.ui.modal builds it; the head/body/foot classes inside are unchanged),
   the scrim is its ::backdrop, and the sheet form under 640 is the same
   card flush with the viewport bottom. `.ww-scrim` stays: nav.js's alerts
   sheet is built on it (as `.ww-scrim.ww-alerts-scrim`, with its own
   restated rules), and `.ww-modal` stays as an alias so the div form still
   reads the same — but new work opens the <dialog>. Focus and Esc are the
   primitive's contract now, not the opener's: focus moves in on open,
   returns to the opener on close, Esc closes (check-ui-dialog.js measures
   all of it). Every colour carries a fallback — twelve header pages do not
   link base.css. */
.ww-scrim {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  background: var(--scrim);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.ww-modal,
dialog.ww-dialog {
  background: var(--card, #fff);
  color: var(--ink, #29364a);
  border-radius: var(--radius-card);
  box-shadow: 0 18px 50px rgba(41,54,74,.28);
  width: 100%;
  max-width: 560px;
  max-height: calc(100dvh - 40px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
/* The UA sheet gives a <dialog> a border, 1em of padding, fit-content sizing
   and a max-width that subtracts its own margins; every one of those is
   reset here so the card measures the same as the div did. `display` is
   set ONLY while open — a bare `display:flex` on a dialog paints it closed,
   the single most common <dialog> bug — and the closed/[hidden] forms are
   restated for the same reason the button trio restates [hidden] above. */
dialog.ww-dialog {
  padding: 0;
  border: 0;
  margin: auto;
  box-sizing: border-box;
  width: min(560px, calc(100vw - 40px));
  min-height: 0;
  height: fit-content;
  font: inherit;
}
dialog.ww-dialog.ww-dialog-sm { max-width: 420px; width: min(420px, calc(100vw - 40px)); }
dialog.ww-dialog.ww-dialog-lg { max-width: 760px; width: min(760px, calc(100vw - 40px)); }
dialog.ww-dialog:not([open]),
dialog.ww-dialog[hidden] { display: none; }
dialog.ww-dialog::backdrop { background: var(--scrim, rgba(41,54,74,.5)); }
/* The page behind an open dialog does not scroll (assets/ui.js counts the
   class on and off). The top layer already makes the page inert; this keeps
   the wheel from moving it under the sheet. */
html.ww-dialog-open,
html.ww-dialog-open body { overflow: hidden; }
.ww-modal-head {
  position: sticky;
  top: 0;
  z-index: 3;
  background: var(--card, #fff);
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line, rgba(41,54,74,.12));
}
.ww-modal-head h2 { font-size: 22px; margin: 0; flex: 1; min-width: 0; overflow-wrap: anywhere; }
.ww-modal-body { overflow-y: auto; padding: 16px 18px; font-size: 17px; line-height: 1.55; overflow-wrap: anywhere; }
.ww-modal-foot > button, .ww-dialog-close { min-height: 48px; font-size: 17px; }
.ww-modal-foot > button { max-width: 100%; white-space: normal; overflow-wrap: anywhere; }
.ww-dialog-close { width: 48px; min-width: 48px; }
/* The foot's gap is 16px, not the action row's 8: a confirm puts the danger
   act beside Cancel, and the sweep's R5 keeps a destructive control 16px
   from a harmless neighbour in the same row. Wrap keeps 48px targets honest
   on a phone. */
.ww-modal-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 16px;
  padding: 12px 18px;
  border-top: 1px solid var(--line, rgba(41,54,74,.12));
  background: var(--card, #fff);
}
@media (max-width: 640px) {
  .ww-scrim { padding: 0; align-items: flex-end; }
  .ww-modal,
  dialog.ww-dialog,
  dialog.ww-dialog.ww-dialog-sm,
  dialog.ww-dialog.ww-dialog-lg {
    width: 100%;
    max-width: none;
    border-radius: var(--radius-card) var(--radius-card) 0 0;
    max-height: calc(100dvh - 24px);
  }
  /* The sheet: the dialog's auto margins centre it in the viewport; zero at
     the bottom pins it there. */
  dialog.ww-dialog { margin: auto 0 0; }
}

/* ---- motion, guarded ---------------------------------------------------
   The four things the motion system animates are overlay enter, accordion
   height, tab indicator and toast; nothing else earns a transition by
   default. Reduced motion turns all of it off wholesale. */
@media (prefers-reduced-motion: no-preference) {
  .ww-btn, .ww-btn-secondary, .ww-btn-quiet, .ww-btn-danger, .ww-btn-icon {
    transition: background-color var(--ww-fast) var(--ww-ease),
      color var(--ww-fast) var(--ww-ease),
      border-color var(--ww-fast) var(--ww-ease);
  }
  .ww-modal,
  dialog.ww-dialog[open] {
    animation: wwModalIn var(--ww-standard) var(--ww-ease);
  }
  @keyframes wwModalIn {
    from { opacity: 0; transform: translateY(14px); }
    to { opacity: 1; transform: none; }
  }
}
