/* My Beach Day — styles
 * Design source: Figma 3qwUeCWoCYbKN0gxZwoXHo frame 2:16 (v1, ds-0).
 * Colours / type scale / radii / shadows below are taken literally from
 * that frame. Zero dependencies, zero build (adr-1.md).
 *
 * Deviation CLOSED (was: single-column condition grid). The frame lays the
 * four condition cards out as a 2x2 grid (sea beside wave, wind beneath).
 * The single-column override cited ux-spec.md #2 "Fold Priority & Vertical
 * Layout" — but that clause belongs to the SUPERSEDED ux-spec
 * (archive/20260912-165213/ux-spec.md:10). The current Release 5.1
 * ux-spec.md has no fold-order requirement, so the only remaining authority
 * on layout is the approved frame, and archive/20260912-165213/
 * ux-validation.md already routed this drift to the Developer as the
 * "Critical Drift: Grid Layout". The grid is now 2x2 at every width, as
 * approved. Palette, type scale, radii, shadows, card anatomy, badges,
 * hero and footer remain taken literally from the frame.
 */

:root {
  --canvas: #eaf6ff;
  --surface: #ffffff;
  --ink: #143049;
  --ink-alt: #14304a;
  --muted: #5c788c;
  --air: #ff7a45;
  --air-soft: #ffefd9;
  --sea: #007696;
  --wave: #705cc7;
  --wave-soft: #ece9ff;
  --wind: #2670ad;
  --wind-soft: #dbedfc;
  --warn: #b3261e;
  --warn-soft: #ffe4e0;

  --card-shadow: 0 4px 12px rgba(13, 51, 89, 0.08);
  --hero-shadow: 0 8px 24px rgba(13, 51, 89, 0.12);
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font-family: Inter, -apple-system, "Segoe UI", Roboto, system-ui, sans-serif;
  font-size: 16px;
  line-height: normal;
}

p,
h1,
h2,
h3 {
  margin: 0;
}

/* ---------- page shell (Figma 2:16) ---------- */

.app {
  display: flex;
  flex-direction: column;
  gap: 22px;
  align-items: stretch;
  padding: 28px 20px 36px;
  max-width: 520px;
  margin: 0 auto;
}

/* ---------- TopBar (2:17) ---------- */

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-width: 0;
}

.location-stack {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.location {
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
}

.app-name {
  font-size: 12px;
  font-weight: 400;
  color: var(--muted);
}

.today {
  font-size: 13px;
  font-weight: 500;
  color: var(--muted);
  white-space: nowrap;
}

/* No auth-bar styles: adr-1.md § "Update — Re-verification pass" keeps this
 * release anonymous and read-only, so there is no sign-in surface to style. */

/* ---------- notices ---------- */

.stale-warning,
.error-banner {
  display: none;
  border-radius: 14px;
  padding: 10px 14px;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.35;
}

.stale-warning {
  background: #fff4d6;
  color: #8a5a00;
  border: 1px solid #f0c65a;
}

.error-banner {
  background: var(--warn-soft);
  color: var(--warn);
  border: 1px solid #f3b4ac;
}

/* Shown by app.js: the `hidden` attribute is removed AND `.stale` /
 * `.visible` is applied, so the element is genuinely painted for the user. */
.stale-warning.stale,
.error-banner.visible {
  display: block;
  opacity: 1;
}

[hidden] {
  display: none !important;
}

/* ---------- HeroCard - air temperature (2:22) ---------- */

.hero-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: var(--surface);
  border-radius: 28px;
  box-shadow: var(--hero-shadow);
  padding: 26px 24px;
  min-width: 0;
}

.hero-icon {
  font-size: 44px;
  color: var(--ink-alt);
  line-height: 1.1;
}

.hero-value {
  font-size: 64px;
  font-weight: 800;
  color: var(--air);
  letter-spacing: -1px;
  line-height: 1.05;
  min-height: 67px; /* reserve the slot so first paint does not jump */
  /* The F-08 fallback text ("Unavailable") is ~390px wide at 64px/800 and
   * would push the document past a 375px viewport, breaking acceptance.md
   * #2 (no horizontal scroll) in exactly the outage state F-08 defines.
   * Matches the guard already on .card-value. */
  max-width: 100%;
  overflow-wrap: anywhere;
  text-align: center;
}

.pill {
  border-radius: 999px;
  padding: 6px 14px;
  font-size: 13px;
  font-weight: 600;
  text-align: center;
}

.pill--warm {
  background: var(--air-soft);
  color: var(--air);
}

/* ---------- section title (2:28) ---------- */

.section-title {
  font-size: 18px;
  font-weight: 700;
  color: var(--ink);
}

/* ---------- conditions grid (2:53) ---------- */

/* Frame 2:16 is a 393px-wide artboard and is already 2-up at that width, so
 * the 2x2 holds unconditionally — no breakpoint. minmax(0, 1fr) (not 1fr)
 * keeps a long fallback value like "Unavailable" from forcing the track
 * wider than its share and overflowing a 375px viewport. */
/* align-items: start (not the CSS default `stretch`) because in the frame the
 * cards size to their own content — they are NOT equal-height within a row.
 * Measured on frame 2:16: row 1 Card-SeaTemp (2:30) h=120 beside
 * Card-WaveHeight (2:34) h=147; row 2 Card-WindSpeed (2:40) h=147 beside
 * Card-WindDirection (2:46) h=139. The cards with a badge/note are taller.
 * Stretching made the sea card 27px taller than approved, with dead white
 * space under its value. */
.grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  gap: 14px;
  min-width: 0;
}

.card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  background: var(--surface);
  border-radius: 20px;
  box-shadow: var(--card-shadow);
  padding: 18px 16px;
  min-width: 0;
}

.card-icon {
  font-size: 26px;
  color: var(--ink-alt);
  line-height: 1.1;
}

.card-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  letter-spacing: 0.02em;
}

.card-value {
  font-size: 26px;
  font-weight: 800;
  line-height: 1.15;
  min-height: 30px;
  overflow-wrap: anywhere;
}

.card--sea .card-value {
  color: var(--sea);
}

.card--wave .card-value {
  color: var(--wave);
}

.card--wind .card-value {
  color: var(--wind);
}

.card-value--dark {
  color: var(--ink);
}

.card-note {
  font-size: 11px;
  font-weight: 400;
  color: var(--muted);
}

.badge {
  border-radius: 999px;
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 600;
  max-width: 100%;
}

.badge--wave {
  background: var(--wave-soft);
  color: var(--wave);
}

.badge--wind {
  background: var(--wind-soft);
  color: var(--wind);
}

/* ---------- manual refresh (qa-findings F-09) ---------- */

.refresh-btn {
  align-self: center;
  border: 1px solid rgba(38, 112, 173, 0.28);
  background: var(--wind-soft);
  color: var(--wind);
  border-radius: 999px;
  padding: 10px 20px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.refresh-btn:hover {
  background: #cfe6fb;
}

.refresh-btn:focus-visible {
  outline: 2px solid var(--wind);
  outline-offset: 2px;
}

.refresh-btn[aria-busy="true"] {
  opacity: 0.6;
  cursor: progress;
}

/* ---------- Footer (2:54) ---------- */

.footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding-top: 4px;
  text-align: center;
  font-size: 11px;
  color: var(--muted);
}

.footer-source {
  font-weight: 500;
}

.footer-updated {
  font-weight: 400;
}

/* ---------- >= 600px ----------
 * The 2x2 is already the approved layout at every width, so wider screens
 * get breathing room, not a re-flow. */

@media (min-width: 600px) {
  .app {
    padding: 36px 20px 44px;
    gap: 24px;
  }
}

@media (prefers-reduced-motion: no-preference) {
  .card-value,
  .hero-value {
    transition: color 120ms ease-in-out;
  }
}

/* ======================================================================
 * Admin console (/admin) — release 3.1
 *
 * Appended per adr-1.md § File layout: "admin view reuses it; admin-only
 * classes may be appended, no second stylesheet". Everything above this
 * banner is untouched, so the public dashboard is unaffected (initiative.md
 * "do not regress existing behavior").
 *
 * Design source is the same Figma frame (3qwUeCWoCYbKN0gxZwoXHo 2:16 v1):
 * the canvas, surface, ink/muted type colours, 20px card radius, card
 * shadow, 12px uppercase-weight labels and 999px pills below are the
 * frame's own tokens, reused via the :root variables declared at the top of
 * this file. The one intentional difference is the accent: the admin
 * surface leads with the frame's blue (--wind) and a lock mark instead of
 * the public page's orange hero, because ux-spec.md #4 requires the admin
 * area to be "visually separated from public application".
 * ====================================================================== */

.admin {
  max-width: 560px;
  gap: 18px;
}

.admin-topbar {
  padding-bottom: 14px;
  border-bottom: 1px solid rgba(38, 112, 173, 0.18);
}

.admin-badge {
  background: var(--wind-soft);
  color: var(--wind);
  border-radius: 999px;
  padding: 4px 12px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.admin-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  background: var(--surface);
  border-radius: 20px;
  box-shadow: var(--card-shadow);
  padding: 22px 20px;
  min-width: 0;
}

.admin-icon {
  font-size: 34px;
  line-height: 1.1;
  color: var(--ink-alt);
}

.admin-card-title {
  font-size: 18px;
  font-weight: 700;
  color: var(--ink);
}

.admin-card-body {
  font-size: 14px;
  font-weight: 400;
  line-height: 1.45;
  color: var(--muted);
}

.admin-eyebrow {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--muted);
}

.admin-note,
.admin-resume {
  font-size: 12px;
  font-weight: 400;
  line-height: 1.45;
  color: var(--muted);
}

.admin-link {
  color: var(--wind);
  font-weight: 600;
}

/* ---------- sign-in view ---------- */

.signin-card {
  align-items: center;
  text-align: center;
  gap: 12px;
  padding: 30px 24px;
}

.signin-btn {
  display: inline-block;
  background: var(--wind);
  color: var(--surface);
  border-radius: 999px;
  padding: 13px 26px;
  font-size: 15px;
  font-weight: 700;
  text-decoration: none;
  box-shadow: var(--card-shadow);
}

.signin-btn:hover {
  background: #1d5b90;
}

.signin-btn:focus-visible,
.signout-btn:focus-visible,
.admin-btn:focus-visible {
  outline: 2px solid var(--wind);
  outline-offset: 3px;
}

/* ---------- dashboard ---------- */

.admin-dashboard {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

.identity-card {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}

.identity-text {
  min-width: 0;
}

.identity-name {
  font-size: 18px;
  font-weight: 700;
  color: var(--ink);
  overflow-wrap: anywhere;
}

.signout-btn {
  flex: none;
  background: var(--wind-soft);
  color: var(--wind);
  border: 1px solid rgba(38, 112, 173, 0.28);
  border-radius: 999px;
  padding: 9px 18px;
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}

.signout-btn:hover {
  background: #cfe6fb;
}

.admin-facts {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0;
}

.admin-facts > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
}

.admin-facts dt {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--muted);
}

.admin-facts dd {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  text-align: right;
  overflow-wrap: anywhere;
}

.admin-sources {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.source-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.source-name {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
}

.source-status {
  border-radius: 999px;
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 600;
  background: #eef3f7;
  color: var(--muted);
  white-space: nowrap;
}

.source-status--ok {
  background: var(--wind-soft);
  color: var(--wind);
}

.source-status--bad {
  background: var(--warn-soft);
  color: var(--warn);
}

.admin-btn {
  align-self: flex-start;
  border: 1px solid rgba(38, 112, 173, 0.28);
  background: var(--wind-soft);
  color: var(--wind);
  border-radius: 999px;
  padding: 9px 18px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.admin-btn:hover {
  background: #cfe6fb;
}

.admin-btn[disabled] {
  opacity: 0.6;
  cursor: progress;
}

/* ---------- 403 ---------- */

.denied-card {
  align-items: center;
  text-align: center;
  gap: 12px;
  padding: 30px 24px;
}

.denied-title {
  color: var(--warn);
}

.admin-footer .footer-updated {
  max-width: 40ch;
  line-height: 1.4;
}

@media (max-width: 420px) {
  .identity-card {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* ======================================================================
 * My Beaches (Release 5.1 · F4) — saved list, Save modal, Remove dialog
 *
 * Design source is the same approved frame (Figma 3qwUeCWoCYbKN0gxZwoXHo
 * 2:16, v1, ds-0). That frame is the CONDITIONS screen and contains no
 * saved-beach surface — v1 predates F4 — so there is no layout in it to
 * copy. What IS in it, and what is reused literally here, is the design
 * system: the #eaf6ff canvas, white surfaces, the 20px card / 28px hero
 * radii, the 999px pill, the card shadow, the 18px/700 section title, the
 * 12px muted labels and the --wind accent. The saved-beach cards are the
 * frame's own condition card at the frame's own radius and shadow; the
 * Save button is the frame's pill in the frame's accent. Nothing new is
 * invented, and no token is altered.
 *
 * Everything above this banner is untouched.
 * ====================================================================== */

.my-beaches {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

.save-beach-btn {
  align-self: stretch;
  border: none;
  border-radius: 999px;
  background: var(--wind);
  color: var(--surface);
  padding: 13px 22px;
  font: inherit;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
  box-shadow: var(--card-shadow);
  transition: background-color 0.2s ease;
}

.save-beach-btn:hover {
  background: #1d5b90;
}

.save-beach-btn:active {
  background: #184877;
}

.save-beach-btn:focus-visible {
  outline: 2px solid var(--wind);
  outline-offset: 3px;
}

/* Empty / error line. Lives OUTSIDE .my-beaches-list so the list element
 * itself is genuinely empty when the visitor has saved nothing — the
 * browser test asserts the absence of beach cards, and a placeholder <li>
 * would have made that assertion weaker than it looks. */
.my-beaches-status {
  font-size: 13px;
  line-height: 1.45;
  color: var(--muted);
}

.my-beaches-status--error {
  background: var(--warn-soft);
  color: var(--warn);
  border: 1px solid #f3b4ac;
  border-radius: 14px;
  padding: 10px 14px;
  font-weight: 600;
}

.my-beaches-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}

.beach-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: var(--surface);
  border-radius: 20px;
  box-shadow: var(--card-shadow);
  padding: 16px;
  min-width: 0;
  transition: box-shadow 0.2s ease;
}

.beach-card:hover {
  box-shadow: 0 6px 18px rgba(13, 51, 89, 0.12);
}

.beach-card--selected {
  box-shadow: 0 0 0 2px var(--wind), var(--card-shadow);
}

.beach-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-width: 0;
}

/* The whole name is the select target, so it is a real button rather than a
 * click handler on a <li>: reachable by keyboard and announced as an action.
 * Stripped back to look like the text it replaces. */
.beach-card-select {
  flex: 1;
  min-width: 0;
  border: none;
  background: none;
  padding: 0;
  margin: 0;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.beach-card-select:focus-visible {
  outline: 2px solid var(--wind);
  outline-offset: 3px;
  border-radius: 6px;
}

.beach-card-name {
  display: block;
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
  overflow-wrap: anywhere;
}

.beach-card-remove-btn {
  flex: none;
  border: none;
  background: none;
  padding: 4px 8px;
  font-size: 18px;
  line-height: 1;
  color: var(--warn);
  cursor: pointer;
  transition: opacity 0.2s ease;
}

.beach-card-remove-btn:hover {
  opacity: 0.7;
}

.beach-card-remove-btn:focus-visible {
  outline: 2px solid var(--warn);
  outline-offset: 2px;
  border-radius: 8px;
}

.beach-card-conditions {
  font-size: 13px;
  line-height: 1.45;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.beach-card-conditions--unavailable {
  font-style: italic;
  opacity: 0.75;
}

/* ---------- Save modal + Remove dialog ----------
 * Both are <dialog> elements toggled by the `open` attribute (not
 * showModal()): a top-layer modal dialog makes the rest of the page inert,
 * and this section sits inside the normal page flow the dashboard keeps
 * using underneath. The UA stylesheet's centring/margin/max-size rules are
 * reset explicitly so the overlay really covers the viewport. */

.save-modal,
.remove-confirm-dialog {
  position: fixed;
  inset: 0;
  margin: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  border: none;
  padding: 0;
  background: rgba(13, 51, 89, 0.45);
  color: inherit;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

.save-modal[open],
.remove-confirm-dialog[open] {
  display: flex;
}

.save-modal:not([open]),
.remove-confirm-dialog:not([open]) {
  display: none;
}

.modal-content,
.dialog-content {
  display: flex;
  flex-direction: column;
  gap: 14px;
  background: var(--surface);
  border-radius: 28px;
  box-shadow: 0 12px 40px rgba(13, 51, 89, 0.2);
  padding: 26px 22px;
  width: 90%;
  max-width: 320px;
  max-height: 90vh;
  overflow-y: auto;
}

.modal-title,
.dialog-title {
  font-size: 18px;
  font-weight: 700;
  color: var(--ink);
}

.modal-location {
  font-size: 13px;
  font-weight: 500;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.modal-label {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--muted);
}

.modal-input {
  border: 1px solid rgba(38, 112, 173, 0.28);
  border-radius: 12px;
  background: #f7fbff;
  padding: 12px 14px;
  font: inherit;
  font-size: 16px;      /* >= 16px: anything smaller zooms iOS Safari on focus */
  color: var(--ink);
  min-width: 0;
  transition: border-color 0.2s ease, background-color 0.2s ease;
}

.modal-input:focus {
  outline: none;
  border-color: var(--wind);
  background: var(--surface);
  box-shadow: 0 0 0 3px var(--wind-soft);
}

.modal-error {
  font-size: 13px;
  font-weight: 600;
  line-height: 1.4;
  color: var(--warn);
}

.modal-buttons,
.dialog-buttons {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
}

.modal-btn,
.dialog-btn {
  border: none;
  border-radius: 999px;
  padding: 11px 20px;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.modal-btn:focus-visible,
.dialog-btn:focus-visible {
  outline: 2px solid var(--wind);
  outline-offset: 3px;
}

.modal-btn--cancel,
.dialog-btn--cancel {
  background: #e7eef4;
  color: var(--ink);
}

.modal-btn--cancel:hover,
.dialog-btn--cancel:hover {
  background: #d8e3ec;
}

.modal-btn--save {
  flex: 1;
  background: var(--wind);
  color: var(--surface);
}

.modal-btn--save:hover {
  background: #1d5b90;
}

.modal-btn--save[disabled] {
  opacity: 0.6;
  cursor: progress;
}

.dialog-btn--remove {
  flex: 1;
  background: var(--warn);
  color: var(--surface);
}

.dialog-btn--remove:hover {
  background: #9d1f18;
}

.dialog-text {
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink);
  overflow-wrap: anywhere;
}
