.app {
  display: grid;
  grid-template-rows: var(--header-height) var(--tabbar-height) auto 1fr;
  height: 100vh;
  height: 100dvh;
}

/* -------- header -------- */
.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 18px;
  background: #12141a;
  border-bottom: 1px solid var(--border);
  gap: 16px;
  min-width: 0;
}
.app-header h1 {
  font-size: 1.05em;
  margin: 0;
  white-space: nowrap;
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.app-header h1 .accent { color: var(--accent); }
/* the name with its one-line description under it, on every screen size */
.app-header .brand { display: grid; gap: 1px; min-width: 0; }
.app-header .tagline { margin: 0; font-size: 0.72em; line-height: 1.2; color: var(--muted); white-space: nowrap; }
@media (max-width: 860px) { .app-header .tagline { white-space: normal; } }
.app-header .freshness {
  font-size: 0.78em;
  color: var(--muted);
  display: flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
}
.source-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }
.source-dot.ok { background: var(--accent2); }
.source-dot.stale { background: var(--gold); }
.source-dot.error { background: var(--danger); }

.header-search {
  flex: 1;
  max-width: 360px;
  min-width: 100px;
}
.header-search input {
  width: 100%;
  background: var(--panel);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 8px 14px;
  border-radius: 999px;
  font-size: 0.85em;
  outline: none;
}
.header-search input:focus { border-color: var(--accent); }

/* -------- body: rail + main + detail -------- */
.app-body {
  display: grid;
  grid-template-columns: var(--rail-width) 1fr;
  min-height: 0;
  position: relative;
}

.filter-rail {
  background: var(--panel);
  border-right: 1px solid var(--border);
  padding: 18px 16px;
  overflow-y: auto;
  min-height: 0;
}

.main-area {
  position: relative;
  min-height: 0;
  display: grid;
  grid-template-rows: 1fr var(--scrubber-height);
}

.view-surface {
  position: relative;
  min-height: 0;
  overflow: hidden;
}

.view-surface > .view-pane {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  display: none;
}
.view-surface > .view-pane.active { display: block; }

/* detail panel — floats over the map on desktop. top offset clears the
   Leaflet zoom/layer controls in the map's top-right corner; z-index sits
   above Leaflet's own control stacking (.leaflet-top/.leaflet-right default
   to 1000) so the panel's close button is never covered and unclickable. */
.detail-panel {
  position: absolute;
  top: 64px;
  right: 16px;
  bottom: 16px;
  width: var(--detail-width);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  z-index: 1200;
  transform: translateX(calc(100% + 32px));
  transition: transform 0.2s ease;
}
.detail-panel.open { transform: translateX(0); }

/* .dp-scroll (not .detail-panel) is the scrolling element, and
   .dp-close is its sibling rather than a scroll participant — the close
   button used to be position:absolute *inside* the scrolling panel, which
   meant its offset was relative to the scrolled content and it scrolled
   out of view with everything else. Splitting scroll (content) from
   position (panel) keeps the close button pinned to the panel's corner
   regardless of how far the content is scrolled. */
.detail-panel .dp-scroll {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  padding: 18px;
}

/* -------- scrubber strip -------- */
.scrubber-wrap {
  background: var(--panel);
  border-top: 1px solid var(--border);
  padding: 10px 18px 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.scrubber-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}
/* Desktop has room for the strip permanently; the summary is the phone-only
   collapsed affordance. */
.scrubber-summary { display: none; }

/* -------- page tabs (top, under the header) -------- */
.tab-bar {
  display: flex;
  gap: 6px;
  padding: 6px 18px 0;
  background: #12141a;
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
}
.tab-bar button {
  flex: 0 1 200px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  color: var(--muted);
  padding: 0 22px;
  /* Desktop sizes this row via the .app grid track, but the mobile media
     query below switches that track to `auto` — without a min-height the
     buttons collapse to bare text height, far under a usable tap target. */
  min-height: 42px;
  font-size: 1em;
  font-weight: 700;
  letter-spacing: 0.01em;
  white-space: nowrap;
  cursor: pointer;
  transition: color 0.15s, background 0.15s, border-color 0.15s;
}
.tab-bar button:hover { color: var(--text); }
.tab-bar button.active {
  color: #0b1a12;
  background: var(--accent);
  border-color: var(--accent);
}

/* ================= responsive ================= */
@media (max-width: 860px) {
  html, body { overflow: auto; }
  .app { height: auto; min-height: 100dvh; grid-template-rows: auto auto auto 1fr; }
  .app-header { flex-wrap: wrap; height: auto; padding: 10px 14px; }
  .header-search { order: 3; max-width: none; flex-basis: 100%; }
  /* the freshness text gives way so the help button stays on the same line */
  .app-header .freshness { flex: 1 1 200px; min-width: 0; overflow: hidden; justify-content: flex-end; }
  .app-header .freshness #freshnessText { overflow: hidden; text-overflow: ellipsis; }
  .app-header .help-btn { flex: none; }

  .app-body { grid-template-columns: 1fr; }
  .filter-rail {
    position: fixed;
    left: 0; right: 0;
    bottom: env(safe-area-inset-bottom, 0px);
    max-height: 60vh;
    border-right: none;
    border-top: 1px solid var(--border);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    box-shadow: var(--shadow-lg);
    z-index: 500;
    transform: translateY(110%);
    transition: transform 0.2s ease;
  }
  .filter-rail.open { transform: translateY(0); }

  /* Tap-dismiss for the filter sheet, and a visual cue that it's modal.
     Kept interactive only while open so it never swallows taps otherwise. */
  .rail-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 499;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
  }
  .rail-backdrop.open { opacity: 1; pointer-events: auto; }

  /* No top padding on the sheet: sticky `top: 0` pins to the content-box
     edge, so any padding-top would leave a strip above the pinned header
     where scrolled content shows through. The header supplies its own
     spacing instead. */
  .filter-rail { padding-top: 0; }
  .rail-mobile-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    position: sticky;
    top: 0;
    z-index: 3;
    /* bleed to the rail's side edges, cancelling its 16px x-padding */
    margin: 0 -16px 12px;
    padding: 4px 8px 4px 16px;
    background: var(--panel);
    border-bottom: 1px solid var(--border);
    font-size: 0.72em;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
  }
  .rail-close {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    background: none;
    border: none;
    color: var(--text);
    font-size: 1.6em;
    line-height: 1;
  }

  /* Once a horizon is chosen the strip is reference, not a control — fold it
     down to a single tappable line so the map keeps the space. */
  .scrubber-summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    width: 100%;
    min-height: 44px;
    padding: 0;
    background: none;
    border: none;
    color: var(--text);
    font: inherit;
    font-size: 0.84em;
    font-weight: 600;
    text-align: left;
  }
  .scrubber-summary-hint { color: var(--muted); font-weight: 400; font-size: 0.9em; }
  .scrubber-summary-hint::after { content: " \25BE"; }
  .scrubber-wrap.collapsed .scrubber-summary-hint::after { content: " \25B4"; }
  .scrubber-wrap.collapsed .scrubber-body { display: none; }
  .scrubber-wrap.collapsed { padding-top: 2px; padding-bottom: 2px; }

  .tab-bar { padding: 6px 12px 0; gap: 6px; }
  .tab-bar button { flex: 1; min-height: var(--tabbar-height-mobile); padding: 0 8px; font-size: 1.02em; }

  .main-area { grid-template-rows: 1fr auto; }
  .detail-panel {
    /* fixed, not absolute: as a bottom sheet it has to stay pinned to the
       viewport, not to a container that can sit below the fold. */
    position: fixed;
    top: auto; left: 0; right: 0;
    bottom: env(safe-area-inset-bottom, 0px);
    width: auto;
    /* a fixed height (not max-height) so .dp-scroll's inset:0 has a
       real box to fill — see the .dp-scroll comment above for why it
       needs to be the scrolling element instead of .detail-panel itself.
       58vh leaves enough map visible above the sheet to keep your bearings;
       at the old 75vh it read as a full-screen takeover. */
    height: 58vh;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    transform: translateY(110%);
  }
  .detail-panel.open { transform: translateY(0); }

  /* Tap-anywhere dismiss. The sheet covers most of the screen, so the
     gesture people reach for first is tapping the bit of map still
     showing — without this that tap does nothing. */
  .detail-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 1150;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
  }
  .detail-backdrop.open { opacity: 1; pointer-events: auto; }

  .rail-toggle-btn { display: inline-flex !important; }
}

@media (min-width: 861px) {
  .rail-toggle-btn { display: none !important; }
  /* The rail is a permanent sidebar here — it never opens as a sheet, so
     it has nothing to dismiss and no backdrop to dismiss it with. */
  .rail-backdrop, .rail-mobile-header, .detail-backdrop { display: none !important; }
}

/* -------- flight plan bar (planner only), docked on the right -------- */
.trip-bar {
  position: relative;
  container: tripbar / inline-size;
  display: none;
  background: var(--panel);
  border-left: 1px solid var(--border);
  overflow-y: auto;
  padding: 14px 14px 24px;
  min-height: 0;
}
.trip-bar-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.trip-bar-head h3 { margin: 0; font-size: 0.82em; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); }
.trip-bar-close { background: none; border: 1px solid var(--border); color: var(--text); border-radius: var(--radius-sm); width: 28px; height: 28px; cursor: pointer; font-size: 1.2em; line-height: 1; }
.trip-backdrop, .trip-open-tab, .trip-toggle-btn { display: none; }
@media (min-width: 861px) {
  .app[data-page="planner"] .app-body { grid-template-columns: var(--rail-width) 1fr var(--tripbar-width); }
  .app[data-page="planner"] .trip-bar { display: block; }
  .app[data-page="planner"][data-trip="closed"] .app-body { grid-template-columns: var(--rail-width) 1fr 0; }
  .app[data-page="planner"][data-trip="closed"] .trip-bar { display: none; }
  /* the hidden bar leaves a tab on the right edge of the map to bring it back */
  .app[data-page="planner"][data-trip="closed"] .trip-open-tab {
    display: block; position: absolute; right: 0; top: 14px; z-index: 600;
    background: var(--accent); color: #0b1a12; border: none; font: inherit; font-size: 0.8em; font-weight: 700;
    padding: 8px 10px; border-radius: var(--radius-sm) 0 0 var(--radius-sm); cursor: pointer; box-shadow: var(--shadow-sm);
  }
}
@media (max-width: 860px) {
  .app[data-page="planner"] .trip-toggle-btn { display: inline-flex; }
  .app[data-page="planner"] .app-body { grid-template-columns: 1fr; }
  .trip-bar {
    display: block;
    position: fixed; left: 0; right: 0; bottom: env(safe-area-inset-bottom, 0px);
    max-height: 78vh; z-index: 1210;
    border-left: none; border-top: 1px solid var(--border);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0; box-shadow: var(--shadow-lg);
    transform: translateY(110%); transition: transform 0.2s ease;
  }
  .trip-bar.open { transform: translateY(0); }
  .trip-bar-close { transform: rotate(90deg); }
  .trip-backdrop { display: block; position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 1205; opacity: 0; pointer-events: none; transition: opacity 0.2s ease; }
  .trip-backdrop.open { opacity: 1; pointer-events: auto; }
}
.app:not([data-page="planner"]) .trip-bar, .app:not([data-page="planner"]) .trip-backdrop, .app:not([data-page="planner"]) .trip-open-tab, .app:not([data-page="planner"]) .trip-toggle-btn { display: none !important; }

/* -------- two pages: planner (home) + events -------- */
/* The rail's sections and the date strip each belong to one page. */
.app[data-page="planner"] [data-page="events"],
.app[data-page="events"] [data-page="planner"] { display: none; }
.app[data-page="planner"] .main-area { grid-template-rows: 1fr; }
.app[data-page="planner"] .scrubber-wrap { display: none; }
/* weekend recs: the shared rail (base + range only), no date strip or map sections */
.app[data-page="weekends"] [data-page="planner"], .app[data-page="weekends"] [data-page="events"] { display: none; }
.app[data-page="weekends"] .scrubber-wrap { display: none; }
.app[data-page="weekends"] .main-area { grid-template-rows: 1fr; }
