:root{
  --pine:#1E4D3B;
  --pine-deep:#123227;
  --lake:#3D7EA6;
  --lake-light:#A9CDE1;
  --sand:#EDE4D3;
  --cream:#FAF6EE;
  --charcoal:#26302C;
  --amber:#D98E32;
  --amber-deep:#B8721F;
  /* Source-type map/badge palette (individual-markers batch 2026-07-30);
     hexes mirror Camping.SOURCE_TYPE_COLORS in camping.js. */
  --teal:#12888E;
  --moss:#6A994E;
  --timber:#7A5230;
  --slate:#6B7870;
  --line:#DDD3BE;
  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
}
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{font-family:var(--sans);color:var(--charcoal);background:var(--cream);line-height:1.5}
h1,h2,h3{font-family:var(--sans);font-weight:800;letter-spacing:-.01em;color:var(--pine-deep)}
a{color:var(--lake)}
img,svg{max-width:100%}
.container{max-width:1120px;margin:0 auto;padding:0 24px}

/* ---------- Header ---------- */
header{display:flex;align-items:center;gap:28px;padding:16px 24px;background:var(--cream);border-bottom:1px solid var(--line);flex-wrap:wrap}
.brand{font-size:21px;font-weight:800;letter-spacing:-.01em;color:var(--pine-deep);display:flex;align-items:center;gap:9px;text-decoration:none;white-space:nowrap}
.brand svg{flex-shrink:0}
nav{display:flex;gap:22px;margin-left:auto;font-size:15px;font-weight:600;flex-wrap:wrap}
nav a{color:var(--charcoal);text-decoration:none;padding:6px 2px;border-bottom:2px solid transparent}
nav a.active,nav a:hover{color:var(--pine-deep);border-bottom-color:var(--lake)}
.header-cta{background:var(--pine);color:#fff;border:none;border-radius:8px;padding:10px 18px;font-size:14px;font-weight:700;text-decoration:none;white-space:nowrap}
.header-cta:hover{background:var(--pine-deep)}

/* ---------- Buttons ---------- */
.btn{display:inline-block;border-radius:9px;padding:13px 26px;font-size:15px;font-weight:700;text-align:center;text-decoration:none;cursor:pointer;border:none}
.btn-amber{background:var(--amber);color:#fff}
.btn-amber:hover{background:var(--amber-deep)}
.btn-pine{background:var(--pine);color:#fff}
.btn-pine:hover{background:var(--pine-deep)}
.btn-ghost{background:none;border:1.5px solid var(--pine);color:var(--pine-deep)}
.btn-ghost:hover{background:var(--sand)}
.btn-ghost-light{background:none;border:1.5px solid rgba(255,255,255,.6);color:#fff}
.btn-ghost-light:hover{background:rgba(255,255,255,.12)}

/* ---------- Hero ---------- */
.hero{background:var(--pine);color:#fff;padding:72px 24px 64px;text-align:center}
.hero h1{color:#fff;font-size:44px;line-height:1.15;max-width:760px;margin:0 auto 16px}
.hero .tagline{font-size:19px;color:var(--lake-light);max-width:560px;margin:0 auto 36px}

/* ---------- Search mockup ---------- */
.search-mock{background:#fff;border-radius:14px;padding:20px;max-width:640px;margin:0 auto;box-shadow:0 12px 32px rgba(18,50,39,.28)}
.search-row{display:flex;gap:10px}
.search-row input{flex:1;padding:13px 16px;border:1px solid var(--line);border-radius:8px;font-size:15px;color:var(--charcoal);background:var(--cream)}
.search-row input::placeholder{color:#7d8a83}
.search-row .btn{white-space:nowrap}
.chip-row{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px;justify-content:center}
.chip{border:1px solid var(--line);background:var(--sand);color:var(--charcoal);border-radius:20px;padding:7px 14px;font-size:13px;font-weight:600;cursor:default;opacity:.85}

/* ---------- Value strip ---------- */
.value-strip{padding:56px 24px;background:var(--cream)}
.value-strip h2{text-align:center;font-size:28px;margin-bottom:36px}
.value-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:24px;max-width:1120px;margin:0 auto}
.value-card{background:#fff;border:1px solid var(--line);border-radius:12px;padding:26px}
.value-card .icon{width:40px;height:40px;margin-bottom:14px}
.value-card h3{font-size:18px;margin-bottom:8px}
.value-card p{font-size:14.5px;color:#4d5750}

/* ---------- How it works ---------- */
.how-band{background:var(--sand);padding:56px 24px}
.how-band h2{text-align:center;font-size:28px;margin-bottom:36px}
.how-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px;max-width:1120px;margin:0 auto;text-align:center}
.how-step .num{width:34px;height:34px;border-radius:50%;background:var(--pine);color:#fff;font-weight:800;display:flex;align-items:center;justify-content:center;margin:0 auto 14px;font-size:15px}
.how-step h3{font-size:16.5px;margin-bottom:6px}
.how-step p{font-size:14px;color:#4d5750}

/* ---------- Owner funnel band ---------- */
.owner-band{background:var(--pine-deep);color:#fff;padding:56px 24px;text-align:center}
.owner-band h2{color:#fff;font-size:26px;margin-bottom:12px}
.owner-band p{max-width:560px;margin:0 auto 26px;color:var(--lake-light);font-size:15.5px}

/* ---------- Content pages (.doc) ---------- */
.doc main{max-width:1060px;margin:0 auto;padding:48px 24px 72px}
.doc h1{font-size:32px;margin-bottom:10px}
.doc .lede{font-size:17px;color:#4d5750;margin-bottom:36px;max-width:640px}
.doc h2{font-size:20px;margin:0 0 10px}
.doc p{font-size:15.5px;color:#3a443d;margin-bottom:14px}
.soon{display:inline-block;font-size:12px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--pine-deep);background:var(--lake-light);border-radius:5px;padding:3px 9px;margin-top:8px}

/* ---------- Region cards ---------- */
.region-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:20px;margin-top:8px}
.region-card{background:#fff;border:1px solid var(--line);border-radius:12px;padding:22px}
.region-card h2{font-size:19px;margin-bottom:8px;color:var(--pine-deep)}
.region-card p{font-size:14.5px;color:#4d5750;margin-bottom:0}

/* ---------- Owners page tiers ---------- */
.tier-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;margin:28px 0 36px}
.tier-card{border:1px solid var(--line);border-radius:12px;padding:20px;background:#fff}
.tier-card.live{border-color:var(--amber);box-shadow:0 0 0 1px var(--amber)}
.tier-card h3{font-size:16px;margin-bottom:6px;color:var(--pine-deep)}
.tier-card p{font-size:13.5px;color:#4d5750;margin:0}

/* ---------- Footer ---------- */
footer{background:var(--pine-deep);color:var(--lake-light);padding:36px 24px;font-size:13.5px}
footer .container{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px}
footer a{color:var(--lake-light)}
footer .attribution{color:rgba(255,255,255,.6)}
.map-attribution{margin-top:10px;padding-top:10px;border-top:1px solid rgba(255,255,255,.15);font-size:12px}

/* ---------- Responsive ---------- */
@media(max-width:860px){
  .value-grid,.how-grid{grid-template-columns:1fr}
  .region-grid{grid-template-columns:1fr}
  .tier-grid{grid-template-columns:1fr}
  .hero h1{font-size:32px}
  .hero .tagline{font-size:16.5px}
  .search-row{flex-direction:column}
  header{gap:14px}
  nav{gap:16px;font-size:14px}
}
@media(max-width:520px){
  .hero{padding:52px 18px 44px}
  .search-mock{padding:16px}
  footer .container{flex-direction:column;text-align:center}
}

/* ==================================================================
   Search + detail pages (Phase 1 Batch 3) — split view, map, cards,
   filters. Same component shapes as Shoreline's live search/listing
   pages, re-themed to this project's Pine/Lake/Sand palette instead
   of Shoreline's blue. .chip is reused from the Phase 0 decorative
   homepage chips; .filters .chip overrides it to be interactive here.
   ================================================================== */

body.search-page{height:100vh;overflow:hidden;display:flex;flex-direction:column}
/* min-height, not height: this header wraps (header{flex-wrap:wrap}) and a
   locked 60px guaranteed the wrapped row overflowed into the filter bar,
   which painted over it. The 'Own a campground?' CTA -- the claim path for
   263 claimable rows -- was unclickable at 390, 414, 480, 768 and 900px.
   At 1280 nothing wraps, so nothing changes. */
body.search-page header{min-height:60px;padding:0 20px;flex-shrink:0}
body.search-page .search{flex:1;max-width:420px;position:relative;margin:0 12px}
body.search-page .search input{width:100%;padding:9px 14px 9px 36px;border:1px solid var(--line);border-radius:8px;font-size:14px;background:var(--cream);color:var(--charcoal)}
body.search-page .search svg{position:absolute;left:11px;top:50%;transform:translateY(-50%);opacity:.5}
body.search-page main{display:flex;flex:1;min-height:0;position:relative}

/* ---------- Filter bar ---------- */
/* The filter bar overflows at every mobile width -- 1053px of controls
   against a 360px screen -- and horizontal scroll is the right answer rather
   than wrapping, because the vertical space it would eat is the same space
   the map overlay needs. In landscape (800x360) <main> gets only ~218px;
   wrapping this bar to three rows would leave roughly 110px of map. Scroll
   was already the intent (overflow-x:auto has always been here); what was
   missing was any sign that it scrolls.

   The affordance is a pair of sticky fade overlays. The first attempt used
   the layered-background trick (white "local" covers over "scroll" shadows,
   which auto-hides at each end with no JS) and it was invisible in practice:
   that draws on the CONTAINER's background, and the chips paint on top of
   it, so at the exact edge where the cue was needed a chip was covering it.
   Replaced with ::before/::after as sticky flex items with negative margins,
   which sit ABOVE the content and consume no layout width, toggled by two
   classes from a scroll listener so each fade appears only when there is
   really more content that way.

   flex:0 0 auto on the children is the other half. Flex items shrink by
   default, so without it a chip gives up width instead of overflowing and
   its label clips mid-word -- which is what "Federal cut in half" was. Now
   nothing shrinks: it either fits or it scrolls, and a partly-visible chip
   at the edge is a deliberate peek rather than a broken label.

   touch-action:pan-x states the intent explicitly instead of relying on the
   default. A vertical swipe starting on the bar no longer does nothing
   ambiguous -- on this page the body does not scroll anyway. */
.filters{display:flex;align-items:center;gap:10px;padding:10px 20px;background:#fff;border-bottom:1px solid var(--line);flex-shrink:0;overflow-x:auto;flex-wrap:nowrap;position:relative;touch-action:pan-x;scroll-padding-inline:20px}
.filters>*{flex:0 0 auto}
.filters::before,.filters::after{content:"";position:sticky;flex:0 0 30px;align-self:stretch;z-index:2;pointer-events:none;opacity:0;transition:opacity .15s}
.filters::before{left:0;margin-right:-30px;background:linear-gradient(to right,#fff,rgba(255,255,255,0))}
.filters::after{right:0;margin-left:-30px;background:linear-gradient(to left,#fff,rgba(255,255,255,0))}
.filters.can-scroll-left::before{opacity:1}
.filters.can-scroll-right::after{opacity:1}
.filters .chip{cursor:pointer;opacity:1}
.filters .chip.set{border-color:var(--pine);background:var(--lake-light);color:var(--pine-deep)}
.filters .chip:disabled{cursor:not-allowed;opacity:.5}
.filters .chip .caret{font-size:10px;opacity:.55;margin-left:4px}
.filter-field{display:flex;flex-direction:column;gap:2px;border:1px solid var(--line);background:#fff;border-radius:8px;padding:5px 12px}
.filter-field label{font-size:10px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:#6b7870}
.filter-field input,.filter-field select{border:none;background:none;font-size:13px;font-weight:600;color:var(--charcoal);padding:1px 0;width:100px;font-family:var(--sans)}
.filter-field input:focus,.filter-field select:focus{outline:none}
.more-filters-wrap{position:relative}
/* position:FIXED, not absolute (2026-08-07). .filters declares only
   overflow-x:auto, but CSS promotes the other axis from visible to auto
   whenever one axis is not visible -- so the filter bar clips vertically
   as a side effect of scrolling horizontally, and this panel showed 15px
   of its 202px at every width. Fixed escapes ancestor overflow entirely,
   leaving the 100vh shell untouched. top/left are computed from the
   toggle at open time, since fixed no longer resolves them against it.
   max-height + overflow-y let it scroll inside itself on short screens
   instead of running off the bottom. */
.more-filters-panel{position:fixed;background:#fff;border:1px solid var(--line);border-radius:10px;padding:12px 16px;box-shadow:0 10px 30px rgba(18,50,39,.18);z-index:60;display:flex;flex-direction:column;gap:8px;min-width:200px;max-width:calc(100vw - 16px);overflow-y:auto;overscroll-behavior:contain}
/* Sparse-filter coverage label. Muted, but on the control itself rather
   than in a tooltip -- the point is that it is unmissable while the choice
   is being made. */
.filter-coverage{margin-left:auto;padding-left:10px;font-size:11.5px;color:var(--slate);white-space:nowrap}
.more-filters-panel label{font-size:13px;display:flex;align-items:center;gap:8px;color:var(--charcoal);cursor:pointer}
.save-search{margin-left:auto;background:var(--pine);color:#fff;border:none;border-radius:8px;padding:8px 14px;font-size:13px;font-weight:600;cursor:pointer;display:flex;align-items:center;gap:7px;white-space:nowrap}
.save-search:disabled{opacity:.55;cursor:not-allowed}

/* ---------- Results / cards ---------- */
.results{width:480px;flex-shrink:0;display:flex;flex-direction:column;border-right:1px solid var(--line);background:var(--cream)}
.results-head{padding:14px 18px 8px}
.results-head h1{font-size:16px;font-weight:800;color:var(--pine-deep)}
.results-head .sub{font-size:12.5px;color:#6b7870;margin-top:2px;font-weight:400}
.active-filters{display:flex;align-items:center;justify-content:space-between;gap:10px;margin:0 18px 8px;padding:7px 12px;background:var(--lake-light);border-radius:8px}
.active-filters-text{font-size:12.5px;font-weight:600;color:var(--pine-deep);line-height:1.4}
.clear-filters-btn{flex-shrink:0;font-size:12px;font-weight:700;color:var(--lake);background:none;border:none;cursor:pointer;text-decoration:underline;padding:0}
.clear-filters-btn:hover{color:var(--pine-deep)}
.cards{flex:1;min-height:0;overflow-y:auto;padding:6px 18px 24px;display:grid;grid-template-columns:1fr 1fr;grid-auto-rows:max-content;gap:14px}

.card{background:#fff;border:1px solid var(--line);border-radius:12px;overflow:hidden;transition:box-shadow .2s ease}
.card:hover{box-shadow:0 6px 18px rgba(18,50,39,.14)}
.card.highlight{box-shadow:0 0 0 3px var(--amber),0 6px 18px rgba(18,50,39,.14)}
.card-link{display:block;text-decoration:none;color:inherit;cursor:pointer}
.thumb{height:110px;position:relative;background:var(--sand)}
.thumb svg{position:absolute;inset:0;width:100%;height:100%}
.thumb .badge{position:absolute;top:8px;left:8px;font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:#fff;background:var(--pine-deep);border-radius:5px;padding:3px 7px}
/* NPS gets its own badge color (existing amber, not a new hue) so a
   National Park Service campground reads as distinct from National
   Forest at a glance -- both share the "Federal" filter chip. */
.badge.badge-nps{background:var(--amber-deep)}
/* Private badge = same teal token as the private map dot (approved
   recolor) so badge and pin agree visually. */
.badge.badge-private{background:var(--teal)}
.thumb .photo-credit{position:absolute;bottom:6px;right:8px;font-size:9px;color:#fff;background:rgba(18,50,39,.6);border-radius:4px;padding:1px 5px}
/* Takedown link inside website-photo captions (photo policy 2026-07-30) */
.photo-credit a{color:#fff;text-decoration:underline}
.photo-credit a:hover{color:var(--lake-light)}
.card-body{padding:10px 12px 4px}
.card-body .price{font-size:15px;font-weight:800;color:var(--pine-deep);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.specs{font-size:12px;color:#4d5750;margin-top:3px;line-height:1.4}
.specs-unknown{color:#93a099;font-style:italic}
.site-type-chips{display:flex;flex-wrap:wrap;gap:4px;margin-top:5px}
.site-type-chip{font-size:10px;font-weight:700;letter-spacing:.02em;color:var(--pine-deep);background:var(--sand);border-radius:4px;padding:2px 6px}
.addr{font-size:11.5px;color:#6b7870;margin-top:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Three actions now, so the footer is a wrapping row (2026-08-06). At
   390px 'Book on midnrreservations.com' + 'Call (989) 735-3881' +
   'Directions' cannot share one line, so they wrap rather than being
   truncated or pushed out of the card. Bottom padding drops from 10px to
   4px because each link now carries its own 44px tap height. */
.card-footer{display:flex;flex-wrap:wrap;align-items:center;column-gap:14px;padding:0 12px 4px}
.card-footer a{display:inline-flex;align-items:center;min-height:44px}
/* Directions is on every card; muting it slightly keeps Book dominant
   without changing the shared link styling. */
.card-directions{opacity:.85}
.book-link{font-size:11.5px;font-weight:700;color:var(--lake);text-decoration:none}
.book-link:hover{text-decoration:underline}

/* ---------- Map ---------- */
.map{flex:1;position:relative;min-width:0;background:var(--lake-light)}
#map-canvas{position:absolute;inset:0;width:100%;height:100%}
.map .maplibregl-ctrl-attrib{font-size:10px;background:rgba(255,255,255,.75)}
.map .maplibregl-popup-content{padding:0;border-radius:12px;overflow:hidden;box-shadow:0 10px 30px rgba(18,50,39,.28)}
.map .maplibregl-popup-close-button{z-index:5;font-size:16px;color:#6b7870}
.popup-content{width:240px;padding:12px 14px 14px}
.popup-content .badge{position:static;display:inline-block;margin-bottom:6px;font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:#fff;background:var(--pine-deep);border-radius:5px;padding:3px 7px}
.popup-content .badge.badge-nps{background:var(--amber-deep)}
.popup-content .badge.badge-private{background:var(--teal)}
.popup-name{display:block;font-size:14.5px;font-weight:800;color:var(--pine-deep);text-decoration:none;line-height:1.3}
.popup-name:hover{text-decoration:underline}
.popup-addr{font-size:12px;color:#6b7870;margin-top:4px;line-height:1.4}
.popup-contact{display:flex;flex-direction:column;gap:3px;margin-top:8px}
.popup-contact a{font-size:12px;font-weight:700;color:var(--lake);text-decoration:none}
.popup-contact a:hover{text-decoration:underline}
/* SCOPED TO .map (2026-08-02). This is the map's corner badge and was
   written as a bare .attribution, but five pages -- index, about, owners,
   regions, campground -- use class="attribution" for FOOTER text, and they
   were all silently inheriting position:absolute along with the badge's
   pill styling. With no positioned ancestor those footer divs anchored to
   the initial containing block, so bottom:8px placed them 8px above the
   fold of the FIRST viewport rather than at the end of the document. On the
   homepage at 360x800 that landed both of them directly on top of the hero
   chip row, making both unreadable. search.html's map badge is the only
   correct use of the bare class, so scoping the rule to .map fixes all five
   pages at once and leaves that one untouched. */
.map .attribution{position:absolute;bottom:8px;right:10px;font-size:10px;color:#4d5750;background:rgba(255,255,255,.7);padding:2px 6px;border-radius:4px;z-index:1}

.map-pin{position:relative;font-size:11.5px;font-weight:700;background:#fff;color:var(--charcoal);border:1.5px solid var(--pine-deep);border-radius:14px;padding:3px 10px;cursor:pointer;box-shadow:0 2px 5px rgba(18,50,39,.22);white-space:nowrap}
.map-pin::after{content:"";position:absolute;left:50%;bottom:-6px;transform:translateX(-50%);border:5px solid transparent;border-top-color:var(--pine-deep)}
.map-pin:hover,.map-pin.map-pin-hover{background:var(--pine-deep);border-color:var(--pine-deep);color:#fff}
.map-pin:hover::after,.map-pin.map-pin-hover::after{border-top-color:var(--pine-deep)}
/* Tier accents (2026-07-29 batch): claimed supplements the standard
   capsule with the established amber convention (border + star, tail
   follows border); live is the solid-amber "lit up" rung -- slot wired,
   nothing sets that tier yet. Hover shifts to amber-deep, not pine, so
   the accent survives interaction. */
.map-pin.tier-claimed{border-color:var(--amber-deep)}
.map-pin.tier-claimed::after{border-top-color:var(--amber-deep)}
.map-pin.tier-claimed .pin-star{color:var(--amber);margin-right:4px}
.map-pin.tier-claimed:hover,.map-pin.tier-claimed.map-pin-hover{background:var(--amber-deep);border-color:var(--amber-deep)}
.map-pin.tier-claimed:hover .pin-star,.map-pin.tier-claimed.map-pin-hover .pin-star{color:#fff}
.map-pin.tier-claimed:hover::after,.map-pin.tier-claimed.map-pin-hover::after{border-top-color:var(--amber-deep)}
.map-pin.tier-live{background:var(--amber);border-color:var(--amber-deep);color:#fff}
.map-pin.tier-live .pin-star{color:#fff;margin-right:4px}
.map-pin.tier-live::after{border-top-color:var(--amber-deep)}
.map-pin.tier-live:hover,.map-pin.tier-live.map-pin-hover{background:var(--amber-deep)}

/* Posted-notes qualifier (Part B): same honesty register as
   specs-unknown -- quiet, unmistakably "their words, point-in-time". */
.posted-note-qualifier{font-size:11px;color:#93a099;font-style:italic;margin-top:2px}

/* Desktop-only dot tooltip (individual-markers batch): follows the
   pointer over the native dots layer; never rendered on touch. */
.map-tooltip{position:absolute;z-index:6;pointer-events:none;background:rgba(18,50,39,.92);color:#fff;font-size:12px;font-weight:700;padding:3px 8px;border-radius:5px;transform:translate(-50%,-135%);white-space:nowrap;display:none}

/* Map legend (pins-with-glyphs, 2026-08-05): ALWAYS rendered, one row
   per populated category, plus tier rows when a claimed/live row is in
   view. The colours are not colourblind-safe by choice, so this legend
   and the per-category glyph shapes are what carry identity. */
.map-legend-pin-img{display:block;width:13px;height:17px;flex:0 0 auto}
/* Dynamic tier legend -- rendered by search.js only when a non-seeded
   pin is actually on the map, so today's all-seeded map stays clean. */
.map-legend{position:absolute;left:10px;bottom:10px;z-index:5;background:rgba(255,255,255,.93);border:1px solid var(--line);border-radius:8px;padding:8px 10px;display:flex;flex-direction:column;gap:5px;font-size:11.5px;color:var(--charcoal);box-shadow:0 2px 5px rgba(18,50,39,.15)}
.map-legend-row{display:flex;align-items:center;gap:7px}
.map-legend-pin{font-size:10px;font-weight:700;line-height:1;background:#fff;color:var(--charcoal);border:1.5px solid var(--pine-deep);border-radius:10px;padding:2px 7px}
.map-legend-pin.tier-claimed{border-color:var(--amber-deep)}
.map-legend-pin.tier-claimed .pin-star{color:var(--amber)}
.map-legend-pin.tier-live{background:var(--amber);border-color:var(--amber-deep);color:#fff}
.map-legend-pin.tier-live .pin-star{color:#fff}

.map-toggle{display:none}

/* ---------- Empty / error states ---------- */
.empty-state{grid-column:1/-1;padding:40px 18px;text-align:center}
.empty-state-title{font-size:16px;font-weight:800;color:var(--pine-deep);margin-bottom:6px}
.empty-state-sub{font-size:13px;color:#6b7870;line-height:1.5;max-width:44ch;margin:0 auto}
.empty-state-sub a{color:var(--lake);font-weight:600;text-decoration:none}
.page-empty-state{max-width:520px;margin:60px auto 0;padding:40px 26px;background:#fff;border:1px solid var(--line);border-radius:12px}
.api-error{grid-column:1/-1;padding:30px 18px;text-align:center;color:#B04A3A}
.api-error-title{font-weight:700;margin-bottom:4px}
.api-error-sub{font-size:13px;color:#6b7870}

/* ---------- Detail page (campground.html) ---------- */
.crumb{font-size:13px;color:#6b7870;margin-bottom:14px}
.crumb a{color:var(--lake);text-decoration:none;font-weight:600}
.crumb a:hover{text-decoration:underline}
.view-larger-area{display:block;padding:12px 18px;font-size:12.5px;font-weight:600;color:var(--lake);text-decoration:none;border-top:1px solid var(--line)}
/* The DNR map PDFs sit as further rows under 'View on map', sharing its
   shape so the card reads as one stack of ways to see the place. */
.map-doc-link{display:block;padding:12px 18px;font-size:12.5px;font-weight:600;color:var(--lake);text-decoration:none;border-top:1px solid var(--line);min-height:44px;display:flex;align-items:center}
.map-doc-link:hover{text-decoration:underline}
.view-larger-area:hover{text-decoration:underline}
/* Sparse-page redesign (2026-08-02). Three grid children: a full-width
   identity strip, the content column, and the right rail. Auto-placement
   does the rest -- the head spans both tracks and lands on row 1, then main
   and aside share row 2. See the mobile block near the foot of this file
   for how the same DOM re-orders to the 390px wireframe. */
.detail{display:grid;grid-template-columns:1fr 340px;gap:22px;align-items:start}
.detail-head{grid-column:1/-1}
.cg-name{font-size:31px;line-height:1.15;font-weight:800;color:var(--pine-deep);margin:6px 0 0}
.identity-line{font-size:14.5px;color:var(--slate);margin-top:6px}
.detail-head .badge-row{display:flex;gap:6px;flex-wrap:wrap}
/* The badge base styles live on .thumb .badge, scoped to the card
   thumbnail. Restate them here rather than widening that selector: the
   detail strip is the only other place a badge renders on its own. */
.detail-head .badge-row .badge{position:static;display:inline-block;font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:#fff;background:var(--pine-deep);border-radius:5px;padding:3px 7px}
.gallery{display:grid;grid-template-columns:2fr 1fr;grid-auto-rows:170px;gap:10px}
/* No photos: one 150px band. */
.gallery.is-empty{grid-auto-rows:150px}
.gallery .photo-credit{font-size:10px;color:#fff;background:rgba(18,50,39,.65);border-radius:4px;padding:2px 6px;max-width:95%;line-height:1.4}
/* Tile opener: fills the cell, invisible, but a real focusable control.
   z-index 1 keeps it BELOW the credit (z-index 2) so the removal mailto
   on the grid is still clickable. */
.gallery .ph-open{position:absolute;inset:0;width:100%;height:100%;padding:0;border:0;background:none;cursor:zoom-in;z-index:1}
.gallery .ph-open:focus-visible{outline:3px solid var(--lake);outline-offset:-3px}
.gallery .photo-credit{z-index:2}
.gallery .ph-more{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(18,50,39,.58);color:#fff;font-weight:700;font-size:15px;letter-spacing:.01em;pointer-events:none;z-index:1}
/* Lightbox. A dialog, not a map bubble: focus-trapped, Escape-closable,
   and the image is CONTAINED -- uncropping it is most of the point. */
.lb{position:fixed;inset:0;z-index:2000;background:rgba(18,50,39,.94);display:none;align-items:center;justify-content:center}
.lb.open{display:flex}
.lb-stage{max-width:100vw;max-height:100vh;display:flex;align-items:center;justify-content:center;padding:56px 8px 84px}
.lb-img{max-width:94vw;max-height:calc(100vh - 150px);width:auto;height:auto;object-fit:contain;border-radius:6px;background:var(--pine-deep)}
.lb-nav{position:absolute;min-width:44px;min-height:44px;width:44px;height:44px;display:flex;align-items:center;justify-content:center;font-size:26px;line-height:1;color:#fff;background:rgba(38,48,44,.72);border:1px solid rgba(255,255,255,.35);border-radius:50%;cursor:pointer;padding:0}
.lb-nav:hover{background:rgba(38,48,44,.9)}
.lb-nav:focus-visible{outline:3px solid var(--lake-light);outline-offset:2px}
.lb-close{top:10px;right:10px}
.lb-prev{left:10px;top:50%;transform:translateY(-50%)}
.lb-next{right:10px;top:50%;transform:translateY(-50%)}
.lb-bar{position:absolute;left:0;right:0;bottom:0;padding:12px 16px;display:flex;gap:12px;align-items:center;justify-content:center;flex-wrap:wrap;color:#fff;font-size:12.5px;background:linear-gradient(transparent,rgba(18,50,39,.75))}
.lb-count{font-variant-numeric:tabular-nums;opacity:.85}
.lb-credit{max-width:70vw}
.lb-credit a{color:var(--lake-light)}
.gallery .ph{border-radius:12px;overflow:hidden;position:relative;background:var(--sand)}
/* Wikimedia tiles show the whole image and put the credit beneath it.
   A column layout, so neither the crop nor the overlay that the RIDB
   tiles use can apply. The dark backdrop is the letterbox that
   contain necessarily leaves. */
.gallery .ph.wm{display:flex;flex-direction:column;background:var(--pine-deep);padding:0 0 2px}
.gallery .ph.wm .photo-credit.below{position:static;display:block;background:none;color:var(--cream);font-size:9.5px;line-height:1.35;padding:3px 6px 4px;max-width:100%;text-align:left;flex:0 0 auto}
.gallery .ph.wm .photo-credit.below a{color:var(--lake-light)}
/* The opener must not cover the credit's licence link. */
.gallery .ph.wm .ph-open{z-index:0}
.lb-credit a{color:var(--lake-light)}
.gallery .ph.main{grid-row:span 2}
/* Six tiles with a 2-row main always leave the bottom-right cell
   empty. The +N tile spans instead, so the row closes. */
.gallery .ph.ph-wide{grid-column:1 / -1}
.gallery svg{position:absolute;inset:0;width:100%;height:100%}
/* ---------- Lodging (2026-08-02) ----------
   One list for three surfaces -- owner cabins, contained-by sub-facilities,
   and absorbed units. The per-item source line is what keeps them from
   reading as contradictory, so it is styled as a distinct, quieter
   register rather than as more specs. */
.lodge-item{padding:10px 0;border-bottom:1px solid var(--line)}
.lodge-item:last-child{border-bottom:none}
.lodge-name{font-weight:700;color:var(--pine-deep);font-size:14.5px}
.lodge-specs{color:var(--slate);font-size:13.5px;margin-top:3px}
.lodge-notes{font-size:14px;line-height:1.55;white-space:pre-wrap;margin-top:5px}
.lodge-src{font-size:12.5px;color:#93a099;font-style:italic;margin-top:4px}
/* Bordered rather than a bare link: measured at 102x20 as plain text at
   390px, which is a poor phone target for the single most actionable thing
   a sub-facility row owns. Padding brings it to ~44px tall. */
.lodge-book{display:inline-block;margin-top:8px;padding:11px 16px;border:1.5px solid var(--lake);border-radius:8px;font-size:13.5px;font-weight:700;color:var(--lake);text-decoration:none}
.lodge-book:hover{background:var(--lake-light);text-decoration:none}
.detail-panel{background:#fff;border:1px solid var(--line);border-radius:12px;padding:18px}
/* ---------- Contact & booking card ---------- */
.panel-h{font-size:12px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--slate);margin:0 0 8px}
.contact-addr{font-size:14px;line-height:1.5;color:var(--charcoal)}
/* Two-line address (2026-08-05). The city line is a sibling rather than
   a suffix so a row with no city renders the street alone, with no
   trailing comma and no orphaned state code. */
.contact-addr-street,.contact-addr-city{display:block}
/* Nearest town (2026-08-06): a derived bearing, not an address, so it is
   visually subordinate to the street lines and always carries its
   distance. */
/* Data sources link (2026-08-06). ODbL and CC BY attach to the pages
   showing the data, so this link sits in the footer of every page --
   including the map shell, which needed a footer built for it. */
/* Keyword search (2026-08-07). */
/* focus= deep-link highlight. A focus ring alone is easy to miss on a page
   that has just rendered several hundred cards, so the control pulses
   briefly as well. Two seconds, then it stops -- a permanent highlight
   would become noise the reader has to dismiss. */
@keyframes focusFlash{0%,100%{box-shadow:0 0 0 0 rgba(217,142,50,0)}25%,75%{box-shadow:0 0 0 4px rgba(217,142,50,.55)}}
.focus-flash{animation:focusFlash 1.1s ease-in-out 2;border-radius:8px}
@media (prefers-reduced-motion:reduce){
  .focus-flash{animation:none;box-shadow:0 0 0 3px rgba(217,142,50,.65)}}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.search{position:relative}
.q-clear{position:absolute;right:5px;top:50%;transform:translateY(-50%);width:24px;height:24px;border:none;background:none;font-size:17px;line-height:1;color:var(--slate);cursor:pointer;border-radius:50%;z-index:3}
/* The header search sits above the sticky filter bar: at 390px the
   clear button's box crosses the header's lower edge and the filter
   bar, painting later, was swallowing the click. */
body.search-page .search{z-index:3}
.q-clear:hover{background:rgba(0,0,0,.07)}
body.search-page .search input{padding-right:34px}
/* Rank 3: the row matched on description text, so its NAME will look
   unrelated to what was typed. Quiet, but present -- without it the card
   reads as a bug. Ranks 1 and 2 get nothing; their relevance is obvious. */
.match-note{margin-top:4px;font-size:11px;color:var(--slate);opacity:.85;font-style:italic}
.empty-state-actions{margin-top:10px}
.empty-state-actions button{background:none;border:1.5px solid var(--pine);color:var(--pine-deep);border-radius:8px;padding:8px 14px;font-size:13px;font-weight:700;cursor:pointer;min-height:40px}
.footer-links a{color:var(--lake-light);text-decoration:underline}
footer.app-footer{flex-shrink:0;padding:7px 20px;font-size:12px;line-height:1.5;
  display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap}
footer.app-footer .attribution{color:rgba(255,255,255,.6)}
ul.sources{list-style:none;padding:0;margin:18px 0}
ul.sources li{padding:13px 0;border-bottom:1px solid rgba(0,0,0,.09);line-height:1.55}
ul.sources li:last-child{border-bottom:none}
ul.sources strong{display:block;margin-bottom:2px}
.contact-nearest,.contact-operator{margin-top:6px;font-size:13px;color:var(--slate);line-height:1.45}
.contact-nearest-label,.contact-operator-label{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--slate);opacity:.8}
/* Padded to a >=44px tap target in its own right -- on a phone this is the
   single most-used control on the page, and 85% of live rows have one. */
.contact-phone{display:inline-block;padding:9px 0;font-size:18px;font-weight:800;color:var(--pine-deep);text-decoration:none}
.contact-phone:hover{text-decoration:underline}
/* Claim-state badges. Pending is amber-outline (in-between state, not
   yet earned the solid tier badge); claimed is solid amber -- amber is
   the claim/upgrade CTA color, and these badges ARE the claim story. */
.badge.badge-claim-pending{background:transparent;color:var(--amber-deep);border:1.5px solid var(--amber-deep);padding:2px 6px}
.badge.badge-claimed{background:var(--amber-deep)}
/* Stacked, full-width, in a 340px rail -- two booking buttons side by side
   in that space produced two cramped half-targets. Column at every width
   means one layout to check rather than a reflow at some breakpoint. */
/* Sits under the reservation button on the 371 rows whose booking
   link is a bare origin, because that is literally the next thing
   the reader has to do when they arrive. */
.cta-hint{font-size:11.5px;color:var(--charcoal);opacity:.75;margin:-4px 0 2px;line-height:1.35}
.detail-cta{display:flex;flex-direction:column;gap:10px;margin:14px 0 0}
/* min-height added 2026-08-06 with the directions button: 13px padding on
   a 14.5px line left the tap target a shade under the 44px floor. Applied
   to all three CTA buttons rather than just the new one -- booking had the
   same problem and it is the button that matters commercially. */
.detail-cta .btn{display:flex;align-items:center;justify-content:center;width:100%;min-height:44px;padding:13px 16px;font-size:14.5px}
/* Secondary to booking by staying ghost against the amber CTA; the
   slightly lighter border keeps it below the website link too. */
.detail-cta .btn-directions{border-color:rgba(30,77,59,.55)}
/* ---------- Claim flow (detail page) ---------- */
.claim-host{margin-top:14px;padding-top:12px;border-top:1px solid var(--line)}
.claim-progress{font-size:13px;color:#4d5750;line-height:1.5}
.owner-fallback{margin-top:16px;font-size:14px;color:#4d5750}
.claim-link{display:inline-block;margin-top:10px;background:none;border:none;padding:0;font:inherit;font-size:13.5px;font-weight:700;color:var(--amber-deep);cursor:pointer;text-align:left}
.claim-link:hover{text-decoration:underline}
.claim-form-note{margin-top:10px;font-size:13px;color:#4d5750;line-height:1.5}
.claim-form{display:flex;gap:8px;margin-top:8px}
.claim-form input[type=email]{flex:1;min-width:0;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font:inherit;font-size:14px;background:#fff}
.claim-form input[type=email]:focus{outline:2px solid var(--lake-light);border-color:var(--lake)}
.claim-form .claim-submit{white-space:nowrap;font-size:13.5px;padding:8px 14px}
.claim-msg{margin-top:8px;font-size:13.5px;line-height:1.5}
.claim-msg div+div{margin-top:4px}
.claim-msg a{color:inherit;text-decoration:underline}
.claim-msg a:hover{text-decoration:none}
.claim-msg.ok{color:var(--pine-deep)}
.claim-msg.warn{color:var(--amber-deep)}
.claim-msg.err{color:#A03A2A}
.detail-aside{display:flex;flex-direction:column;gap:16px}
.map-panel{padding:0;overflow:hidden}
.map-addr-host-unused{padding:12px 14px 0;font-size:13.5px;font-weight:600;color:var(--pine-deep)}
.detail-map-canvas{height:340px;width:100%;position:relative;background:var(--lake-light)}
/* Campsite grouping as text, between the map and the 'View on map' row.
   Loop labels are shown verbatim -- their convention varies per
   facility, so they are displayed and never parsed. */
/* Only when campsites were actually drawn -- the 657 rows without any
   keep the 220px card exactly as it was. */
/* has-campsites no longer changes the height -- 340px is the
   baseline for every row now. The class stays as the marker that
   the layer was drawn, which is what the expand control keys off. */
.map-card-host.map-expanded .detail-map-canvas{height:70vh}
.map-expand-btn{display:block;width:100%;text-align:left;padding:12px 18px;min-height:44px;font:inherit;font-size:12.5px;font-weight:600;color:var(--lake);background:none;border:0;border-top:1px solid var(--line);cursor:pointer}
.map-expand-btn:hover{text-decoration:underline}
/* Compact attribution button on the detail map. Scoped to the map card so
   the browse map keeps MapLibre's own sizing. 44px is this site's tap
   target floor and applies here too -- an accessibility obligation does
   not get a discount for being an attribution control. Kept fully
   opaque and bordered: it must be VISIBLY present, since it is now the
   only route to the full credit on this map. */
.map-card-host .maplibregl-ctrl-attrib-button{width:44px;height:44px;min-width:44px;min-height:44px;background-color:rgba(255,255,255,.95);border-radius:22px;background-size:20px 20px;background-position:center;background-repeat:no-repeat;box-shadow:0 0 0 1px rgba(38,48,44,.25)}
.map-card-host .maplibregl-ctrl-attrib.maplibregl-compact{min-height:44px;background:none;box-shadow:none}
/* When expanded, the full text is shown -- unchanged, just readable. */
.map-card-host .maplibregl-ctrl-attrib.maplibregl-compact-show{background:rgba(255,255,255,.96);border-radius:12px;padding-right:46px}
.campsite-loops{padding:10px 18px 12px;border-top:1px solid var(--line);font-size:12.5px}
.campsite-loops h4{margin:0 0 6px;font-size:12.5px;font-weight:700;color:var(--charcoal)}
.campsite-loop-row{display:flex;justify-content:space-between;gap:14px;padding:2px 0;color:var(--charcoal)}
.campsite-loop-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.campsite-loop-count{flex:0 0 auto;font-variant-numeric:tabular-nums;color:var(--lake)}
/* Hover label. pointer-events:none so it can never intercept a
   gesture, and it is a plain div -- not a Popup. */
.campsite-tip{position:absolute;pointer-events:none;z-index:3;display:none;background:rgba(38,48,44,.93);color:#fff;padding:3px 7px;border-radius:3px;font-size:11.5px;line-height:1.3;white-space:nowrap;transform:translate(-50%,-165%)}
.section{margin-top:26px}
.section h2{font-size:19px;margin-bottom:10px;color:var(--pine-deep)}
.section p{font-size:14.5px;line-height:1.6;color:var(--charcoal);max-width:70ch}
.amenity-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0 24px;font-size:13.5px}
.amenity-grid div{padding:7px 0;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;gap:12px}
.amenity-grid span{color:#6b7870}
.amenity-grid .unknown{color:#93a099;font-style:italic}

/* ---------- Search/detail responsive (mirrors Shoreline's breakpoints) ---------- */
/* 980 -> 760 (2026-08-02): at 980 this hid the map on tablets and on any
   half-width laptop window, which is where a split view is still perfectly
   usable. .results is a fixed 480px, so 760 leaves the map ~280px -- narrow
   but real. Below that the map becomes the toggle-driven overlay instead.
   The .cards rule inside is a no-op (it restates the default at line 170)
   and is left alone rather than folded into this change. */
@media(max-width:760px){
  .results{width:100%}
  .map{display:none}
  .cards{grid-template-columns:1fr 1fr}
  body.search-page nav{display:none}
  .map-toggle{display:flex;position:fixed;bottom:18px;left:50%;transform:translateX(-50%);background:var(--charcoal);color:#fff;border:none;border-radius:20px;padding:10px 18px;font-size:13px;font-weight:700;gap:7px;align-items:center;box-shadow:0 4px 14px rgba(18,50,39,.35);z-index:10}
}
/* The narrow-layout map overlay -- the only thing that undoes the
   .map{display:none} above, and deliberately scoped INSIDE the same
   breakpoint so it is structurally impossible for a stale body.map-open
   class to cover a desktop layout after a resize or rotate.

   Absolutely positioned inside <main> rather than fixed below a hardcoded
   header offset. The first version used top:60px (the header height) and
   covered the filter bar completely -- .filters is a sibling BEFORE main,
   occupying 60->142, so an overlay starting at 60 buried every filter
   control while the map was open. Caught by measuring the live overlay, not
   by reading. Filling main instead lands it below the header and the filters
   with no magic numbers, and survives either of them changing height.
   z-index stays under .map-toggle's 10 so the button remains tappable. */
@media(max-width:760px){
  body.map-open .map{display:block;position:absolute;inset:0;z-index:9}
  /* 111px -- the widest thing in the bar -- for a button that is disabled
     ("coming soon, needs an account") and does nothing when tapped. It sat
     at the far end, so every real filter had to be scrolled PAST it. Hidden
     below the breakpoint rather than removed: it is still there on desktop
     where the room exists. This is the one change here that alters what is
     on screen rather than how it behaves, and it removes a control that
     cannot be used. */
  .filters .save-search{display:none}
}
/* Phone landscape (800x360, 915x412) clears the 760 WIDTH breakpoint above
   while being just as cramped -- the bar still overflows by ~354px and the
   dead Save-search button still strands every real filter behind it. Height
   is the honest axis for that case, so it gets its own query rather than
   being forced into the width one. Tablets and desktops are unaffected;
   nothing else is height-dependent. */
@media(max-height:500px){
  .filters .save-search{display:none}
}
/* Detail page, single column. The DOM order is desktop's (head, main,
   aside) but the phone order has to be identity -> contact & booking ->
   details -> lodging -> nearby -> the ask, with the map after the content
   rather than wedged into the middle of it.

   display:contents dissolves the aside so its two cards become grid items
   in their own right, which is the only way to interleave them with .main
   without a second copy of the markup. One DOM, one set of behaviours --
   the alternative was duplicating the contact card, which would have meant
   two claim forms on the page and two things to keep in step. */
@media(max-width:900px){
  .detail{grid-template-columns:1fr}
  .detail-aside{display:contents}
  .detail-head{order:1}
  .contact-panel{order:2}
  .detail-main{order:3}
  .map-card-host{order:4}
  .cg-name{font-size:26px}
}
@media(max-width:560px){
  .cards{grid-template-columns:1fr}
}

/* ---------- Nearby services (2026-07-31 Gate 2) ----------
   Mirrors .amenity-grid's auto-fill column behaviour so the two
   sections read as one system on the detail page. Empty categories are
   styled to look deliberate (same muted italic as the amenity grid's
   "Not listed") rather than broken -- they are a real answer, not a
   gap. */
.nearby-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:18px 24px}
.nearby-cat-label{font-size:12px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:#6b7870;padding-bottom:5px;border-bottom:1px solid var(--line);margin-bottom:6px}
.nearby-list{list-style:none;display:flex;flex-direction:column;gap:4px}
.nearby-list li{display:flex;justify-content:space-between;align-items:baseline;gap:10px;font-size:13.5px}
.nearby-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.nearby-dist{color:#6b7870;font-variant-numeric:tabular-nums;white-space:nowrap;flex:none}
.nearby-none{font-size:13.5px;color:#93a099;font-style:italic}
.nearby-qualifier{font-size:11.5px;color:#93a099;font-style:italic;margin-top:14px;max-width:62ch}
.nearby-credit{font-size:11px;color:#93a099;margin-top:8px}

/* ---------- Owner FAQs ----------
   Rendered heading-and-all by detail.js only when the campground has
   some -- absence stays silent, and ships no static heading. */
.faq-item{padding:10px 0;border-bottom:1px solid var(--line)}
.faq-item:last-child{border-bottom:none}
.faq-q{font-weight:700;color:var(--pine-deep);font-size:14.5px;margin-bottom:3px}
.faq-a{font-size:14px;line-height:1.55;white-space:pre-wrap}

/* ---------- Details block (2026-08-02) ----------
   Replaces the old About / Sites & amenities / Season / posted-notes split.
   It renders only what is KNOWN: the previous grid printed all 19 amenity
   rows unconditionally, so a zero-fact page -- 23% of the site -- was a
   heading over nineteen greyed-out "Not listed" lines. .amenity-grid keeps
   its name and its two-column row shape; what changed is which rows exist,
   which is why the .unknown variant no longer appears on this page. */
.detail-description{margin-bottom:14px}
.posted-note{margin-top:14px;font-size:14px;line-height:1.5}
.posted-note-label{font-size:12px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--slate);margin-bottom:2px}

/* ---------- Collapsible section heading ----------
   The <h2> stays a real heading and the <button> sits inside it, so the
   document outline is unchanged whether the section is open or shut. */
.section-toggle-h{margin-bottom:4px}
/* min-height, not padding: the heading text stays optically where an
   ordinary <h2> would sit, while the hit area reaches 44px. Measured at
   342x29 before this. */
.section-toggle{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;min-height:44px;background:none;border:none;padding:0;font:inherit;color:inherit;cursor:pointer;text-align:left}
.section-toggle .chev{font-size:14px;color:var(--slate);transition:transform .15s ease}
.nearby-section.collapsed .section-toggle .chev{transform:rotate(-90deg)}
.nearby-section.collapsed .nearby-host{display:none}
/* DEBT: these two are a deliberate copy of the pair above, not a
   generalisation. Rewriting the nearby selectors to cover both sections
   would mean editing rules that work today on the one always-present rich
   block; two duplicated lines is the cheaper risk. detail.js carries the
   same duplication in wireLocalEventsToggle. If a third collapsible section
   ever appears, generalise then -- with three call sites the trade flips. */
.local-events-section.collapsed .section-toggle .chev{transform:rotate(-90deg)}
.local-events-section.collapsed .local-events-body{display:none}
.le-count{font-size:11.5px;color:#93a099;margin-bottom:5px}
/* The chevron stays visible at every width. Hiding it above 760px was the
   first version and it built a trap: the button still worked, so a desktop
   click collapsed the section and left no affordance to open it again.
   Only the INITIAL state is breakpoint-dependent (see wireNearbyToggle) --
   the control itself behaves identically everywhere. */

/* ---------- "What we don't know yet" (2026-08-02) ----------
   Sand rather than white: it is not a data card, it is the page admitting
   a gap, and it should not compete with the sections that carry facts.
   Capped in practice at four lines -- title, up to two lines of gaps, one
   line of ask. On a zero-fact page it must not become the page; a block
   that dominates a phone screen converts "incomplete" into "nagging". */
.gap-card{margin-top:26px;background:var(--sand);border:1px solid var(--line);border-radius:12px;padding:16px 18px}
.gap-title{font-size:12px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--slate)}
.gap-list{font-size:15px;font-weight:600;color:var(--pine-deep);margin-top:4px;line-height:1.45}
.gap-ask{margin-top:10px;font-size:13.5px;line-height:1.5;color:#4d5750}
/* 12px of vertical padding takes both asks from a measured 41px to ~53px.
   These are the conversion path off a sparse page -- the claim link is the
   whole acquisition case for 266 rows -- so they get a real target. */
.gap-link{display:inline-block;margin-top:4px;padding:12px 0;font-weight:700;color:var(--amber-deep);text-decoration:none}
.gap-link:hover{text-decoration:underline}
.gap-ask .claim-link{margin-top:4px;padding:12px 0}

/* Operator-published rates (details.rates). site.css carried no table, dl or
   dd rules at all, so these are self-contained. */
.rate-table{display:flex;flex-direction:column;margin:8px 0 4px}
.rate-row{display:flex;flex-wrap:wrap;gap:8px 14px;align-items:baseline;padding:7px 0;border-bottom:1px solid var(--line)}
.rate-row:last-child{border-bottom:0}
.rate-label{flex:1 1 210px;font-weight:600;font-size:13.5px}
.rate-cells{display:flex;flex-wrap:wrap;gap:6px 16px}
.rate-cell{display:inline-flex;gap:6px;align-items:baseline;white-space:nowrap;font-size:13.5px}
.rate-period{color:#6b7870;font-size:.9em}
.rate-amount{font-variant-numeric:tabular-nums;font-weight:600}
.rate-note{font-size:13px;color:#4d5750;margin:10px 0 0;line-height:1.5}

/* Verbatim amenity names an operator lists (details.amenities). */
.amenity-list{display:flex;flex-wrap:wrap;gap:6px 8px;margin:8px 0 4px}
.amenity-chip{display:inline-block;padding:3px 10px;border-radius:999px;border:1px solid var(--line);font-size:13px;line-height:1.6;color:var(--charcoal)}

/* Operator's own campground map, hotlinked from their server, wrapped in the
   existing link so clicking the picture opens their page. */
a.has-map-img{display:block;text-decoration:none}
.campground-map-img{display:block;width:100%;height:auto;margin:8px 0 6px;border-radius:8px;border:1px solid var(--line);background:rgba(128,128,128,.06)}

/* County hub pages */
.hub{max-width:900px;margin:0 auto;padding:24px}
.hub h1{margin:0 0 8px}
.hub-list{list-style:none;padding:0;display:grid;gap:6px;grid-template-columns:repeat(auto-fill,minmax(280px,1fr))}
.hub-list li{padding:8px 0;border-bottom:1px solid var(--line)}
.hub-list.one-col{grid-template-columns:1fr}
.hub-facts{opacity:.7;font-size:.9em}

/* ---- owner submission form (2026-08-12) ---------------------------------- */
.form-row{margin:14px 0}
.form-row label{display:block;font-size:13.5px;font-weight:600;color:var(--pine-deep);margin-bottom:4px}
.form-row input,.form-row textarea{width:100%;max-width:640px;font:inherit;font-size:15px;padding:9px 11px;border:1px solid var(--line);border-radius:6px;background:#fff;color:var(--charcoal)}
.form-row input:focus,.form-row textarea:focus{outline:2px solid var(--lake);outline-offset:1px;border-color:var(--lake)}
.form-row textarea{resize:vertical;min-height:70px}
.form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:0 20px;max-width:640px}
.req{color:#a4442f}
.form-hint{font-size:12.5px;color:#6b7870;max-width:62ch;margin:6px 0 0}
.form-error{margin:14px 0;padding:10px 12px;border-left:3px solid #a4442f;background:#fdf3f1;color:#7a3122;font-size:14px;max-width:640px}
.amenity-fieldset{border:1px solid var(--line);border-radius:8px;padding:14px 16px;margin:22px 0;max-width:640px}
.amenity-fieldset legend{font-size:13.5px;font-weight:600;color:var(--pine-deep);padding:0 6px}
.amenity-boxes{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:4px 16px;margin-top:10px}
.amenity-boxes label{display:flex;align-items:center;gap:7px;font-size:13.5px;font-weight:400;color:var(--charcoal);min-height:28px}
.amenity-boxes input{flex:none}
/* Honeypot. Positioned off-canvas rather than display:none, because some
   form-fillers skip display:none and fill everything else. Paired with
   tabindex="-1" and aria-hidden in the markup, so it is unreachable by
   keyboard and unannounced by a screen reader. */
.hp-field{position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden}

/* ---------------- access restriction (Batch D) ----------------
   A WARNING, not a feature. Amber is used for its attention value, NOT the
   amber CTA treatment -- a filled amber block reads as a promotion, which is
   the opposite of what this says. Bordered and quiet, sitting above the
   gallery so it is read before rates and amenities rather than after. */
.access-section{border:1px solid var(--amber-deep);background:#FDF3E0;border-radius:6px;
  padding:12px 16px;margin:0 0 18px}
.access-section h2{margin:0 0 6px;font-size:15px;letter-spacing:.02em}
.access-section .access-notice{margin:0;font-size:14px;line-height:1.5;color:var(--charcoal)}
/* Card badge. Deliberately unlike .site-type-chip and .amenity-chip: those
   are features and this is a restriction, so it must not read as another
   thing the campground offers. */
.access-badge{display:inline-block;margin:2px 0 4px;padding:2px 8px;border-radius:4px;
  font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;
  color:#7a4a05;background:#FDF3E0;border:1px solid var(--amber-deep)}

/* ---------------- local event rows (Batch E) ----------------
   Two lines. The first is the row a reader scans -- name left, distance hard
   right. The second carries when and where in a quieter weight, so adding
   two more facts does not turn the list into a paragraph. The old single line
   put all four on one row and truncated the town away at the width where it
   mattered most. */
.le-row{padding:4px 0}
.le-line1{display:flex;justify-content:space-between;align-items:baseline;gap:10px}
.le-line1 .le-name{flex:1 1 auto;min-width:0}
.le-line1 .le-dist{flex:0 0 auto;white-space:nowrap;color:var(--charcoal);opacity:.75;
  font-variant-numeric:tabular-nums}
.le-meta{font-size:12.5px;line-height:1.35;color:var(--charcoal);opacity:.7;margin-top:1px}

/* ---------------- notes & sources (Batch F) ----------------
   Footnotes, and styled as footnotes: smaller and quieter than body copy,
   with the credit quieter still. They sit at the foot of the page rather
   than inside the blocks they describe, so the straight-line caveat is
   written once and visibly covers both nearby services and local events. */
.notes-section{margin-top:22px;padding-top:14px;border-top:1px solid var(--line)}
.notes-section h2{font-size:15px;margin:0 0 8px}
.notes-section .note-line{font-size:12.5px;line-height:1.5;color:var(--charcoal);
  opacity:.75;margin:0 0 6px}
.notes-section .note-credit{font-size:11.5px;line-height:1.45;color:var(--charcoal);
  opacity:.6;margin:8px 0 0}

/* ---------------- region cards become links (Batch G) ----------------
   The cards were bare <div>s with no href, no handler and cursor:auto --
   unclickable, and not even signalling that they were meant to be. They are
   anchors now, and they say how many campgrounds are actually there instead
   of "Campgrounds coming soon". */
a.region-card{display:block;text-decoration:none;color:inherit;
  transition:border-color .12s ease, box-shadow .12s ease}
a.region-card:hover{border-color:var(--pine);box-shadow:0 1px 5px rgba(0,0,0,.07)}
a.region-card:focus-visible{outline:2px solid var(--lake);outline-offset:2px}
a.region-card h2{color:var(--pine-deep)}
.region-count{display:inline-block;font-size:12px;font-weight:700;letter-spacing:.03em;
  text-transform:uppercase;color:var(--pine-deep);background:var(--lake-light);
  border-radius:5px;padding:3px 9px;margin-top:8px}

/* ---------------- plan cards (Batch J3) ----------------
   Same surface treatment as .region-card and .detail-panel: white on the sand
   page, one hairline border, the same radius. NO new colour tokens -- the
   accent on Featured is --amber-deep, already the CTA colour everywhere else,
   and the only card that gets it is the one being sold. */
.plan-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));
  gap:18px;margin:22px 0 30px}
.plan-card{background:#fff;border:1px solid var(--line);border-radius:12px;padding:20px 22px;
  display:flex;flex-direction:column}
.plan-card h2{font-size:19px;margin:0 0 4px;color:var(--pine-deep)}
.plan-card-accent{border-color:var(--amber-deep);box-shadow:0 1px 5px rgba(0,0,0,.06)}
.plan-price{font-size:26px;font-weight:700;color:var(--pine-deep);margin:0 0 6px;
  line-height:1.15}
.plan-per{font-size:15px;font-weight:600;opacity:.75}
.plan-note{display:inline-block;font-size:13px;font-weight:600;opacity:.7;margin-left:4px}
.plan-sub{font-size:14.5px;margin:0 0 10px;color:#4d5750}
.plan-list{margin:0 0 12px;padding-left:18px;font-size:14.5px;line-height:1.55}
.plan-list li{margin-bottom:5px}
.plan-why{font-size:14px;line-height:1.5;color:#4d5750;margin:0 0 14px}
.plan-cta{margin:auto 0 6px}
.plan-fineprint{font-size:12.5px;line-height:1.45;color:var(--charcoal);opacity:.72;margin:0}
@media (max-width:560px){.plan-grid{grid-template-columns:1fr}}

/* Featured badge (Batch J2). Amber on white, the same pair the pin ring uses,
   so badge and marker read as one signal. Labelled in words because paid
   placement above organic results has to say it is paid -- a gold pin alone
   reads as "nicer", not as "bought". */
.featured-badge{display:inline-block;font-size:10.5px;font-weight:700;letter-spacing:.05em;
  text-transform:uppercase;color:#fff;background:var(--amber-deep);border-radius:4px;
  padding:2px 7px;margin:0 6px 4px 0;vertical-align:middle}

/* Featured strip (Batch K). Same card surface as .plan-card and .region-card,
   no new tokens. Never rendered when nothing is featured, and never padded to
   fill the row -- showing two when two have paid is the honest layout. */
.featured-strip{padding:26px 0 6px}
.featured-strip h2{margin:0 0 4px}
.featured-strip .strip-note{font-size:13px;color:var(--charcoal);opacity:.7;margin:0 0 14px}
.featured-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px}
.featured-card{background:#fff;border:1px solid var(--line);border-radius:12px;
  overflow:hidden;text-decoration:none;color:inherit;display:block;
  transition:border-color .12s ease,box-shadow .12s ease}
.featured-card:hover{border-color:var(--pine);box-shadow:0 1px 5px rgba(0,0,0,.07)}
.featured-card img{display:block;width:100%;height:132px;object-fit:cover}
.featured-card .fc-body{padding:12px 14px 14px}
.featured-card .fc-name{font-weight:700;color:var(--pine-deep);margin:4px 0 2px;
  font-size:15.5px;line-height:1.3}
.featured-card .fc-where{font-size:13px;color:#4d5750}
@media (max-width:560px){.featured-grid{grid-template-columns:1fr}}


/* ---- Reviews (M3) ---- */
/* THE HONEYPOT. Off-screen rather than display:none -- some bots skip fields
   that are display:none, and the whole point is that a bot fills it. It is
   already tabindex=-1 and aria-hidden in detail.js, so a keyboard or screen
   reader user cannot reach it either. It rendered VISIBLE on the first pass
   because this rule did not exist, which would have meant real people typing
   into it and having their review silently dropped. */
.rf-pot { position: absolute !important; left: -9999px; width: 1px; height: 1px;
  overflow: hidden; }
.reviews-section { margin: 1.5rem 0; }
.review-average { font-size: 1.05rem; margin: .25rem 0 1rem; }
.review-stars { color: #D98E32; letter-spacing: .06em; }
.review-rating-num { color: #5c6b63; font-size: .85rem; margin-left: .4rem; }
.review { border-top: 1px solid #e6dfd0; padding: .9rem 0; }
.review-head { display: flex; flex-wrap: wrap; gap: .55rem; align-items: baseline; }
.review-name { font-weight: 600; }
.review-stayed, .review-date { color: #6a776f; font-size: .85rem; }
.review-verified { font-size: .75rem; color: #1E4D3B; border: 1px solid #b9d3c6;
  background: #f0f6f2; border-radius: 3px; padding: .05rem .4rem; cursor: help; }
.review-title { margin: .5rem 0 .25rem; font-size: 1rem; }
.review-body { margin: .35rem 0; line-height: 1.6; white-space: pre-wrap; }
.review-empty { color: #3d4a44; }
.review-cta { margin: 1.25rem 0 .5rem; font-size: 1rem; }
.review-form { display: grid; gap: .75rem; max-width: 34rem; }
.review-form .rf { display: grid; gap: .2rem; }
.rf-label { font-size: .9rem; color: #3d4a44; }
.rf-note { font-size: .78rem; color: #6a776f; }
.review-form input[type=text], .review-form input[type=email],
.review-form textarea, .review-form select {
  font: inherit; padding: .4rem .55rem; border: 1px solid #cfc8b8;
  border-radius: 4px; width: 100%; max-width: 100%; box-sizing: border-box; }
.rf-submit { font: inherit; padding: .5rem 1.1rem; border-radius: 4px;
  border: 1px solid #1E4D3B; background: #1E4D3B; color: #fff; cursor: pointer;
  justify-self: start; }
.rf-msg:not(:empty) { color: #9b2c2c; font-size: .9rem; }
.review-thanks { line-height: 1.6; }


/* ---------- County cards (R9) ----------
   Same surface treatment as .region-card: white on the sand page, one
   hairline --line border, radius 12, the same hover/focus transitions. No new
   colour tokens and no new spacing scale -- everything here already existed.

   The grid is auto-fill rather than .region-grid's repeat(2,1fr) because the
   regions are very uneven: 33 counties under Mid-Michigan against 5 under
   Thumb & Blue Water. Two fixed columns would be 17 rows deep in one section
   and 3 in another.

   NO narrow-width override, and that is deliberate rather than an omission.
   .region-grid needs one because repeat(2,1fr) stays two columns at any
   width; auto-fill with a 200px floor already collapses on its own, and
   forcing 1fr at 860px would make a phone-friendly two-up layout worse on a
   tablet. There is no dark-mode block in this stylesheet to mirror. */
.county-region-head{margin:26px 0 10px;font-size:19px;color:var(--pine-deep)}
.county-region-head a{color:inherit;text-decoration:none}
.county-region-head a:hover{text-decoration:underline}

.county-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));
  gap:14px;margin:0 0 6px}

a.county-card{display:block;text-decoration:none;color:inherit;background:#fff;
  border:1px solid var(--line);border-radius:12px;padding:14px 16px;
  transition:border-color .12s ease, box-shadow .12s ease}
a.county-card:hover{border-color:var(--pine);box-shadow:0 1px 5px rgba(0,0,0,.07)}
a.county-card:focus-visible{outline:2px solid var(--lake);outline-offset:2px}

.county-name{display:block;font-size:16px;font-weight:600;color:var(--pine-deep)}
.county-count{display:block;font-size:13px;color:#4d5750;margin-top:2px}
/* The type mix. Muted on purpose: it is the third thing on the card, after
   the name and the count, and it is a breakdown rather than a headline. */
.county-mix{display:block;font-size:12.5px;color:#6a776f;margin-top:6px}
