/* Font request moved to a <link> in index.html's <head> (loads
   alongside this file instead of only being discovered after it - see
   the comment there). Keep this file free of the @import if the fonts
   ever move again, or the same load-order problem comes right back. */

:root {
  /* V2 - light by default. Same variable names as v1 so every existing
     rule keeps working; only the values are flipped. Dark overlays
     (photo lightbox, "How it works" tour) re-declare the old dark values
     locally further down. */
  --asphalt:      #F6F3EA;
  --asphalt-2:     #FFFFFF;
  --asphalt-3:     #EFEBDF;
  --paper:         #2A2923;
  --paper-dim:     #5C5A52;
  --ink:           #17181A;
  --hazard:        #F2A900;
  --hazard-ink:    #1A1400;
  --hazard-text:   #8A5A00;
  --alert:         #C93A27;
  --alert-dim:     #F7D9D3;
  --clean:         #2F7D4F;
  --clean-dim:     #D5EBDD;
  --steel:         #6B685D;
  --border:        rgba(23,24,26,0.18);
  --radius: 2px;
  --font-display: 'Anton', sans-serif;
  --font-body: 'IBM Plex Sans', sans-serif;
  --font-mono: 'IBM Plex Mono', monospace;
}

/* Matches the <meta name="color-scheme"> in index.html's <head> -
   that meta tag is what stops the white flash before this file loads
   (it's read straight off the HTML, before any CSS request), this
   property just keeps browser-drawn UI (scrollbars, form controls,
   the mobile "pull to refresh" spinner) dark to match once this file
   is in effect too. Keep both in sync if the theme ever changes. */
:root { color-scheme: light; }

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--asphalt);
  color: var(--ink);
  font-family: var(--font-body);
  scroll-behavior: smooth;
}

/* Pairs with the `body { visibility: hidden; }` in the inline <style>
   at the top of index.html's <head> - that rule hides the page the
   instant parsing starts, before this file has even been requested,
   so nothing renders unstyled while this file is still loading. Once
   this file actually lands and applies, this rule is what lifts that
   hide again. Needs to stay a plain `body` selector to match that
   inline rule's specificity and win by simply loading later in the
   cascade - if this ever grows a more specific selector it'll stop
   overriding the hide and the page will never reveal itself. */
body { visibility: visible; }

body { min-height: 100vh; display: flex; flex-direction: column; }
main { flex: 1 0 auto; }
footer { flex-shrink: 0; }

a { color: inherit; }

h1, h2, h3 {
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  line-height: 1.02;
  margin: 0;
}

.eyebrow {
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 0.72rem;
  color: var(--hazard);
}

.mono { font-family: var(--font-mono); }

/* ---------- Hazard stripe motif ---------- */
.hazard-stripe {
  height: 10px;
  background: repeating-linear-gradient(
    135deg,
    var(--hazard),
    var(--hazard) 14px,
    var(--asphalt) 14px,
    var(--asphalt) 28px
  );
}

/* ---------- Interstitial ---------- */
/* Set by the inline script at the top of <head> in index.html, before
   anything on the page has painted, if it finds today's date already
   stored against lfep_interstitial_last_seen. Skips drawing the
   interstitial at all on a same-day repeat visit, rather than
   painting it and then having js/app.js hide it a moment later, which
   showed up as a flash of the interstitial popping up over the home
   page. (1 class + 1 id) beats the plain #interstitial rule below (1
   id) on specificity, so this doesn't need !important. */
html.interstitial-seen #interstitial { display: none; }

#interstitial {
  position: fixed;
  inset: 0;
  /* Higher than Leaflet's own panes/controls (which sit around
     z-index 1000) so the map underneath can never bleed through the
     interstitial while it's up - Leaflet inits immediately on load,
     before the interstitial has been dismissed. */
  z-index: 9999;
  background: var(--asphalt);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 24px;
}
#interstitial .frame {
  border: 3px solid var(--hazard);
  padding: 48px 32px;
  max-width: 640px;
  position: relative;
}
#interstitial .frame::before,
#interstitial .frame::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  height: 10px;
  background: repeating-linear-gradient(135deg, var(--hazard), var(--hazard) 14px, var(--asphalt) 14px, var(--asphalt) 28px);
}
#interstitial .frame::before { top: -13px; }
#interstitial .frame::after { bottom: -13px; }
/* Full logo lockup (icon + FIX-EP wordmark + tagline) shown above the
   headline - a transparent PNG with its tagline recoloured to the
   site's --ink cream so it stays legible straight on the dark asphalt
   background (the source export's tagline was black-on-transparent,
   meant for a light backdrop). Centered via margin auto since .frame
   itself is text-align:center but this is a block-level image. */
#interstitial .interstitial-logo {
  display: block;
  width: min(380px, 80%);
  height: auto;
  margin: 0 auto 22px;
}
#interstitial h1 {
  font-size: clamp(2.2rem, 7vw, 3.6rem);
  color: var(--ink);
  margin-bottom: 6px;
}
#interstitial h1 span { color: var(--alert); }
#interstitial p {
  font-family: var(--font-mono);
  color: var(--paper-dim);
  font-size: 1rem;
  margin: 18px 0 30px;
  letter-spacing: 0.02em;
}
#interstitial button {
  font-family: var(--font-mono);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: var(--hazard);
  color: var(--hazard-ink);
  border: none;
  padding: 14px 28px;
  cursor: pointer;
  font-size: 0.85rem;
}
#interstitial button:hover { background: #ffbe2e; }
#interstitial .stamp {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  color: var(--steel);
  margin-top: 22px;
  letter-spacing: 0.08em;
}

/* ---------- Header / nav ---------- */
header.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: rgba(23,24,26,0.92);
  backdrop-filter: blur(6px);
  border-bottom: 1px solid var(--border);
}
.nav-inner {
  max-width: 1180px;
  margin: 0 auto;
  padding: 14px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  position: relative;
}
.brand {
  /* It's a <button> so the logo can act as a "back to map" link -
     reset all the default button chrome back to plain text. */
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  color: inherit;
  text-align: left;
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-display);
  text-transform: uppercase;
  /* +30% (1.15rem -> 1.5rem) - now that the big lockup no longer
     shows on the interstitial (see index.html), this header wordmark
     is the main place the brand shows up, so it's sized up to carry
     more of that weight. */
  font-size: 1.5rem;
  letter-spacing: 0.01em;
}
/* +30% (20px -> 26px), same reasoning as .brand's font-size above.
   The icon itself is a black toolbox with amber trim - the black
   part was nearly invisible against the header's own near-black
   background, so it gets a small light --paper chip behind it (same
   treatment as the favicon, see img/) to give it something to
   contrast against instead of just the amber wrench standing out on
   its own. object-fit:contain keeps the icon's own proportions inside
   the now-square chip rather than stretching it. */
.brand-mark {
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  object-fit: contain;
}
.brand .by { font-family: var(--font-mono); font-size: 0.62rem; color: var(--steel); text-transform: uppercase; letter-spacing: 0.1em; }

/* Nav + campaign CTA share one wrapper so they collapse into a single
   dropdown together on mobile, campaign CTA last so it lands at the
   bottom of that dropdown. */
.nav-menu {
  display: flex;
  align-items: center;
  gap: 20px;
}
nav.primary-nav { display: flex; gap: 4px; flex-wrap: wrap; }
nav.primary-nav button {
  background: none;
  border: none;
  color: var(--paper-dim);
  font-family: var(--font-mono);
  font-size: 0.76rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 8px 12px;
  cursor: pointer;
  border-bottom: 2px solid transparent;
}
nav.primary-nav button:hover { color: var(--ink); }
nav.primary-nav button.active { color: var(--hazard); border-bottom-color: var(--hazard); }
.campaign-cta {
  background: var(--alert);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 9px 16px;
  text-decoration: none;
  white-space: nowrap;
}
.campaign-cta:hover { background: #ef4c34; }

/* Hamburger button - hidden on desktop */
.hamburger {
  display: none;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: 40px;
  height: 40px;
  background: none;
  border: 1px solid var(--border);
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
}
.hamburger span {
  display: block;
  width: 20px;
  height: 2px;
  background: var(--ink);
  transition: transform 0.15s ease, opacity 0.15s ease;
}
.hamburger.open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.hamburger.open span:nth-child(2) { opacity: 0; }
.hamburger.open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

@media (max-width: 860px) {
  .hamburger { display: flex; }

  /* Closed by default: a plain dropdown panel under the header,
     only shown once .open is toggled on by the hamburger button. */
  .nav-menu {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: var(--asphalt-2);
    border-bottom: 1px solid var(--border);
    padding: 6px 24px 20px;
  }
  .nav-menu.open { display: flex; }

  nav.primary-nav { flex-direction: column; gap: 0; }
  nav.primary-nav button {
    text-align: left;
    padding: 14px 4px;
    border-bottom: 1px solid var(--border);
  }

  /* Back the campaign sits last in the DOM, so it naturally lands at
     the bottom of the stacked mobile menu. */
  .campaign-cta {
    margin-top: 16px;
    text-align: center;
  }
}

/* ---------- Layout ---------- */
/* Explicit width:100% (on top of the max-width/margin:auto centering
   below) is deliberate, not redundant - main is a flex item (see
   `body` above) and is meant to always stretch to fill that flex
   container's cross axis regardless of what's inside it. Without an
   explicit width, a .grid-3 with fewer items than its 3 columns in
   its only/last row (see Search & Fix's Fixed/Still bad filter in
   js/search.js - filtering can easily leave just one or two cards)
   can pull main's own width down to shrink-fit that narrower content
   instead - a real Chromium/Firefox quirk where an empty 1fr grid
   track doesn't count toward the intrinsic width a stretched flex
   item falls back to. Every card on the page would visibly shrink and
   the whole grid would go off-centre until a wider row loaded back
   in. Confirmed by measuring main's own computed width before/after
   this line while filtering to "Fixed" with only 2 results on screen. */
main { width: 100%; max-width: 1180px; margin: 0 auto; padding: 40px 24px 100px; }
.view { display: none; }
.view.active { display: block; }

.page-head { margin-bottom: 32px; }
.page-head h2 { font-size: clamp(1.8rem, 4vw, 2.6rem); color: var(--ink); }
/* ~3/4 of main's 1180px content width - was capped at 640px, which on
   a wide desktop left an odd-looking gap of empty space to the right
   of the intro paragraph on every page. The map page's page-head is
   the one exception (see .map-head-row below): it shares its row with
   the "Jump to a postcode" form, so it keeps the narrower width there
   instead of competing with the form for space. */
.page-head p { color: var(--paper-dim); max-width: 860px; margin-top: 10px; font-size: 0.98rem; }
/* A page-head that starts a new section partway down a page (e.g.
   "Get involved" and "Sponsoring a clean-up" on the campaign page,
   "Recent clean-ups" on the home page) rather than a view's very own
   top heading - a rule above it, same weight as the borders used
   elsewhere on the site, gives it a clean break from whatever content
   came before instead of just relying on margin to read as a new
   section. */
.page-head-divider { margin-top: 48px; padding-top: 40px; border-top: 1px solid var(--border); }

/* ---------- Hero (home) ---------- */
/* Full-bleed: breaks out of <main>'s max-width:1180px column so the
   background photo runs edge-to-edge, while .hero-inner below puts the
   actual copy/stats back on the same 1180px column as the rest of the
   page (same trick .site-header uses with .nav-inner). */
.hero {
  position: relative;
  padding: 20px 0 24px;
  /* Pulled up by main's own 40px top padding and no bottom margin, so
     the hero butts straight up against the header's hazard stripe and
     straight down onto the map band's amber top border - no dead strip
     of plain background above or below the photo. */
  margin-top: -40px;
  margin-bottom: 0;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
}
/* A little more room on desktop than mobile, but kept tight so the
   hero is sized by its copy rather than padded out with empty space. */
@media (min-width: 861px) { .hero { padding: 40px 0 44px; } }
.hero-inner {
  display: grid;
  grid-template-columns: 3fr 1fr;
  gap: 40px;
  /* Bottom-aligned rather than centered - the two columns aren't the
     same height (the button stack runs taller than the eyebrow/h1/lede
     block), so centering synced their midpoints and left the button
     stack visibly hanging lower than the paragraph text. Aligning to
     the end lines both columns' bottom edges up instead, which reads
     as a clean shared baseline. */
  align-items: end;
  max-width: 1180px;
  margin: 0 auto;
  padding: 0 24px;
  position: relative;
  z-index: 1;
}
@media (max-width: 860px) { .hero-inner { grid-template-columns: 1fr; } }

/* Full-width hero photo sits behind the copy. A dark asphalt-tinted
   scrim (::after) keeps the light text readable over whatever's in
   the photo without needing per-image tuning. */
.hero-bg {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}
.hero-bg img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 35%;
  /* Desaturated so the photo's own colour cast (this one leaned blue)
     can't fight the black/hazard-amber/alert-red palette - a flat,
     high-contrast grey reads more like a stark evidence photo and
     lets the ::after scrim below do the only tinting the hero needs.
     Contrast/brightness pushed further than a first pass (1.15/0.95)
     so the photo itself doesn't sit at the same flat mid-grey as the
     scrim and the map band below it - more like a hard evidence photo,
     less like a uniform grey wash. */
  /* The image itself is now an outline-only version of the photo
     (hero-outline*.jpg: thin grey edges on near-black), already toned
     for the site, so no filter is needed on top of it. */
  filter: none;
}
/* Above 860px the <picture> above swaps in hero-litter-wide.jpg - a
   pre-cropped wide-letterbox version made for the short, full-width
   desktop hero band (the tall crop used on mobile looked squashed and
   over-zoomed stretched across a whole desktop viewport). That image
   is already framed for this shape, so it wants a plain center
   crop rather than the mobile image's bottom-weighted one. */
@media (min-width: 861px) { .hero-bg img { object-position: center; } }
/* Darker again below 861px, on top of the base brightness/contrast
   above - the lede paragraph sits directly over the photo on mobile
   with no separate backdrop behind it (unlike the stats column), so
   it needs more contrast underneath it than the desktop crop does. */
@media (max-width: 860px) {
  .hero-bg::after { background: linear-gradient(180deg, rgba(23,24,26,0.45) 0%, rgba(23,24,26,0.45) 30%, rgba(10,11,12,0.85) 100%); }
}
/* Graduated rather than a flat wash - lighter over the headline near
   the top, darker toward the bottom where the stats/buttons column
   sits, so the photo keeps some visible texture instead of reading as
   one uniform grey panel behind the copy. */
.hero-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(23,24,26,0.2) 0%, rgba(23,24,26,0.2) 35%, rgba(15,16,17,0.7) 100%);
}

.hero h1 {
  font-size: clamp(2.4rem, 6vw, 4rem);
  color: var(--ink);
  /* Shrinks the H1's own box to its widest rendered line (it breaks
     across two lines via a <br> in the markup, and the two lines
     aren't the same width) rather than filling the whole grid column -
     doesn't change how it looks, but means JS can measure its real
     width to line the paragraph below up with it (see app.js). */
  width: fit-content;
  max-width: 100%;
}
.hero .red { color: var(--alert); }
.hero .lede-strong { color: var(--ink); }
.hero p.lede { font-size: 1.05rem; color: #FFFFFF; margin: 18px 0 0; }

/* Stats + report buttons live in the slot the stock photo used to
   occupy. The three stats stay side by side (number over its own
   label) even on narrow screens - only the buttons below them stack
   full-width. */
.hero-stats-col {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 10px;
}
/* Evenly spaced so the rightmost stat's right edge lines up with the
   right edge of the buttons below (both span the full column width),
   and each number sits centered above its own label rather than
   flush to the label's left edge. A faint backdrop card (background +
   border) behind the row keeps the numbers reading as a data readout
   against the photo rather than floating text with nothing anchoring
   it - same trick the flip-tag/lightbox chrome elsewhere on the site
   already uses over photos. */
.hero-stats-col .stat-row {
  flex-direction: row;
  flex-wrap: nowrap;
  justify-content: space-between;
  gap: 10px;
  margin-top: 0;
  /* No backdrop box - the numbers sit straight on the hero now that the
     outline-only photo behind them is dark enough not to need one. */
  background: none;
  border: none;
  padding: 0;
}
.hero-stats-col .stat-row .stat { text-align: center; }
/* Zeroes out .btn-row's own base margin-top (28px, meant for when it
   follows the stat-row lower down a normal page) - inside the hero
   the gap between stat-row and btn-row is meant to come entirely from
   .hero-stats-col's own flex gap above, not from this stacking on
   top of it uncontrolled. */
.hero-stats-col .btn-row { flex-direction: column; margin-top: 0; }
.hero-stats-col .btn-row .btn { width: 100%; text-align: center; }
/* Same colour as the lede paragraph under the h1, rather than the
   dimmer --steel used for stat labels elsewhere on the site. */
.hero-stats-col .stat-row .stat .label { color: #FFFFFF; }
/* "Red zones" is the alarming number of the three, so it gets the
   site's alert red instead of the hazard amber the other two stats
   (and most CTAs) already use - keeps it from reading as just another
   yellow highlight. Applied via a modifier class on that one stat
   (see index.html) rather than repainting .stat .num everywhere. */
.stat.stat-alert .num { color: var(--alert); }

.stat-row { display: flex; gap: 28px; margin-top: 26px; flex-wrap: wrap; }
.stat { font-family: var(--font-mono); }
.stat .num { font-size: 1.8rem; font-weight: 600; color: var(--hazard); display: block; }
.stat .label { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--steel); }
.btn-row { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 28px; }
.btn {
  font-family: var(--font-mono);
  text-transform: uppercase;
  font-size: 0.85rem;
  letter-spacing: 0.05em;
  padding: 13px 22px;
  border: 1px solid var(--hazard);
  color: var(--hazard);
  background: none;
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
}
.btn:hover { background: var(--hazard); color: var(--hazard-ink); }
.btn.solid { background: var(--hazard); color: var(--hazard-ink); }
.btn.solid:hover { background: #ffbe2e; }
.btn.alert { border-color: var(--alert); color: var(--alert); }
.btn.alert:hover { background: var(--alert); color: var(--ink); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
.btn:disabled:hover { background: none; color: var(--hazard); }
.btn.solid:disabled:hover { background: var(--hazard); color: var(--hazard-ink); }

/* A plain hyperlink for secondary actions like "Use my location" -
   same accent colour as .btn but without the button chrome. */
.text-link {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  color: var(--hazard);
  text-decoration: underline;
  cursor: pointer;
}
.text-link:hover { color: #ffbe2e; }

/* Same colour/underline treatment as .text-link, but keeps whatever
   font it's sitting in rather than switching to mono - for a link
   sitting inline inside a normal paragraph (see the map page-head in
   index.html), where switching fonts mid-sentence would look odd. */
.inline-link { color: var(--hazard); text-decoration: underline; cursor: pointer; }
.inline-link:hover { color: #ffbe2e; }

/* ---------- Map ---------- */
/* Deliberately not id="map" - that collided with the "#map" URL
   fragment used for view routing (see js/app.js), and browsers will
   still natively scroll to an element whose id matches the current
   URL fragment even when the fragment was set via history.replaceState,
   which was cutting the top of the page off under the sticky header
   whenever you navigated to the map view. */
/* Full-bleed band (same trick as .hero above) so the map section sits
   on a visibly different tone (--asphalt-2) than the hero/body
   (--asphalt) above and below it - without this, the hero's photo
   fades into the same flat dark grey as the map section beneath it
   and the page reads as one long grey slab. The hazard-coloured top
   border doubles as a section break in place of the header's own
   hazard stripe. .map-band-inner puts the actual content back on the
   shared 1180px column, same as .hero-inner does inside .hero. */
.map-band {
  background: var(--asphalt-2);
  border-top: 1px solid var(--hazard);
  border-bottom: 1px solid var(--border);
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  padding: 40px 0 44px;
  margin-bottom: 28px;
}
.map-band-inner {
  max-width: 1180px;
  margin: 0 auto;
  padding: 0 24px;
}
/* Page-head + "jump to a postcode" form side by side on desktop (same
   3fr/1fr split as .hero) so the form doesn't strand itself on the
   left with empty space to its right - collapses to stacked below
   860px like everything else. */
.map-head-row {
  display: grid;
  grid-template-columns: 3fr 1fr;
  gap: 40px;
  /* Bottom-aligned rather than top-aligned: the description text runs
     longer than the form, so top-aligning left the form stranded up
     near the heading with a gap of empty space below it before the
     map actually started. Bottom-aligning drops it down flush with
     the map's top edge instead. */
  align-items: end;
  margin-bottom: 32px;
}
@media (max-width: 860px) { .map-head-row { grid-template-columns: 1fr; gap: 20px; } }
.map-postcode-form { margin-bottom: 0; }
/* Keeps the map page-head's paragraph at the original, narrower width
   rather than the site-wide ~860px (see .page-head p above) - this is
   the one page-head that already shares its row with something else
   (the postcode form), so it doesn't have the same empty-space issue
   the other pages had. */
.map-head-row .page-head p { max-width: 640px; }

#hotspot-map {
  /* Was 520px - cut down so the map band sits closer to the hero's
     height and the Recent fixes section below starts sooner. */
  height: 340px;
  width: 100%;
  border: 1px solid var(--border);
  /* One shade darker than the --map-band it now sits inside (was the
     same --asphalt-2 as the page background before that band
     existed) - reads as a distinct tool/box set into the band rather
     than blending straight into it. */
  background: var(--asphalt);
}
/* The CartoDB dark basemap's roads are a low-contrast grey on a
   near-black background - fine on a dim screen, but washes out
   completely on a phone or laptop in daylight. Brightening/adding
   contrast just to the tile layer (not the pins, halos or legend on
   top of it) makes the road lines themselves stand out more without
   turning the whole map a different colour. Applies to both maps -
   the upload page's map uses the same basemap. */
#hotspot-map .leaflet-tile-pane,
#upload-map .leaflet-tile-pane {
  filter: brightness(1.45) contrast(1.2);
}
.map-legend {
  display: flex;
  gap: 18px;
  margin-top: 12px;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--paper-dim);
  flex-wrap: wrap;
}
.map-legend span { display: flex; align-items: center; gap: 6px; }
.swatch { width: 10px; height: 10px; display: inline-block; }

/* ---------- Cards / lists ---------- */
.card {
  background: var(--asphalt-2);
  border: 1px solid var(--border);
  padding: 20px;
}
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
@media (max-width: 780px) { .grid-2, .grid-3 { grid-template-columns: 1fr; } }
/* Home page "Recent fixes" strip - 8 smaller before/after cards, 4 per
   row (2 rows) on desktop, 2 per row on tablets and phones so they
   stay small rather than each one filling the whole screen width. The
   card text is scaled down a touch to suit the narrower tiles. */
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; }
@media (max-width: 980px) { .grid-4 { grid-template-columns: repeat(2, 1fr); } }
.grid-4 .photo-card .meta { padding: 10px 10px 12px; }
.grid-4 .photo-card .meta .cap { font-size: 0.8rem; }
/* Search & Fix uses the same 4-per-row grid, but drops to a single
   column on phones like before - its cards carry checkboxes, votes and
   "I've fixed this" buttons that get too cramped two-up on a phone, and
   js/search.js only turns the photo lightbox on above 780px on the
   assumption that below that each photo already fills the screen. */
@media (max-width: 780px) { .grid-4-search { grid-template-columns: 1fr; } }

/* Search & Fix photo grid opens a fullscreen lightbox on click, but
   only above the width where the grid drops to one column (see
   js/search.js' lightboxEnabled/LIGHTBOX_MIN_WIDTH, kept in step with
   the breakpoint just above) - below that a card's photo already
   spans the full screen on its own, so there's nothing to open. */
@media (min-width: 781px) {
  #search-results .photo-card img[data-lightbox-id] { cursor: pointer; }
}

.select-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  padding: 12px 14px;
  margin-bottom: 16px;
  border: 1px solid var(--border);
  background: var(--asphalt-2);
  font-family: var(--font-mono);
  font-size: 0.78rem;
}
.select-bar .select-actions { display: flex; align-items: center; gap: 12px; }
.select-bar #select-count { color: var(--steel); }

.fix-panel { margin-bottom: 20px; }
.fix-panel p { margin: 0 0 12px; color: var(--paper-dim); font-size: 0.95rem; }
.fix-panel .personal-info-hint { color: var(--alert); font-size: 0.8rem; margin-top: -4px; }
.fix-panel textarea { margin-top: 12px; }
.fix-panel .row { display: flex; gap: 12px; align-items: center; margin-top: 14px; flex-wrap: wrap; }
/* Small opt-in checkbox style, styled as a quiet aside rather than a
   normal form field - used by the campaign form's "I'm interested in"
   ticks (see index.html). (Previously also used by the fix panel's
   "No photo for this one" exception, which has since been removed -
   an after-photo is mandatory for every fix now.) */
.exception-checkbox {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  text-transform: none;
  letter-spacing: 0;
  color: var(--steel);
  cursor: pointer;
}
.exception-checkbox input { width: auto; margin: 0; cursor: pointer; }
input[type=file]:disabled { opacity: 0.4; cursor: not-allowed; }

/* ---------- Per-report fix photo slots ----------
   Fixing one or several reports at once (see fixSlotHtml/showFixPanel
   in js/search.js) gives each report its own tap-to-pick, tap-to-flip
   photo slot instead of one shared file input - the whole point is a
   guaranteed 1:1 pairing between one "before" report and one "after"
   photo, so there's never a shared pool of files that could end up
   matched to the wrong spot. */
.fix-slots-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 14px;
  margin: 4px 0 18px;
}
.fix-slot { background: var(--asphalt-3); border: 1px solid var(--border); padding: 10px; }
.fix-slot .flip-stage { cursor: pointer; }
.fix-slot-loc {
  font-family: var(--font-mono); font-size: 0.62rem; color: var(--paper-dim);
  text-transform: uppercase; letter-spacing: 0.03em;
  margin: 8px 0 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fix-slot-pick { width: 100%; margin-top: 8px; padding: 7px 8px; font-size: 0.66rem; }
.fix-slot.has-photo .fix-slot-pick { border-color: var(--clean); color: var(--clean); }

.photo-card { background: var(--asphalt-2); border: 1px solid var(--border); overflow: hidden; position: relative; }
/* 3:4 portrait rather than a short landscape strip - photos are asked
   to be taken in portrait (capture="environment" on a phone defaults
   to that), and a fixed short landscape box was cropping most of a
   tall portrait photo away to fill it. Applies to a plain photo, the
   no-photo placeholder, and the before/after flip stage below, so all
   three keep the same card height regardless of which one a given
   card happens to show. */
.photo-card img { width: 100%; aspect-ratio: 3 / 4; object-fit: cover; display: block; background: var(--asphalt-3); }
.photo-placeholder {
  width: 100%; aspect-ratio: 3 / 4;
  display: flex; align-items: center; justify-content: center;
  background: var(--asphalt-3); color: var(--steel);
  font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em;
}
.select-checkbox {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 2;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(10,10,11,0.7);
  border: 1px solid var(--border);
}
.select-checkbox input { width: 16px; height: 16px; margin: 0; cursor: pointer; }
.photo-card .meta { padding: 12px 14px 16px; }
/* Location text + the "show on map" pin button share one row - .loc
   is the flexible side (truncated with an ellipsis rather than
   wrapping, so a long street name can't grow the tile), the button is
   a fixed-size icon that never gets squeezed. */
.photo-card .meta .loc-row { display: flex; align-items: center; gap: 8px; }
.photo-card .meta .loc {
  font-family: var(--font-mono); font-size: 0.72rem; color: var(--hazard);
  text-transform: uppercase; letter-spacing: 0.04em;
  flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Small icon-only button, distinct from the text-link style buttons
   elsewhere on the card (flag-link, vote-btns) since it needs to stay
   compact and recognisable at a glance rather than reading as prose -
   see window.showReportOnMap in js/map.js. */
.map-pin-btn {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  background: none;
  color: var(--steel);
  cursor: pointer;
  padding: 0;
}
.map-pin-btn:hover { color: var(--hazard); border-color: var(--hazard); }
.map-pin-btn svg { width: 13px; height: 13px; }
/* Category + postcode sit together as small compact tags rather than
   running text, so adding the postcode alongside the street name
   above doesn't cost the tile any real extra height - see
   .category-pill / .postcode-pill below. */
.pill-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 4px 0 8px; }
.photo-card .meta .cap { font-size: 0.88rem; color: var(--paper-dim); margin: 6px 0 10px; min-height: 1em; }
.photo-card .meta .row { display: flex; justify-content: space-between; align-items: center; font-family: var(--font-mono); font-size: 0.7rem; color: var(--steel); }
.vote-btns { display: flex; gap: 8px; align-items: center; }
.vote-btns button {
  background: none; border: 1px solid var(--border); color: var(--paper-dim);
  font-family: var(--font-mono); font-size: 0.72rem; padding: 4px 9px; cursor: pointer;
}
.vote-btns button:hover:not(:disabled) { border-color: var(--hazard); color: var(--hazard); }
.vote-btns button:disabled { opacity: 0.4; cursor: default; }
.flag-link { font-family: var(--font-mono); font-size: 0.65rem; color: var(--steel); background: none; border: none; cursor: pointer; text-decoration: underline; padding: 0; }
.flag-link:hover { color: var(--alert); }
.status-badge { font-family: var(--font-mono); font-size: 0.62rem; text-transform: uppercase; padding: 2px 7px; letter-spacing: 0.05em; }
.status-badge.active { background: var(--alert-dim); color: #ffb3a3; }
.status-badge.resolved { background: var(--clean-dim); color: #a9e3bd; }
.fixed-by { font-family: var(--font-mono); font-size: 0.72rem; color: var(--clean); margin: -2px 0 10px; }
.sponsor-credit { font-family: var(--font-mono); font-size: 0.72rem; color: var(--hazard); margin: -2px 0 10px; }
.category-pill {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.64rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--steel);
  border: 1px solid var(--border);
  padding: 2px 7px;
}
/* Shown alongside the category pill only when a report has a postcode
   on top of (or instead of) a street name - see cardHtml/
   beforeAfterCardHtml - so the full location is visible without
   costing the .loc line above any extra width or wrapping. */
.postcode-pill {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.64rem;
  letter-spacing: 0.04em;
  color: var(--paper-dim);
  border: 1px solid var(--border);
  padding: 2px 7px;
}
/* How long a spot sat reported before it got cleaned up - sits in the
   bottom-right corner of a before/after card's own .row (see
   beforeAfterCardHtml in js/util.js), which already lays its children
   out with justify-content:space-between, so this lands at the far
   right of the box for free, next to the "Fixed ... ago" text on the
   left, rather than overlaying the photo itself. */
.days-pill {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.64rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--paper-dim);
  border: 1px solid var(--border);
  padding: 2px 7px;
}

/* ---------- Before & after flip card ----------
   A resolved report that's linked back to its own original "before"
   photo (see beforeAfterCardHtml / the pairing pass in
   renderReportCards, js/search.js) gets this instead of a plain
   photo-card: a simple 3D flip between the two photos, toggled
   automatically every 2 seconds for as long as Search your street
   stays the visible view (see startFlip in js/search.js) - a
   continuous, hands-free before/after effect without generating an
   actual animated .gif file, so it loads instantly and needs no extra
   library. Sits inside a .photo-card exactly like a normal card, just
   with this in place of a plain <img>. */
.flip-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 3 / 4;
  perspective: 1200px;
}
.flip-inner {
  position: relative;
  width: 100%;
  height: 100%;
  transition: transform 0.7s cubic-bezier(0.4, 0.2, 0.2, 1);
  transform-style: preserve-3d;
}
.flip-stage.flipped .flip-inner { transform: rotateY(180deg); }
.flip-face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
.flip-face img { width: 100%; height: 100%; object-fit: cover; display: block; background: var(--asphalt-3); }
.flip-face.flip-back { transform: rotateY(180deg); }
.flip-tag {
  position: absolute;
  top: 10px;
  left: 10px;
  font-family: var(--font-mono);
  font-size: 0.64rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 3px 8px;
  background: rgba(23,24,26,0.82);
}
.flip-tag.before { color: #ffcfc4; }
.flip-tag.after { color: #a9e3bd; }
/* Optional business logo badge - sits over a photo's top-right corner
   (the "Before"/"After" tag already owns the top-left, see .flip-tag
   above). Used both on the after-face of a flip card and on a plain
   resolved photo-card (see js/util.js and js/search.js) - the
   ".photo-card img" rule above sizes every <img> in a card to fill it,
   so this overrides width/aspect-ratio/object-fit back down to a small
   badge, and the class-count wins the specificity fight regardless of
   source order. A light background behind it keeps a logo with a
   transparent background (light or dark) legible over any photo. */
.photo-card .sponsor-logo-badge {
  position: absolute;
  top: 10px;
  right: 10px;
  width: auto;
  height: auto;
  max-width: 95px;
  max-height: 45px;
  aspect-ratio: auto;
  object-fit: contain;
  background: rgba(255,255,255,0.92);
  border-radius: 4px;
  padding: 5px 7px;
  box-shadow: 0 1px 4px rgba(0,0,0,0.35);
}
/* Respect a visitor's reduced-motion preference - swap the continuous
   flip for a plain crossfade instead of turning it off outright, so
   the before/after comparison itself is still visible either way. */
@media (prefers-reduced-motion: reduce) {
  .flip-inner { transition: opacity 0.7s ease; transform: none !important; }
  .flip-stage.flipped .flip-inner { transform: none; }
  .flip-face.flip-back { transform: none; opacity: 0; }
  .flip-stage.flipped .flip-face.flip-front { opacity: 0; }
  .flip-stage.flipped .flip-face.flip-back { opacity: 1; }
  .flip-face { transition: opacity 0.7s ease; opacity: 1; }
}

/* ---------- Leaderboard ---------- */
.rank-row {
  display: grid;
  grid-template-columns: 44px 1fr auto auto;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  border: 1px solid var(--border);
  margin-bottom: 8px;
  background: var(--asphalt-2);
}
.rank-row .rank { font-family: var(--font-display); font-size: 1.4rem; color: var(--steel); }
.rank-row.sev-red .rank { color: var(--alert); }
.rank-row.sev-amber .rank { color: var(--hazard); }
.rank-row.sev-green .rank { color: var(--clean); }
.rank-row .area { font-family: var(--font-mono); font-size: 0.95rem; }
.rank-row .count { font-family: var(--font-mono); font-size: 0.8rem; color: var(--paper-dim); text-align: right; }
.severity-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; margin-right: 8px; }

/* ---------- Forms ----------
   Not scoped to `form` - the upload postcode field now lives outside
   its <form> (moved next to the map, see index.html) but still needs
   to look and space like every other field. */
.field { margin-bottom: 18px; }
label { display: block; font-family: var(--font-mono); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--steel); margin-bottom: 7px; }
input[type=text], input[type=email], input[type=search], textarea, input[type=file], select {
  width: 100%;
  background: var(--asphalt-3);
  border: 1px solid var(--border);
  color: var(--ink);
  padding: 11px 12px;
  font-family: var(--font-body);
  font-size: 1rem;
}
textarea { resize: vertical; min-height: 90px; }
input:focus, textarea:focus, select:focus { outline: 2px solid var(--hazard); outline-offset: 1px; }
.hint { font-family: var(--font-mono); font-size: 0.82rem; color: var(--steel); margin-top: 6px; }
.form-msg { font-family: var(--font-mono); font-size: 0.88rem; margin-top: 14px; padding: 10px 12px; display: none; }
.form-msg.show { display: block; }
.form-msg.ok { background: var(--clean-dim); color: #c3f0d2; }
.form-msg.err { background: var(--alert-dim); color: #ffcfc4; }

/* ---------- Calendar list ---------- */
.event-row { display: flex; gap: 18px; padding: 16px; border: 1px solid var(--border); margin-bottom: 10px; align-items: flex-start; flex-wrap: wrap; }
.event-date { font-family: var(--font-mono); background: var(--hazard); color: var(--hazard-ink); padding: 8px 12px; text-align: center; min-width: 74px; }
.event-date .d { font-size: 1.3rem; font-weight: 700; display: block; line-height: 1; }
.event-date .m { font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.05em; }
.event-body { flex: 1; min-width: 200px; }
.event-body h3 { font-family: var(--font-body); font-weight: 600; text-transform: none; font-size: 1.02rem; }
.event-body p { color: var(--paper-dim); font-size: 0.88rem; margin: 6px 0 0; }
.event-body .when { font-family: var(--font-mono); font-size: 0.75rem; color: var(--hazard); margin-top: 6px; }
/* Pushed to the right with margin-left:auto (rather than relying on
   .event-row's own justify-content, which would also space out
   .event-date/.event-body) and centred vertically within the row
   regardless of how tall .event-body's text ends up - this is what
   fills the empty space next to a short event description now that
   the event list is full-width on desktop (see .page-head p above -
   this section stopped sharing a column with anything once
   "Sponsoring a clean-up" got pulled out into its own section). Wraps
   onto its own line below the rest on narrow screens (see
   .event-row's flex-wrap) rather than squeezing everything into one
   cramped row. */
.event-rsvp { margin-left: auto; align-self: center; display: flex; flex-direction: column; align-items: flex-end; gap: 6px; flex-shrink: 0; }
.rsvp-btn { padding: 8px 16px; font-size: 0.7rem; white-space: nowrap; }
.rsvp-btn:disabled { opacity: 0.7; cursor: default; border-color: var(--clean); color: var(--clean); }
.rsvp-count { font-family: var(--font-mono); font-size: 0.68rem; color: var(--steel); white-space: nowrap; }

/* ---------- Campaign banner ---------- */
.campaign-banner {
  background: var(--alert-dim);
  border: 1px solid var(--alert);
  padding: 24px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
  margin-bottom: 40px;
  flex-wrap: wrap;
}
/* Capped so a wide desktop can't stretch this to a single very long
   line right up against the button - short banner copy like this
   reads better wrapped over two lines than spanning the full box. */
.campaign-banner p { margin: 0; max-width: 560px; font-family: var(--font-mono); font-size: 0.85rem; color: var(--ink); }
.campaign-banner .btn { color: var(--ink); }
.campaign-banner .btn:hover { color: var(--ink); }

/* ---------- Footer ---------- */
footer {
  border-top: 1px solid var(--border);
  padding: 30px 24px;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--steel);
}
.footer-links {
  margin-top: 12px;
  display: flex;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
}
.footer-links a { text-decoration: underline; color: var(--steel); }
.footer-links a:hover { color: var(--hazard); }

/* ---------- Legal pages (privacy / terms) ---------- */
.legal-page { max-width: 720px; }
.legal-page h3 {
  font-family: var(--font-body);
  font-weight: 600;
  text-transform: none;
  font-size: 1.05rem;
  color: var(--ink);
  margin: 30px 0 8px;
}
.legal-page h3:first-child { margin-top: 0; }
.legal-page p {
  color: var(--paper-dim);
  font-size: 0.95rem;
  line-height: 1.6;
  margin: 0 0 10px;
}

/* ---------- Who's responsible ---------- */
.responsible-list { display: flex; flex-direction: column; gap: 16px; max-width: 760px; }
.responsible-card h3 {
  font-family: var(--font-body);
  font-weight: 600;
  text-transform: none;
  font-size: 1.05rem;
  color: var(--ink);
  margin-bottom: 6px;
}
.responsible-who {
  font-family: var(--font-mono);
  font-size: 0.76rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--hazard);
  margin: 0 0 10px;
}
.responsible-card p:not(.responsible-who) { color: var(--paper-dim); font-size: 0.92rem; line-height: 1.6; margin: 0 0 16px; }
.responsible-card .btn { padding: 9px 16px; font-size: 0.72rem; }

/* ---------- Empty state ---------- */
.empty-state { font-family: var(--font-mono); color: var(--steel); font-size: 0.85rem; padding: 30px 0; text-align: center; }

/* ---------- Admin ---------- */
/* Mod page sub-navigation - Photo review / Sponsorship messages /
   Clean-up events. Reuses the same look as the site's main nav
   (nav.primary-nav in the header) rather than inventing a second tab
   style, just scoped to the admin panel instead of the header. */
.admin-subnav { display: flex; gap: 4px; flex-wrap: wrap; margin-bottom: 26px; border-bottom: 1px solid var(--border); }
.admin-subnav button {
  background: none;
  border: none;
  color: var(--paper-dim);
  font-family: var(--font-mono);
  font-size: 0.76rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 10px 14px;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.admin-subnav button:hover { color: var(--ink); }
.admin-subnav button.active { color: var(--hazard); border-bottom-color: var(--hazard); }
.admin-page { display: none; }
.admin-page.active { display: block; }

/* Small on/off switch - used for the "Allow RSVPs" toggle on the
   Clean-up events admin page (both the add form and each event row's
   quick switch). A real checkbox under the hood (keyboard/screen
   reader friendly), just repainted as a sliding track+knob rather
   than the default browser checkbox. */
.toggle-switch { display: inline-flex; align-items: center; gap: 8px; font-family: var(--font-mono); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--steel); cursor: pointer; }
.toggle-switch input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.toggle-switch .track { position: relative; width: 34px; height: 18px; background: var(--asphalt-3); border: 1px solid var(--border); flex-shrink: 0; transition: background 0.15s ease, border-color 0.15s ease; }
.toggle-switch .track::after { content: ""; position: absolute; top: 1px; left: 1px; width: 14px; height: 14px; background: var(--paper-dim); transition: transform 0.15s ease, background 0.15s ease; }
.toggle-switch input:checked + .track { background: var(--clean-dim); border-color: var(--clean); }
.toggle-switch input:checked + .track::after { transform: translateX(16px); background: var(--clean); }
.toggle-switch input:focus-visible + .track { outline: 2px solid var(--hazard); outline-offset: 2px; }

/* flex-wrap so the action buttons drop to their own line instead of
   forcing the row wider than the screen - at narrow widths a fixed
   160px photo, the location/status text, and two buttons all on one
   unbreakable line don't fit, which was causing the whole page to
   scroll sideways on mobile. */
.admin-row { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; padding: 12px; border: 1px solid var(--border); margin-bottom: 8px; }
.admin-row img { width: 160px; height: 160px; object-fit: cover; flex-shrink: 0; }
.admin-row-no-photo {
  width: 160px; height: 160px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--asphalt-3); color: var(--steel);
  font-family: var(--font-mono); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em;
}
.admin-row .info { flex: 1; min-width: 180px; font-family: var(--font-mono); font-size: 0.75rem; color: var(--paper-dim); }
.fix-target { display: flex; align-items: center; gap: 8px; margin: 6px 0; flex-wrap: wrap; }
.fix-target img { width: 44px; height: 44px; object-fit: cover; flex-shrink: 0; border: 1px solid var(--border); }
.fix-target-more { color: var(--steel); font-size: 0.7rem; }
.admin-row .actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* Mod screen photo-review rows (Pending review / Flagged photos / All
   reports - see adminRowHtml in js/admin.js, which adds this modifier
   class alongside .admin-row) - stacked instead of side-by-side, with
   the photo itself shown close to full size above the info and
   buttons. The old fixed 160x160 crop sat squeezed next to the text
   and action buttons, which on a narrow phone screen left the photo
   you were actually meant to be checking (for faces, plates, etc.
   before approving it) almost too small to make anything out.
   object-fit:contain rather than cover, since a moderator needs to
   see the whole photo uncropped, not just a square out of the middle
   of it - letterboxes rather than cropping for a photo that isn't a
   perfect match for the box. Doesn't touch sponsor-lead rows (see
   sponsorLeadRowHtml), which reuse plain .admin-row/.admin-row-no-photo
   without this class and keep their small side-by-side layout. */
.photo-row { flex-direction: column; align-items: stretch; }
.photo-row > img,
.photo-row > .admin-row-no-photo,
.photo-row > .photo-wrap > img {
  width: 100%;
  height: auto;
  max-height: 70vh;
  object-fit: contain;
  background: var(--asphalt-3);
}
.photo-row > .admin-row-no-photo { min-height: 120px; padding: 30px 0; }
.photo-row > .info { min-width: 0; }

/* Wraps a fix row's own after-photo (see afterPhotoHtml in js/admin.js)
   purely so the star button below can be positioned in its corner -
   plain img/.admin-row-no-photo rows (a "before" photo, a fix with no
   photo yet) skip this wrapper entirely and have no star. */
.photo-row > .photo-wrap { position: relative; display: block; }

/* Star toggle for featuring an after-photo on the home page's "Recent
   clean-ups" strip - see toggleFeatured in js/admin.js. Sits over the
   photo's top-right corner rather than down among the row's other
   buttons, since it's the exact photo it's marking. Unstarred is a
   plain outline so it doesn't compete with the photo; starred fills
   solid in the site's hazard accent, matching the active state used
   elsewhere on the Mod page (.admin-subnav button.active). */
.feature-star-btn {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(10, 10, 10, 0.55);
  border: 1px solid var(--border);
  border-radius: 50%;
  color: var(--paper-dim);
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}
.feature-star-btn svg { width: 19px; height: 19px; }
.feature-star-btn:hover { color: var(--paper); border-color: var(--hazard); }
.feature-star-btn.active { color: var(--hazard); background: rgba(10, 10, 10, 0.7); border-color: var(--hazard); }

/* Small explainer line above "All reports" on the Photo review tab -
   see #featured-hint in the Mod section and renderAdminPanel's
   #featured-count update in js/admin.js. */
.admin-hint { font-family: var(--font-mono); font-size: 0.72rem; color: var(--steel); margin: -4px 0 14px; }

/* Inline edit form (postcode / street / description) swapped into a
   row's .info slot by the "Edit" action - see startEditRow in
   js/admin.js. Deliberately more compact than the site-wide .field
   spacing (18px margins, 1rem inputs) since it has to sit comfortably
   inside a single admin row rather than a whole page. */
.edit-form label { font-size: 0.66rem; margin-bottom: 4px; }
.edit-form input[type=text], .edit-form textarea, .edit-form input[type=file], .edit-form input[type=datetime-local] {
  padding: 8px 10px;
  font-size: 0.85rem;
  margin-bottom: 10px;
}
.edit-form textarea { min-height: 56px; }
.edit-form .form-msg { margin-top: 8px; padding: 6px 10px; font-size: 0.78rem; }
.sponsor-logo-preview { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.sponsor-logo-preview img { width: 44px; height: 44px; object-fit: contain; background: rgba(255,255,255,0.92); border-radius: 4px; padding: 4px; }
.sponsor-logo-preview .exception-checkbox { margin-top: 0; }

/* ---------- Photo lightbox ----------
   Clicking a map pin opens this instead of a small popup - a
   fullscreen viewer with prev/next arrows so several photos at the
   same postcode (from different people) can be flicked through, most
   recent first. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 6000;
  background: rgba(10,10,11,0.94);
  display: none;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.lightbox.open { display: flex; }
.lightbox-stage {
  position: relative;
  max-width: min(920px, 92vw);
  max-height: 88vh;
  width: 100%;
}
.lightbox-stage img {
  display: block;
  width: 100%;
  max-height: 88vh;
  object-fit: contain;
  background: var(--asphalt-2);
}
.lightbox-topbar {
  position: absolute;
  top: 0; left: 0; right: 0;
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 16px;
  background: linear-gradient(to bottom, rgba(10,10,11,0.85), transparent);
}
.lightbox-topbar-right {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  flex-shrink: 0;
}
.lightbox-fixed-btn {
  padding: 9px 16px;
  font-size: 0.72rem;
  white-space: nowrap;
}
.lightbox-loc {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--hazard);
}
.lightbox-cap {
  font-size: 0.88rem;
  color: var(--paper);
  margin-top: 4px;
  max-width: 60vw;
}
.lightbox-date {
  position: absolute;
  bottom: 12px;
  right: 12px;
  background: rgba(23,24,26,0.82);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  padding: 5px 10px;
  letter-spacing: 0.04em;
}
.lightbox-counter {
  position: absolute;
  bottom: 12px;
  left: 12px;
  background: rgba(23,24,26,0.82);
  color: var(--paper-dim);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  padding: 5px 10px;
  letter-spacing: 0.04em;
}
.lightbox-close {
  position: absolute;
  top: 18px;
  right: 22px;
  background: none;
  border: none;
  color: var(--ink);
  font-size: 2rem;
  line-height: 1;
  cursor: pointer;
  padding: 6px;
}
.lightbox-nav {
  background: rgba(23,24,26,0.7);
  border: 1px solid var(--border);
  color: var(--ink);
  font-size: 2rem;
  line-height: 1;
  width: 52px;
  height: 52px;
  flex-shrink: 0;
  cursor: pointer;
  margin: 0 8px;
}
.lightbox-nav:hover { background: var(--hazard); color: var(--hazard-ink); border-color: var(--hazard); }
@media (max-width: 640px) {
  .lightbox-nav { width: 40px; height: 40px; font-size: 1.5rem; margin: 0 2px; }
  .lightbox-close { top: 10px; right: 12px; }
  .lightbox-cap { max-width: 100%; }
}

/* ---------- Search & Fix: floating "Done" button ----------
   Ticking checkboxes to batch-fix several reports happens as you
   scroll down the grid, but "Mark selected as fixed" only lives in
   the select bar back at the top (see .select-bar above) - once more
   than one photo is ticked, this floating button appears so there's a
   quick way back up rather than scrolling by hand. Lives inside
   #view-search (see index.html) so it's hidden automatically whenever
   that view itself isn't showing - no extra JS needed for that part,
   see the show/hide toggling in js/search.js. */
.select-done-float {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 500;
  display: none;
  align-items: center;
  gap: 6px;
  border: none;
  border-radius: 999px;
  background: var(--hazard);
  color: var(--hazard-ink);
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 14px 22px;
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(0,0,0,0.45);
}
.select-done-float:hover { background: #ffbe2e; }
.select-done-float.show { display: inline-flex; }

/* ---------- Search & Fix: Fixed / Still bad filter ----------
   Sits between the postcode/street search box and the results grid
   (see index.html) - filters whatever's currently rendered (the
   browse feed or a search result) without re-fetching anything, see
   applyStatusFilter in js/search.js. */
.status-filter-bar { display: flex; gap: 8px; flex-wrap: wrap; margin: -14px 0 26px; }
.status-filter-bar .btn { padding: 8px 16px; font-size: 0.72rem; }

/* ---------- Search & Fix: "narrow your search" overlay ----------
   Pops once (see js/search.js) after the browse feed's second "load
   more" - by then there's easily 70+ photos on screen, so this points
   back at the postcode/street search box instead of leaving people to
   keep scrolling by hand. Same fixed full-screen backdrop treatment as
   #interstitial/.lightbox above, just a lighter touch - dismissible,
   and only ever shown once per browser (see the localStorage flag in
   js/search.js). */
.narrow-search-overlay {
  position: fixed;
  inset: 0;
  z-index: 5000;
  background: rgba(10,10,11,0.78);
  display: none;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.narrow-search-overlay.open { display: flex; }
.narrow-search-card {
  position: relative;
  max-width: 440px;
  width: 100%;
  background: var(--asphalt-2);
  border: 1px solid var(--hazard);
  padding: 28px 26px;
}
.narrow-search-card h3 { color: var(--ink); font-size: 1.3rem; margin: 0 0 12px; }
.narrow-search-card p { color: var(--paper-dim); font-size: 0.92rem; margin: 0 0 18px; }
.narrow-search-card p strong { color: var(--hazard); }
.narrow-search-form { display: flex; gap: 10px; margin-bottom: 16px; }
.narrow-search-close {
  position: absolute;
  top: 10px;
  right: 10px;
  background: none;
  border: none;
  color: var(--steel);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  padding: 4px 8px;
}
.narrow-search-close:hover { color: var(--ink); }
.narrow-search-card .text-link { background: none; border: none; padding: 0; display: inline-block; }
@media (max-width: 480px) {
  .narrow-search-form { flex-direction: column; }
}


/* =====================================================================
   V2 OVERRIDES - light theme + map-first home screen
   Everything below sits after the v1 rules on purpose so it wins by
   order. Delete this block (and swap the :root values back) to get v1.
   ===================================================================== */

/* ---------- Dark scopes: overlays that stay dark ---------- */
/* (.fxt-overlay - the "How it works" tour - is light now, see css/tour-v2.css) */
.lightbox {
  --asphalt:      #17181A;
  --asphalt-2:     #212226;
  --asphalt-3:     #2B2C31;
  --paper:         #E7E3D6;
  --paper-dim:     #B9B6AA;
  --ink:           #F4F2EA;
  --steel:         #A8AEB4;
  --alert:         #D6402C;
  --alert-dim:     #7A2A20;
  --clean:         #3F8F5F;
  --clean-dim:     #285A3B;
  --border:        rgba(244,242,234,0.14);
  color-scheme: dark;
}

/* ---------- Light-theme corrections ---------- */
header.site-header { background: rgba(255,255,255,0.94); }
.hazard-stripe,
#interstitial .frame::before,
#interstitial .frame::after {
  background: repeating-linear-gradient(135deg, var(--hazard), var(--hazard) 14px, #17181A 14px, #17181A 28px);
}
.campaign-cta { color: #FFFFFF; }
.campaign-cta:hover { background: #A92D1C; }
nav.primary-nav button.active { color: var(--ink); border-bottom-color: var(--hazard); border-bottom-width: 3px; }

/* amber is a fill colour on light, not a text colour */
.eyebrow, .stat .num, .photo-card .meta .loc, .text-link, .inline-link,
.event-body .when, .responsible-who, .narrow-search-card p strong,
.rank-row.sev-amber .rank, .sponsor-credit { color: var(--hazard-text); }
.text-link:hover, .inline-link:hover { color: var(--ink); }
.tour-launch .tour-launch-icon { border-color: var(--ink); color: var(--ink); }
.tour-launch:hover .tour-launch-icon { background: var(--hazard); border-color: var(--ink); color: var(--hazard-ink); }

/* buttons: ink outline, amber fill for the primary */
.btn { color: var(--ink); border: 2px solid var(--ink); font-weight: 600; }
.btn:hover { background: var(--ink); color: var(--asphalt); }
.btn.solid { background: var(--hazard); color: var(--hazard-ink); border-color: var(--ink); }
.btn.solid:hover { background: #ffbe2e; color: var(--hazard-ink); }
.btn.alert { color: var(--alert); border-color: var(--alert); }
.btn.alert:hover { background: var(--alert); color: #FFFFFF; }
.btn:disabled:hover { background: none; color: var(--ink); }
.btn.solid:disabled:hover { background: var(--hazard); color: var(--hazard-ink); }
.campaign-banner .btn, .campaign-banner .btn:hover { color: var(--alert); }
.campaign-banner .btn:hover { background: var(--alert); color: #FFFFFF; }

/* status + message chips need dark text on their light tints */
.status-badge.active { color: #8F2415; }
.status-badge.resolved { color: #1F5A37; }
.form-msg.ok { color: #1F5A37; }
.form-msg.err { color: #8F2415; }

.card, .photo-card, .rank-row, .event-row, .fix-slot { box-shadow: none; }
.photo-card { border: 2px solid var(--ink); background: #FFFFFF; }

/* the basemap is CARTO's dark style - invert it to a light map */
/* (the home map now uses CARTO Voyager - natural colours - so it is NOT
   inverted; see initMapView in js/map.js. Only the upload map still is.) */
.stage #hotspot-map .leaflet-tile-pane {
  filter: saturate(1.05);
}
#upload-map .leaflet-tile-pane {
  filter: invert(1) hue-rotate(180deg) brightness(0.97) contrast(0.92) saturate(0.7);
}

/* ---------- Home: map-first opening screen (replaces the hero) ---------- */
main { padding-top: 0; }
.view:not(#view-map) { padding-top: 40px; }

.stage {
  position: relative;
  isolation: isolate;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  min-height: 860px;
  background: #EAE6D9;
  border-bottom: 3px solid var(--ink);
  overflow: hidden;
}
.stage #hotspot-map {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  z-index: 0;
  background: #EAE6D9;
}
.stage-ui {
  position: relative;
  z-index: 2;
  max-width: 1180px;
  min-height: 860px;
  margin: 0 auto;
  padding: 48px 24px 40px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 40px;
  pointer-events: none;      /* let the map underneath be dragged */
}
.stage-top, .stage-bottom {
  display: flex;
  flex-wrap: wrap;
  gap: 32px;
  justify-content: space-between;
}
.stage-top { align-items: flex-start; }
.stage-bottom { align-items: flex-end; gap: 20px; }

.sticker {
  pointer-events: auto;
  flex: 1 1 480px;
  max-width: 640px;
  min-width: 0;
  background: #FFFFFF;
  border: 3px solid var(--ink);
  box-shadow: 10px 10px 0 var(--hazard);
  padding: 32px 36px 36px;
}
.sticker h1 {
  font-size: clamp(2.6rem, 5.6vw, 5.2rem);
  line-height: 0.98;
  margin-top: 14px;
  color: var(--ink);
}
.sticker h1 .red { color: var(--alert); }
.sticker p.lede { font-size: 1.02rem; line-height: 1.55; color: #3B3A35; margin: 22px 0 0; max-width: 540px; }
.sticker p.lede .lede-strong { color: var(--ink); font-weight: 600; letter-spacing: 0.02em; }
.sticker .btn-row { margin-top: 26px; }

.stage-stats { display: flex; flex-direction: column; gap: 14px; width: 250px; max-width: 100%; }
.stat-tile {
  pointer-events: auto;
  background: #FFFFFF;
  border: 3px solid var(--ink);
  box-shadow: 6px 6px 0 var(--ink);
  padding: 14px 20px;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.stat-tile:hover { transform: translate(-3px, -3px); box-shadow: 9px 9px 0 var(--ink); }
.stat-tile .num { font-family: var(--font-display); font-size: 4rem; line-height: 1; color: var(--ink); }
.stat-tile .label {
  font-family: var(--font-mono); font-size: 0.68rem; font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.1em; text-align: right; color: var(--paper-dim);
}
.stat-tile.alert { background: var(--alert); }
.stat-tile.alert .num, .stat-tile.alert .label { color: #FFFFFF; }
.stat-tile.alert .label { font-weight: 600; }
.stat-tile.good { box-shadow: 6px 6px 0 var(--clean); }
.stat-tile.good .num { color: var(--clean); }

.stage .map-postcode-form {
  pointer-events: auto;
  background: #FFFFFF;
  border: 3px solid var(--ink);
  padding: 14px 16px;
  margin: 0;
  flex: 0 1 420px;
}
.stage .map-postcode-form label { margin-bottom: 8px; font-size: 0.72rem; letter-spacing: 0.12em; font-weight: 600; }
.stage .map-postcode-form input[type=text] { height: 46px; border: 2px solid var(--ink); font-family: var(--font-mono); }
.stage .map-postcode-form .hint:empty { display: none; }
.map-key {
  pointer-events: auto;
  background: #FFFFFF;
  border: 3px solid var(--ink);
  padding: 14px 18px;
  max-width: 460px;
}
.map-key p { margin: 0 0 10px; font-size: 0.9rem; line-height: 1.5; color: #3B3A35; }
.map-key .map-legend { margin-top: 0; color: var(--ink); font-size: 0.74rem; gap: 8px 18px; }
.map-key .swatch { width: 12px; height: 12px; border-radius: 50%; border: 2px solid var(--ink); }

@media (max-width: 860px) {
  .stage, .stage-ui { min-height: 0; }
  .stage-stats { flex-direction: row; flex-wrap: wrap; width: 100%; }
  .stat-tile { flex: 1 1 140px; flex-direction: column; align-items: flex-start; }
  .stat-tile .label { text-align: left; }
  .sticker { padding: 24px 22px 28px; }
  /* Phones/tablets: instead of the map hiding behind the stacked cards,
     lay the stage out as a column - card, stats, then the map as its
     own full, visible block, then postcode finder + key underneath.
     (display:contents lets the existing wrappers' children be ordered
     directly inside .stage.) */
  .stage {
    display: flex;
    flex-direction: column;
    gap: 20px;
    padding: 24px 16px 28px;
  }
  .stage-ui, .stage-top { display: contents; }
  .sticker { order: 1; flex: none; max-width: none; }
  .stage-stats { order: 2; flex: none; }
  .stage #hotspot-map {
    order: 3;
    position: relative;
    inset: auto;
    width: 100%;
    height: 62vh;
    min-height: 360px;
    border: 3px solid var(--ink);
    box-shadow: 6px 6px 0 var(--ink);
  }
  .stage-bottom {
    order: 4;
    margin-top: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 16px;
  }
  .stage .map-postcode-form { flex: none; }
  .map-key { max-width: none; }
}

/* ---------- Tape ticker ---------- */
.tape-ticker {
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  background: var(--hazard);
  border-bottom: 3px solid var(--ink);
  overflow: hidden;
}
.tape-track { display: flex; width: max-content; animation: fixep-tape 40s linear infinite; }
.tape-half { display: flex; white-space: nowrap; }
.tape-half span {
  font-family: var(--font-display);
  font-size: 1.9rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--hazard-ink);
  padding: 12px 26px;
  line-height: 1;
}
@keyframes fixep-tape { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) { .tape-track { animation: none; } }

/* ---------- Recent fixes heading + sponsor band ---------- */
.proof-head { margin: 96px 0 36px; }
.proof-head h2 { font-size: clamp(3rem, 6.5vw, 5.6rem); line-height: 0.96; margin-top: 12px; }
.proof-head p { color: var(--paper-dim); max-width: 760px; margin-top: 20px; font-size: 1.05rem; line-height: 1.6; }

.sponsor-band {
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  margin-top: 90px;
  margin-bottom: -100px;     /* cancels <main>'s bottom padding so it meets the footer */
  background: #17181A;
  color: #F6F3EA;
}
.sponsor-band .band-stripe { height: 10px; background: repeating-linear-gradient(135deg, var(--hazard), var(--hazard) 14px, #17181A 14px, #17181A 28px); }
.sponsor-band-inner {
  max-width: 1180px; margin: 0 auto; padding: 72px 24px;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 36px;
}
.sponsor-band .band-copy { flex: 1 1 460px; max-width: 760px; min-width: 0; }
.sponsor-band .eyebrow { color: var(--hazard); }
.sponsor-band h2 { font-size: clamp(2.8rem, 6vw, 5rem); color: #F6F3EA; margin-top: 12px; }
.sponsor-band h2 span { color: var(--hazard); }
.sponsor-band p { font-size: 1.1rem; line-height: 1.6; color: #E0DCCD; margin: 20px 0 0; max-width: 620px; }
.sponsor-band .band-actions { display: flex; flex-wrap: wrap; gap: 14px; }
.sponsor-band .btn { padding: 18px 28px; border-color: #F6F3EA; color: #F6F3EA; }
.sponsor-band .btn:hover { background: #F6F3EA; color: #17181A; }
.sponsor-band .btn.solid { background: var(--hazard); color: var(--hazard-ink); border-color: var(--hazard); }
.sponsor-band .btn.solid:hover { background: #ffbe2e; }
.sponsor-band .btn.alert { background: var(--alert); color: #FFFFFF; border-color: var(--alert); }
.sponsor-band .btn.alert:hover { background: #E04A35; }


/* ---------- Before/after slider + copy box (above the Recent fixes cards) ---------- */
.ba-feature {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: 32px;
  align-items: stretch;
  margin: 0 0 40px;
}
.ba-feature[hidden] { display: none; }
.ba-slider {
  position: relative;
  min-height: 420px;
  overflow: hidden;
  background: #EAE6D9;
  border: 3px solid var(--ink);
  box-shadow: 10px 10px 0 var(--hazard);
  cursor: ew-resize;
  touch-action: pan-y;      /* vertical swipes still scroll the page */
  user-select: none;
  -webkit-user-select: none;
}
.ba-img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  pointer-events: none;
}
.ba-before-wrap {
  position: absolute; inset: 0;
  clip-path: inset(0 calc(100% - var(--pos, 50%)) 0 0);
}
.ba-tag {
  position: absolute; top: 14px; z-index: 3;
  font-family: var(--font-mono); font-size: 0.72rem; font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase;
  padding: 6px 10px; border: 2px solid var(--ink);
  pointer-events: none;
}
.ba-tag-before { left: 14px; background: var(--alert); color: #fff; }
.ba-tag-after  { right: 14px; background: var(--hazard); color: var(--ink); }
.ba-handle {
  position: absolute; top: 0; bottom: 0; z-index: 4;
  left: var(--pos, 50%);
  width: 4px; margin-left: -2px;
  background: #fff;
  box-shadow: 0 0 0 2px var(--ink);
  outline: none;
}
.ba-handle span {
  position: absolute; top: 50%; left: 50%;
  width: 46px; height: 46px; margin: -23px 0 0 -23px;
  background: var(--hazard);
  border: 3px solid var(--ink);
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
}
.ba-handle span::before { content: "\2194"; font-size: 1.3rem; font-weight: 700; color: var(--ink); line-height: 1; }
.ba-handle:focus-visible span { box-shadow: 0 0 0 4px #fff, 0 0 0 7px var(--ink); }

.ba-copy {
  background: #FFFFFF;
  border: 3px solid var(--ink);
  box-shadow: 10px 10px 0 var(--ink);
  padding: 34px 36px;
  display: flex; flex-direction: column;
}
.ba-copy h3 {
  font-family: var(--font-display);
  font-size: clamp(2.2rem, 4.2vw, 3.6rem);
  line-height: 1; margin: 12px 0 18px; color: var(--ink);
}
.ba-copy h3 span { color: var(--clean); }
.ba-lede { font-size: 1.02rem; line-height: 1.6; color: #3B3A35; margin: 0 0 12px; }
.ba-facts { margin: 14px 0 6px; border-top: 2px solid var(--ink); }
.ba-fact {
  display: flex; justify-content: space-between; gap: 16px;
  padding: 10px 0; border-bottom: 1px solid rgba(23,24,26,.2);
}
.ba-fact-label { font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--paper-dim); padding-top: 3px; }
.ba-fact-val { font-weight: 600; text-align: right; color: var(--ink); }
.ba-cap { font-style: italic; color: #3B3A35; margin: 10px 0 0; }
.ba-actions {
  margin-top: auto; padding-top: 22px;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 14px;
}
.ba-nav { display: inline-flex; align-items: center; gap: 10px; }
.ba-nav .btn { padding: 8px 14px; }
.ba-count { font-family: var(--font-mono); font-size: 0.8rem; color: var(--paper-dim); min-width: 44px; text-align: center; }

@media (max-width: 860px) {
  .ba-feature { grid-template-columns: 1fr; gap: 28px; }
  .ba-slider { min-height: 0; aspect-ratio: 4 / 3; }
  .ba-copy { padding: 24px 22px; }
}
