:root {
  --bg: #fcfbf9;
  --ink: #1c1c1a;
  --muted: #8a8a84;
  --card: #f1efea;
  --map-water: #0b0d10;
  --map-land: #2a2e35;
  --map-shore: #494f59;
  --map-dot: #6f747d;
  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #161614;
    --ink: #ecebe7;
    --muted: #83837c;
    --card: #211f1d;
  }
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  overflow: hidden; /* the page itself never scrolls — .sheet-inner does */
  background: var(--map-water);
  color: var(--ink);
  font: 16px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-feature-settings: "tnum";
}

/* ---------- map (fills the whole page, fixed behind everything) ---------- */

.map-band {
  position: fixed;
  inset: 0;
  z-index: 0;
}

/* Hidden until either MapKit fails to show up in time (app.js reveals it
   as a real fallback via .show-fallback) or it's confirmed ready — never
   flashed on load just because it's the first thing painted. Until then
   the plain body background (--map-water) is the whole backdrop. */
#map {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  pointer-events: none;
  transition: opacity .6s;
}

.map-band.show-fallback #map { opacity: 1; pointer-events: auto; }

.water-bg { fill: var(--map-water); }
.water-texture { pointer-events: none; }

/* Real Apple Maps, layered over the hand-drawn fallback. Stays invisible
   and non-interactive until mapkit-bridge.js confirms the SDK initialized
   (see index.html for why that's not guaranteed to happen), at which point
   it fades in and the fallback fades out underneath it. */
.mapkit-map {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity .6s;
}

.mapkit-map.ready { opacity: 1; pointer-events: auto; }
/* !important so this always wins over .show-fallback, whichever class
   lands second — MapKit succeeding should always beat the fallback timer. */
.map-band.mapkit-ready #map { opacity: 0 !important; pointer-events: none !important; }

.mk-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--map-dot);
  cursor: pointer;
  transition: background-color .25s;
}

.mk-dot:hover { background: #fff; }

.mk-pin {
  position: relative;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid #0d0f12;
  box-shadow: 0 0 0 8px rgba(255, 255, 255, .14);
}

.mk-pin::after {
  content: "";
  position: absolute;
  inset: -8px;
  border: 1.5px solid #fff;
  border-radius: 50%;
  animation: pulse 2.6s ease-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .mk-pin::after { animation: none; opacity: 0; }
}

#world {
  transition: transform 1s cubic-bezier(.25, 1, .3, 1);
}

@media (prefers-reduced-motion: reduce) {
  #world, #pin { transition: none !important; }
}

#land, .landmass {
  fill: var(--map-land);
  stroke: var(--map-shore);
  stroke-width: .8;
  vector-effect: non-scaling-stroke;
}

#lake-label {
  fill: #59606b;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .55em;
  text-transform: uppercase;
}

#dots circle {
  fill: var(--map-dot);
  cursor: pointer;
  transition: fill .25s;
}

#dots circle:hover { fill: #fff; }

#pin { transition: transform 1s cubic-bezier(.25, 1, .3, 1); }
.pin-halo { fill: #fff; opacity: .12; }
.pin-dot { fill: #fff; stroke: #0d0f12; stroke-width: 1.5; }
.pin-pulse {
  fill: none;
  stroke: #fff;
  stroke-width: 1.5;
  transform-box: fill-box;
  transform-origin: center;
  animation: pulse 2.6s ease-out infinite;
}

@keyframes pulse {
  0% { transform: scale(.45); opacity: .7; }
  75% { transform: scale(1.9); opacity: 0; }
  100% { transform: scale(1.9); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .pin-pulse { animation: none; opacity: 0; }
}

/* floating controls over the map */
.map-band header {
  position: absolute;
  top: 0; left: 0; right: 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: max(1rem, env(safe-area-inset-top)) 1.25rem 0;
  margin: 0 auto;
  max-width: 34rem;
}

.wordmark {
  display: flex;
  align-items: center;
  gap: .4rem;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: -.01em;
  color: #fff;
  text-shadow: 0 1px 8px rgba(0, 0, 0, .6);
}

.wordmark .wave { font-size: 1.15em; }

.wordmark .locale-tag {
  margin-left: .12rem;
  font-size: .58em;
  font-weight: 700;
  letter-spacing: .07em;
  color: rgba(255, 255, 255, .55);
  text-shadow: none;
  vertical-align: .1em;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: .5rem;
}

.icon-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  flex: none;
  border: 1px solid rgba(255, 255, 255, .18);
  border-radius: 50%;
  background-color: rgba(28, 30, 34, .6);
  color: #fff;
  cursor: pointer;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  transition: background-color .2s;
}

.icon-btn:hover { background-color: rgba(28, 30, 34, .85); }

.icon-btn:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

@keyframes flash-confirm {
  0%, 100% { background-color: rgba(28, 30, 34, .6); }
  35% { background-color: rgba(10, 122, 255, .85); }
}

.icon-btn.confirm { animation: flash-confirm .9s ease; }

#beach-select {
  appearance: none;
  border: 1px solid rgba(255, 255, 255, .18);
  border-radius: 99px;
  background-color: rgba(28, 30, 34, .6);
  color: #fff;
  font: inherit;
  font-size: .85rem;
  font-weight: 550;
  padding: .3rem 1.6rem .3rem .9rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5'%3E%3Cpath d='M0 0l4 5 4-5z' fill='%23ffffff'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right .7rem center;
  cursor: pointer;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

#beach-select option { color: #1c1c1a; background: #fff; }

#beach-select:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

/* ---------- the sheet ---------- */

/* A floating card over the full-page map: fixed from partway down the
   screen to the bottom, inset from the sides so the map peeks through on
   either side — just a sliver on mobile, a wide margin on larger screens.
   The map itself never scrolls; .sheet-inner scrolls its own content. */
.sheet {
  position: fixed;
  z-index: 1;
  /* dvh (not vh) so this already accounts for Safari's collapsing chrome —
     a plain rem/vh guess measured wrong the moment the toolbar showed or
     hid. ~34rem is roughly the title + status card + 2x2 stat grid + note,
     so this sits a touch above "just barely fits" without any JS
     measuring/resizing after load (that caused a visible jump). app.js
     nudges `top` further, continuously, as the sheet's own content is
     scrolled — see the parallax listener there — but this is the resting,
     load-time value and never changes on its own. */
  top: clamp(10dvh, calc(100dvh - 34rem), 46dvh);
  left: 0;
  right: 0;
  bottom: 1.25rem;
  width: calc(100% - 1rem);
  max-width: 38rem;
  margin-inline: auto;
  background: var(--bg);
  border-radius: 1.75rem;
  box-shadow: 0 20px 50px rgba(0, 0, 0, .4);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  transition: top .15s ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .sheet { transition: none; }
}

.grabber {
  width: 2.4rem;
  height: .3rem;
  border-radius: 99px;
  background: var(--muted);
  opacity: .4;
  margin: .55rem auto 0;
  flex: none;
  /* Needs to render above .scroll-fade, which now starts at the very top
     of the card (y:0) and would otherwise wash it out — position: static
     elements always stack below positioned ones regardless of DOM order. */
  position: relative;
  z-index: 2;
}

/* Fixed to the top of .sheet regardless of scroll position (a sibling of
   the scrolling .sheet-inner, not a child of it) so content fades out
   under the grabber as it scrolls past, rather than hard-clipping at the
   rounded corner. .sheet's own `position: fixed` is what this is absolute
   against. Must start at the very top edge (top: 0) — starting any lower
   left a gap where scrolled-past content still showed at full, unfaded
   opacity right up against the rounded corner, i.e. exactly the "hard
   line" this is supposed to avoid. Kept short so it clears the h1 title
   at rest (unscrolled) instead of washing out the page heading. */
.scroll-fade {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1.5rem;
  background: linear-gradient(to bottom, var(--bg) 50%, transparent);
  pointer-events: none;
  z-index: 1;
}

.sheet-inner {
  flex: 1;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  width: 100%;
  padding: .9rem 1.25rem 2rem;
}

h1 {
  margin: 0 0 1rem;
  font-size: 2rem;
  font-weight: 800;
  letter-spacing: -.02em;
  line-height: 1.15;
}

/* the verdict card — full width above the stat cards */
.status-card {
  grid-column: 1 / -1;
  transition: background-color .4s;
}

.status-card #status-word {
  display: block;
  font-size: 2.2rem;
  font-weight: 800;
  letter-spacing: -.03em;
  line-height: 1.1;
}

.status-card #status-detail {
  margin-top: .3rem;
  font-size: .82rem;
}

.status-card.good {
  background: #0a7aff;
  color: #fff;
}

.status-card.bad {
  background: #eb4034;
  color: #fff;
}

/* Still swimmable, but the E. coli reading is close enough to the posting
   limit that it's worth a second look before heading out — not a full
   "no swim", but not a plain "swim" either. */
.status-card.caution {
  background: #f2b90f;
  color: #201a02;
}

.status-card.good dt,
.status-card.bad dt,
.status-card.good #status-detail,
.status-card.bad #status-detail {
  color: rgba(255, 255, 255, .82);
}

.status-card.caution dt,
.status-card.caution #status-detail {
  color: rgba(32, 26, 2, .72);
}

/* stat cards */
.stats {
  margin: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: .75rem;
}

.stat {
  background: var(--card);
  border-radius: 1.15rem;
  padding: .85rem 1rem .95rem;
  /* Reserves room for a full value line + subtext line from the start, so
     cards don't visibly collapse/resize as data streams in at different
     times (water quality + water temp arrive first, wind/air/waves after
     the weather fetch resolves) — that resizing was the "empty state
     flash" on load. */
  min-height: 5.7rem;
}

.stat dt {
  font-size: .72rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
}

.stat dd {
  margin: .2rem 0 0;
  font-size: 1.4rem;
  font-weight: 650;
  letter-spacing: -.01em;
}

/* The value itself fades in once its JS-populated span actually has
   content, rather than snapping from blank to filled. */
.stat dd > span {
  display: inline-block;
  opacity: 1;
  transition: opacity .3s ease;
}

.stat dd > span:empty {
  opacity: 0;
}

.stat dd small {
  display: block;
  font-size: .78rem;
  font-weight: 450;
  color: var(--muted);
  letter-spacing: 0;
}

.arrow {
  display: inline-block;
  font-weight: 400;
  color: var(--muted);
}

.paddle-note {
  margin: 1.2rem 0 0;
  font-size: .9rem;
  color: var(--muted);
}

#loading {
  color: var(--muted);
  margin: 1rem 0;
}

footer {
  margin-top: auto;
  padding-top: 2.5rem;
  font-size: .75rem;
  color: var(--muted);
}

footer p { margin: .3rem 0; }

footer a {
  color: inherit;
  text-underline-offset: 2px;
}

footer .made-in {
  margin-top: .7rem;
  opacity: .8;
}

.text-btn {
  margin-top: .9rem;
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  font-size: .82rem;
  font-weight: 600;
  color: #0a7aff;
  cursor: pointer;
  transition: opacity .2s;
}

.text-btn:hover { opacity: .75; }

.text-btn:focus-visible {
  outline: 2px solid #0a7aff;
  outline-offset: 3px;
}

@keyframes flash-confirm-text {
  0%, 100% { color: #0a7aff; }
  35% { color: #1fae5a; }
}

.text-btn.confirm { animation: flash-confirm-text .9s ease; }

/* ---------- about dialog ---------- */

dialog#about-dialog {
  position: fixed;
  margin: auto;
  border: none;
  border-radius: 1.5rem;
  padding: 1.6rem 1.6rem 1.8rem;
  width: calc(100% - 2.5rem);
  max-width: 24rem;
  max-height: min(85dvh, 34rem);
  overflow-y: auto;
  background: var(--bg);
  color: var(--ink);
  box-shadow: 0 20px 60px rgba(0, 0, 0, .45);
}

dialog#about-dialog::backdrop {
  background: rgba(0, 0, 0, .55);
}

dialog#about-dialog h2 {
  margin: 0 0 .7rem;
  font-size: 1.25rem;
  font-weight: 800;
  letter-spacing: -.02em;
}

dialog#about-dialog h3 {
  margin: 1.2rem 0 .4rem;
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--muted);
}

dialog#about-dialog p {
  margin: .55rem 0;
  font-size: .92rem;
  line-height: 1.55;
}

dialog#about-dialog ul {
  margin: 0;
  padding-left: 1.1rem;
  font-size: .88rem;
  line-height: 1.65;
}

dialog#about-dialog a {
  color: inherit;
}

dialog#about-dialog .fine-print {
  margin-top: 1.1rem;
  font-size: .78rem;
  color: var(--muted);
}

dialog#about-dialog form {
  position: absolute;
  top: .8rem;
  right: .8rem;
  margin: 0;
}

.dialog-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  border: none;
  border-radius: 50%;
  background: var(--card);
  color: var(--ink);
  font-size: .85rem;
  line-height: 1;
  cursor: pointer;
}

.toggle-row {
  display: flex;
  align-items: flex-start;
  gap: .75rem;
  margin: .5rem 0 0;
  cursor: pointer;
}

.toggle-row input[type="checkbox"] {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.toggle-switch {
  flex: none;
  width: 2.6rem;
  height: 1.5rem;
  margin-top: .1rem;
  border-radius: 99px;
  background: var(--card);
  border: 1px solid rgba(128, 128, 128, .3);
  position: relative;
  transition: background-color .2s, border-color .2s;
}

.toggle-switch::after {
  content: "";
  position: absolute;
  top: .14rem;
  left: .14rem;
  width: 1.1rem;
  height: 1.1rem;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
  transition: transform .2s;
}

.toggle-row input:checked + .toggle-switch {
  background: #0a7aff;
  border-color: transparent;
}

.toggle-row input:checked + .toggle-switch::after {
  transform: translateX(1.1rem);
}

.toggle-row input:focus-visible + .toggle-switch {
  outline: 2px solid #0a7aff;
  outline-offset: 2px;
}

.toggle-copy {
  display: flex;
  flex-direction: column;
  gap: .2rem;
}

.toggle-copy strong {
  font-size: .92rem;
}

.toggle-copy small {
  font-size: .8rem;
  line-height: 1.45;
  color: var(--muted);
}
