/**
 * Live At The Markets — base stylesheet.
 * Shared layout/component rules for every franchise. :root here holds
 * neutral default colours/fonts; style-{subdomain}.css loads after this
 * and overrides just those variables per franchise.
 */

:root{
  --highlight: #9CA3AF;
  --callout:   #CBD5E1;
  --tertiary:  #F1F5F9;
  --secondary: #64748B;
  --primary:   #334155;
  --dark:      #111827;
  --light:     #FAFAFA;
  --white:     #FFFFFF;
  --line:      #E5E7EB;
  --shadow:    0 8px 24px rgba(0,0,0,0.08);
  --radius:    12px;
  --radius-sm: 8px;
  --radius-lg: 999px;
  --font-display: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --font-body:    -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;

  --primary-hover:  #1e293b; /* primary button hover shade */
  --primary-shadow: rgba(51,65,85,.35); /* glow tint under the primary button */

  /* UI chrome (not brand colours) — stays out of franchise theme files. */
  --latm:              #94AC35;
  --text-muted:        #6b7268;  /* secondary body text (meta, sub-labels) */
  --text-subtle:       #8b9188;  /* lighter helper/caption text */
  --footer-muted:      #cfd8d0;  /* muted text on the dark footer */
  --success-bg:        #E7F3D9;
  --success-text:      #3F6212;
  --danger-bg:         #FCE7E3;
  --danger-text:       #A3301C;
  --warning-bg:        #FFF3D6;
  --warning-text:      #8A5B00;
  --badge-neutral-bg:  #EEEEEE;
  --badge-neutral-text:#666666;
  --strikethrough:     #ff9999; /* "was" price line-through colour */
  --video-bg:          #101613; /* livestream player background */
  --youtube-grey:      #282828; /* third-party brand colour, not ours */
  --overlay-light:     rgba(255,255,255,.25); /* translucent badge on hero */
  --overlay-dark:      rgba(35,48,42,.4);     /* basket drawer backdrop */
  --overlay-dark-soft: rgba(35,48,42,.18);    /* basket drawer drop-shadow tint */
  --shadow-color:      rgba(0,0,0,.08);       /* hero text-shadow tint */
  --shadow-color-soft: rgba(0,0,0,.06);       /* sticky-bar top shadow tint */
  --google-red:        #DD4B39; /* third-party brand colour, not ours */
  --facebook-blue:     #3b5998; /* third-party brand colour, not ours */
  --apple-black:       #1D1D1F; /* third-party brand colour, not ours */
}

* {
  box-sizing: border-box;
}
html,
body {
  margin: 0;
  padding: 0;
}

/* ---------- subtle custom scrollbars (WebKit/Blink only) ---------- */
@supports selector(::-webkit-scrollbar) {
  ::-webkit-scrollbar {
    width: 6px;
    height: 6px;
  }
  ::-webkit-scrollbar-track {
    background: transparent;
  }
  ::-webkit-scrollbar-thumb {
    background: rgba(0, 0, 0, 0.4);
    border-radius: 5px;
  }
  ::-webkit-scrollbar-thumb:hover {
    background: rgba(0, 0, 0, 0.6);
  }
  ::-webkit-scrollbar-button {
    display: none;
  }
  ::-webkit-scrollbar-corner {
    display: none;
  }
}

html {
  scrollbar-gutter: stable;
}
/* .sticky-bar (position:sticky; bottom:0) only "sticks" once the page
   would actually scroll past that point — if the content before it is
   shorter than the viewport (e.g. live.php's Live filter showing a
   single product instead of a full grid), .sticky-bar just sits at
   wherever normal document flow placed it, since sticky positioning
   doesn't pull an element down to fill slack space, only stop it
   scrolling away once it would. Giving .live-wrap a min-height
   covering "the rest of the viewport below the topbar" means short
   content still pushes .sticky-bar down to the true bottom. Deliberately
   NOT done by making body a flex column (tried first) — that turned
   .live-wrap into a flex item, which picks up an implicit
   min-width:auto based on its content's natural size and let the whole
   page overflow horizontally; min-height via calc() avoids that
   entirely. Only relevant below the 769px breakpoint where live.php
   switches to this normally-scrolling mobile layout — the desktop
   fixed-pane layout already has its own explicit height regardless of
   content length. */
@media (max-width: 768px) {
  body.live-page .live-wrap {
    min-height: calc(100vh - var(--topbar-h, 71px) - var(--sticky-bar-h, 64px));
    min-height: calc(100dvh - var(--topbar-h, 71px) - var(--sticky-bar-h, 64px));
  }
}
body {
  font-family: var(--font-body);
  background: var(--light);
  color: var(--dark);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
img {
  max-width: 100%;
  display: block;
}
a {
  color: var(--primary);
  text-decoration: none;
}
/* A one-click action that has to be a POST — Delete, Deactivate, Suspend.
   It sits in a row of links and should read as one; see action_button()
   in franchise/includes/functions.php for why it is a form. */
.inline-form {
  display: inline;
}
.link-button {
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: var(--primary);
  cursor: pointer;
}
.link-button:hover {
  text-decoration: underline;
}
.link-button:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}
h1,
h2,
h3,
h4 {
  font-family: var(--font-body);
  font-weight: 800;
  margin: 0 0 0.4em;
  line-height: 1.15;
}
.script {
  font-family: var(--font-display);
  font-weight: 700;
}
p {
  margin: 0 0 1em;
}
.container {
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 16px;
  padding-left: calc(16px + env(safe-area-inset-left));
  padding-right: calc(16px + env(safe-area-inset-right));
}
button {
  font-family: inherit;
  cursor: pointer;
  color: var(--dark);
}

/* ---------- buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  padding: 12px 22px;
  border-radius: var(--radius-lg);
  border: none;
  font-weight: 600;
  font-size: 15px;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.btn:active {
  transform: scale(0.97);
}
.btn-primary {
  background: var(--primary);
  color: var(--white);
  box-shadow: 0 2px 16px var(--primary-shadow);
}
.btn-basket-open {
  background: var(--secondary);
}
.btn-reminder-set {
  cursor: default;
}
.btn-reminder-set:disabled {
  opacity: 1;
}
.btn-primary.btn-reminder {
  background: var(--callout);
  color: var(--light);
}
.btn-primary:hover {
  background: var(--primary-hover);
}
.btn-secondary {
  background: var(--white);
  color: var(--dark);
  border: 2px solid var(--line);
}
.btn-secondary:hover {
  border-color: var(--primary);
  color: var(--primary);
}
.btn-block {
  width: 100%;
}
.btn-sm {
  padding: 8px 14px;
  font-size: 13px;
}
.btn-ghost {
  background: transparent;
  color: var(--primary);
  padding: 8px 10px;
}
.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Skip-to-content link: off-screen until keyboard focus. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
  background: var(--dark);
  color: var(--white);
  padding: 12px 20px;
  border-radius: 0 0 8px 0;
  z-index: 1000;
  font-weight: 600;
  text-decoration: none;
}
.skip-link:focus {
  left: 0;
  top: 0;
  width: auto;
  height: auto;
  overflow: visible;
}

/* ---------- top nav ---------- */
.topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--white);
  border-bottom: 1px solid var(--line);
  padding-top: env(safe-area-inset-top);
}
/* live.php mobile only: topbar unpins to reclaim vertical space.
   relative (not static) keeps .topbar a positioned ancestor for
   .nav-links' absolute dropdown. */
@media (max-width: 768px) {
  body.live-page .topbar {
    position: relative;
  }
}
.topbar-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 16px;
  gap: 10px;
}
.brand {
  display: flex;
  align-items: center;
  gap: 14px;
}
.brand img {
  width: 50px;
  height: 50px;
  border-radius: 50%;
}
.brand img.latm-logo {
  background: var(--latm);
}
.brand img.brand-logo {
  width: auto;
  height: 40px;
  border-radius: 0;
}
.brand-text {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 700;
  color: var(--dark);
}
.brand-text small {
  display: block;
  font-family: var(--font-body);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--primary);
}
.nav-toggle {
  display: block;
  background: none;
  border: none;
  font-size: 26px;
  line-height: 1;
  padding: 4px 8px;
}
.nav-links {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 10;
  background: var(--white);
  border-bottom: 1px solid var(--line);
  flex-direction: column;
  padding: 8px 16px 16px;
  box-shadow: var(--shadow);
}
.nav-links.open {
  display: flex;
}
.nav-links a {
  color: var(--dark);
  padding: 10px 0;
  font-weight: 500;
  border-bottom: 1px solid var(--line);
}
.nav-links a:last-child {
  border-bottom: none;
}
.nav-cart {
  position: relative;
  font-size: 22px;
}
.nav-cart .badge {
  position: absolute;
  top: -6px;
  right: -8px;
  background: var(--primary);
  color: var(--white);
  font-size: 10px;
  font-weight: 700;
  border-radius: 50%;
  width: 18px;
  height: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
}

@media (min-width: 860px) {
  .nav-toggle {
    display: none;
  }
  .nav-links {
    display: flex;
    position: static;
    flex-direction: row;
    background: none;
    border: none;
    box-shadow: none;
    padding: 0;
    align-items: center;
    gap: 22px;
  }
  .nav-links a {
    border-bottom: none;
    padding: 0;
  }
}

/* ---------- hero ---------- */
.hero {
  background: center / cover no-repeat;
  color: var(--white);
  padding: 42px 0 56px;
  position: relative;
  overflow: hidden;
}
.hero::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 1;
  background: linear-gradient(160deg, var(--callout) 0%, var(--highlight) 100%);
  opacity: 0.85;
}
.hero::after {
  content: "";
  position: absolute;
  right: -60px;
  bottom: -60px;
  width: 220px;
  height: 220px;
  z-index: 2;
  background: var(--tertiary);
  border-radius: 50%;
  opacity: 0.15;
}
.hero .container {
  position: relative;
  z-index: 3;
}
.hero h1 {
  font-family: var(--font-display);
  font-size: 42px;
  font-weight: 700;
  color: var(--white);
  text-shadow: 0 2px 12px var(--shadow-color);
  max-width: 520px;
}
.hero p {
  font-size: 17px;
  max-width: 520px;
  opacity: 0.95;
}
.hero-badge {
  display: inline-block;
  background: var(--overlay-light);
  border-radius: var(--radius-lg);
  padding: 6px 14px;
  font-weight: 600;
  font-size: 13px;
  margin-bottom: 14px;
  backdrop-filter: blur(4px);
}
.hero-badge i {
  margin-right: 5px;
}

/* ---------- upcoming events cards ---------- */
.events-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
  margin-top: -34px;
  position: relative;
  z-index: 2;
}
@media (min-width: 640px) {
  .events-grid {
    grid-template-columns: 1fr 1fr;
  }
}
.event-card {
  background: var(--white);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 22px;
  border-top: 6px solid var(--secondary);
}
.event-card .date {
  font-family: var(--font-display);
  font-size: 30px;
  font-weight: 700;
  color: var(--primary);
}
.event-card .meta {
  color: var(--text-muted);
  font-size: 14px;
  margin-bottom: 10px;
}
.countdown-mini {
  display: flex;
  gap: 8px;
  margin: 12px 0;
  justify-content: space-evenly;
}
.countdown-mini div {
  background: var(--light);
  border-radius: 8px;
  padding: 6px 10px;
  text-align: center;
  min-width: 52px;
  width: 100%;
}
.countdown-mini strong {
  display: block;
  font-size: 18px;
}
.countdown-mini span {
  font-size: 10px;
  text-transform: uppercase;
  color: var(--text-subtle);
}

/* ---------- featured ---------- */
.featured-home {
  background: var(--white);
}

/* ---------- section ---------- */
.section-top {
  padding-bottom: 46px;
  padding-top: 46px;
}
.section-title {
  text-align: center;
  margin-bottom: 8px;
  font-size: 30px;
}
.section-sub {
  text-align: center;
  color: var(--text-muted);
  max-width: 520px;
  margin: 0 auto 30px;
}
.how-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
}
@media (min-width: 760px) {
  .how-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}
.how-card {
  background: var(--white);
  border-radius: var(--radius);
  padding: 20px;
  box-shadow: var(--shadow);
  text-align: center;
}
.how-card .num {
  font-family: var(--font-display);
  font-size: 34px;
  color: var(--primary);
  font-weight: 700;
}

/* ---------- forms ---------- */
.form-wrap {
  max-width: 480px;
  margin: 30px auto;
  background: var(--white);
  padding: 28px 24px;
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

/* ---------- PWA install/notification prompts (includes/pwa-prompts.php) ---------- */
.pwa-prompts {
  max-width: 480px;
  margin: 20px auto;
}
.pwa-prompt-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 0;
  border-top: 1px solid var(--line);
}
.pwa-prompt-row:first-of-type {
  border-top: 1px solid var(--line);
  margin-top: 6px;
}
.pwa-prompt-row strong {
  display: block;
  margin-bottom: 2px;
}
.pwa-prompt-row .btn {
  flex-shrink: 0;
}
.pwa-ios-steps {
  margin: 0;
  padding-left: 20px;
  color: var(--text-subtle);
  font-size: 14px;
  line-height: 1.7;
}
.pwa-ios-steps li {
  margin-bottom: 2px;
}

/* ---------- "Add to Home Screen" bar (includes/install-bar.php) ----------
   Site-wide, bottom-anchored, and deliberately above the basket's
   .sticky-bar rather than underneath it: --sticky-bar-h is set by
   live.js on the live market page and is simply unset everywhere else,
   where the fallback of 0 puts the bar on the floor. z-index sits above
   the sticky bar (75) and the basket drawer, below the product modal
   (1200) and the offline banner (2000) — an install invitation must
   never cover either of those. */
.pwa-install-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: var(--sticky-bar-h, 0px);
  z-index: 1150;
  background: var(--white);
  border-top: 1px solid var(--line);
  box-shadow: 0 -8px 28px rgba(0, 0, 0, 0.12);
  padding: 12px 16px;
  padding-bottom: calc(12px + env(safe-area-inset-bottom));
  transform: translateY(110%);
  /* The EXIT. Plain and quick — dismissing something should feel like it
     got out of the way, not like it performed on the way out. */
  transition: transform .32s ease-in;
}
/* The ENTRANCE, overriding the above only while the class is on. It runs
   two seconds after load (see install-bar.php), by which point the page
   has stopped moving, so this is the only thing that does. Slower than
   the exit on purpose — this one is asking to be noticed.

   easeOutExpo: fast off the mark, long soft landing. Deliberately NOT an
   overshoot curve, tempting as one is here. Overshooting means passing
   translateY(0) into negative Y, which lifts the bar's bottom edge off
   the bottom of the viewport — on the live market page that would flash
   a sliver of the basket's sticky-bar underneath it, and patching that
   with a filler element would leave the filler sitting over the sticky
   bar for the rest of the page's life. */
.pwa-install-bar.is-in {
  transform: translateY(0);
  transition: transform .5s cubic-bezier(.16, 1, .3, 1);
}
@media (prefers-reduced-motion: reduce) {
  /* No slide, no overshoot — it simply appears. The two-second beat
     still applies, because the timing is what draws the eye here; the
     movement was only ever how it arrived. */
  .pwa-install-bar,
  .pwa-install-bar.is-in {
    transition: none;
  }
}
.pwa-install-bar-inner {
  max-width: 768px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 48px 1fr;
  grid-template-areas:
    "icon copy"
    "actions actions";
  align-items: center;
  gap: 10px 12px;
}
.pwa-install-bar-icon {
  grid-area: icon;
  width: 48px;
  height: 48px;
  border-radius: 11px; /* the iOS/Android squircle, near enough at 48px */
  display: block;
}
.pwa-install-bar-copy {
  grid-area: copy;
  min-width: 0;
}
.pwa-install-bar-copy strong {
  display: block;
  font-size: 15px;
  line-height: 1.25;
  color: var(--dark);
}
.pwa-install-bar-copy span {
  display: block;
  font-size: 13px;
  line-height: 1.35;
  color: var(--text-muted);
  margin-top: 2px;
}
.pwa-install-bar-actions {
  grid-area: actions;
  display: flex;
  align-items: center;
  gap: 8px;
}
/* The primary action takes the width on a phone. "Add to Home Screen"
   is a long label and shrinking it to fit beside the text is how it
   ends up wrapping mid-phrase or, worse, getting abbreviated to
   something nobody recognises from their own home screen. */
.pwa-install-bar-actions .btn {
  flex: 1 1 auto;
  justify-content: center;
  white-space: nowrap;
}
.pwa-install-bar-later {
  flex: 0 0 auto;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-muted);
  background: none;
  border: none;
  padding: 10px 12px;
  cursor: pointer;
  border-radius: var(--radius-sm);
}
.pwa-install-bar-later:hover {
  color: var(--primary);
  background: var(--tertiary);
}
@media (min-width: 620px) {
  /* Room for one row: icon, copy, then the buttons on the right. */
  .pwa-install-bar-inner {
    grid-template-columns: 48px 1fr auto;
    grid-template-areas: "icon copy actions";
  }
  .pwa-install-bar-actions .btn {
    flex: 0 0 auto;
  }
}

/* ---------- iOS Share-sheet walkthrough (includes/install-bar.php) ----------
   iOS exposes no install API to any browser, Safari included, so this
   is not a fallback — it is the entire flow on an iPhone. */
.pwa-ios-sheet {
  position: fixed;
  inset: 0;
  z-index: 1300;
}
.pwa-ios-sheet-backdrop {
  position: absolute;
  inset: 0;
  background: var(--overlay-dark);
  opacity: 0;
  transition: opacity .26s ease;
}
.pwa-ios-sheet.is-in .pwa-ios-sheet-backdrop {
  opacity: 1;
}
.pwa-ios-sheet-panel {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  max-width: 520px;
  margin: 0 auto;
  background: var(--white);
  border-radius: 18px 18px 0 0;
  padding: 22px 22px 18px;
  padding-bottom: calc(18px + env(safe-area-inset-bottom));
  transform: translateY(100%);
  transition: transform .26s ease;
}
.pwa-ios-sheet.is-in .pwa-ios-sheet-panel {
  transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  .pwa-ios-sheet-backdrop,
  .pwa-ios-sheet-panel {
    transition: none;
  }
}
.pwa-ios-sheet-panel h2 {
  margin: 0 0 6px;
  font-size: 20px;
  color: var(--dark);
}
.pwa-ios-sheet-lead {
  margin: 0 0 14px;
  font-size: 14px;
  line-height: 1.45;
  color: var(--text-muted);
}
.pwa-ios-sheet-close {
  position: absolute;
  top: 10px;
  right: 12px;
  font-size: 26px;
  line-height: 1;
  background: none;
  border: none;
  color: var(--text-subtle);
  cursor: pointer;
  padding: 6px 10px;
}
.pwa-ios-sheet .pwa-ios-steps {
  font-size: 15px;
  color: var(--dark);
  line-height: 1.9;
}
/* The Share glyph, inline in the sentence, so the step names the icon
   the shopper is looking at rather than describing it. */
.pwa-ios-share {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 7px;
  background: var(--tertiary);
  color: #0a84ff; /* iOS system blue — the colour the real button is */
  vertical-align: -7px;
  margin: 0 2px;
}
/* Points at the Safari toolbar below, where Share actually is. */
.pwa-ios-sheet-arrow {
  text-align: center;
  font-size: 24px;
  line-height: 1;
  color: var(--text-subtle);
  margin-top: 10px;
  animation: pwa-ios-nudge 1.6s ease-in-out infinite;
}
@keyframes pwa-ios-nudge {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(5px); }
}
@media (prefers-reduced-motion: reduce) {
  .pwa-ios-sheet-arrow {
    animation: none;
  }
}

.form-wrap h1 {
  font-size: 26px;
}
label {
  display: block;
  font-weight: 600;
  font-size: 13px;
  margin: 14px 0 6px;
}
input[type="text"],
input[type="email"],
input[type="password"],
input[type="tel"],
input[type="time"],
input[type="number"],
input[type="date"],
input[type="datetime-local"],
select,
textarea {
  width: 100%;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  border: 1.5px solid var(--line);
  font-family: inherit;
  font-size: 15px;
  background: var(--light);
}
input:focus,
select:focus,
textarea:focus {
  outline: 2px solid var(--callout);
  border-color: var(--callout);
}
.form-row {
  display: flex;
  gap: 10px;
}
@media (max-width: 760px) {
  .form-row {
    flex-direction: column;
  }
}
.form-row > div {
  flex: 1;
}
.form-help {
  font-size: 12px;
  color: var(--text-subtle);
  margin-top: 4px;
}

/* Visually hidden native file input; a styled label triggers it. */
.file-input-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.image-upload-row {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  margin: 8px 0 4px;
}
.image-upload-row label.file-btn {
  margin: 0;
}
.image-current {
  display: flex;
  align-items: center;
  gap: 8px;
}
.image-current img {
  width: 64px;
  height: 64px;
  border-radius: 10px;
  object-fit: cover;
  border: 1px solid var(--line);
}
.icon-btn-trash {
  background: none;
  border: none;
  color: var(--primary);
  font-size: 16px;
  cursor: pointer;
  padding: 6px;
  border-radius: 8px;
  line-height: 1;
}
.icon-btn-trash:hover {
  background: var(--danger-bg);
}
.icon-btn {
  background: none;
  border: none;
  color: var(--primary);
  font-size: 16px;
  cursor: pointer;
  padding: 6px;
  border-radius: 8px;
  line-height: 1;
}
.icon-btn:hover {
  background: var(--primary-hover);
}
.icon-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ---------- grower/timebox-products.php: buying options ---------- */
.btn-highlight {
  background: var(--highlight);
  color: var(--white);
}
.btn-highlight:hover {
  background: var(--primary-hover);
}

/* Slide toggle via max-height transition (JS keeps it in sync with
   content height — see assets/js/grower-options.js). Border/padding
   live on an inner wrapper so they collapse cleanly at max-height:0. */
.buying-options-panel {
  overflow: hidden;
  max-height: 0;
  transition: max-height 500ms ease;
}
.buying-options-panel-inner {
  border-top: 1px solid var(--line);
  margin-top: 18px;
  padding-top: 12px;
}
.buying-options-panel h4 {
  font-size: 13px;
  font-weight: 700;
  margin: 0 0 8px;
}

.options-table {
  width: 100%;
  border-collapse: collapse;
}
.options-table th {
  text-align: left;
  font-size: 12px;
  color: var(--text-subtle);
  font-weight: 600;
  padding: 4px 6px;
}
/* Combined selector needed to out-specificity .options-table th. */
.options-table th.text-center {
  text-align: center;
}
.options-table td {
  padding: 4px 6px;
  vertical-align: middle;
  justify-content: space-evenly;
}
.options-table input {
  width: 100%;
  padding: 6px 8px;
  font-size: 14px;
}
.options-table .drag-handle {
  cursor: grab;
  text-align: center;
  color: var(--text-subtle);
  width: 32px;
  touch-action: none;
}
.options-table .drag-handle:active {
  cursor: grabbing;
}
.options-table tr.option-row-dragging {
  opacity: 0.4;
}
.options-table tr.option-row-drop-target {
  box-shadow: inset 0 2px 0 var(--primary);
}
.option-actions-cell {
  display: flex;
  gap: 2px;
  white-space: nowrap;
}

/* Flip animation for the save-icon state swap (save/spinner/tick). */
@keyframes optionIconFlip {
  0% {
    transform: rotateY(0deg);
  }
  50% {
    transform: rotateY(90deg);
  }
  100% {
    transform: rotateY(0deg);
  }
}
.icon-flip {
  display: inline-block;
  animation: optionIconFlip 0.3s ease;
}
.alert {
  padding: 12px 16px;
  border-radius: var(--radius-sm);
  font-size: 14px;
  margin-bottom: 16px;
}
.alert-error {
  background: var(--danger-bg);
  color: var(--danger-text);
}
.alert-success {
  background: var(--success-bg);
  color: var(--success-text);
}
.form-links {
  display: flex;
  justify-content: space-between;
  margin-top: 14px;
  font-size: 13px;
}
.divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 20px 0;
  color: var(--text-subtle);
  font-size: 12px;
  text-transform: uppercase;
}
.divider::before,
.divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}
.social-buttons {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.social-buttons .btn {
  justify-content: center;
}
.btn-google {
  background: var(--google-red);
  color: var(--white);
}
.btn-apple {
  background: var(--apple-black);
  color: var(--white);
}
.btn-facebook {
  background: var(--facebook-blue);
  color: var(--white);
}

/* ---------- live shopping page ---------- */
.live-wrap {
  max-width: 1280px;
  margin: 0 auto;
}

/* Sticky stack for cutoff banner/video/tabs; `top` offset set by live.js. */
.stick-layer {
  position: sticky;
  z-index: 40;
}

.video-panel {
  background: var(--youtube-grey);
}
/* Caps video height at 35vh via width (aspect-ratio derives height),
   letterboxing on wide/short viewports. No-op on mobile, where width
   is already the limiting factor. */
.video-panel .ratio {
  position: relative;
  width: min(100%, 37vh * 16 / 9);
  aspect-ratio: 16/9;
  margin: 0 auto;
}
.video-panel iframe,
.video-panel video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
.live-badge {
  position: absolute;
  top: 12px;
  left: 12px;
  background: var(--primary);
  color: var(--white);
  font-weight: 700;
  font-size: 12px;
  padding: 5px 12px;
  border-radius: var(--radius-lg);
  display: flex;
  align-items: center;
  gap: 6px;
  z-index: 3;
}
.live-badge .dot {
  width: 8px;
  height: 8px;
  background: var(--white);
  border-radius: 50%;
  animation: pulse 1.4s infinite;
}
@keyframes pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.3;
  }
}
.cutoff-banner {
  background: var(--tertiary);
  color: var(--dark);
  text-align: center;
  padding: 8px 14px;
  font-weight: 600;
  font-size: 13px;
  position: absolute;
  top: 0;
  z-index: 9;
  width: 100%;
  padding: 12px 14px;
}

.tabs {
  display: flex;
  border-bottom: 1px solid var(--line);
  background: var(--white);
}
.tabs button {
  flex: 1;
  background: none;
  border: none;
  padding: 12px;
  font-weight: 600;
  color: var(--text-subtle);
  border-bottom: 3px solid transparent;
}
.tabs button.active {
  color: var(--primary);
  border-color: var(--primary);
}

/* >=769px: Live Chat becomes a permanent sidebar instead of a tab.
   grid-template-areas stacks video+market left, chat spans both rows
   right. 5fr/2fr split, widening to 2fr/1fr above 1024px. */
@media (min-width: 769px) {
  #topLevelTabs {
    display: none;
  }

  .live-wrap {
    display: grid;
    grid-template-columns: 5fr 2fr;
    grid-template-rows: auto 1fr;
    grid-template-areas:
      "video chat"
      "market chat";
    column-gap: 15px;
    padding: 15px;
    /* Fixed to the space below topbar / above sticky-bar; never scrolls. */
    height: calc(100vh - var(--topbar-h, 71px) - var(--sticky-bar-h, 64px));
    overflow: hidden;
  }
  /* min-width:0 overrides grid's default min-width:auto so each track
     respects its fr share instead of growing to fit its content
     (#marketPanel's horizontally-scrollable grower tabs). */
  .live-wrap > .video-panel,
  .live-wrap > #marketPanel,
  .live-wrap > #chatPanel {
    min-width: 0;
  }

  /* relative (not sticky/static): .live-wrap doesn't scroll here, but
     these still need to stay a positioned ancestor for children like
     .live-badge. */
  body.live-page .video-panel,
  body.live-page #growerTabs {
    position: relative;
  }

  .live-wrap > .video-panel {
    grid-area: video;
    border-radius: var(--radius) var(--radius) 0 0;
    overflow: hidden;
  }

  /* Fixed-height flex column: grower-tabs natural size, #productGrid
     takes the rest and is the only part that scrolls. !important beats
     the inline display:none the mobile Live Chat tab toggle sets. */
  .live-wrap > #marketPanel {
    grid-area: market;
    display: flex !important;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
  }
  /* flex-shrink:0 stops the default shrink algorithm compressing the
     tabs proportionally along with #productGrid when space is tight. */
  #growerTabs {
    flex-shrink: 0;
  }
  #productGrid {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    align-content: start; /* stops sparse rows stretching to fill flex:1 height */
  }

  /* Fills the full grid area height via the fixed-height live-wrap. */
  .live-wrap > #chatPanel {
    grid-area: chat;
    display: block !important; /* overrides mobile's inline display:none */
    height: 100%;
    min-height: 0;
  }
  .live-wrap > #chatPanel .chat-panel-full {
    height: 100%;
    max-width: none;
    margin: 0;
  }
}
@media (min-width: 1025px) {
  .live-wrap {
    grid-template-columns: 2fr 1fr;
  }
}
.live-wrap ~ footer {
  display: none;
}

/* Horizontally-scrolling pill tabs, matching .portal-nav. */
.tabs-scroll {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding: 10px 16px;
  border-bottom: 1px solid var(--line);
  background: var(--white);
  -webkit-overflow-scrolling: touch;
}
.tabs-scroll button {
  flex: 0 0 auto;
  padding: 8px 14px;
  border-radius: var(--radius-lg);
  background: var(--white);
  border: 1.5px solid var(--line);
  color: var(--dark);
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
}
.tabs-scroll button.active {
  background: var(--dark);
  color: var(--white);
  border-color: var(--dark);
}
.tabs-scroll button.live-tag {
  border-color: var(--highlight);
  color: var(--highlight);
}
.tabs-scroll button.live-tag.active {
  background: var(--highlight);
  border-color: var(--highlight);
  color: var(--white);
}

.product-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
  padding: 12px;
}
@media (min-width: 560px) {
  .product-grid {
    grid-template-columns: repeat(3, 1fr);
    padding: 14px 16px;
  }
}
/* Single full-width product when the "Live" filter tab is active. */
.product-grid.live-filter-active {
  grid-template-columns: 1fr;
}
/* Live-filter single-product layout, tablet width up: .thumb and
   .stock-bar share grid cell 1/1 (bar overlays the image) since they're
   siblings, not nested; .body becomes its own grid for name/grower
   (full width) then price/compare and qty/button (side by side). */
/*@media (min-width: 768px) {*/
  .product-grid.live-filter-active .product-card {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }
  .product-grid.live-filter-active .product-card .thumb {
    grid-column: 1;
    grid-row: 1;
    aspect-ratio: unset;
    height: 0;
    min-height: 100%;
    max-height: unset !important;
  }
  .product-grid.live-filter-active .product-card .stock-bar {
    grid-column: 1;
    grid-row: 1;
    align-self: end;
    margin: 0 0 20px;
    z-index: 2;
  }
  .product-grid.live-filter-active /* The home page's teaser cards are wholly clickable through to the Live
   market. The <a> becomes the card's flex column so .thumb and .body
   stay direct flex items of a column container exactly as they were
   before it was introduced — without this the anchor collapses to an
   inline box and the card loses its layout. Live-page cards are NOT
   wrapped: those hold the option <select> and add-to-basket <button>,
   which must not sit inside a link. */
.product-card > .card-link {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
  color: inherit;
  text-decoration: none;
}
.product-card > .card-link:hover h4 {
  text-decoration: underline;
}
/* Ring the whole card, not just the text line, so the keyboard focus
   indicator matches the clickable area. */
.product-card > .card-link:focus-visible {
  outline: 3px solid var(--dark);
  outline-offset: -3px;
  border-radius: var(--radius);
}

.product-card .body {
    grid-column: 2;
    grid-row: 1;
    display: grid;
    grid-template-areas:
      /*
      "h4 h4"
      "muted muted"
      "price price-compare"
      "qty-row btn-primary";
      */
      "h4 h4"
      "muted muted"
      "price price"
      "price-compare price-compare"
      "qty-row qty-row"
      "btn-primary btn-primary";

    grid-template-columns: 1fr 1fr;
    gap: 4px 14px;
    padding: 10px 12px 12px;
  }
  /* Direct-child selectors: h4 contains a nested .muted (weight) span
     that must not be pulled into its own grid area. */
  .product-grid.live-filter-active .product-card .body > h4 {
    grid-area: h4;
  }
  .product-grid.live-filter-active .product-card .body > .muted {
    grid-area: muted;
  }
  .product-grid.live-filter-active .product-card .body > .price,
  .product-grid.live-filter-active .product-card .body > .option-select {
    grid-area: price;
  }
  .product-grid.live-filter-active .product-card .body > .price-compare {
    grid-area: price-compare;
  }
  .product-grid.live-filter-active .product-card .body > .qty-row {
    grid-area: qty-row;
  }
  .product-grid.live-filter-active .product-card .body > .add-btn {
    grid-area: btn-primary;
  }
/*}*/
@media (min-width: 768px) {
  .product-grid {
    padding: 15px 0 0;
  }
}
.product-card {
  background: var(--white);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: clip;
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.product-card .thumb {
  aspect-ratio: 4/3;
  background: linear-gradient(135deg, var(--tertiary), var(--secondary));
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--white);
  font-size: 12px;
  position: relative;
  overflow: hidden;
}
@media (max-width: 640px) {
  .product-card .thumb {
    aspect-ratio: 16/9;
  }
}
.product-grid.live-filter-active .product-card .thumb {
  aspect-ratio: unset;
}
.product-card .thumb .thumb-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Single positioned flex wrapper for both badges — lets one collapse
   cleanly (e.g. live-tag hidden on the Live-filter view) without
   leaving a gap, which independent position:absolute tags can't do. */
.thumb .tag-holder {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 2;
  display: flex;
  gap: 8px;
}
.thumb .tag-holder .featured-tag,
.thumb .tag-holder .live-tag {
  position: static;
}
/* .live-tag is now a real <button> (was a <span>) so it gets native
   keyboard/click activation for free — these resets exist purely to
   cancel out the browser's default button chrome (border, margin,
   platform-specific appearance) that would otherwise show through,
   since .live-tag itself never had a border to begin with. */
.thumb .tag-holder .live-tag {
  border: none;
  margin: 0;
  appearance: none;
  -webkit-appearance: none;
}
.product-card .featured-tag {
  background: var(--white);
  color: var(--primary);
  font-size: 10px;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: var(--radius-lg);
}
.product-card .live-tag {
  background: var(--highlight);
  color: var(--white);
  font-size: 10px;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: var(--radius-lg);
  text-transform: uppercase;
}

.stock-bar {
  height: 7px;
  background: rgb(from var(--dark) r g b / 30%);
  margin-top: -7px;
  position: relative;
  z-index: 1;
  width: 100%;
}
.stock-bar .stock-fill {
  display: block;
  height: 100%;
  background: var(--highlight);
  transition: width 0.4s ease;
  position: relative;
}
.stock-bar .stock-count {
  position: absolute;
  right: 0;
  font-size: 10px;
  background: var(--highlight);
  border-radius: 18px;
  color: var(--white);
  width: 24px;
  height: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  line-height: 10px;
  z-index: 1;
  transform: translate(23px, -6px);
}
.low-stock.stock-bar .stock-fill {
  background: var(--danger-text);
}
.low-stock.stock-bar .stock-count {
  background: var(--danger-text);
}

.product-card .body {
  padding: 10px 12px 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1;
}
.product-card h4 {
  font-size: 14px;
  margin: 0;
}
.product-card .muted {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  width: 100%;
}
.product-card .price {
  font-weight: 700;
  color: var(--primary);
  font-size: 17px;
  padding: 5px 0;
}
.price-compare {
  line-height: 16.5px;
}
.product-card .was {
  font-size: 11px;
  color: var(--text-subtle);
  text-decoration: line-through var(--strikethrough);
  display: inline;
  margin-right: 5px;
}
.product-card .save {
  font-size: 11px;
  color: var(--success-text);
  font-weight: 600;
  display: inline-block;
}

.qty-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 6px;
}
.qty-row button {
  width: 36px;
  max-width: 50px;
  height: 36px;
  border-radius: var(--radius-lg);
  border: 1.5px solid var(--line);
  background: rgb(from var(--line) r g b / 30%);
  font-weight: 700;
  transition: background-color 0.15s ease, color 0.15s ease,
    border-color 0.15s ease;
  display: flex;
  align-items: center;
  justify-content: center;
}
.qty-row button:active {
  background: var(--secondary);
  color: var(--white);
  border-color: var(--secondary);
}
.qty-row button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.qty-row .qty-val {
  min-width: 20px;
  text-align: center;
  font-weight: 600;
}
@media (max-width: 640px) {
  .qty-row {
    justify-content: space-between;
  }
  .qty-row button {
    flex: 1 1 auto;
  }
}
.add-btn {
  margin-top: 6px;
}

.chat-panel-full {
  display: grid;
  grid-template-rows: 1fr auto auto;
  background: var(--white);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  max-width: 640px;
  margin: 16px auto;
}
/* Mobile: chat fills the space between sticky head and sticky-bar.
   --sticky-head-h / --sticky-bar-h set by live.js layoutStickyStack(). */
@media (max-width: 640px) {
  .chat-panel-full {
    margin: 0;
    max-width: none;
    border-radius: 0;
    box-shadow: none;
    height: calc(
      100vh - var(--sticky-head-h, 300px) - var(--sticky-bar-h, 64px)
    );
  }
}
.chat-messages {
  flex: 1;
  overflow-y: auto;
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.chat-msg {
  background: var(--line);
  border-radius: var(--radius-sm);
  padding: 8px 12px;
  font-size: 14px;
  max-width: 85%;
  width: fit-content;
  align-self: flex-start;
}
.chat-msg.host {
  background: var(--highlight);
  color: var(--white);
  align-self: flex-start;
  font-weight: 600;
}
.chat-msg.own {
  align-self: flex-end;
  background: var(--light);
}
.chat-msg.own .name {
  display: none;
}
.chat-msg .name {
  font-weight: 700;
  font-size: 11px;
  display: block;
  margin-bottom: 2px;
}
.chat-input {
  display: flex;
  gap: 6px;
  padding: 0 10px 10px; /* padding:10px; border-top:1px solid var(--line); */
}
.chat-input input {
  flex: 1;
  min-width: 0;
}
.emoji-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 10px;
  border-top: 1px solid var(--line);
}
.emoji-row button {
  background: var(--light);
  border: none;
  border-radius: 8px;
  font-size: 18px;
  padding: 4px 8px;
}

.basket-panel {
  padding: 14px;
}
.basket-item {
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
  font-size: 14px;
}
.basket-item-top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
}
.basket-item .name {
  font-weight: 600;
}
.basket-item .sub {
  color: var(--text-subtle);
  font-size: 12px;
}
.basket-item-qty-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 4px;
}
.basket-item-qty-row .qty-row {
  justify-content: flex-start;
  margin-top: 0;
}
.basket-total-row {
  display: flex;
  justify-content: space-between;
  padding-top: 12px;
  font-weight: 700;
  font-size: 16px;
}
.min-order-note {
  font-size: 12px;
  color: var(--text-subtle);
  margin-top: 6px;
}

/* Basket drawer: hidden by default, slides up above tab content but
   below the sticky-bar's z-index. Full-width for the slide transform,
   capped/centred by max-width + auto margins. */
.basket-backdrop {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: var(--sticky-bar-h, 64px);
  background: var(--overlay-dark);
  z-index: 65;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}
.basket-backdrop.open {
  opacity: 1;
  pointer-events: auto;
}
.basket-drawer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: var(--sticky-bar-h, 64px);
  max-width: 768px;
  margin-left: auto;
  margin-right: auto;
  background: var(--white);
  border-radius: 16px 16px 0 0;
  box-shadow: 0 -12px 30px var(--overlay-dark-soft);
  z-index: 70;
  max-height: 75vh;
  overflow-y: auto;
  /* 140% (not 100%) fully clears the drop-shadow and the sticky-bar. */
  transform: translateY(140%);
  transition: transform 0.3s ease;
}
.basket-drawer.open {
  transform: translateY(2px);
}
.basket-drawer-handle {
  width: 40px;
  height: 4px;
  background: var(--line);
  border-radius: var(--radius-lg);
  margin: 10px auto 0;
}

/* position:sticky (not fixed) so it scrolls away with the footer at
   page end. Full-bleed background; content capped at 1280px via a
   nested .container (wider here only, not the shared 1100px default). */
.sticky-bar {
  position: sticky;
  bottom: 0;
  background: var(--white);
  border-top: 1px solid var(--line);
  padding: 14px 0;
  padding-bottom: calc(14px + env(safe-area-inset-bottom));
  z-index: 75;
  box-shadow: 0 -6px 20px var(--shadow-color-soft);
}
.sticky-bar .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  max-width: 1280px;
}
.sticky-bar .total {
  font-weight: 700;
}
.sticky-bar .cutoff {
  font-size: 11px;
  color: var(--primary);
  font-weight: 600;
}

/* ---------- generic cards / tables (admin & grower portals) ---------- */
.card {
  background: var(--white);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 20px;
  /* Symmetrical, not just below. margin-bottom alone left the first card
     on a page sitting hard under whatever was above it — a banner, the
     nav — while every gap between cards was 18px. Adjacent margins
     collapse, so this does not double the spacing between them. */
  margin: 18px 0;
}
/* The courier run sheet jumps to the active stop after each delivery.
   Without this the card lands flush against the top of the viewport,
   under the sticky banner; 18px matches the gap between cards. */
.not-open-yet {
  margin: 10px 0 0;
  padding: 9px 10px;
  text-align: center;
  font-size: 13px;
  line-height: 1.35;
  color: var(--muted-text, #6b6b6b);
  background: var(--light, #f5f4ef);
  border-radius: var(--radius-sm, 6px);
}
.card-error {
  margin: 8px 0 0;
  font-size: 13px;
  line-height: 1.4;
  color: var(--danger-text, #b3261e);
}
[data-next-stop],
#next-stop {
  /* Cleared below the sticky .topbar, not just below the viewport edge.
     18px alone put the card's heading 38px BEHIND the bar: the bar is
     ~71px tall and a fragment jump lands its target at scroll-margin-top
     from the viewport's own edge, which is underneath it.

     --topbar-h is measured and set by courier/run-detail.php, because the
     bar's height is 10px + the brand mark + 10px + env(safe-area-inset-top)
     — a notch changes it, and CSS cannot add that up here. The fallback is
     a phone without one, so the offset is close even if the script never
     runs. */
  scroll-margin-top: calc(var(--topbar-h, 71px) + 18px);
}
.table-wrap {
  overflow-x: auto;
}
table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}
th,
td {
  text-align: left;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
th {
  color: var(--text-muted);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.badge {
  display: inline-block;
  padding: 3px 10px;
  border-radius: var(--radius-lg);
  font-size: 11px;
  font-weight: 700;
}
.badge-green {
  background: var(--success-bg);
  color: var(--success-text);
}
.badge-yellow {
  background: var(--warning-bg);
  color: var(--warning-text);
}
.badge-red {
  background: var(--danger-bg);
  color: var(--danger-text);
}
.badge-grey {
  background: var(--badge-neutral-bg);
  color: var(--badge-neutral-text);
}
.dash-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}
@media (min-width: 700px) {
  .dash-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}
.stat-card {
  background: var(--white);
  border-radius: var(--radius);
  padding: 18px;
  box-shadow: var(--shadow);
}
.stat-card .value {
  font-size: 26px;
  font-weight: 800;
  color: var(--primary);
}
.stat-card .label {
  font-size: 12px;
  color: var(--text-subtle);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.portal-nav {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  padding: 14px 0;
  border-bottom: 1px solid var(--line);
  margin-bottom: 20px;
}
.portal-nav a {
  padding: 8px 14px;
  border-radius: var(--radius-lg);
  background: var(--white);
  border: 1.5px solid var(--line);
  color: var(--dark);
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
}
.portal-nav a.active,
.portal-nav a:hover {
  background: var(--dark);
  color: var(--white);
  border-color: var(--dark);
}

/* ---------- footer ---------- */
footer {
  background: var(--dark);
  color: var(--footer-muted);
  padding: 34px 0;
}
footer .container {
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: center;
  text-align: center;
}
footer a {
  color: var(--white);
}
.footer-brand {
  font-family: var(--font-display);
  font-size: 22px;
  color: var(--white);
}

/* ---------- utility ---------- */
.mt-0 {
  margin-top: 0;
}
.mb-0 {
  margin-bottom: 0;
}
.text-center {
  text-align: center;
}
.text-right {
  text-align: right;
}
.strong-col {
  font-weight: 700;
  color: var(--dark);
}

/* ---------- grower/order-detail.php & invoice.php ---------- */
tfoot td {
  border-bottom: none;
  border-top: 1px solid var(--line);
  white-space: nowrap;
}
tfoot .totals-row td {
  border-top: 2px solid var(--dark);
  padding-top: 12px;
  font-size: 15px;
}
.remit-card {
  position: relative;
  overflow: hidden;
}
.remit-stamp-anchor {
  position: relative;
}
.remittance-stamp {
  position: absolute;
  top: 2%;
  left: 4%;
  transform: rotate(-16deg);
  transform-origin: top left;
  z-index: 2;
  opacity: 0.4;
  color: var(--danger-text);
  pointer-events: none;
  user-select: none;
}
.remittance-stamp-text {
  font-size: 58px;
  font-weight: 800;
  letter-spacing: 0.05em;
  line-height: 1;
  white-space: nowrap;
}
.remittance-stamp-date {
  font-size: 20px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-align: right;
  margin-top: -6px;
  white-space: nowrap;
}
@media (max-width: 600px) {
  .remittance-stamp-text {
    font-size: 34px;
  }
  .remittance-stamp-date {
    font-size: 14px;
    margin-top: -3px;
  }
}

/* ---------- invoice.php ---------- */
.invoice-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
  flex-wrap: wrap;
  padding-bottom: 16px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--line);
}
.invoice-company {
  margin: 0;
  font-size: 22px;
}
.invoice-tag {
  color: var(--secondary);
  font-weight: 800;
  font-size: 13px;
  letter-spacing: 0.08em;
  border: 1.5px solid var(--secondary);
  border-radius: var(--radius-sm);
  padding: 4px 10px;
  white-space: nowrap;
}
.invoice-meta {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
  margin-bottom: 20px;
  font-size: 14px;
}
@media (min-width: 560px) {
  .invoice-meta {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* Print / "Download PDF": only the invoice card renders — nav, footer,
   page actions and any other page chrome are hidden so the saved PDF
   is just the document itself, not a screenshot of the whole site. */
@media print {
  .topbar, footer, .no-print, .skip-link {
    display: none !important;
  }
  body {
    background: var(--white);
  }
  main {
    padding: 0 !important;
    margin: 0 !important;
  }
  .invoice-print-area {
    box-shadow: none !important;
    border: none !important;
    padding: 0 !important;
  }
  .remittance-stamp {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
}


/* ---------- admin/live-product.php ---------- */
.live-product-option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-top: 1px solid var(--line);
  cursor: pointer;
}
.live-product-option:first-of-type {
  border-top: none;
}
.live-product-option input {
  width: auto;
}
.live-product-none {
  background: var(--shadow-color-soft);
}
.flex {
  display: flex;
}
.items-center {
  align-items: center;
}
.justify-between {
  justify-content: space-between;
}
.gap-8 {
  gap: 8px;
}
.gap-16 {
  gap: 16px;
}
/* Shorthand for the always-together "flex justify-between items-center". */
.flex-between {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.muted {
  color: var(--text-subtle);
  font-size: 13px;
}
.empty-state {
  text-align: center;
  padding: 50px 20px;
  color: var(--text-subtle);
}

/* ---------- admin/calculate-runs.php: drag-to-reorder stop list ---------- */
.stop-item {
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
}
.drag-handle {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border: none;
  background: transparent;
  border-radius: 6px;
  color: var(--text-subtle);
  cursor: grab;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}
.drag-handle:hover,
.drag-handle:focus-visible {
  background: var(--shadow-color-soft);
  color: var(--dark);
  outline: none;
}
.drag-handle:active {
  cursor: grabbing;
}
/* The gap left behind while an item is being dragged — other stops
   slide to close around it, same visual language as jQuery UI
   Sortable's placeholder. */
.drag-placeholder {
  border: 2px dashed var(--secondary);
  border-radius: var(--radius-sm);
  background: var(--shadow-color-soft);
  box-sizing: border-box;
  margin: 4px 0;
}
/* The item itself, detached from flow and following the pointer. */
.drag-ghost {
  position: fixed;
  z-index: 1000;
  pointer-events: none;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.2);
  padding: 8px 12px;
  box-sizing: border-box;
}

/* ---------- invoice.php: Request Refund modal ---------- */
.icon-btn-refund {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  margin-right: 6px;
  border: none;
  background: var(--primary);
  color: var(--white);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.icon-btn-refund:hover,
.icon-btn-refund:focus-visible {
  background: var(--primary-hover);
  outline: none;
}
/* Reserves the same width the icon button would take up, wherever it's
   not shown (already requested, or the order's past the refund window)
   — so the product name still lines up under names on rows that do
   have the button, rather than jumping left. */
.icon-btn-refund-spacer {
  display: inline-block;
  width: 26px;
  margin-right: 6px;
}
.modal-overlay {
  position: fixed;
  inset: 0;
  background: var(--overlay-dark);
  z-index: 1200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}
.modal-box {
  background: var(--white);
  border-radius: var(--radius-sm);
  padding: 24px;
  width: 100%;
  max-width: 440px;
  max-height: calc(100vh - 32px);
  overflow-y: auto;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.25);
}
.modal-box h3 {
  margin-top: 0;
}

/* ---------- courier run-detail.php: direction buttons ---------- */
.btn-directions {
  background: var(--badge-neutral-bg);
  flex: 1 1 0;
}
.btn-directions img {
  width: 18px;
  height: 18px;
}
@media (min-width: 700px) {
  .btn-directions {
    flex: 0 0 auto;
    width: auto;
  }
}


/* ---------- footer legal links (includes/footer.php) ----------
   Sits on the second line of the copyright paragraph. The separators
   are their own spans rather than a CSS ::after so they can carry
   aria-hidden — a screen reader announcing "pipe" between every link
   is noise, but a bare "|" glyph in the markup can't be hidden. */
.footer-legal {
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 4px 12px;
  font-size: 12px;
  margin-top: 4px;
}
.footer-legal span {
  opacity: 0.4;
}
.footer-legal a {
  color: var(--text-subtle);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.footer-legal a:hover {
  color: var(--white);
}

/* ---------- legal pages (privacy-policy, disclaimer, terms-of-use) ----------
   .container is 1100px wide, which is right for product grids and
   admin tables but far too wide to read a paragraph across. These
   pages cap their own measure at ~68 characters and set a looser
   line-height, since they're the only long-form reading on the site. */
.legal {
  max-width: 42rem;
  margin: 32px auto 56px;
  background: var(--white);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 40px;
  line-height: 1.7;
}
@media (max-width: 600px) {
  .legal {
    padding: 24px 20px;
    margin: 16px auto 32px;
  }
}
.legal-head {
  border-bottom: 1px solid var(--line);
  padding-bottom: 16px;
  margin-bottom: 24px;
}
.legal h1 {
  font-size: 30px;
  margin-bottom: 6px;
}
.legal h2 {
  font-size: 20px;
  margin-top: 40px;
  padding-top: 20px;
  border-top: 1px solid var(--line);
}
.legal h3 {
  font-size: 16px;
  margin-top: 28px;
}
/* First heading after the intro shouldn't stack its own top rule
   against the header's bottom rule. */
.legal h2:first-of-type {
  margin-top: 32px;
}
.legal-meta {
  color: var(--text-subtle);
  font-size: 13px;
  margin: 0;
}
.legal-lead {
  font-size: 17px;
  color: var(--dark);
}
.legal p,
.legal li {
  color: var(--text-muted);
}
.legal ul {
  margin: 0 0 1em;
}
.legal li {
  margin-bottom: 8px;
}
.legal strong {
  color: var(--dark);
}
.legal a {
  text-decoration: underline;
  text-underline-offset: 2px;
}
/* Pulled-out passage for the things a reader must not miss — the
   "we never sell your data" promise, the ACL guarantees, the
   copyright line. Deliberately the only decorated block on the page. */
.legal-callout {
  border-left: 3px solid var(--primary);
  background: var(--light);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  padding: 14px 18px;
  margin: 20px 0;
}
.legal-summary {
  background: var(--light);
  border-radius: var(--radius-sm);
  padding: 16px 16px 16px 38px;
  list-style: disc;
}
.legal-related {
  margin-top: 40px;
  padding-top: 20px;
  border-top: 1px solid var(--line);
  font-size: 14px;
}

/* ---------- honeypot field (support.php) ----------
   Hidden from people without display:none or visibility:hidden, both
   of which the bots this catches already know to check for. */
.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ---------- consent checkbox row (register.php, profile.php) ----------
   The global input rule sets width:100% for text fields, which would
   stretch a checkbox across the form; width:auto puts it back to its
   native size. The label sits beside the box rather than above it, so
   it overrides the global label rule's block display and top margin.
   align-items:flex-start keeps the box aligned to the FIRST line when
   the label wraps to two lines on a narrow screen, rather than
   floating to the vertical middle of the text. */
.checkbox-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-top: 18px;
}
.checkbox-row input[type="checkbox"] {
  /* Explicit size overrides the global width:100% that the shared input
     rule applies to form fields; native checkboxes also render small, so
     18px is both the reset and a comfortable tap target. */
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  margin: 1px 0 0;
  accent-color: var(--primary);
}
.checkbox-row label {
  display: inline;
  margin: 0;
  font-weight: 400;
  font-size: 14px;
  line-height: 1.4;
  cursor: pointer;
}

/* ---------- spambot-resistant email links (includes/functions.php) ----------
   These are rendered WITHOUT an href — assets/js/anti-spambot.js adds it
   after decoding the address — so the `a` element rule that colours real
   links doesn't apply until then. Styling the class directly keeps them
   looking and behaving like links from first paint, with no flash of
   restyled text when the script runs. */
.anti-spambot {
  color: var(--primary);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
  /* An address is one token: break it mid-string rather than let it
     push the layout wide on a narrow screen. */
  word-break: break-word;
}

/* ---------- offline banner + pending basket state ----------
   (assets/js/connection.js, assets/js/live.js) */
.net-banner {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 2000; /* above .topbar (1000) and the basket drawer backdrop */
  align-items: center;
  gap: 10px;
  padding: 10px 16px;
  padding-top: calc(10px + env(safe-area-inset-top));
  background: var(--warning-bg);
  color: var(--warning-text);
  font-size: 14px;
  line-height: 1.35;
  box-shadow: 0 2px 8px var(--shadow-color);
}
.net-banner.is-visible {
  display: flex;
}
.net-banner-text {
  flex: 1;
}
.net-banner-dot {
  flex-shrink: 0;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--danger-text);
  /* A slow pulse reads as "still trying" without being a distraction.
     Anyone who has asked not to see motion gets a static dot instead. */
  animation: netPulse 2s ease-in-out infinite;
}
@keyframes netPulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}
@media (prefers-reduced-motion: reduce) {
  .net-banner-dot { animation: none; }
}
.net-banner-retry {
  flex-shrink: 0;
  border: 1px solid currentColor;
  background: transparent;
  color: inherit;
  font-weight: 700;
  font-size: 13px;
  padding: 5px 12px;
  border-radius: var(--radius-lg);
}
.net-banner-retry:disabled {
  opacity: 0.6;
}
/* The banner is fixed, so push the page down by roughly its height
   rather than letting it cover the topbar. */
body.is-offline {
  padding-top: 46px;
}

/* Products with unsent changes. Deliberately not styled as a success
   state — nothing has been reserved yet. */
.product-card.is-pending {
  outline: 2px dashed var(--warning-text);
  outline-offset: -2px;
}
.pending-badge {
  margin-top: 6px;
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  background: var(--warning-bg);
  color: var(--warning-text);
  font-size: 11px;
  font-weight: 700;
  text-align: center;
}
.pending-note {
  margin-bottom: 12px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: var(--warning-bg);
  color: var(--warning-text);
  font-size: 13px;
  line-height: 1.45;
}
.sync-result {
  position: relative;
  margin: 12px 0;
  padding: 12px 36px 12px 14px;
  border-radius: var(--radius-sm);
  font-size: 14px;
  line-height: 1.45;
}
.sync-result p {
  margin: 0 0 6px;
}
.sync-result p:last-of-type {
  margin-bottom: 0;
}
.sync-ok {
  background: var(--success-bg);
  color: var(--success-text);
}
.sync-warn {
  background: var(--warning-bg);
  color: var(--warning-text);
}
.sync-result-close {
  position: absolute;
  top: 6px;
  right: 8px;
  border: none;
  background: transparent;
  color: inherit;
  font-size: 20px;
  line-height: 1;
  padding: 2px 6px;
}
/* Checkout blocked while offline or while changes are unsent — it needs
   a server-side basket, and the pending items aren't in one yet. */
.btn.is-disabled {
  opacity: 0.55;
  pointer-events: none;
  cursor: not-allowed;
}

/* =====================================================================
   Delivery-location map (register.php / profile.php)
   ===================================================================== */

/* Same max-height slide as .buying-options-panel, for the same reason:
   CSS cannot animate height:auto. On the profile the JS opens it on load,
   so it is only ever animated on the register form.

   max-height is set to "none" by the JS once the transition finishes —
   a fixed pixel value would clip the status line the moment it wraps to
   two lines, or the map when fullscreen exits. */
.address-map {
  overflow: hidden;
  max-height: 0;
  transition: max-height 500ms ease;
  /* overflow:hidden is what makes the slide work, and it also clips the
     canvas and button shadows against the panel's bottom edge. A little
     room below keeps them whole. */
  padding-bottom: 1rem;
}
.address-map-inner {
  border-top: 1px solid var(--line);
  margin-top: 18px;
  padding-top: 14px;
}
.address-map-title {
  font-size: 13px;
  font-weight: 700;
  margin: 0 0 8px;
}

/* A fixed height rather than an aspect-ratio: the useful thing here is
   roughly two streets in either direction, which is a height question,
   not a proportion of the form's width. */
.address-map-canvas {
  height: 280px;
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
  background: var(--line);   /* grey while tiles load, not white flash */
}

.address-map-status {
  font-size: 13px;
  margin: 10px 0 4px;
  min-height: 1.2em;         /* reserve the line so nothing jumps */
}
.address-map-status.is-warn  { color: var(--primary-ink, #B75A09); }
.address-map-status.is-error { color: #B3261E; font-weight: 600; }
.address-map-status.is-ok    { color: #2F6F4F; }

/* Confirmed is a state, not a disabled button — it stays pressable so
   someone can re-confirm after a nudge without hunting for a reset. */
.address-map-confirm {
  margin-top: 10px;
  /* Half width. Full-bleed reads as the form's primary action, which it
     is not — the shopper is agreeing the pin looks right, not
     submitting. */
  width: 50%;
}
.address-map-confirm.is-confirmed {
  background: #2F6F4F;
  border-color: #2F6F4F;
}

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

/* Shorter on a phone: 280px of map plus the form above it leaves nothing
   of the page visible, and the pin is what matters, not the context. */
@media (max-width: 560px) {
  .address-map-canvas { height: 220px; }
}
