:root {
  --blue-900: #0b2a4a;
  --blue-700: #1f4e78;
  --blue-100: #eaf2fb;
  --grey-500: #6b7280;
  --grey-200: #e5e7eb;
  --white: #ffffff;
  /* Location/Fishing Condition score colours (1-5) — see
     conditionStripColor's own comment in charts.js for the full
     reasoning: two SEPARATE gradients (red->orange below 3.0, light
     green->deep green at 3.0 and above), not one continuous rainbow, so
     nothing below the "acceptable" threshold ever looks green and
     nothing at/above it ever looks orange. These five values are the
     exact colours that same gradient math produces at each whole number,
     so a badge here (Week Ahead tiles, Live's current-condition badge —
     see CONDITION_COLORS/conditionColor, charts.js) always agrees with
     the strip drawn directly on the graph. */
  --cond-5: #15803d;
  --cond-4: #4eb875;
  --cond-3: #86efac;
  --cond-2: #e33f19;
  --cond-1: #dc2626;
  --cond-none: #9ca3af;
  --radius: 12px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: #f4f7fb;
  color: #111827;
}

/* One compact dark bar: site name on the left, the tab menu in the middle,
   "Updated ..." on the right — all on one row (see .sitebar wrapper in each
   page's HTML). .topbar itself is display:contents so its h1 and status
   div become flex items of .sitebar directly. Below 1200px the tabs drop to
   their own row and the name/date share a single small line above. */
.sitebar {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 2px 16px;
  background: var(--blue-900);
  color: var(--white);
  padding: 8px 16px 0;
}

.topbar {
  display: contents;
}

/* Settings only (locations.html's body.settings-page): keeps the title+tabs bar visible while
   scrolling the page. Scoped rather than added to the bare .sitebar rule, since conditions.html and
   index.html share that same class but each have their own full-page/sticky-header layouts already. */
body.settings-page .sitebar {
  position: sticky;
  top: 0;
  z-index: 100;
}

.topbar h1 {
  order: 0;
  margin: 0;
  padding-bottom: 10px;
  font-size: 1.05rem;
  line-height: 1.2;
}

/* Wordmark (the h1 in every page's header; js/backend.js renderWordmark() fills in the three spans, the h1 holds
   plain "Yepyepyep" until then and with scripts off): "yep" small / light / muted, "Yep" medium / semi-bold / teal,
   "YEP!" large / black / extra-wide / orange — the same word getting louder, all on one baseline. Colours are the
   navy-header ones (the bar is navy in every theme of the site). Archivo is self-hosted (fonts/, OFL) rather than
   linked from Google Fonts: sw.js only caches same-origin files and the pinned CDNs, so a Google Fonts file would be
   missing offline (and every page load would depend on a third party). The width axis only exists in Archivo; if the
   file fails to load, the system font still shows the same three sizes / weights, just without the stretch. */
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 62% 125%;
  font-display: swap;
  src: url("fonts/archivo-latin-var.woff2") format("woff2");
}

.topbar h1.wordmark {
  display: flex;
  align-items: baseline;
  gap: 0.28em;
  font-family: "Archivo", system-ui, sans-serif;
  font-size: 15px; /* the three spans are sized in em of this, so the whole mark scales from one number */
  line-height: 1;
  white-space: nowrap;
  min-width: max-content; /* never squeezed by its neighbours: they wrap to the next row instead */
}

.wordmark .wm-1 { font-size: 1em; font-weight: 300; font-stretch: 70%; color: #8fb3cf; }
.wordmark .wm-2 { font-size: 1.4em; font-weight: 600; color: #7fd1d8; }
.wordmark .wm-3 { font-size: 2em; font-weight: 900; font-stretch: 125%; color: #ff8a3d; letter-spacing: 0.02em; }

/* Phones: scale the whole mark down a step rather than hide it or push the tabs off screen. */
@media (max-width: 480px) {
  .topbar h1.wordmark { font-size: 12px; }
}

.updated {
  order: 3;
  margin: 0 0 10px auto;
  font-size: 0.78rem;
  color: #b7c9e0;
  white-space: nowrap;
}

/* WillyWeather API terms: credit by name and brand logo wherever its data is
   used. Sits just right of the tabs on the wide one-row bar. */
/* Keyboard users need to see where focus is: a clear outline on anything
   focusable, but only for keyboard focus (not mouse clicks). */
a:focus-visible,
button:focus-visible,
select:focus-visible,
input:focus-visible,
textarea:focus-visible,
[role="button"]:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid #f59e0b;
  outline-offset: 2px;
}

.updated.stale {
  color: #fbbf24;
  font-weight: 600;
}

.ww-credit {
  order: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  margin: 0 0 6px 4px;
  color: #b7c9e0;
  text-decoration: none;
  font-size: 0.6rem;
  line-height: 1;
}

.ww-credit img {
  display: block;
  height: 20px;
  width: auto;
}

@media (max-width: 1200px) {
  .sitebar {
    align-items: baseline;
    padding: 6px 12px 0;
  }
  .topbar h1 {
    flex: 1 1 0;
    min-width: 0;
    padding-bottom: 4px;
    font-size: 0.9rem;
  }
  .updated {
    order: 2;
    margin: 0 0 4px;
    font-size: 0.68rem;
  }
  .ww-credit {
    order: 1;
    flex-direction: row;
    align-items: center;
    gap: 5px;
    margin: 0 0 4px;
  }
  .ww-credit img {
    height: 15px;
  }
}

@media (max-width: 480px) {
  .ww-credit span {
    display: none; /* logo alone on phones — keeps the site name on one line */
  }
  .ww-credit img {
    height: 13px;
  }
}

main {
  max-width: 720px;
  margin: 0 auto;
  padding: 12px 12px 40px;
}

/* Location tab (conditions.html) ONLY — the map fills essentially the
   whole viewport below the header/tabnav, with a floating summary+graph
   panel appearing over it on marker click (see .location-hover-panel
   below, showLocationHoverPanel/selectLocationByKey in app.js) rather than
   separate scrolling sections underneath. Scoped to a body class rather
   than changing `main`/`body` globally — every other page still uses the
   normal scrolling card layout untouched. */
.map-fullpage-body {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh; /* accounts for mobile browsers' address bar resizing the viewport; 100vh above is the fallback for browsers that don't support dvh yet, not a duplicate mistake */
  /* In the installed app on Android, 100dvh can be taller than the window you can actually see (measured on a
     phone: 781px against a 725px window, until the phone is rotated), so app.js sets --app-height to the real
     window height there (syncAppHeight). Everywhere else the var is unset and this is just 100dvh again. */
  height: var(--app-height, 100dvh);
  overflow: hidden;
}

.map-fullpage-body .sitebar {
  flex: none;
}

.map-fullpage-body main {
  flex: 1;
  min-height: 0;
  max-width: none;
  margin: 0;
  padding: 0;
  position: relative;
}

.location-map-fullpage {
  width: 100%;
  height: 100%;
}

/* Chrome draws faint white lines along map-tile edges whenever tiles land on part-pixels — any browser zoom other
   than 100%, or a map that starts a fraction of a pixel down the page (the heading/toolbar heights above it depend
   on fonts and width). "plus-lighter" adds each tile's edge pixels to its neighbour's instead of letting the
   background show through, which hides the seams. The map tiles are opaque, so nothing else changes. */
.leaflet-tile-container img.leaflet-tile {
  mix-blend-mode: plus-lighter;
}

/* Wraps the map + the mark detail panel (below) in a flex row so the
   panel sits permanently to the right of the map rather than floating
   over it — real, reported friction: a Leaflet popup positioned right
   above whatever you clicked, on a map already busy with marks and
   Session lines, could easily bury the very point (and its neighbours)
   you were trying to look at. Only wraps the map itself; the hover
   panel (.location-hover-panel, above) stays a direct child of `main`
   so its own position:absolute (relative to `main`, the nearest
   positioned ancestor) is completely unaffected by this wrapper. */
.map-with-panel-layout {
  display: flex;
  width: 100%;
  height: 100%;
}
.map-with-panel-layout .location-map-fullpage {
  flex: 1;
  min-width: 0; /* a flex child's own content (the map) would otherwise refuse to shrink below its intrinsic width, quietly pushing the panel out of view instead of actually sharing space with it */
}

/* A real Leaflet popup element gets reparented straight into this panel
   while open (wireMarkPopupButtons' own popupopen handler, charts.js)
   — not a copy, the SAME DOM node, so every button/listener already
   wired onto it keeps working untouched. autoPan is turned off for
   every mark popup (bindPopup's own options) specifically because of
   this — Leaflet's own auto-pan-to-keep-the-popup-visible logic
   assumes the popup is still floating in its usual spot near the
   marker, and would compute nonsense offsets (and could visibly jerk
   the map around) once it's actually sitting over here instead. */

/** The temporary rectangle drawn while Ctrl+dragging on the map to
 * box-select marks (initMarkSelectionBoxDrag, charts.js) — a plain
 * absolutely-positioned div, sized/positioned directly in JS on every
 * mousemove rather than via any Leaflet layer, since it only ever
 * needs to exist for the duration of one drag gesture. */
.mark-select-box {
  position: absolute;
  border: 2px dashed #f59e0b;
  background: rgba(245, 158, 11, 0.12);
  pointer-events: none; /* the box itself must never intercept the very mouseup that finishes the drag */
  z-index: 1000;
}

/** REAL BUG, FOUND AND FIXED: createMarkClusterIcon's own comment
 * claimed this override already existed ("no default plugin styling —
 * see MarkerCluster.Default.css override, style.css") but it never
 * actually did. leaflet.markercluster adds its OWN classes
 * (marker-cluster, plus marker-cluster-small/-medium/-large) to the
 * exact same element mark-cluster-icon is set on — alongside it, not
 * instead of it — and MarkerCluster.Default.css gives those classes a
 * semi-transparent coloured background on both the outer element and
 * its own direct child div. That background sat directly behind this
 * icon's own satellite shapes (markShapeToCssHtml), reported directly:
 * a Catch's own "+" specifically was hard to make out, blending into
 * that colour. `> div`, not a bare descendant selector, is
 * deliberate — createMarkClusterIcon's own wrapper div (the one the
 * plugin's own "marker-cluster div" rule targets) is a direct child
 * of this element, but the individual satellite shape divs are
 * nested deeper still and DO need their own inline background color
 * (markShapeToCssHtml) to keep working — a bare "div" descendant
 * selector with !important would have wiped those out too. */
.mark-cluster-icon {
  background: none !important;
  border-radius: 0 !important;
}
.mark-cluster-icon > div {
  background: none !important;
  margin: 0 !important;
}

.mark-detail-panel {
  width: 340px;
  flex-shrink: 0;
  height: 100%;
  overflow-y: auto;
  background: var(--white);
  border-left: 1px solid var(--grey-200);
  padding: 14px;
}
/* The reparented popup no longer needs its own floating chrome (the
   little downward-pointing tip, the anchoring/tip-container Leaflet
   normally draws beneath it) now that it's sitting in a fixed panel
   rather than anchored to a specific point on the map. */
.mark-detail-panel .leaflet-popup-tip-container,
.mark-detail-panel .leaflet-popup-tip {
  display: none;
}
.mark-detail-panel .leaflet-popup-content-wrapper {
  box-shadow: none;
  border-radius: 0;
  /* Leaflet computes and inline-styles this wrapper's own width based on
     its `maxWidth` bind option (260px) and where it expected to float —
     real, reported bug: once reparented into this fixed-width panel,
     that inline width didn't actually match the panel's own width, so
     the content ended up needing horizontal scrolling to read at all.
     !important is genuinely needed here, not just for tidiness — an
     inline style (which is what Leaflet sets) otherwise always wins
     over a plain stylesheet rule regardless of selector specificity. */
  width: 100% !important;
  box-sizing: border-box;
}
.mark-detail-panel .leaflet-popup-content {
  width: 100% !important;
  max-width: 100% !important;
  margin: 13px 16px !important; /* same idea as the wrapper above — Leaflet's own inline margin assumes the popup's old floating width, not this panel's */
  box-sizing: border-box;
}
.mark-detail-panel .leaflet-popup {
  /* THREE overrides needed, not two — confirmed directly by inspecting
     the actual inline style Leaflet left on this element:
     `transform: translate3d(600px, 334px, 0px); bottom: -7px; left: -144px;`.
     position/margin (below) neutralize the margin- and left/bottom-based
     part of Leaflet's positioning, but a CSS transform applies
     REGARDLESS of position value — position:static doesn't make
     transform:translate3d(...) stop applying, unlike left/bottom (which
     genuinely are ignored once position is static). That translate3d
     was the actual, sole cause of the panel's scrollable width
     ballooning to nearly 3x its visible size — every individual
     element's own content measured well within the panel on its own;
     it was being dragged hundreds of pixels sideways by this transform,
     not overflowing from its own content. */
  position: static !important;
  margin: 0 !important;
  transform: none !important;
  width: 100% !important;
}

@media (max-width: 760px) {
  /* Not enough width for map + a 340px column on a phone — the panel
     drops below the map instead, capped to just under half the
     available height so the map itself stays usably visible above it
     rather than getting squeezed to almost nothing. */
  .map-with-panel-layout {
    flex-direction: column;
  }
  .mark-detail-panel {
    width: 100%;
    height: auto;
    max-height: 45%;
    border-left: none;
    border-top: 1px solid var(--grey-200);
  }
}

/* Small floating banner over the Live tab's map, shown while GPS is still
   resolving or failed entirely — before there's any matched location to
   open a proper hover panel for. Hidden (display:none, set in JS) once a
   location's been matched and its panel opens; the panel itself takes
   over as the page's main content at that point. Sits above the map
   (z-index) but well below the hover panel's own 2000, since the panel —
   once open — should always be able to cover it. */
.live-gps-status {
  position: absolute;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 500;
  background: var(--white);
  padding: 8px 16px;
  border-radius: 999px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.18);
  font-size: 0.85rem;
  max-width: 90%;
  text-align: center;
}

/* The Live tab's "you are here" marker (buildCurrentPositionDivIcon,
   charts.js) — a small solid dot with an expanding, fading ring animated
   around it, the standard "current location" treatment on most map apps.
   Deliberately not a teardrop pin like the fishing-location markers (see
   that function's own comment for why): this needs to read as a
   different KIND of marker at a glance, not just a different color of
   the same shape. */
.current-position-marker {
  position: relative;
  width: 22px;
  height: 22px;
}

.current-position-dot {
  position: absolute;
  top: 6px;
  left: 6px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #2563eb;
  border: 2px solid white;
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.4);
}

.current-position-pulse {
  position: absolute;
  top: 0;
  left: 0;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: rgba(37, 99, 235, 0.35);
  animation: current-position-pulse-anim 2s infinite;
}

@keyframes current-position-pulse-anim {
  0% { transform: scale(0.55); opacity: 0.8; }
  70% { transform: scale(2); opacity: 0; }
  100% { opacity: 0; }
}

/* Floating summary+graph panel, shown over the map on marker click — a
   bottom-sheet, the familiar "tap a map pin, details slide up" pattern
   from Google/Apple Maps, since this site is mobile-first and that's the
   shape people already expect a map location card to take. */
.location-hover-panel {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: 75%;
  overflow-y: auto;
  /* Explicit, not left to the default "auto" — this panel nests a
     horizontally-scrolling chart area (.location-hover-panel-chart-scroll,
     below) inside a vertically-scrolling one (this element). Relying on
     overflow-y/overflow-x properties alone to sort out which element a
     given touch gesture belongs to is a known-unreliable pattern on
     mobile — a swipe intended to scroll THIS panel down/up, if it starts
     on the chart underneath, can get captured by the chart's own
     horizontal-scroll handling instead of bubbling up, especially on
     iOS Safari, leaving the panel effectively stuck (map visible
     underneath, rest of the panel's content unreachable — exactly the
     "can't scroll" symptom this fixes). touch-action:pan-y tells the
     browser's compositor, before any scroll-bubbling logic even runs,
     that a touch anywhere in this panel should be interpreted as a
     vertical pan on THIS element specifically. */
  touch-action: pan-y;
  background: var(--white);
  border-radius: 16px 16px 0 0;
  box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.25);
  padding: 18px 16px 20px;
  /* 2000, not 1000 — Leaflet's own control container
     (.leaflet-top/.leaflet-bottom, holding the zoom +/- buttons and
     attribution) uses z-index:1000 BY DEFAULT in leaflet.css. 1000 here
     used to be an exact tie with that, which CSS resolves by paint/DOM
     order, not by "which one should logically win" — meaning if anything
     inside Leaflet's control layer repaints after this panel does (which
     can happen once tiles finish loading and the map settles a moment
     after initial page load), it could end up visually on top despite
     the "equal" z-index, covering the panel entirely with no way to
     interact with it underneath. 2000 clears that tie outright, while
     staying well below chart-modal-overlay's 10000, so tapping the graph
     inside this panel to open the full-screen view still correctly
     draws over it. */
  z-index: 2000;
}

/* Higher specificity than the shared ".chart-section, .table-section"
   rule (chosen over reordering, or !important) deliberately — needed
   because a plain single-class selector here would lose to that rule on
   source order alone (same 0,1,0 specificity, but .chart-section's own
   rule appears LATER in this file) and its box-shadow/padding/background
   would silently win, which is exactly what was happening before this
   comment existed: a visible shadow "border" around the graph that
   .location-hover-panel-chart's own box-shadow:none was never actually
   managing to remove. */
.chart-section.location-hover-panel-chart {
  box-shadow: none;
  background: none;
  padding: 0;
  margin: 0;
}

/* #hoverPanelLocationName now doubles as the panel's own heading (see
   renderLocation, app.js, which sets this <h2>'s text to the location's
   name) rather than a static "Conditions Graph" label — inherits its
   look from the shared .chart-section h2 rule, no separate styling
   needed. The badges/stats that used to sit above it in a separate
   summary card are gone; the name moved up to take that spot instead. */

.location-hover-panel-heading-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  /* Leaves clear room for .location-hover-panel-close (top:10px; right:10px;
     32px wide) sitting over this same row. */
  padding-right: 36px;
  margin-bottom: 4px;
}

/* The Live tab's collapsed banner — the only thing shown for a location
   right when it's first matched/selected (see setPanelExpanded in
   live.js), before the person taps it to reveal ratings/timings/the
   chart underneath. A real <button> (full width, no default button
   styling) rather than a div with a click handler, so it's a proper,
   keyboard-and-screen-reader-accessible toggle, not just a div that
   happens to respond to clicks. */
/* Nearest / Current Location toggle in the Live graph header (under the name, above the distance). */
.live-graph-mode {
  display: inline-block;
  margin: 2px 0 3px;
  padding: 2px 10px;
  border-radius: 999px;
  background: var(--blue-100);
  border: 1px solid var(--blue-700);
  color: var(--blue-900);
  font-size: 0.75rem;
  font-weight: 600;
  cursor: pointer;
  user-select: none;
}
.live-graph-mode[aria-checked="true"] { background: var(--blue-700); color: var(--white); }
.live-hover-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  background: var(--white);
  border: none;
  padding: 8px 36px 8px 0; /* right padding clears .location-hover-panel-close, same reasoning as .location-hover-panel-heading-row above; top/bottom now genuinely padding rather than 0, since this sticks and needs to look like a proper bar, not just inline text, once content is scrolled under it */
  margin: 0 0 4px;
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: inherit;
  /* Stays visible while the expanded content below it (ratings, timings,
     the chart) scrolls past underneath — .location-hover-panel is this
     button's nearest scrolling ancestor (overflow-y:auto), so top:0 sticks
     it there once scrolled that far. Needs its own opaque background
     (var(--white), same as the panel's) since without one, scrolled
     content would show through/behind it instead of being properly
     covered — sticky elements don't get an opaque backing for free.
     z-index above the expanded content's own default stacking so it's
     genuinely ON TOP while stuck, not just visually coincidentally above
     it. */
  position: sticky;
  top: 0;
  z-index: 5;
}

.live-hover-chevron {
  font-size: 1.3rem;
  color: var(--grey-400, #9ca3af);
  flex: 0 0 auto;
  margin-left: 10px;
}

/* Live tab's own heading row (live.html) additionally carries the
   condition/temp/wind/tide stats and the Home By timing control — all
   crammed onto the same line as the location name, per request, rather
   than each living in its own stacked section below. align-items:center
   (not baseline, the shared rule's default above) since this row mixes
   badges/inputs/buttons of very different heights, which look better
   vertically centered against each other than baseline-aligned; flex-wrap
   since there's no realistic width (especially on mobile) that fits a
   title + five stats + a time input + a button on a single visual line
   without wrapping — the wrapping is expected and fine, "same line" here
   means "one flex container", not "guaranteed no wrap". */
.live-heading-row {
  align-items: center;
  flex-wrap: wrap;
  row-gap: 8px;
}

.live-inline-stats {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

/* Same colors/meaning as the standalone .condition-badge elsewhere on the
   site, just sized down to fit inline next to a heading rather than in
   its own card — 44px there would dwarf the title text next to it here. */
.live-inline-badge {
  min-width: 28px;
  height: 28px;
  font-size: 0.78rem;
  padding: 0 6px;
}

.live-inline-stat {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--grey-700);
  white-space: nowrap;
}

.live-inline-timings {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.live-inline-timings label {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--grey-500);
}

.live-inline-timings input[type="time"] {
  padding: 4px 6px;
  border-radius: 6px;
  border: 1px solid var(--grey-200);
  font-size: 0.85rem;
}

.live-inline-timings button {
  padding: 6px 12px;
  font-size: 0.8rem;
}

/* ID selector, not ".location-hover-panel-heading-row h2" — deliberately,
   because that lost to the shared ".chart-section h2, .table-section h2"
   rule below: both are (0,1,1) specificity, and .chart-section h2 sits
   LATER in this file, so it silently won on source order alone. That's
   the exact same class of bug as the earlier box-shadow "border" issue —
   this h2 has actually been rendering at the shared rule's 0.95rem/
   margin:0 0 10px this whole time, not the smaller/flush values below,
   which is a real chunk of the "still too much space" being reported. An
   ID beats a class unconditionally, regardless of source order, so this
   can't lose the same way again. */
#hoverPanelLocationName {
  margin: 0;
  font-size: 0.85rem;
}

#hoverPanelEmptyState {
  margin: 0;
}

/* Shore/Type pickers shown above the graph ONLY while previewing an
   unsaved map click (see populatePreviewControls/showPreviewControls,
   app.js) — a real saved location has this config already, so these stay
   hidden for it. flex-wrap lets the hint line drop to its own row on
   narrow phones without the two selects themselves ever squashing. */
.location-hover-panel-preview-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px;
  margin: 0 0 10px;
}

.preview-control-label {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--grey-500);
}

.preview-control-select {
  padding: 5px 8px;
  border-radius: 6px;
  border: 1px solid var(--grey-200);
  background: var(--white);
  font-size: 0.82rem;
  color: #111827;
}

/* Full-width so it wraps onto its own line under the two selects rather
   than fighting them for horizontal space — this note is often the
   longest-running text in the row (see populatePreviewControls). */
.preview-shore-hint {
  flex-basis: 100%;
  margin: 2px 0 0;
}

/* "Add as permanent location" button (see showAddPermanentButton/
   onAddPreviewAsLocation, app.js) — full-width, matching the two
   dropdowns above it, and only ever shown while previewing AND connected
   to GitHub (see canEditLocations, app.js) — someone just viewing the
   public site with no GitHub token never sees this at all, same as
   Settings' own save button is meaningless without a connection. */
.location-preview-add-btn {
  width: 100%;
  margin: 0 0 4px;
}

/* The whole "graph area" — hint overlay + horizontally-scrolling chart —
   as one fullscreen-able unit (see setupFullscreenToggle, charts.js,
   called on this element's id in app.js), matching how Week Ahead and
   Live both do it. Chart.js's own responsive:true/maintainAspectRatio:
   false logic sizes the canvas to fill its immediate parent — if that
   parent's height were intrinsic (sized to its own content, which
   INCLUDES the canvas), the two would feed into each other and the chart
   would grow without bound on every resize tick (the exact bug already
   fixed once on the Live tab) — this fixed, explicit height is what
   prevents that, same principle as every other chart-wrap on this site. */
/* height: the base chart itself needs (378px scaled to 302px, see below)
   PLUS 28px reserved for buildConditionStripsPlugin's two condition-strip
   rows (charts.js's renderConditionsChart, layout.padding.bottom) — that
   28px has to be genuinely EXTRA canvas height, not squeezed out of the
   302px the chart already needed, or the data lines just get compressed
   closer to the new, higher bottom edge instead of clearing the strips
   (this is exactly what happened the first time the strip-dipping issue
   was "fixed": reserving the padding without also growing this height
   made it worse, not better). 302 + 28 = 330.
   28, not the 39 this used to be — 39 was a flat, deliberately-padded
   number meant to safely cover both desktop and mobile strip sizes at
   once; confirmed directly against the rendered canvas (pixel-sampled
   the actual output) that the "safe" extra was actually a second real
   bug, not a harmless margin — an 11px band of literally transparent
   canvas between the bottom of the strips and the edge of the frame.
   28 is the EXACT amount the strips use at desktop size, no more. */
.location-hover-panel-chart-frame {
  position: relative;
  height: 330px;
}

/* Same 900px threshold as isMobileDevice in app.js (Math.min(innerWidth,
   innerHeight) <= 900) — the two aren't literally the same check (this is
   a pure viewport-width media query; that also considers height, and
   reacts to live window resizes a media query wouldn't need JS for) but
   are meant to agree on "is this a phone" in practice. Keep them in sync
   if either threshold ever changes. Mobile gets a further ~20% off the
   already-reduced desktop height (302px, before the +22 strip reservation
   below), per request — desktop doesn't, since "show the whole graph, no
   scrolling" was specifically a desktop ask, not a further height cut.
   242 + 22 = 264 — 22, not 28, because mobile's own strip rows
   (buildConditionStripsPlugin's stripHeight) are genuinely shorter than
   desktop's, and this reservation is now exact rather than a flat
   over-estimate; see the desktop rule above for why exact matters. */
@media (max-width: 900px) {
  .location-hover-panel-chart-frame {
    height: 264px;
  }
}

.location-hover-panel-chart-frame:fullscreen,
.location-hover-panel-chart-frame:-webkit-full-screen {
  height: 100vh;
  width: 100vw;
  background: var(--white);
}

/* No longer positioned over the chart frame — the .graph-gesture-hint
   element moved out of #locationChartFrame and into
   .location-hover-panel-heading-row instead (conditions.html), right
   after the location name, so it reads as part of the title area rather
   than floating on top of the graph. That row's own flex layout (gap:10px)
   handles the placement; margin-left:auto pushes it to the row's right
   edge (matching where it used to sit visually) rather than crowding
   right up against the title text. Live's own .graph-gesture-hint
   (.live-chart-frame .graph-gesture-hint, further below) is a completely
   separate rule and unaffected by this — Live didn't get this same move. */
.location-hover-panel-heading-row .graph-gesture-hint {
  margin-left: auto;
  text-align: right;
}

/* Fills the frame above regardless of its current size (normal or
   fullscreen) via inset:0, rather than needing its own separate
   fullscreen-height override to keep in sync with the frame's — one
   fewer place that could drift out of sync with the other. Horizontal
   scrolling only (overflow-y:hidden) — vertical is handled by the OUTER
   .location-hover-panel's own scroll instead, so this graph never ends up
   with two independent scrollbars stacked on top of each other.
   overscroll-behavior-x:contain stops a firm horizontal swipe from
   bubbling past this element into the browser's own back/forward
   navigation gesture, which several mobile browsers bind to edge swipes —
   exactly the kind of thing that would otherwise navigate away from the
   page mid-scroll on a phone. */
.location-hover-panel-chart-scroll {
  position: absolute;
  inset: 0;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  /* The other half of the touch-action:pan-y on .location-hover-panel
     above — this element only ever needs to handle horizontal panning
     (the chart's own left-right scroll), so it explicitly disclaims
     vertical panning rather than leaving it to overflow-y:hidden alone
     to imply that. Without this, a vertical swipe starting here could
     get captured by this element instead of bubbling up to the panel's
     own vertical scroll, which is exactly the bug this pairs with fixing. */
  touch-action: pan-x;
  cursor: grab;
}

.location-hover-panel-chart-scroll.chart-scroll-dragging {
  cursor: grabbing;
}

/* Deliberately NOT width:100% — set to an explicit pixel width by app.js
   (proportional to the real time range being shown, same PIXELS_PER_HOUR
   convention as Week Ahead's row charts), so the graph renders at a
   genuinely readable size instead of being squashed to fit the phone's
   width. min-width:100% is only the fallback for the brief moment before
   that JS runs, so it doesn't flash at 0 width first. */
.location-hover-panel-chart-wrap {
  position: relative;
  height: 100%;
  min-width: 100%;
}

.location-hover-panel-chart-wrap canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100% !important;
  height: 100% !important;
}

.location-hover-panel-close {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: none;
  background: var(--grey-100);
  color: var(--grey-500);
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
}

.picker-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 14px 0;
}

.picker-row label {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--grey-500);
}

#locationSelect {
  font-size: 1rem;
  padding: 12px;
  border-radius: var(--radius);
  border: 1px solid var(--grey-200);
  background: var(--white);
  width: 100%;
}

.summary-card {
  background: var(--white);
  border-radius: var(--radius);
  padding: 16px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.08);
  margin-bottom: 16px;
}

/* Settings: Contact (any signed-in user) and Messages (Admin) cards — locationsadmin.js. */
.contact-textarea {
  width: 100%;
  box-sizing: border-box;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--grey-200);
  font: inherit;
  resize: vertical;
}
.contact-send-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.contact-count { margin: 0; }
.contact-status { margin-top: 6px; font-size: 0.85rem; min-height: 1em; }
.messages-list { margin-top: 10px; text-align: left; }
.messages-heading { font-size: 0.8rem; font-weight: 700; margin: 6px 0; }
.message-item { border: 1px solid var(--grey-200); border-radius: 8px; padding: 8px 10px; margin-bottom: 8px; }
.message-item.is-unread { border-color: var(--blue-700); }
/* Admin inbox items (renderAdminMessages) also carry .mark-edit-group, for the SAME collapsible
   header/caret/body chrome used everywhere else on the site (Map's mark filters/edit form) —
   .mark-edit-group-head/-body bring their own padding, so drop this class's own to avoid doubling
   it up. myMessagesList's own items (loadMyMessages) are plain .message-item, not collapsible,
   and keep the padding below untouched. */
.message-item.mark-edit-group { padding: 0; }
.message-item.mark-edit-group .message-actions { padding: 0 9px; }
.message-meta { font-size: 0.75rem; color: var(--grey-500); }
.message-body { white-space: pre-wrap; font-size: 0.9rem; margin: 2px 0 4px; }
.message-reply { margin-top: 6px; padding: 6px 8px; border-left: 3px solid var(--blue-700); background: #f1f5f9; border-radius: 4px; }
.message-actions { display: flex; gap: 6px; margin-top: 6px; }
.message-actions .btn-secondary, .message-delete-own { padding: 2px 10px; font-size: 0.78rem; }
.message-meta-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.message-delete-own { color: #dc2626; }
.messages-unread { font-size: 0.72rem; font-weight: 700; color: var(--white); background: var(--blue-700); border-radius: 999px; padding: 1px 7px; margin-left: 6px; vertical-align: middle; }
.messages-unread[hidden] { display: none; }
/* The count on the Settings tab (refreshMessagesBadge, js/backend.js). */
.tab-badge { display: inline-block; min-width: 16px; margin-left: 5px; padding: 0 5px; border-radius: 999px; background: #dc2626; color: #fff; font-size: 0.68rem; font-weight: 700; line-height: 16px; text-align: center; vertical-align: middle; }

/* What signing in lets you do (Settings' sign-in card). */
.footnote.signin-benefits {
  margin: 0 0 10px;
  padding-left: 20px;
  text-align: left;
}
.signin-benefits li { margin-bottom: 4px; }

.summary-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
  flex-wrap: wrap;
}

.summary-title {
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--blue-900);
}

.summary-sub {
  font-size: 0.8rem;
  color: var(--grey-500);
  margin-top: 2px;
}

.condition-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  height: 44px;
  border-radius: 999px;
  color: var(--white);
  font-weight: 700;
  font-size: 1.1rem;
  padding: 0 10px;
}

.badge-stack {
  display: flex;
  gap: 10px;
}

.badge-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
}

.badge-label {
  font-size: 0.62rem;
  font-weight: 600;
  color: var(--grey-500);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.stat-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  margin-top: 14px;
}

@media (min-width: 480px) {
  .stat-grid { grid-template-columns: repeat(3, 1fr); }
}

.stat {
  background: var(--blue-100);
  border-radius: 10px;
  padding: 10px 12px;
}

.stat .label {
  font-size: 0.72rem;
  color: var(--grey-500);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.stat .value {
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--blue-900);
  margin-top: 2px;
}

.chart-section, .table-section {
  background: var(--white);
  border-radius: var(--radius);
  padding: 14px 14px 18px;
  margin-bottom: 16px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.08);
}

.chart-section h2, .table-section h2 {
  margin: 0 0 10px;
  font-size: 0.95rem;
  color: var(--blue-900);
}

.table-scroll {
  overflow-x: auto;
  /* Without this, the browser implicitly treats overflow-y as "auto" too
     (per the CSS overflow spec, setting only one axis forces the other),
     which makes this div its own vertical scroll container — and since it
     has no height limit of its own, it never actually scrolls, so the
     sticky header below ends up stuck relative to a box that never moves
     instead of the actual page. */
  overflow-y: visible;
}

table {
  border-collapse: collapse;
  width: 100%;
  font-size: 0.85rem;
}

thead th {
  position: sticky;
  top: 0;
  background: var(--blue-700);
  color: var(--white);
  text-align: left;
  padding: 8px 10px;
  white-space: nowrap;
}

tbody td {
  padding: 8px 10px;
  border-bottom: 1px solid var(--grey-200);
  white-space: nowrap;
}

tbody tr:nth-child(even) {
  background: #fafbfd;
}

.pill {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--white);
}

.footnote {
  text-align: center;
  font-size: 0.75rem;
  color: var(--grey-500);
  margin-top: 8px;
}

.footnote a {
  color: var(--blue-700);
}

.empty-state {
  text-align: center;
  color: var(--grey-500);
  padding: 20px 0;
  font-size: 0.9rem;
}

.tabnav {
  display: flex;
  gap: 4px;
  padding: 10px 12px 0;
  max-width: 720px;
  margin: 0 auto;
}

.tabnav a {
  flex: 1;
  min-width: 0; /* REAL BUG, FOUND AND FIXED: flex items don't shrink below their own text's natural width by default — with 5 tabs this happened to just barely fit, but adding a 6th (Reports) pushed the nav's own total width past the viewport at phone width, confirmed directly against the previously-deployed 5-tab version (no overflow) vs this one (25px over at 390px) before this fix. min-width:0 lets flex:1 actually compress each tab to fit, wrapping its own label onto a second line rather than refusing to shrink at all. */
  text-align: center;
  padding: 10px;
  border-radius: 10px 10px 0 0;
  text-decoration: none;
  color: var(--grey-500);
  font-weight: 600;
  font-size: 0.85rem;
  background: #e7edf5;
}

@media (max-width: 480px) {
  .tabnav {
    padding: 8px 6px 0;
    gap: 2px;
  }
  .tabnav a {
    padding: 8px 2px;
    font-size: 0.72rem;
  }
}

.tabnav a.active {
  background: var(--white);
  color: var(--blue-900);
}

/* Tabs living inside the dark .sitebar: sit on its bottom edge, tinted for
   the dark background, centred between the title and the updated stamp. */
.sitebar .tabnav {
  order: 1;
  flex: 0 1 600px;
  margin: 0 0 0 8px; /* tabs sit right after the site name, credit right after the tabs */
  padding: 0;
  max-width: none;
}

.sitebar .tabnav a {
  padding: 8px 10px;
  background: rgba(255, 255, 255, 0.14);
  color: #cfe0f5;
}

.sitebar .tabnav a.active {
  background: var(--white);
  color: var(--blue-900);
}

@media (min-width: 1201px) {
  .sitebar .tabnav a {
    white-space: nowrap; /* one-row bar: never let a label wrap and make it taller */
  }
}

@media (max-width: 1200px) {
  .sitebar .tabnav {
    order: 3;
    margin: 0;
    flex: 1 1 100%; /* full-width row of its own... */
    max-width: none;
    padding: 0 max(0px, calc((100% - 720px) / 2)); /* ...but tabs themselves stay centred, max 720px wide as before */
  }
}

@media (max-width: 480px) {
  .sitebar .tabnav a {
    padding: 7px 2px;
  }
}

.filter-row {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
}

.thresholds-row {
  /* Min Condition and Min Consecutive Hours stay side by side regardless
     of screen width — unlike a generic .filter-row, this one deliberately
     never wraps, so it doesn't stack even on narrow phones. */
  flex-wrap: nowrap;
}

.thresholds-and-times-wrap {
  display: flex;
  flex-wrap: wrap;
  gap: 20px 32px;
  align-items: flex-start;
}

.thresholds-and-times-wrap > div {
  flex: 1 1 260px;
  min-width: 0;
}

.filter-row > div {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.filter-row label {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--grey-500);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.rating-stepper {
  display: flex;
  align-items: center;
  gap: 10px;
}

.rating-stepper-btn {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  border: 1px solid var(--grey-200);
  background: var(--white);
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--grey-500);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

.rating-stepper-btn:hover {
  background: var(--grey-100);
}

.rating-stepper-btn:disabled {
  opacity: 0.4;
  cursor: default;
  background: var(--white);
}

.rating-stepper-badge {
  /* .condition-badge is a circle sized for a single-line value already —
     just needs a fixed width here so digit-count changes (e.g. 3.0 -> 24.0)
     don't visibly resize the circle itself. */
  width: 52px;
  min-width: 52px;
  height: 52px;
  font-size: 1rem;
}

.rating-stepper-badge-neutral {
  /* Min consecutive hours isn't a 1-5 condition rating, so it doesn't get
     conditionColor()'s red-to-green scale — a fixed neutral color instead,
     same badge shape purely for visual consistency with the one next to it. */
  background: var(--blue-900) !important;
}

.day-group {
  margin-bottom: 18px;
}

.day-heading {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--blue-900);
  margin: 0 0 8px;
  padding: 8px 4px 6px;
  border-bottom: 2px solid var(--blue-100);
  /* Sticky section header: sticks just below the sticky graph panel on mobile
     (--detail-panel-height is measured and set by JS, since the panel's height
     varies — short placeholder vs. full chart, and chart height itself varies
     by viewport width). Needs an opaque background since cards scroll up
     underneath it. Each day's heading naturally replaces the previous one as
     you scroll, since every .day-group is its own sticky "slot" of the same height. */
  position: sticky;
  top: var(--detail-panel-height, 0px);
  background: var(--white);
  z-index: 4;
}

.window-card {
  background: var(--blue-100);
  border-radius: 10px;
  padding: 12px 14px;
  margin-bottom: 10px;
}

.window-card:last-child {
  margin-bottom: 0;
}

.window-card-top {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-start;
  gap: 10px;
}

.window-card .condition-badge {
  min-width: 36px;
  height: 36px;
  font-size: 0.95rem;
}

.window-card .badge-label {
  font-size: 0.58rem;
}

.window-loc {
  font-weight: 700;
  color: var(--blue-900);
  font-size: 0.95rem;
}

.window-sub {
  font-size: 0.8rem;
  color: var(--grey-500);
  margin-top: 2px;
}

.window-card .stat-grid {
  margin-top: 10px;
  grid-template-columns: repeat(3, 1fr);
}

.window-card .stat {
  background: var(--white);
  padding: 8px 10px;
}

.btn-primary, .btn-secondary {
  font-size: 0.85rem;
  font-weight: 600;
  padding: 9px 16px;
  border-radius: 8px;
  border: none;
  cursor: pointer;
}

.btn-primary {
  background: var(--blue-700);
  color: var(--white);
}

.btn-primary:hover {
  background: var(--blue-900);
}

.btn-secondary {
  background: var(--grey-200);
  color: #374151;
}

.btn-secondary:hover {
  background: #d1d5db;
}

main.wide {
  max-width: 1100px;
}

.good-layout {
  display: grid;
  gap: 16px;
  /* Mobile-first: thresholds at the top (scrolls away normally), then the graph,
     which sticks to the viewport top once you've scrolled past the thresholds,
     then the windows list scrolling beneath it. */
  grid-template-columns: 1fr;
  grid-template-areas:
    "filters"
    "detail"
    "windows";
}

.gc-filters {
  grid-area: filters;
}

.gc-windows {
  grid-area: windows;
}

.gc-detail {
  grid-area: detail;
  position: sticky;
  top: 0;
  z-index: 5;
}

.window-card {
  cursor: pointer;
  transition: box-shadow 0.15s ease, transform 0.15s ease;
}

.window-card:hover {
  box-shadow: 0 2px 8px rgba(0,0,0,0.12);
}

.window-card.selected {
  outline: 3px solid var(--blue-900);
  outline-offset: 1px;
}

.session-block {
  padding: 6px;
  margin: 6px -6px 0;
}

.session-block + .session-block {
  border-top: 1px dashed rgba(0,0,0,0.15);
  padding-top: 10px;
  margin-top: 6px;
}

/* On desktop, revert to the original side-by-side arrangement: thresholds above the
   windows list on the left, graph as its own sticky column on the right — the
   top-sticky mobile graph arrangement above is a small-screen-only affordance. */
@media (min-width: 900px) {
  .good-layout {
    grid-template-columns: 1fr 1fr;
    grid-template-areas:
      "filters detail"
      "windows detail";
    /* Grid items stretch to fill their row's height by default, which for "detail"
       means stretching to match the combined height of filters+windows (often very
       tall) — leaving the sticky panel with nothing to travel through as you scroll,
       so it never appears to move/stick. align-items:start keeps it its natural,
       short height instead, so the sticky positioning actually has room to work. */
    align-items: start;
  }

  .gc-detail {
    top: 12px;
  }

  .day-heading {
    /* Desktop: graph is a separate column, not stacked above the windows list,
       so no need to clear its height — just a small fixed offset. */
    top: 12px;
  }
}

/* On desktop, let the Conditions graph escape the centered 720px column and
   fill the actual browser width, while the rest of the page (summary card,
   table) stays at a comfortable reading width. */
@media (min-width: 900px) {
  .full-width-chart {
    width: 100vw;
    max-width: 100vw;
    margin-left: calc(-50vw + 50%);
    margin-right: calc(-50vw + 50%);
    border-radius: 0;
    box-sizing: border-box;
    padding-left: 32px;
    padding-right: 32px;
  }

  .full-width-chart canvas {
    max-height: 480px;
  }
}

.loc-filter-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin: 14px 0 8px;
}

.loc-filter-header label {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--grey-500);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.loc-filter-actions {
  display: flex;
  gap: 6px;
}

.chip-action {
  font-size: 0.72rem;
  font-weight: 600;
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px solid var(--grey-200);
  background: var(--white);
  color: var(--blue-700);
  cursor: pointer;
}

.chip-action:hover {
  background: var(--blue-100);
}

.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.loc-chip {
  font-size: 0.78rem;
  font-weight: 600;
  padding: 5px 12px;
  border-radius: 999px;
  border: 1px solid var(--grey-200);
  background: var(--white);
  color: var(--grey-500);
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
  white-space: nowrap;
}

.loc-chip.active {
  background: var(--blue-700);
  border-color: var(--blue-700);
  color: var(--white);
}

.type-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.type-chip svg {
  flex-shrink: 0;
}

.loc-edit-label {
  display: block;
  font-size: 0.72rem;
  color: var(--grey-500);
  margin-bottom: 4px;
}

.loc-edit-top {
  display: flex;
  gap: 8px;
  align-items: flex-end;
  flex-wrap: wrap;
}

.loc-time-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
}

@media (min-width: 560px) {
  .loc-time-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

@media (min-width: 900px) {
  .loc-time-grid {
    grid-template-columns: repeat(6, 1fr);
  }
}

.loc-edit-card {
  cursor: default; /* override the generic .window-card pointer cursor — this one isn't clickable */
}

/* A timing in minutes (Settings location cards — see renderTypeSection, locationsadmin.js). */
.minutes-input {
  width: 100%;
  min-width: 0;
  padding: 8px 6px;
  border-radius: 8px;
  border: 1px solid var(--grey-200);
  font-size: 0.9rem;
}

.schedule-timeline {
  /* A fixed 7-column grid guarantees all steps fit the actual screen width exactly,
     however narrow — unlike a flex row with min-widths, which needs horizontal
     scrolling once those minimums no longer add up. minmax(0, 1fr) lets each
     column shrink freely instead of being held to its content's natural width. */
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 3px;
  margin-top: 8px;
}

.schedule-step {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 0;
  background: var(--blue-100);
  border-radius: 5px;
  padding: 3px 1px;
}

.schedule-time {
  font-weight: 700;
  font-size: 0.62rem;
  color: var(--blue-900);
  white-space: nowrap;
}

.schedule-label {
  font-size: 0.4rem;
  line-height: 1.15;
  color: var(--grey-500);
  text-transform: uppercase;
  letter-spacing: 0;
  margin-top: 1px;
  text-align: center;
  word-break: break-word;
}

/* Fishing time stays full-size at every screen width — it's the number that
   actually matters. It's also the tap target that shows/hides the full timeline
   below it, which is collapsed by default to save space. */
.schedule-fishing-time {
  margin-top: 6px;
  padding: 10px 14px;
  background: var(--cond-5);
  color: var(--white);
  border-radius: 8px;
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  user-select: none;
}

.schedule-fishing-time.negative {
  background: var(--cond-1);
}

.schedule-fishing-time strong {
  font-size: 1.1rem;
}

.schedule-toggle-hint {
  float: right;
  font-size: 0.7rem;
  font-weight: 500;
  opacity: 0.85;
}

.schedule-timeline.collapsed {
  display: none;
}

/* Desktop has plenty of room — switch back from the mobile grid to a normal
   wrapped flex row of larger, more comfortably-spaced steps. */
@media (min-width: 900px) {
  .schedule-timeline {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
  }

  .schedule-step {
    padding: 8px 12px;
    min-width: 72px;
  }

  .schedule-time {
    font-size: 1rem;
  }

  .schedule-label {
    font-size: 0.68rem;
    letter-spacing: 0.02em;
    margin-top: 2px;
    white-space: nowrap;
  }
}

/* Filter toolbars (Week Ahead, Reports) — icon+badge buttons opening either a .live-card-*
   full-screen panel (Week Ahead's Type/Session Criteria/Group/Direction/Locations, week.js) or
   a .ww-candidate-* modal (Reports' own fields). Generic (not map-scoped), unlike
   .map-toolbar/.map-filter-btn, since it's used outside the Map tab. */
.filters-toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
}
.filters-gear-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 8px;
  white-space: nowrap;
}
.filters-badge { font-weight: 700; }
.filters-badge:empty { display: none; }
.filters-active-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 6px;
}
.filters-active-chips:empty {
  margin-top: 0;
}

.window-card-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
}

.loc-type-sections {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-top: 14px;
}

.loc-type-section {
  border-top: 1px solid var(--grey-200);
  padding-top: 12px;
}

.loc-type-section-header {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--blue-700);
  margin-bottom: 8px;
}

.type-photo-row {
  display: flex;
  gap: 12px;
  margin: 10px 0;
}

.type-photo-card {
  flex: 1;
  border: 3px solid transparent;
  border-radius: 12px;
  padding: 0;
  background: var(--white);
  cursor: pointer;
  overflow: hidden;
  text-align: center;
  transition: border-color 0.15s ease, transform 0.1s ease;
}

.type-photo-card img {
  width: 100%;
  height: 70px;
  object-fit: cover;
  display: block;
}

.type-photo-card span {
  display: block;
  padding: 6px 4px;
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--grey-500);
}

.type-photo-card.active {
  border-color: var(--blue-700);
}

.type-photo-card.active span {
  color: var(--blue-700);
}

.chart-modal-overlay {
  position: fixed;
  inset: 0;
  background: var(--white);
  z-index: 10000;
  display: flex;
  flex-direction: column;
}

.chart-modal-close {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 10001;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: none;
  background: var(--grey-100);
  color: var(--grey-500);
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
}

.chart-modal-inner {
  flex: 1;
  padding: 44px 10px 10px;
  min-height: 0;
}

.chart-modal-inner canvas {
  width: 100% !important;
  height: 100% !important;
}

/* Week Ahead's own version of the chart modal — contained within the
   timeline control's own section (position:absolute relative to that
   section, which is position:relative) rather than a full-screen
   position:fixed takeover. Also has room for the trip schedule
   (fishing/drive time) below the chart, not just the chart alone. */
.week-chart-modal-overlay {
  position: absolute;
  inset: 0;
  background: var(--white);
  z-index: 20;
  display: flex;
  flex-direction: column;
}

.week-chart-modal-inner {
  flex: 1;
  min-height: 0;
  padding: 44px 10px 10px;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
}

.week-chart-modal-inner canvas {
  /* Chart.js's own responsive:true logic expects a sized, position:relative
     wrapper with the canvas filling it (see .week-chart-modal-chart-wrap
     below) — forcing the canvas's own size directly, rather than its
     wrapper's, fights with that and causes stretched/clipped rendering. */
  position: absolute;
  top: 0;
  left: 0;
  width: 100% !important;
  height: 100% !important;
}

.week-chart-modal-chart-wrap {
  position: relative;
  width: 100%;
  max-width: 700px;
  height: 260px;
  flex-shrink: 0;
  align-self: center;
}

#weekScheduleContainer {
  flex-shrink: 0;
  padding: 0 4px;
}

/* Week Ahead: horizontal Gantt-style timeline */

.week-timeline-scroll {
  overflow-x: auto;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  max-height: 65vh;
  /* Keep both scrollbars visibly present at all times, not just on hover/
     scroll (which some platforms, notably macOS, do by default) — the whole
     point is never needing to hunt for them. */
  scrollbar-width: auto;
}

.week-timeline-scroll::-webkit-scrollbar {
  width: 12px;
  height: 12px;
}

.week-timeline-scroll::-webkit-scrollbar-track {
  background: var(--grey-100);
}

.week-timeline-scroll::-webkit-scrollbar-thumb {
  background: var(--grey-300, #cbd5e1);
  border-radius: 6px;
}

.week-timeline-inner {
  display: inline-block;
  min-width: 100%;
}

.week-track {
  position: relative;
}

.week-header-track {
  height: 32px;
  min-height: 32px;
  border-bottom: 1px solid var(--grey-100);
  /* Sticky within .week-timeline-scroll's own vertical scroll — stays
     pinned at the top as you scroll down through lanes, while still
     scrolling horizontally with the rest of the content since sticky only
     pins the axis given (top), not left/right. Needs an opaque background,
     or lanes scrolling underneath would show through it. */
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--white);
}

.week-hour-tick {
  position: absolute;
  top: 0;
  width: 1px;
  height: 5px;
  background: var(--grey-200);
}

.week-hour-label {
  position: absolute;
  top: 6px;
  transform: translateX(-50%);
  font-size: 0.6rem;
  color: var(--grey-400, #9ca3af);
  white-space: nowrap;
}

.week-moon-icon {
  position: absolute;
  top: 8px;
}

.week-sun-marker {
  /* Same single line as the day label/hour ticks — the hour-tick loop
     skips any tick that would land close enough to collide with one of
     these, so both can safely share this one row. */
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
}

.week-sun-tick {
  width: 1px;
  height: 5px;
  background: #b45309;
}

.week-sun-label {
  font-size: 0.62rem;
  font-weight: 700;
  color: #b45309;
  white-space: nowrap;
}

.week-lanes-wrap {
  position: relative;
}

.week-shading-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  pointer-events: none;
  z-index: 0;
}

.week-shade-band {
  position: absolute;
  top: 0;
  bottom: 0;
}

.week-lane {
  position: relative;
  z-index: 1;
  min-height: 150px;
  border-bottom: 1px solid var(--grey-100);
}

.week-header-day-tint {
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 0;
}

.week-day-boundary {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--grey-200);
  z-index: 1;
}

.week-day-label {
  position: absolute;
  top: 8px;
  padding-left: 4px;
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--grey-500);
  white-space: nowrap;
}

.week-now-line {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  background: #0f172a;
  z-index: 2;
}

.week-tile {
  position: absolute;
  top: 10px;
  bottom: 10px;
  border-radius: 12px;
  border: 1px solid rgba(15, 23, 42, 0.12);
  box-shadow: 0 1px 4px rgba(15, 23, 42, 0.14);
  cursor: pointer;
  text-align: left;
  /* Deliberately NOT overflow:hidden here — that would break the sticky
     content below, since position:sticky needs an unclipped containing
     block to slide freely within. The photo background's own rounded-
     corner clipping lives on the separate .week-tile-bg layer instead. */
}

.week-tile-bg {
  position: absolute;
  inset: 0;
  border-radius: 12px;
  overflow: hidden;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

.week-tile-content {
  /* Sticky WITHIN the tile's own bounds — bounded by .week-tile (its
     containing block, since that's position:absolute), not free to drift
     past either edge of it. Scrolling the timeline right pins this to the
     visible left edge; once the tile's own right edge would pass that
     point, it's constrained there and starts scrolling away with the rest
     of the tile, exactly matching what a mostly-off-screen tile should do. */
  position: sticky;
  left: 0;
  right: 0;
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  width: max-content;
  max-width: 100%;
}

.week-tile-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 10px;
}

.week-tile .window-loc {
  font-size: 0.85rem;
  line-height: 1.2;
}

.week-tile .badge-stack {
  gap: 8px;
}

.week-tile .window-sub {
  font-size: 0.72rem;
}

.week-tile .stat-grid {
  grid-template-columns: repeat(3, 1fr) !important;
  gap: 6px;
  margin-top: 8px;
  max-width: 320px;
}

.week-tile .stat {
  padding: 5px 6px;
}

.week-tile .stat .label {
  font-size: 0.6rem;
}

.week-tile .stat .value {
  font-size: 0.78rem;
}

.week-tile .condition-badge {
  min-width: 34px;
  height: 34px;
  font-size: 0.85rem;
}

.week-tile-stats {
  margin-top: auto;
  gap: 6px;
}

.week-hover-preview {
  position: fixed;
  z-index: 30;
  background: var(--white);
  border-radius: 10px;
  border: 1px solid var(--grey-200);
  box-shadow: 0 6px 20px rgba(15, 23, 42, 0.22);
  padding: 30px 14px 12px;
  pointer-events: none;
  width: 840px;
  max-width: calc(100vw - 20px);
  max-height: 690px;
  overflow-y: auto;
}

.week-preview-close {
  display: none;
}

.week-hover-preview.pinned {
  /* Pinned via a tile click — stays open regardless of mouse position, and
     becomes genuinely interactive (the schedule's own "tap for times"
     toggle needs real clicks to work, which pointer-events:none blocks). */
  pointer-events: auto;
}

.week-hover-preview.pinned .week-preview-close {
  display: block;
}

.week-hover-preview-chart-wrap {
  position: relative;
  width: 100%;
  height: 390px;
}

.week-hover-preview-chart-wrap canvas {
  /* Chart.js's own responsive:true logic expects exactly this pattern — a
     sized, position:relative wrapper with the canvas filling it — and
     manages the canvas's actual width/height itself from there. Forcing
     the canvas's own size directly (rather than its wrapper's) is what
     was fighting with that and causing the stretched, clipped rendering. */
  position: absolute;
  top: 0;
  left: 0;
  width: 100% !important;
  height: 100% !important;
}

/* Week Ahead (new) — row-per-location layout. Replaces the earlier
   per-session .weeknew-tile / .weeknew-lane rules entirely (this page's own
   redesign superseded that version) with a sticky-left-sidebar + scrolling-
   chart row structure: a standard "frozen first column" pattern for a
   grid that scrolls both ways, NOT the same nested-position:sticky-inside-
   position:absolute setup that broke plain CSS sticky for the old tile
   content — this is plain sticky on ordinary flex children, which is
   well-supported. */
.weeknew-header-row {
  display: flex;
  position: sticky;
  top: 0;
  z-index: 6;
  width: max-content;
  background: var(--white);
}

.weeknew-header-spacer {
  flex: 0 0 220px;
  width: 220px;
  border-bottom: 1px solid var(--grey-100);
  /* Sticky on left, composed with .weeknew-header-row's own sticky-top
     above — pins this specifically to the top-LEFT corner of the visible
     scroll area (regardless of horizontal OR vertical scroll), the same
     "frozen corner cell" pattern used for each row's own sidebar below,
     just also anchored vertically since this lives in the sticky header
     row rather than an ordinary scrolling row. */
  position: sticky;
  left: 0;
  z-index: 7;
  background: var(--white);
  display: flex;
  align-items: center;
  padding: 2px 8px;
}

.graph-gesture-hint {
  font-size: 0.6rem;
  line-height: 1.3;
  color: var(--grey-500);
  pointer-events: none;
}

.weeknew-row {
  display: flex;
  width: max-content;
  border-bottom: 1px solid var(--grey-100);
}

.weeknew-row-sidebar {
  position: sticky;
  left: 0;
  z-index: 2;
  flex: 0 0 220px;
  width: 220px;
  background: var(--white);
  /* Per-location Kayak/Land based photo (backgroundImage set inline, per
     row, in week.js — this is just the shared positioning/sizing, the
     same regardless of which specific image a given row loads). Full-bleed
     photo behind the row's content rather than a small corner mark. */
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  border-right: 1px solid var(--grey-100);
  padding: 10px 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  /* Capped to match the chart's own height (same variable, same fallback)
     so a location with many qualifying sessions can't inflate this taller
     than the graph beside it — .weeknew-row stretches both children to
     match the tallest one by default, so an uncapped sidebar was
     stretching the CHART taller too ("funny" extra height) whenever a
     row had 4+ session chips. overflow:hidden is a safety net; the actual
     scrolling happens one level in, on .weeknew-row-sessions specifically. */
  max-height: var(--weeknew-mobile-row-height, 328px);
  overflow: hidden;
}

.weeknew-row-title-line {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  flex: 0 0 auto;
}

.weeknew-row-title .window-loc {
  font-size: 0.85rem;
  line-height: 1.25;
}

.weeknew-row-title .window-sub {
  font-size: 0.72rem;
}

.weeknew-row-sessions {
  display: flex;
  flex-direction: column;
  gap: 6px;
  overflow-y: auto;
  /* flex:1 lets this take whatever vertical space is left in the sidebar
     after the name/pin line above; min-height:0 is required for a flex
     child to actually shrink below its own content's natural size —
     without it, overflow-y:auto has nothing to ever trigger on, since the
     box just keeps growing to fit every chip instead. */
  flex: 1 1 auto;
  min-height: 0;
}

.weeknew-session-chip {
  border: 1px solid var(--grey-100);
  border-radius: 8px;
  padding: 6px 8px;
  background: rgba(22, 163, 74, 0.06);
  cursor: pointer;
}

/* Wraps the two arm buttons ("+ Fishing times" / "+ Home to home") side
   by side on one line — see week.js's buildLocationRowElement. */
.weeknew-add-buttons-row {
  display: flex;
  gap: 6px;
}

/* The "+ Fishing times" / "+ Home to home" arm buttons — deliberately NOT
   styled like the green qualifying-session chips above (which are just
   informational/navigational now — see week.js), so they read as
   their own distinct action: a dashed border and the same teal accent
   used everywhere else this feature shows up (computed-session chips,
   chart markers). */
.weeknew-add-fishing-times {
  border: 1px dashed #0f766e;
  border-radius: 8px;
  padding: 6px 8px;
  background: none;
  color: #0f766e;
  font-size: 0.68rem;
  font-weight: 600;
  cursor: pointer;
  text-align: center;
  flex: 1 1 0;
  min-width: 0;
}

/* Tapped state while a row is primed for click-drag-release — see
   wireSessionRangeSelect/onArmScheduleClick in week.js. Same accent
   color as the computed-session chart markers/sidebar entries below, so
   it reads as "one connected feature" rather than an unrelated
   highlight. Shared between the qualifying-session chips (no longer
   armable themselves, but kept here in case a future caller reuses this
   class) and the add-button, which IS what actually gets it now. */
.weeknew-session-chip.armed,
.weeknew-add-fishing-times.armed {
  border-color: #0f766e;
  background: rgba(15, 118, 110, 0.12);
  box-shadow: 0 0 0 2px rgba(15, 118, 110, 0.25);
}

/* The row whose chart is currently primed for a drag — crosshair cursor is
   the main functional cue (this is the only state where a drag on this
   specific chart means something other than pan-the-board), the outline
   is a secondary at-a-glance one for anyone scanning down a long list of
   rows to see which is currently armed. */
.weeknew-row.armed-for-schedule .weeknew-row-chart {
  cursor: crosshair;
  outline: 2px solid #0f766e;
  outline-offset: -2px;
}

/* Same "primed for a drag" outline as above, for the Map tab and Map Live
   mode — those pass their own chart frame directly as onArmScheduleClick's
   "row" argument (js/week-tools.js), since neither has a per-row wrapper
   like Week Ahead's .weeknew-row to hang this off of instead. */
.location-hover-panel-chart-frame.armed-for-schedule,
.live-chart-frame.armed-for-schedule {
  cursor: crosshair;
  outline: 2px solid #0f766e;
  outline-offset: -2px;
}

/* A computed (drag-derived) schedule, persisted and shown alongside the
   auto-detected "qualifying session" chips above — deliberately a
   different accent (teal, matching the on-chart markers) so the two kinds
   of chip are never confused: qualifying-session chips are the site's own
   automatic read of the conditions data, computed-session chips are a
   specific plan the person built by hand and can come back to compare. */
.weeknew-computed-session {
  border: 1px solid #0f766e;
  border-radius: 8px;
  padding: 6px 8px;
  background: rgba(15, 118, 110, 0.07);
  position: relative;
}

.weeknew-computed-session .weeknew-session-time {
  color: #0f766e;
  padding-right: 16px; /* room for the remove button below, so it never overlaps the time text */
}

.weeknew-computed-session-remove {
  position: absolute;
  top: 4px;
  right: 4px;
  background: none;
  border: none;
  color: var(--grey-400);
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1;
  padding: 2px;
}

.weeknew-computed-session-line {
  font-size: 0.66rem;
  color: var(--grey-700);
  line-height: 1.5;
}

.weeknew-computed-session-note {
  font-size: 0.6rem;
  color: var(--grey-400);
  font-style: italic;
}

.weeknew-session-time {
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--grey-500);
  margin-bottom: 4px;
}

.weeknew-session-chip .badge-stack {
  gap: 4px;
  /* Forced single-line (see "Loc"/"Fish" label shortening in week.js,
     and the tighter sizing below) rather than wrapping into a second
     row — a wrapped second row for just one badge (Rain) read as visually
     unbalanced and made chip height inconsistent depending on how many
     badges a session happened to have. */
  flex-wrap: nowrap;
  align-items: flex-start;
}

.weeknew-session-chip .badge-item {
  /* Shrinks below its own content's natural width when the row is tight —
     needed for flex-wrap:nowrap to actually fit 5 items rather than just
     letting them overflow the chip's edge. min-width:0 is the standard
     escape hatch (flex items default to min-width:auto, which refuses to
     shrink past content size otherwise). */
  min-width: 0;
  flex: 1 1 0;
}

.weeknew-session-chip .condition-badge {
  min-width: 22px;
  height: 22px;
  font-size: 0.62rem;
  padding: 0 4px;
}

/* Temp/Wind/Rain badges — same badge "family" as the Location/Fishing
   circles (same height, same white bold text) but these show a literal
   range/percentage rather than a 1–5 score, so there's no color scale to
   apply (conditionColor() doesn't mean anything for a temperature range) —
   a fixed, distinct color per type instead, and a pill rather than a
   perfect circle since text like "14–19°" doesn't fit a 22px circle.
   Selector matches .weeknew-session-chip .condition-badge's own
   specificity (two classes) rather than just .weeknew-range-badge alone —
   otherwise that more specific existing rule's font-size/min-width would
   win over this one's, regardless of which comes later in the file. */
.weeknew-session-chip .weeknew-range-badge {
  min-width: 0;
  width: auto;
  max-width: 100%;
  padding: 0 4px;
  border-radius: 11px;
  font-size: 0.56rem;
  /* Truncates rather than wrapping/overflowing in the rare case a value
     is wider than its column has room for at this squeeze (e.g. a
     3-digit wind range) — every real value seen in practice fits without
     ever actually triggering the ellipsis, this is just a safety net. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.weeknew-session-chip .badge-label {
  font-size: 0.56rem;
  /* Truncates a label that's still wider than its column at this size —
     shortening LOCATION/FISHING to LOC/FISH (see week.js) already
     covers the normal case; this is the same safety net as the badge
     value above, for anything unexpectedly long. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

.weeknew-no-session {
  margin: 4px 0 0;
  text-align: left;
  font-size: 0.72rem;
}

.weeknew-row-chart {
  position: relative;
  flex: 0 0 auto;
  /* --weeknew-mobile-row-height is set by the inline script in
     index.html, on phone-portrait devices only — measured against the
     actual space available in the rotated layout, so a row's graph fills
     the visible board without needing a second scroll just to see its own
     top/bottom (that JS already accounts for the same +22px reserved
     strip space this fallback does — see its own comment). Falls back to
     328px (the original 300px + 28px reserved for
     buildConditionStripsPlugin's two condition-strip rows — see the
     matching +28 on .location-hover-panel-chart-frame/.live-chart-frame
     above for why that has to be added height, not padding carved out of
     the existing 300px; 28, not the 39 this used to be, since 39 was a
     flat safe-margin number confirmed — by pixel-sampling the actual
     rendered canvas — to leave an 11px band of literally empty space
     below the strips, not a harmless excess) wherever that variable
     isn't set (desktop, or JS disabled).
     .weeknew-row-sidebar's own max-height uses this SAME variable and
     MUST use the same fallback number — it exists specifically to stop a
     location with many session chips from stretching this chart taller
     than intended (.weeknew-row defaults to align-items:stretch), and a
     mismatched fallback silently breaks that: this element renders at
     328px but the sidebar caps itself at whatever ITS fallback says,
     leaving a gap between them that flex then has to stretch across —
     which is exactly the "gaps introduced" bug this comment is now
     here to prevent a repeat of. */
  min-height: var(--weeknew-mobile-row-height, 328px);
  /* NOT padding — Chart.js's own responsive-resize logic reads this
     element's clientHeight/clientWidth and subtracts ITS OWN padding
     from that when deciding what pixel size to make the canvas's actual
     drawing buffer, even though the canvas (position:absolute, inset via
     top/left:0 + width/height:100%!important below) visually fills the
     FULL box regardless of this padding, ignoring it entirely for layout
     purposes. The result was a canvas whose displayed CSS size and whose
     internal Chart.js-managed pixel size disagreed by exactly this
     padding amount (confirmed directly in the browser: 4px top + 4px
     bottom here meant Chart.js sized its internal chartArea math for an
     8px-shorter canvas than what was actually on screen, stretched to
     fill the gap) — which is exactly the "gap above/below the chart"
     bug that kept resurfacing no matter how the internal chart padding
     numbers were tuned, since the actual cause was outside the chart
     entirely. If visual breathing room around a row's chart is wanted
     later, add it as margin on a DIFFERENT element, not padding on this
     one — this one has to stay at 0 so Chart.js's own measurement of it
     matches what's actually displayed. */
  padding: 0;
}
.weeknew-row-chart canvas {
  /* Same Chart.js responsive:true pattern used everywhere else on the
     site — a sized, position:relative wrapper with the canvas absolutely
     filling it, so Chart.js manages the canvas's actual pixel size itself
     rather than fighting a directly-set one. */
  position: absolute;
  top: 0;
  left: 0;
  width: 100% !important;
  height: 100% !important;
}

/* Pin/star button — used both on the location filter chips and on each
   row's own sidebar. Unfilled (☆) vs filled (★) is the only state; no
   separate icon asset, just the two star glyphs. */
.weeknew-pin-btn {
  border: none;
  background: transparent;
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  padding: 2px 4px;
  color: var(--grey-300, #cbd5e1);
  flex-shrink: 0;
}

.weeknew-pin-btn.pinned {
  color: #eab308;
}

.weeknew-chip {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding-left: 4px;
}

.weeknew-chip-label {
  border: none;
  background: transparent;
  cursor: pointer;
  font: inherit;
  color: inherit;
  padding: 6px 10px 6px 2px;
}

.week-full-bleed {
  /* Breaks out of main.wide's centered 1100px column + 12px side padding —
     stretches to the actual browser viewport edges, since a Gantt-style
     timeline benefits from every pixel of width it can get, unlike the
     rest of the page's content. */
  width: 100vw;
  max-width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  border-radius: 0;
}

/* Double-tap-to-fullscreen (setupFullscreenToggle, js/week-tools.js): real browser fullscreen on
   the board, with a best-effort landscape lock on phones. max-height !important overrides
   the inline pixel height index.html's sizing script sets for the normal layout. */
#weekTimelineScroll:fullscreen,
#weekTimelineScroll:-webkit-full-screen {
  width: 100vw;
  height: 100vh;
  max-height: 100vh !important;
  background: var(--white);
}

/* Desktop click-and-drag-to-pan (week.js's setupDragToScroll) — grab cursor invites the drag,
   grabbing while one is in progress. Inert on touch devices. Scoped to html.weeknew-page
   (only index.html) so the other pages sharing .week-timeline-scroll don't show it. */
.weeknew-page .week-timeline-scroll {
  cursor: grab;
}
.weeknew-page .week-timeline-scroll.weeknew-dragging {
  cursor: grabbing;
}

/* Location Groups tag input (locations.html / locationsadmin.js) — click a
   suggestion to add it as a group for that location, × on a chip to
   remove it. Only existing groups (managed in the "Location Groups"
   section above) are ever suggested — this isn't a free-text field for
   inventing new group names on the fly. */
.grouptag-box {
  position: relative;
  border: 1px solid var(--grey-200);
  border-radius: 8px;
  padding: 6px 8px;
  background: var(--white);
}

.grouptag-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 4px;
}

.grouptag-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--blue-700);
  color: var(--white);
  font-size: 0.75rem;
  font-weight: 600;
  padding: 4px 8px;
  border-radius: 999px;
  white-space: nowrap;
}

.grouptag-chip button {
  background: none;
  border: none;
  color: inherit;
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1;
  padding: 0;
}

.grouptag-input {
  width: 100%;
  border: none;
  outline: none;
  font-size: 0.85rem;
  padding: 2px 0;
  font-family: inherit;
}

.grouptag-suggestions {
  display: none;
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  z-index: 10;
  background: var(--white);
  border: 1px solid var(--grey-200);
  border-radius: 8px;
  margin-top: 4px;
  max-height: 160px;
  overflow-y: auto;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}

.grouptag-suggestion {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: 8px 10px;
  cursor: pointer;
  font-size: 0.85rem;
  font-family: inherit;
}

.grouptag-suggestion:hover {
  background: var(--grey-100);
}

/* Live tab's chart frame (live.html) — fixes a real bug, not just a style
   choice: the old plain <canvas height="220"> had no CSS-defined height on
   its parent section at all (only an HTML attribute, which is just an
   initial hint, not something Chart.js's maintainAspectRatio:false sizing
   can keep anchored to). Without a definite-height container to measure
   against, the canvas's actual rendered size drifted across re-renders —
   worse on mobile, where devicePixelRatio scaling exaggerates any such
   drift — which is what "stretches vertically" actually was. A fixed,
   stable height here is the fix; the canvas fills it via the same
   position:absolute + width/height:100% pattern used everywhere else on
   this site for exactly this reason. */
.live-chart-frame {
  position: relative;
  /* 380px base + 28px reserved for buildConditionStripsPlugin's two
     condition-strip rows (charts.js's renderConditionsChart,
     layout.padding.bottom) — that 28px has to be genuinely EXTRA canvas
     height, not squeezed out of the 380px the chart already needed, or
     the data lines just get compressed closer to the new, higher bottom
     edge instead of clearing the strips. Same reasoning as the matching
     +28 on .location-hover-panel-chart-frame above — 28, not the 39 this
     used to be, since 39 was a flat safe-margin number that turned out
     to leave an 11px band of literally empty canvas below the strips
     (confirmed by pixel-sampling the actual rendered output), not a
     harmless excess. 380 + 28 = 408. */
  height: 408px;
  margin-bottom: 4px;
}

/* Deliberately, substantially shorter than desktop on mobile — not just
   the small strip-reservation difference the 402px above was (which
   only ever amounted to a 6px adjustment for accounting reasons, not a
   real size choice). This is a genuine height reduction, per request,
   freeing up vertical space on a phone screen for the rest of the panel
   (ratings, timings) rather than letting the chart claim as much of the
   screen as desktop gives it. The chart's own width/horizontal-scroll
   behavior (renderForLocation's isMobileDevice branch, .live-chart-scroll
   below) is unrelated to this and already existed before this change —
   this only touches height. 238 (reduced base) + 22 (unchanged —
   buildConditionStripsPlugin's actual mobile strip reservation, not a
   number to arbitrarily shrink along with the rest) = 260. */
@media (max-width: 900px) {
  .live-chart-frame {
    height: 260px;
  }
}

/* Wraps the canvas so it can be wider than the frame and scrolled
   horizontally on mobile (see live.js's renderForLocation) — NOT the
   canvas itself, since a canvas with position:absolute (the OLD, direct
   .live-chart-frame canvas rule below) is taken out of normal flow and
   doesn't contribute to a parent's scrollable width at all, no matter how
   wide it's set — this wrapper is what actually establishes a scrollable
   area, matching the same pattern week.js's own #weekTimelineScroll/
   #weekTimelineInner already uses for the same reason. */
.live-chart-scroll {
  position: absolute;
  inset: 0;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  touch-action: pan-x;
}

/* The actual width-establishing element — NOT the canvas itself (see the
   long comment on .live-chart-frame canvas below for why that approach
   was tried first and doesn't work). live.js sets this element's width
   directly: 100% on desktop (matches .live-chart-scroll's own width
   exactly, so nothing scrolls, unchanged from before any of this
   existed) or an explicit wide pixel value on mobile. Normal flow (not
   position:absolute) so its real width is what makes .live-chart-scroll's
   own scrollable area the right size — same reasoning as week.js's own
   #weekTimelineInner. height:100% so IT fills .live-chart-scroll
   vertically, which the canvas then fills in turn.
   */
.live-chart-wide-inner {
  height: 100%;
}

.live-chart-frame canvas {
  display: block;
  width: 100% !important;
  height: 100% !important;
  /* ALWAYS 100% — of .live-chart-wide-inner, not of .live-chart-scroll or
     the frame itself. This used to be left unset here, with live.js
     setting the canvas's OWN width directly (100% desktop, an explicit
     wide pixel value on mobile) — confirmed, by directly inspecting a
     live chart, that this never actually worked: Chart.js's own
     responsive resize logic runs after renderConditionsChart creates the
     chart and resets the canvas back down to match its parent's width
     regardless of what was set beforehand, discarding the wide value
     silently every time. Chart.js is always going to win that fight, on
     every render — the only way to actually get a wide canvas through
     Chart.js's own responsive behavior is to make its PARENT
     (.live-chart-wide-inner) wide instead, and let Chart.js's normal
     "fill 100% of my container" logic do the rest. */
}

.live-chart-frame:fullscreen,
.live-chart-frame:-webkit-full-screen {
  height: 100vh;
  width: 100vw;
  background: var(--white);
}

/* Overrides .graph-gesture-hint's own position:fixed (correct for Week
   (graphs), where the graph board effectively IS the whole page) —
   here the chart is just one section among several (GPS status, summary,
   timings form, then the chart), so pinning to the browser viewport's
   corner would show the hint disconnected from the chart itself whenever
   it isn't already scrolled to the top. position:absolute, anchored to
   this frame's own position:relative box instead, keeps it visually tied
   to the chart specifically, in or out of fullscreen. */
.live-chart-frame .graph-gesture-hint {
  position: absolute;
  top: 6px;
  right: 8px;
  z-index: 5;
}

/* Location-picker map (conditions.html's Location tab, locations.html's
   Settings tab — see renderLeafletLocationMap in charts.js). Leaflet
   REQUIRES its container to have an explicit height in CSS or it renders
   as a blank 0-height box — this is that height, plus rounding to match
   the card it sits inside. */
.location-map {
  height: 280px;
  border-radius: 10px;
  overflow: hidden;
  background: var(--grey-100);
}

/* Settings tab only — while "📍 Click map to add location" is armed (see
   toggleAddLocationClickMode/onSettingsMapClick in locationsadmin.js), the
   cursor becomes a crosshair over the map itself so it's visually obvious
   the next click places a new location rather than just panning around. */
.location-map.map-click-armed {
  cursor: crosshair;
}

/* Same active/pressed treatment as .type-photo-card.active and
   .loc-chip.active elsewhere on this page — reused here for
   #btnAddByMapClick while armed, so "this button is currently on" reads
   consistently with the rest of the Settings tab's own toggle buttons. */
.btn-secondary.active {
  background: var(--blue-700);
  color: var(--white);
}

.btn-secondary.active:hover {
  background: var(--blue-900);
}

/* Resets Leaflet's default marker-icon styling (a background/border meant
   for its own default image-based pin) so the hand-drawn SVG pins (see
   buildMapPinDivIcon, charts.js) render on their own, without an extra
   box around them. */
.location-map-pin {
  background: none;
  border: none;
}

/* Fishing-mark popups (view/edit — see buildMarkPopupViewHtml/
   buildMarkPopupEditHtml, charts.js) can run to a dozen-plus fields in edit
   mode, easily taller than the available viewport on mobile — Leaflet's
   popup container doesn't cap its own height, so without this it can render
   partly off-screen with no way to reach the Save button underneath.
   Scoped to this popup's own className (set in loadAndRenderMarks) rather
   than every .leaflet-popup-content on the site, so it doesn't affect the
   short, single-purpose popups used elsewhere. */
.mark-popup-leaflet .leaflet-popup-content {
  max-height: 60vh;
  overflow-y: auto;
  margin-right: 4px;
}

/* Bulk edit form (renderBulkEditForm, js/marks-tools.js): Save/Cancel stay pinned to the bottom of the scrolling
   .mark-detail-panel; the negative bottom offset cancels that panel's own 14px padding so the bar sits flush. */
.bulk-edit-actions {
  position: sticky;
  bottom: -14px;
  z-index: 2;
  margin: 8px 0 -14px;
  padding: 8px 0 14px;
  background: var(--white);
  border-top: 1px solid var(--grey-200);
}

/* Save/Cancel stay pinned to the bottom of the scrolling popup while editing. */
.mark-edit-actions {
  position: sticky;
  bottom: 0;
  z-index: 2;
  margin-top: 10px;
  padding: 6px 0;
  background: #fff;
  border-top: 1px solid var(--grey-200);
}

/* Mark edit form (buildMarkPopupEditHtml, js/marks-core.js): collapsible sections and single/multi-choice pills,
   laid out like the map's filter dialog. The real <select>s / tick-boxes stay in the form, hidden — the pills
   drive them (syncMarkFormPills). */
.mark-edit-field {
  display: block;
  font-size: 0.8rem;
  font-weight: 600;
  margin: 6px 0 2px;
}
.mark-edit-groups { margin-top: 8px; }
.mark-edit-group {
  margin-bottom: 6px;
  border: 1px solid var(--grey-200);
  border-radius: 8px;
}
.mark-edit-group-head {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  background: none;
  border: none;
  padding: 7px 9px;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: inherit;
}
.mark-edit-caret { display: inline-block; width: 0.9em; transition: transform 0.1s; }
.mark-edit-group-head[aria-expanded="false"] .mark-edit-caret { transform: rotate(-90deg); }
.mark-edit-group-label { font-size: 0.8rem; font-weight: 600; flex-shrink: 0; }
.mark-edit-summary { display: flex; flex-wrap: wrap; gap: 4px; min-width: 0; }
.mark-edit-group-head[aria-expanded="true"] .mark-edit-summary { display: none; }
.mark-edit-summary-chip {
  cursor: default;
  font-size: 0.72rem;
  padding: 2px 7px;
  background: #dbeafe;
  border-color: #2563eb;
  color: #1e40af;
  white-space: normal;
}
.mark-edit-group-body { padding: 0 9px 9px; }
.mark-edit-hidden-controls { display: none; }
.mark-pill-row { display: flex; flex-wrap: wrap; gap: 6px; }
.mark-pill { cursor: pointer; white-space: normal; }
.mark-pill.is-on { background: #dbeafe; border-color: #2563eb; color: #1e40af; }
.mark-pill[aria-disabled="true"] { opacity: 0.45; cursor: not-allowed; }
.mark-edit-source { margin-top: 8px; font-size: 0.75rem; color: var(--grey-500); }

/* GPS coordinates with a Copy button (view and edit popups — markGpsRowHtml, js/marks-core.js). */
.mark-gps-row {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.85rem;
  margin: 3px 0 6px;
}
.mark-gps-label { font-weight: 600; min-width: 64px; }
.mark-gps-value { font-variant-numeric: tabular-nums; }
.mark-gps-copy { padding: 2px 8px; font-size: 0.75rem; margin-left: auto; }

/* PART 1 of 2 (see the .map-has-open-popup rule below for part 2, and
   wireMapPopupZIndexToggle's own comment in charts.js for the full story)
   — raises Leaflet's own popup PANE, above every OTHER pane inside the
   same map (tile/marker/etc), so a popup can never end up visually
   covered by one of ITS OWN map's other layers. Leaflet's default popup
   pane z-index is 700 (leaflet.css, between markerPane and tooltipPane in
   Leaflet's fixed scheme).
   On its own this part turned out NOT to be enough to fix the actual bug
   reported (a mark's entry popup still rendering behind the Live tab's
   bottom-sheet hover panel and the mark filter modal on a narrow
   viewport) — see wireMapPopupZIndexToggle in charts.js for the real
   underlying cause and why a SECOND piece (below) was needed too. Kept
   anyway as still-correct, still-useful defensive layering within the map
   itself.
   Targets the PANE, not one popup's own wrapper — every popup on the map
   shares ONE .leaflet-popup-pane element (Leaflet doesn't create a
   separate pane per popup), so a z-index set on an individual
   .leaflet-popup instead can't achieve even this much: children can't
   escape their own parent's stacking context.
   Selector specificity note: plain `.leaflet-popup-pane` alone would tie
   leaflet.css's own same-specificity rule, and since style.css loads
   BEFORE leaflet.css in every page's <head>, the tie would go to
   leaflet.css (later rule wins when specificity is equal) — silently
   undoing this override. The extra `.leaflet-container` ancestor class
   here (present on every Leaflet map's own root element) raises this
   rule's specificity past that tie, the same technique already relied on
   by .mark-popup-leaflet .leaflet-popup-content just above. */
.leaflet-container .leaflet-popup-pane {
  z-index: 10500;
}

/* PART 2 of 2 — the piece that actually fixes the reported bug. See
   wireMapPopupZIndexToggle's own comment in charts.js for the full
   explanation of WHY part 1 alone wasn't enough (short version: the map's
   own container element had no `position` at all — a plain static
   element's entire contents always paint below any POSITIONED sibling
   with a z-index, like .location-hover-panel or the filter modal,
   regardless of any z-index trick played among the static element's own
   descendants). position:relative here doesn't change anything
   visually on its own (z-index stays "auto" until the class below is
   actually toggled) — it's what makes that toggle able to do anything at
   all once it IS applied. */
.location-map-fullpage,
.location-map {
  position: relative;
}

/* Toggled by wireMapPopupZIndexToggle (charts.js) directly on the map's
   own container — via Leaflet's popupopen/popupclose events — for
   exactly as long as any popup is open on that map. Raises the WHOLE map
   (not just the popup pane inside it — see PART 1 above for why that
   alone can't reach far enough) above every other floating layer on the
   page ONLY while genuinely needed, rather than permanently: leaving the
   map permanently above .location-hover-panel would otherwise defeat the
   hover panel's own entire purpose (visually covering the bottom portion
   of the map while it's showing). 10500 matches PART 1's value — nothing
   depends on them being different, they're just both clear of everything
   else on the site (10001 was previously the highest, .chart-modal-close). */
.leaflet-container.map-has-open-popup {
  z-index: 10500;
}

/* Floating "colour by" + Filters control bar over the Location/Live maps
   (see initMarkControls, charts.js) — starts hidden (display:none, set in
   HTML) until marks actually load. Top-aligned with Leaflet's zoom
   buttons (see below). It used to sit lower (56px) to stay clear of the
   Live tab's own .live-gps-status pill (horizontally centered, can run
   fairly wide with a real location name in it); that lower position is now
   kept only for narrow Live screens — see the media query just below.
   max-width: calc(100vw - 20px), not a flat 220px — a flat px cap can
   still be wider than what's actually left on a narrow phone once the
   10px right-inset is accounted for; tying it to the viewport guarantees
   the box itself never claims more width than genuinely fits, on any
   screen. */
/* On the Map tab the Live GPS message sits in the toolbar above the map (see .map-toolbar in conditions.html),
   not floating over it, so the filter bar below always lines up with the zoom buttons — in every mode, at any
   width — instead of having to move down out of the message's way. */
.map-toolbar .live-gps-status {
  position: static;
  transform: none;
  background: none;
  box-shadow: none;
  padding: 0;
  border-radius: 0;
  max-width: none;
  text-align: left;
  font-size: 0.8rem;
}

.mark-controls-bar {
  position: absolute;
  /* 12px, not 10: Leaflet's zoom box sits at top:10px but has a 2px
     translucent border, so its visible white top edge is at 12px — this
     makes the top of this bar line up with the top of the +/- buttons. */
  top: 12px;
  /* Left, not right — the side panel (see .map-with-panel-layout below)
     now permanently occupies the map's own right edge whenever a mark's
     popup is open, and this bar used to sit right on top of it there.
     55px clears Leaflet's own default zoom control (top:10px; left:10px;
     roughly 30-34px wide including its border) with a clean gap, rather
     than butting right up against it. */
  left: 55px;
  z-index: 500;
  background: var(--white);
  border-radius: 12px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.18);
  padding: 8px 10px;
  max-width: calc(100vw - 65px);
}

/* Compact icon-button style shared by the Map toolbar's palette (Colour by) and funnel (Filters) buttons —
   the active-filter count follows the funnel as "(5)", empty/hidden when no filter is on) and its
   Import/Export button (see .device-tools-menu below). */
.map-toolbar #baseMapBtn.active {
  background: var(--navy, #0b2a4a);
  color: #fff;
}
.map-toolbar .map-filter-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 8px;
  white-space: nowrap;
}
.mark-filter-badge {
  font-weight: 700;
}
/* House-with-+ in the Map toolbar (js/homes.js): arms the next map click to add a home. */
/* Week Ahead: "From: GPS / <home town>" in front of a row's + Fishing times / + Home to home (week.js). */
.weeknew-trip-origin {
  font-size: 0.75rem;
  padding: 3px 6px;
  border-radius: 999px;
  border: 1px solid var(--grey-200);
  background: var(--white);
  color: var(--blue-900);
  max-width: 100%;
}

.map-toolbar .map-home-btn {
  display: inline-flex;
  align-items: center;
  padding: 3px 8px;
}
.map-toolbar .map-home-btn.active {
  background: var(--blue-700);
  border-color: var(--blue-700);
  color: var(--white);
}
/* Moving a location pin (js/location-move.js): while held it grows and glows over the 2 s it takes to pick up;
   once picked up the cursor is the pin (--pin-cursor, set from the pin's own picture) until it's put down. */
.location-map-pin.pin-holding svg {
  transform-origin: 50% 100%;
  animation: pin-hold 2s linear forwards;
}
@keyframes pin-hold {
  from { transform: scale(1); filter: drop-shadow(0 0 0 rgba(37, 99, 235, 0)); }
  to { transform: scale(1.3); filter: drop-shadow(0 0 6px rgba(37, 99, 235, 0.9)); }
}
#locationMap.pin-carrying,
#locationMap.pin-carrying .leaflet-grab,
#locationMap.pin-carrying .leaflet-interactive {
  cursor: var(--pin-cursor, crosshair) !important;
}
.pin-move-hint .pin-move-undo {
  border: none;
  background: none;
  padding: 0;
  color: var(--blue-700);
  font: inherit;
  font-weight: 700;
  text-decoration: underline;
  cursor: pointer;
}

.home-popup-title { font-weight: 600; margin-bottom: 6px; }
.home-popup-status { margin-top: 4px; font-size: 0.8rem; }
.mark-filter-badge:empty {
  display: none;
}

.mark-active-filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

/* No top margin at all when there's nothing to show (renderActiveFilterChips
   leaves this element's innerHTML empty when no filters are active) — the
   6px margin-top above is only meant to separate the chip row FROM the
   controls row when a chip row actually exists. */
.mark-active-filter-chips:empty {
  margin-top: 0;
}

/* Brief highlight flash when a location's edit card is jumped to from
   the Settings map (see jumpToLocationRow, locationsadmin.js) — confirms
   which card the map click actually landed on, since scrollIntoView
   alone can be easy to miss on a long list. */
.loc-edit-card-highlight {
  animation: loc-edit-card-flash 1.8s ease-out;
}

@keyframes loc-edit-card-flash {
  0%, 20% {
    box-shadow: 0 0 0 3px var(--blue-700);
  }
  100% {
    box-shadow: none;
  }
}

/* WillyWeather candidate picker — shown after clicking the Settings map
   with "click to add" armed and the willyweather-search Worker configured
   (see showLocationCandidatePicker, locationsadmin.js). A centered,
   bounded dialog rather than the site's other (fullscreen) modal pattern
   used for chart previews (.chart-modal-overlay) — this needs to show a
   short scrollable list plus two buttons, not take over the whole screen. */
.ww-candidate-overlay {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.5);
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.ww-candidate-dialog {
  position: relative;
  background: var(--white);
  border-radius: var(--radius);
  padding: 20px;
  max-width: 420px;
  width: 100%;
  max-height: 80vh;
  overflow-y: auto;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.25);
}

/* Filter dialog (showMarkFilterModal, js/marks-tools.js): Clear all / Done stay pinned to the bottom of the
   scrolling dialog; the negative offsets cancel the dialog's own 20px padding so the bar sits flush. */
.mark-filter-actions {
  position: sticky;
  bottom: -20px;
  z-index: 2;
  margin: 6px -20px -20px;
  padding: 10px 20px 20px;
  background: var(--white);
  border-top: 1px solid var(--grey-200);
}

.ww-candidate-close {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: none;
  background: var(--grey-100);
  color: var(--grey-500);
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
}

.ww-candidate-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.ww-candidate-item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  width: 100%;
  padding: 10px 12px;
  border-radius: 8px;
  border: 1px solid var(--grey-200);
  background: var(--white);
  cursor: pointer;
  text-align: left;
}

.ww-candidate-item:hover {
  background: var(--grey-100);
  border-color: var(--blue-700);
}

.ww-candidate-name {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--blue-900);
}

.ww-candidate-meta {
  font-size: 0.78rem;
  color: var(--grey-500);
}

/* Sync tab (sync.html/sync.js) — the review list of import candidates.
   Plain scrolling stack of rows rather than a <table>: each row needs a
   variable-height textarea plus several inputs, which fights a table's
   fixed-row-height assumptions, and this site already leans on styled divs
   rather than tables for other admin-y lists (see locationsadmin.js). */
.sync-review-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 70vh;
  overflow-y: auto;
  padding-right: 4px;
}

.sync-row {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 10px 12px;
  border-radius: 8px;
  border: 1px solid var(--grey-200);
  background: var(--white);
}

.sync-row-check {
  padding-top: 3px;
}

.sync-row-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.sync-row-badges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

.sync-row-coords,
.sync-row-date {
  font-size: 0.75rem;
  color: var(--grey-500);
}

.sync-row-inputs {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.sync-row-inputs select,
.sync-row-inputs input[type="text"],
.sync-row-inputs input[type="number"] {
  padding: 6px 8px;
  border-radius: 6px;
  border: 1px solid var(--grey-200);
  font-size: 0.85rem;
  min-width: 90px;
}

.sync-row-inputs input[type="number"] {
  width: 90px;
}

.sync-row-body textarea {
  width: 100%;
  padding: 6px 8px;
  border-radius: 6px;
  border: 1px solid var(--grey-200);
  font-size: 0.85rem;
  font-family: inherit;
  resize: vertical;
  box-sizing: border-box;
}

/* Pill colour variants — .pill itself (above) only sets shape/type, not
   colour, so every user is expected to supply one; these three are the
   Sync tab's own. */
.sync-pill-new {
  background: var(--cond-5);
}
.sync-pill-visits {
  background: var(--blue-700);
}

/* ---------------------------------------------------------------------
   Week Ahead on phones (screens up to 700px wide, either orientation):
   the "compare board". The graphs are the point — you scan them by eye and
   only then look at which location it is — so instead of a 220px name panel
   beside every graph (which leaves a phone almost no room for the graph),
   each row's name becomes a small pill floating on the graph's top-left
   corner. It is sticky, so it stays in view while you scroll sideways
   through the week, and every row shares the one sideways scroll and the
   sticky day header, so all the graphs line up for comparison. The details
   button (ⓘ) on the pill opens the row's session chips and planning
   buttons in a popover. (js/week-tools.js / week.js: sidebarWidth(),
   the details button in buildLocationRowElement.)
   --------------------------------------------------------------------- */
.weeknew-row-details-btn {
  display: none; /* phones only, below */
}

@media (max-width: 700px) {
  .weeknew-page .week-timeline-scroll {
    /* Height of each row's graph on a phone (incl. the two condition strips):
       short enough that 2-3 locations fit on screen at once. */
    --weeknew-mobile-row-height: 210px;
  }
  .weeknew-page .week-timeline-scroll + .footnote {
    display: none; /* the gesture hints text costs a lot of height on a phone */
  }
}

/* The compare board below applies at every width (phones AND desktop): only
   the row height and the hints footnote above are phone-specific. */
@media (min-width: 701px) {
  .weeknew-row-title-line .window-loc {
    font-size: 0.85rem;
  }
  .weeknew-row-title-line .window-sub {
    font-size: 0.7rem;
  }
}
@media all {
  .weeknew-header-spacer {
    display: none;
  }
  .weeknew-row {
    position: relative;
  }
  .weeknew-row-sidebar {
    flex: 0 0 0;
    width: 0;
    padding: 0;
    gap: 0;
    border: 0;
    max-height: none;
    overflow: visible;
    background-image: none !important; /* the per-row photo is set inline in week.js */
    background: transparent;
  }
  .weeknew-row-title-line {
    position: absolute;
    top: 6px;
    left: 6px;
    z-index: 3;
    display: flex;
    align-items: center;
    gap: 2px;
    max-width: calc(100vw - 20px);
    padding: 2px 4px 2px 2px;
    background: rgba(255, 255, 255, 0.9);
    border-radius: 999px;
    box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3);
  }
  .weeknew-row-title-line .weeknew-row-title {
    min-width: 0;
  }
  .weeknew-row-title .window-loc {
    font-size: 0.72rem;
    line-height: 1.15;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 55vw;
  }
  .weeknew-row-title .window-sub {
    font-size: 0.6rem;
    line-height: 1.1;
  }
  .weeknew-row-details-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    border: none;
    border-radius: 50%;
    background: var(--blue-100, #eaf2fb);
    color: var(--blue-900);
    font-size: 0.95rem;
    line-height: 1;
    cursor: pointer;
  }
  .weeknew-row.details-open .weeknew-row-details-btn {
    background: var(--blue-900);
    color: var(--white);
  }
  /* Sessions and planning buttons: hidden until the ⓘ button is tapped. */
  .weeknew-row-sessions {
    display: none;
  }
  .weeknew-row.details-open .weeknew-row-sidebar {
    z-index: 5; /* above later rows' sidebars, below the sticky day header (6-7) */
  }
  .weeknew-row.details-open .weeknew-row-sessions {
    display: flex;
    position: absolute;
    top: 42px;
    left: 6px;
    width: min(300px, calc(100vw - 24px));
    max-height: 60vh;
    overflow-y: auto;
    padding: 8px;
    background: var(--white);
    border-radius: 12px;
    box-shadow: 0 6px 22px rgba(0, 0, 0, 0.35);
    z-index: 4;
  }
}

/* ---------------------------------------------------------------------
   Floating location pill + details tile on every graph (Location and Live
   tabs; Week Ahead has its own phone-layout version above). Built by
   mountLocationPill (js/week-tools.js) inside the graph frame, so it stays
   put while the graph scrolls sideways and remains visible in fullscreen.
   The tile (opened with the pill's ⓘ) takes the Kayak/Land based photo as
   its background, under a light wash so the text stays readable.
   --------------------------------------------------------------------- */
.loc-pill {
  position: absolute;
  top: 6px;
  left: 6px;
  z-index: 6;
  display: flex;
  align-items: center;
  gap: 4px;
  max-width: calc(100% - 16px);
  padding: 2px 4px 2px 10px;
  background: rgba(255, 255, 255, 0.9);
  border-radius: 999px;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3);
}
.loc-pill[hidden],
.loc-tile[hidden],
.loc-pill-sub[hidden] {
  display: none;
}
.loc-pill-name {
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1.15;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 55vw;
}
.loc-pill-sub {
  font-size: 0.6rem;
  line-height: 1.1;
  color: var(--grey-500);
}
.loc-pill-details-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border: none;
  border-radius: 50%;
  background: var(--blue-100, #eaf2fb);
  color: var(--blue-900);
  font-size: 0.95rem;
  line-height: 1;
  cursor: pointer;
}
.loc-pill.open .loc-pill-details-btn {
  background: var(--blue-900);
  color: var(--white);
}
/* Gear after the graph panel's location name (Map page, Admin only) — opens the location editor (js/location-editor.js). */
.location-edit-gear-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border: none;
  border-radius: 50%;
  background: var(--blue-100, #eaf2fb);
  color: var(--blue-900);
  cursor: pointer;
  align-self: center;
}
.location-edit-gear-btn[hidden] { display: none; }
/* Kayak / Land based pills after the name and gear, for a location usable for both (renderLocationTypePills, app.js). */
.location-type-pills { display: flex; flex-wrap: wrap; gap: 4px; align-self: center; }
.location-type-pills:empty { display: none; }
.location-type-pills .mark-pill { font-size: 0.72rem; padding: 3px 10px; }

/* Location editor dialog — reuses the mark edit form's sections and pills (.mark-edit-group, .mark-pill). */
.loc-editor { max-width: 460px; width: 100%; }
.loc-editor-hm-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 10px; }
.loc-editor-newtype { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
.loc-editor-status { min-height: 1.2em; margin-top: 8px; font-size: 0.8rem; }
.loc-editor-remove { margin-top: 12px; }
.loc-editor-remove-confirm {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  margin-top: 8px;
  padding: 8px;
  border: 1px solid #fecaca;
  background: #fef2f2;
  border-radius: 6px;
  font-size: 0.85rem;
}
.loc-editor-remove-confirm[hidden] { display: none; }
.loc-editor-remove-confirm > div { flex-basis: 100%; }
.loc-tile {
  position: absolute;
  top: 42px;
  left: 6px;
  z-index: 5;
  box-sizing: border-box;
  width: min(320px, calc(100% - 12px));
  padding: 10px;
  border-radius: 12px;
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.35);
  background-color: var(--white);
}
.loc-tile,
.weeknew-row.details-open .weeknew-row-sessions {
  background-image: linear-gradient(rgba(255, 255, 255, 0.72), rgba(255, 255, 255, 0.72)), var(--tile-photo, none);
  background-size: cover;
  background-position: center;
}
.live-chart-frame:fullscreen .loc-tile,
.live-chart-frame:-webkit-full-screen .loc-tile {
  max-height: calc(100vh - 50px);
  overflow-y: auto;
}
/* Live's stats / Home By / gesture hint / type picker, now stacked inside the tile */
.loc-tile .live-heading-row {
  flex-direction: column;
  align-items: stretch;
  padding: 0;
  margin: 0 0 8px;
}
.loc-tile .graph-gesture-hint {
  position: static;
  text-align: left;
  margin: 0;
}
.loc-tile #typePickerSection {
  margin: 0 !important;
}
.loc-tile .type-photo-row {
  margin: 0;
}

/* Landscape phone (see setupFullscreenToggle): fullscreen-like extra room
   even when the browser won't enter real fullscreen. */
.landscape-immersive .sitebar,
.landscape-immersive.weeknew-page main > :not(.week-full-bleed),
.landscape-immersive.weeknew-page .week-timeline-scroll + .footnote {
  display: none;
}
.landscape-immersive.weeknew-page main {
  padding: 0;
  margin: 0;
}
/* Same thing in pure CSS (no JS involved), so it applies on any touch phone
   held sideways even if the script-based detection misses. */
@media (orientation: landscape) and (max-height: 600px) and (hover: none) {
  .sitebar,
  .weeknew-page main > :not(.week-full-bleed),
  .weeknew-page .week-timeline-scroll + .footnote {
    display: none;
  }
  .weeknew-page main {
    padding: 0;
    margin: 0;
  }
}

.loc-tile-info { font-size: 0.85rem; margin: 0 0 8px; }
.loc-tile-type { font-weight: 700; }

.loc-tile-heading { font-size: 0.8rem; font-weight: 700; margin: 0 0 4px; }
.loc-tile-sessions { display: flex; flex-direction: column; gap: 6px; margin: 0 0 8px; }
.loc-tile { max-height: 70vh; overflow-y: auto; }


/* Installed-app (PWA) layout: keep content clear of the phone's system bars.
   env() is 0 wherever there is no inset, so browsers/desktop are unaffected.
   Needs viewport-fit=cover in each page's viewport meta. */
body {
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
.map-fullpage-body {
  box-sizing: border-box; /* the padding above comes out of the 100dvh, not on top of it */
}
.sitebar {
  border-top: env(safe-area-inset-top, 0px) solid var(--blue-900);
}

/* The installed app used to keep a fixed 48px (24px sideways) gap along the bottom on phones that report no
   safe-area inset (Samsung S21). That guess was wrong once the phone was rotated — it showed as an empty
   strip — because the real cause was 100dvh being taller than the visible window. The map page's height is
   now set from the actual window height instead (--app-height, see .map-fullpage-body above and
   syncAppHeight in app.js), so no fixed gap is needed. */

/* Week Ahead on touch devices: no spare padding under the graph board (the sizing
   script in index.html fills the screen down to the bottom edge), and in landscape
   the rows are half their usual height so more locations fit on the short screen
   (a phone in landscape otherwise gets the 328px desktop row height). */
@media (hover: none) {
  .weeknew-page main {
    padding-bottom: 0 !important; /* overrides the inline padding on <main> */
  }
}

/* Week Ahead name pills: pinned just under the sticky day header while their
   row is on screen, leaving with the row when it scrolls away. The wrapper is
   sticky inside the (zero-width) sidebar, which is as tall as the row, so it
   can never leave the row; 38px covers the pill (6px offset + ~30px tall). */
.weeknew-row-sticky {
  position: sticky;
  top: var(--weeknew-header-h, 0px);
  flex: 0 0 38px;
  height: 38px;
  width: 0;
  z-index: 3;
}


/* "Install app" button in the header bar (js/install-app.js). Hidden unless the browser can install the site and it isn't installed already. */
.install-btn {
  order: 4;
  margin: 0 0 8px 8px;
  padding: 3px 10px;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--white);
  background: transparent;
  border: 1px solid #b7c9e0;
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
}
.install-btn[hidden] {
  display: none;
}
.install-btn:hover {
  background: rgba(255, 255, 255, 0.12);
}
@media (max-width: 1200px) {
  .install-btn {
    order: 3;
    margin: 0 0 4px 6px;
    padding: 2px 8px;
    font-size: 0.66rem;
  }
}
/* iPhone/iPad have no install dialog — this note explains the Share menu route. */
.install-note {
  position: fixed;
  top: calc(56px + env(safe-area-inset-top, 0px));
  left: 8px;
  right: 8px;
  max-width: 320px;
  margin-left: auto;
  padding: 10px 12px;
  background: var(--white);
  color: var(--grey-700);
  font-size: 0.85rem;
  line-height: 1.35;
  border-radius: 10px;
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.35);
  z-index: 20000;
}


/* A button that can't be used right now (e.g. Previous session on the oldest one). */
.btn-primary:disabled,
.btn-secondary:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* ---------------------------------------------------------------------------
   Map tab (conditions.html): toolbar, Live toggle, and the Import review panel
   (the old Sync tab's review list and trail tree, now in the right-hand column).
   --------------------------------------------------------------------------- */
.map-toolbar {
  flex: none;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  padding: 6px 12px;
  border-bottom: 1px solid var(--grey-200);
  background: var(--white);
  font-size: 0.85rem;
}
.map-device-tools { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 8px; }
.map-import-btn { cursor: pointer; display: inline-flex; align-items: center; }
.map-toolbar .btn-secondary { padding: 4px 10px; font-size: 0.8rem; }
.map-toolbar-status { font-size: 0.8rem; }
.map-toolbar-status:empty { display: none; }

/* Import/Export button: a single icon button that opens a small floating menu
   with the Import GPX / Export Lowrance / Export Garmin actions, modeled on the
   Week Ahead details popover (.weeknew-row.details-open .weeknew-row-sessions). */
.device-tools-menu { position: relative; display: inline-flex; }
.device-tools-dropdown {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 4px;
  flex-direction: column;
  gap: 4px;
  background: var(--white);
  border-radius: 10px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.22);
  padding: 6px;
  z-index: 600;
  min-width: 170px;
  max-width: calc(100vw - 24px);
}
.device-tools-menu.open .device-tools-dropdown { display: flex; }
.device-tools-menu-item { justify-content: flex-start; width: 100%; text-align: left; white-space: nowrap; }

.live-toggle { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; font-weight: 600; user-select: none; }
.live-toggle input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.live-toggle-track { position: relative; width: 36px; height: 20px; border-radius: 10px; background: var(--grey-300, #cbd5e1); transition: background 0.15s; }
.live-toggle-track::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: #fff; transition: transform 0.15s; }
.live-toggle input:checked + .live-toggle-track { background: #16a34a; }
.live-toggle input:checked + .live-toggle-track::after { transform: translateX(16px); }
.live-toggle input:focus-visible + .live-toggle-track { outline: 2px solid var(--blue-900, #0b2a4a); outline-offset: 2px; }
.live-toggle input:disabled + .live-toggle-track { opacity: 0.5; }
.live-toggle:has(input:disabled) { cursor: not-allowed; opacity: 0.7; }

.import-review-panel {
  width: 340px;
  flex-shrink: 0;
  height: 100%;
  flex-direction: column;
  background: var(--white);
  border-left: 1px solid var(--grey-200);
  min-height: 0;
}
.import-review-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 10px 12px 6px; }
.import-review-head .summary-title { margin: 0; }
#reviewSection { flex: 1; min-height: 0; overflow-y: auto; padding: 0 12px 8px; }
.import-review-summary { margin: 0 0 8px; text-align: left; }
.import-review-search { width: 100%; box-sizing: border-box; padding: 7px 10px; border-radius: 8px; border: 1px solid var(--grey-200); margin-bottom: 8px; }
.import-review-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.import-review-more { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 6px; }
.import-review-foot { flex: none; padding: 8px 12px 12px; border-top: 1px solid var(--grey-200); display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.import-review-status { flex-basis: 100%; font-size: 0.85rem; }
.import-review-status:empty { display: none; }
@media (max-width: 760px) {
  .import-review-panel { width: 100%; height: auto; max-height: 50%; border-left: none; border-top: 1px solid var(--grey-200); }
}

.sync-side-group-title { font-weight: 600; font-size: 0.9rem; padding: 6px 4px; cursor: pointer; display: flex; align-items: center; gap: 6px; }
.sync-side-group-title .caret { display: inline-block; width: 0.9em; transition: transform 0.1s; }
.sync-side-group-title.collapsed .caret { transform: rotate(-90deg); }
.candidate-row-compact { display: flex; align-items: center; gap: 8px; padding: 4px 4px; font-size: 0.85rem; border-radius: 6px; cursor: pointer; }
.candidate-row-compact:hover, .candidate-row-compact-selected { background: var(--blue-100, #eaf2fb); }
.tracks-tree-node { display: flex; align-items: center; gap: 4px; padding: 3px 2px; font-size: 0.82rem; border-radius: 6px; }
.tracks-tree-node[data-level="track"], .tracks-tree-node[data-level="day"] { cursor: pointer; }
.tracks-tree-node[data-level="track"] { font-weight: 600; }
.tree-checkbox-col { width: 24px; flex-shrink: 0; text-align: center; display: flex; align-items: center; justify-content: center; }
.tree-checkbox-col svg { display: block; }
.tree-node-label { display: flex; align-items: center; gap: 4px; flex: 1; min-width: 0; }
.tree-node-label .caret { display: inline-block; width: 0.9em; font-size: 0.75em; transition: transform 0.1s; flex-shrink: 0; }
.tree-node-label .caret.collapsed { transform: rotate(-90deg); }
.tracks-tree-header { display: flex; align-items: center; gap: 4px; font-size: 0.72rem; font-weight: 600; color: var(--grey-500); text-transform: uppercase; letter-spacing: 0.02em; padding: 0 2px 4px; border-bottom: 1px solid var(--grey-200); margin-bottom: 4px; }
.tree-header-icon { cursor: pointer; border-radius: 4px; }
.tree-header-icon:hover { background: var(--blue-100, #eaf2fb); color: var(--blue-900, #0b2a4a); }
.tracks-tree-node[data-level="candidate"] .tree-node-label span[data-role="select-candidate"] { cursor: pointer; text-decoration: underline dotted; }
.tracks-tree-node[data-level="day"] .tree-node-label span[data-role="zoom-day"],
.tracks-tree-node[data-level="segment"] .tree-node-label span[data-role="zoom-highlight-segment"] { cursor: pointer; }
.tracks-tree-node[data-level="day"] .tree-node-label span[data-role="zoom-day"]:hover,
.tracks-tree-node[data-level="segment"] .tree-node-label span[data-role="zoom-highlight-segment"]:hover { text-decoration: underline; }
.segment-convert-icon { margin-left: auto; padding: 2px 4px; cursor: pointer; border-radius: 4px; color: var(--grey-500); flex-shrink: 0; }
.segment-convert-icon:hover { background: var(--blue-100, #eaf2fb); color: var(--blue-900, #0b2a4a); }
.candidate-step-btn { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; border-radius: 4px; cursor: pointer; font-weight: 700; color: var(--grey-500); flex-shrink: 0; }
.candidate-step-btn:hover { background: var(--blue-100, #eaf2fb); color: var(--blue-900, #0b2a4a); }
.tracks-tree-node-selected { background: var(--blue-100, #eaf2fb); }

/* Live mode quick-entry cards (js/live-cards.js): full-screen stack of large-button cards for Session defaults and Catch. */
.live-card-overlay { position: fixed; inset: 0; z-index: 10001; background: var(--blue-900, #0b2a4a); display: flex; padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px) env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px); }
body.live-card-open { overflow: hidden; }
.live-card { display: flex; flex-direction: column; width: 100%; max-width: 640px; margin: 0 auto; height: 100vh; height: 100dvh; padding: 14px; box-sizing: border-box; color: #fff; }
.live-card-head { flex: 0 0 auto; text-align: center; }
.live-card-progress { font-size: 0.85rem; opacity: 0.75; }
.live-card-title { margin: 2px 0 0; font-size: 1.6rem; color: #fff; }
.live-card-prompt { margin: 4px 0 10px; font-size: 1rem; opacity: 0.9; }
.live-card-hint { margin: 0 0 10px; font-size: 0.85rem; color: #fde68a; }
.live-card-gallery-toggle { display: block; margin: 0 0 6px; padding: 6px 4px; background: none; border: none; color: #93c5fd; font-size: 0.85rem; font-weight: 600; text-decoration: underline; cursor: pointer; }
.live-card-grid { flex: 1 1 auto; overflow-y: auto; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; align-content: start; padding: 2px 0 10px; -webkit-overflow-scrolling: touch; }
.live-card-choice { min-height: 64px; padding: 10px 8px; border-radius: 14px; border: 2px solid rgba(255, 255, 255, 0.35); background: rgba(255, 255, 255, 0.08); color: #fff; font-size: 1.15rem; font-weight: 600; cursor: pointer; overflow-wrap: anywhere; }
.live-card-choice.selected { background: #16a34a; border-color: #bbf7d0; }
.live-card-empty { grid-column: 1 / -1; text-align: center; opacity: 0.85; }
.live-card-nav { flex: 0 0 auto; display: grid; grid-template-columns: 1fr 1fr 1.4fr; gap: 10px; padding-top: 8px; }
.live-card-nav-btn { min-height: 60px; border-radius: 14px; border: 2px solid rgba(255, 255, 255, 0.4); background: transparent; color: #fff; font-size: 1.1rem; font-weight: 700; cursor: pointer; }
.live-card-nav-btn.live-card-next { background: #fff; color: var(--blue-900, #0b2a4a); border-color: #fff; }
.live-card-nav-btn:disabled { opacity: 0.35; cursor: not-allowed; }
/* "+ Session" hub (js/live-cards.js's showSessionStartFlow): a 2-button nav (Close / Start Session or Back to
   overview), a single-column Date/Time card, and a small +/-0.1 m fine row under the Water depth stepper. */
.live-card-nav-2 { grid-template-columns: 1fr 1.4fr; }
.live-card-nav-1 { grid-template-columns: 1fr; }
.live-card-grid-datetime { grid-template-columns: 1fr; align-content: start; }
.live-card-datetime-input { min-height: 52px; padding: 10px 12px; border-radius: 14px; border: 2px solid rgba(255, 255, 255, 0.35); background: rgba(255, 255, 255, 0.08); color: #fff; font-size: 1.1rem; color-scheme: dark; }
.live-card-stepper-row-fine { display: flex; gap: 10px; }
.live-card-step-btn-fine { min-height: 52px; flex: 1 1 0; font-size: 1rem; font-weight: 700; }
.live-card-toast { position: fixed; left: 50%; bottom: calc(24px + env(safe-area-inset-bottom, 0px)); transform: translateX(-50%); z-index: 10002; max-width: 90vw; padding: 12px 18px; border-radius: 12px; background: #16a34a; color: #fff; font-weight: 600; box-shadow: 0 6px 24px rgba(0, 0, 0, 0.3); }
.live-card-toast.error { background: #dc2626; }
.map-toolbar .live-quick-btn { font-weight: 700; }
.map-toolbar .live-quick-btn .btn-icon { display: block; }
.live-card-divider { grid-column: 1 / -1; display: flex; align-items: center; gap: 10px; margin: 6px 0 2px; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.06em; opacity: 0.8; }
.live-card-divider::before, .live-card-divider::after { content: ""; flex: 1; height: 2px; background: rgba(255, 255, 255, 0.4); }
/* Catch cards with limits: option sub-lines, tones, the size stepper. */
.live-card-choice { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; }
.live-card-choice-sub { font-size: 0.72rem; font-weight: 500; opacity: 0.85; line-height: 1.25; }
.live-card-choice.tone-warn { border-color: #fbbf24; }
.live-card-choice.tone-full { border-color: #f87171; background: rgba(220, 38, 38, 0.28); }
.live-card-choice.selected.tone-warn, .live-card-choice.selected.tone-full { background: #16a34a; border-color: #bbf7d0; }

/* Week Ahead's Location Group/Shore Direction/Locations filter panels (week.js's showFacetCardPanel)
   reuse this same card look for a 3-state include/exclude/neutral choice, not Catch's plain
   selected/not — .excluded is the red counterpart to .selected's green. The Locations panel also
   layers a small pin-to-top (★/☆) toggle in the corner of each card; .live-card-choice-wrap is a
   plain (non-button) positioning wrapper since a real <button> can't nest inside another <button>. */
.live-card-choice.excluded { background: rgba(220, 38, 38, 0.28); border-color: #f87171; }
.live-card-choice-wrap { position: relative; }
.live-card-choice-wrap .live-card-choice { width: 100%; }
.live-card-pin-btn {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: none;
  background: rgba(0, 0, 0, 0.35);
  color: rgba(255, 255, 255, 0.7);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
}
.live-card-pin-btn.pinned { color: #fde68a; }
/* Week Ahead's Session Criteria panel (week.js's showSessionCriteriaModal) hits real min/max
   bounds (Min Condition 1-5, Min consecutive hours 1-24) — Water depth's own steppers never
   clamp, so this disabled look was never needed until now. */
.live-card-step-btn:disabled { opacity: 0.35; cursor: not-allowed; }
.live-card-stepper { grid-column: 1 / -1; display: flex; flex-direction: column; gap: 12px; }
.live-card-stepper-value { text-align: center; font-size: 3.6rem; font-weight: 800; line-height: 1.1; padding-top: 6px; }
.live-card-stepper-value.small { color: #fca5a5; font-size: 2.6rem; }
.live-card-stepper-verdict { text-align: center; min-height: 1.5em; font-weight: 600; }
.live-card-stepper-verdict.tone-ok { color: #86efac; }
.live-card-stepper-verdict.tone-big { color: #fde68a; }
.live-card-stepper-verdict.tone-bad { color: #fca5a5; }
.live-card-toosmall { min-height: 64px; }
.mark-limit-warning { margin: 6px 0; padding: 6px 8px; border-radius: 6px; background: #fef2f2; border: 1px solid #fecaca; color: #991b1b; font-size: 0.8rem; }
/* Size stepper: - number + row, Too small, then 100s / 10s / 1s digit columns. */
.live-card-stepper { gap: 10px; }
.live-card-stepper-row { display: grid; grid-template-columns: 76px minmax(0, 1fr) 76px; gap: 10px; align-items: center; }
.live-card-step-btn { min-height: 76px; font-size: 2.2rem; font-weight: 700; }
.live-card-stepper-value { padding-top: 0; }
.live-card-stepper-unit { font-size: 1.3rem; font-weight: 600; opacity: 0.8; }
.live-card-digit-cols { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; align-items: start; }
.live-card-digit-col { display: flex; flex-direction: column; gap: 6px; }
.live-card-digit-caption { text-align: center; font-size: 0.8rem; opacity: 0.8; }
.live-card-choice.live-card-digit { min-height: 44px; padding: 4px; font-size: 1.25rem; }.live-card-stepper { gap: 8px; }
.live-card-step-btn { min-height: 64px; }
.live-card-stepper-value { font-size: 3.2rem; }
.live-card-toosmall { min-height: 52px; }
.live-card-digit-col { gap: 4px; }
.live-card-choice.live-card-digit { min-height: 38px; padding: 2px; font-size: 1.15rem; }
/* Size stepper, second layout: two digit columns (10s: 1-12, 1s: 0-9) that share the remaining height so every number is visible. */
.live-card-grid-stepper { align-content: stretch; grid-template-rows: minmax(0, 1fr); overflow-y: auto; }
/* REAL BUG, FOUND AND FIXED (third round on this element — see js/live-cards.js's own comment on galleryHtml for the
   full story). Rounds one and two fixed how big each PICTURE rendered; this one is about POSITION. As a CSS Grid (3
   columns, implicit rows), a taller picture in one column made its whole ROW taller everywhere — including the
   OTHER two columns' cells in that same row — so a card several rows down could end up overlapped by the one below
   it: the row heights just didn't match what those columns actually needed. Fixed in the markup, not CSS alone: no
   more grid/rows at all for this card. Three independent top-to-bottom stacks (js/live-cards.js's galleryHtml),
   each card's position simply following the one above it in its OWN stack — nothing in column 2 can ever move
   anything in column 1. `display: block` here overrides the shared .live-card-grid's own `display: grid`, since the
   three columns are now plain flex stacks below, not this container's grid columns. */
.live-card-grid-gallery { display: block; }
.live-card-gallery-cols { display: flex; align-items: flex-start; gap: 10px; }
.live-card-gallery-col { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 10px; }
.live-card-gallery-item { padding: 4px; gap: 4px; margin: 0; }
.live-card-gallery-item img { width: 100%; height: auto; display: block; border-radius: 8px; }
.live-card-gallery-item span { font-size: 0.72rem; font-weight: 600; text-align: center; }
.live-card-stepper { min-height: 100%; }
.live-card-digit-cols { flex: 1 1 auto; grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: stretch; min-height: 0; }
.live-card-digit-col { min-height: 0; }
.live-card-digit-buttons { flex: 1 1 0; min-height: 0; display: flex; flex-direction: column; gap: 4px; }
.live-card-choice.live-card-digit { flex: 1 1 0; min-height: 30px; padding: 0; font-size: 1.1rem; }.live-card-step-btn { min-height: 56px; }
.live-card-stepper-value { font-size: 2.9rem; }
.live-card-toosmall { min-height: 46px; }
.live-card-stepper-verdict { min-height: 1.3em; }
.live-card-digit-buttons { gap: 3px; }
.live-card-choice.live-card-digit { min-height: 26px; font-size: 1rem; }
/* Species pictures (Settings): thumbnails on each species and the full-screen viewer. */
.species-thumb { padding: 0; border: 2px solid rgba(255, 255, 255, 0.7); border-radius: 10px; overflow: hidden; width: 56px; height: 56px; background: var(--grey-200, #e5e7eb); cursor: zoom-in; }
/* A picture that never loads (see wireImagePictureFallback, js/species-image.js) shows this in its place instead of staying a blank box. */
.species-image-missing { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; font-size: 0.68rem; opacity: 0.75; text-align: center; padding: 2px; }
.species-thumb img { width: 100%; height: 100%; object-fit: contain; display: block; }
.species-add-image { padding: 6px 10px; border-radius: 8px; border: 1px dashed currentColor; background: transparent; color: inherit; font-size: 0.75rem; font-weight: 600; cursor: pointer; }
.species-image-viewer { position: fixed; inset: 0; z-index: 10002; background: rgba(0, 0, 0, 0.97); display: flex; flex-direction: column; color: #fff; padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px) env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px); }
.species-image-viewer-top { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; font-weight: 600; }
.species-image-viewer-top button { background: none; border: none; color: #fff; font-size: 2rem; line-height: 1; cursor: pointer; padding: 0 6px; }
.species-image-viewer-stage { flex: 1 1 auto; min-height: 0; display: flex; align-items: center; justify-content: center; gap: 6px; padding: 0 6px; }
.species-image-viewer-stage img { flex: 1 1 auto; min-width: 0; max-width: 100%; max-height: 100%; object-fit: contain; }
.species-image-nav { flex: 0 0 auto; background: rgba(255, 255, 255, 0.12); border: none; color: #fff; font-size: 2.2rem; line-height: 1; width: 44px; height: 72px; border-radius: 10px; cursor: pointer; }
.species-image-viewer-actions { display: flex; justify-content: center; gap: 12px; padding: 12px 14px 16px; }
.species-image-viewer-actions button { min-width: 120px; padding: 12px 18px; border-radius: 10px; border: 2px solid rgba(255, 255, 255, 0.6); background: transparent; color: #fff; font-size: 1rem; font-weight: 700; cursor: pointer; }
.species-image-viewer-actions button.danger { border-color: #f87171; color: #fca5a5; }
.species-image-viewer-actions button.primary { background: #16a34a; border-color: #bbf7d0; }
/* A pick-card choice that has a picture (the first picture of that rod/rig/bait/berley/method/rig option, js/live-cards.js): picture above the name; in
   Trip Defaults' pill-shaped choices it sits to the left of the name instead. */
.live-card-choice-thumb { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; z-index: 0; } /* the whole picture fills the pill, fitted: never cropped or stretched */
.live-card-choice.has-thumb { position: relative; overflow: hidden; min-width: 0; min-height: 110px; }
.live-card-choice.has-thumb::after { content: ""; position: absolute; inset: 0; background: rgba(0, 0, 0, 0.5); z-index: 0; pointer-events: none; } /* dark veil keeps the name readable */
.live-card-choice.has-thumb.selected::after { background: rgba(22, 163, 74, 0.6); }
.live-card-choice.has-thumb > span { position: relative; z-index: 1; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); }
/* Trip Defaults' pill-shaped choices keep the small round picture to the left of the name */
.td-choices .live-card-choice.has-thumb { flex-direction: row; gap: 8px; padding: 6px 16px 6px 6px; min-height: 0; overflow: visible; }
.td-choices .live-card-choice.has-thumb::after { display: none; }
.td-choices .live-card-choice-thumb { position: static; width: 34px; height: 34px; border-radius: 999px; object-fit: cover; flex: 0 0 auto; background: rgba(255, 255, 255, 0.9); }
.td-choices .live-card-choice.has-thumb > span { text-shadow: none; }
/* Tap a choice's thumbnail on a touch screen (js/species-image.js's showImageLightbox): the picture in a popup over a dimmed page. */
.image-popup-backdrop { position: fixed; inset: 0; z-index: 10060; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; padding: 16px; }
.image-popup { max-width: min(90vw, 460px); background: #fff; color: #111827; border-radius: 14px; padding: 8px; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5); }
.image-popup img { display: block; max-width: 100%; max-height: 62vh; margin: 0 auto; border-radius: 8px; object-fit: contain; }
.image-popup-caption { text-align: center; font-weight: 600; padding: 6px 4px 2px; }
/* Trip Defaults, opened from a running trip's gear: the action's active choices as tappable summary chips; the open category's options show below. */
.td-summary { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 4px; }
.td-choices .live-card-choice.td-summary-chip, .td-summary .td-summary-chip { flex-direction: column; align-items: flex-start; gap: 0; min-height: 0; padding: 8px 14px; border-radius: 14px; text-align: left; font-size: 1rem; }
.td-summary-label { font-size: 0.72rem; font-weight: 600; opacity: 0.8; }
.td-summary-value { font-size: 1rem; font-weight: 600; }
/* The large picture shown while the pointer is over a choice's thumbnail in Trip Defaults (js/species-image.js's wireHoverImagePreview). */
.hover-image-preview { position: fixed; z-index: 10050; max-width: min(380px, 60vw); max-height: min(380px, 70vh); border-radius: 12px; border: 3px solid #fff; background: #fff; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.55); pointer-events: none; object-fit: contain; }
/* Map > Trip Defaults (js/trip-defaults.js): reuses the .live-card-* look; these are its own sections, name inputs and
   Rod Setup pills (a selectable pill with a gear on its right that opens the setup's editor). */
.td-grid { display: block; }
.td-section { margin: 14px 0 6px; }
.td-section-title { margin: 14px 0 6px; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.06em; opacity: 0.8; }
.td-section .td-section-title { margin-top: 0; }
.td-choices { display: flex; flex-wrap: wrap; gap: 8px; }
.td-choices .live-card-choice { min-height: 0; padding: 10px 16px; border-radius: 999px; border-width: 2px; font-size: 1.05rem; }
.td-choices .live-card-empty { width: 100%; }
.td-input { width: 100%; box-sizing: border-box; margin-bottom: 4px; }
.td-add-row { display: flex; gap: 8px; margin-top: 10px; }

.td-add-btn { flex: 0 0 auto; min-height: 0; padding: 10px 16px; border-radius: 999px; font-size: 1.05rem; font-weight: 600; }
.td-pills { display: flex; flex-wrap: wrap; gap: 8px; }
.td-pill { display: inline-flex; align-items: stretch; border-radius: 999px; border: 2px solid rgba(255, 255, 255, 0.35); background: rgba(255, 255, 255, 0.08); color: #fff; overflow: hidden; }
.td-pill.selected { background: #16a34a; border-color: #bbf7d0; }
.td-pill-main { padding: 10px 6px 10px 16px; background: none; border: none; color: inherit; font-size: 1.05rem; font-weight: 600; cursor: pointer; }
.td-pill-grid { position: relative; min-width: 150px; min-height: 58px; }
.td-slots { position: absolute; inset: 0; display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; gap: 2px; padding: 2px; }
.td-slot { border: none; border-radius: 6px; background: rgba(255, 255, 255, 0.14); cursor: pointer; padding: 0; }
.td-slot.mine { background: #16a34a; }
.td-slot.taken { background: rgba(120, 120, 120, 0.35); cursor: not-allowed; opacity: 0.45; }
.td-pill-name { position: relative; z-index: 1; pointer-events: none; display: inline-flex; align-items: center; padding: 8px 10px 8px 16px; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); }
.td-pill-grid .td-pill-gear { position: relative; z-index: 2; margin-left: auto; }
.td-pill-gear { display: inline-flex; align-items: center; padding: 0 12px 0 8px; background: none; border: none; border-left: 1px solid rgba(255, 255, 255, 0.3); color: inherit; cursor: pointer; }
.td-delete { display: flex; flex-direction: column; gap: 8px; margin: 22px 0 8px; }
.td-delete-btn { min-height: 0; padding: 10px 16px; border-radius: 999px; font-size: 1.05rem; font-weight: 600; align-self: flex-start; border-color: rgba(248, 113, 113, 0.7); color: #fca5a5; }
.td-delete-yes { background: #dc2626; color: #fff; border-color: #dc2626; }
.td-delete-btn[hidden] { display: none; }
.td-add-pill { border-style: dashed; }
.td-inline-add { display: inline-flex; flex-wrap: wrap; gap: 8px; align-items: center; width: 100%; }
.td-inline-add .td-input { flex: 1 1 140px; width: auto; margin-bottom: 0; }
.td-inline-add .live-card-choice { min-height: 0; }
.td-nav { grid-template-columns: 1fr auto; align-items: center; }
.td-back-btn { min-height: 64px; font-size: 1.25rem; }
.td-exit-btn { min-height: 0; padding: 10px 18px; border-radius: 999px; font-size: 1rem; font-weight: 600; }
/* Live mode trips (map-live.js): the chosen trip's Actions as toolbar buttons; the running one is green. */
.live-trip-actions { display: inline-flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.map-toolbar .live-trip-action { background: var(--blue-700); border-color: var(--blue-700); color: #fff; }
.map-toolbar .live-trip-action:hover:not(:disabled):not(.active) { background: var(--blue-900); border-color: var(--blue-900); }
.map-toolbar #btnStartTrip.trip-start { background: #16a34a; border-color: #16a34a; color: #fff; }
.map-toolbar #btnStartTrip.trip-stop { background: #dc2626; border-color: #dc2626; color: #fff; }
#btnStartTrip svg { display: block; }
#btnLiveWater, #btnLiveDepth { display: inline-flex; align-items: center; gap: 6px; }
/* An action pill: the name button plus a gear button on its right that edits the action. */
.live-trip-pill { display: inline-flex; align-items: stretch; }
.map-toolbar .live-trip-pill .live-trip-action:not(.live-trip-gear) { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.map-toolbar .live-trip-pill .live-trip-gear { border-top-left-radius: 0; border-bottom-left-radius: 0; border-left: 1px solid rgba(255, 255, 255, 0.35); padding-left: 8px; padding-right: 8px; display: inline-flex; align-items: center; }
.live-trip-gear svg { display: block; width: 16px; height: 16px; }
.map-toolbar .live-trip-action.active { background: #16a34a; border-color: #16a34a; color: #fff; }
.map-toolbar .live-trip-action:disabled { opacity: 0.55; }
.map-toolbar #btnLiveWater, .map-toolbar #btnLiveDepth { font-weight: 600; }
.mark-edit-sublabel { font-size: 0.75rem; font-weight: 600; margin: 8px 0 3px; opacity: 0.85; }

/* Trips on the Map (js/trip-map.js) */
.trip-map-panel { position: fixed; top: 120px; left: 12px; z-index: 1000; width: min(340px, calc(100vw - 24px)); max-height: 60vh; overflow: auto; background: var(--white, #fff); border: 1px solid var(--grey-200, #e5e7eb); border-radius: 8px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); padding: 8px 10px; }
.trip-map-panel-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; }
.trip-map-panel-close { background: none; border: 0; font-size: 1.4rem; line-height: 1; cursor: pointer; color: inherit; }
.trip-map-group summary { cursor: pointer; font-weight: 600; padding: 4px 0; }
.trip-map-count { font-weight: 400; opacity: 0.7; }
.trip-map-group-actions { display: flex; gap: 6px; margin: 2px 0 4px; }
.trip-map-group-actions button { padding: 2px 8px; font-size: 0.75rem; }
.trip-map-row { display: flex; align-items: center; gap: 6px; padding: 3px 0; font-size: 0.85rem; cursor: pointer; }
.trip-map-row-text { flex: 1; min-width: 0; }
.trip-map-swatch { width: 12px; height: 12px; border-radius: 3px; flex: none; }
.trip-map-log-link { font-size: 0.75rem; }
.trip-map-hover { margin: 6px 0; }
.trip-pin-wrap { background: none; border: 0; }
.trip-pin { display: flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: 50%; background: var(--trip-c, #2563eb); color: #fff; border: 2px solid #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); font: 700 10px/1 system-ui, sans-serif; box-sizing: border-box; }
.trip-pin-C { transform: scale(0.75); opacity: 0.9; }
.trip-pin-F { border-width: 2px; }
.trip-pin-E { border-radius: 4px; }
.map-toolbar #tripMapBtn[aria-pressed="true"] { background: var(--navy, #0b2a4a); color: #fff; }

/* ---- Trip tab (trip.html, trip.js): phone-first trip recording, tap-only ---- */
.trip-main { max-width: 640px; margin: 0 auto; padding: 12px 16px calc(24px + env(safe-area-inset-bottom)); box-sizing: border-box; }
.trip-card { background: var(--white, #fff); border: 1px solid var(--grey-200, #e5e5e5); border-radius: 12px; padding: 14px; margin-bottom: 12px; }
.trip-title { margin: 0 0 10px; font-size: 1.05rem; }
.trip-muted { color: var(--grey-600, #666); font-size: 0.9rem; margin: 6px 0 0; }
.trip-hint { margin-top: 10px; }
.trip-list { display: grid; gap: 10px; margin-bottom: 8px; }
.trip-big { display: flex; align-items: center; justify-content: center; gap: 10px; width: 100%; min-height: 64px; padding: 12px 16px; border: 0; border-radius: 14px; font-size: 1.15rem; font-weight: 700; color: #fff; cursor: pointer; touch-action: manipulation; box-sizing: border-box; }
.trip-big:disabled { opacity: 0.45; cursor: default; }
.trip-go { background: #16a34a; }
.trip-catch { background: #1d5fa8; min-height: 88px; font-size: 1.4rem; margin-bottom: 12px; }
.trip-end { background: #dc2626; margin-top: 4px; }
.trip-link { background: none; border: 0; color: var(--blue-600, #1d5fa8); font-size: 0.95rem; padding: 10px 4px; cursor: pointer; min-height: 44px; }
.trip-status { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.trip-status-name { font-weight: 700; font-size: 1.1rem; }
.trip-status-time { font-variant-numeric: tabular-nums; font-size: 1.4rem; font-weight: 700; color: #16a34a; }
.trip-actions { display: grid; gap: 10px; }
.trip-pill { display: flex; border: 2px solid var(--grey-300, #ccc); border-radius: 14px; overflow: hidden; background: var(--white, #fff); }
.trip-pill.active { border-color: #16a34a; background: #16a34a; }
.trip-pill-main { flex: 1; min-height: 60px; padding: 8px 14px; border: 0; background: transparent; font-size: 1.1rem; font-weight: 600; text-align: left; color: inherit; cursor: pointer; touch-action: manipulation; }
.trip-pill.active .trip-pill-main, .trip-pill.active .trip-pill-gear { color: #fff; }
.trip-pill-gear { width: 56px; border: 0; border-left: 1px solid rgba(0, 0, 0, 0.12); background: transparent; color: var(--grey-600, #666); cursor: pointer; display: flex; align-items: center; justify-content: center; }
.trip-pill-gear svg { width: 22px; height: 22px; }
.trip-pill button:disabled { opacity: 0.6; }
.trip-conditions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 12px; }
.trip-cond { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; min-height: 68px; padding: 10px 14px; border: 2px solid var(--grey-300, #ccc); border-radius: 14px; background: var(--white, #fff); color: inherit; cursor: pointer; text-align: left; touch-action: manipulation; }
.trip-cond-label { font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--grey-600, #666); }
.trip-cond-value { font-size: 1.25rem; font-weight: 700; }
.trip-rods { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 8px; }
.trip-rod-wrap { position: relative; display: flex; }
.trip-rod-wrap > .trip-rod { flex: 1; padding-bottom: 38px; }
.trip-rod-gear { position: absolute; right: 6px; bottom: 6px; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; border: 0; border-radius: 10px; background: rgba(255, 255, 255, 0.7); color: var(--grey-700, #444); cursor: pointer; }
.trip-rod-gear svg { width: 22px; height: 22px; }
.trip-rod { display: flex; flex-direction: column; align-items: flex-start; justify-content: flex-start; gap: 4px; min-height: 130px; padding: 12px; border: 3px solid var(--grey-300, #ccc); border-radius: 14px; background: var(--white, #fff); color: inherit; text-align: left; cursor: pointer; touch-action: manipulation; overflow: hidden; }
.trip-rod.filled { background: rgba(22, 163, 74, 0.16); border-color: #16a34a; } /* the rods of the running action are green */
.trip-rod.empty { border-style: dashed; align-items: center; justify-content: center; }
.trip-rod.moving { border-color: #f59e0b; background: rgba(245, 158, 11, 0.18); }
.trip-rod-name { font-size: 1.05rem; font-weight: 700; overflow-wrap: anywhere; }
.trip-rod-bait { font-size: 0.9rem; color: #1d5fa8; overflow-wrap: anywhere; }
.trip-rod-rig { font-size: 0.85rem; color: var(--grey-600, #666); overflow-wrap: anywhere; }
.trip-rod-empty { color: var(--grey-500, #888); font-size: 1rem; }
.trip-status-action { margin: 2px 0 0; }
.trip-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 12px; }
.trip-small { min-height: 48px; padding: 8px 10px; border: 2px solid var(--grey-300, #ccc); border-radius: 12px; background: var(--white, #fff); color: inherit; font-size: 0.95rem; font-weight: 600; cursor: pointer; touch-action: manipulation; }
.trip-small.on { border-color: #f59e0b; background: rgba(245, 158, 11, 0.18); }
.trip-end-action { background: #6b7280; margin-bottom: 8px; min-height: 56px; font-size: 1.05rem; }
.trip-rod { position: relative; }
.trip-rod > span { position: relative; z-index: 1; }
.trip-rod-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; z-index: 0; } /* the whole picture, fitted, never cropped */
.trip-rod.has-img::after { content: ""; position: absolute; inset: 0; background: rgba(0, 0, 0, 0.6); z-index: 0; } /* the dark veil keeps the text readable */
.trip-rod.has-img .trip-rod-name { color: #fff; }
.trip-rod.has-img .trip-rod-bait { color: #93c5fd; }
.trip-rod.has-img .trip-rod-rig { color: #d1d5db; }
.trip-rod-catch { position: absolute; left: 6px; bottom: 6px; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; border: 0; border-radius: 10px; background: rgba(255, 255, 255, 0.7); color: #1d5fa8; cursor: pointer; z-index: 2; }
.trip-rod-catch svg { width: 24px; height: 24px; }
.trip-rod-gear { z-index: 2; }
.trip-rod-remove { position: absolute; top: 6px; right: 6px; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; border: 0; border-radius: 10px; background: rgba(255, 255, 255, 0.7); color: #dc2626; font-size: 1.6rem; line-height: 1; cursor: pointer; z-index: 2; }
.trip-trip-pill { border-color: #16a34a; background: #16a34a; color: #fff; }
.trip-trip-pill .trip-pill-main { display: flex; align-items: center; gap: 10px; font-size: 1.15rem; font-weight: 700; }
.trip-trip-pill .trip-pill-gear { color: #fff; }
.trip-add-pill { width: 100%; min-height: 56px; border: 2px dashed var(--grey-400, #aaa); border-radius: 14px; background: transparent; color: var(--grey-700, #444); font-size: 1.05rem; font-weight: 600; cursor: pointer; touch-action: manipulation; }
.trip-add-row { display: flex; gap: 8px; align-items: center; }
.trip-add-input { flex: 1; min-width: 0; min-height: 48px; padding: 6px 12px; border: 2px solid var(--grey-300, #ccc); border-radius: 12px; font-size: 1rem; box-sizing: border-box; }
/* Our own confirm / alert dialogs (js/dialog.js) */
.dlg-backdrop { position: fixed; inset: 0; z-index: 10100; background: rgba(0, 0, 0, 0.55); display: flex; align-items: center; justify-content: center; padding: 16px; box-sizing: border-box; }
.dlg-box { width: 100%; max-width: 420px; max-height: 90dvh; overflow-y: auto; background: var(--white, #fff); color: var(--grey-900, #111); border-radius: 16px; padding: 18px; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5); box-sizing: border-box; }
.dlg-title { margin: 0 0 8px; font-size: 1.15rem; }
.dlg-message { margin: 0 0 16px; font-size: 1rem; line-height: 1.4; white-space: pre-line; overflow-wrap: anywhere; }
.dlg-buttons { display: flex; gap: 10px; justify-content: flex-end; }
.dlg-btn { flex: 1; min-height: 52px; padding: 8px 14px; border: 2px solid var(--grey-300, #ccc); border-radius: 12px; background: var(--white, #fff); color: inherit; font-size: 1rem; font-weight: 700; cursor: pointer; touch-action: manipulation; }
.dlg-btn-ok { background: #1d5fa8; border-color: #1d5fa8; color: #fff; }
.dlg-btn-danger { background: #dc2626; border-color: #dc2626; }
