/* Hamnkapten customer web app: SJÖKORT component recipes, mobile-first.
   Tokens come from ui-tokens/generated/theme.css; nothing in here hardcodes a
   colour. The class vocabulary is shared with the web admin so the two surfaces
   read as one system; customer-only pieces (hatched water, booking cards,
   feedback form) are appended. Constitution reminders: mono = every numeral;
   signal magenta = non-text, budgeted; motion is event-driven and plays once. */

/* 1. Fonts: the three SJÖKORT voices. Plex Sans = UI, Plex Mono = soundings
   (every time/code numeral), Besley = display at the enumerated places only.
   All SIL OFL, self-hosted. */
@font-face { font-family: 'IBM Plex Sans'; font-style: normal; font-weight: 400; font-display: swap; src: url('/fonts/ibm-plex-sans-latin-400-normal.woff2') format('woff2'); }
@font-face { font-family: 'IBM Plex Sans'; font-style: normal; font-weight: 500; font-display: swap; src: url('/fonts/ibm-plex-sans-latin-500-normal.woff2') format('woff2'); }
@font-face { font-family: 'IBM Plex Sans'; font-style: normal; font-weight: 600; font-display: swap; src: url('/fonts/ibm-plex-sans-latin-600-normal.woff2') format('woff2'); }
@font-face { font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url('/fonts/ibm-plex-mono-latin-400-normal.woff2') format('woff2'); }
@font-face { font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 500; font-display: swap; src: url('/fonts/ibm-plex-mono-latin-500-normal.woff2') format('woff2'); }
@font-face { font-family: 'Besley'; font-style: normal; font-weight: 500; font-display: swap; src: url('/fonts/besley-latin-500-normal.woff2') format('woff2'); }
@font-face { font-family: 'Besley'; font-style: normal; font-weight: 600; font-display: swap; src: url('/fonts/besley-latin-600-normal.woff2') format('woff2'); }

/* 2. Base */
*, *::before, *::after { box-sizing: border-box; }
body {
  margin: 0;
  background: var(--bg-base);
  color: var(--fg-primary);
  font-family: var(--font-ui);
  font-size: var(--text-base);
  line-height: var(--leading-body);
  -webkit-font-smoothing: antialiased;
}
h1, h2 { font-weight: var(--weight-display); line-height: var(--leading-heading); margin: 0 0 var(--space-2); }
h1 { font-size: var(--text-2xl); font-family: var(--font-display); }
h2 { font-size: var(--text-lg); margin-top: var(--space-3); }
p { margin: 0 0 var(--space-2); }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.mono, .booking-time, .booking-code-value, .avail-slot-time { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.cal-colhead { text-transform: uppercase; letter-spacing: 0.06em; font-size: var(--text-xs); }

/* Reduced-motion contract: everything collapses to a short fade. Reduce when
   EITHER the OS prefers reduced motion OR the in-app toggle sets
   `[data-reduce-motion]` on the root. The two triggers cannot share one selector
   (one is an @media query, one a plain attribute selector), so the block is
   repeated once. The timing is factored into the `--motion-reduced` token. */
@media (prefers-reduced-motion: reduce) {
  * {
    transition-property: opacity !important;
    transition-duration: var(--motion-reduced) !important;
    animation: none !important;
  }
}
[data-reduce-motion] * {
  transition-property: opacity !important;
  transition-duration: var(--motion-reduced) !important;
  animation: none !important;
}

/* 3. Frame */
.app-frame { display: flex; flex-direction: column; min-height: 100vh; }
.app-frame .app-shell { flex: 1; min-height: 0; }
.app-shell { display: flex; align-items: stretch; }
.app-nav {
  --nav-item-h: 40px;
  position: relative;
  display: flex; flex-direction: column; gap: var(--space-0-5);
  width: 208px; flex-shrink: 0;
  padding: var(--space-3) var(--space-2);
  background: var(--bg-elevated);
  border-right: 1px solid var(--border-default);
}
.app-nav a {
  display: flex; align-items: center; gap: var(--space-1);
  height: var(--nav-item-h); box-sizing: border-box;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  color: var(--fg-secondary); font-weight: var(--weight-ui);
  transition: background var(--motion-fast) var(--ease-standard), color var(--motion-fast) var(--ease-standard);
  animation: rise-in var(--motion-base) var(--ease-standard) backwards;
}
.app-nav a:hover { background: var(--bg-sunken); color: var(--fg-primary); text-decoration: none; }
.app-nav a[aria-current="page"] { color: var(--accent); background: var(--bg-sunken); }
.app-nav a:nth-of-type(1) { animation-delay: 30ms; }
.app-nav a:nth-of-type(2) { animation-delay: 60ms; }
.app-nav a:nth-of-type(3) { animation-delay: 90ms; }
/* Travelling route-line marker: one 3px signal bar that slides to the active
   item, set inline from the active route. Reduced motion drops the transition. */
.nav-marker {
  position: absolute; left: var(--space-2); top: var(--space-3);
  width: 3px; height: var(--nav-item-h);
  border-radius: 1.5px; background: var(--signal);
  transition: transform var(--motion-base) ease-in-out;
  pointer-events: none;
}
.app-main {
  flex: 1; min-width: 0; max-width: 900px; padding: var(--space-4);
  animation: rise-in var(--motion-base) var(--ease-standard) backwards;
}
.boot-status, .boot-error { padding: var(--space-4); color: var(--fg-secondary); }
.boot-error { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2); }
@keyframes rise-in { from { opacity: 0; transform: translateY(12px); } }

/* 4. Top bar */
.topbar {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border-default);
}
.topbar-brand { display: flex; align-items: center; gap: var(--space-1); color: var(--accent); }
.topbar-brand:hover { text-decoration: none; }
.topbar-mark svg { width: 22px; height: 22px; display: block; }
.topbar-wordmark { font-family: var(--font-display); font-weight: var(--weight-display); font-size: var(--text-lg); color: var(--fg-primary); }
.topbar-spacer { flex: 1; }
.topbar select { width: auto; margin-top: 0; }

/* 5. Controls: 4 px radius, borders over shadows, fast hover micro. */
button {
  font: inherit; font-weight: var(--weight-ui); cursor: pointer;
  padding: var(--space-1) var(--space-3);
  border: 1px solid transparent; border-radius: var(--radius-sm);
  background: var(--accent); color: var(--fg-on-accent);
  transition: background var(--motion-fast) var(--ease-standard), border-color var(--motion-fast) var(--ease-standard);
}
button:hover { background: var(--accent-hover); }
button:active { transform: scale(0.99); }
button:disabled { opacity: .6; cursor: not-allowed; }
.btn-secondary { background: transparent; color: var(--accent); border-color: var(--border-strong); }
.btn-secondary:hover { background: var(--bg-sunken); }
.btn-danger { background: var(--danger); color: var(--fg-on-accent); }

label { display: block; font-weight: var(--weight-ui); font-size: var(--text-sm); color: var(--fg-secondary); margin-bottom: var(--space-2); }
input, select, textarea {
  font: inherit; width: 100%; margin-top: var(--space-0-5);
  padding: var(--space-1) var(--space-2);
  background: var(--bg-elevated); color: var(--fg-primary);
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
}
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--focus-ring); outline-offset: 2px; border-color: var(--focus-ring);
}
.form-error { color: var(--danger); font-size: var(--text-sm); }

/* 5b. Six-box OTP input that mirrors the admin SegmentedCode look. */
.totp-field { display: flex; flex-direction: column; gap: var(--space-0-5); }
.totp-label { display: inline-flex; align-items: center; }
.totp-boxes { display: flex; gap: var(--space-1); margin-top: var(--space-0-5); }
.totp-box {
  width: 40px; height: 48px; flex: 0 0 auto; margin-top: 0;
  padding: 0; text-align: center;
  font-size: var(--text-xl); font-variant-numeric: tabular-nums;
  background: var(--bg-base); color: var(--fg-primary);
  border: 1px solid var(--border-default); border-radius: var(--radius-md);
}
.totp-box:focus-visible {
  outline: none; border-width: 2px; border-color: var(--focus-ring);
  margin: -1px; padding: 0;
}

/* 6. Neatline register-ticks: four L-shaped corner strokes on cards/modals. */
.neatline { position: relative; }
.neatline::before {
  content: ""; position: absolute; inset: 6px; pointer-events: none;
  --tick: color-mix(in srgb, var(--fg-secondary) 35%, transparent);
  background:
    linear-gradient(var(--tick), var(--tick)) 0 0 / 6px 1px,
    linear-gradient(var(--tick), var(--tick)) 0 0 / 1px 6px,
    linear-gradient(var(--tick), var(--tick)) 100% 0 / 6px 1px,
    linear-gradient(var(--tick), var(--tick)) 100% 0 / 1px 6px,
    linear-gradient(var(--tick), var(--tick)) 0 100% / 6px 1px,
    linear-gradient(var(--tick), var(--tick)) 0 100% / 1px 6px,
    linear-gradient(var(--tick), var(--tick)) 100% 100% / 6px 1px,
    linear-gradient(var(--tick), var(--tick)) 100% 100% / 1px 6px;
  background-repeat: no-repeat;
}

/* 7. Cards + sections */
.settings-section {
  padding: var(--space-3); margin-bottom: var(--space-3);
  background: var(--bg-elevated); border: 1px solid var(--border-default);
  border-radius: var(--radius-md); box-shadow: var(--shadow-sm);
}
.settings-values { margin: 0; }
.settings-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-3); padding: var(--space-1) 0; }
.settings-row dt { color: var(--fg-secondary); font-size: var(--text-sm); min-width: 160px; }
.settings-row dd { margin: 0; }
.settings-check { display: flex; align-items: center; gap: var(--space-1); margin-bottom: 0; }
.settings-checkbox { width: auto; margin-top: 0; accent-color: var(--accent); }
.account-feedback-link, .account-logout { margin-right: var(--space-1); }
.customer-gdpr .btn-danger { margin-top: var(--space-1); }

/* 8. Badges + status inks: status is never colour alone. */
.badge {
  display: inline-flex; align-items: center; gap: var(--space-0-5);
  padding: 2px var(--space-1); border-radius: var(--radius-pill);
  font-size: var(--text-xs); font-weight: var(--weight-ui);
  background: var(--bg-sunken); color: var(--fg-secondary);
}
.badge.active { background: var(--status-paid-tint); color: var(--status-paid); }
.booking-status { font-size: var(--text-sm); font-weight: var(--weight-ui); }
.booking-status.paid { color: var(--status-paid); }
.booking-status.unpaid { color: var(--status-unpaid); }
.booking-status.held { color: var(--status-held); }
.booking-status.cancelled { color: var(--status-cancelled); text-decoration: line-through; }

/* 9. Mina bokningar: booking cards */
.booking-list { list-style: none; margin: 0; padding: 0; }
.booking-cards { display: flex; flex-direction: column; gap: var(--space-2); }
.booking-card {
  display: flex; flex-direction: column; gap: var(--space-1);
  padding: var(--space-3);
  background: var(--bg-elevated); border: 1px solid var(--border-default);
  border-radius: var(--radius-md); box-shadow: var(--shadow-sm);
}
.booking-card-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-2); }
.booking-time { color: var(--fg-primary); font-weight: var(--weight-ui); }
.booking-card-meta { display: flex; gap: var(--space-1); align-items: center; }
.booking-price { color: var(--fg-muted); font-size: var(--text-sm); }
.booking-code-block {
  display: flex; flex-direction: column; gap: var(--space-0-5);
  padding: var(--space-2); margin-top: var(--space-0-5);
  background: var(--bg-sunken); border-radius: var(--radius-sm);
}
.booking-code-label { display: inline-flex; align-items: center; color: var(--fg-secondary); font-size: var(--text-sm); }
/* The code is a sounding: mono, large, primary ink, the value the customer copies. */
.booking-code-value { font-size: var(--text-xl); font-weight: var(--weight-ui); letter-spacing: 0.08em; color: var(--fg-primary); }
.booking-code-swish { color: var(--fg-secondary); font-size: var(--text-sm); }
.hold-countdown { margin: var(--space-1) 0 0; color: var(--accent); font-weight: var(--weight-ui); }

/* 10. Tillgänglighet: the availability chart. */
.cal-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin-bottom: var(--space-2); }
.cal-range { font-weight: var(--weight-ui); }
.cal-viewtoggle { margin-left: auto; display: flex; gap: var(--space-0-5); }
.cal-viewtoggle button { background: transparent; color: var(--accent); border-color: var(--border-strong); }
.cal-viewtoggle button:hover { background: var(--bg-sunken); }
.cal-viewtoggle button.active { background: var(--accent); color: var(--fg-on-accent); border-color: var(--accent); }
.cal-grid { display: grid; grid-template-columns: auto 1fr; gap: var(--space-0-5); padding: var(--space-0-5); background: var(--bg-sunken); border: 1px solid var(--border-default); border-radius: var(--radius-md); }
.cal-grid.cal-week { grid-template-columns: auto repeat(7, 1fr); }
/* Borderless, elevated card. The grid draws the separators: `.cal-grid`'s gap
   lets the well's sunken fill show through between columns, so the gap IS the
   rule and no column carries an edge of its own. The radius is whole again now
   that no border has to stop anywhere, and `overflow: clip` keeps the sticky
   header band inside the rounded top. Mirrors the Slint week and day grids. */
.cal-col { background: var(--bg-elevated); border-radius: var(--radius-sm); overflow: clip; }
/* High contrast keeps the column edge. Dropping it in the other themes is a
   deliberate look, but here the gap alone measures 1.15:1 against the card
   where the border measured 19.27:1, and WCAG 1.4.11 asks 3:1 of a boundary
   that carries meaning. This theme exists to make that boundary explicit, so
   it opts back in rather than inheriting the quieter chrome. */
:root[data-theme="high-contrast"] .cal-col { border: 1px solid var(--border-default); }
.cal-colhead {
  position: sticky; top: 0; z-index: 3;
  display: flex; align-items: center; justify-content: center;
  height: var(--cal-header-h); padding: 0 var(--space-0-5);
  background: var(--bg-sunken); color: var(--fg-secondary);
  font-weight: var(--weight-display);
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
.cal-colhead.today { color: var(--accent); font-weight: 700; }
/* 24 h window (00:00–24:00) × 40 px/h = 960 px; slot top/height percentages
   resolve against this. Solid 1 px hour hairlines. */
.cal-track {
  position: relative; height: 960px; cursor: pointer;
  background: repeating-linear-gradient(
    to bottom,
    transparent 0, transparent calc(100% / 24 - 1px),
    var(--border-default) calc(100% / 24 - 1px), var(--border-default) calc(100% / 24)
  );
}
.cal-gutter { display: flex; flex-direction: column; width: 28px; }
.cal-gutter-head { visibility: hidden; }
.cal-gutter-track { position: relative; flex: 1; }
.cal-hour {
  position: absolute; right: 2px; transform: translateY(-50%);
  font-family: var(--font-mono); font-size: var(--text-xs);
  font-weight: var(--weight-display);
  color: var(--fg-secondary); line-height: var(--leading-mono);
}
.cal-hour:first-child { transform: none; }

/* 10b. Anonymous hatched water (G-1): an occupied span renders as "sea you
   cannot sail": a hatch fill on theme tokens, its time range as the only text,
   nothing traceable to a booking or its holder. This comment said "an Upptaget
   label" until 2026-08-28; nothing here injects one and the markup has none.
   Free water stays the calm elevated
   surface. The hatch is a repeating-linear-gradient of the muted ink over the
   held tint, status-neutral (it never signals paid/unpaid), non-interactive. */
.avail-slot {
  position: absolute; left: 2px; right: 2px;
  border: 1px solid var(--border-default); border-radius: var(--radius-block);
  padding: 2px 4px; overflow: hidden; line-height: 1.2;
  color: var(--fg-secondary);
  background-color: var(--bg-sunken);
  background-image: repeating-linear-gradient(
    45deg,
    color-mix(in srgb, var(--fg-muted) 22%, transparent) 0,
    color-mix(in srgb, var(--fg-muted) 22%, transparent) 2px,
    transparent 2px, transparent 7px
  );
}
.avail-slot-time { font-size: var(--text-xs); color: var(--fg-secondary); }
/* High contrast: the tint carries no meaning there, so lean on a denser hatch
   and a solid edge for the "occupied" read. */
:root[data-theme="high-contrast"] .avail-slot {
  border-width: 2px; border-color: var(--fg-primary);
  background-image: repeating-linear-gradient(
    45deg, var(--fg-primary) 0, var(--fg-primary) 1px, transparent 1px, transparent 6px
  );
}

/* 11. Help-hints */
.help-hint { position: relative; display: inline-flex; vertical-align: middle; margin-left: var(--space-0-5); }
.help-hint-button {
  width: 16px; height: 16px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; color: var(--fg-muted);
  border: 1px solid var(--fg-muted); border-radius: var(--radius-pill);
  font-size: var(--text-xs); font-weight: var(--weight-ui); line-height: 1;
  transition: color var(--motion-fast) var(--ease-standard), border-color var(--motion-fast) var(--ease-standard);
}
.help-hint-button:hover { background: transparent; color: var(--accent); border-color: var(--accent); }
.help-hint-text {
  position: absolute; left: 0; right: auto; top: calc(100% + var(--space-0-5)); z-index: 10;
  width: max-content; max-width: min(240px, calc(100vw - var(--space-4)));
  padding: var(--space-1) var(--space-2);
  background: var(--bg-elevated); color: var(--fg-primary);
  border: 1px solid var(--border-default); border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  font-size: var(--text-sm); font-weight: var(--weight-body);
  opacity: 0; visibility: hidden; transition: opacity var(--motion-fast) var(--ease-standard);
}
.help-hint:hover .help-hint-text, .help-hint:focus-within .help-hint-text { opacity: 1; visibility: visible; }

/* 12. Feedback form + prompt */
.feedback-rating { display: flex; align-items: center; gap: var(--space-2); border: none; padding: 0; margin: 0; }
.feedback-rating legend { padding: 0; color: var(--fg-secondary); font-size: var(--text-sm); }
.feedback-star { display: inline-flex; align-items: center; gap: 2px; margin: 0; font-weight: var(--weight-ui); }
.feedback-star input { width: auto; margin: 0; accent-color: var(--accent); }
.feedback-body { min-height: 96px; resize: vertical; }
/* The prompt reuses the onboarding modal shell (`.onb` + `.loggkort-scrim`). */
.onb {
  position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
  z-index: 60; width: min(420px, 92vw);
  background: var(--bg-elevated); border: 1px solid var(--border-strong);
  border-radius: var(--radius-md); box-shadow: var(--shadow-md);
  padding: var(--space-3);
  animation: rise-in var(--motion-base) var(--ease-standard) backwards;
}
.onb-title { font-family: var(--font-display); margin: 0 0 var(--space-1); }
.onb-price { margin: 0; color: var(--fg-muted); }
.onb-actions { display: flex; align-items: center; gap: var(--space-1); margin-top: var(--space-3); flex-wrap: wrap; }
.loggkort-scrim {
  position: fixed; inset: 0; z-index: 40;
  background: rgba(22, 48, 62, 0.32);
  animation: scrim-in var(--motion-base) var(--ease-standard) backwards;
}
@keyframes scrim-in { from { opacity: 0; } }
.loggkort-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); margin-top: var(--space-1); }
.loggkort-note { color: var(--fg-secondary); font-size: var(--text-sm); margin: 0; flex-basis: 100%; }

/* 13. Signalen: the toast. The lifetime hairline's animation IS the dismissal
   clock; hover pauses both. */
.signalen-stack {
  position: fixed; top: var(--space-2); left: 50%; transform: translateX(-50%);
  display: flex; flex-direction: column; gap: var(--space-1); z-index: 100;
}
.signalen {
  position: relative; overflow: hidden;
  display: flex; align-items: center; gap: var(--space-1);
  min-width: 260px; max-width: 420px;
  padding: var(--space-1) var(--space-2) calc(var(--space-1) + 2px);
  background: var(--bg-inverse); color: var(--fg-inverse);
  border: 1px solid var(--border-strong); border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  animation: signal-in var(--motion-base) var(--ease-standard) backwards;
}
@keyframes signal-in { from { opacity: 0; transform: translateY(12px); } }
.signalen-flag { flex-shrink: 0; }
.signalen-flag svg { width: 14px; height: 14px; display: block; }
.signalen.danger .signalen-flag { color: color-mix(in srgb, var(--danger) 50%, var(--fg-inverse)); }
.signalen.info .signalen-flag { color: color-mix(in srgb, var(--info) 50%, var(--fg-inverse)); }
.signalen.success .signalen-flag { color: color-mix(in srgb, var(--success) 50%, var(--fg-inverse)); }
.signalen.warning .signalen-flag { color: color-mix(in srgb, var(--warning) 50%, var(--fg-inverse)); }
/* FR-28 §7: primary feedback surface at base size, medium weight. */
.signalen-text { flex: 1; font-size: var(--text-base); font-weight: 500; }
.signalen-close {
  background: transparent; border: none; padding: 0 var(--space-0-5);
  color: var(--fg-inverse); opacity: .7; font-size: var(--text-sm);
}
.signalen-close:hover { background: transparent; opacity: 1; }
.signalen-life {
  position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: var(--signal); transform-origin: left;
  animation: signal-life 5s linear forwards;
}
.signalen:hover .signalen-life { animation-play-state: paused; }
@keyframes signal-life { to { transform: scaleX(0); } }
:root[data-theme="high-contrast"] .signalen { border-width: 2px; }
:root[data-theme="high-contrast"] .signalen.danger { border-color: var(--danger); }
:root[data-theme="high-contrast"] .signalen.info { border-color: var(--info); }
:root[data-theme="high-contrast"] .signalen.success { border-color: var(--success); }
:root[data-theme="high-contrast"] .signalen.warning { border-color: var(--warning); }
:root[data-theme="high-contrast"] .signalen-life { background: var(--fg-primary); }

/* 14. Login lockup + card. */
.login-brand {
  display: flex; align-items: center; justify-content: center; gap: var(--space-1);
  margin-top: var(--space-8); color: var(--accent);
}
.login-mark svg { width: 28px; height: 28px; display: block; }
.login-wordmark { font-family: var(--font-display); font-weight: var(--weight-display); font-size: var(--text-xl); color: var(--fg-primary); }
.login-title { margin-bottom: 0; }
.login-form {
  display: flex; flex-direction: column; gap: var(--space-2); max-width: 360px;
  margin: var(--space-3) auto 0;
  padding: var(--space-4);
  background: var(--bg-elevated); border: 1px solid var(--border-default);
  border-radius: var(--radius-md); box-shadow: var(--shadow-md);
  animation: rise-in var(--motion-slow) var(--ease-standard) backwards;
}

/* 15. Elevation-as-light (dark theme): a 1px lighter top edge plus a faint glow
   painted inside each surface. The two tokens are fully transparent in light and
   high-contrast, so this rule is inert there. */
.settings-section, .booking-card, .login-form, .help-hint-text, .signalen, .onb {
  background-image: linear-gradient(
    180deg,
    var(--elevation-edge) 0,
    var(--elevation-edge) 1px,
    var(--elevation-glow) 1px,
    transparent 48px
  );
}

/* 16. Responsive: mobile-first is this surface's primary device. The rail
   collapses to a pinned bottom bar; the week grid linearises to an agenda stack. */
@media (max-width: 768px) {
  /* Help-hint popup can't stay on-screen for every trigger position at narrow
     widths, so re-pin it as a fixed, viewport-clamped banner. */
  .help-hint-text {
    position: fixed; left: var(--space-2); right: var(--space-2);
    top: auto; bottom: calc(var(--nav-bar-h, 0px) + var(--space-2));
    width: auto; max-width: none;
  }
}
@media (max-width: 640px) {
  :root { --nav-bar-h: 56px; }
  .topbar-wordmark { display: none; }
  .topbar { gap: var(--space-1); padding: var(--space-1); flex-wrap: wrap; }
  .topbar select { max-width: 8rem; flex-shrink: 0; }
  .app-shell { flex-direction: column; }
  .app-nav {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
    flex-direction: row; width: auto; height: var(--nav-bar-h); gap: var(--space-0-5);
    border-right: none; border-top: 1px solid var(--border-default);
    padding: var(--space-0-5); background: var(--bg-elevated);
  }
  .app-nav a {
    flex: 1; height: auto; min-width: 0;
    flex-direction: column; justify-content: center; gap: 2px;
    padding: var(--space-0-5);
  }
  .app-nav a .nav-label { font-size: var(--text-xs); }
  .nav-marker { display: none; }
  .app-nav a:hover { background: transparent; }
  .app-nav a[aria-current="page"] { background: transparent; box-shadow: inset 0 2px 0 var(--signal); }
  .app-main { padding: var(--space-2); padding-bottom: calc(var(--nav-bar-h) + var(--space-2)); }
  /* Agenda stack: the week grid linearises to one column and the fixed-height
     hour track collapses so each occupied span reads as a stacked list item. */
  .cal-toolbar { flex-wrap: wrap; gap: var(--space-1); }
  .cal-viewtoggle { margin-left: 0; flex-wrap: wrap; }
  .cal-range { min-width: 0; }
  .cal-grid.cal-week { grid-template-columns: 1fr; }
  .cal-gutter { display: none; }
  .cal-track { height: auto; min-height: 48px; background: none; padding: var(--space-0-5); }
  .avail-slot { position: static; display: block; width: 100% !important; left: auto !important; margin: 2px 0; }
}

/* 11. Sea-state banner, ported from the admin calendar (crates/app-admin-web/
   styles/app.css); the water itself is painted from ui-tokens, so the three
   clients draw one sea. */
/* Sea-state: the density-tier caption above a decorative water strip.

   The strip is a canvas, and nothing here draws on it. `seaband` builds the
   geometry, ui-tokens' `sea_canvas` finds the canvas by its `data-sea`
   attribute and paints it frame by frame, and the desktop paints the same scene
   through its own rasteriser. What this file settles is the two things the
   painter reads back off the element.

   Nothing here decides the water's colours, and that is deliberate. The banner
   carries one of sixteen palettes, four hours by four seasons, chosen by the
   clock and held in ui-tokens; the theme the viewer picked decides the caption
   above the strip and nothing inside it. So a December dusk warms the water
   toward amber on a page that is still the user's light or dark, and no text
   anywhere shifts by a shade. It is safe only because the strip is decorative,
   `aria-hidden`, carries no text, and is dropped outright under high contrast.

   Reduced motion: the §2 `* { animation: none }` contract cannot reach inside a
   canvas, so the painter honours the preference itself, from the media query and
   the `[data-reduce-motion]` toggle alike. It paints `seaband`'s chosen still
   frame, the moment in the loop whose fleet is best spread across the band,
   rather than pausing the water wherever it happened to be.

   High contrast: the strip is dropped (water texture off, per spec); the
   caption above it still shows. The painter reads a zero-width canvas and skips
   it, so a hidden strip costs nothing per frame. */
.sea-caption { text-align: center; margin-bottom: var(--space-1); }
.sea-title {
  font-family: var(--font-display); font-weight: var(--weight-display);
  font-size: var(--text-lg); margin: 0; padding-top: var(--space-2);
  animation: rise-in var(--motion-slow) var(--ease-standard) backwards;
}
.sea-text {
  color: var(--fg-secondary); margin: var(--space-0-5) 0 0;
  animation: rise-in var(--motion-slow) var(--ease-standard) backwards;
}

/* The caption's wave glyph, standing where a dash used to separate the sea-state
   phrase from the booking count. It is generated from the same surface as the
   water below it (`hamnkapten-sea-wave`), carries its own px size in the SVG
   attributes, matching the fixed `--text-*` scale the rest of the type uses, and
   strokes in `currentColor` so it takes the caption's ink in every theme.
   `middle` sits it on the text's centre; the baseline would hang it low. */
.sea-wave-glyph { vertical-align: middle; }
.sea-banner {
  position: relative; height: var(--sea-strip-h); overflow: hidden;
  margin-bottom: var(--space-2);
}
:root[data-theme="high-contrast"] .sea-banner { display: none; }
/* The canvas fills the strip. This is its CSS size only: the painter sizes the
   backing store in device pixels, so the band stays crisp on a 2x display
   instead of being an upscaled CSS-sized buffer. */
.sea-canvas { display: block; width: 100%; height: 100%; }

/* 12. Month lattice, ported from the admin calendar (crates/app-admin-web/
   styles/app.css), stripped to anonymous per-day occupancy (no per-booking chips):
   a Monday-start 6×7 grid of day-cell buttons that helm to the Day view, each
   showing its date and an aggregate occupancy count. */
.cal-grid.cal-month { grid-template-columns: repeat(7, 1fr); }
.cal-mhead { padding: var(--space-0-5); text-align: center; background: var(--bg-sunken); color: var(--fg-secondary); }
.cal-mday {
  display: flex; flex-direction: column; align-items: stretch; gap: 2px;
  min-height: 88px; padding: var(--space-0-5);
  background: var(--bg-elevated); color: var(--fg-primary);
  border: 1px solid var(--border-default); border-radius: var(--radius-sm);
  cursor: pointer; text-align: left; font: inherit;
}
.cal-mday:hover { background: var(--bg-sunken); }
.cal-mday.other-month { background: var(--bg-base); }
.cal-mday.other-month .cal-mdate { color: var(--fg-muted); }
.cal-mday.today .cal-mdate { color: var(--accent); font-weight: 700; }
.cal-mdate { align-self: flex-end; }
.cal-mcount {
  margin-top: auto; text-align: center; font-size: var(--text-sm);
  padding: 2px 4px; border-radius: var(--radius-sm);
  background: var(--bg-sunken); color: var(--fg-secondary);
}
.cal-mcount.busy { background: var(--accent); color: var(--fg-on-accent); }
