:root {
  --bg: #f2f8fd;
  --surface: #ffffff;
  --surface-alt: #eaf3fb;
  --border: #dbe9f5;
  --text: #22384d;
  --muted: #6c8299;
  --primary: #6fb1e8;
  --primary-strong: #4a93d6;
  --primary-text: #0d3a63;
  --accent-green: #7fcfae;
  --accent-green-text: #0e4d38;
  --accent-coral: #f0a898;
  --accent-coral-text: #6b2a1c;
  --danger: #e07a66;
  --danger-strong: #cc6350;
  --shadow-sm: 0 2px 8px rgba(30, 74, 120, 0.06);
  --shadow-md: 0 6px 20px rgba(30, 74, 120, 0.10);
  --radius: 16px;
  --radius-sm: 10px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
}

body {
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 {
  margin: 0 0 8px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text);
}

h1 { font-size: 1.6rem; }
h2 { font-size: 1.05rem; }

p { margin: 0 0 8px; color: var(--muted); }

a { color: var(--primary-strong); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---------- layout ---------- */

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  max-width: 960px;
  margin: 0 auto;
}

.brand {
  font-weight: 700;
  font-size: 1.1rem;
  color: var(--primary-text);
  letter-spacing: -0.01em;
}

/* Desktop nav only. The bottom bar takes over below 720px so the two are
   never on screen at the same time. */
.topnav {
  display: none;
  gap: 4px;
}

@media (min-width: 720px) {
  .topnav { display: flex; }
}

.topnav a {
  color: var(--muted);
  font-weight: 500;
  font-size: 0.9rem;
  padding: 8px 12px;
  border-radius: var(--radius-sm);
}

.topnav a.active {
  color: var(--primary-text);
  background: var(--surface-alt);
}

.topnav a:hover { text-decoration: none; background: var(--surface-alt); }

.page {
  max-width: 960px;
  margin: 0 auto;
  padding: 8px 20px 96px;
}

.page.narrow { max-width: 480px; }

@media (min-width: 720px) {
  .page { padding-bottom: 40px; }
}

.bottomnav {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  display: flex;
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding: 8px 12px calc(8px + env(safe-area-inset-bottom));
  box-shadow: 0 -2px 12px rgba(30, 74, 120, 0.05);
}

.bottomnav a {
  flex: 1;
  text-align: center;
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--muted);
  padding: 6px 0;
  border-radius: var(--radius-sm);
}

.bottomnav a.active { color: var(--primary-strong); }

/* Must come after the .bottomnav rules above, or the display:flex wins and
   both navs show at once. */
@media (min-width: 720px) {
  .bottomnav { display: none; }
}

/* ---------- cards & panels ---------- */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 18px;
  margin-bottom: 16px;
}

.card-flat {
  background: var(--surface-alt);
  border-radius: var(--radius);
  padding: 18px;
  margin-bottom: 16px;
}

.place-card {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 14px;
  margin-bottom: 12px;
  cursor: pointer;
  transition: box-shadow 0.15s ease, transform 0.15s ease;
}

.place-card:hover { box-shadow: var(--shadow-md); transform: translateY(-1px); }

.place-thumb {
  width: 56px;
  height: 56px;
  border-radius: var(--radius-sm);
  background: linear-gradient(135deg, var(--primary), var(--accent-green));
  flex-shrink: 0;
}

.place-info { flex: 1; min-width: 0; }
.place-name { font-weight: 600; font-size: 1.02rem; }
.place-meta { color: var(--muted); font-size: 0.85rem; margin-top: 2px; }

.badge {
  display: inline-block;
  font-size: 0.72rem;
  font-weight: 600;
  padding: 4px 10px;
  border-radius: 999px;
  letter-spacing: 0.02em;
}

.badge-go { background: #e3f7ee; color: var(--accent-green-text); }
.badge-no-go { background: #fbeeeb; color: var(--accent-coral-text); }
.badge-neutral { background: var(--surface-alt); color: var(--muted); }

.badge-red    { background: #fbe6e2; color: #8a3323; }
.badge-orange { background: #fdecd6; color: #8a5a12; }
.badge-yellow { background: #fbf3ce; color: #7a6a0f; }
.badge-green  { background: #e3f7ee; color: var(--accent-green-text); }

/* ---------- score / dashboard ---------- */

.verdict-block { text-align: center; margin: 4px 0 20px; }

.verdict-pill {
  display: inline-block;
  padding: 14px 32px;
  border-radius: 999px;
  font-size: 1.3rem;
  font-weight: 700;
  letter-spacing: 0.01em;
}

.verdict-pill.red    { background: #fbe6e2; color: #8a3323; }
.verdict-pill.orange { background: #fdecd6; color: #8a5a12; }
.verdict-pill.yellow { background: #fbf3ce; color: #7a6a0f; }
.verdict-pill.green  { background: #e3f7ee; color: var(--accent-green-text); }

.best-hours { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0; }
.hour-chip {
  background: var(--surface-alt);
  color: var(--primary-text);
  font-weight: 600;
  font-size: 0.85rem;
  padding: 6px 12px;
  border-radius: 999px;
}

.reasons { list-style: none; padding: 0; margin: 8px 0 0; color: var(--muted); font-size: 0.9rem; }
.reasons li { padding: 3px 0; }

.dash-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
}

@media (min-width: 560px) {
  .dash-grid { grid-template-columns: repeat(4, 1fr); }
}

.dash-tile {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 14px;
  cursor: pointer;
  transition: box-shadow 0.15s ease;
}

.dash-tile:hover { box-shadow: var(--shadow-md); }
.dash-tile.active { border-color: var(--primary); background: var(--surface-alt); }

.dash-label { font-size: 0.72rem; color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
.dash-value { font-size: 1.4rem; font-weight: 700; color: var(--text); margin-top: 4px; }
.dash-sub { font-size: 0.78rem; color: var(--muted); margin-top: 2px; }

.outlook-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.9rem;
}
.outlook-row:last-child { border-bottom: none; }

/* ---------- map ---------- */

/* `position: relative` is load-bearing, not cosmetic. The splash elements are
   absolutely positioned children of this box and are placed in ITS coordinate
   space. Without it they resolve against the initial containing block, so every
   ripple was drawn at page coordinates - hundreds of pixels away from the map,
   somewhere off the top of the document. That, plus the z-index below, is why
   the splash appeared not to exist. */
.map-wrap {
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  border: 1px solid var(--border);
  margin-bottom: 16px;
}

#map { height: 360px; width: 100%; }

@media (min-width: 720px) {
  #map { height: 480px; }
}

.zone-panel {
  display: none;
}
.zone-panel.visible { display: block; }

.zone-panel .badge { margin-bottom: 6px; }

.map-legend-note {
  font-size: 0.78rem;
  color: var(--muted);
  margin-top: 6px;
}

/* ---------- buttons ---------- */

.btn {
  display: inline-block;
  width: 100%;
  padding: 15px;
  border-radius: var(--radius-sm);
  border: none;
  font-size: 1rem;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  min-height: 48px;
  font-family: inherit;
}

.btn:hover { text-decoration: none; }

.btn-primary { background: var(--primary-strong); color: #ffffff; box-shadow: var(--shadow-sm); }
.btn-primary:hover { background: #3d80bd; }
.btn-secondary { background: var(--surface); color: var(--text); border: 1px solid var(--border); }
.btn-ghost { background: transparent; color: var(--muted); border: 1px dashed var(--border); }
.btn-block-gap { margin-bottom: 10px; }

/* ---------- method/rods picker ---------- */

.method-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin: 16px 0;
}

.method-card {
  background: var(--surface);
  border: 2px solid var(--border);
  border-radius: var(--radius);
  padding: 16px 8px;
  text-align: center;
  cursor: pointer;
  font-weight: 600;
  font-size: 0.9rem;
}

.method-card input { display: none; }
.method-card.selected,
.method-card:has(input:checked) {
  border-color: var(--primary-strong);
  background: var(--surface-alt);
  color: var(--primary-text);
}

.stepper {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
  margin: 12px 0 20px;
}

.stepper button {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  font-size: 1.3rem;
  font-weight: 600;
  color: var(--primary-strong);
  cursor: pointer;
}

.stepper .count { font-size: 1.8rem; font-weight: 700; min-width: 40px; text-align: center; }

/* ---------- session logging ---------- */

.chip-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin: 16px 0;
}

.chip {
  padding: 16px 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  text-align: center;
  font-weight: 600;
  min-height: 48px;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.95rem;
  color: var(--text);
}

.chip.secondary { color: var(--muted); font-size: 0.85rem; }
.chip:hover { border-color: var(--primary); }

.counter { text-align: center; margin: 16px 0; }
.counter-number { font-size: 2.6rem; font-weight: 700; color: var(--primary-text); }
.counter-label { color: var(--muted); font-size: 0.85rem; }

.catch-list { list-style: none; padding: 0; margin: 0; max-height: 200px; overflow-y: auto; }
.catch-list li {
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
  display: flex;
  justify-content: space-between;
  color: var(--muted);
  font-size: 0.9rem;
}

label { display: block; margin: 14px 0 6px; color: var(--muted); font-size: 0.85rem; font-weight: 500; }

input[type=text], input[type=number], textarea {
  width: 100%;
  padding: 13px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 1rem;
  font-family: inherit;
}

input:focus, textarea:focus { outline: 2px solid var(--primary); outline-offset: 1px; }

textarea { min-height: 76px; resize: vertical; }

.blank-toggle {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px;
  background: var(--surface-alt);
  border-radius: var(--radius-sm);
  margin: 14px 0;
  font-size: 0.92rem;
}

.blank-toggle input { width: 20px; height: 20px; }

/* ---------- history/stats ---------- */

table.history { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
table.history th, table.history td { text-align: left; padding: 9px 6px; border-bottom: 1px solid var(--border); }
table.history th { color: var(--muted); font-weight: 600; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.03em; }

.stat-row { display: flex; gap: 12px; margin-bottom: 18px; }
.stat { flex: 1; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px; text-align: center; box-shadow: var(--shadow-sm); }
.stat-number { font-size: 1.5rem; font-weight: 700; color: var(--primary-text); }
.stat-label { color: var(--muted); font-size: 0.76rem; margin-top: 2px; }

.timer { text-align: center; color: var(--muted); font-size: 0.9rem; margin-bottom: 4px; }

.bottom-fixed {
  position: fixed;
  left: 0; right: 0; bottom: 56px;
  padding: 10px 16px;
  background: linear-gradient(180deg, transparent, var(--bg) 35%);
}

@media (min-width: 720px) {
  .bottom-fixed { position: static; background: none; padding: 12px 0 0; }
}

.section-label {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin: 4px 0 10px;
}

/* ---------- weather table ---------- */

table.weather {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.88rem;
}

table.weather th {
  text-align: left;
  padding: 8px 6px;
  color: var(--muted);
  font-weight: 600;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border-bottom: 1px solid var(--border);
}

table.weather td {
  padding: 11px 6px;
  border-bottom: 1px solid var(--border);
}

table.weather tbody tr {
  cursor: pointer;
  transition: background 0.12s ease;
}

table.weather tbody tr:hover { background: var(--surface-alt); }
table.weather tbody tr.selected { background: var(--surface-alt); box-shadow: inset 3px 0 0 var(--primary-strong); }
table.weather tbody tr:last-child td { border-bottom: none; }

.wind-arrow {
  display: inline-block;
  font-size: 1.05rem;
  color: var(--primary-strong);
  margin-right: 4px;
}

/* ---------- heat legend ---------- */

.heat-legend {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.72rem;
  color: var(--muted);
  margin-top: 10px;
}

.heat-bar {
  flex: 1;
  height: 10px;
  border-radius: 999px;
  background: linear-gradient(90deg, #c73e2d, #de6a3a, #ed9844, #f6c359,
                                     #f3e378, #cee07a, #9cd07a, #6abc7c, #3aa06a);
}

.lang-switch {
  display: flex;
  gap: 2px;
  background: var(--surface-alt);
  border-radius: 999px;
  padding: 3px;
}

.lang-switch a {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--muted);
  padding: 6px 9px;
  border-radius: 999px;
  text-decoration: none;
  min-width: 32px;
  text-align: center;
}

.lang-switch a:hover { text-decoration: none; color: var(--primary-strong); }
.lang-switch a.active { background: var(--surface); color: var(--primary-text); box-shadow: var(--shadow-sm); }

.season-line {
  font-size: 0.88rem;
  color: var(--text);
  margin-bottom: 12px;
}

.season-caveat { color: var(--muted); font-size: 0.8rem; }

.session-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  background: #e3f7ee;
  color: var(--accent-green-text);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  margin-bottom: 14px;
  font-size: 0.86rem;
  text-decoration: none;
}

.session-banner:hover { text-decoration: none; background: #d6f2e5; }

.inline-link-row {
  display: flex;
  gap: 10px;
  margin-bottom: 14px;
}

.inline-link-row a {
  flex: 1;
  text-align: center;
  padding: 11px;
  border-radius: var(--radius-sm);
  background: var(--surface);
  border: 1px solid var(--border);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--primary-strong);
  text-decoration: none;
}

.inline-link-row a:hover { text-decoration: none; border-color: var(--primary); }

.provenance-note {
  font-size: 0.75rem;
  color: var(--muted);
  background: #fff8e6;
  border: 1px solid #f2e2b8;
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  margin-top: 10px;
}

/* ---------- fish pin + splash ---------- */

.pin-dock {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--surface-alt);
  border-radius: var(--radius);
  padding: 12px 14px;
  margin-bottom: 12px;
}

.fish-pin {
  width: 54px;
  height: 40px;
  cursor: grab;
  flex-shrink: 0;
  touch-action: none;
  animation: fish-bob 2.4s ease-in-out infinite;
}

.fish-pin:active { cursor: grabbing; }

@keyframes fish-bob {
  0%, 100% { transform: translateY(0) rotate(-3deg) scaleY(1); }
  35%      { transform: translateY(-4px) rotate(1deg) scaleY(0.97); }
  70%      { transform: translateY(-1px) rotate(3deg) scaleY(1.02); }
}

.pin-hint { font-size: 0.82rem; color: var(--muted); }

/* ---------- splash ----------

   The old splash was three concentric white circles growing from 4px to 120px
   by animating `width`/`height`. Two things were wrong with it. It never
   appeared at all - see the z-index note below and `position` on `.map-wrap` -
   and even where it did, perfect circles growing on a map read as a radar
   ping, not as water. Water seen at an angle spreads as an ELLIPSE, the rings
   travel at different speeds, and the impact throws droplets.

   Everything here animates `transform` and `opacity` only, so the whole splash
   runs on the compositor and stays smooth on a phone. Animating width/height
   forced layout on every frame of every ring, which is most of why it stuttered.

   z-index 500 clears Leaflet's panes, which sit at 400. Below that the rings
   are painted UNDER the map tiles and are invisible even when positioned
   correctly - the second half of the "there are no splashes" bug. */

.splash-layer {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 500;
}

.splash-ring {
  position: absolute;
  width: 130px;
  height: 58px;              /* flattened: surface ripples seen at an angle */
  margin-left: -65px;
  margin-top: -29px;
  border: 2px solid rgba(255, 255, 255, 0.92);
  border-radius: 50%;
  will-change: transform, opacity;
  animation: ripple 1150ms cubic-bezier(0.16, 0.85, 0.3, 1) forwards;
}

@keyframes ripple {
  0%   { transform: scale(0.06); opacity: 0; }
  9%   { opacity: 0.95; }
  55%  { opacity: 0.5; }
  100% { transform: scale(1); opacity: 0; }
}

/* The contact ring: small, fast, and bright, so the moment of impact has a
   hard edge before the slower rings take over. */
.splash-ring.contact {
  width: 44px; height: 20px; margin-left: -22px; margin-top: -10px;
  border-width: 3px;
  animation-duration: 420ms;
  animation-timing-function: cubic-bezier(0.1, 0.8, 0.2, 1);
}

/* Water displaced upward, then falling back. Stretches vertically on the way
   up and squashes as it collapses. */
.splash-crown {
  position: absolute;
  width: 26px; height: 26px;
  margin-left: -13px; margin-top: -13px;
  border-radius: 50% 50% 46% 46%;
  background: radial-gradient(ellipse at 50% 70%,
              rgba(255, 255, 255, 0.95), rgba(214, 236, 250, 0.75) 60%,
              rgba(180, 216, 240, 0) 100%);
  will-change: transform, opacity;
  animation: crown 520ms cubic-bezier(0.2, 0.7, 0.3, 1) forwards;
}

@keyframes crown {
  0%   { transform: translateY(2px) scale(0.3, 0.25); opacity: 0; }
  18%  { transform: translateY(-9px) scale(0.8, 1.45); opacity: 1; }
  55%  { transform: translateY(-4px) scale(1.05, 0.85); opacity: 0.7; }
  100% { transform: translateY(3px) scale(1.3, 0.28); opacity: 0; }
}

/* Thrown droplets. Each gets its own --dx / --peak / --fall inline, so the
   spray is asymmetric - a symmetric one reads as a graphic, not a splash.
   The arc comes from the 42% keyframe sitting above the endpoint. */
.splash-drop {
  position: absolute;
  width: 6px; height: 6px;
  margin-left: -3px; margin-top: -3px;
  border-radius: 50%;
  background: #ffffff;
  box-shadow: 0 0 3px rgba(255, 255, 255, 0.8);
  will-change: transform, opacity;
  animation: droplet 620ms cubic-bezier(0.3, 0.5, 0.6, 1) forwards;
}

@keyframes droplet {
  0%   { transform: translate(0, 0) scale(0.4); opacity: 0; }
  14%  { opacity: 1; }
  42%  { transform: translate(calc(var(--dx) * 0.6), var(--peak)) scale(1); opacity: 1; }
  100% { transform: translate(var(--dx), var(--fall)) scale(0.35); opacity: 0; }
}

/* A brief dark dimple where the fish went through the surface. */
.splash-hole {
  position: absolute;
  width: 30px; height: 14px;
  margin-left: -15px; margin-top: -7px;
  border-radius: 50%;
  background: radial-gradient(ellipse at 50% 50%,
              rgba(22, 60, 92, 0.5), rgba(22, 60, 92, 0) 72%);
  will-change: transform, opacity;
  animation: hole 700ms ease-out forwards;
}

@keyframes hole {
  0%   { transform: scale(0.2); opacity: 0; }
  16%  { transform: scale(1); opacity: 1; }
  100% { transform: scale(1.7); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .splash-layer { display: none; }
}

/* The pin follows the finger while held, instead of the browser's drag ghost
   (which is invisible on touch). */
.pin-ghost {
  position: fixed;
  z-index: 3000;
  width: 60px;
  height: 44px;
  margin-left: -30px;
  margin-top: -22px;
  pointer-events: none;
  filter: drop-shadow(0 6px 10px rgba(30, 74, 120, 0.35));
  transition: transform 80ms ease-out;
}

.pin-ghost.lifted { transform: scale(1.15) rotate(-8deg); }

/* Release: gather, arc over the top, then drop nose-first through the surface.

   Rotation is NEGATIVE throughout the descent. The silhouette's head is at the
   LEFT, and positive (clockwise) rotation swings the left side UP - which dives
   the fish tail first. This shipped tail-first three times. Check it with
   `tools/animation_filmstrip.py` and look at the frames; you cannot see it from
   the keyframes.

   Why it read as stiff before: one linear-ish ease carried the whole descent,
   so the fish fell at a constant rate. Real falling accelerates. Each phase now
   sets its own `animation-timing-function` - slow out of the anticipation, then
   a steep gravity curve from the apex down. The rotation also leads the path
   slightly, the way a real fish pitches before it drops rather than after. */
.pin-ghost.diving {
  animation: dive 860ms forwards;
  transform-origin: 50% 50%;
  transition: none;
  will-change: transform, opacity;
}

@keyframes dive {
  /* Gather: a small dip and a nose-up pitch. Anticipation in the opposite
     direction to the move is what stops it looking like a dropped object. */
  0%   { transform: translate(0, 0) rotate(6deg) scale(1.15);
         animation-timing-function: cubic-bezier(0.34, 0.02, 0.62, 0.5); }
  10%  { transform: translate(-2px, 5px) rotate(14deg) scale(1.13);
         animation-timing-function: cubic-bezier(0.16, 0.7, 0.4, 1); }
  /* Up and over the apex. Decelerating into the top of the arc. */
  30%  { transform: translate(6px, -26px) rotate(20deg) scale(1.1);
         animation-timing-function: cubic-bezier(0.4, 0, 0.7, 0.42); }
  /* Pitching down through horizontal while barely losing height - the pivot
     that makes it read as a dive rather than a fall. */
  46%  { transform: translate(11px, -20px) rotate(-30deg) scale(1.06);
         animation-timing-function: cubic-bezier(0.5, 0, 0.85, 0.5); }
  /* Committed, accelerating, stretched along its own axis to sell speed. */
  66%  { transform: translate(14px, 12px) rotate(-72deg) scale(1.02, 1.12);
         animation-timing-function: cubic-bezier(0.55, 0, 0.9, 0.6); }
  /* Contact. Squash back to neutral as the surface takes it. The splash is
     fired from JS at this instant. */
  82%  { transform: translate(15px, 46px) rotate(-88deg) scale(0.9, 0.94);
         opacity: 1; animation-timing-function: cubic-bezier(0.4, 0, 0.9, 0.7); }
  /* Under the surface. It only fades once it is below, so it reads as entering
     water rather than dissolving in mid-air. */
  100% { transform: translate(15px, 86px) rotate(-95deg) scale(0.42); opacity: 0; }
}



.fish-pin.is-held { opacity: 0.25; }

.leaflet-container .fish-marker { animation: fish-bob 2.4s ease-in-out infinite; }

/* ---------- catch cards + species search ---------- */

.catch-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}

.catch-card:last-child { border-bottom: none; }

.catch-photo {
  width: 46px; height: 46px;
  border-radius: var(--radius-sm);
  object-fit: cover;
  flex-shrink: 0;
}

.catch-photo-empty {
  width: 46px; height: 46px;
  border-radius: var(--radius-sm);
  background: var(--surface-alt);
  flex-shrink: 0;
}

.catch-main { flex: 1; min-width: 0; }
.catch-name { font-weight: 600; font-size: 0.95rem; }
.catch-meta { color: var(--muted); font-size: 0.8rem; }

.catch-actions { display: flex; gap: 6px; }

.icon-btn {
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
  cursor: pointer;
  font-size: 0.8rem;
  color: var(--muted);
  font-family: inherit;
}

.icon-btn:hover { border-color: var(--primary); color: var(--primary-strong); }
.icon-btn.danger:hover { border-color: var(--danger); color: var(--accent-coral-text); }

/* ---------- one-tap fish buttons ---------- */

.fish-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin-bottom: 18px;
}

.fish-btn {
  width: 100%;
  min-height: 80px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 12px 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text);
  line-height: 1.15;
  transition: transform 0.08s ease, box-shadow 0.15s ease;
}

.fish-btn:hover { box-shadow: var(--shadow-md); }
.fish-btn:active { transform: scale(0.96); }

/* Icons inherit `color`, so each species can tint its own silhouette while
   the shapes stay shared. */
.fish-icon { display: block; }
.catch-photo-empty .fish-icon { opacity: 0.55; }

/* Idle: a slow tail-side swim. Deliberately small - this sits behind a
   number the angler is reading, so it must never pull focus. */
.fish-btn .fish-icon { animation: swim 3.2s ease-in-out infinite; transform-origin: 70% 50%; }
.fish-btn:nth-child(2n) .fish-icon { animation-delay: -0.8s; }
.fish-btn:nth-child(3n) .fish-icon { animation-delay: -1.6s; }

@keyframes swim {
  0%, 100% { transform: translateX(0) rotate(0deg); }
  25%      { transform: translateX(1.5px) rotate(-3deg); }
  75%      { transform: translateX(-1.5px) rotate(3deg); }
}

/* Squash on press, overshoot on release: the standard tactile-feedback pair.
   Kept to 0.94/1.04 so it reads as responsive rather than bouncy. */
.fish-btn:active { transform: scale(0.94); transition: transform 70ms ease-out; }
.fish-btn { transition: transform 260ms cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.15s ease; }

.fish-icon.pop { animation: icon-pop 420ms cubic-bezier(0.34, 1.56, 0.64, 1); }

@keyframes icon-pop {
  0%   { transform: scale(0.6) rotate(-12deg); opacity: 0.4; }
  60%  { transform: scale(1.08) rotate(3deg); opacity: 1; }
  100% { transform: scale(1) rotate(0deg); }
}

.species-row-inline { display: flex; align-items: center; gap: 12px; }
.species-row-inline .select-input { flex: 1; }

@media (prefers-reduced-motion: reduce) {
  .fish-btn .fish-icon, .fish-icon.pop, .fish-pin { animation: none !important; }
}

.catch-photo-empty {
  display: flex;
  align-items: center;
  justify-content: center;
}

details.card summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 0.92rem;
  color: var(--primary-strong);
  list-style: none;
}

details.card summary::-webkit-details-marker { display: none; }
details.card summary::before { content: "＋ "; font-weight: 700; }
details.card[open] summary::before { content: "－ "; }

.select-input {
  width: 100%;
  padding: 13px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 1rem;
  font-family: inherit;
}

/* ---------- size sliders ---------- */

.slider-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin: 16px 0 2px;
}

.slider-value {
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--primary-text);
}

.slider {
  width: 100%;
  height: 40px;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  margin: 0;
}

.slider::-webkit-slider-runnable-track {
  height: 8px;
  border-radius: 999px;
  background: var(--surface-alt);
  border: 1px solid var(--border);
}

.slider::-moz-range-track {
  height: 8px;
  border-radius: 999px;
  background: var(--surface-alt);
  border: 1px solid var(--border);
}

/* Deliberately oversized: this is dragged with cold, wet hands. */
.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 32px;
  height: 32px;
  margin-top: -13px;
  border-radius: 50%;
  background: var(--primary-strong);
  border: 3px solid #fff;
  box-shadow: var(--shadow-sm);
  cursor: grab;
}

.slider::-moz-range-thumb {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--primary-strong);
  border: 3px solid #fff;
  box-shadow: var(--shadow-sm);
  cursor: grab;
}

.search-row { display: flex; gap: 8px; margin-bottom: 12px; }
.search-row input { flex: 1; }

.species-results { max-height: 260px; overflow-y: auto; }

.species-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}

.species-latin { font-size: 0.75rem; color: var(--muted); font-style: italic; }

.btn-danger-solid {
  background: var(--danger);
  color: #fff;
  border: none;
}

.btn-danger-solid:hover { background: #cc5340; }

/* ---------- current conditions ---------- */

.now-card {
  display: flex;
  align-items: center;
  gap: 16px;
  background: linear-gradient(135deg, var(--surface), var(--surface-alt));
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 16px 18px;
  margin-bottom: 16px;
}

.now-temp {
  font-size: 2.6rem;
  font-weight: 700;
  color: var(--primary-text);
  line-height: 1;
}

.now-temp span { font-size: 1.1rem; font-weight: 600; color: var(--muted); }
.now-meta { font-size: 0.86rem; color: var(--text); }
.now-label {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  margin-bottom: 3px;
}

.partial-flag {
  display: inline-block;
  font-size: 0.62rem;
  font-weight: 600;
  color: var(--muted);
  background: var(--surface-alt);
  border-radius: 999px;
  padding: 2px 7px;
  margin-left: 5px;
  vertical-align: middle;
}

/* Segmented switch. One track, one thumb, exactly one option on - the same
   control as the language picker in the topbar, scaled up to a full-width row.
   Used for the water-type filter on the places list: a PZW water and a
   commercial fishery are alternatives, and a switch says that where three
   separate pills only said "here are three buttons".

   The thumb is positioned from --switch-index (0-based) over --switch-count
   equal columns, so the same CSS serves any number of options and the slide is
   a single transition rather than a class swap per segment. */
.switch {
  position: relative;
  display: grid;
  grid-template-columns: repeat(var(--switch-count, 3), 1fr);
  align-items: stretch;
  gap: 0;
  margin: 0 0 14px;
  padding: var(--switch-pad, 3px);
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 999px;
  /* Nothing inside is selectable text - it is a control, and a long-press that
     highlights "Commercial fishery" is never what was meant. */
  user-select: none;
  -webkit-user-select: none;
}

.switch-thumb {
  position: absolute;
  top: var(--switch-pad, 3px);
  bottom: var(--switch-pad, 3px);
  left: var(--switch-pad, 3px);
  width: calc((100% - 2 * var(--switch-pad, 3px)) / var(--switch-count, 3));
  transform: translateX(calc(var(--switch-index, 0) * 100%));
  background: var(--surface);
  border-radius: 999px;
  box-shadow: var(--shadow-sm);
  transition: transform 0.22s cubic-bezier(0.32, 0.72, 0, 1);
  pointer-events: none;
}

.switch-option {
  position: relative;   /* above the thumb */
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 32px;
  padding: 6px 8px;
  font-size: 0.82rem;
  font-weight: 600;
  line-height: 1.15;
  /* A long translation must never break out of the track - it gets clipped
     instead, and the aria-label still names the control. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: center;
  text-decoration: none;
  color: var(--muted);
  border-radius: 999px;
  transition: color 0.18s ease;
  -webkit-tap-highlight-color: transparent;
}

.switch-option:hover { text-decoration: none; color: var(--primary-strong); }
.switch-option.is-active { color: var(--primary-text); }

/* A tap that lands on the option the thumb is already under still has to feel
   like a press. */
.switch-option:active { transform: scale(0.97); }

@media (prefers-reduced-motion: reduce) {
  .switch-thumb { transition: none; }
  .switch-option:active { transform: none; }
}

.empty-state {
  color: var(--muted);
  font-size: 0.9rem;
  padding: 18px 2px;
}

/* ---------------------------------------------------------------------------
   Accounts: the topbar control, and the two forms.

   Same pastel instrument language as the rest of the app - a sign-in page that
   looks like a bank's would be the loudest screen in a quiet app. The only
   colour that is not ours is Google's mark, which their brand terms require to
   be drawn as issued.
   --------------------------------------------------------------------------- */
.account-form {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0;
}

.account-name {
  font-size: 0.74rem;
  font-weight: 700;
  color: var(--primary-text);
  max-width: 84px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.account-signout,
.account-signin {
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--muted);
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 5px 10px;
  text-decoration: none;
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
}

.account-signout:hover,
.account-signin:hover { color: var(--primary-text); text-decoration: none; }

.auth-lede {
  color: var(--muted);
  font-size: 0.9rem;
  margin: -6px 0 18px;
}

.auth-form { display: flex; flex-direction: column; gap: 14px; margin-bottom: 18px; }

.field { display: flex; flex-direction: column; gap: 6px; }

.field-label {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

.field input[type=email],
.field input[type=password],
.field input[type=text] {
  width: 100%;
  padding: 13px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 1rem;
  font-family: inherit;
}

.field-hint { font-size: 0.78rem; color: var(--muted); }

/* Empty until something is wrong, so the form does not reserve a red line
   under every field before anyone has typed. */
.field-error { font-size: 0.78rem; color: var(--accent-coral-text); }
.field-error:empty { display: none; }

.field.has-error input { border-color: var(--danger); }

.form-error-banner {
  background: #fbe6e2;
  color: var(--accent-coral-text);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  margin-bottom: 16px;
  font-size: 0.86rem;
}

/* A rule with the word "or" sitting in the gap. */
.auth-or {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 6px 0 14px;
  color: var(--muted);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.auth-or::before,
.auth-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

.btn-google {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
}
.btn-google:hover { background: var(--surface-alt); }
.btn-google svg { flex: none; }

.auth-switch { font-size: 0.86rem; color: var(--muted); margin-top: 4px; }
.auth-switch a { font-weight: 600; }

/* ---------------------------------------------------------------------------
   The day strip: a calendar icon on the lake header, and eight day chips that
   roll down under it.

   The chips carry a colour and a time, nothing else. There is no number on
   them on purpose - the day score is never shown raw, and a percentage would
   claim a calibration this project has not earned yet.
   --------------------------------------------------------------------------- */
.lake-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.lake-heading .section-label { margin-bottom: 0; }

.calendar-toggle {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  color: var(--muted);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}

.calendar-toggle:hover { color: var(--primary-text); background: var(--surface-alt); }
.calendar-toggle.is-open {
  color: #ffffff;
  background: var(--primary-strong);
  border-color: var(--primary-strong);
}

/* Rolls open to a measured height rather than `auto`, which cannot be
   transitioned. The height is set from scrollHeight in the template's script. */
.day-strip {
  overflow: hidden;
  margin: 10px 0 14px;
}

.day-strip.is-rolling {
  max-height: 0;
  transition: max-height 0.26s cubic-bezier(0.32, 0.72, 0, 1);
}

.day-strip.is-rolling.is-open { max-height: var(--roll-height, 200px); }

@media (prefers-reduced-motion: reduce) {
  .day-strip.is-rolling { transition: none; }
}

/* Horizontal rail, not a month grid: eight days is what the predictions cover,
   and a grid of mostly empty squares would be four taps of scrolling to say so. */
.day-strip-rail {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  padding: 2px 2px 6px;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.day-strip-rail::-webkit-scrollbar { display: none; }

.day-chip {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  min-width: 54px;
  padding: 9px 8px 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-family: inherit;
  cursor: pointer;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.day-chip-weekday {
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
}

.day-chip-date { font-size: 1rem; font-weight: 700; color: var(--primary-text); }

/* The band. A bar, not a dot: at this size a dot reads as a bullet point and a
   bar reads as a measurement. */
.day-chip-band {
  width: 28px;
  height: 6px;
  border-radius: 999px;
  margin: 3px 0 0;
}

.band-red    { background: #d9553f; }
.band-orange { background: #e58a3c; }
.band-yellow { background: #e5c645; }
.band-green  { background: #4fae7c; }
/* No prediction row for that day. Hollow, so it cannot be mistaken for a band
   at a glance, and never filled in from the day beside it (law 4). */
.band-none {
  background: repeating-linear-gradient(
    45deg, var(--border), var(--border) 2px, transparent 2px, transparent 4px
  );
}

.day-chip.is-today { border-color: var(--primary); }

.day-chip.is-selected {
  border-color: var(--primary-strong);
  box-shadow: 0 0 0 2px rgba(111, 177, 232, 0.35);
}

/* Far out, and saying so. Faded rather than hidden: pretending the model sees
   only five days would be its own inaccuracy. */
.day-chip.is-far { opacity: 0.55; }
.day-chip.is-empty { opacity: 0.7; }

.day-strip-note {
  font-size: 0.72rem;
  color: var(--muted);
  line-height: 1.35;
  margin: 2px 2px 0;
}

/* Which day the overlay is drawn for, over the map, because the conditions
   card deliberately keeps showing now. */
.map-day-badge {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 500;
  padding: 5px 10px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.92);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--primary-text);
  pointer-events: none;
}

/* The conditions card while a forecast day is selected. Same card, quietly
   marked, so a glance tells you it is not a reading. */
.now-card.is-forecast {
  border: 1px dashed var(--border);
  background: linear-gradient(135deg, var(--surface), var(--surface-alt));
}

/* A quiet notice block. Used by the static build's "this site only reads"
   banner - which has been rendering unstyled since that build was written -
   and by the stage note on a water that is still being prepared. */
.callout {
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  margin-bottom: 14px;
  font-size: 0.86rem;
  line-height: 1.45;
  color: var(--text);
}

/* A search result you can add. It is a submit button styled as a place card,
   so the whole row is the tap target on a phone rather than a small link. */
.candidate-form { margin: 0 0 8px; }

.candidate-card {
  width: 100%;
  border: none;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}

.candidate-card.is-not-water { opacity: 0.7; }

/* What stage a newly added water is at. Quiet by default - it is progress, not
   a problem - and only coloured when something actually failed. */
.build-note {
  font-size: 0.84rem;
  line-height: 1.45;
}

.build-note-failed {
  background: #fbe6e2;
  color: var(--accent-coral-text);
}

/* ---------- collected local knowledge ---------- */
/* Deliberately quieter than the weather card above it. Nothing in here has
   been verified by anybody, and it must not read with the same authority as a
   measurement. Hence muted text, no colour band, and the source link carrying
   the only emphasis in the row - the source is the part worth tapping. */

.intel-note {
  font-size: 0.78rem;
  color: var(--muted);
  margin: -4px 0 10px;
}

.intel-card { padding: 4px 14px 10px; }

.intel-topic + .intel-topic {
  border-top: 1px solid var(--border);
}

.intel-topic-label {
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 12px 0 4px;
}

.intel-list { list-style: none; margin: 0; padding: 0 0 8px; }

.intel-fact {
  font-size: 0.88rem;
  line-height: 1.45;
  padding: 5px 0;
}

.intel-key {
  font-weight: 600;
  color: var(--text);
}

.intel-key::after { content: " — "; font-weight: 400; color: var(--muted); }

.intel-value { color: var(--text); }

.intel-source {
  display: inline-block;
  margin-left: 6px;
  font-size: 0.74rem;
  color: var(--primary-strong);
  text-decoration: none;
  border-bottom: 1px solid var(--primary);
  max-width: 14em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

.intel-source:hover { border-bottom-color: var(--primary-strong); }

/* A source that answered 404. The claim is kept and shown - the page may have
   moved - but it must not look like one that checked out. */
.intel-source-dead {
  color: var(--muted);
  border-bottom-style: dotted;
}

.intel-dead {
  font-size: 0.7rem;
  color: var(--danger-strong);
  margin-left: 4px;
}

/* ---------- 404 / 500 ---------- */
/* Reached by someone who did not mean to be here, quite possibly one-handed
   on a riverbank. Big number, one sentence, one button back. */

.error-page { text-align: center; padding: 32px 0; }

.error-code {
  font-size: 3.2rem;
  font-weight: 700;
  color: var(--primary);
  line-height: 1;
  margin-bottom: 8px;
}

.error-page h1 { margin-bottom: 6px; }
.error-page .auth-lede { margin-bottom: 20px; }
