/* ============================================================================
   home.css — page section layout: brand field, hero, footer.
   (The nav moved to css/nav.css — it is on every page now, not just this one.)
   Recommended-forecast section lands in Step 3 and reuses .brandfield.
   ========================================================================== */

/* ── Brand field ──────────────────────────────────────────────────────────────
   Reusable decorative background: isobar contours over a light teal wash.
   Drop it as the first child of any position:relative section (hero now,
   recommended forecast in Step 3) and give that section's content z-index 1.
   The teal gradient is fine here — it is a stroke, never text.
   ────────────────────────────────────────────────────────────────────────── */
.brandfield {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  background:
    radial-gradient(120% 100% at 12% 0%, rgba(61, 224, 196, 0.16), rgba(61, 224, 196, 0) 58%),
    linear-gradient(180deg, var(--bf-top) 0%, var(--bf-bot) 72%);
}
.brandfield__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* ── Hero ─────────────────────────────────────────────────────────────────── */
.hero {
  position: relative;
  overflow: hidden;
}
.hero__inner {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1.06fr 1fr;
  gap: var(--sp-16);
  align-items: center;
  /* padding-BLOCK, not the `padding` shorthand: this element is also .wrap,
     and a shorthand here resets padding-inline to 0, putting the hero hard
     against the screen edge on a phone. */
  padding-block: var(--sp-20);
}

.hero__media {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--r-xl);
  overflow: hidden;
  background: var(--surface);
  box-shadow: var(--shadow-lg);
}
.hero__media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Neutral stand-in while the clip is absent: holds the 16/9 box so nothing
   reflows when the video lands, and shows no broken-media chrome. */
.hero__placeholder {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: linear-gradient(158deg, #E9F4F1 0%, #F7FAFA 100%);
  border: 1px solid var(--line);
  border-radius: inherit;
}
.hero__placeholder img {
  width: 96px;
  height: auto;
  opacity: 0.16;
}

.hero__title {
  font-size: var(--fs-display);
  font-weight: var(--fw-display);
  line-height: 1.05;
  letter-spacing: var(--tracking-tight);
}
.hero__title em {
  font-style: normal;
  color: var(--accent);
}
.hero__sub {
  max-width: 34ch;
  margin-top: var(--sp-5);
  color: var(--text-secondary);
  font-size: clamp(1.05rem, 1.5vw, 1.25rem);
}
.hero__cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-top: var(--sp-8);
}

@media (max-width: 900px) {
  .hero__inner {
    grid-template-columns: 1fr;
    gap: var(--sp-8);
    padding-block: var(--sp-10) var(--sp-12);
  }
  .hero__sub { max-width: none; }
}
@media (max-width: 640px) {
  .hero__cta { gap: var(--sp-3); }
  .hero__cta .btn { width: 100%; }
}

/* ── Recommended forecast ─────────────────────────────────────────────────── */
.recs {
  position: relative;
  overflow: hidden;
  border-top: 1px solid var(--line);
}
.recs__inner {
  position: relative;
  z-index: 1;
  padding-block: var(--sp-16);
}

.recs__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-6);
  flex-wrap: wrap;
  margin-bottom: var(--sp-8);
}
.recs__title {
  font-size: var(--fs-h2);
  font-weight: var(--fw-display);
  letter-spacing: var(--tracking-tight);
}
.recs__controls {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  margin-left: auto;
}
.recs__arrows { display: flex; gap: var(--sp-2); }

.arrowbtn {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.arrowbtn:hover { border-color: var(--accent); background: var(--accent-tint); color: var(--accent); }
.arrowbtn svg { width: 19px; height: 19px; }

/* Native horizontal scroll + snap: touch and trackpad work without JS, and the
   arrows drive the same scroller rather than a second mechanism. */
.recs__viewport {
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  /* room for card shadow + focus ring, which a plain overflow would clip */
  margin-inline: calc(-1 * var(--sp-2));
  padding: var(--sp-2);
}
.recs__viewport::-webkit-scrollbar { display: none; }

.recs__track {
  display: flex;
  gap: var(--sp-4);
  margin: 0;
  padding: 0;
  list-style: none;
}
.recs__track > li {
  flex: 0 0 clamp(190px, 21vw, 232px);
  scroll-snap-align: start;
}

.card {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: var(--sp-5);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}
.card:hover {
  transform: translateY(-3px);
  border-color: var(--accent);
  box-shadow: var(--shadow-md);
}

/* Rating pill — the APP'S verdict, in the APP'S colours.
 *
 * Every colour here comes from css/rating-palette.css, which is GENERATED from
 * the app's lib/theme/fetch_theme.dart. Do not hand-write a rating colour in
 * this file; `node tools/rating-palette.mjs` fails when the two diverge.
 *
 * The band string arrives from spot_teaser, computed by the app's own Dart
 * (computeRating for surf, marineRating for marine) rather than restated here.
 * Two ladders share this one rule: eight surf bands FLAT..EPIC and five marine
 * bands CALM..HAZARDOUS.
 */
.card__rating {
  align-self: flex-start;
  margin-bottom: var(--sp-3);
  padding: 4px var(--sp-3);
  border-radius: var(--r-pill);
  font-size: var(--fs-sm);
  font-weight: var(--fw-label);
  letter-spacing: 0.06em;
  white-space: nowrap;
  /* Unknown/missing band: neutral, never a guess at a verdict. */
  background: var(--surface-sunken);
  color: var(--text-secondary);
}

/* ── The eight surf bands and five marine bands ──────────────────────────── */
.card__rating[data-band="flat"]      { background: var(--rating-flat-bg);      color: var(--rating-flat-fg); }
.card__rating[data-band="poor"]      { background: var(--rating-poor-bg);      color: var(--rating-poor-fg); }
.card__rating[data-band="poor-fair"] { background: var(--rating-poor-fair-bg); color: var(--rating-poor-fair-fg); }
.card__rating[data-band="fair"]      { background: var(--rating-fair-bg);      color: var(--rating-fair-fg); }
.card__rating[data-band="fair-good"] { background: var(--rating-fair-good-bg); color: var(--rating-fair-good-fg); }
.card__rating[data-band="good"]      { background: var(--rating-good-bg);      color: var(--rating-good-fg); }
.card__rating[data-band="good-epic"] { background: var(--rating-good-epic-bg); color: var(--rating-good-epic-fg); }
.card__rating[data-band="epic"]      { background: var(--rating-epic-bg);      color: var(--rating-epic-fg); }

.card__rating[data-band="calm"]      { background: var(--rating-calm-bg);      color: var(--rating-calm-fg); }
.card__rating[data-band="moderate"]  { background: var(--rating-moderate-bg);  color: var(--rating-moderate-fg); }
.card__rating[data-band="choppy"]    { background: var(--rating-choppy-bg);    color: var(--rating-choppy-fg); }
.card__rating[data-band="rough"]     { background: var(--rating-rough-bg);     color: var(--rating-rough-fg); }
.card__rating[data-band="hazardous"] { background: var(--rating-hazardous-bg); color: var(--rating-hazardous-fg); }

/* ── ⚠ CONTRAST CORRECTIONS — SEVEN BANDS DELIBERATELY DIFFER FROM THE APP ──
 *
 * These are the ONLY place the web palette departs from fetch_theme.dart, and
 * the departure is deliberate. They live here, in the consuming rule, and NOT
 * in css/rating-palette.css — that file must stay a faithful mirror or the
 * divergence check stops meaning anything.
 *
 * WHY: the app renders these at 11px in a chip, a glanceable label beside the
 * number it describes. The web pill is the card's headline and is read on its
 * own, so it holds text at normal size — and WCAG's 4.5:1 applies, since
 * "large text" starts at 24px, or 18.66px at weight 700. Enlarging the pill to
 * 13px does NOT relax the threshold; only colour can.
 *
 * Each foreground below is the MINIMUM darkening of the app's own `onLight`
 * that reaches 4.5:1 on that band's tint. Hue angle is untouched, backgrounds
 * are untouched, and the app's palette is unchanged — see PRE_MERGE.md.
 *
 * Six bands are absent from this block because they already pass: FLAT 4.75,
 * POOR 5.04, GOOD 4.87, EPIC 5.53, CALM 4.87, HAZARDOUS 5.04.
 */
/* ⚠ THE CORRECTION IS A LIGHT-MODE ARTEFACT, AND IT DOES NOT SURVIVE A THEME
 * SWAP. Each value below was derived as the minimum darkening that reaches
 * 4.5:1 ON WHITE — so re-using the same percentage on navy is not a smaller
 * correction, it is a meaningless one, pushing a light foreground toward an
 * ink that is now the ground.
 *
 * Re-derived against the dark ground, all seven come back at 100% — no
 * correction. That is structural, not luck: light's container is an OPAQUE
 * pastel of the hue over white, which crowds a mid-tone foreground, while
 * dark's is the same hue at ~18% over a near-black card, which barely lifts
 * off it. Measured, the worst dark pair is POOR/HAZARDOUS at 5.91:1 against
 * the 4.04-4.40 that forced these seven in light.
 *
 * So the light values are RECORDED here verbatim (not re-derived — they were
 * hand-solved with mixed inks and no single mix reproduces all seven), and
 * dark simply reads the app's own foreground. tools/a11y.mjs asserts both
 * sides rather than trusting this comment. */
:root {
  --rating-poor-fair-fg-aa: #A5591C;   /* app #A85A1C  4.40 -> 4.51  (1% darker) */
  --rating-fair-fg-aa:      #8E640F;   /* app #92670F  4.34 -> 4.54  (2% darker) */
  --rating-fair-good-fg-aa: #5D771D;   /* app #5F7A1E  4.36 -> 4.54  (2% darker) */
  --rating-good-epic-fg-aa: #197693;   /* app #1B7F9E  4.04 -> 4.56  (7% darker) */
  --rating-moderate-fg-aa:  #4D7853;   /* app #4F7A55  4.38 -> 4.51  (2% darker) */
  --rating-choppy-fg-aa:    #8E640F;   /* app #92670F  4.34 -> 4.54  (2% darker) */
  --rating-rough-fg-aa:     #A5591C;   /* app #A85A1C  4.40 -> 4.51  (1% darker) */
}
:root[data-theme='dark'] {
  --rating-poor-fair-fg-aa: var(--rating-poor-fair-fg);   /* 6.34 uncorrected */
  --rating-fair-fg-aa:      var(--rating-fair-fg);        /* 7.06 */
  --rating-fair-good-fg-aa: var(--rating-fair-good-fg);   /* 7.44 */
  --rating-good-epic-fg-aa: var(--rating-good-epic-fg);   /* 6.82 */
  --rating-moderate-fg-aa:  var(--rating-moderate-fg);    /* 6.99 */
  --rating-choppy-fg-aa:    var(--rating-choppy-fg);      /* 7.06 */
  --rating-rough-fg-aa:     var(--rating-rough-fg);       /* 6.34 */
}
.card__rating[data-band="poor-fair"] { color: var(--rating-poor-fair-fg-aa); }
.card__rating[data-band="fair"]      { color: var(--rating-fair-fg-aa); }
.card__rating[data-band="fair-good"] { color: var(--rating-fair-good-fg-aa); }
.card__rating[data-band="good-epic"] { color: var(--rating-good-epic-fg-aa); }
.card__rating[data-band="moderate"]  { color: var(--rating-moderate-fg-aa); }
.card__rating[data-band="choppy"]    { color: var(--rating-choppy-fg-aa); }
.card__rating[data-band="rough"]     { color: var(--rating-rough-fg-aa); }

.card__name {
  font-weight: var(--fw-label);
  color: var(--text);
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.card__sub {
  margin-top: 2px;
  font-size: var(--fs-xs);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.card__stats {
  display: flex;
  gap: var(--sp-5);
  margin-top: auto;
  padding-top: var(--sp-4);
}
.stat { display: flex; flex-direction: column; }
.stat b {
  font-size: 1.3rem;
  font-weight: var(--fw-display);
  letter-spacing: var(--tracking-tight);
  color: var(--text);
  line-height: 1.1;
}
.stat b i { font-style: normal; font-size: 0.72rem; font-weight: var(--fw-medium); color: var(--text-muted); }
.stat em {
  font-style: normal;
  margin-top: 2px;
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

/* Weather card — home-widget shape */
.card--weather { align-items: flex-start; }
.card--weather .card__icon { color: var(--accent); }
.card--weather .card__icon svg { width: 34px; height: 34px; }
.card__temp {
  margin-top: var(--sp-3);
  font-size: 2rem;
  font-weight: var(--fw-display);
  letter-spacing: var(--tracking-tight);
  line-height: 1;
  color: var(--text);
}
.card__sky { margin-top: var(--sp-2); font-size: var(--fs-sm); color: var(--text-secondary); }
.card__feels { font-size: var(--fs-xs); color: var(--text-muted); }
.card--weather .card__name { margin-top: var(--sp-4); }

.recs__empty { padding: var(--sp-8) 0; color: var(--text-muted); }

@media (max-width: 720px) {
  .recs__inner { padding-block: var(--sp-12); }
  .recs__head { gap: var(--sp-4); margin-bottom: var(--sp-6); }
  .recs__title { font-size: 1.5rem; }
  .recs__controls { width: 100%; margin-left: 0; justify-content: space-between; }
  .segmented__btn { padding: 8px var(--sp-4); font-size: var(--fs-xs); }
  .recs__track > li { flex: 0 0 min(74vw, 232px); }
}

/* ⚠ THE FOOTER'S RULES LIVE IN css/footer.css, NOT HERE. They were in this
   file, which ONLY index.html loads — so when the footer became an injected
   module and started appearing on the forecast and account pages, it arrived
   with no rules behind it: three columns collapsed to one stack, the column
   headings rendered at heading size instead of small caps, and the links lost
   their treatment. Moved rather than copied, and loaded by every document that
   mounts it. */

}

/* ── Map modes that are not live yet ──────────────────────────────────────────
 * aria-disabled rather than the disabled attribute (see js/main.js): the tile
 * stays in the accessibility tree so a screen reader announces "Coming soon"
 * instead of silence. Dimmed, not hidden — the roadmap is worth showing.
 */
.mode-tile[aria-disabled="true"] {
  opacity: 0.55;
  cursor: not-allowed;
}
.mode-tile[aria-disabled="true"]:hover {
  transform: none;
  border-color: var(--line);
  box-shadow: none;
}
.mode-tile__soon {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-xs);
  font-weight: var(--fw-label);
  color: var(--text-muted);
  letter-spacing: 0.04em;
}

/* Shown only when a blocked map selection opened the Fetch+ overlay. */
.plus__quota {
  margin: 0 0 var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background: var(--accent-tint);
  color: var(--accent-text);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  text-align: center;
}
.plus__quota[hidden] { display: none; }

/* A saved spot with no teaser row. Same frame, same size, no verdict — the
   card must not reflow the rail, and must not imply a rating. */
.card--nodata { justify-content: flex-start; }
.card__nodata {
  margin-top: auto;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text-muted);
}

/* ── Region rail ──────────────────────────────────────────────────────────
   Sits directly under the mode row and filters the carousel to one region of
   the visitor's own favorites. Scrolls horizontally rather than wrapping: a
   user with saves in fifteen regions should not push the carousel down the
   page. */
.recs__regions {
  display: flex; gap: var(--sp-2);
  width: 100%;
  margin: 0 0 var(--sp-6);
  overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: none;
}
/* ⚠ THE HEAD'S OWN BOTTOM MARGIN IS var(--sp-8), sized for a header sitting
   straight on top of the carousel. With a rail between them that gap lands in
   the wrong place — a wide band above the filters and a tight one below. When
   a VISIBLE rail follows, the head gives its space up and the rail carries the
   separation instead. :has() is doing real work here; where it is unsupported
   the rail simply sits lower, which is loose rather than broken. */
.recs__head:has(+ .recs__regions:not([hidden])) { margin-bottom: var(--sp-4); }
.recs__regions::-webkit-scrollbar { display: none; }
.recs__region {
  display: inline-flex; align-items: center; gap: 6px;
  flex: 0 0 auto;
  padding: 6px var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--bg);
  color: var(--text-secondary);
  font-size: var(--fs-sm); font-weight: var(--fw-label);
  cursor: pointer; white-space: nowrap;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.recs__region:hover { background: var(--surface-sunken); color: var(--text); }
.recs__region[aria-selected='true'] {
  background: var(--accent-tint); border-color: var(--accent); color: var(--accent-text);
}
.recs__region-n {
  font-variant-numeric: tabular-nums;
  font-size: 0.78em; opacity: 0.75;
}

/* A failed favorites query, said out loud in the rail's own row. Styled as a
   note rather than a chip: it is not a filter you can press. */
.recs__railerr {
  margin: 0; padding: 6px 0;
  font-size: var(--fs-sm); color: var(--danger-text);
}

/* The pre-rebuild-saves note: one muted sentence, deliberately NOT a banner.
   It reports a permanent fact about old data, not a failure to retry. */
.recs__legacy {
  margin: calc(var(--sp-2) * -1) 0 var(--sp-4);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/* ⚠ A PAGE ENTRY, NOT A COMING-SOON ONE. Radar is live and clickable; the
   sub-label says it LEAVES the map rather than switching a layer. Deliberately
   the same type treatment as .mode-tile__soon — both answer "why does this
   tile not behave like the others" — but the accent colour distinguishes
   "goes somewhere" from "goes nowhere yet". */
.mode-tile__sub {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-xs);
  font-weight: var(--fw-label);
  /* ⚠ --accent-text, NOT --accent. css/tokens.css says it in as many words:
     "--accent-text is what any small text uses", because the FILL colour
     measures under AA at this size. I used the fill and axe caught it —
     color-contrast on .mode-tile__sub — which is the token comment describing
     the mistake before it was made. */
  color: var(--accent-text);
  letter-spacing: 0.04em;
}


/* ── Renewal banner (cancelled-but-active subscribers) ────────────────────
   ⚠ z-index 50 — deliberately BELOW the nav's 60. The About-page bug was a
   page-level rule overriding the nav's own z-index, which buried the
   Forecast/Maps dropdowns; this block styles only .renewbar and never
   touches a nav selector, so the dropdowns render above the banner. It sits
   under the sticky nav (top: var(--nav-h)) and slides down from behind it. */
.renewbar {
  position: fixed; top: var(--nav-h); left: 0; right: 0; z-index: 50;
  display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: 4px var(--sp-2);
  padding: 10px var(--sp-4);
  background: color-mix(in srgb, var(--surface, #fff) 74%, transparent);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
  color: var(--text); font-size: var(--fs-sm); text-align: center;
  transform: translateY(-110%); transition: transform .4s ease;
}
.renewbar.is-in { transform: translateY(0); }
@media (prefers-reduced-motion: reduce) { .renewbar { transition: none; } }
.renewbar__link {
  color: var(--accent-text, var(--accent)); font-weight: var(--fw-medium);
  text-decoration: underline; text-underline-offset: 2px;
}
.renewbar__x {
  border: 0; background: none; cursor: pointer; padding: 0 6px;
  font-size: 1.15rem; line-height: 1; color: var(--text-muted);
}
.renewbar__x:hover, .renewbar__x:focus-visible { color: var(--text); }
