/* ============================================================
   SPORTOPOTTY - Site-wide stylesheet
   Design tokens copied 1:1 from the approved app.php design
   (porcelain / ink / mint palette, SF Pro Rounded display).
   Loaded by includes/header.php on every page.
   ============================================================ */

:root {
  --porcelain: #F2F6F7;
  --tile:      #E4EDEF;
  --ink:       #14252B;
  --ink-soft:  #5B7079;
  --mint:      #15B7A1;
  --mint-deep: #0E8C7C;
  --amber:     #E8A33D;
  --clay:      #8B5E3C;
  --white:     #FFFFFF;
  --line:      rgba(20,37,43,.10);
  --line-2:    rgba(20,37,43,.16);
  --danger:    #D9534F;
  --warn:      #b9791b;
  /* Caution yellow — "No Bathroom Found" pins and everything that
     explains them. Deliberately brighter and greener than --amber
     (#E8A33D) and than the premium-business gold (#E1A100): those two
     are decoration on a listing, this one is a warning, and at 40px on a
     phone in sunlight the difference has to survive. --caution-ink is
     the only text colour that clears 4.5:1 on it. */
  --caution:     #FFD21E;
  --caution-ink: #5C4300;
  --caution-bg:  #FFF8DC;
  /* Mint tint — the calm counterpart to --caution. Used by the two places
     that say "this is a business" without asking for anything: the note in
     the Drop-a-Pin form and the unclaimed-listing card on a pin page.
     Amber in this app means "hold on a second" (the duplicate note, the
     no-bathroom note), so neither of those could borrow it without reading
     as a problem to solve. --mint-ink is the text colour that clears 4.5:1
     on --mint-pale; --mint-deep does not at 13px. */
  --mint-pale: #E4F4EF;
  --mint-line: #7FC9B5;
  --mint-ink:  #0B5F54;
  --ok:        #0E8C7C;
  --shadow:    0 12px 40px rgba(20,37,43,.18);
  --radius:    18px;
  --tap:       48px;
  --font-display: ui-rounded, "SF Pro Rounded", "Segoe UI", Roboto, system-ui, sans-serif;
  --font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, system-ui, sans-serif;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  overflow-x: hidden;   /* full-bleed hero uses viewport-width margins */
  font-family: var(--font-body);
  background: var(--porcelain);
  color: var(--ink);
  line-height: 1.6;
}
h1, h2, h3 { font-family: var(--font-display); letter-spacing: -.02em; }

/* ---------- iOS: never let Safari zoom the page on a field tap ----------
   Mobile Safari zooms the viewport whenever a form field is focused whose
   font-size is below 16px, and it does NOT zoom back out when the field is
   blurred. Every field on this site was between 13 and 15px, so the first
   tap on the map's search box left the whole document wider than the screen
   — which is why the hamburger and the search button ended up sliced off at
   the right edge, unreachable behind `body { overflow-x: hidden }`.

   16px is the threshold, not a preference: 15.9px still zooms. It is applied
   only at touch widths so the desktop type scale below is untouched.

   The alternative fix — `maximum-scale=1` in the viewport meta — also kills
   pinch-zoom for everybody, including people with low vision who need it.
   Never do that; make the fields big enough instead. */
@media (max-width: 860px) {
  input, select, textarea,
  .sp-search input[type="search"],
  .sp-search-radius,
  .results-meta select,
  .filter-group select {
    font-size: 16px;
  }
}

/* `overflow-x: hidden` on body alone does not stop a horizontal scroll in
   every mobile browser, and where it applies it CLIPS the overflow rather
   than letting you reach it. `clip` on the root is the modern half of the
   pair — it has no effect on position:sticky (which `hidden` can break) and
   it is a backstop, not the fix: the real causes are the ones above. */
html { overflow-x: clip; }

/* ---------- Header (sticky, frosted porcelain — matches .site-head) ----------
   Two rows: .header-row-main (brand + site nav) and
             .header-row-user (page tools + Map/Profile/Logout).            */
.site-header {
  position: sticky; top: 0; z-index: 1100; /* above Leaflet panes/controls (max ~1000) */
  padding: 0;
  background: rgba(242,246,247,.86);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
.header-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 12px;
  padding: 10px 18px;
}
.header-row-user {
  border-top: 1px solid var(--line);
  padding-top: 8px; padding-bottom: 8px; gap: 8px;
}
.header-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 0; }
.header-tools:empty { display: none; }
.user-nav { display: flex; align-items: center; gap: 4px; margin-left: auto; flex-wrap: wrap; }
.user-nav a {
  display: inline-block; padding: 8px 12px; border-radius: 10px;
  font-size: 13.5px; font-weight: 700; color: var(--ink-soft);
  text-decoration: none; white-space: nowrap; min-height: var(--tap); line-height: 30px;
}
.user-nav a:hover { color: var(--ink); background: var(--tile); }
.user-nav a.active { color: var(--ink); background: var(--tile); }
/* The leading emoji is decoration, not content — a screen reader announcing
   "world map Map" / "office building Business" is what happens when it isn't
   marked aria-hidden. It gets its own span rather than a plain space so the
   gap between glyph and label is a number we can tighten on small screens. */
.user-nav .un-i { margin-right: 6px; font-size: 15px; line-height: 1; }
/* "Drop a Pin" rides at the head of this row (map.php $navTools -> header.php).
   It is a button among links, so it needs the sizing the links get for free:
   fixed square, never squeezed by flex, and a little air before "Map" so the
   two do not read as one control. */
.user-nav .nav-pin-btn { flex: 0 0 auto; margin-right: 6px; }
/* Mid-flow the button grows a text instruction ("tap the exact spot…").
   Uncapped, that sentence is wider than a phone and shoves the rest of the
   row onto a second line. Capped, it stays a hint and the row holds. */
.user-nav .nav-pin-btn .btn-icon-label {
  max-width: 46vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.user-nav a.user-logout, .user-nav a.user-login {
  background: var(--mint); color: #04231f;
  font-family: var(--font-display); font-weight: 800;
  padding: 8px 16px; border-radius: 11px;
}
.user-nav a.user-logout:hover, .user-nav a.user-login:hover { filter: brightness(.97); }

/* ---------- Where Profile / Admin live ----------
   One rule, two homes, never both at once:
     < 860px  the hamburger menu (menu.php .nav-account-mobile)
     >= 860px the account row     (header.php .user-nav a.un-desk)
   860px is the width the primary nav already switches at, so the account
   links move at the same moment the hamburger appears — which is the only
   moment a user would look for them somewhere else.

   Both copies ship in the HTML on every request. That is deliberate: the
   alternative is PHP guessing the viewport, which it cannot do, and a
   cached page then showing the wrong one. */
.user-nav a.un-desk { display: none; }
@media (min-width: 860px) {
  .user-nav a.un-desk { display: inline-block; }
  .nav-account-mobile { display: none; }
}

/* Below 860px every remaining link in the account row is .un-desk and
   therefore hidden, so the row holds nothing unless the page put something in
   $headerTools. An empty row is not nothing on screen — it is a 1px rule and
   16px of padding, a grey stripe under the logo that reads as a component
   that failed to load.

   :has() asks the question PHP cannot: the header is rendered once and cached
   without knowing the viewport, so the decision has to be made in CSS. In a
   browser too old for :has() the stripe simply comes back — the layout is
   correct either way. */
@media (max-width: 859px) {
  .header-row-user:not(:has(.header-tools > *)) { display: none; }
}

/* ---------- Bottom tab bar (includes/footer.php) ----------
   Map / Teams / Profile, fixed to the bottom edge on phones, where a thumb
   reaches without the hand re-gripping the device. Above 860px it is gone and
   the header nav does the job — the same breakpoint everything else in this
   stylesheet switches at.

   Fixed rather than a row in the flex column, because it has to behave the
   same on the map page (a 100dvh flex column that never scrolls) and on every
   ordinary scrolling page. The space it occupies is paid for by padding on
   the body, so nothing is ever underneath it. */
.tab-bar { display: none; }

@media (max-width: 859px) {
  .tab-bar {
    /* 1060 is chosen, not arbitrary. It must beat Leaflet's panes (~1000)
       and the map's own List/Map toggle (1050), and it must LOSE to
       .site-header (1100) — because the search sheet lives inside that
       header, and a full-screen sheet with a navigation bar sitting on top
       of it is not full screen. z-index inside the header is local to the
       header's own context, so the sheet's 1300 cannot beat this on its
       own; the header's 1100 is what carries it. */
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 1060;
    display: flex; align-items: stretch;
    background: rgba(255,255,255,.97);
    backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
    border-top: 1px solid var(--line);
    /* The home-indicator strip on a modern iPhone. Without this the labels
       sit under the swipe area and the row reads as cut off. Resolves to 0
       everywhere else, so it costs nothing on Android or on a desktop. */
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  .tab-bar a {
    flex: 1 1 0; min-width: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 2px; padding: 7px 4px 8px; min-height: 56px;
    text-decoration: none; color: var(--ink-soft);
  }
  .tab-bar a.active { color: var(--mint-deep); }
  .tab-bar a:focus-visible { outline: 3px solid var(--mint); outline-offset: -3px; border-radius: 12px; }
  .tab-i { font-size: 19px; line-height: 1; }
  /* The label is not decoration. An icon-only tab bar asks people to learn
     what a stadium glyph means before they can navigate; the words cost one
     line and remove the guess. */
  .tab-l { font-size: 11px; font-weight: 800; font-family: var(--font-display); letter-spacing: .01em; }

  /* Reserve the bar's height so no page ends underneath it. 56px + the safe
     area, matching the rule above — border-box means this comes out of a
     100dvh page rather than adding to it. */
  body { padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px)); }

  /* ---- Except the map page ----
     body.map-fullscreen is already a 100dvh flex column, so the tab bar does
     not need to float over anything: footer.php prints it as a row in that
     column, between the map and the disclaimer, and the column gives it its
     height. Fixed positioning AND reserved padding would be paying for the
     same 56px twice — the map would lose a strip it never got back.

     The stack ends up map -> tabs -> disclaimer, which is the point: the
     legal line is the last thing on screen instead of a band wedged between
     the map and the navigation. */
  body.map-fullscreen { padding-bottom: 0; }
  body.map-fullscreen .tab-bar { position: static; padding-bottom: 0; }
  /* The disclaimer is the bottom-most element there, so IT clears the home
     indicator now. */
  body.map-fullscreen .site-footer-compact {
    padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
  }
  /* About / Contact / Donate / Terms of Service: every one of them is in the
     hamburger, and two are in the tab bar. On a phone this row was a third
     copy of the navigation, printed in 12px grey under it. The disclaimer is
     the only thing here that is not reachable somewhere better, so it is the
     only thing that stays. */
  body.map-fullscreen .site-footer-compact .footer-links { display: none; }
  body.map-fullscreen .site-footer-compact .footer-disclaimer { margin-bottom: 0; }

  /* Map, Teams and Profile are in the tab bar at this width, so the
     hamburger drops its copies of exactly those three.

     Admin is deliberately NOT in this list. It carries .nav-account-mobile
     but not .nav-in-tabbar, and there is no Admin tab — hiding it here would
     leave an admin on a phone with no route to the admin screens at all,
     since its other copy (.un-desk in the account row) only appears at
     860px and up. */
  .nav-in-tabbar { display: none; }
}

/* ---------- Brand row steps aside while the results are scrolled ----------
   map.js adds .sp-brand-away to <body> once .results-list is scrolled past
   56px, and takes it off again near the top. Worth about 50px of listing on
   a phone — a whole extra result on most screens.

   display:none rather than an animated collapse. The header is a flex item in
   a 100dvh column, so removing it hands its height to the map shell instantly
   and exactly; a height or transform transition would animate against a
   layout that is being recalculated on every frame, and Leaflet would be
   resizing underneath it. Instant and correct beats smooth and approximate.

   Map view is untouched, deliberately: nothing scrolls there, so the row
   could only disappear on a gesture that has no meaning in that view. */
@media (max-width: 859px) {
  body.sp-brand-away .header-row-main { display: none; }
}

/* Phones: keep the whole account row on ONE line.
   Below 860px the row is 📍 Drop a Pin / Map / Teams / Business — Profile and
   Admin have moved into the hamburger (see .un-desk above), which is what
   bought the room. Everything below is the same row spending less on padding,
   gap, and glyph size; the labels and icons all survive, and the 44px
   min-height still clears the tap-target floor.

   The row is deliberately still allowed to wrap rather than overflow — a
   long-labelled state of the pin button ("tap the exact spot…") is the one
   thing that can still push it to two lines, and briefly. */
@media (max-width: 600px) {
  .header-row-user { padding-left: 12px; padding-right: 12px; }
  /* Left-aligned rather than spread edge-to-edge: a user with no business has
     three items, and space-between would strand them at opposite ends of the
     row looking like something failed to load. */
  .user-nav { gap: 2px; width: 100%; margin-left: 0; justify-content: flex-start; }
  .user-nav a { padding: 6px 8px; font-size: 12.5px; min-height: 44px; line-height: 32px; }
  .user-nav .un-i { margin-right: 4px; font-size: 14px; }
  .user-nav .nav-pin-btn { margin-right: 4px; width: 38px; min-width: 38px; min-height: 38px; }
}
/* The narrowest phones still in circulation (320px — an SE on its original
   screen). One more notch down; below this the labels would have to start
   disappearing, and a nav you can't read is worse than one that wraps. */
@media (max-width: 359px) {
  .header-row-user { padding-left: 8px; padding-right: 8px; }
  .user-nav { gap: 0; }
  .user-nav a { padding: 6px 6px; font-size: 12px; }
  .user-nav .un-i { margin-right: 3px; font-size: 13px; }
  .user-nav .nav-pin-btn { margin-right: 3px; width: 36px; min-width: 36px; min-height: 36px; }
}
/* Compact header buttons (map tools row) */
.btn.btn-sm-h { min-height: 40px; padding: 8px 14px; font-size: 13.5px; }
/* Square icon-only button (e.g. My Location crosshair). Keeps the 40px tap
   target the labelled buttons have — an icon button that shrinks to the
   glyph is the classic thing nobody can hit on a phone. */
.btn.btn-icon {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; width: 40px; min-width: 40px; flex: 0 0 auto;
}
.btn.btn-icon svg { display: block; }
/* Same button once JS gives it a label (drop-pin mode): icon keeps its spot,
   text sits beside it and the fixed square width is released. */
.btn-icon-label { margin-left: 8px; }
.btn.btn-sm-h:has(> .btn-icon-label) { display: inline-flex; align-items: center; }
.btn.btn-sm-h > svg { flex: 0 0 auto; }
.brand { display: flex; align-items: center; gap: 8px; text-decoration: none; color: var(--ink); }
/* The logo is a wide wordmark (roughly 5.2:1). Height is the control, not
   width — a fixed width would let a tall header row squash it. auto width plus
   an explicit height keeps the aspect ratio the width/height attributes on the
   tag declare, so the reserved box and the painted box are the same. */
.brand-logo { display: block; height: 30px; width: auto; }
.brand-icon { font-size: 1.35rem; }
.brand-name {
  display: block; font-family: var(--font-display);
  font-weight: 800; font-size: 19px; letter-spacing: -.01em;
}
.brand-tag { display: none; }
.nav-toggle {
  margin-left: auto; background: none; border: 1px solid var(--line-2); color: var(--ink);
  font-size: 1.25rem; border-radius: 10px; padding: 4px 10px; cursor: pointer;
}

/* ---------- Menu (matches .site-nav; lives INSIDE the sticky header) ---------- */
.site-nav { flex-basis: 100%; }
/* `> ul`, not `ul`: these are the drawer's own rules and they were reaching
   into the nested .sub-nav as well — forcing it to display:flex the moment
   the drawer opened, and giving it the drawer's vertical padding. The
   sub-menu owns its own display state (see .has-sub / .is-open below). */
.site-nav > ul { list-style: none; margin: 0; padding: 6px 0; display: none; flex-direction: column; }
.site-nav.open > ul { display: flex; }
.site-nav a {
  display: block; padding: 9px 12px; border-radius: 10px;
  font-size: 14px; font-weight: 600; color: var(--ink-soft);
  text-decoration: none; cursor: pointer;
}
.site-nav a:hover { color: var(--ink); }
.site-nav a.active { color: var(--ink); background: var(--tile); }
.site-nav a.nav-cta {
  background: var(--mint); color: #04231f;
  font-family: var(--font-display); font-weight: 800;
  padding: 9px 16px; border-radius: 11px; text-align: center; margin-top: 4px;
}
.site-nav a.nav-cta:hover { filter: brightness(.97); }

/* ---------- Sub-menu (About ▸ How It Works / Terms of Service) ----------
   Two shapes from one piece of markup, and no JavaScript in either.

   PHONE (the default below): the drawer is already a vertical list you opened
   deliberately, so the children are simply indented under their parent and
   always visible. A disclosure that needed a second tap to reach How It Works
   would be worse than the flat menu this replaced.

   DESKTOP (in the 860px block further down): a dropdown on :hover and on
   :focus-within — the second half is what makes it reachable by keyboard,
   and it is why this needs no script. */
.site-nav .has-sub > a { display: flex; align-items: center; gap: 6px; }

/* The section header for a placeholder like About — a <button>, because it
   goes nowhere and only opens the list below it. Everything here exists to
   make a button look and sit exactly like the <a> elements beside it: the
   browser's default button styling shares none of their font, colour or box. */
.nav-sub-toggle {
  display: flex; align-items: center; gap: 6px; width: 100%;
  padding: 9px 12px; border: 0; border-radius: 10px;
  background: none; cursor: pointer; text-align: left;
  font-family: var(--font-body); font-size: 14px; font-weight: 600;
  color: var(--ink-soft); line-height: inherit;
}
.nav-sub-toggle:hover { color: var(--ink); }
/* Lit for any page inside the section, so standing on Terms of Service never
   leaves the nav with nothing highlighted. */
.nav-sub-toggle.active { color: var(--ink); background: var(--tile); }
.nav-sub-toggle:focus-visible { outline: 3px solid var(--mint); outline-offset: 2px; }

/* The caret is real now — the button genuinely opens and closes the section
   at every width — so it points the way it currently means. */
.sub-caret { font-size: 10px; line-height: 1; opacity: .65; transition: transform .15s ease; }
.has-sub.is-open > .nav-sub-toggle .sub-caret { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .sub-caret { transition: none; } }

/* Phone: main.js adds .is-open at this width on load, so the section is
   already open the moment the drawer is — and the button can still close it
   for the room. One class, .is-open, meaning "expanded" here and "clicked
   open" in the 860px block below.

   No JS-less fallback is needed: the drawer this lives in is itself opened by
   main.js (.site-nav.open), so if that script never runs there is no menu to
   put a sub-menu in. */
.sub-nav { list-style: none; margin: 0; padding: 0; }
.has-sub:not(.is-open) > .sub-nav { display: none; }
.sub-nav a {
  display: block; padding: 8px 12px 8px 26px;
  font-size: 13.5px; font-weight: 600; color: var(--ink-soft);
  text-decoration: none; border-radius: 10px;
}
/* The rule is the indent's reason for existing — it says "these belong to the
   thing above" without needing a word to say it. */
.sub-nav > li { position: relative; }
.sub-nav > li::before {
  content: ""; position: absolute; left: 14px; top: 6px; bottom: 6px;
  width: 1px; background: var(--line-2);
}
.sub-nav a:hover { color: var(--ink); background: var(--tile); }
.sub-nav a.active { color: var(--ink); background: var(--tile); }

/* ---------- Main / layout ---------- */
.site-main { max-width: 980px; margin: 0 auto; padding: 1.25rem 20px 3rem; }

/* ---------- Hero (matches .hero: radial mint gradient) ---------- */
.hero {
  text-align: center; color: #fff;
  background: radial-gradient(120% 90% at 50% -20%, #1dd0b6 0%, var(--mint) 42%, var(--mint-deep) 100%);
  margin: -1.25rem calc(50% - 50vw) 2rem;
  padding: 54px 20px 60px;
}
.hero-icons { font-size: 54px; margin-bottom: 0; line-height: 1.1; }
/* The rotating slot is a fixed-width inline-block. Emoji are not all the
   same width — a lacrosse stick is far narrower than a football — so
   without this the toilet beside it would shuffle left and right on every
   swap, which reads as a glitch rather than an animation. */
.hero-sport {
  display: inline-block; width: 1.15em; text-align: center;
  transition: opacity .28s ease, transform .28s ease;
}
.hero-sport.is-swapping { opacity: 0; transform: translateY(-8px); }
@media (prefers-reduced-motion: reduce) {
  .hero-sport { transition: none; }
  .hero-sport.is-swapping { opacity: 1; transform: none; }
}
.hero h1 {
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(34px, 8vw, 52px); letter-spacing: -.035em;
  margin: 10px auto 6px; line-height: 1.02; color: #fff; max-width: 40rem;
}
.hero-sub {
  font-size: clamp(16px, 4vw, 20px); opacity: .95;
  max-width: 560px; margin: 0 auto; line-height: 1.4; color: #fff;
}

/* Rating scale chips (matches .hero-scale .sw) */
.scale-chips {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: 7px;
  margin: 26px auto 0; max-width: 520px;
}
.scale-chip {
  background: rgba(255,255,255,.16); border: none; border-radius: 13px;
  padding: 11px 4px; backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
  text-align: center; min-width: 0; overflow: hidden;
}
/* Per-score colors: same mint→amber→clay interpolation as app.php scoreColor() */
.scale-chip.s0 { background: linear-gradient(160deg, rgb(21,183,161),  rgba(21,183,161,.8)); }
.scale-chip.s1 { background: linear-gradient(160deg, rgb(105,175,121), rgba(105,175,121,.8)); }
.scale-chip.s2 { background: linear-gradient(160deg, rgb(190,167,81),  rgba(190,167,81,.8)); }
.scale-chip.s3 { background: linear-gradient(160deg, rgb(213,149,61),  rgba(213,149,61,.8)); }
.scale-chip.s4 { background: linear-gradient(160deg, rgb(176,122,60),  rgba(176,122,60,.8)); }
.scale-chip.s5 { background: linear-gradient(160deg, rgb(139,94,60),   rgba(139,94,60,.8)); }
.scale-chip .icons {
  display: block; font-size: clamp(10px, 2.4vw, 13px);
  line-height: 20px; height: 20px; white-space: nowrap;
  letter-spacing: -1.5px; overflow: hidden;
}
.scale-chip .num { display: block; font-family: var(--font-display); font-weight: 800; font-size: 17px; line-height: 1.2; }
.scale-chip .lbl {
  display: block; font-size: 10px; opacity: .92; font-weight: 600;
  letter-spacing: .01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
@media (max-width: 480px) {
  .scale-chips { grid-template-columns: repeat(3, 1fr); max-width: 340px; gap: 8px; }
  .scale-chip { padding: 12px 6px; }
  .scale-chip .icons { font-size: 15px; letter-spacing: -1px; line-height: 22px; height: 22px; }
  .scale-chip .lbl { font-size: 11px; }
}

/* Hero CTAs (matches .cta.solid / .cta.ghost) */
.hero-buttons { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin-top: 30px; }
.btn-hero-primary {
  font-family: var(--font-display); font-weight: 800; font-size: 16px;
  padding: 15px 28px; border-radius: 14px; border: none; cursor: pointer;
  background: #fff; color: var(--mint-deep); text-decoration: none; display: inline-block;
  box-shadow: 0 14px 30px rgba(8,60,53,.3);
}
.btn-hero-ghost {
  font-family: var(--font-display); font-weight: 800; font-size: 16px;
  padding: 15px 28px; border-radius: 14px; cursor: pointer;
  background: rgba(255,255,255,.16); color: #fff;
  border: 1.5px solid rgba(255,255,255,.5); text-decoration: none; display: inline-block;
}
.btn-hero-primary:active, .btn-hero-ghost:active { transform: translateY(1px); }

/* ---------- Feature cards (matches .feat) ---------- */
.feature-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; }
.feature-card { background: var(--white); border: 1px solid var(--line); border-radius: 16px; padding: 18px; }
.feature-card h3 { font-family: var(--font-display); font-size: 15.5px; margin: 0 0 3px; color: var(--ink); }
.feature-card p { font-size: 13.5px; color: var(--ink-soft); line-height: 1.45; margin: 0; }

/* ---------- Content pages ---------- */
.content-page h1 {
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(26px, 6vw, 34px); letter-spacing: -.025em; color: var(--ink);
}
.content-page h2 { color: var(--ink); font-family: var(--font-display); font-weight: 800; font-size: 20px; }
.content-page p { color: #2c424a; }
.pullquote {
  font-size: 1.25rem; font-weight: 700; color: var(--ink);
  border-left: 3px solid var(--mint); background: var(--white);
  border-radius: 0 10px 10px 0; padding: 12px 14px; margin: 1.25rem 0;
}
.step h2 { margin-bottom: 0.25rem; }
.scale-table { border-collapse: collapse; width: 100%; max-width: 30rem; background: var(--white); border-radius: 12px; }
.scale-table th, .scale-table td { border: 1px solid var(--line); padding: 0.4rem 0.75rem; text-align: left; }
.scale-table th { background: var(--ink); color: #fff; }

/* ---------- How It Works ---------- */
.hiw { max-width: 52rem; }
.hiw-lede { font-size: 1.05rem; line-height: 1.6; }

/* Table of contents */
.hiw-toc {
  background: var(--white); border: 1px solid var(--line);
  border-radius: 16px; padding: 16px 20px; margin: 1.5rem 0 2rem;
}
.hiw-toc h2 {
  font-size: 14px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-soft); margin: 0 0 .6rem;
}
.hiw-toc ol {
  margin: 0; padding: 0; list-style: none;
  display: grid; gap: 6px 20px;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  counter-reset: hiwtoc;
}
.hiw-toc li { counter-increment: hiwtoc; font-size: 14.5px; }
.hiw-toc li::before {
  content: counter(hiwtoc) ". "; color: var(--ink-soft); font-weight: 700;
}
.hiw-toc a { color: var(--mint-deep); text-decoration: none; font-weight: 600; }
.hiw-toc a:hover { text-decoration: underline; }

/* Section spacing + deep-link offset under the sticky header */
.hiw .step { margin: 0 0 2.5rem; scroll-margin-top: 90px; }

/* Section eyebrow — "Start Here" and "Everything Else, Briefly".
   Marks the break between the three steps a newcomer has to read and
   the reference cards below it, without adding a heading level that
   would compete with the step h2s in the document outline.

   Colour is --ink-soft, not --mint-deep: at 12px bold, mint-deep on
   porcelain lands at 3.8:1 and misses AA. The mint lives in the rule
   before the text, which is decorative and doesn't have to pass. */
.hiw-eyebrow {
  font-family: var(--font-display); font-size: 12px; font-weight: 800;
  letter-spacing: .16em; text-transform: uppercase; color: var(--ink-soft);
  margin: 0 0 1rem;
}
.hiw-eyebrow::before {
  content: ""; display: inline-block; width: 22px; height: 3px;
  border-radius: 2px; background: var(--mint); vertical-align: middle;
  margin-right: 10px; transform: translateY(-1px);
}

/* Deep-link offset for everything else that carries an id. Each of the
   old numbered steps had its own id and got this from `.hiw .step`
   above; after the Aug 28 rewrite several of those ids (#filters,
   #teams, #profile, #report…) sit on `.hiw-card`s inside one section,
   and links to them predate the rewrite. Same 90px sticky-header
   offset, so the two rules agree where they overlap. */
.hiw [id] { scroll-margin-top: 90px; }

/* Screenshot figures */
.hiw-shot {
  margin: 1.25rem 0; padding: 0;
  background: var(--white); border: 1px solid var(--line);
  border-radius: 16px; overflow: hidden;
}
.hiw-shot img { display: block; width: 100%; height: auto; }
.hiw-shot figcaption {
  font-size: 13.5px; color: var(--ink-soft);
  padding: 10px 14px; border-top: 1px solid var(--line); background: var(--porcelain);
}
.hiw-shot-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; text-align: center; min-height: 190px; padding: 24px 16px;
  background: repeating-linear-gradient(
    45deg, var(--porcelain), var(--porcelain) 12px, var(--tile) 12px, var(--tile) 24px);
  color: var(--ink-soft);
}
.hiw-shot-icon { font-size: 28px; }
.hiw-shot-name {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px; font-weight: 700; color: var(--ink);
  background: var(--white); border: 1px solid var(--line-2);
  border-radius: 8px; padding: 4px 10px;
}
.hiw-shot-alt { font-size: 12.5px; max-width: 34rem; line-height: 1.45; }

/* Feature grids */
.hiw-grid {
  display: grid; gap: 12px; margin: 1rem 0;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.hiw-card {
  background: var(--white); border: 1px solid var(--line);
  border-radius: 14px; padding: 14px 16px;
}
.hiw-card h3 {
  margin: 0 0 .35rem; font-size: 15.5px; font-weight: 800; color: var(--ink);
}
.hiw-card p { margin: 0; font-size: 13.5px; line-height: 1.5; color: var(--ink-soft); }

/* Bulleted feature lists */
.hiw-list { margin: .75rem 0 1rem; padding-left: 1.15rem; color: #2c424a; }
.hiw-list li { margin-bottom: .45rem; line-height: 1.55; }

/* Tip + callout */
.hiw-tip {
  background: var(--white); border-left: 3px solid var(--mint);
  border-radius: 0 10px 10px 0; padding: 10px 14px;
  font-size: 14px; color: #2c424a;
}
.hiw-callout {
  background: var(--white); border: 1px solid var(--line-2);
  border-left: 3px solid var(--amber);
  border-radius: 0 14px 14px 0; padding: 14px 16px; margin: 1.25rem 0;
}
.hiw-callout h3 { margin: 0 0 .35rem; font-size: 16px; font-weight: 800; }
.hiw-callout p { margin: 0; font-size: 14px; line-height: 1.55; }

.hiw-plans { max-width: 34rem; }
.hiw-plans td:first-child { white-space: nowrap; }

/* ---------- Cards & forms (matches .contact-card) ---------- */
.card {
  background: var(--white); border: 1px solid var(--line);
  border-radius: 20px; padding: 24px; margin-bottom: 1.25rem;
}
.form-narrow { max-width: 560px; }
label { display: block; font-weight: 600; margin: 0.9rem 0 0.3rem; font-size: 14.5px; }
input[type="text"], input[type="email"], input[type="password"],
input[type="url"], input[type="tel"], input[type="search"], input[type="number"],
textarea, select {
  width: 100%; min-height: 46px; padding: 10px 14px;
  border: 1.5px solid var(--line-2); border-radius: 13px;
  font-size: 15px; font-family: var(--font-body);
  background: var(--porcelain); color: var(--ink);
}
textarea { min-height: 0; }
input:focus, textarea:focus, select:focus { outline: 2px solid var(--mint); background: var(--white); }
fieldset { border: 1.5px solid var(--line-2); border-radius: 13px; margin-top: 0.75rem; }
.checkbox-inline { display: inline-block; font-weight: 400; margin-right: 0.75rem; }
.form-actions { display: flex; gap: 10px; margin-top: 1rem; }
.form-alt { font-size: 0.9rem; margin-top: 0.9rem; color: var(--ink-soft); }
.form-alt a { color: var(--mint-deep); }

/* ---------- Buttons ---------- */
.btn {
  display: inline-block; min-height: 46px; padding: 11px 22px;
  border-radius: 13px; font-family: var(--font-display);
  font-weight: 800; font-size: 14.5px; text-decoration: none;
  cursor: pointer; border: none; transition: .15s;
}
.btn-primary { background: var(--mint); color: #04231f; }
.btn-primary:hover { filter: brightness(.97); }
.btn-outline { background: var(--white); color: var(--ink); border: 1.5px solid var(--line-2); }
.btn-outline:hover { background: var(--tile); }
.btn-danger { background: var(--danger); color: #fff; }
.btn-sm { min-height: 0; padding: 6px 12px; font-size: 12.5px; border-radius: 10px; }
.btn-green { background: var(--mint-deep); color: #fff; border: 1.5px solid var(--mint-deep); }
.btn-green:hover { background: #0b6f63; border-color: #0b6f63; color: #fff; }
/* Label + external-link glyph on one line */
.btn-ext { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.btn-ext .ext-icon { flex: 0 0 auto; }
.btn:active { transform: translateY(1px); }

/* ---------- Flash messages ---------- */
.flash { padding: 12px 15px; border-radius: 13px; margin: 0.75rem 0; font-weight: 600; font-size: 14.5px; }
.flash-success { background: #e2f5f1; color: var(--mint-deep); border: 1px solid var(--mint); }
.flash-error   { background: #fdecec; color: #a23b38; border: 1px solid var(--danger); }
.flash-info    { background: #fff8e8; color: var(--warn); border: 1px solid var(--amber); }

/* ============================================================
   Map page — Yelp-style full-screen layout
   filters (left) | results list (center) | map (right)
   ============================================================ */
body.map-fullscreen .site-main { max-width: none; padding: 0; margin: 0; }
body.map-fullscreen .site-main .flash { margin: 0.75rem 20px; }

/* The map page's compact footer.
 *
 * The footer used to be display:none here, because the map is sized to
 * fill the viewport and anything after it was unreachable. It now shows
 * as a slim bar (see includes/footer.php) carrying the two things that
 * are not optional on a ratings site: the disclaimer and the terms link.
 *
 * Getting it on screen is a layout change, not a height guess. The old
 * `height: calc(100vh - var(--sp-header-h, 118px))` on .map-shell is a
 * magic number that was already one wrapped header row away from being
 * wrong; subtracting a second guess for the footer would compound that
 * — and the footer's height genuinely varies, since the disclaimer
 * wraps to three lines on a phone and one on a desktop.
 *
 * So the page becomes a flex column instead: header and footer take the
 * height they need, the map shell takes everything left over. Nothing
 * has to be measured, and it stays correct at any width.
 */
body.map-fullscreen {
  /* height, NOT min-height. This is the line the whole layout turns on.
     A flex item only shrinks when the container has negative free space,
     and that calculation needs a DEFINITE height — min-height does not
     supply one. With min-height the column would size itself to its
     tallest child instead, which is the filters rail (it is ~700px of
     chips and checkboxes), pushing the footer below the fold on any
     laptop under about 925px tall. Which is the exact problem this
     change exists to fix.

     dvh second so it wins where supported: on mobile, 100vh is the
     viewport with the browser chrome RETRACTED, so a vh-sized page hides
     its own last ~60px behind the address bar until you scroll. */
  height: 100vh;
  height: 100dvh;
  /* Escape hatch. Everything below is sized to fit, but if a viewport is
     short enough that the shell hits its floor, scrolling to the footer
     beats clipping it. */
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}
body.map-fullscreen .site-header,
body.map-fullscreen .tab-bar,
body.map-fullscreen .site-footer-compact { flex: 0 0 auto; }
body.map-fullscreen .site-main {
  flex: 1 1 auto;
  min-height: 0;            /* without this a flex item refuses to shrink
                               below its content and pushes the footer off */
  display: flex;
  flex-direction: column;
}

.map-shell {
  display: grid;
  grid-template-columns: 240px minmax(340px, 430px) 1fr;
  height: calc(100vh - var(--sp-header-h, 118px));
  min-height: 420px;
  overflow: hidden;
  position: relative;
}

/* Inside the flex column the shell is measured by the flexbox, so the
   calc() above is dropped rather than fought with. It still applies if
   .map-shell is ever used on a non-full-screen page. */
body.map-fullscreen .map-shell {
  height: auto;
  flex: 1 1 auto;
  /* The base rule's 420px floor is lowered rather than removed. At 420
     the shell would refuse to shrink on a landscape phone and shove the
     footer off screen; at 0 it could collapse to nothing on the same
     device. 200px keeps a usable map and lets the body scroll in the
     rare case that even that does not fit. */
  min-height: 200px;
}

/* LEFT: filters */
.map-filters {
  overflow-y: auto; padding: 16px 16px 32px;
  border-right: 1px solid var(--line);
  background: var(--white);
}
.filters-head { display: flex; align-items: center; justify-content: space-between; }
.filters-head h2 { font-size: 16px; margin: 0 0 4px; }
.filters-close { display: none; background: none; border: none; font-size: 18px; cursor: pointer; color: var(--ink-soft); padding: 6px; }
.filter-group { margin-bottom: 18px; }
.filter-group h3 { font-size: 13px; margin: 0 0 8px; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .04em; }
.filter-group .sport-chip { display: inline-block; margin: 0 4px 6px 0; }
.filter-check {
  display: flex; align-items: center; gap: 8px;
  font-weight: 500; font-size: 13.5px; margin: 0 0 6px; cursor: pointer;
}
.filter-check input { accent-color: var(--mint); width: 16px; height: 16px; }
.filter-group select { min-height: 40px; font-size: 13.5px; }
.filters-open { display: none; }

/* 30 Day/Avg rating chips.
   Reuses .sport-chip so the two rows are visibly the same control and
   only need styling in one place. The rating row wraps to a tighter
   grid because its labels are short (💩💩 2) where sport labels are
   words — left on the default flow they'd sprawl over four lines. */
.rating-chip { padding: 7px 11px; letter-spacing: -.01em; }
#ratingFilter .filter-note { margin: 2px 0 8px; color: var(--ink-soft); line-height: 1.4; }
#ratingFilter .filter-check { margin-top: 2px; }
/* The one line of explanation under the Kind of place chips. Small and
   quiet on purpose: it is there for the owner who scrolls past it once,
   not for the parent filtering their way to a ball field. */
.filter-note { margin: 6px 0 0; color: var(--ink-soft); line-height: 1.45; }
.filter-note a { font-weight: 700; }

/* "Own this business?" — the claim box in the right-hand column of a
   business pin page, directly under Save & Share. Mint-tinted rather than
   white so it reads as an aside to the page rather than one more section
   of bathroom intel. It is the only sales surface on a public pin page, so
   it gets exactly one accent colour and no badge, banner or ribbon. */
.pin-claim-card {
  background: var(--mint-pale);
  border: 1.5px solid var(--mint-line);
}
.pin-claim-card h2 { margin-top: 0; }
/* .pin-claim-lede / .pin-claim-list removed: the box is a heading, two
   buttons and one line of small print. The what-you-get list and the plan
   prices live on /for-business, which has the room to argue them. */
.pin-claim-foot { margin-bottom: 0; color: var(--ink-soft); }

/* The basis tag: says whether a score is a live 30-day average or the
   last figure on record. Deliberately quiet — it qualifies the number
   next to it, it does not compete with it. */
.rating-basis {
  display: inline-block; font-family: var(--font-display); font-weight: 800;
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em;
  padding: 2px 7px; border-radius: 999px; white-space: nowrap;
  background: var(--tile); color: var(--ink-soft); vertical-align: middle;
}
.rating-basis.last { background: #fdf1dc; color: #8a6320; }
.rating-basis-note {
  font-size: 13px; color: var(--ink-soft); margin: -.4rem 0 .8rem; line-height: 1.5;
}

/* CENTER: results list */
.map-results { display: flex; flex-direction: column; overflow: hidden; background: var(--porcelain); border-right: 1px solid var(--line); }
.results-head { padding: 12px 16px; border-bottom: 1px solid var(--line); background: var(--white); }
.map-title { margin: 0; font-size: clamp(20px, 3vw, 26px); flex: 0 0 auto; }
.results-meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.results-meta select { width: auto; min-height: 38px; padding: 6px 10px; font-size: 13px; margin-left: auto; }
.results-list { flex: 1; overflow-y: auto; padding: 12px 14px 32px; }
.results-empty { color: var(--ink-soft); padding: 12px 4px; }

/* Result cards (Yelp-style rows) */
.result-card {
  background: var(--white); border: 1px solid var(--line); border-radius: 16px;
  padding: 14px 16px; margin-bottom: 10px; cursor: pointer; transition: .15s;
}
.result-card:hover, .result-card.active { border-color: var(--mint); box-shadow: 0 4px 18px rgba(20,37,43,.08); }
.result-card h3 { margin: 0 0 2px; font-size: 15.5px; }
.result-card .pin-sport { margin: 0 0 6px; font-weight: 700; font-size: 12px; color: var(--mint-deep); }
.result-rating { display: flex; align-items: center; gap: 8px; margin: 0 0 6px; flex-wrap: wrap; }
.score-pill {
  display: inline-block; padding: 3px 10px; border-radius: 999px;
  font-family: var(--font-display); font-weight: 800; font-size: 12.5px; color: #fff;
  background: var(--ink-soft);
}
.score-pill.s0 { background: rgb(21,183,161); }
.score-pill.s1 { background: rgb(105,175,121); }
.score-pill.s2 { background: rgb(190,167,81); }
.score-pill.s3 { background: rgb(213,149,61); }
.score-pill.s4 { background: rgb(176,122,60); }
.score-pill.s5 { background: rgb(139,94,60); }
.result-rating .count { font-size: 12.5px; color: var(--ink-soft); font-weight: 600; }
.result-tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 6px; }
.result-tag {
  font-size: 11.5px; font-weight: 700; color: var(--ink-soft);
  background: var(--tile); border-radius: 999px; padding: 3px 9px; white-space: nowrap;
}
.result-snippet { font-size: 13px; color: #2c424a; margin: 4px 0 0; line-height: 1.45; }
.result-snippet em { color: var(--ink-soft); }
.result-stale { font-size: 12px; color: var(--warn); margin: 4px 0 0; }

/* RIGHT: map */
.map-panel { position: relative; }
.map-canvas { height: 100%; width: 100%; }

/* The floating .map-area-controls slot used to live here: the "Search Area |
   Search with Move" segmented switch, and a second face carrying a duplicate
   area pill and a "Search this area instead" button. All of it is gone.

   It was the widest thing on the map and the least useful — a reload STRATEGY
   the user was asked to choose before seeing a result — and under 420px its
   two segments could not shrink below their own text (`flex: 1 1 0` with the
   default `min-width: auto`), so the switch ran off the right edge of the map.
   Its whole job now belongs to one button in the header shelf: .sp-refresh.  */

/* ---------- Add-pin-mode banner: an unmistakable, distinct state so a tap
   on the map can never be confused with searching or browsing. Full-width
   across the bottom of the map panel, clear of the list/map toggle which
   sits centred at the same edge (see .view-toggle) — the banner's own
   padding-right keeps its text from running under that button on a phone. */
.add-mode-banner {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 1040;
  background: var(--mint-deep); color: var(--white);
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 16px; padding-right: 120px;
  font-family: var(--font-display); font-weight: 800; font-size: 13.5px;
}
.add-mode-banner[hidden] { display: none; }
.add-mode-banner button {
  flex: 0 0 auto; border: 1.5px solid rgba(255,255,255,.55); border-radius: 999px;
  background: rgba(255,255,255,.16); color: var(--white);
  font-family: var(--font-display); font-weight: 800; font-size: 12.5px;
  padding: 7px 14px; cursor: pointer;
}
.add-mode-banner button:hover { background: rgba(255,255,255,.28); }
@media (max-width: 480px) {
  .add-mode-banner { padding-right: 16px; flex-wrap: wrap; }
}

.results-capped { border-top: 1px dashed var(--line-2); padding-top: 10px; }
.results-widened {
  margin: 0 0 10px; padding: 9px 12px; border-radius: 10px;
  background: var(--tile); color: var(--ink-soft);
  font-size: 13px; font-weight: 600; line-height: 1.4;
}

/* Mobile list/map toggle (floating, Yelp-style) */
.view-toggle {
  display: none; position: absolute; bottom: 18px; left: 50%; transform: translateX(-50%);
  z-index: 1050; background: var(--ink); color: #fff; border: none; cursor: pointer;
  font-family: var(--font-display); font-weight: 800; font-size: 14px;
  padding: 12px 22px; border-radius: 999px; box-shadow: var(--shadow);
}

/* Add-pin form becomes a centered overlay on the full-screen map page */
body.map-fullscreen .pin-form-wrap {
  position: fixed; inset: 0; z-index: 1200; /* above Leaflet panes/controls (max ~1000) */
  background: rgba(20,37,43,.45);
  display: flex; align-items: flex-start; justify-content: center;
  overflow-y: auto; padding: 4vh 16px;
  margin: 0;
}
body.map-fullscreen .pin-form-wrap[hidden] { display: none; }
body.map-fullscreen .pin-form-wrap .card { max-width: 520px; width: 100%; box-shadow: var(--shadow); }

/* Auto-suggested name hint + duplicate/update note in the pin form */
.field-hint {
  display: block; margin-top: 4px; font-size: 12px; color: var(--mint-deep);
  font-weight: 600;
}
.pin-dupe-note {
  margin: 0 0 12px; padding: 9px 12px; border-radius: 10px;
  background: #FFF6E6; border: 1.5px solid #F0C777; color: #6b4c12;
  font-size: 13px; line-height: 1.4;
}
.pin-dupe-note[hidden] { display: none; }

/* "Looks like a business" — the same shape as the dupe note, in mint rather
   than amber. Amber is the colour this form uses for "hold on a second"
   (the duplicate note, the no-bathroom note); this note interrupts nothing
   and asks for nothing, so borrowing that colour would make it read as a
   problem to solve before saving. */
.pin-biz-note {
  margin: 0 0 12px; padding: 9px 12px; border-radius: 10px;
  background: var(--mint-pale); border: 1.5px solid var(--mint-line);
  color: var(--mint-ink);
  font-size: 13px; line-height: 1.4;
}
.pin-biz-note[hidden] { display: none; }
#pinName[readonly] { background: var(--tile); color: var(--ink-soft); cursor: not-allowed; }

/* ---- "No Bathroom Found" ----------------------------------------------
   The option is bold in the pick list because it is not a fifth kind of
   bathroom, it is the answer "there isn't one" — and it sits at the
   bottom, where a list is skimmed fastest.

   Note: option styling is honoured by Firefox and by desktop Chrome, and
   IGNORED by iOS Safari, which draws its own native picker. That is why
   the option ALSO carries the ⚠️ and the words NO BATHROOM FOUND in
   caps: on the phone where most of these pins get dropped, the glyph and
   the caps are the whole of the emphasis. Never let weight be the only
   thing carrying a distinction in a <select>. */
.pin-style-none { font-weight: 800; color: var(--caution-ink); }

.pin-nobath-note {
  margin: -4px 0 12px; padding: 10px 12px; border-radius: 10px;
  background: var(--caution-bg); border: 1.5px solid var(--caution);
  border-left-width: 5px; color: var(--caution-ink);
  font-size: 13px; line-height: 1.45;
}
.pin-nobath-note[hidden] { display: none; }

/* The team alert block. Indented under its own rule so it reads as a
   consequence of the choice above it rather than another form field. */
.pin-team-alert {
  margin: 0 0 14px; padding: 10px 12px; border-radius: 10px;
  background: var(--tile); border: 1px solid var(--line-2);
}
.pin-team-alert[hidden] { display: none; }
.pin-team-check {
  display: flex; align-items: center; gap: 9px;
  font-weight: 700; font-size: 14px; cursor: pointer; margin: 0;
}
.pin-team-check input { accent-color: var(--mint); width: 18px; height: 18px; }
.pin-team-pick { display: block; margin: 10px 0 0; font-size: 13.5px; font-weight: 600; }
.pin-team-pick[hidden] { display: none; }

/* The warning where a score would be — popup, result card, pin page. */
.pin-nobath, .result-nobath {
  margin: 6px 0; padding: 7px 10px; border-radius: 8px;
  background: var(--caution-bg); border-left: 4px solid var(--caution);
  color: var(--caution-ink); font-size: 13px; line-height: 1.4;
}
.pin-page-nobath { border-top: 5px solid var(--caution); }
.pin-page-nobath h2 { color: var(--caution-ink); }
.pin-page-nobath-lede { font-size: 15px; line-height: 1.55; margin: 0 0 1rem; }
.pin-page-nobath-fix {
  font-size: 13.5px; color: var(--ink-soft); margin: 1rem 0 0;
}

/* The "⚠️ No bathroom reported" filter checkbox — separated from the four
   real styles above it, because it asks the opposite question. */
.filter-check-warn {
  margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--line-2);
  color: var(--caution-ink); font-weight: 700;
}

/* ---- Pin colour legend (how-it-works.php) ----
   Each row is a real marker next to a sentence.

   The first two rows use the ACTUAL marker images the map draws — the
   blue is Leaflet's own file and the yellow is that same file recoloured
   — rather than a CSS lookalike. A legend drawn by hand is a second
   implementation of the pin, and the day one of them changes it starts
   quietly describing a map that no longer exists. The business rows are
   CSS shapes because those markers ARE CSS shapes (divIcons). */
.pin-legend { list-style: none; margin: 1rem 0; padding: 0; display: grid; gap: 10px; }
.pin-legend li {
  display: flex; align-items: flex-start; gap: 12px;
  background: var(--white); border: 1px solid var(--line);
  border-radius: 12px; padding: 11px 13px;
}
.pin-legend .legend-text { font-size: 13.5px; line-height: 1.5; color: var(--ink-soft); }
.pin-legend .legend-text strong { color: var(--ink); display: block; font-size: 14.5px; }
/* Real marker artwork. Sized to the natural 25x41 so it is the same pin
   at the same scale the user sees on the map. */
.legend-img {
  flex: 0 0 34px; width: 25px; height: 41px; margin: 0 4px 0 5px;
  display: block; align-self: center;
}
.legend-pin {
  flex: 0 0 34px; width: 34px; height: 34px; margin-top: 2px;
  display: flex; align-items: center; justify-content: center;
  font-size: 17px; line-height: 1;
  border-radius: 50% 50% 50% 0; transform: rotate(-45deg);
  border: 2px solid rgba(20,37,43,.45); background: var(--white);
  box-shadow: 0 1px 4px rgba(0,0,0,.25);
}
.legend-pin > span { display: inline-block; transform: rotate(45deg); }
.legend-pin.is-biz      { background: var(--white); border-color: var(--mint-deep); }
/* Business logo pins are a rounded square, not a teardrop — the legend
   has to show that, or people look for a pointed pin that isn't there. */
.legend-pin.is-logo, .legend-pin.is-premium {
  border-radius: 9px; transform: none;
}
.legend-pin.is-logo > span, .legend-pin.is-premium > span { transform: none; }
.legend-pin.is-logo    { border-color: var(--mint-deep); }
.legend-pin.is-premium { border-color: #E1A100; box-shadow: 0 0 0 3px rgba(225,161,0,.35); }
.legend-pin.is-me {
  border-radius: 50%; transform: none;
  background: #1B6CA8; border: 3px solid var(--white);
  box-shadow: 0 0 0 5px rgba(27,108,168,.18);
}
.legend-pin.is-me > span { transform: none; }

/* Tablet: filters become a slide-over drawer */
@media (max-width: 1099px) {
  .map-shell { grid-template-columns: minmax(320px, 42%) 1fr; }
  .map-filters {
    position: absolute; top: 0; bottom: 0; left: 0; z-index: 1050;
    width: min(300px, 85vw); transform: translateX(-105%);
    transition: transform .2s ease; box-shadow: var(--shadow);
  }
  .map-filters.open { transform: translateX(0); }
  .filters-close { display: block; }
  /* The results header used to grow a "☰ Filters" button at these widths.
     The quick rail above the list now leads with one, and two identical
     buttons eight pixels apart is the duplication this rework keeps
     deleting. The rail's is better placed anyway: it sits with the filters
     it summarises, and it is sticky, so it is reachable from anywhere in the
     rail rather than only at the top of the list. */
  .filters-open { display: none; }
}

/* Phone: single column with floating list/map toggle */
@media (max-width: 767px) {
  .map-shell { grid-template-columns: 1fr; }
  .map-panel { display: none; }
  .map-results { border-right: none; }
  .view-toggle { display: block; position: fixed; bottom: 22px; }
  /* On the map page the shell no longer runs to the bottom of the
     viewport — the compact footer does. A fixed toggle would sit on top
     of it, so it goes back to being absolute inside .map-shell (which is
     position: relative), landing just above the footer instead. */
  body.map-fullscreen .view-toggle { position: absolute; bottom: 18px; }
  .map-shell[data-view="map"] .map-panel { display: block; }
  .map-shell[data-view="map"] .map-results { display: none; }

  /* Phone viewports are short — every row above the list costs a listing.
     The page title is dropped from view so the Filters button and the sort
     select share one compact row. (.pin-count is already hidden globally.) */
  .results-head { padding: 8px 12px; }
  .map-title { display: none; }
  .results-meta { flex-wrap: nowrap; gap: 8px; }
  /* Sort is now the only control in this row (the page title is hidden here
     and Filters moved to the rail), so it takes the width rather than
     hugging the right edge looking like something failed to load. */
  .results-meta select {
    margin-left: 0; flex: 1 1 auto; min-width: 0;
    max-width: none; text-overflow: ellipsis;
  }
}

/* Address search (lives in the header tools row) */

/* ---------- Two-field map search (Find / Near / radius) ------------------
   One pill, three cells and a go button, hairline-ruled like a Yelp bar.
   It is a single control on purpose: WHAT and WHERE are two halves of one
   question, and two separate boxes with two separate buttons would let
   someone submit half of it.

   The bar carries its own border and background, so the inputs inside give
   theirs up — nested rounded rectangles read as two controls, not one. */
.sp-search {
  position: relative;
  display: flex; align-items: stretch; gap: 0;
  flex: 1 1 460px; min-width: 260px; max-width: 720px;
  background: var(--white);
  border: 1.5px solid var(--line-2);
  border-radius: 999px;
  padding: 3px;
  box-shadow: 0 1px 2px rgba(20,37,43,.05);
}
.sp-search:focus-within { border-color: var(--mint); box-shadow: 0 0 0 3px rgba(21,183,161,.18); }

.sp-search-cell { position: relative; display: flex; align-items: center; min-width: 0; }
/* WHAT gets the larger share: it is free text of unknown length, while a place
   name is short and predictable.

   The basis is a real width, not 0. The site-wide form rule further up gives
   every select `width: 100%`, and a select that does not shrink next to two
   cells that flex from nothing is a select that takes the entire bar — which
   is exactly what it did until the radius got its width back below. */
.sp-search-what  { flex: 1.5 1 170px; }
.sp-search-where { flex: 1 1 150px; }

.sp-search input[type="search"] {
  width: 100%; min-width: 0;
  border: 0; background: transparent; box-shadow: none; outline: none;
  padding: 8px 26px 8px 14px;
  min-height: 38px; font-size: 14.5px; font-family: inherit; color: var(--ink);
  border-radius: 999px;
  -webkit-appearance: none; appearance: none;
}
.sp-search input[type="search"]::-webkit-search-decoration,
.sp-search input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; }
.sp-search input::placeholder { color: var(--ink-soft); opacity: .85; }

/* Our own clear button rather than the browser's: Safari hides its native one
   until focus, so on a phone the only way to see a stale term was to tap into
   the box you were trying to empty. */
.sp-search-clear {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  width: 22px; height: 22px; padding: 0; line-height: 1;
  border: 0; border-radius: 50%; cursor: pointer;
  background: var(--tile); color: var(--ink-soft); font-size: 11px;
}
.sp-search-clear:hover { background: var(--line-2); color: var(--ink); }

.sp-search-rule { width: 1px; margin: 7px 0; background: var(--line-2); flex: 0 0 1px; }

.sp-search-radius {
  /* `width: auto` is load-bearing: the shared select rule sets width:100%,
     and with flex-shrink 0 that resolves against the whole bar. */
  width: auto; flex: 0 0 auto;
  border: 0; background: transparent; box-shadow: none;
  padding: 8px 6px 8px 12px; min-height: 38px;
  font-size: 13.5px; font-weight: 700; font-family: inherit;
  color: var(--ink-soft); cursor: pointer;
}

/* The shared rule paints a 2px mint outline around any focused field. Inside
   this pill that draws a second rounded box inside the first. The pill's own
   focus ring replaces it — and because one pill holds three controls, the
   focused cell tints so a keyboard user can still tell which one they are in. */
.sp-search input:focus, .sp-search select:focus { outline: none; background: transparent; }
.sp-search-cell:focus-within { background: rgba(21,183,161,.07); border-radius: 999px; }
.sp-search-radius:focus-visible { color: var(--ink); text-decoration: underline; }

.sp-search-go {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; margin-left: 4px; padding: 0;
  border: 0; border-radius: 50%; cursor: pointer;
  background: var(--mint); color: var(--white);
}
.sp-search-go:hover { background: var(--mint-deep); }
.sp-search-go:focus-visible { outline: 3px solid rgba(21,183,161,.45); outline-offset: 2px; }

/* The suggestion list hangs off the WHERE cell, so it lines up under the box
   that produced it instead of spanning a bar that is mostly about something
   else. */
.sp-near-menu { top: calc(100% + 7px); left: -8px; right: -8px; min-width: 240px; }
.sp-near-menu .addr-result-sub { display: block; font-size: 12px; color: var(--ink-soft); }
.sp-near-menu .addr-result-me { font-weight: 700; color: var(--mint-deep); }
.sp-near-menu .addr-result-head {
  display: block; padding: 7px 10px 3px; font-size: 11.5px; font-weight: 800;
  letter-spacing: .04em; text-transform: uppercase; color: var(--ink-soft);
}

.sp-visually-hidden {
  position: absolute !important; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* The go button says "Search" in the sheet and is icon-only in the header
   pill, where the words would eat a third of a bar that has two fields to
   fit. Same button, same submit — only the label comes and goes. */
.sp-search-go-label { display: none; }

/* ============================================================
   THE SEARCH SHELF  (map.php $headerTools)
   ============================================================
   One 44px line: what you're looking for, reload, drop a pin.

   It replaced roughly 250px of chrome on a phone — a 145px stacked search
   pill, a 51px account row of links the hamburger already had, and a
   segmented mode switch floating over the map. The rule that got us here:
   a control that only ever answers a question ("what am I searching?")
   does not need to be a form on screen; it needs to be a sentence you can
   tap.

   ONE copy of the form exists, at every width. Below 861px .sp-sheet is a
   full-screen overlay holding it; above, .sp-sheet is a plain flex wrapper
   and the form inside is the header pill exactly as it always was. A second
   mobile copy would mean duplicate ids, and every getElementById in map.js
   would silently bind to whichever came first in the document.            */
.sp-shelf {
  display: flex; align-items: center; gap: 8px;
  flex: 1 1 auto; min-width: 0;
}

/* ---- The collapsed bar (<=860px only) ----
   A div wrapping two buttons, not one button with a ✕ inside it: nesting
   interactive elements is invalid, and assistive tech disagrees about which
   one was activated. */
.sp-searchbtn { display: none; }
.sp-searchbtn-main {
  flex: 1 1 auto; min-width: 0; height: 100%;
  display: flex; align-items: center; gap: 9px;
  padding: 0 4px 0 14px; border: 0; background: none; cursor: pointer;
  font-family: inherit; color: var(--ink); text-align: left;
}
.sp-searchbtn-mag { flex: 0 0 auto; display: block; color: var(--mint-deep); }
/* The whole point of this control: it reads back the current search. Long
   place names ellipsize rather than pushing the radius off the end. */
.sp-searchbtn-text {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
  font-size: 15px; font-weight: 700;
}
.sp-searchbtn-text.is-empty { font-weight: 500; color: var(--ink-soft); }
.sp-searchbtn-sub { flex: 0 0 auto; font-size: 13px; font-weight: 700; color: var(--ink-soft); }
.sp-searchbtn-sub[hidden] { display: none; }
.sp-searchbtn-clear {
  flex: 0 0 auto; width: 30px; height: 30px; padding: 0; line-height: 1;
  border: 0; border-radius: 50%; cursor: pointer; font-size: 12px;
  background: var(--tile); color: var(--ink-soft);
}
.sp-searchbtn-clear:hover { background: var(--line-2); color: var(--ink); }
.sp-searchbtn-clear[hidden] { display: none; }

/* ---- Refresh: the whole of the old mode switch, in one button ----
   Meaning: search whatever the map is showing now. map.js turns it mint
   (.is-stale) once you have moved far enough that pressing it would change
   something — so the "search this area" nudge is a state of a control that
   is always there, not a control that appears and disappears over the map. */
.sp-refresh {
  flex: 0 0 auto; width: 44px; height: 44px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1.5px solid var(--line-2); border-radius: 50%;
  background: var(--white); color: var(--ink-soft); cursor: pointer;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.sp-refresh:hover { background: var(--tile); color: var(--ink); }
.sp-refresh:focus-visible { outline: 3px solid var(--mint); outline-offset: 2px; }
.sp-refresh.is-stale { background: var(--mint-deep); border-color: var(--mint-deep); color: var(--white); }
.sp-refresh.is-stale:hover { background: #0b6f63; color: var(--white); }
.sp-refresh.is-loading { pointer-events: none; opacity: .75; }
.sp-refresh.is-loading svg { animation: sp-spin .8s linear infinite; }
@keyframes sp-spin { to { transform: rotate(360deg); } }

/* Drop a Pin: same 44px round target as the refresh beside it. It used to be
   a 38-40px rounded square in the account row; matched to its new neighbour
   the two read as one pair of actions rather than two stray buttons. */
.sp-shelf .nav-pin-btn {
  flex: 0 0 auto; width: 44px; min-width: 44px; height: 44px; min-height: 44px;
  border-radius: 50%; padding: 0;
}
/* ---- Armed state ----
   The button used to grow the instruction as text. In a fixed 44px circle
   that sentence had nowhere to go and ran off the right edge of the screen,
   and it duplicated the banner across the bottom of the map. Colour carries
   it instead: amber for "this is armed, your next tap on the map places a
   pin", clearly not the resting mint and clearly not the mint-deep the
   refresh button turns when IT wants pressing.

   The ring is what makes it read as a mode rather than a differently-coloured
   button — it is the only thing on the shelf wearing one. */
.sp-shelf .nav-pin-btn.is-adding,
.btn.btn-primary.nav-pin-btn.is-adding {
  background: var(--amber);
  border-color: var(--amber);
  box-shadow: 0 0 0 4px rgba(232,163,61,.30);
}
.sp-shelf .nav-pin-btn.is-adding:hover { background: #d8942f; }
/* The pin's inner dot is mint at rest, which on amber is two mid-tones
   fighting. Dark brown is the one colour that stays legible at 20px against
   both the white pin and the amber behind it. */
.sp-shelf .nav-pin-btn.is-adding svg circle { fill: var(--caution-ink); }
.sp-shelf-login { flex: 0 0 auto; white-space: nowrap; }

/* ---- The sheet ----
   >=861px: an invisible flex wrapper. The form inside is the pill. */
.sp-sheet { display: flex; flex: 1 1 460px; min-width: 260px; max-width: 720px; }
.sp-sheet-head { display: none; }

/* ---- Phones and small tablets ---- */
@media (max-width: 860px) {
  .sp-searchbtn {
    display: flex; align-items: center; flex: 1 1 auto; min-width: 0;
    height: 44px; padding-right: 5px; overflow: hidden;
    background: var(--white); border: 1.5px solid var(--line-2); border-radius: 999px;
    box-shadow: 0 1px 2px rgba(20,37,43,.05);
  }
  .sp-searchbtn:focus-within { border-color: var(--mint); box-shadow: 0 0 0 3px rgba(21,183,161,.18); }

  /* Full screen. Not a centred modal card: a search form on a phone should
     own the screen, so the keyboard has somewhere to go and the fields can be
     52px tall.

     `position: absolute`, NOT fixed, and this is the whole trick. .site-header
     sets `backdrop-filter: blur(10px)` for its frosted look, and a
     backdrop-filter — exactly like transform and filter — makes the element a
     containing block for fixed-position DESCENDANTS. So `position: fixed;
     inset: 0` on anything inside this header does not mean "the viewport", it
     means "the header": the sheet came out 125px tall and the map painted
     straight over the rest of it.

     Absolute against the same box is honest about what is happening, and an
     explicit 100vw/100dvh gives it the size `inset: 0` was supposed to. The
     header is a direct child of body and sticks to top: 0, so the header's
     box and the viewport's top-left are the same point.

     z-index is local to the header's stacking context (1100), which already
     sits above Leaflet (~1000) — so 1300 here only has to beat the brand row
     above it, and does. */
  .sp-sheet {
    position: absolute; top: 0; left: 0; z-index: 1300;
    width: 100vw; height: 100vh; height: 100dvh;
    flex: 0 0 auto; max-width: none; min-width: 0;
    flex-direction: column; background: var(--porcelain);
    display: none;
  }
  .sp-sheet.is-open { display: flex; }
  .sp-sheet-head {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    flex: 0 0 auto; padding: 13px 16px;
    background: var(--white); border-bottom: 1px solid var(--line);
  }
  .sp-sheet-head h2 { margin: 0; font-size: 19px; }
  .sp-sheet-close {
    flex: 0 0 auto; width: 40px; height: 40px; padding: 0; line-height: 1;
    border: 1px solid var(--line-2); border-radius: 50%; cursor: pointer;
    background: var(--white); color: var(--ink); font-size: 15px;
  }

  /* The pill unpacks into a stack of real, separate fields. Nothing here is
     squeezed, because nothing is sharing a line any more. */
  .sp-sheet .sp-search {
    flex: 1 1 auto; min-width: 0; max-width: none;
    flex-direction: column; align-items: stretch; gap: 12px;
    padding: 16px; overflow-y: auto;
    background: transparent; border: 0; border-radius: 0; box-shadow: none;
  }
  .sp-sheet .sp-search:focus-within { border: 0; box-shadow: none; }
  .sp-sheet .sp-search-cell {
    flex: 0 0 auto;
    background: var(--white); border: 1.5px solid var(--line-2); border-radius: 14px;
  }
  .sp-sheet .sp-search-cell:focus-within {
    background: var(--white); border-color: var(--mint);
    box-shadow: 0 0 0 3px rgba(21,183,161,.18); border-radius: 14px;
  }
  /* 16px is not a taste call — below it, iOS zooms the viewport on focus and
     never zooms back. See the touch-width block near the top of this file. */
  .sp-sheet .sp-search input[type="search"] {
    min-height: 52px; padding: 13px 44px 13px 16px; font-size: 16px; border-radius: 14px;
  }
  .sp-sheet .sp-search-clear { right: 10px; width: 26px; height: 26px; font-size: 12px; }
  .sp-sheet .sp-search-rule { display: none; }
  .sp-sheet .sp-search-radius {
    flex: 0 0 auto; width: 100%; min-height: 52px; padding: 13px 16px;
    font-size: 16px; color: var(--ink); border-radius: 14px;
    background: var(--white); border: 1.5px solid var(--line-2);
  }
  .sp-sheet .sp-search-go {
    width: 100%; height: 52px; margin: 4px 0 0; gap: 9px;
    border-radius: 14px; font-family: var(--font-display); font-size: 16px; font-weight: 800;
  }
  .sp-sheet .sp-search-go-label { display: inline; }
  .sp-sheet .sp-near-menu { left: 0; right: 0; top: calc(100% + 6px); }
}

/* The page behind a full-screen sheet must not scroll under it — otherwise
   closing the sheet hands back a map you have scrolled away from. */
body.sp-sheet-open { overflow: hidden; }

/* The .area-chips / .area-chip rules lived here: the "📍 North Haven, CT ·
   within 5 mi ✕" pill in the results header, and its twin over the map. Both
   are gone — the search shelf's own button is the one place that sentence is
   written now. Nothing in any page's markup carried these classes any more,
   so they are removed rather than left to rot as a style with no element. */

/* ---------- "Not Mapped Yet" — places with no bathroom pin ----------
   Visibly a different KIND of row from a result card: dashed border, no
   score pill, no rating. A bordered-solid card here would read as a
   bathroom someone has rated, and the whole point is that nobody has. */
.place-section { margin-top: 18px; padding-top: 14px; border-top: 1px dashed var(--line-2); }
.place-head {
  margin: 0 0 2px; font-size: 13px; font-weight: 800; letter-spacing: .05em;
  text-transform: uppercase; color: var(--ink-soft);
}
.place-note { margin: 0 0 10px; font-size: 13px; color: var(--ink-soft); }
.place-card {
  background: var(--white); border: 1.5px dashed var(--line-2); border-radius: 16px;
  padding: 12px 14px; margin-bottom: 10px; cursor: pointer;
}
.place-card:hover { border-color: var(--mint); background: rgba(21,183,161,.04); }
.place-card h3 {
  margin: 0 0 2px; font-size: 16.5px; text-transform: capitalize;
}
.place-sub { margin: 0 0 10px; font-size: 13px; color: var(--ink-soft); }
.place-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.place-btn {
  padding: 8px 13px; border-radius: 999px; cursor: pointer;
  border: 1.5px solid var(--line-2); background: var(--white);
  color: var(--ink); font-family: inherit; font-weight: 700; font-size: 13.5px;
  min-height: 38px;
}
.place-btn:hover { border-color: var(--mint); color: var(--mint-deep); }
.place-btn-primary { background: var(--mint); border-color: var(--mint); color: var(--white); }
.place-btn-primary:hover { background: var(--mint-deep); border-color: var(--mint-deep); color: var(--white); }

/* Offer to widen, shown inside the empty state when a radius search comes back
   with nothing. It is the fix for that exact message, so it lives with it
   rather than sending the user back up to the bar to guess. */
.results-widen-btn {
  display: inline-block; margin-top: 10px; padding: 8px 14px;
  border: 1.5px solid var(--mint); border-radius: 999px; cursor: pointer;
  background: var(--white); color: var(--mint-deep); font-weight: 700; font-size: 13.5px;
  font-family: inherit;
}
.results-widen-btn:hover { background: var(--mint); color: var(--white); }
.addr-results {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 40;
  background: var(--white); border: 1.5px solid var(--line-2); border-radius: 13px;
  box-shadow: var(--shadow); padding: 6px; max-height: 240px; overflow-y: auto;
}
.addr-results em { display: block; padding: 8px 10px; color: var(--ink-soft); font-size: 13.5px; }
.addr-result {
  display: block; width: 100%; text-align: left; background: none; border: none;
  padding: 9px 10px; border-radius: 9px; font-size: 13.5px; color: var(--ink);
  cursor: pointer; font-family: var(--font-body);
}
.addr-result:hover { background: var(--tile); }
.sport-chip {
  border: 1.5px solid var(--line-2); background: var(--white); color: var(--ink);
  border-radius: 999px; padding: 7px 14px; font-size: 13px; font-weight: 700;
  font-family: var(--font-display); cursor: pointer; transition: .15s;
}
.sport-chip:hover { background: var(--tile); }
.sport-chip.active { background: var(--mint); border-color: var(--mint); color: #04231f; }

/* ---------- Quick filter rail (map.php, under the search shelf) ----------
   Sport and cleanliness, one tap away, on the widths where the filters panel
   is a drawer you have to open rather than a rail that is simply there.

   Above 1100px it is hidden: the full panel is already on screen at that
   width, and a second copy of two of its rows beside it is just noise.     */
.sp-filter-rail { display: none; }

@media (max-width: 1099px) {
  .sp-filter-rail {
    display: flex; align-items: center; gap: 6px;
    flex: 0 0 auto;                 /* a fixed band in the page's flex column */
    padding: 8px 14px;
    background: rgba(242,246,247,.94);
    border-bottom: 1px solid var(--line);
    /* Horizontal scroll is the whole idea: more filters than fit is normal
       here, and chips running off the right edge is the affordance that says
       so. overflow-y must be hidden, not auto, or some browsers give the
       band a vertical scrollbar it has no room for. */
    overflow-x: auto; overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    scroll-padding-left: 14px;
    /* `contain: paint` is load-bearing, not an optimisation. Without it the
       chips that sit beyond the right edge of this scroller still counted
       toward the DOCUMENT's scroll width — the whole page picked up 114px of
       sideways scroll on a 375px screen, which is the exact class of bug this
       rework exists to remove. overflow-x: auto alone did not stop it and
       neither did overflow-x: hidden on the body; containment does, because
       it promises the browser that nothing inside this box can affect layout
       or painting outside it.

       Verified at 320/375/390/600/768/859px: page scrollX stays 0 while the
       rail itself still scrolls its full 971px of chips. */
    contain: paint;
    /* A horizontal swipe that runs out of chips must not turn into the
       browser's back gesture. */
    overscroll-behavior-x: contain;
  }
  .sp-filter-rail::-webkit-scrollbar { display: none; }
}

/* Chips never squeeze — they scroll. Without flex-shrink: 0 a long rail
   compresses every chip until the labels wrap two characters wide. */
.rail-chip { flex: 0 0 auto; min-height: 36px; }
/* Emoji-only sport chips need the glyph big enough to read at a glance, and
   even padding so they come out round rather than oval. */
.rail-chip[data-sport]:not([data-sport="all"]) { padding: 7px 11px; font-size: 15px; }

/* Sticky at the left edge so the way to the full panel never scrolls out of
   reach. The chips pass underneath it, which is why it carries its own
   opaque background and a fade on its trailing edge. */
.rail-more {
  position: sticky; left: 0; z-index: 2; flex: 0 0 auto;
  min-height: 36px; padding: 7px 13px;
  border: 1.5px solid var(--ink); border-radius: 999px;
  background: var(--ink); color: var(--white);
  font-family: var(--font-display); font-size: 13px; font-weight: 800;
  cursor: pointer; white-space: nowrap;
  box-shadow: 6px 0 8px -4px rgba(242,246,247,.94);
}
.rail-more:hover { background: #0d1a1f; }
.rail-more:focus-visible { outline: 3px solid var(--mint); outline-offset: 2px; }

.rail-div {
  flex: 0 0 1px; align-self: stretch; margin: 3px 5px;
  background: var(--line-2);
}
.rail-label {
  flex: 0 0 auto; padding-right: 1px;
  font-size: 11px; font-weight: 800; letter-spacing: .07em;
  text-transform: uppercase; color: var(--ink-soft); white-space: nowrap;
}
/* The "22 of 22 pins" readout is no longer shown on any viewport — it cost a
   row of listing space and the list itself already makes the count obvious.
   It stays in the DOM, clipped rather than display:none, so map.js can keep
   writing to it and its aria-live update is still announced to screen readers. */
.pin-count {
  position: absolute; width: 1px; height: 1px;
  margin: 0; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
.pin-popup .pin-sport { margin: 0 0 0.3rem; font-weight: 700; font-size: 12.5px; color: var(--mint-deep); }
.pin-form-wrap { margin-top: 1rem; }
.poop-scale small { color: var(--ink-soft); }
/* Location names always read as Title Case.
   Done in CSS, not in the database, on purpose: the name people typed
   stays intact for search, for the moderation log, and for the day you
   decide you want normal case back. text-transform only changes how the
   glyphs are drawn — a screen reader still announces the real name.
   `capitalize` only touches the first letter of each word, so names that
   are genuinely all-caps (YMCA, CVS) keep their own casing. */
.pin-popup h3,
.result-card h3,
.profile-favs .fav-main a strong,
.team-pins a strong {
  text-transform: capitalize;
}
.pin-popup h3 { margin: 0 0 0.3rem; }
.pin-popup .stale { color: var(--warn); font-size: 0.8rem; }

/* ---------- Public bathroom page (/bathroom/42-slug) ---------- */
.breadcrumb { font-size: 13px; color: var(--ink-soft); margin: 0 0 .6rem; }
.breadcrumb a { color: var(--ink-soft); text-decoration: none; }
.breadcrumb a:hover { color: var(--mint-deep); text-decoration: underline; }
.pin-page-title { margin: 0 0 .2rem; text-transform: capitalize; line-height: 1.15; }
.pin-page-sub { margin: 0 0 1rem; color: var(--ink-soft); font-weight: 600; font-size: 14px; }
.pin-page-flag {
  margin: .4rem 0; padding: .55rem .7rem; border-radius: 10px;
  font-size: .9rem; line-height: 1.4;
}
.pin-page-flag.disputed { background: #FDEDEC; border: 1px solid var(--danger); color: #9c3733; }
.pin-page-flag.stale    { background: #FFF6E5; border: 1px solid var(--amber); color: #7a5510; }
.pin-page-rating { font-size: 1.15rem; margin: .2rem 0 .8rem; }
.pin-page-facts { list-style: none; padding: 0; margin: 0 0 1rem; }
.pin-page-facts li { padding: 7px 0; border-bottom: 1px solid var(--line); font-size: 14.5px; }
.pin-page-facts li:last-child { border-bottom: 0; }
.pin-page-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; }

/* Current Condition (2/3) beside Save & Share (1/3), stacking on a phone.
   Same grid pattern as .profile-columns and .team-columns, and the same
   1.25rem gap as the default .card margin-bottom, so every white box on
   the page is separated by one spacing value. align-items:start keeps the
   short save/share card from stretching to the height of the report. */
.pin-page-columns {
  display: grid; grid-template-columns: 1fr;
  gap: 1.25rem; margin-bottom: 1.25rem; align-items: start;
}
.pin-page-columns > .card { margin-bottom: 0; }

/* The right-hand column is a stack: Save & Share, then the claim box on a
   business pin nobody owns. It is a flex column rather than two more grid
   children because grid auto-placement would drop the second one back
   under the report in column ONE — the one place the claim box must not
   be, since its whole argument is about the rating sitting beside it. */
.pin-page-side { display: flex; flex-direction: column; gap: 1.25rem; }
.pin-page-side > .card { margin-bottom: 0; }

@media (min-width: 900px) {
  .pin-page-columns { grid-template-columns: 2fr 1fr; }
  /* A third of the width is too narrow for the buttons to wrap gracefully
     two-and-one, so in that column they stack full width instead. Below
     900px the card is full width again and the default wrapping row is
     the better shape, which is why this lives inside the query. */
  .pin-page-save .pin-page-actions,
  .pin-claim-card .pin-page-actions { flex-direction: column; align-items: stretch; }
  .pin-page-save .pin-page-actions .btn,
  .pin-claim-card .pin-page-actions .btn {
    width: 100%; display: inline-flex; align-items: center; justify-content: center;
  }
}
/* .pin-page-dispatches / .dispatch-* removed Aug 18: the Dispatches card
   was merged into Announcements and those comments now render with the
   .announce-item / .announce-body / .announce-meta rules below. */
.pin-page-foot { margin-top: 1.2rem; }
.pin-page-foot a { color: var(--mint-deep); text-decoration: none; font-weight: 700; }

/* A button that reads as a link. Used for Report / Remove so the action
   is a real <button> for keyboards and screen readers without looking
   like a call to action it isn't. */
.linklike {
  background: none; border: 0; padding: 0; margin: 0;
  color: var(--ink-soft); font: inherit; font-size: 12.5px;
  text-decoration: underline; cursor: pointer;
}
.linklike:hover { color: var(--danger); }

/* Favorite button in its saved state */
.btn.is-on { background: #FFF6E5; border-color: var(--amber); color: #7a5510; }

/* Favorite button on the bathroom page. Wears the SAME heart and the same
   mint as the map popup and My Favorites — the amber .btn.is-on above is
   the generic pressed-button look and is deliberately overridden here, so
   one action doesn't change colour depending on which page you're on. */
.btn.btn-fav { display: inline-flex; align-items: center; gap: 7px; }
.btn.btn-fav .fav-heart { flex: 0 0 auto; }
.btn.btn-fav.is-on {
  background: var(--tile);
  border-color: var(--mint);
  color: var(--mint-deep);
}
.btn.btn-fav.is-on:hover { border-color: var(--mint-deep); color: var(--mint-deep); }

/* ---------- Announcements ---------- */
.announce-list { list-style: none; padding: 0; margin: 0 0 1rem; }
.announce-item {
  padding: .6rem .7rem; margin-bottom: .5rem;
  background: #FFF6E5; border: 1px solid var(--amber); border-radius: 10px;
}
.announce-body { margin: 0 0 .25rem; font-weight: 600; }
.announce-meta { margin: 0; color: #7a5510; font-size: 12.5px; }

/* Same list, two lifetimes. A notice posted with a "Show for" duration is
   time-sensitive and wears the amber above; a note filed with a rating is
   permanent and wears this quieter version. Amber on every row would make
   a page with a dozen old notes read as a dozen alarms, and the reader
   would stop seeing the ones that matter. Same shape, same spacing, so it
   still reads as one list rather than two sections in a trench coat. */
.announce-item.is-note { background: var(--porcelain); border-color: var(--line-2); }
.announce-item.is-note .announce-body { font-weight: 500; }
.announce-item.is-note .announce-meta { color: var(--ink-soft); }
.announce-form { border-top: 1px solid var(--line); padding-top: .8rem; }
.announce-form label { display: block; font-weight: 700; font-family: var(--font-display); font-size: 13px; }
.announce-form textarea {
  width: 100%; margin: .3rem 0 .5rem; padding: 8px 10px;
  border: 1px solid var(--line-2); border-radius: 10px;
  font-family: var(--font-body); font-size: 14px; resize: vertical;
}
.announce-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.announce-days { font-weight: 600; font-size: 13px; color: var(--ink-soft); }
.announce-days select { margin-left: 4px; padding: 5px 8px; border-radius: 8px; border: 1px solid var(--line-2); }
.announce-hint { margin: .5rem 0 0; color: var(--ink-soft); }

/* ---------- History timeline ---------- */
.history-list { list-style: none; padding: 0; margin: 0; }
.history-item {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
.history-item:last-child { border-bottom: 0; }
.history-icon { flex: 0 0 auto; font-size: 15px; line-height: 1.5; }
.history-body { min-width: 0; }
.history-text { display: block; font-size: 14px; }
.history-meta { display: block; color: var(--ink-soft); font-size: 12.5px; }

/* ---------- Slimmed map popup ---------- */
.pin-popup-slim .pin-score { margin: .1rem 0 .35rem; }
.pin-title-link { color: inherit; text-decoration: none; }
.pin-title-link:hover { color: var(--mint-deep); text-decoration: underline; }
.pin-act-primary {
  background: var(--mint-deep); border-color: var(--mint-deep); color: #fff;
}
.pin-act-primary:hover { background: #0b6f63; border-color: #0b6f63; color: #fff; }

/* ---------- Profile ---------- */
.profile-card { text-align: left; }
.profile-stats { font-weight: 700; color: var(--mint-deep); }

/* Profile / My Teams two-column layout.
   Single column on phones (profile first, teams below); side by side once
   there's room, matching the .team-columns pattern used on team.php.
   One spacing value (1.25rem, same as the default .card margin) drives the
   column gap, the gap between cards inside a column and the gap between the
   two grid blocks, so every white box is separated by the same amount. */
/* .biz-columns is the same shape under a name that reads honestly on
   business.php — an alias, not a copy, so the two pages can never drift
   apart visually. */
.profile-columns, .biz-columns { display: grid; grid-template-columns: 1fr; gap: 1.25rem; margin-bottom: 1.25rem; }
@media (min-width: 900px) {
  .profile-columns, .biz-columns { grid-template-columns: 1.6fr 1fr; align-items: start; }
}
.profile-col-main, .profile-col-side,
.biz-col-main, .biz-col-side { display: grid; gap: 1.25rem; align-content: start; }
.profile-col-main > .card, .profile-col-side > .card,
.biz-col-main > .card, .biz-col-side > .card { margin-bottom: 0; }

/* My Activity block inside the profile card */
.activity-head {
  font-family: var(--font-display); font-weight: 800; font-size: 13px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-soft);
  margin: 18px 0 10px; padding-top: 14px; border-top: 1px solid var(--line);
}
.activity-stats {
  list-style: none; padding: 0; margin: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px;
}
.activity-stats li {
  background: var(--tile); border: 1px solid var(--line);
  border-radius: 14px; padding: 12px 14px;
}
.activity-num {
  display: block; font-family: var(--font-display); font-weight: 800;
  font-size: 20px; color: var(--mint-deep); line-height: 1.2;
}
.activity-lbl { display: block; font-size: 13px; color: var(--ink-soft); margin-top: 2px; }
.activity-note { font-size: 12.5px; color: var(--ink-soft); margin: 10px 0 0; }
.danger-zone { border-color: var(--danger); }
.danger-zone h2 { color: var(--danger); }

/* ---------- Footer (matches .site-foot) ---------- */
.site-footer { background: var(--ink); color: #cdd9dd; margin-top: 2.5rem; }
.footer-inner { max-width: 980px; margin: 0 auto; padding: 40px 20px 30px; text-align: center; }
.footer-brand { font-family: var(--font-display); font-weight: 800; font-size: 20px; color: #fff; margin: 0 0 0.4rem; }
.footer-links a { color: #cdd9dd; text-decoration: none; font-size: 14px; }
.footer-links a:hover { color: #fff; }
.footer-note, .footer-copy { font-size: 12.5px; color: #7f9298; margin: 0.4rem 0 0; }

/* ---------- Footer social icons ----------
   Markup comes from includes/social_icons.php (inline SVG, fill=currentColor,
   so `color` drives everything below).

   Resting state is the same muted gray as .footer-links; each icon fades to
   its real brand color on hover/focus. The 44x44 box is deliberate — it is
   the minimum touch target both Apple and WCAG 2.5.5 ask for, and these sit
   at the bottom of the page where a thumb reach is least precise. The icon
   itself stays 22px; the extra space is invisible padding.                */
.footer-social {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin: 1rem 0 0.5rem;
}
.footer-social .social-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  color: #cdd9dd;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: color .18s ease, background-color .18s ease, transform .18s ease;
}
.footer-social .social-icon svg { display: block; }

.footer-social .social-icon:hover,
.footer-social .social-icon:focus-visible {
  background: rgba(255, 255, 255, .08);
  transform: translateY(-2px);
}

/* Keyboard focus must stay visible even though hover shares the styling
   above — a mouse user gets the lift, a keyboard user also gets the ring. */
.footer-social .social-icon:focus-visible {
  outline: 2px solid var(--mint);
  outline-offset: 2px;
}

/* Brand colors on hover/focus.
   X is pure white rather than its #000 brand black, which would vanish
   against the dark ink footer. */
.footer-social .social-x:hover,
.footer-social .social-x:focus-visible         { color: #FFFFFF; }
.footer-social .social-facebook:hover,
.footer-social .social-facebook:focus-visible  { color: #1877F2; }
.footer-social .social-tiktok:hover,
.footer-social .social-tiktok:focus-visible    { color: #25F4EE; }
.footer-social .social-youtube:hover,
.footer-social .social-youtube:focus-visible   { color: #FF0000; }

/* Instagram's brand mark is a gradient, not a flat color. An SVG filled with
   currentColor can't hold a gradient, so the gradient is painted on the link
   and clipped to the glyph. background-clip: text needs the -webkit- prefix
   to work in Safari and Chrome, and the fallback below the @supports guard
   keeps a solid pink anywhere the clip isn't supported. */
.footer-social .social-instagram:hover,
.footer-social .social-instagram:focus-visible { color: #E1306C; }

@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .footer-social .social-instagram:hover svg,
  .footer-social .social-instagram:focus-visible svg {
    background: linear-gradient(45deg, #F58529, #DD2A7B 45%, #8134AF 75%, #515BD4);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}

/* Respect the OS "reduce motion" setting: color still changes (that's the
   affordance), but the lift and the easing are dropped. */
@media (prefers-reduced-motion: reduce) {
  .footer-social .social-icon { transition: none; }
  .footer-social .social-icon:hover,
  .footer-social .social-icon:focus-visible { transform: none; }
}

/* Devices with no hover (phones, tablets): show the brand colors at rest,
   since a hover state there is either unreachable or fires only after a tap
   has already started navigating away. */
@media (hover: none) {
  .footer-social .social-x         { color: #FFFFFF; }
  .footer-social .social-facebook  { color: #1877F2; }
  .footer-social .social-instagram { color: #E1306C; }
  .footer-social .social-tiktok    { color: #25F4EE; }
  .footer-social .social-youtube   { color: #FF0000; }
}

/* ---------- Admin ---------- */
.admin-body { background: var(--porcelain); }
.admin-layout { display: flex; flex-direction: column; min-height: 100vh; }
.admin-content { flex: 1; padding: 1.25rem 20px; max-width: 1100px; width: 100%; margin: 0 auto; }
.admin-header { background: var(--ink); position: static; border-bottom: none; backdrop-filter: none; }
.admin-header .brand, .admin-header .brand-name { color: #fff; }
.admin-nav { background: var(--mint-deep); }
.admin-nav ul { list-style: none; display: flex; flex-wrap: wrap; margin: 0; padding: 0; }
.admin-nav a { display: block; padding: 10px 16px; color: #fff; text-decoration: none; font-weight: 600; font-size: 14px; }
.admin-nav a:hover, .admin-nav a.active { background: var(--mint); color: #04231f; }
.data-table { width: 100%; border-collapse: collapse; background: var(--white); font-size: 13.5px; border-radius: 12px; }
.data-table th, .data-table td { border: 1px solid var(--line); padding: 8px 10px; text-align: left; vertical-align: top; }
.data-table th { background: var(--ink); color: #fff; position: sticky; top: 0; }
.data-table tr:nth-child(even) { background: var(--porcelain); }
.table-scroll { overflow-x: auto; }
.stat-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); margin-bottom: 1.5rem; }
.stat-card { background: var(--white); border: 1px solid var(--line); border-radius: var(--radius); padding: 18px; text-align: center; }
.stat-card .num { font-family: var(--font-display); font-size: 2rem; font-weight: 800; color: var(--mint-deep); display: block; }

/* ---- Claim review, seen from the admin panel --------------------------
   Three surfaces, one colour. Amber is already this app's "hold on a
   second"; a claim awaiting review is exactly that and nothing stronger,
   so it deliberately does NOT borrow --danger. Nothing is broken and
   nobody is in trouble — a business is waiting on you. */
.stat-card-alert { border-color: #E1A100; background: #FFF8E6; }
.stat-card-alert .num { color: #8a6100; }
.admin-claim-banner {
  margin: 0 0 1.25rem; padding: 11px 14px; border-radius: 10px;
  background: #FFF8E6; border: 1.5px solid #E1A100; color: #6b4c12;
  font-size: 14px; line-height: 1.45;
}
.admin-claim-banner a { color: #6b4c12; font-weight: 800; }
/* The per-row chip. Small, because in a list of listings the row's own
   name is what you are scanning for and this is the exception note. */
.claim-chip {
  display: inline-block; margin-top: 3px; padding: 1px 7px; border-radius: 999px;
  background: #FFF1CC; border: 1px solid #E1A100; color: #6b4c12;
  font-size: 11px; font-weight: 700; white-space: nowrap;
}
/* The nav badge. The one count in this panel with a business waiting at
   the other end of it, so it is visible from every admin page rather than
   only from the one you would have to think to open. */
.admin-nav-badge {
  display: inline-block; margin-left: 6px; min-width: 18px; padding: 0 5px;
  border-radius: 999px; background: #E1A100; color: #241a00;
  font-size: 11px; font-weight: 800; line-height: 18px; text-align: center;
  vertical-align: middle;
}
.badge { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 700; }
.badge-green { background: #e2f5f1; color: var(--mint-deep); }
.badge-red { background: #fdecec; color: #a23b38; }
.badge-amber { background: #fff8e8; color: var(--warn); }
/* Two more badge colours so the admin Contact Inbox can give each contact
   reason its own tint. Three colours made "support" and "PR" share one,
   which defeats the point of colouring the column at all. */
.badge-blue { background: #e9f0fd; color: #2b5599; }
.badge-slate { background: #eef0f3; color: var(--ink-soft); }
.inline-form { display: inline; }

/* ---------- Desktop ---------- */
@media (min-width: 860px) {
  .nav-toggle { display: none; }
  /* Nav sits right-aligned in the sticky header bar (matches app.php) */
  .site-nav { flex-basis: auto; margin-left: auto; }
  .site-nav > ul { display: flex; flex-direction: row; align-items: center; gap: 4px; padding: 0; }
  .site-nav a.nav-cta { margin: 0 0 0 6px; }

  /* ---- About's dropdown ----
     The parent <li> is the hover target, so the pointer can travel from the
     word "About" into the panel without passing over dead space and closing
     it — which is why the panel is anchored to the <li> and not offset below
     the link with a gap. */
  .site-nav .has-sub { position: relative; }
  .sub-caret { display: inline; }
  .sub-nav {
    position: absolute; top: 100%; left: 0; z-index: 1200;
    min-width: 190px; padding: 6px;
    background: var(--white); border: 1px solid var(--line-2);
    border-radius: 14px; box-shadow: var(--shadow);
    display: none;
  }
  /* Hover for the mouse; .is-open for everything else.

     :focus-within used to be here and had to go. It reopened the panel the
     instant Escape returned focus to the button, so Escape set
     aria-expanded="false" over a panel that was still on screen — the
     accessibility tree and the pixels disagreeing, which is worse than
     either being wrong on its own. Keyboard support now runs entirely
     through the button and main.js, which is the ordinary disclosure
     pattern: Enter opens, Tab walks the links, Escape closes and hands
     focus back.

     .is-open is also what makes this work on a touch device at this width —
     an iPad in landscape has no hover, and About has no page of its own to
     fall back to any more. */
  .site-nav .has-sub:hover > .sub-nav,
  .site-nav .has-sub.is-open > .sub-nav { display: block; }
  .nav-sub-toggle { width: auto; padding: 9px 12px; }

  .sub-nav a { padding: 9px 12px; white-space: nowrap; }
  /* The vertical rule is the phone layout's way of showing nesting. In a
     panel that is already visibly a panel, it is just a line. */
  .sub-nav > li::before { display: none; }
}

/* ================================================================
   Signup / profile additions: social sign-in, name row, disclaimer,
   member-since badge
   ================================================================ */
.social-auth {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
}
.social-auth .social-btn-slot { min-height: 44px; }
.btn-apple {
  background: #000;
  color: var(--white);
  border: 1px solid #000;
  border-radius: 6px;
  padding: 10px 22px;
  min-width: 280px;
  font-size: 1rem;
  cursor: pointer;
}
.btn-apple:hover { background: #1a1a1a; }
.social-note {
  font-size: .8rem;
  color: var(--ink-soft);
  margin: 0;
  text-align: center;
}
.auth-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 14px 0;
  color: var(--ink-soft);
  font-size: .85rem;
}
.auth-divider::before,
.auth-divider::after {
  content: "";
  flex: 1;
  border-top: 1px solid var(--line-2);
}
.field-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
@media (max-width: 480px) {
  .field-row { grid-template-columns: 1fr; }
}
.checkbox-disclaimer {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: .88rem;
  color: var(--ink-soft);
  margin: 12px 0;
}
.checkbox-disclaimer input { margin-top: 3px; flex-shrink: 0; }
.member-since {
  text-align: center;
  color: var(--ink-soft);
  font-size: .95rem;
  margin: 28px 0 8px;
}

/* ============================================================
   MY TEAMS
   ============================================================ */
.lead { color: var(--ink-soft); max-width: 60ch; }
.muted { color: var(--ink-soft); }
.hint { color: var(--ink-soft); font-size: .85rem; margin: 6px 0; }
.crumb { margin: 0 0 8px; }
.crumb a { color: var(--mint-deep); text-decoration: none; font-weight: 600; }

.team-empty { text-align: center; color: var(--ink-soft); }

.code-input {
  text-transform: uppercase;
  letter-spacing: .18em;
  font-family: var(--font-display);
  font-weight: 700;
}

.team-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 16px;
  margin: 18px 0 28px;
}
.team-card {
  display: block;
  text-decoration: none;
  color: var(--ink);
  transition: transform .12s ease, box-shadow .12s ease;
}
.team-card:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
.team-card h2 { margin: 6px 0 4px; }
.team-sport {
  display: inline-block;
  font-size: .8rem;
  font-weight: 700;
  color: var(--mint-deep);
  background: var(--tile);
  border-radius: 999px;
  padding: 2px 10px;
}
.team-meta { color: var(--ink-soft); font-size: .9rem; margin: 4px 0 0; }
.team-badge {
  display: inline-block;
  font-size: .78rem;
  font-weight: 700;
  color: var(--clay);
}

/* team page layout */
.team-head { margin-bottom: 8px; }
.team-head h1 { margin: 8px 0 4px; }
.team-columns { display: grid; grid-template-columns: 1fr; gap: 18px; }
@media (min-width: 900px) {
  .team-columns { grid-template-columns: 1.6fr 1fr; align-items: start; }
}
.team-col-main > .card, .team-col-side > .card { margin-bottom: 18px; }

/* .team-invite (the old full-width invite card) is gone — invite now lives
   inside the Members tile as a collapsible block. See .invite-block below. */
.invite-row { display: flex; gap: 8px; margin: 10px 0 6px; }
.invite-row input {
  flex: 1; min-width: 0;
  font-size: .9rem;
  padding: 10px 12px;
  border: 1px solid var(--line-2);
  border-radius: 12px;
  background: var(--white);
  color: var(--ink);
}
.invite-code { color: var(--ink-soft); font-size: .9rem; letter-spacing: .04em; }

/* Admin-only "rename this team" panel under the team header */
.team-edit { margin-top: 10px; }
.team-edit > summary {
  display: inline-block;
  cursor: pointer;
  font-size: .9rem;
  font-weight: 600;
  color: var(--mint-deep);
  padding: 6px 12px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: var(--white);
  list-style: none;
}
.team-edit > summary::-webkit-details-marker { display: none; }
.team-edit[open] > summary { margin-bottom: 12px; }
.team-edit-form {
  max-width: 460px;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--tile);
}
.team-edit-form label { display: block; margin-bottom: 10px; font-size: .9rem; font-weight: 600; }
.team-edit-form input[type="text"],
.team-edit-form select { width: 100%; font-weight: 400; }
.team-edit-form .form-actions { margin-top: 4px; }
.team-edit-form .hint { margin: 8px 0 0; }

.team-compose textarea,
.team-compose input[type="text"],
.team-compose select { width: 100%; }
.team-compose .form-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; }

.team-feed { list-style: none; padding: 0; margin: 0; }
.feed-item {
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 14px;
  margin-bottom: 10px;
  background: var(--white);
}
.feed-item.feed-alert { border-left: 4px solid var(--danger); background: #fff7f6; }
.feed-item.feed-pin   { border-left: 4px solid var(--mint); }
.feed-body { line-height: 1.5; }
.feed-meta { display: block; color: var(--ink-soft); font-size: .8rem; margin-top: 6px; }
.feed-pin-link { display: inline-block; margin-top: 6px; color: var(--mint-deep); font-weight: 600; text-decoration: none; font-size: .9rem; }

.team-pins, .team-members, .profile-teams { list-style: none; padding: 0; margin: 0; }
.team-pins li, .profile-teams li { padding: 8px 0; border-bottom: 1px solid var(--line); }
.team-pins li:last-child, .profile-teams li:last-child { border-bottom: 0; }
.team-pins a, .profile-teams a { color: var(--ink); text-decoration: none; }
.pin-note { color: var(--ink-soft); }

.team-members li {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; padding: 8px 0; border-bottom: 1px solid var(--line);
}
.team-members li:last-child { border-bottom: 0; }
.mem-you {
  font-size: .72rem; font-weight: 700; color: var(--mint-deep);
  background: var(--tile); border-radius: 999px; padding: 1px 8px; margin-left: 4px;
}
.mem-remove { margin: 0; }

/* ---------- Invite, inside the Members tile ----------
   The bottom half of the members card: a full-width button that opens to
   reveal the link, the code and (for admins) the reset. Collapsed by
   default because a team is assembled once and used for a season — the
   roster is the standing fact, inviting is the occasional act. */
.invite-block {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}
.invite-summary {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  cursor: pointer;
  list-style: none;
  min-height: 40px;
  padding: 8px 14px;
  border-radius: 12px;
  border: 1.5px solid var(--mint);
  background: var(--tile);
  color: var(--mint-deep);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 14px;
  transition: .15s;
}
/* Safari/older WebKit draw their own disclosure triangle; ours is the ▾. */
.invite-summary::-webkit-details-marker { display: none; }
.invite-summary::marker { content: ''; }
.invite-summary:hover { background: var(--white); border-color: var(--mint-deep); }
.invite-summary:focus-visible { outline: 2px solid var(--mint-deep); outline-offset: 2px; }

.invite-chevron { transition: transform .15s ease; font-size: 12px; }
.invite-block[open] .invite-summary { border-radius: 12px 12px 0 0; }
.invite-block[open] .invite-chevron { transform: rotate(180deg); }

.invite-body {
  border: 1.5px solid var(--mint);
  border-top: 0;
  border-radius: 0 0 12px 12px;
  padding: 12px 14px 14px;
  background: var(--white);
}
.invite-body .hint { margin-top: 0; }
.invite-body .invite-code { margin-bottom: 10px; }

/* The side column is narrow, so the link and Copy stack rather than
   squeezing the URL into an unreadable sliver. */
.team-col-side .invite-row { flex-wrap: wrap; }
.team-col-side .invite-row input { flex: 1 1 100%; }
.team-col-side .invite-row .btn { flex: 1 1 auto; }
.link-danger {
  background: none; border: 0; cursor: pointer;
  color: var(--danger); font-size: 1rem; line-height: 1; padding: 4px;
}

.teams-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; }
.teams-head h2 { margin: 0; }
.teams-head { margin-bottom: 14px; }

/* ---------- My Business card (profile.php side column) ----------
   Deliberately the same .card as My Teams and My Favorites beside it — this
   is one more thing you own, not a promotion. The only thing setting it apart
   is the mint top edge, which the pitch state drops: an empty slot advertising
   itself louder than the filled one gets it exactly backwards. */
.biz-card { border-top: 4px solid var(--mint); }
.biz-card-pitch { border-top-color: var(--line-2); }
.biz-ident { display: flex; align-items: center; gap: 12px; }
.biz-ident-text { min-width: 0; }
.biz-ident-text strong { display: block; font-size: 15.5px; line-height: 1.3; }
/* Two lines, then ellipsis. A long street address is reassurance ("yes, this
   is my listing"), not something anyone reads to the end — left uncapped it
   pushes the buttons down the card and, on a narrow phone, off the fold. */
.biz-ident-text .feed-meta {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Square, not a circle: this is the same artwork the map draws as a rounded
   square logo pin, and a round crop here would promise a marker shape the
   map does not use. */
.biz-logo {
  flex: 0 0 44px; width: 44px; height: 44px; border-radius: 11px;
  object-fit: cover; background: var(--white); border: 2px solid var(--mint-deep);
}
.biz-logo-fallback {
  display: flex; align-items: center; justify-content: center;
  font-size: 21px; border-color: var(--line-2);
}
.biz-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.biz-pitch-lede { font-weight: 700; font-size: 15.5px; margin: 0 0 4px; }
.biz-card-pitch .btn { margin-top: 12px; }

/* Saved bathrooms on the profile page */
.profile-favs { list-style: none; padding: 0; margin: 0; }
.profile-favs li {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 0; border-bottom: 1px solid var(--line);
}
.profile-favs li:last-child { border-bottom: 0; }
.profile-favs .fav-main { min-width: 0; }
.profile-favs a { color: var(--ink); text-decoration: none; }
.profile-favs a:hover { color: var(--mint-deep); text-decoration: underline; }
.fav-remove { margin: 0; flex: 0 0 auto; }

/* ---------- Favorite heart, non-map surfaces ----------
   Same shape and same mint as the heart in the map popup (see heartIcon()
   in includes/functions.php and HEART in assets/js/map.js). In the
   favorites list the FILLED heart is the remove control: filled means
   saved, tapping a filled heart un-saves it — the identical gesture the
   map already taught, rather than a second word for it. */
.fav-heart-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  min-height: 34px;
  padding: 6px;
  border: 1.5px solid var(--mint);
  border-radius: 9px;
  background: var(--tile);
  color: var(--mint);
  cursor: pointer;
  transition: .15s;
}
.fav-heart-btn .fav-heart { display: block; }
.fav-heart-btn:hover {
  color: var(--mint-deep);
  border-color: var(--mint-deep);
}
.fav-heart-btn:focus-visible { outline: 2px solid var(--mint-deep); outline-offset: 2px; }

/* Heading + inline-in-a-sentence hearts: the icon, not a button. */
.fav-title { display: flex; align-items: center; gap: 7px; }
.fav-title-heart { color: var(--mint); display: inline-flex; }
.fav-inline-heart {
  color: var(--mint);
  display: inline-flex;
  vertical-align: -3px;
}

/* In the narrow side column the name + rating meta and the Remove button
   won't sit on one line comfortably, so let the row wrap. */
.profile-col-side .profile-favs li { flex-wrap: wrap; align-items: flex-start; gap: 8px; }
.profile-col-side .profile-favs .fav-main { flex: 1 1 100%; }
.profile-col-side .fav-remove { margin-left: auto; }

/* invite landing */
.team-invite-landing { text-align: center; }
.team-invite-landing .team-sport { margin-bottom: 10px; }
.invite-team-name { color: var(--mint-deep); }
.invite-actions { display: flex; flex-direction: column; gap: 10px; margin: 18px 0 10px; }
@media (min-width: 560px) { .invite-actions { flex-direction: row; justify-content: center; } }

/* ================================================================
   Business listings on the map (Stripe: logo / premium plans)
   ================================================================ */

/* Distinct pin for a free business listing */
.sp-marker { background: none; border: 0; }
.sp-biz-pin-inner {
  width: 34px; height: 34px; line-height: 34px; text-align: center;
  font-size: 20px; background: #fff; border: 2px solid #0E8C7C;
  border-radius: 50% 50% 50% 0; transform: rotate(-45deg);
  box-shadow: 0 1px 4px rgba(0,0,0,.35);
}
.sp-biz-pin-inner { }
.sp-biz-pin .sp-biz-pin-inner > * { display: inline-block; transform: rotate(45deg); }

/* ---- "No Bathroom Found" marker ----
   No styling needed, and that is the point: it is Leaflet's own marker
   image with the hue rotated to yellow (assets/img/marker-icon-nobath.png,
   generated from leaflet@1.9.4's marker-icon.png), so it inherits every
   rule the standard pin already follows. The class exists purely as a
   hook — do NOT give it a border, shadow or transform here, or the yellow
   pin stops being the same pin as the blue one, which is the whole
   requirement. */

/* Logo shown in a consistent branded box in place of the pin */
.sp-logo-box {
  width: 46px; height: 46px; background: #fff; border: 2px solid #0E8C7C;
  border-radius: 10px; box-shadow: 0 2px 6px rgba(0,0,0,.35);
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
.sp-logo-box img { max-width: 40px; max-height: 40px; display: block; }
/* Premium gets a gold highlight ring */
.sp-logo-pin.sp-premium .sp-logo-box { border-color: #E1A100; box-shadow: 0 0 0 3px rgba(225,161,0,.35), 0 2px 6px rgba(0,0,0,.35); }

/* Business popup.
   Matches the featured result card (see .result-card-featured further down):
   same outline, same corner badge, same logo box, same Directions button.
   The layout classes themselves (.result-biz / .result-logo / .result-addr)
   are shared with the card rather than duplicated here. */
.pin-popup.pin-business {
  position: relative;
  border: 2px solid #0E8C7C;
  box-shadow: 0 0 0 1px #0E8C7C inset;
  border-radius: 8px;
  padding: 1.9rem .6rem .6rem; /* top padding leaves room for the badge */
}
.pin-popup.pin-business-premium { border-color: #E1A100; box-shadow: 0 0 0 1px #E1A100 inset; }
.pin-featured-badge {
  position: absolute; top: 6px; left: 8px;
  background: #0E8C7C; color: #fff;
  font-size: .66rem; font-weight: 700; letter-spacing: .04em;
  padding: 3px 8px; border-radius: 5px; text-transform: uppercase;
}
/* Badge sits LEFT in the popup (it sits right on the card): Leaflet's own
   close button ✕ owns the top-right corner of every popup, and the two
   overlap there. */
.pin-business-premium .pin-featured-badge { background: #E1A100; }
.pin-business .pin-logo { max-width: 120px; max-height: 70px; display: block; margin: 0 0 .35rem; }
.pin-biz-tag { color: #0E8C7C; font-weight: 600; margin: .15rem 0 .4rem; }
.btn-directions {
  display: inline-block; margin-top: .25rem; padding: .3rem .6rem; border-radius: 6px;
  background: #0E8C7C; color: #fff; text-decoration: none; font-size: .9em;
}
.btn-directions:hover { background: #0b6f63; }
/* Leaflet ships `.leaflet-container a { color: #0078A8 }`, which outranks a
   single class and repainted this button's label link-blue and underlined
   inside popups only — the same button read white on the result cards. The
   container-scoped selector below wins it back. */
.leaflet-container a.btn-directions,
.leaflet-container a.btn-directions:hover,
.leaflet-container a.btn-directions:visited {
  color: #fff; text-decoration: none;
}

/* ---------- Verification votes (👍 still accurate / 👎 it's changed) ---------- */
.vote-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin: .45rem 0 .1rem;
}
.vote-q {
  font-family: var(--font-display); font-size: 12px; font-weight: 800;
  color: var(--ink-soft); margin-right: 2px;
}
.vote-btn {
  display: inline-flex; align-items: center; gap: 4px;
  min-height: 32px; padding: 5px 10px; border-radius: 999px;
  border: 1.5px solid var(--line-2); background: var(--white); color: var(--ink);
  font-family: var(--font-display); font-size: 12.5px; font-weight: 700;
  text-decoration: none; cursor: pointer; transition: .15s;
}
.vote-btn:hover { background: var(--tile); border-color: var(--mint); }
.vote-btn:disabled { opacity: .5; cursor: default; }
.vote-btn:focus-visible { outline: 2px solid var(--mint-deep); outline-offset: 2px; }
/* Your own vote reads back clearly — otherwise people tap twice and
   accidentally withdraw the vote they just cast. */
.vote-btn.on { background: #EAF7F4; border-color: var(--mint); color: var(--mint-deep); }
.vote-btn.on.down { background: #FDEDEC; border-color: var(--danger); color: #9c3733; }
.vote-n { font-variant-numeric: tabular-nums; }
.vote-static { font-size: 12.5px; color: var(--ink-soft); font-weight: 600; }

/* Freshness / dispute banners */
.pin-popup .disputed {
  margin: .3rem 0; padding: .4rem .5rem; border-radius: 8px;
  background: #FDEDEC; border: 1px solid var(--danger); color: #9c3733;
  font-size: .82rem; line-height: 1.35;
}
.pin-popup .confirmed { color: var(--mint-deep); font-size: .8rem; margin: .25rem 0; }
.result-votes { margin: .15rem 0; color: var(--ink-soft); }
.result-disputed { margin: .15rem 0; color: var(--danger); font-size: .78rem; font-weight: 700; }

/* ---------- Pin actions: Add to My Team · Favorite · Share ----------
   Rendered inside the Leaflet popup, which is narrow, so the row wraps
   and every control keeps a finger-sized tap target. */
.pin-actions {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin: .55rem 0 .4rem; padding-top: .5rem;
  border-top: 1px solid var(--line);
}
.pin-act {
  flex: 1 1 auto; min-height: 34px;
  display: inline-flex; align-items: center; justify-content: center; gap: 4px;
  padding: 6px 9px; border-radius: 9px;
  border: 1.5px solid var(--line-2); background: var(--white); color: var(--ink);
  font-family: var(--font-display); font-size: 12.5px; font-weight: 700;
  text-decoration: none; cursor: pointer; white-space: nowrap; transition: .15s;
}
.pin-act:hover { background: var(--tile); border-color: var(--mint); }
.pin-act:disabled { opacity: .55; cursor: default; }
.pin-act:focus-visible { outline: 2px solid var(--mint-deep); outline-offset: 2px; }
/* ---------- Favorite heart (map popup) ----------
   Sits beside View Report and Share, so it must NOT stretch the way the
   other two do — it is an icon, and a stretched icon button reads as a
   third label with its text missing. Fixed width, square-ish, and the
   only thing that changes on toggle is the fill. */
.pin-act.fav-btn {
  flex: 0 0 auto;
  width: 40px;
  padding: 6px;
  color: var(--ink-soft);
}
.pin-act.fav-btn .fav-heart { display: block; }
.pin-act.fav-btn:hover { color: var(--mint-deep); border-color: var(--mint); }

/* Saved: mint heart, mint edge. The shape doesn't move — only the fill —
   so the change reads as this bathroom becoming yours rather than as a
   different button appearing where the last one was. */
.pin-act.fav-btn.is-on {
  color: var(--mint);
  border-color: var(--mint);
  background: var(--tile);
}
.pin-act.fav-btn.is-on:hover { color: var(--mint-deep); border-color: var(--mint-deep); }

/* ---------- Share panel (copy link · SMS · email) ---------- */
.share-panel, .team-panel {
  margin: .1rem 0 .5rem; padding: .55rem .6rem;
  border: 1px solid var(--line-2); border-radius: 10px; background: var(--porcelain);
}
.share-url-label {
  display: block; font-size: 11.5px; font-weight: 700; color: var(--ink-soft);
  font-family: var(--font-display); margin-bottom: .35rem;
}
.share-url {
  display: block; width: 100%; margin-top: 3px; padding: 7px 8px;
  border: 1px solid var(--line-2); border-radius: 8px; background: var(--white);
  font-size: 12px; color: var(--ink); font-family: var(--font-body);
}
.share-btns { display: flex; flex-wrap: wrap; gap: 6px; }
.share-btns .btn { flex: 1 1 auto; min-height: 34px; padding: 6px 10px; font-size: 12.5px; }
.share-note { margin: .4rem 0 0; color: var(--ink-soft); }

/* ---------- Team picker ---------- */
.team-panel-head {
  margin: 0 0 .4rem; font-family: var(--font-display);
  font-size: 12.5px; font-weight: 800; color: var(--ink);
}
.team-pick {
  display: block; width: 100%; text-align: left; margin-bottom: 5px;
  min-height: 36px; padding: 7px 9px; border-radius: 9px;
  border: 1.5px solid var(--line-2); background: var(--white); color: var(--ink);
  font-family: var(--font-display); font-size: 12.5px; font-weight: 700; cursor: pointer;
}
.team-pick:hover { background: var(--tile); border-color: var(--mint); }
.team-pick small { font-weight: 600; color: var(--ink-soft); margin-left: 4px; }
/* Already-saved teams stay listed but inert, so the list doesn't shuffle */
.team-pick.done {
  background: #EAF7F4; border-color: var(--mint); color: var(--mint-deep); cursor: default;
}
.team-note-label {
  display: block; margin-top: .4rem; font-size: 11.5px; font-weight: 700;
  color: var(--ink-soft); font-family: var(--font-display);
}
.team-note {
  display: block; width: 100%; margin-top: 3px; padding: 7px 8px;
  border: 1px solid var(--line-2); border-radius: 8px; background: var(--white);
  font-size: 12.5px; font-family: var(--font-body);
}
.team-panel-note { margin: .4rem 0 0; color: var(--ink-soft); }

/* Popups get taller with the action row; let them scroll rather than
   overflow the map on a phone. */
.leaflet-popup-content { max-height: 60vh; overflow-y: auto; }

/* "Try us instead" alert on a rough (>3) bathroom popup */
.rival-alert {
  margin: .5rem 0; padding: .5rem .6rem; border-radius: 8px;
  background: #FFF6E5; border: 1px solid #E1A100; font-size: .92em; line-height: 1.35;
}

/* Business result cards */
.result-biz { display: flex; align-items: center; gap: .5rem; }
.result-logo { width: 40px; height: 40px; object-fit: contain; border: 1px solid #ddd; border-radius: 6px; background: #fff; padding: 2px; }
.result-addr { margin: .15rem 0; color: #444; }

/* Featured business result cards (bold outline + corner label) */
.result-card-featured {
  position: relative;
  border: 2px solid #0E8C7C;
  box-shadow: 0 0 0 1px #0E8C7C inset;
  padding-top: 1.9rem; /* room for the corner badge */
}
.result-card-featured.result-card-premium { border-color: #E1A100; box-shadow: 0 0 0 1px #E1A100 inset; }
.result-featured-badge {
  position: absolute; top: 8px; right: 8px;
  background: #0E8C7C; color: #fff;
  font-size: .66rem; font-weight: 700; letter-spacing: .04em;
  padding: 3px 8px; border-radius: 5px; text-transform: uppercase;
}
.result-card-premium .result-featured-badge { background: #E1A100; }

/* ---------- Residential guard modal ----------
   Shown when someone tries to pin a bathroom at what looks like a private
   home. z-index sits above the pin-form overlay (1200) and the sticky header
   (1100) so it reads as a full stop, not another layer of the same form. */
.res-modal {
  position: fixed; inset: 0; z-index: 1400;
  background: rgba(20,37,43,.55);
  display: flex; align-items: center; justify-content: center;
  padding: 5vh 16px; overflow-y: auto;
}
.res-modal[hidden] { display: none; }
.res-modal-card {
  background: var(--white); border-radius: var(--radius); box-shadow: var(--shadow);
  max-width: 440px; width: 100%; padding: 26px 24px 22px; text-align: center;
  animation: resPop .18s ease-out;
}
@keyframes resPop {
  from { transform: scale(.94) translateY(8px); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .res-modal-card { animation: none; } }
.res-modal-emoji { font-size: 42px; line-height: 1; margin-bottom: 10px; }
.res-modal-title {
  margin: 0 0 10px; font-size: 21px; font-weight: 800; color: var(--ink);
}
.res-modal-body {
  margin: 0 0 20px; font-size: 14.5px; line-height: 1.55; color: var(--ink-soft);
}
.res-modal-actions {
  display: flex; flex-direction: column; gap: 9px;
}
.res-modal-actions .btn { width: 100%; }
.res-modal-actions .btn[hidden] { display: none; }

/* ---------- Duplicate-pin picker ----------
   Shown when a new pin lands within the soft duplicate ring (~400 ft) of
   pins that already exist. Same z-index and backdrop as the residential
   modal — both are "stop before you save this" moments, and they should
   read as the same kind of interruption. Only one can ever be open at a
   time: duplicates are screened first and the residential check runs after,
   so they queue rather than stack. */
.dupe-modal {
  position: fixed; inset: 0; z-index: 1400;
  background: rgba(20,37,43,.55);
  display: flex; align-items: center; justify-content: center;
  padding: 5vh 16px; overflow-y: auto;
}
.dupe-modal[hidden] { display: none; }
.dupe-modal-card {
  background: var(--white); border-radius: var(--radius); box-shadow: var(--shadow);
  max-width: 460px; width: 100%; padding: 26px 24px 22px; text-align: center;
  animation: resPop .18s ease-out;
}
@media (prefers-reduced-motion: reduce) { .dupe-modal-card { animation: none; } }
.dupe-modal-emoji { font-size: 42px; line-height: 1; margin-bottom: 10px; }
.dupe-modal-title {
  margin: 0 0 10px; font-size: 21px; font-weight: 800; color: var(--ink);
}
.dupe-modal-body {
  margin: 0 0 16px; font-size: 14.5px; line-height: 1.55; color: var(--ink-soft);
}

/* The list caps its height rather than growing without limit: at a dense
   venue this can be six or seven pins, and pushing the "different bathroom"
   button off the bottom of a phone screen would hide the answer half the
   users need. */
.dupe-list {
  list-style: none; margin: 0 0 16px; padding: 0;
  text-align: left; max-height: 42vh; overflow-y: auto;
}
.dupe-list li + li { margin-top: 8px; }
.dupe-item {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 11px 13px; border: 1px solid var(--line, #dfe6e9);
  border-radius: 12px; background: var(--white);
  font: inherit; text-align: left; cursor: pointer;
  transition: border-color .12s ease, background .12s ease;
}
.dupe-item:hover, .dupe-item:focus-visible {
  border-color: var(--mint, #12b886);
  background: rgba(18,184,134,.06);
  outline: none;
}
.dupe-item:focus-visible { box-shadow: 0 0 0 3px rgba(18,184,134,.25); }
.dupe-item-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.dupe-item-name {
  font-weight: 700; font-size: 14.5px; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dupe-item-meta { font-size: 12.5px; color: var(--ink-soft); }
/* The distance is the field people actually compare, so it never wraps and
   never gets squeezed out by a long pin name. */
.dupe-item-dist {
  flex: none; font-size: 13px; font-weight: 700; color: var(--ink-soft);
  white-space: nowrap;
}
.dupe-modal-actions { display: flex; flex-direction: column; gap: 9px; }
.dupe-modal-actions .btn { width: 100%; }
.dupe-modal-actions .btn[hidden] { display: none; }

/* ============================================================
   Legal: Terms of Service page, footer disclaimer, signup modal
   ============================================================ */

/* ---------- Footer disclaimer ----------
   Deliberately a step brighter than .footer-note and .footer-copy around
   it. Those are decoration; this is the sentence that says our ratings
   are one person's opinion at one moment and not a health inspection.
   Styled to be read, not to be tidy. */
.footer-disclaimer {
  font-size: 12.5px;
  line-height: 1.55;
  color: #a8b8bd;
  margin: 0.9rem auto 0;
  max-width: 620px;
}

/* ---------- Compact footer (map page only) ----------
   One slim bar under the map. Height is whatever the text needs — the
   flex column above it adapts, so this can wrap freely on a phone
   without pushing anything off screen. */
.site-footer-compact {
  background: var(--ink);
  color: #cdd9dd;
  margin-top: 0;
  padding: 10px 20px;
  text-align: center;
  border-top: 1px solid rgba(255,255,255,.08);
}
.site-footer-compact .footer-disclaimer {
  margin: 0 auto 4px;
  max-width: 760px;
  font-size: 12px;
}
.site-footer-compact .footer-links {
  margin: 0;
  font-size: 12.5px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  align-items: center;
  justify-content: center;
}
.site-footer-compact .footer-copy { margin: 0; }

@media (max-width: 767px) {
  /* Every pixel this bar gives up is a pixel of map, and on a phone the map
     is the whole product — so this is squeezed hard. What is NOT removed is
     the disclaimer or the terms link: those are the two reasons the bar
     exists at all. What gets squeezed is the space around them — padding,
     line-height, and the gap between the two lines. */
  .site-footer-compact { padding: 5px 12px; }
  .site-footer-compact .footer-disclaimer {
    font-size: 11px; line-height: 1.3; margin-bottom: 2px;
  }
  .site-footer-compact .footer-links { font-size: 11px; line-height: 1.3; gap: 2px 7px; }
}
@media (max-width: 600px) {
  /* The copyright line is the one thing in this bar that is pure
     furniture — it protects nothing and tells a person standing at a field
     nothing. It is the first thing to go when the row has to fit. */
  .site-footer-compact .footer-copy { display: none; }
  .site-footer-compact { padding: 4px 10px; }
  .site-footer-compact .footer-disclaimer { font-size: 10.5px; }
  .site-footer-compact .footer-links { font-size: 10.5px; }
}

/* ---------- Terms of Service page ---------- */
.legal-page { max-width: 800px; }
.legal-doc h2 {
  font-size: 19px;
  margin: 1.9rem 0 .5rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--line);
}
.legal-doc p, .legal-doc li { font-size: 15px; line-height: 1.65; }
.legal-doc ul { padding-left: 1.2rem; }
.legal-doc li { margin-bottom: .35rem; }
.legal-meta {
  background: var(--tile);
  border-left: 4px solid var(--mint);
  border-radius: 0 13px 13px 0;
  padding: 12px 16px;
  font-size: 13.5px;
  line-height: 1.7;
}
.legal-callout {
  background: var(--white);
  border: 1.5px solid var(--line-2);
  border-radius: 13px;
  padding: 12px 16px;
}
.legal-signoff {
  margin-top: 2rem; padding-top: 1.1rem; border-top: 1px solid var(--line);
  text-align: center; font-size: 13px; color: var(--ink-soft);
}

/* ---------- Signup terms gate ---------- */
/* Set apart from the marketing opt-in directly above it. Two checkboxes
   in a row read as one block of small print, and the one that is
   required to create an account should not look like the one that is
   not. */
.terms-gate {
  background: var(--white);
  border: 1.5px solid var(--line-2);
  border-radius: 13px;
  padding: 12px 14px;
  margin-top: .9rem;
}
/* The global `label` rule is display:block / bold / margined, which is
   right for the form fields above and wrong for a sentence sitting
   inline next to its checkbox. Reset it inside the gate only. */
.terms-gate label {
  display: inline; font-weight: 500; margin: 0; font-size: inherit; color: inherit;
  cursor: pointer;
}
.terms-gate a { color: var(--mint-deep); font-weight: 700; }
.terms-gate small { color: var(--ink-soft); font-weight: 400; }
.terms-gate small a { font-weight: 400; }
.social-terms {
  font-size: 12.5px; color: var(--ink-soft); text-align: center; margin: 0 0 .7rem;
}
.social-terms a { color: var(--mint-deep); }

/* ---------- Terms modal ----------
   z-index sits above the add-pin overlay (1200) and the reservation
   modal (1400), because it can be opened from on top of either. */
.terms-modal { position: fixed; inset: 0; z-index: 1500; display: flex; }
.terms-modal[hidden] { display: none; }
.terms-modal-backdrop { position: absolute; inset: 0; background: rgba(20,37,43,.55); }
body.terms-modal-open { overflow: hidden; }

.terms-modal-box {
  position: relative;
  margin: auto;
  background: var(--white);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  width: min(720px, 100%);
  /* The box is a flex column so the BODY scrolls and the head and foot
     stay put. If the whole box scrolled instead, the Agree button would
     be somewhere off screen and the scroll-to-enable rule below would
     have nothing to measure. */
  max-height: min(88vh, 100%);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.terms-modal-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 16px 20px; border-bottom: 1px solid var(--line); flex: 0 0 auto;
}
.terms-modal-head h2 { margin: 0; font-size: 19px; }
.terms-modal-x {
  background: none; border: none; font-size: 20px; cursor: pointer;
  color: var(--ink-soft); padding: 4px 8px; line-height: 1;
}
.terms-modal-body {
  flex: 1 1 auto; overflow-y: auto; padding: 4px 24px 20px;
  -webkit-overflow-scrolling: touch;
}
.terms-modal-body:focus { outline: none; }
.terms-modal-body .legal-doc h2 { font-size: 16.5px; }
.terms-modal-body .legal-doc p,
.terms-modal-body .legal-doc li { font-size: 14px; }
.terms-modal-foot {
  flex: 0 0 auto; padding: 12px 20px 16px;
  border-top: 1px solid var(--line); background: var(--porcelain);
}
.terms-modal-foot .form-actions { margin-top: 0; flex-wrap: wrap; }
.terms-scroll-hint { margin: 0 0 8px; font-size: 12.5px; color: var(--ink-soft); }
.terms-scroll-hint[hidden] { display: none; }
#termsAgreeBtn:disabled { opacity: .5; cursor: not-allowed; }

@media (max-width: 600px) {
  .terms-modal-box { max-height: 100%; border-radius: 0; width: 100%; }
  .terms-modal-body { padding: 4px 16px 16px; }
  .terms-modal-foot .form-actions .btn { flex: 1 1 100%; text-align: center; }
}

/* ================================================================
   Business hub (business.php)
   ----------------------------------------------------------------
   Deliberately no new visual language here. The boxes are the same
   .card as My Teams and the profile page, the buttons are the same
   .btn, and the grid below is the same one-column-then-two shape as
   .team-columns / .profile-columns. Only the pieces that don't exist
   elsewhere — the plan price, the plan pill, the logo preview — are
   defined, and they're built from existing tokens.
   ================================================================ */
.biz-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.25rem;              /* matches the default .card margin-bottom */
  margin-bottom: 1.25rem;
}
@media (min-width: 900px) {
  .biz-grid { grid-template-columns: 1fr 1fr; }
}
/* Cards in a row stretch to match each other. Two plan cards of
   different heights read as two different offers rather than one
   choice between two. */
.biz-grid > .card { align-self: stretch; }
/* The cards space themselves via the grid gap, so their own bottom
   margin would double every gutter. */
.biz-grid > .card { margin-bottom: 0; }

.biz-grid .card h2 { margin: 6px 0 4px; }
.biz-addr { color: var(--ink-soft); margin: 4px 0 2px; }
.biz-meta { color: var(--ink-soft); font-size: .9rem; margin: 2px 0; }
.biz-meta a { color: var(--mint-deep); }

/* Plan pill — same shape as .team-sport, so a listing's tier reads
   like a team's sport badge. Paid tiers go gold to match the gold
   Featured badge on the map. */
.biz-plan-pill {
  display: inline-block;
  font-size: .8rem; font-weight: 700;
  color: var(--mint-deep); background: var(--tile);
  border-radius: 999px; padding: 2px 10px;
}
.biz-plan-pill.is-paid { color: #6b4c12; background: #FFF0C9; }

.biz-logo-preview {
  max-width: 120px; max-height: 120px;
  border: 1px solid var(--line); border-radius: 13px;
  padding: 6px; background: var(--white); display: block; margin-bottom: .5rem;
}

/* Plan cards sit in the same .biz-grid, so a two-plan lineup reads as
   a straight side-by-side comparison on desktop. */
.plan-card { position: relative; }
.plan-price {
  position: absolute; top: 24px; right: 24px;
  font-family: var(--font-display); font-weight: 800;
  color: var(--mint-deep);
}
.plan-card h2 { padding-right: 5.5rem; } /* keep the title clear of the price */
.plan-card.is-current { border-color: var(--mint); box-shadow: 0 0 0 1px var(--mint) inset; }
.plan-current { color: var(--mint-deep); font-weight: 700; }
.plan-warn { color: var(--clay); }
.btn:disabled { opacity: .5; cursor: not-allowed; }

/* The address-confirm card on the sign-up form. Amber rather than red:
   this is a question about the address, not a rejection of it. */
.biz-confirm { border-left: 4px solid #E1A100; background: #FFF8E6; }
.biz-confirm h2 { margin-top: 0; }
.biz-confirm-check {
  display: flex; gap: .6rem; align-items: flex-start;
  font-weight: 400; margin-top: 1rem;
}
.biz-confirm-check input { width: auto; min-height: 0; margin-top: .25rem; }

/* "Which pin is yours?" — the claim-match question on the registration
   form. Whole-row targets rather than bare radios: this is answered on a
   phone by somebody standing in their own shop, and a 16px radio dot is
   not a tap target. Nothing is pre-selected, so no row carries a default
   highlight — see the markup comment in business.php. */
.biz-claim-list { border: 0; padding: 0; margin: 1rem 0 0; }
.biz-claim-list legend {
  padding: 0; margin-bottom: .5rem; font-weight: 700; font-size: 14px;
}
.biz-claim-opt {
  display: flex; gap: .7rem; align-items: flex-start;
  padding: .7rem .8rem; margin-bottom: .5rem;
  border: 1.5px solid var(--line-2); border-radius: 12px;
  background: var(--white); font-weight: 400; cursor: pointer;
  min-height: var(--tap);
}
.biz-claim-opt:has(input:checked) { border-color: var(--mint); background: var(--mint-pale); }
.biz-claim-opt input { width: auto; min-height: 0; margin-top: .3rem; flex: none; }
.biz-claim-body { display: block; }
.biz-claim-body small {
  display: block; margin-top: 2px; color: var(--ink-soft); line-height: 1.45;
}
/* "None of these" is an answer, not a way out of the question — but it is
   a different kind of answer from picking a pin, so it sits apart. */
.biz-claim-none { margin-top: .8rem; border-style: dashed; }

/* "(optional)" inside a field label shouldn't shout as loud as the
   field name it qualifies. */
.label-opt { font-weight: 400; color: var(--ink-soft); }

/* File pickers are the one input the global rule above can't restyle
   (the browser owns the button). Give it its own line at least. */
input[type="file"] { display: block; margin-top: .35rem; }

/* Logo panel on the business hub */
.logo-locked {
  background: #FFF8E6; border: 1px solid #F0C777; color: #6b4c12;
  border-radius: 13px; padding: 10px 12px;
  font-size: .9rem; line-height: 1.45; margin: 6px 0 12px;
}
.logo-locked a { color: #6b4c12; font-weight: 700; }
/* A logo that isn't live yet is shown greyed, so the preview matches the
   sentence above it instead of contradicting it. */
.biz-logo-preview.is-locked { filter: grayscale(1); opacity: .6; }

.logo-specs {
  list-style: none; padding: 0; margin: .9rem 0 0;
  font-size: .9rem; color: var(--ink-soft);
}
.logo-specs li { margin: 3px 0; }
.logo-specs strong { color: var(--ink); font-weight: 700; }

.logo-size-error {
  margin: .5rem 0 0; padding: 9px 12px; border-radius: 10px;
  background: #FDECEC; border: 1px solid #E9A9A9; color: #8A2020;
  font-size: .88rem; line-height: 1.4;
}
.logo-size-error[hidden] { display: none; }

/* ============================================================
   DONATE  (donate.php, donate-thanks.php, donate-cancel.php)
   Appended block — see includes/config.php > SP_DONATE_AMOUNTS
   for the amounts themselves.
   ============================================================ */

/* ---------- The Donate link in the main nav ----------
   Not .nav-cta. That treatment is a solid mint fill and it already belongs
   to Log in / Log out — the one thing people hunt for. Two solid fills in a
   six-item nav is two things shouting and neither being heard, so Donate
   gets the quieter tinted-outline version: clearly an action, obviously not
   the login button. */
.site-nav a.nav-support {
  color: var(--mint-deep);
  font-weight: 800;
}
.site-nav a.nav-support:hover { color: var(--mint-deep); background: #e2f5f1; }
.site-nav a.nav-support.active { background: #e2f5f1; color: var(--mint-deep); }

/* ---------- The support line in the site footer ---------- */
.footer-support { font-size: 13.5px; color: #a8b8bd; margin: 0.6rem 0 0; }
.footer-support a { color: #7fdcc9; font-weight: 700; text-decoration: none; }
.footer-support a:hover { color: #fff; text-decoration: underline; }
.footer-links a.footer-donate { color: #7fdcc9; font-weight: 700; }
.footer-links a.footer-donate:hover { color: #fff; }

/* ---------- Page ---------- */
.donate-page { max-width: 46rem; }
.donate-lede { font-size: 1.06rem; line-height: 1.6; }
.donate-choose { margin: 1.5rem 0 2rem; }

/* ---------- The amount tiles ----------
   Each tile is a real submit button inside a real form, which is why there
   is no JavaScript on this page at all: no radio state to keep in sync, no
   "selected" class to manage, and the whole tile — not a 16px radio dot —
   is the tap target. A donate page that stops working because a script
   didn't load is a donate page that stops taking money.

   auto-fit + minmax means four tiles on a desktop, two on a phone, without
   a media query having to name either number. */
.donate-grid {
  display: grid;
  gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  margin: 0.75rem 0 0;
}
.donate-tile {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; text-align: center;
  min-height: 116px; padding: 18px 14px;
  background: var(--white);
  border: 1.5px solid var(--line-2);
  border-radius: 16px;
  font-family: var(--font-body);
  cursor: pointer;
  transition: .15s;
}
.donate-tile:hover {
  border-color: var(--mint);
  background: #f4fdfb;
  transform: translateY(-2px);
  box-shadow: 0 8px 22px rgba(20,37,43,.10);
}
/* Focus ring kept visible and separate from :hover — a keyboard user needs
   to know which tile is about to take their money. */
.donate-tile:focus-visible { outline: 3px solid var(--mint); outline-offset: 2px; }
.donate-tile:active { transform: translateY(1px); }
.donate-amt {
  font-family: var(--font-display);
  font-size: 2rem; font-weight: 800; line-height: 1;
  letter-spacing: -.03em; color: var(--mint-deep);
}
.donate-blurb { font-size: 12.5px; line-height: 1.4; color: var(--ink-soft); }

/* ---------- Custom amount ---------- */
.donate-custom { margin-top: 1.25rem; }
.donate-custom label { font-size: 14.5px; }
.donate-custom-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.donate-currency {
  font-family: var(--font-display); font-size: 1.4rem; font-weight: 800;
  color: var(--ink-soft); line-height: 1;
}
/* Wide enough for "5000.00" and no wider. A currency box the width of the
   page invites the extra zero the SP_DONATE_MAX_CENTS ceiling exists to
   catch. flex-basis rather than width so it can still shrink on a phone. */
.donate-custom-row input[type="text"] { flex: 0 1 10rem; min-width: 7rem; }
.donate-custom-row .btn { flex: 0 0 auto; }
.donate-hint { font-size: 12.5px; color: var(--ink-soft); margin: 0.5rem 0 0; }

/* ---------- Where the money goes ---------- */
.donate-uses {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  margin: 1rem 0 1.5rem;
}
.donate-use {
  background: var(--white); border: 1px solid var(--line);
  border-radius: 16px; padding: 16px 18px;
}
.donate-use h3 {
  font-family: var(--font-display); font-size: 16px; font-weight: 800;
  margin: 0 0 6px; color: var(--ink);
}
.donate-use p { font-size: 14px; line-height: 1.55; color: #2c424a; margin: 0; }

.donate-free { padding-left: 1.15rem; }
.donate-free li { margin: 0.4rem 0; color: #2c424a; }
.donate-cta-again { margin: 1.5rem 0; }

/* ---------- Thank-you receipt block ---------- */
.donate-receipt {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  background: var(--white); border: 1px solid var(--line);
  border-radius: 18px; padding: 24px 18px; margin: 1.25rem 0 1.5rem;
  text-align: center;
}
.donate-receipt-amt {
  font-family: var(--font-display); font-size: 3rem; font-weight: 800;
  line-height: 1; letter-spacing: -.03em; color: var(--mint-deep);
}
.donate-receipt-cap {
  font-family: var(--font-display); font-size: 12.5px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-soft);
}

/* ---------- The legal block ----------
   Smaller and quieter than the page around it, but NOT grey-on-grey. This
   is the text that says a donation is not tax-deductible, and copy that
   people are expected to have read has to be legible enough that they
   plausibly did. 13.5px on a near-body colour, not 11px on #aaa. */
.donate-legal {
  margin-top: 2.5rem; padding-top: 1.25rem;
  border-top: 1px solid var(--line-2);
}
.donate-legal h2 { font-size: 17px; color: var(--ink-soft); }
.donate-legal p {
  font-size: 13.5px; line-height: 1.6; color: #46606a; margin: 0 0 0.7rem;
}
.donate-legal strong { color: var(--ink); }
.donate-legal a { color: var(--mint-deep); }

/* ============================================================
   MY TEAMS — section divider + 2-up Join/Create layout
   ============================================================ */
.section-label {
  margin: 0 0 4px;
  font-size: 1.05rem;
  font-weight: 800;
  color: var(--ink);
}
.section-rule {
  border: 0;
  border-top: 1px solid var(--line-2, #dbe4e7);
  margin: 6px 0 24px;
}
.teams-actions {
  display: grid;
  grid-template-columns: 1fr;
  gap: 18px;
  align-items: start;
}
.teams-actions > .card { margin: 0; }
@media (min-width: 800px) {
  .teams-actions { grid-template-columns: 1fr 1fr; }
}

/* Card header with an action button on the right (team.php pins card). */
.card-head-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}
.card-head-row h2 { margin: 0; }

/* ============================================================
   Yes / No toggle (team.php → Team Alerts)
   ------------------------------------------------------------
   A segmented pill built from two radio inputs. Radios, not a
   styled checkbox or a div with a click handler, for three
   reasons: the browser gives the group keyboard arrow-key
   navigation for free, a screen reader announces "Yes, radio
   button, 1 of 2" inside the <legend>'s question, and the form
   still submits with JavaScript off.

   The inputs are visually hidden rather than display:none —
   display:none removes them from the focus order, which would
   make the control unreachable by keyboard. They stay in the
   layout at zero size, and the visible state lives on the
   sibling <label> via :checked + label.

   Segmented-pill vocabulary: the container owns the border,
   radius and clipping; the segments own only their fill, so the
   seam between them is one hairline.
   ============================================================ */
.sp-toggle-field {
  border: 0;
  padding: 0;
  margin: 0 0 14px;
  min-width: 0;   /* fieldsets default to min-content; this lets it shrink on phones */
}
.sp-toggle-field legend {
  padding: 0;
  margin-bottom: 8px;
  font-weight: 600;
  font-size: 14px;
  color: var(--ink-soft);
}

.sp-toggle {
  display: inline-flex;
  align-items: stretch;
  background: var(--white);
  border: 1px solid var(--line-2);
  border-radius: 999px;
  overflow: hidden;
  position: relative;   /* anchors the absolutely-positioned radios inside the pill */
}

/* Present to assistive tech and the focus order, invisible to the eye. */
.sp-toggle input[type="radio"] {
  position: absolute;
  width: 1px; height: 1px;
  margin: 0; padding: 0;
  opacity: 0;
  pointer-events: none;
}

.sp-toggle label {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  margin: 0;
  padding: 10px 26px;
  min-height: var(--tap);   /* thumb-sized, like every other tap target */
  box-sizing: border-box;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 15px;
  color: var(--ink-soft);
  white-space: nowrap;
  transition: background .15s ease, color .15s ease;
}
.sp-toggle label + input[type="radio"] + label { border-left: 1px solid var(--line-2); }
.sp-toggle input[type="radio"]:not(:checked) + label:hover {
  background: var(--porcelain);
  color: var(--ink);
}

/* The chosen side. Green for Yes because it is the state that makes the
   feature do something; slate for No so "off" reads as a setting rather
   than an error. */
.sp-toggle input[type="radio"]:checked + label { background: var(--ink-soft); color: #fff; }
.sp-toggle input[type="radio"][value="1"]:checked + label { background: var(--mint-deep); }

/* Ring goes INSIDE — the pill clips overflow, so an outset ring would be
   shaved off at the rounded ends. */
.sp-toggle input[type="radio"]:focus-visible + label {
  outline: 3px solid var(--mint);
  outline-offset: -3px;
}

/* The note under the toggle. */
.sp-toggle-note { margin: 8px 0 0; font-size: 13px; }

/* Inline save feedback. Empty by default and it collapses to nothing, so
   the card doesn't reserve a blank line waiting for a message. */
.sp-toggle-status {
  margin: 8px 0 0;
  font-size: 13px;
  font-weight: 600;
  min-height: 0;
}
.sp-toggle-status:empty { margin: 0; }
.sp-toggle-status.is-ok  { color: var(--ok); }
.sp-toggle-status.is-err { color: var(--danger); }

/* Mid-save: dimmed and inert, so a fast double-flip can't race two
   requests and land on whichever reply happens to return last. */
.sp-toggle-field.is-saving,
form.is-saving .sp-toggle { opacity: .6; pointer-events: none; }

@media (prefers-reduced-motion: reduce) {
  .sp-toggle label { transition: none; }
}

/* Phones: the halves share the width instead of overflowing the card. */
@media (max-width: 420px) {
  .sp-toggle { display: flex; width: 100%; }
  .sp-toggle label { flex: 1 1 0; padding: 10px 8px; }
}

/* ============================================================
   Profile — view / edit modes, sport icons          (Aug 18)
   The page renders one state at a time (?edit=1). These rules
   cover the header control that switches between them, the
   read-only sport badges, and the icon-only sport picker.
   ============================================================ */

/* Text that exists only for screen readers. Not display:none —
   that would take it out of the accessibility tree too, which is
   the opposite of the point. */
.sr-text {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* Page title and the Edit / Done control on one line. Wraps on
   narrow phones rather than squeezing the h1. */
.profile-head, .biz-head {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 12px; margin-bottom: 1.25rem;
}
.profile-head h1, .biz-head h1 { margin: 0; }

/* ---- Read-only sport icons (profile card, teams list, favorites) ----
   Just the emoji at a readable size. The name is in title= and in
   .sr-text alongside it, so hover and assistive tech still get the
   word — only the visual clutter is gone. */
.sport-badges { display: inline-flex; flex-wrap: wrap; gap: 4px; vertical-align: middle; }
.sport-badge {
  display: inline-block; font-size: 17px; line-height: 1.2;
  cursor: default;
}
.profile-sports-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }

/* ---- Icon-only sport picker (edit form) ----
   Deliberately a separate class from the map's .sport-chip: that one
   is a JS-toggled <button> on map.php, this one is a <label> driven
   by the checkbox inside it. Same pill language on purpose, but the
   two can't break each other. */
.sport-picker { margin-top: 1rem; }
.sport-picks { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 4px; }
.sport-pick { display: inline-block; margin: 0; cursor: pointer; font-weight: 400; }
/* The real checkbox stays focusable — hidden visually only, so
   keyboard users can still tab to it and Space to toggle. */
.sport-pick input { position: absolute; opacity: 0; width: 0; height: 0; }
.sport-pick-face {
  display: flex; align-items: center; justify-content: center;
  width: var(--tap); height: var(--tap);
  border: 1.5px solid var(--line-2); background: var(--white);
  border-radius: 999px; transition: .15s;
  /* Grayed down until picked, so the chosen sports are what the eye
     lands on. filter, not opacity — emoji keep their weight. */
  filter: grayscale(1); opacity: .55;
}
.sport-pick-icon { font-size: 22px; line-height: 1; }
.sport-pick:hover .sport-pick-face { background: var(--tile); filter: grayscale(.4); opacity: .85; }
.sport-pick input:checked + .sport-pick-face {
  background: var(--mint); border-color: var(--mint);
  filter: none; opacity: 1;
}
/* Focus ring on the label's face, since the input itself is invisible. */
.sport-pick input:focus-visible + .sport-pick-face {
  outline: 3px solid var(--mint-deep); outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .sport-pick-face { transition: none; }
}
