html, body {
  height: 100%;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

:root {
  /* Single source of truth for the docked sidebar's width -- read by both
     `.docked-panel` (its own width) and `body.map-docked #map` (how much
     to narrow the map by), so `setupDockResize`'s drag handler in app.js
     only has to update this one custom property to resize both in lockstep,
     rather than needing to set two separate inline styles every drag frame. */
  --docked-width: 400px;
}

#map {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  /* `right` (not a plain width:100%) specifically so docking can narrow
     just this edge -- see `body.map-docked #map` below -- while leaving
     top/left/bottom alone. */
  right: 0;
  /* Contains the map library's own internal compositing (Leaflet moves its
     tile/marker panes via CSS transforms) to this element's own stacking
     context. Without this, confirmed directly (headless AND a real
     non-headless Chromium under Xvfb, plus a minimal repro with no app
     code at all): once Leaflet's raster tile layer loads, every sibling
     element positioned on top of the map -- the Eclipses/Terrain/Contact
     Images panels -- stops painting entirely, even though it's still
     correctly present in the DOM and hit-testable/clickable. Leaflet's own
     internally-managed overlays (popups, zoom controls) were never
     affected, only true page-level siblings of #map -- isolating the
     map's own stacking context is what actually fixes it. Harmless for
     the Google Maps backend, which never exhibited this. */
  isolation: isolate;
}

/* Docking the popup narrows the map itself rather than overlaying the
   dock on top of it -- `setDocked` in app.js toggles this class on
   <body> and calls `mapProvider.resize()` right after so the active
   backend notices its container actually changed size (neither Google
   Maps nor Leaflet watches for that on their own). `--docked-width` (not
   a literal value) keeps this in sync with `.docked-panel`'s own width
   below as `setupDockResize`'s drag handler changes it. */
body.map-docked #map {
  right: var(--docked-width);
}

.banner {
  position: absolute;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  max-width: 90vw;
  padding: 10px 16px;
  border-radius: 6px;
  font-size: 13px;
  line-height: 1.5;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
}

.banner--error {
  background: #fdecea;
  color: #611a15;
  border: 1px solid #f5c6c1;
}

.banner code {
  background: rgba(0, 0, 0, 0.06);
  padding: 1px 4px;
  border-radius: 3px;
}

/* Wraps the Eclipses selector and the provider-profile toggle so they sit
   side by side at a fixed bottom-left screen position regardless of either
   one's own width (the selector's collapsed/expanded width changes, and
   the toggle's width depends on how many profiles config.js defines) --
   simpler than absolutely positioning each one separately and hand-tuning
   a `left` offset for the second that would need to track the first's
   actual rendered width. */
.bottom-left-controls {
  position: absolute;
  bottom: 24px;
  left: 12px;
  z-index: 10;
  display: flex;
  align-items: flex-end;
  gap: 8px;
}

.legend {
  background: #fff;
  color: #222;
  padding: 10px 14px;
  border-radius: 8px;
  font-size: 12px;
  line-height: 1.6;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
  max-width: 280px;
}

/* Segmented control built by buildProviderToggle() in app.js, one button
   per config.js profile -- hidden entirely (see that function) when only
   one profile exists, so this never shows as a single, permanently-"active"
   option with nothing else to switch to. */
.provider-toggle {
  display: flex;
  background: #fff;
  border-radius: 8px;
  padding: 3px;
  gap: 2px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}

.provider-toggle__btn {
  border: none;
  background: none;
  cursor: pointer;
  font-size: 12px;
  font-weight: 500;
  padding: 6px 12px;
  border-radius: 6px;
  color: #666;
}

.provider-toggle__btn:hover {
  color: #222;
}

.provider-toggle__btn--active {
  background: #1a73e8;
  color: #fff;
}

.legend__title {
  font-weight: 600;
}

.legend__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 4px;
}

.panel-toggle {
  border: none;
  background: none;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  color: #888;
  padding: 2px 4px;
  flex: none;
}

.panel-toggle:hover {
  color: #222;
}

.legend__hint {
  margin-top: 6px;
  color: #666;
  font-style: italic;
}

.selector-row {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 3px 0;
  cursor: pointer;
}

.selector-row input[type='checkbox'] {
  margin: 0;
  flex: none;
  cursor: pointer;
}

.selector-swatch {
  width: 14px;
  height: 14px;
  border-radius: 3px;
  flex: none;
}

.selector-row__label {
  line-height: 1.3;
}

.info-window {
  position: relative;
  font-size: 13px;
  line-height: 1.6;
  min-width: 220px;
  padding-right: 24px;
}

/* Not flush against the corner -- the active mapProvider's own native
   popup close button sits right there (confirmed directly: at flush-right,
   this button rendered fully behind Google's close-x, DOM-present and
   correctly styled but completely covered since the two ended up at
   nearly the same position). `right: 20px` here is tuned for Google's own
   close button specifically (measured directly: ends up a tight ~2px
   gap); Leaflet's sits at a different inset in its own popup chrome, so
   `body.map-provider-leaflet` below overrides this with a value tuned
   against Leaflet's instead (measured directly the same way: the shared
   20px value left a visibly looser ~21px gap there). */
.info-window__dock-btn {
  position: absolute;
  top: 1px;
  right: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  cursor: pointer;
  color: #888;
  padding: 3px;
}

body.map-provider-leaflet .info-window__dock-btn {
  right: 1px;
}

.info-window__dock-btn:hover {
  color: #222;
}

/* Google's own InfoWindow chrome (not anything of ours in index.html) --
   confirmed via headless-browser DOM/measurement inspection, not guessed
   (see scratchpad notes from that session). `.gm-style-iw-chr` is a full-
   width header ROW Google inserts above `.gm-style-iw-d` (our content) to
   hold its close button and an (unused, empty here) title slot -- our own
   info window never sets an InfoWindow title, so that whole row is just
   the button's clearance. Shrinking the row (an earlier version of this
   fix) still left it reserving real flow height, measured at 34px above
   the header text vs. 12px to its left -- visibly asymmetric, not the
   "natural padding" this was meant to produce. Taking the button out of
   flow entirely (`position: absolute`, collapsing the row to 0 height)
   instead lets `.gm-style-iw-c`'s own padding-top be the *only* top
   offset, matching the left/right padding exactly; the button then floats
   over the corner instead of pushing content down. Re-measured after this
   version: 12px on all four sides. */
.gm-style .gm-style-iw-c {
  padding: 12px !important;
}

.gm-style .gm-style-iw-d {
  padding-top: 0 !important;
}

.gm-style .gm-style-iw-chr {
  height: 0 !important;
  min-height: 0 !important;
  overflow: visible !important;
}

.gm-style .gm-style-iw-chr .gm-style-iw-ch {
  display: none !important;
}

.gm-style .gm-style-iw-chr button.gm-ui-hover-effect {
  position: absolute !important;
  top: 8px !important;
  right: 8px !important;
  width: 22px !important;
  height: 22px !important;
}

.gm-style .gm-style-iw-chr button.gm-ui-hover-effect span {
  margin: 3px !important;
  width: 16px !important;
  height: 16px !important;
}

.info-window__header-duration {
  margin: 0;
  font-size: 19px;
  font-weight: 700;
}

.info-window__header-pct {
  font-size: 12px;
  font-weight: 400;
  color: #777;
}

.info-window__header-date {
  font-size: 11px;
  color: #777;
  margin: 1px 0 0;
}

.info-window__kind {
  color: #777;
  font-size: 11px;
  margin: 0 0 6px;
}

.info-window table {
  border-collapse: collapse;
}

.info-window td {
  padding: 1px 8px 1px 0;
  vertical-align: top;
  white-space: nowrap;
}

.info-window td.label {
  color: #555;
}

.info-window__local-time {
  color: #999;
  font-size: 10px;
}

.info-window__duration-detail {
  color: #777;
  font-size: 11px;
  padding-top: 0;
}

.info-window__totality-time {
  display: inline-block;
  background: #fff3cd;
  color: #7a5b00;
  font-weight: 700;
  padding: 1px 6px;
  border-radius: 4px;
}

.info-window .approx-note {
  margin-top: 8px;
  font-size: 11px;
  color: #777;
  font-style: normal;
  white-space: normal;
  max-width: 260px;
}

.info-window__badges {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}

.badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 10px;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.6;
  white-space: nowrap;
  color: #fff;
}

.badge--low,
.badge--clear {
  background: #1a7a1a;
}

.badge--medium {
  background: #b36b00;
}

.badge--high,
.badge--blocked {
  background: #b3261e;
}

.badge--pending,
.badge--unknown {
  background: #888;
}

.panel-summary-badge {
  flex: none;
}

.info-window__beads-mini {
  display: flex;
  gap: 10px;
  margin-top: 8px;
}

.info-window__beads-mini-col {
  text-align: center;
}

.info-window__beads-mini-label {
  font-size: 10px;
  color: #777;
  margin-bottom: 2px;
}

.info-window__beads-mini-circle {
  width: 46px;
}

.info-window__beads-mini-circle svg {
  display: block;
  width: 100%;
  height: auto;
}

/* Top-left -- the one corner Terrain (bottom-right) and Contact Images
   (top-right) leave free. */
.weather-panel {
  position: absolute;
  left: 12px;
  top: 12px;
  z-index: 10;
  max-width: calc(100vw - 24px);
  background: #fff;
  color: #222;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
  padding: 12px 14px 10px;
  font-size: 12px;
}

/* Same reasoning as #elevation-panel-body/#beads-panel-body: the fixed
   width belongs to the body, not the panel, so collapsing (body
   display:none) shrinks the panel down to just its header row. */
#weather-panel-body {
  width: 280px;
}

.weather-panel__close {
  position: absolute;
  top: 6px;
  right: 8px;
  border: none;
  background: none;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  color: #888;
  padding: 4px;
}

.weather-panel__close:hover {
  color: #222;
}

.weather-panel__toggle {
  position: absolute;
  top: 6px;
  right: 32px;
  font-size: 16px;
}

.weather-panel__header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
  padding-right: 60px;
}

.weather-panel__title {
  font-weight: 600;
  font-size: 13px;
}

.weather-panel__eclipse {
  color: #777;
  margin-bottom: 8px;
}

.weather-panel__badges {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}

.weather-panel__temps {
  margin-bottom: 8px;
  color: #333;
}

.weather-panel__caption {
  color: #555;
  line-height: 1.5;
}

.weather-panel__loading,
.weather-panel__error {
  padding: 14px 0;
  text-align: center;
  color: #777;
}

.elevation-panel {
  position: absolute;
  right: 12px;
  bottom: 24px;
  z-index: 10;
  max-width: calc(100vw - 24px);
  background: #fff;
  color: #222;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
  padding: 12px 14px 10px;
  font-size: 12px;
}

/* The fixed width lives on the body, not the panel itself, so a collapsed
   panel (body display:none via its `hidden` attribute) shrinks to fit just
   the header row instead of staying pinned at the expanded chart's width. */
#elevation-panel-body {
  width: 360px;
}

.elevation-panel__close {
  position: absolute;
  top: 6px;
  right: 8px;
  border: none;
  background: none;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  color: #888;
  padding: 4px;
}

.elevation-panel__close:hover {
  color: #222;
}

.elevation-panel__toggle {
  position: absolute;
  top: 6px;
  right: 32px;
  font-size: 16px;
}

.elevation-panel__header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
  padding-right: 60px;
}

.elevation-panel__title {
  font-weight: 600;
  font-size: 13px;
}

.elevation-panel__eclipse {
  color: #777;
  margin-bottom: 8px;
}

.elevation-panel__chart {
  width: 100%;
}

.elevation-panel__chart svg {
  display: block;
  width: 100%;
  height: auto;
}

.elevation-panel__caption {
  margin-top: 6px;
  color: #555;
  line-height: 1.5;
}

.elevation-panel__caption .verdict--clear {
  color: #1a7a1a;
  font-weight: 600;
}

.elevation-panel__caption .verdict--blocked {
  color: #b3261e;
  font-weight: 600;
}

.elevation-panel__loading,
.elevation-panel__error {
  padding: 20px 0;
  text-align: center;
  color: #777;
}

.beads-panel {
  position: absolute;
  right: 12px;
  top: 12px;
  z-index: 10;
  max-width: calc(100vw - 24px);
  background: #fff;
  color: #222;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
  padding: 12px 14px 10px;
  font-size: 12px;
}

/* Same reasoning as #elevation-panel-body: the fixed width belongs to the
   body, not the panel, so collapsing (body display:none) shrinks the panel
   down to just its header row instead of staying pinned at the expanded
   charts' width. */
#beads-panel-body {
  width: 400px;
}

.beads-panel__close {
  position: absolute;
  top: 6px;
  right: 8px;
  border: none;
  background: none;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  color: #888;
  padding: 4px;
}

.beads-panel__close:hover {
  color: #222;
}

.beads-panel__toggle {
  position: absolute;
  top: 6px;
  right: 32px;
  font-size: 16px;
}

.beads-panel__header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
  padding-right: 60px;
}

.beads-panel__title {
  font-weight: 600;
  font-size: 13px;
}

.beads-panel__eclipse {
  color: #777;
  margin-bottom: 8px;
}

.beads-panel__charts {
  display: flex;
  gap: 10px;
}

.beads-panel__charts--contacts {
  margin-bottom: 10px;
}

.beads-panel__chart-col {
  flex: 1;
  min-width: 0;
}

.beads-panel__chart-label {
  font-size: 10px;
  color: #777;
  text-align: center;
  margin-bottom: 2px;
}

.beads-panel__circle svg {
  display: block;
  width: 100%;
  height: auto;
}

.beads-panel__circle {
  width: 70%;
  margin: 0 auto 4px;
}

.beads-panel__caption {
  margin-top: 8px;
  color: #555;
  line-height: 1.5;
}

.beads-panel__loading,
.beads-panel__error {
  padding: 14px 0;
  text-align: center;
  color: #777;
}

/* A real sidebar flush against the right edge, full viewport height --
   `body.map-docked #map` above narrows the map to make room rather than
   this overlaying it. Contact Images and Terrain move into this same
   sidebar as accordion sections while docked (see `setDocked` in app.js),
   which is also why nothing here needs to worry about clearing those two
   panels' usual top-right/bottom-right corners -- they're not there
   anymore once docked. Flat-edged on the right (no border-radius on that
   side) to read as attached to the screen edge rather than a floating
   card. */
.docked-panel {
  /* Without this, `width: var(--docked-width)` is a *content* width --
     this panel's own 14px left/right padding then adds 28px on top of
     that for the actual rendered box, so the panel's true left edge ends
     up 28px further left than `--docked-width` alone says. That both
     left a 28px gap between the map's right edge and the panel's real
     edge (the map's own `right: var(--docked-width)` assumed no such
     gap) and put the resize handle -- positioned from the same variable --
     28px inside the panel, on top of its own text instead of on the
     border. border-box makes `--docked-width` the actual rendered width,
     matching what both the map and the handle already assumed it was. */
  box-sizing: border-box;
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: var(--docked-width);
  max-width: calc(100vw - 12px);
  z-index: 10;
  background: #fff;
  color: #222;
  border-radius: 8px 0 0 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
  padding: 34px 14px 12px;
  font-size: 12px;
  overflow-y: auto;
}

/* Three buttons, deliberately distinct actions (see app.js's
   undockEverything doc comment for the close-vs-undock split):
   #docked-panel-close ("x") hides the dock and undocks Terrain/Contact
   Images back to their own floating panels, same as before this button
   existed on its own; #docked-panel-undock (the DevTools-style "separate
   window" icon) does all of that *and* reopens the click info as a real
   floating popup back over the point that produced it; #docked-panel-
   minimize just collapses the sidebar to a small corner button without
   touching any of that. Ordered right-to-left close/minimize/undock to
   put "x" in the same outermost corner spot every other panel's own close
   button already occupies. */
.docked-panel__close,
.docked-panel__minimize,
.docked-panel__undock {
  position: absolute;
  top: 6px;
  border: none;
  background: none;
  cursor: pointer;
  color: #888;
  padding: 4px;
}

.docked-panel__close,
.docked-panel__minimize {
  font-size: 18px;
  line-height: 1;
}

.docked-panel__close {
  right: 8px;
}

.docked-panel__minimize {
  right: 32px;
}

.docked-panel__undock {
  right: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.docked-panel__close:hover,
.docked-panel__minimize:hover,
.docked-panel__undock:hover {
  color: #222;
}

/* Straddles the dock's left edge (tracking the same --docked-width custom
   property `.docked-panel` itself uses, kept in sync by setupDockResize's
   drag handler in app.js) rather than living inside `.docked-panel` --
   that element's own `overflow-y: auto` implicitly forces `overflow-x` to
   `auto` too (a real CSS quirk: setting only one overflow axis promotes
   the other away from its `visible` default), which would clip a child
   positioned to stick out past the panel's own left border the way a
   resize handle needs to. A plain sibling avoids that entirely. */
.docked-panel-resize-handle {
  position: absolute;
  top: 0;
  bottom: 0;
  right: calc(var(--docked-width) - 3px);
  width: 6px;
  z-index: 11;
  cursor: ew-resize;
}

/* The full-height strip above is deliberately grabbable along its entire
   length (easier to hit than a single small icon), but with nothing drawn
   there it was reported as an invisible line sitting on top of the dock's
   own text -- this pseudo-element draws an actual visible grip (a small
   rounded bar, the standard "draggable divider" affordance) centered
   vertically on it, so there's a real icon marking the drag target instead
   of unmarked space over live content. */
.docked-panel-resize-handle::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 4px;
  height: 36px;
  border-radius: 2px;
  background: #ccc;
}

.docked-panel-resize-handle:hover::after {
  background: #888;
}

/* Shown only while docked *and* minimized (see updateDockedLayout in
   app.js) -- top-right is free to use here regardless of minimized state,
   since Contact Images/Terrain only ever occupy that corner while
   undocked entirely (see .docked-panel's own comment). */
.docked-panel-restore {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 10;
  background: #fff;
  color: #888;
  border: none;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  padding: 10px 12px;
}

.docked-panel-restore:hover {
  color: #222;
}

/* Weather (#weather-panel), Contact Images (#beads-panel), and Terrain
   (#elevation-panel) get reparented into #docked-panel itself (as siblings
   of #docked-panel-body, after it) while docked, turning them from
   standalone floating panels into stacked accordion sections -- same
   #weather-toggle/#elevation-toggle/#beads-toggle collapse behavior either
   way (setupCollapsiblePanel only cares about element ids, not where they
   live in the DOM), just restyled here to fit inline instead of as their
   own floating card. `position: relative`
   (not `static`) is required, not just "also works" -- each panel's own
   `−`/`+` toggle and `×` close button are `position: absolute` and need
   *some* positioned ancestor to anchor to; without at least `relative`
   here, they'd anchor to #docked-panel (or further up) instead of their
   own panel, landing in the wrong place entirely. The explicit
   top/right/bottom/left: auto resets clear the standalone-panel corner
   offsets (e.g. #elevation-panel's own `bottom: 24px; right: 12px`) that
   would otherwise still nudge a `position: relative` element away from
   its normal in-flow position, since those offsets apply relative to the
   element's own box under `relative` instead of being ignored the way
   they were under the standalone `absolute` positioning. */
.docked-panel #weather-panel,
.docked-panel #elevation-panel,
.docked-panel #beads-panel {
  position: relative;
  top: auto;
  right: auto;
  bottom: auto;
  left: auto;
  width: auto;
  max-width: none;
  box-shadow: none;
  border-radius: 0;
  border-top: 1px solid #e1e0d9;
  margin: 14px 0 0;
}

.docked-panel #weather-panel-body,
.docked-panel #elevation-panel-body,
.docked-panel #beads-panel-body {
  width: auto;
}

/* `.info-window td` (see that rule) sets `white-space: nowrap` so the
   floating popup -- sized to fit its own content, fixed-width in Leaflet
   mode via createPopup's own minWidth/maxWidth -- never wraps mid-row.
   The docked sidebar has no such guarantee: its width is user-resizable
   down to DOCK_MIN_WIDTH_PX, narrower than what the floating popup was
   sized for, so that same nowrap text (specifically the local-time
   parenthetical, the longest variable-length text in any row) can run
   past the sidebar's edge -- and unlike everything else in the dock,
   which just reflows, this forced a real horizontal scrollbar (the
   `.docked-panel`-wide one documented above, from `overflow-y: auto`
   implicitly promoting `overflow-x` to `auto`). Overriding back to
   `normal` here only for the docked case lets it wrap like every other
   line of text in the sidebar instead; `overflow-wrap: anywhere` is a
   safety net for a long IANA zone id with no internal space to break at
   (there's normally at least one, between the clock time and the zone
   id itself, but not guaranteed for every zone name). */
.docked-panel .info-window__local-time {
  white-space: normal;
  overflow-wrap: anywhere;
}
