/* ============================================================
   modern.css — progressive enhancements on the modern platform.
   Everything here is additive: in a browser without support the
   base app.css layout still works, just with less flourish.
   ============================================================ */

/* ---- 1. View Transitions ------------------------------------
   Switching view mode or event type cross-fades the whole result
   region, and a tapped card morphs into the detail sheet hero.

   NOTE: no `@view-transition { navigation: auto }` here. That opts into
   cross-document transitions on ordinary navigation, which on a
   single-page app just risks a stalled snapshot covering live content.
   All transitions are driven explicitly from JS instead. */

::view-transition-old(results),
::view-transition-new(results) {
  animation-duration: .34s;
  animation-timing-function: cubic-bezier(.16, 1, .3, 1);
}
::view-transition-old(results) { animation-name: vt-out; }
::view-transition-new(results) { animation-name: vt-in; }
@keyframes vt-out { to { opacity: 0; transform: translateY(-8px) scale(.99) } }
@keyframes vt-in { from { opacity: 0; transform: translateY(10px) scale(.995) } }

/* The card→sheet morph is disabled. It required the sheet to be a View
   Transition participant, which is what produced the unstable feel on
   close. Opening and closing the sheet is now instantaneous. */

/* Rail chips previously carried a persistent `view-transition-name` so the
   selected pill would slide. Removed: named participants that persist
   between transitions are the exact cause of elements being replaced by a
   stale snapshot, and duplicate names abort the transition outright. */

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* ---- 2. Container queries ----------------------------------
   Cards respond to the width of their own grid cell, not the
   viewport, so the same card works in the sheet's nearby list,
   a narrow sidebar or a wide desktop grid. */
.grid { container-type: inline-size; }
.card { container-type: inline-size; }

@container (min-width: 380px) {
  .card { padding: 22px 22px 18px; }
  .card h3 { font-size: 18.5px; }
  .meta { font-size: 13.5px; grid-template-columns: 1fr 1fr; }
  .meta div.full { grid-column: 1 / -1; }
}
@container (max-width: 260px) {
  .card .meta .lbl { display: none; }
  .card h3 { font-size: 16px; }
}

/* ---- 3. Scroll-driven progress bar --------------------------
   The per-item reveal animation was removed with the agenda view; a
   scroll-linked animation on list rows is unnecessary and previously
   caused rows to be pinned invisible. Only the header bar remains. */
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .topbar::after {
      content: ''; position: absolute; left: 0; bottom: -1px; height: 2px;
      width: 100%; transform-origin: 0 50%;
      background: linear-gradient(90deg, var(--accent), var(--accent2));
      animation: scaleX linear both;
      animation-timeline: scroll(root block);
    }
    @keyframes scaleX { from { transform: scaleX(0) } to { transform: scaleX(1) } }
  }
}

/* ---- 4. :has() driven state -- no JS class bookkeeping ------ */
/* Dim the rail when a search query is active, to signal that text
   search spans every type regardless of chip selection. */
.wrap:has(#q:not(:placeholder-shown)) .rail { opacity: .48; filter: saturate(.4); }
.rail { transition: opacity .3s var(--ease), filter .3s var(--ease); }

/* Lift the whole card when its star is hovered, without a wrapper. */
.card:has(.fav:hover) { border-color: color-mix(in srgb, #ffcc00 55%, transparent); }

/* Collapse the "clear filters" affordance unless something is set. */
.statusline:not(:has(.pill-clear)) { min-height: 20px; }

/* Give an empty result set a distinct, calmer stage. */
#results:has(.empty) ~ * { opacity: .7; }

/* ---- 5. Native dialog & popover ----------------------------- */
dialog.sheet {
  padding: 0; border: 0; max-width: none; max-height: none;
  color: var(--ink);
}
dialog.sheet::backdrop {
  background: rgba(0, 0, 0, .48);
  -webkit-backdrop-filter: blur(7px); backdrop-filter: blur(7px);
}

/* No open/close transition on the sheet. A slide-in looked fine opening but
   read as sluggish and unstable on close (the panel drifted while the
   backdrop had already gone). It now appears and dismisses instantly. */

/* ---- Mobile: a true bottom sheet with a drag handle ---------- */
@media (max-width: 720px) {
  dialog.sheet {
    width: 100%; left: 0; right: 0; top: auto; bottom: 0;
    /* dvh tracks the shrinking viewport when Safari's toolbars collapse,
       so the sheet never hides behind browser chrome. */
    height: min(90dvh, 100%);
    max-height: 90dvh;
    border-radius: 22px 22px 0 0;
    border-left: 0; border-top: 1px solid var(--hairline);
    box-shadow: 0 -18px 60px -18px rgba(0, 0, 0, .6);
  }

  /* Follows the finger during a drag; snaps back or dismisses on release.
     This is the only place the sheet is transformed, and it is driven
     directly by touch rather than by an open/close animation. */
  dialog.sheet.dragging { transform: translateY(var(--drag, 0px)); }

  .sheet-hero { padding-top: 26px; touch-action: none; }
  /* Grabber */
  .sheet-hero::before {
    content: ''; position: absolute; top: 9px; left: 50%; transform: translateX(-50%);
    width: 38px; height: 4.5px; border-radius: 99px;
    background: color-mix(in srgb, var(--ink) 26%, transparent);
  }
  .sheet-body {
    overscroll-behavior: contain;
    padding-bottom: max(28px, calc(env(safe-area-inset-bottom) + 20px));
  }
}

/* Popover panels (preferences, about, trip) */
[popover].panel {
  /* Fixed to the viewport and sized independently of content, so the panel
     never jumps or resizes as its contents change. */
  position: fixed; inset: auto; margin: 0;
  top: calc(var(--topbar-h) + 6px); right: max(16px, env(safe-area-inset-right));
  width: min(340px, calc(100vw - 24px));
  /* Bound by the space actually available BELOW the panel's own top edge,
     not by a flat viewport fraction — `top + 74dvh` could exceed the screen
     and push the footer of the panel off-screen. 14px keeps a bottom margin. */
  max-height: calc(100dvh - var(--topbar-h) - 26px - env(safe-area-inset-bottom));
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Reserve the scrollbar gutter so content does not reflow when a list
     grows past the max height. */
  scrollbar-gutter: stable;
  background: var(--surface-solid); color: var(--ink);
  border: 1px solid var(--hairline); border-radius: 14px;
  box-shadow: 0 30px 80px -24px rgba(0, 0, 0, .55);
  padding: 16px;
  /* NO open/close transition. Any fade or move here read as instability
     against the blurred, animating page behind it. The panel now appears
     and disappears instantly, which is what makes it feel solid. */
}
[popover].panel::backdrop {
  background: rgba(0, 0, 0, .22);
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
}

@media (max-width: 560px) {
  [popover].panel {
    top: auto; bottom: 0; right: 0; left: 0;
    width: 100%; max-height: 82dvh;
    border-radius: 20px 20px 0 0;
    padding-bottom: max(20px, env(safe-area-inset-bottom));
  }
}

.panel h2 { font-family: var(--display); letter-spacing: var(--tracking); font-size: 16px; margin: 0 0 10px; }
.panel-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.panel-head h2 { margin: 0 0 10px; }
.panel h3 { font-size: 10.5px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-faint); margin: 0 0 6px; }
.panel hr { border: 0; border-top: 1px solid var(--hairline); margin: 12px 0; }
.panel .hint { font-size: 11.5px; line-height: 1.5; color: var(--ink-faint); margin: 0 0 9px; max-width: var(--measure); }
.panel .field { margin-bottom: 6px; height: 38px; }
.prow { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 9px; }
.prow label { font-size: 13px; font-weight: 500; }
.panel .btn { padding: 9px 12px; font-size: 13px; }
.panel .actions { gap: 7px; }
.keys { gap: 5px; }
.keys li { font-size: 12px; }

/* Accent-colored native controls */
.panel { accent-color: var(--accent); }
.switch {
  appearance: none; width: 44px; height: 26px; border-radius: 999px; cursor: pointer;
  background: color-mix(in srgb, var(--ink) 18%, transparent);
  border: 0; position: relative; flex: 0 0 auto;
  transition: background-color .22s var(--ease);
}
.switch::after {
  content: ''; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px;
  border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.3);
  transition: transform .24s var(--ease-out);
}
.switch:checked { background: var(--accent); }
.switch:checked::after { transform: translateX(18px); }

.keys { list-style: none; margin: 0; padding: 0; display: grid; gap: 7px; }
.keys li { display: flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--ink-soft); }
.keys li span { margin-left: auto; }
kbd {
  font-family: var(--mono); font-size: 10.5px; line-height: 1;
  padding: 4px 6px; border-radius: 5px;
  border: 1px solid var(--hairline);
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  color: var(--ink-soft);
}

/* ---- 6. Modern selectors & niceties ------------------------ */

/* Smooth in-page motion and sticky-header-aware anchoring. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}
/* scroll-margin is set below, keyed to --topbar-h. */

/* Focus rings only for keyboard users, themed to the accent.
   Cards and list rows opt out: they carry their own accent stripe, and a
   second accent-coloured ring on top of it doubled up the same colour.
   Their selected/focus treatment lives in app.css instead. */
:focus-visible { outline: 2.5px solid var(--accent); outline-offset: 2px; border-radius: 6px; }
.card:focus-visible, .lrow:focus-visible { outline: none; }
:focus:not(:focus-visible) { outline: none; }

/* Themed selection and caret. */
::selection { background: color-mix(in srgb, var(--accent) 30%, transparent); }
input, select, textarea { caret-color: var(--accent); }

/* Themed, unobtrusive scrollbars. */
* { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--ink) 22%, transparent) transparent; }
*::-webkit-scrollbar { width: 9px; height: 9px; }
*::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--ink) 20%, transparent);
  border-radius: 99px; border: 2px solid transparent; background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--ink) 34%, transparent); background-clip: content-box; }

:target, .lrow, .card { scroll-margin-top: calc(var(--topbar-h) + 16px); }

/* Safe-area padding for notched devices. */
body { padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); }
/* Footer padding is handled in app.css (fixed bar, safe-area aware). */

/* Text wrapping refinements. */
h1, h2, h3, .brand-name { text-wrap: balance; }
.panel .hint, .empty p, .dl dd { text-wrap: pretty; }

/* Field sizing for the search input on mobile Safari (no zoom on focus). */
@media (max-width: 720px) {
  .field input, .field select { font-size: 16px; }
}

/* Lazy rendering for very long agenda lists. */
/* NOTE: `content-visibility: auto` was removed here. It skips rendering for
   off-screen subtrees, which prevents the `view()` scroll timeline above
   from measuring the group — the same "renders then vanishes" class of bug.
   Agenda lists are small enough that the optimisation is not needed. */

/* Cards fall back to a plain border where color-mix is unsupported. */
@supports not (color: color-mix(in srgb, red 50%, blue)) {
  :root { --hairline: rgba(128, 128, 128, .28); --ink-soft: #6b6b6b; --ink-faint: #949494; }
}

/* ============================================================
   MOBILE / TOUCH
   Phones are the realistic primary device for "what's on this
   weekend?", so this is treated as a first-class layout rather
   than a squeezed desktop.
   ============================================================ */

/* Kill the 300ms tap delay and iOS tap highlight flash. */
a, button, .card, .lrow, .chip, [role="button"] {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* Momentum scrolling containers should not rubber-band the page. */
.rail, .sheet-body, .palette ul, [popover].panel { -webkit-overflow-scrolling: touch; }

@media (max-width: 720px) {

  :root { --gutter: 16px; }

  /* --- Compact sticky header ------------------------------- */
  .topbar { padding: 10px 0 9px; gap: 8px; }
  .brand-glyph { font-size: 21px; }
  .brand-name { font-size: 19px; }

  /* --- Type rail: edge-to-edge, swipeable ----------------- */
  /* Bleeds past the gutter so chips run to the screen edge and
     read as scrollable, with the first chip still aligned to text. */
  .rail {
    margin: 0 calc(var(--gutter) * -1);
    padding: 13px var(--gutter) 12px;
    scroll-padding-left: var(--gutter);
    gap: 7px;
  }
  .chip { padding: 9px 14px 9px 11px; font-size: 13px; }

  /* --- Controls: search full width, selects share a row ---- */
  .controls { gap: 8px; padding-bottom: 14px; }
  .controls .field:first-child { flex: 1 1 100%; }
  .controls .field:not(:first-child) { flex: 1 1 0; min-width: 0; }
  .field { height: 44px; }

  /* --- Cards: single column, tighter rhythm --------------- */
  .grid { grid-template-columns: 1fr; gap: 14px; }
  .card { padding: 16px 16px 14px; }
  .card h3 { font-size: 16.5px; }
  .ribbon { font-size: 10px; padding: 3px 9px; }

  /* Primary action gets its own full-width row on narrow screens. */
  .card-actions { gap: 8px; }
  .lnk.primary { flex: 1 1 100%; }

  /* --- Agenda ---------------------------------------------- */

  /* --- Map: taller, and hint that it is interactive -------- */
  .mapview { height: 62dvh; border-radius: 14px; }
  .maplegend { font-size: 10.5px; padding: 7px 9px; max-width: 68%; gap: 3px 9px; }
  .mapzoom button { width: 38px; height: 38px; font-size: 18px; }

  /* --- Footer stays a single thin line; drop the least critical bits -- */
  .foot { gap: 6px; font-size: 10.5px; }
  .foot .dot, .foot #footCount, .foot .c { display: none; }
  .foot .sh { width: 26px; height: 26px; font-size: 13px; }

  /* --- Command palette becomes a top sheet ---------------- */
  .palette { top: 0; width: 100%; border-radius: 0 0 16px 16px; }
  .palette input { font-size: 16px; padding: 15px 16px; }
  .palette ul { max-height: 52dvh; }
  .palette li { padding: 12px; }
  .palette .hintbar { display: none; }
}

/* Very narrow phones (SE class): drop to essentials. */
@media (max-width: 380px) {
  .brand-name { font-size: 17px; }
  /* The palette button is redundant when search is already visible. */
  #cmdBtn { display: none; }
  .icon-btn { width: 36px; height: 36px; }
  .seg button { padding: 8px 8px; font-size: 12.5px; }
  .card-actions .lnk:not(.primary) { flex: 1 1 0; justify-content: center; }
}

/* Landscape phone: the sticky day headers and topbar eat too much
   vertical room, so reclaim it. */
@media (max-height: 460px) and (orientation: landscape) {
  .topbar { position: static; }
  .mapview { height: 78dvh; }
  dialog.sheet { height: 100dvh; max-height: 100dvh; border-radius: 0; }
}

/* Coarse pointers: enlarge every interactive target and drop
   hover-only affordances that can never be triggered. */
@media (pointer: coarse) {
  .icon-btn { width: 42px; height: 42px; }
  .fav { font-size: 19px; padding: 7px; margin: -7px -7px -7px 0; }
  .seg button { padding: 10px 14px; }
  .mapdot { r: 9; }
  /* Tooltips depend on hover; the tap-to-open sheet covers this instead. */
  .maptip { display: none; }
}

/* Installed as a home-screen app: no browser chrome to lean on. */
@media (display-mode: standalone) {
  .topbar { padding-top: max(10px, env(safe-area-inset-top)); }
}

/* ============================================================
   OVERLAY STABILITY
   Panels and the sheet sit above blurred, animating layers. Without
   their own compositing context they get re-rasterised on every
   background frame, which reads as jitter or drift.
   ============================================================ */

[popover].panel, dialog.sheet, .palette, .toast {
  /* Own compositing layer + isolation: paints once, then just moves. */
  contain: layout paint;
  isolation: isolate;
  /* Do not inherit backdrop blur work from ancestors. */
  backdrop-filter: none; -webkit-backdrop-filter: none;
}

/* A sticky header over a Ken Burns backdrop is the other jitter source;
   promote it so the blur is cached rather than recomputed per frame. */
.topbar { will-change: transform; transform: translateZ(0); }

/* The photo layers animate transform only — never a property that would
   force layout on the elements stacked above them. */
.bg-stack { contain: strict; }

/* Pause the drift while any overlay is open: the animation is decorative
   and its recomposite cost is what destabilises the panel above it. */
html:has([popover].panel:popover-open) .bg-layer,
html:has(dialog.sheet[open]) .bg-layer {
  animation-play-state: paused;
}

/* Panel internals must not resize the panel itself. */
.panel #tripBody { min-height: 96px; }

/* ---------- Trip planner ---------- */
.trip-list { list-style: none; margin: 0 0 14px; padding: 0; display: grid; gap: 6px; }
.trip-list li {
  display: grid; grid-template-columns: 24px 1fr auto; gap: 9px; align-items: center;
  padding: 7px 9px; border-radius: 9px;
  border: 1px solid var(--hairline);
  background: color-mix(in srgb, var(--c-a, var(--accent)) 7%, transparent);
}
.trip-list .seq {
  width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 11px; font-weight: 700; color: #fff;
  background: linear-gradient(135deg, var(--c-a, var(--accent)), var(--c-b, var(--accent2)));
}
.trip-list .ti { min-width: 0; display: grid; gap: 2px; }
.trip-list .ti strong { font-size: 13.5px; font-weight: 600; overflow-wrap: anywhere; }
.trip-list .ti .sub { font-size: 11.5px; color: var(--ink-faint); }
.trip-stats {
  display: flex; flex-wrap: wrap; gap: 3px 12px;
  padding: 8px 10px; margin-bottom: 10px;
  border-radius: 10px; font-size: 12.5px; color: var(--ink-soft);
  background: color-mix(in srgb, var(--ink) 5%, transparent);
}
.trip-stats strong { color: var(--ink); font-variant-numeric: tabular-nums; }
.hint.warn {
  color: color-mix(in srgb, #ff9500 78%, var(--ink));
  background: color-mix(in srgb, #ff9500 12%, transparent);
  border-radius: 9px; padding: 8px 10px;
}

/* Trip toggle on cards reads as selected, not just hovered. */
.lnk.trip[aria-pressed="true"] {
  background: color-mix(in srgb, var(--c-a, var(--accent)) 16%, transparent);
  border-color: color-mix(in srgb, var(--c-a, var(--accent)) 45%, transparent);
  color: color-mix(in srgb, var(--c-a, var(--accent)) 82%, var(--ink));
}

/* Archive mode: drain the colour a little, so it reads as "the past". */
html:has(#archiveBtn[aria-pressed="true"]) .bg-stack { filter: grayscale(.55) sepia(.18); }

/* ============================================================
   FAILSAFE
   Results must never be left invisible. If a View Transition is
   interrupted (tab backgrounded, rapid re-render, engine bug), the
   snapshot pseudo-elements are discarded but the live DOM stays — these
   rules guarantee the live DOM is the thing you can actually see.
   ============================================================ */

#results, .grid, .listview, .card, .lrow {
  /* No rule anywhere may leave these hidden. */
  visibility: visible !important;
}
/* `.past` dimming moves onto the inner content, so the card element itself
   is never partially transparent — that keeps `card-in` (which animates the
   card's own opacity) as the single owner of card opacity. */
.card.past { opacity: 1; }
.card.past > *:not(.ribbon) { opacity: .72; }
.card.past:hover > * { opacity: 1; }

/* The stage layers sit behind content; assert that explicitly so a
   stacking-context change can never float them over the cards. */
.bg-stack, .stage, .texture, .confetti { pointer-events: none; }
#results { position: relative; z-index: 1; }

/* ---------- List view on phones ----------
   The desktop 5-column table cannot fit a phone, so the row reflows into
   two lines: date + name on top, venue + distance beneath. The header row
   is dropped since the columns no longer align to it. */
@media (max-width: 720px) {
  .lhead { display: none; }
  .lrow {
    /* Fixed action column so the top and bottom lines stay aligned to the
       same right edge; `auto` made the two rows disagree. */
    grid-template-columns: 58px minmax(0, 1fr) 68px;
    grid-template-areas:
      "when name act"
      "when where dist";
    gap: 3px 10px;
    padding: 10px 12px;
    align-items: center;
  }
  .lwhen  { grid-area: when; }
  .lname  { grid-area: name; min-width: 0; }
  .lwhere { grid-area: where; }
  .ldist  { grid-area: dist; justify-self: end; font-size: 11.5px; }
  .lact   { grid-area: act; justify-self: end; }
  .lname .nm { font-size: 14.5px; }
  /* Comfortable touch targets without inflating row height much. */
  .lact .lnk.icon { min-height: 32px; min-width: 32px; justify-content: center; }
}

@media (max-width: 380px) {
  .lrow { grid-template-columns: 52px minmax(0, 1fr) 62px; padding: 9px 10px; }
  .lwhere .st { display: none; }   /* city + time is enough at this width */
  .lname .tag { display: none; }
}

/* Long lists: skip painting rows that are far off-screen. Safe here because
   .lrow has no scroll-driven animation attached. */
.listview { content-visibility: auto; contain-intrinsic-size: auto 600px; }

/* Round-trip endpoints in the trip list: visually distinct from the numbered
   event stops, since they are your start point rather than an event. */
.trip-list .stop-home {
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  border-style: dashed;
}
.trip-list .stop-home .seq {
  background: none; color: var(--ink-soft);
  border: 1px dashed color-mix(in srgb, var(--ink) 35%, transparent);
  font-size: 12px; font-weight: 400;
}
.trip-list .stop-home strong { font-size: 12.5px; font-weight: 600; color: var(--ink-soft); }
