/* Self-hosted (CSP is style-src/font-src 'self', so no external font
   CDN) -- DM Sans site-wide (headings + body), chosen after a 10-way
   side-by-side comparison. Clean geometric humanist, used as a single
   family for both display and body text rather than pairing two
   different faces. JetBrains Mono stays separate for data-like content
   (phone numbers, hours, addresses) -- ledger/ticket feel. */
@font-face {
  font-family: "DM Sans";
  src: url("fonts/dm-sans-latin-400-normal.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "DM Sans";
  src: url("fonts/dm-sans-latin-600-normal.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "DM Sans";
  src: url("fonts/dm-sans-latin-700-normal.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "JetBrains Mono";
  src: url("fonts/jetbrains-mono-latin-500-normal.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Palette sourced directly from the B1 Rolodex logo: cyan-blue glow,
     deep navy structure, silver hardware ring. Light, glass, technical --
     "Tron on white," not moody-dark. */
  --bg: #ffffff;
  --surface: #ffffff;
  --surface-alt: #f2f5fa;
  --border: #d7dfec;
  --border-strong: #aeb9cc;
  --text: #1a2438;
  --muted: #5c6784;
  --heading: #182236;
  --primary: #2e9bfc;
  --primary-dark: #1979d6;
  --primary-soft: #e3f1ff;
  --primary-contrast: #ffffff;
  --deep: #233148;
  --silver: #a3b5cf;
  --silver-soft: #e4eaf3;
  --accent-warm: #2e9bfc;
  --accent-warm-soft: #e3f1ff;

  /* Glass surface: translucent white over blur, hairline border, soft
     blue-tinted glow instead of a heavy dark drop shadow. */
  --glass-bg: rgba(255, 255, 255, 0.62);
  --glass-border: rgba(163, 181, 207, 0.55);
  --glass-blur: blur(18px);
  --glow: 0 8px 30px rgba(46, 155, 252, 0.14);
  --glow-strong: 0 12px 40px rgba(46, 155, 252, 0.22);

  --shadow: 0 14px 34px rgba(24, 34, 54, 0.1);
  --shadow-soft: 0 2px 10px rgba(24, 34, 54, 0.05);
  --radius: 14px;
  --radius-sm: 10px;
  --radius-lg: 20px;
  --max-width: 1180px;
  --focus: #1979d6;

  /* Utility face for data-like content: phone numbers, hours, addresses,
     stats -- gives the directory a technical/ledger feel. */
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Consolas, monospace;
  --font-display: "DM Sans", system-ui, -apple-system, sans-serif;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  font-family: "DM Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-feature-settings: "ss01" 1;
  letter-spacing: -0.005em;
  background: var(--bg);
  color: var(--text);
  line-height: 1.55;
  text-rendering: optimizeLegibility;
}

img {
  max-width: 100%;
  display: block;
}

a {
  color: var(--primary);
  text-underline-offset: 0.18em;
  text-decoration-thickness: 1.5px;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -0.02em;
}

a:hover {
  color: var(--primary-dark);
}

a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
}

button,
input[type="submit"] {
  min-height: 46px;
  padding: 0.8rem 1.15rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--primary);
  color: var(--primary-contrast);
  font-weight: 650;
  letter-spacing: -0.01em;
  cursor: pointer;
  transition: background 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}

button:hover,
input[type="submit"]:hover {
  background: var(--primary-dark);
  box-shadow: var(--glow-strong);
}

button:active,
input[type="submit"]:active {
  transform: translateY(1px);
}

.skip-link {
  position: absolute;
  left: 1rem;
  top: -4rem;
  z-index: 1000;
  background: var(--primary-dark);
  color: var(--primary-contrast);
  padding: 0.85rem 1rem;
  border-radius: 999px;
  transition: top 0.2s ease;
}

.skip-link:focus {
  top: 1rem;
}

.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;
}

.site-header,
.site-footer,
.hero,
.search,
.card,
.business-card,
.form-card,
.empty {
  position: relative;
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--shadow-soft);
  transition: box-shadow 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}

/* Fallback for browsers without backdrop-filter support: a solid surface
   instead of a half-transparent panel over unpredictable content. */
@supports not (backdrop-filter: blur(1px)) {
  .site-header,
  .site-footer,
  .hero,
  .search,
  .card,
  .business-card,
  .form-card,
  .empty {
    background: var(--surface);
  }
}

.site-header {
  max-width: var(--max-width);
  margin: 1rem auto 1.25rem;
  padding: 0.85rem 1.1rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  position: sticky;
  top: 0.75rem;
  z-index: 40;
}

.site-brand {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
}

.logo-link {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
}

.logo {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.brand-copy {
  min-width: 0;
}

.site-title {
  display: inline-block;
  font-family: var(--font-display);
  font-size: 1.35rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--heading);
  text-decoration: none;
}

.site-tagline {
  margin: 0.2rem 0 0;
  color: var(--muted);
  font-size: 0.95rem;
}

/* --- Hamburger nav ---------------------------------------------------
   A plain <button id="navToggle"> + <nav id="siteNav" class="hidden">,
   toggled by JS in footer.php (adds/removes .hidden + .is-open, flips
   aria-expanded). Deliberately NOT a <details>/<summary> disclosure --
   that relies on the browser fully suppressing its own native marker
   triangle, which is inconsistent across browsers/versions and was
   showing up as a stray, unlabeled arrow. A real button with an explicit
   3-line icon can't render as anything but what we draw. */
.nav-hamburger {
  position: relative;
  margin-left: auto;
}

.hamburger-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 50px;
  height: 50px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--deep);
  cursor: pointer;
}

.hamburger-icon-open,
.hamburger-icon-close {
  width: 26px;
  height: 26px;
}

/* Two separate, explicit SVGs swapped by aria-expanded state, rather
   than one icon animated with CSS transforms -- a static swap can't
   render as an ambiguous "dot," which the animated version was doing in
   at least one browser. */
.hamburger-icon-close {
  display: none;
}

.hamburger-toggle[aria-expanded="true"] .hamburger-icon-open {
  display: none;
}

.hamburger-toggle[aria-expanded="true"] .hamburger-icon-close {
  display: block;
}

.hamburger-toggle[aria-expanded="true"] {
  background: var(--primary-soft);
  border-color: var(--primary);
  color: var(--primary-dark);
}

.site-nav {
  position: absolute;
  right: 0;
  top: calc(100% + 0.5rem);
  z-index: 45;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-width: 240px;
  padding: 0.6rem;
  /* Fully opaque, not the shared --glass-bg glass treatment used
     elsewhere -- a dropdown menu sits directly over page content and
     needs to stay legible. This used to be 94% opaque with a
     backdrop-filter blur, which reads as "basically solid" over plain
     background but isn't actually solid: whatever sits directly behind
     the menu shows through faintly via that leftover 6% transparency,
     blurred by backdrop-filter. Over plain white that's invisible, but
     over something high-contrast (e.g. a card thumbnail with saturated
     colors, like a flag graphic) it becomes a visible color bleed,
     worst at the rounded corners where the blur sampling wraps around
     the clip boundary -- which is what was showing up as faint reddish
     streaking at the menu's bottom corner. Solid color removes the
     effect entirely rather than just reducing it, and backdrop-filter
     is dropped too since it has no visible effect on a fully opaque
     background (nothing shows through to blur).*/
  background: var(--surface);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
  box-shadow: var(--glow);
}

.site-nav.hidden,
html.has-js .site-nav:not(.is-open) {
  display: none;
}

.site-nav a {
  display: flex;
  align-items: center;
  min-height: 42px;
  padding: 0.55rem 0.8rem;
  border-radius: var(--radius-sm);
  color: var(--heading);
  text-decoration: none;
  font-weight: 600;
}

.site-nav a:hover {
  background: var(--primary-soft);
  color: var(--primary-dark);
}

.nav-dropdown {
  position: relative;
}

.nav-dropdown summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 42px;
  padding: 0.55rem 0.8rem;
  border-radius: var(--radius-sm);
  color: var(--heading);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
  position: relative;
  transition: background 0.15s ease, color 0.15s ease;
}

.nav-dropdown summary::-webkit-details-marker {
  display: none;
}

.nav-dropdown summary::marker {
  content: "";
}

.nav-dropdown summary::after {
  content: "";
  width: 7px;
  height: 7px;
  flex: 0 0 auto;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
  transition: transform 0.15s ease;
}

.nav-dropdown[open] summary::after {
  transform: rotate(-135deg);
}

.nav-dropdown summary:hover {
  background: var(--primary-soft);
  color: var(--primary-dark);
}

.nav-dropdown-menu {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.25rem 0 0.25rem 0.75rem;
}

.nav-dropdown-menu a {
  padding: 0.5rem 0.7rem;
  border-radius: var(--radius-sm);
  color: var(--heading);
  text-decoration: none;
  font-weight: 550;
  font-size: 0.92rem;
}

.nav-dropdown-menu a:hover {
  background: var(--primary-soft);
  color: var(--primary-dark);
}

@media (max-width: 640px) {
  .site-nav {
    right: -0.6rem;
    min-width: 220px;
    max-width: calc(100vw - 2rem);
  }
}

.site-main,
.wrap {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 1.1rem 2.5rem;
}

.hero {
  padding: 1.4rem 1.4rem 1.4rem 1.6rem;
  margin-bottom: 1.2rem;
  border-left: 4px solid var(--accent-warm);
}

/* Homepage-only: the hero here now holds nothing visible (the h1/p are
   sr-only -- that same wording moved to the visible footer instead, see
   footer.php), so there's no reason to keep drawing a glass panel box
   around empty space. Every OTHER page's .hero (about, blog, account,
   etc.) keeps its normal boxed treatment -- this only strips it where
   explicitly opted in via the second class. */
.hero.hero--minimal {
  background: none;
  border: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: none;
  padding: 0;
  margin: 0;
}

/* --- Homepage video hero -----------------------------------------------
   The rolodex-flip video fills its own frame; the search form (a real
   .search glass panel, matching everywhere else on the site) sits
   centered on top. Starts paused on frame 0 (see poster + the JS
   controller near the end of this file) -- silent, inline, no controls;
   it's a background element, not a media player.

   Uses CSS Grid stacking: .hero-video-frame and .hero-video-overlay both
   sit in grid-area 1/1, so they ALWAYS occupy the exact same box -- this
   is guaranteed by how grid stacking works, not by carefully matching
   absolute-positioning math against a normal-flow sibling (which is what
   earlier versions did, and which was producing a visible misalignment
   on desktop that this rules out entirely). The grid row's height is
   automatically whichever child needs more room: normally that's the
   video's own aspect-ratio box, but if the search form ever needs more
   vertical space than that on a narrow phone (this happens once the
   options row wraps to two lines), the row simply grows to fit the form
   instead of clipping it -- which is what was causing the mobile search
   bar to disappear entirely in an earlier version. */
.hero.hero--video {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: auto;
  width: min(100%, 780px);
  margin: 0 auto 1rem;
  border-radius: var(--radius-lg);
  background: #fff;
}

.hero-video-frame {
  grid-area: 1 / 1;
  aspect-ratio: 16 / 9;
  max-height: 50vh;
  overflow: hidden;
  border-radius: inherit;
}

.hero-video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.hero-video-overlay {
  grid-area: 1 / 1;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: 1.5rem;
}

.hero-video-overlay .search {
  width: 100%;
  max-width: 640px;
}

/* Locked state while the video plays out a search -- see JS controller.
   Dims and disables interaction rather than hiding the form outright, so
   it's clear something is happening rather than the form just vanishing. */
.hero-video-overlay .search.is-locked {
  opacity: 0.6;
  pointer-events: none;
}

@media (max-width: 640px) {
  .hero.hero--video {
    border-radius: var(--radius-sm);
  }

  .hero-video-frame {
    max-height: 68vw;
  }

  .hero-video-overlay {
    padding: 0.7rem;
    align-items: flex-end;
    padding-bottom: 1rem;
  }

  /* This block is the actual fix for the mobile hero ballooning to
     roughly half the screen ("mostly white" -- because at rest the
     video's own resting frame IS white, so a bloated box just reads as
     empty space). The cause: .hero.hero--video is a CSS Grid stacking
     the video frame and this search overlay in the same cell, and the
     grid row's height auto-sizes to whichever is taller. Below 720px,
     .search-bar-row stacks THREE full-width rows (query input, location
     input, submit button) plus the filter chips below wrap to 2 lines --
     comfortably 300px+ of content on a narrow phone, versus the video's
     own natural 16:9 height (~210px on a 375px-wide phone). The grid
     row was growing to fit that, dragging the whole video frame along
     with it and leaving a large white gap since the video has nothing
     to show past its own aspect box.
     The fix tightens the two things that were driving that overshoot:
     the query input keeps its own full-width row (it's the one field
     worth typing into comfortably), but the location input and submit
     button now share a row instead of each getting their own (removes
     one full stacked row); the button becomes icon-only here to leave
     room for that (aria-label keeps it accessible). Together this keeps
     the overlay's needed height much closer to the video's natural
     size, so the grid row only needs to grow a little instead of
     ballooning by 50%+. */
  .hero-video-overlay .search-bar-row {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "query query"
      "loc submit";
    gap: 0.45rem;
  }

  .search-bar-row .search-input-wrap {
    grid-area: query;
  }

  .search-bar-row > input[type="text"] {
    grid-area: loc;
    margin-bottom: 0;
  }

  .search-bar-row .search-input-wrap input[type="text"] {
    margin-bottom: 0;
  }

  .search-bar-row .search-submit {
    grid-area: submit;
    padding: 0.8rem;
  }

  .search-bar-row .search-submit span {
    /* Visually hidden, not display:none -- stays announced by screen
       readers even though aria-label already covers it too; belt and
       suspenders costs nothing here. */
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }

  /* Wraps to a second line rather than scrolling horizontally -- an
     earlier version made this row horizontal-scroll instead, to keep
     the mobile hero as compact as possible, but a scrollable filter row
     with no visible affordance just reads as content being cut off
     (which is exactly what it looked like: the last chip clipped mid-
     word at the edge of the card, easy to mistake for a layout bug
     rather than "swipe for more"). The 2-row search-bar-row restructure
     above already recovered enough vertical space that wrapping here
     instead only adds back a modest, no-longer-alarming amount of
     height, so it's worth the trade for something that reads correctly
     at a glance instead of looking broken. */
  .hero-video-overlay .search-options-row {
    flex-wrap: wrap;
    margin-top: 0.5rem;
  }

  .hero-video-overlay .search {
    padding: 0.65rem;
  }
}

.hero h1 {
  margin: 0 0 0.35rem;
  font-size: clamp(1.6rem, 2.5vw, 2.2rem);
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--heading);
}

.hero p {
  margin: 0;
  color: var(--muted);
}

.search {
  padding: 0.9rem;
  margin-bottom: 1.2rem;
}

.search-bar-row {
  display: flex;
  align-items: stretch;
  gap: 0.6rem;
}

.search-bar-row .search-input-wrap {
  flex: 1.3 1 0;
}

.search-bar-row > input[type="text"] {
  flex: 1 1 0;
}

.search-submit {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  white-space: nowrap;
}

.search-submit svg {
  width: 18px;
  height: 18px;
}

@media (max-width: 720px) {
  .search-bar-row {
    flex-direction: column;
  }

  .search-submit {
    justify-content: center;
  }

  .search-submit span {
    display: inline;
  }
}

.search-options-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

/* Toggle chips replace raw checkbox+label pairs, whose default browser
   checkbox styling was reading as dated. Real <input type="checkbox">
   underneath (so it's a normal, keyboard/screen-reader accessible form
   control with no JS needed for the toggle itself) -- just visually
   hidden and re-skinned as a pill via the label's :has()-free sibling
   state (the checked style keys off the input's :checked state via the
   general sibling/label-wrapping pattern already used by .star-rating
   elsewhere in this file). */
.search-chip {
  display: inline-flex !important;
  align-items: center;
  min-height: 38px;
  margin: 0 !important;
  padding: 0.4rem 0.9rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-alt);
  color: var(--muted);
  font-size: 0.86rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.search-chip input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.search-chip:has(input:checked) {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--primary-contrast);
}

.search-chip:has(input:focus-visible) {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

/* :has() has broad support at this point, but if it's ever missing,
   fail safe by keeping the real checkbox visible rather than leaving a
   chip with no visible checked/unchecked state at all. */
@supports not selector(:has(a)) {
  .search-chip input {
    position: static;
    opacity: 1;
    width: auto;
    height: auto;
    margin-right: 0.45rem !important;
  }
}

.search-radius-wrap {
  display: inline-flex !important;
  align-items: center;
  gap: 0.4rem;
  min-height: 38px;
  margin: 0 !important;
  padding: 0.35rem 0.8rem 0.35rem 0.6rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--primary-dark);
}

/* Beats the rule above's own !important -- without this, .hidden alone
   (display:none, no !important) silently loses to .search-radius-wrap's
   display:inline-flex !important, and the picker stays visible AND
   interactive even when Near Me isn't checked. */
.search-radius-wrap.hidden {
  display: none !important;
}

.search-radius-wrap svg {
  width: 15px;
  height: 15px;
  flex: 0 0 auto;
}

.search-radius {
  min-height: auto;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--text);
  font-size: 0.86rem;
  font-weight: 600;
}

.search-radius:focus-visible {
  outline: none;
}

/* Universal spacing between a question (label) and its input box, and
   between one field and the next -- applies everywhere on the site
   (business, blog, review, account forms, etc.) without needing to
   touch every individual form file. Deliberately only adds margin here,
   not width/border/padding, so it can't conflict with more specific
   styling (like .form-card's own input rules below) or affect things
   like inline search boxes that aren't meant to be full-width. */
label {
  display: block;
  margin-top: 1rem;
  margin-bottom: 0.35rem;
}

input[type="text"],
input[type="search"],
input[type="email"],
input[type="tel"],
input[type="url"],
input[type="password"],
input[type="number"],
input[type="date"],
textarea,
select {
  width: 100%;
  min-height: 48px;
  margin-bottom: 0.6rem;
  padding: 0.8rem 0.95rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font: inherit;
}

/* Search-bar-row's inputs need to share a row rather than stack full-
   width -- flex-basis (set via flex: 1 1 0 on these already) overrides
   the plain width property in a flex context, so this isn't fighting
   the rule above, just noting why they don't visually go full-width
   despite it. */

textarea {
  min-height: 140px;
  resize: vertical;
}

/* Native <select> keeps the browser's own compact sizing rather than
   the 48px/full-width treatment meant for text-style fields -- a
   dropdown reads oddly stretched edge-to-edge and doesn't need the
   extra height a typed field does. */
select {
  width: auto;
  min-height: 40px;
  padding: 0.55rem 0.8rem;
}

/* Real multi-field forms (add/edit business, submitter vetting, etc.)
   want their <select> dropdowns matching the full-width look of the
   text fields around them, unlike a standalone filter/utility dropdown
   (e.g. the search radius picker) which reads better compact. */
.form-card select {
  width: 100%;
  min-height: 48px;
  padding: 0.8rem 0.95rem;
}

.meta,
.topbar {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  color: var(--muted);
  margin: 0 0 1rem;
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1rem;
}

/* --- Blog listing cards -------------------------------------------------
   Same .card glass/hover treatment as everywhere else on the site, just
   with tighter, more editorial internal spacing than the generic .card
   default (which is tuned for business listings, not article previews). */
.blog-card {
  display: flex;
  flex-direction: column;
  position: relative;
  cursor: pointer;
}

.blog-card-title {
  margin: 0 0 0.35rem;
}

.blog-card-meta {
  margin: 0 0 0.75rem;
  font-size: 0.85rem;
}

.blog-card-excerpt {
  margin: 0 0 1rem;
  line-height: 1.55;
}

.blog-card-readmore {
  margin: auto 0 0;
}

.blog-card-readmore a {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-weight: 650;
}

.blog-card-readmore a::after {
  content: '\2192';
  transition: transform 0.15s ease;
}

.blog-card-readmore a:hover::after {
  transform: translateX(3px);
}

/* --- Blog images -------------------------------------------------------
   Card thumbnails (listing), the post's own featured image, and inline
   content-block images (see blog_post.php / blog_block_editor.php). */
/* --- Contributors page --------------------------------------------------
   Names in a column on the left, photo alongside on the right, on
   anything wide enough for that to look intentional rather than
   cramped. Stacks (photo below the list) on mobile/tablet instead. */
.contributors-layout {
  display: flex;
  align-items: flex-start;
  gap: 2rem;
  flex-wrap: wrap;
}

.contributors-list {
  flex: 2 1 320px;
  margin: 0;
  column-count: 2;
  column-gap: 1.5rem;
}

.contributors-list li {
  break-inside: avoid;
}

.contributors-image {
  flex: 1 1 280px;
  max-width: 360px;
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  display: block;
}

@media (max-width: 900px) {
  .contributors-layout {
    flex-direction: column;
    align-items: stretch;
  }

  .contributors-image {
    max-width: 100%;
    order: 2;
  }

  .contributors-list {
    order: 1;
  }
}

@media (max-width: 480px) {
  .contributors-list {
    column-count: 1;
  }
}

.blog-card-thumb-link {
  display: block;
  margin: -1.1rem -1.1rem 0.9rem;
  border-radius: var(--radius) var(--radius) 0 0;
  overflow: hidden;
}

.blog-card-thumb {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

.page-feature-image {
  display: block;
  float: right;
  width: 100%;
  max-width: 300px;
  height: auto;
  margin: 0 0 1rem 1.5rem;
  border-radius: var(--radius);
}

.page-feature-image--logo {
  max-width: 180px;
  background: none;
}

.page-feature-image--bottom {
  float: none;
  margin: 1.5rem auto 0;
}

.page-feature-image--centered-top {
  float: none;
  margin: 0 auto 1.25rem;
}

.page-feature-image--lg {
  max-width: 440px;
}

.page-feature-image--logo-lg {
  max-width: 280px;
  background: none;
}

/* download_data.php's illustration -- wider than the standard --lg cap
   (440px) since it's a landscape-oriented graphic (roughly 3:2) meant to
   read as a single wide banner, not a square logo-style image. Sized to
   occupy about the same overall footprint the old two-image side-by-side
   pair used to (~544px combined width+gap), just as one image now. */
.page-feature-image--wide {
  max-width: 560px;
}

.page-feature-image-pair {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 1.5rem;
  margin: 0 auto 1.25rem;
}

.page-feature-image-pair-img {
  display: block;
  width: 100%;
  max-width: 260px;
  height: auto;
  border-radius: var(--radius);
}

.page-feature-image-pair-img--logo {
  max-width: 200px;
  background: none;
}

.card-has-float-image::after {
  content: "";
  display: table;
  clear: both;
}

@media (max-width: 640px) {
  .page-feature-image {
    float: none;
    max-width: 220px;
    margin: 0 auto 1rem;
  }

  .page-feature-image--logo {
    max-width: 150px;
  }

  .page-feature-image--lg {
    max-width: 260px;
  }

  .page-feature-image--logo-lg {
    max-width: 180px;
  }

  .page-feature-image--wide {
    max-width: 100%;
  }

  .page-feature-image-pair {
    flex-direction: column;
    gap: 0.75rem;
  }

  .page-feature-image-pair-img {
    max-width: 220px;
  }

  .page-feature-image-pair-img--logo {
    max-width: 160px;
  }
}

.blog-post-featured-image {
  display: block;
  width: 100%;
  max-height: 420px;
  object-fit: cover;
  border-radius: var(--radius);
  margin: 1rem 0;
}

.blog-post-content {
  line-height: 1.7;
}

.blog-post-text-block {
  margin-bottom: 1.25rem;
}

.blog-post-image-link {
  display: block;
}

.blog-post-block-image {
  display: block;
  height: auto;
  border-radius: var(--radius);
  margin: 0.5rem auto 1.5rem;
  transition: opacity 0.15s ease;
}

/* object-fit isn't relevant once width/height are both auto (nothing to
   fit into), but kept here as a safety net in case a future change adds
   an explicit box size back -- contain, never cover, so a photo is
   always shown in full rather than cropped to fill a shape. */
.blog-post-block-image {
  object-fit: contain;
  max-width: 100%;
}

.blog-post-block-image--small {
  width: 100%;
  max-width: 320px;
}

.blog-post-block-image--medium {
  width: 100%;
  max-width: 560px;
}

.blog-post-block-image--large {
  width: 100%;
  max-width: 860px;
}

.blog-post-block-image--full {
  max-width: 100%;
  width: 100%;
}

.blog-post-image-link:hover .blog-post-block-image {
  opacity: 0.9;
}

.blog-post-video-wrap {
  position: relative;
  width: 100%;
  max-width: 860px;
  margin: 0.5rem auto 1.5rem;
  padding-top: 56.25%;
  border-radius: var(--radius);
  overflow: hidden;
  background: #000;
}

.blog-post-video-wrap iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* --- Block editor (add/edit blog) --------------------------------------
   Staff-only (add_blog.php/edit_blog.php are gated by require_role admin/
   editor before any of this renders). */
.block-editor {
  margin-bottom: 1rem;
}

.block-editor-block {
  position: relative;
  padding: 1rem;
  margin-bottom: 0.85rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-alt);
}

.block-editor-block--text textarea {
  margin-bottom: 0;
}

.block-editor-block--image label {
  margin-top: 0.7rem;
}

.block-editor-block--image label:first-child {
  margin-top: 0;
}

.block-editor-block--video label:first-child {
  margin-top: 0;
}

.block-editor-preview {
  display: block;
  max-width: 240px;
  max-height: 160px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  margin: 0.5rem 0;
  border: 1px solid var(--border);
}

.block-remove-btn {
  margin-top: 0.75rem;
  min-height: 36px;
  padding: 0.4rem 0.8rem;
  font-size: 0.85rem;
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--border);
}

.block-remove-btn:hover {
  background: #fff1f1;
  color: #8a1f1f;
  border-color: #f0b7b7;
  box-shadow: none;
}

.block-editor-add-row {
  display: flex;
  gap: 0.6rem;
  margin-bottom: 1rem;
}

.block-add-btn {
  background: var(--surface);
  color: var(--primary-dark);
  border: 1px solid var(--primary);
  font-size: 0.9rem;
}

.block-add-btn:hover {
  background: var(--primary-soft);
  box-shadow: none;
}

/* Search results specifically: a steady 2-column grid (matches the 8
   results-per-page batch size below) rather than auto-fit, which would
   otherwise squeeze in a 3rd/4th column on wide monitors and make the
   rectangular business-card layout look cramped and inconsistent. */
.search-results-grid {
  grid-template-columns: repeat(2, 1fr);
}

@media (max-width: 720px) {
  .search-results-grid {
    grid-template-columns: 1fr;
  }
}

.resource-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1rem;
  margin-top: 1rem;
}

.resource-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-soft);
  padding: 1.15rem;
  transition: box-shadow 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}

.resource-card:hover {
  border-color: var(--accent-warm);
  box-shadow: var(--shadow);
  transform: translateY(-2px);
}

.resource-card h2 {
  margin: 0 0 0.5rem;
  font-size: 1.1rem;
  letter-spacing: -0.01em;
  color: var(--heading);
}

.resource-helps {
  color: var(--muted);
  font-size: 0.92rem;
  margin: 0 0 0.75rem;
}

.resource-contact {
  margin: 0;
  font-size: 0.92rem;
  overflow-wrap: anywhere;
}

.resource-contact a {
  overflow-wrap: anywhere;
}

.card,
.business-card,
.form-card {
  padding: 1.15rem;
}

/* Safety net for long unbroken tokens (URLs, mostly) inside any card --
   without this, a long link/code string with no natural break points
   (spaces, hyphens) overflows past the card's edge on narrow screens
   instead of wrapping. overflow-wrap: anywhere only kicks in when a
   token genuinely doesn't fit, so it never affects normal text wrapping
   otherwise. */
.card a,
.card code,
.business-card a,
.business-card code {
  overflow-wrap: anywhere;
}

.card:hover {
  border-color: #b8cce4;
}

.grid .card:hover,
.grid .business-card:hover {
  border-color: var(--accent-warm);
  box-shadow: var(--shadow);
  transform: translateY(-2px);
}

.name {
  margin: 0 0 0.45rem;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.15rem;
  line-height: 1.25;
  letter-spacing: -0.02em;
}

.name a {
  color: var(--heading);
  text-decoration: none;
}

.name a:hover {
  color: var(--primary);
  text-decoration: underline;
}

/* The article's own H1 -- .name alone (1.15rem) is sized for card titles
   used everywhere across the site, which is too small for a blog post's
   main heading. Noticeably larger on desktop specifically, where there's
   room for it to actually read as the page's headline. */
.blog-post-title {
  font-size: 1.6rem;
  line-height: 1.2;
}

@media (min-width: 768px) {
  .blog-post-title {
    font-size: 2.5rem;
  }
}

.small {
  font-size: 0.95rem;
}

.muted {
  color: var(--muted);
}

.tag {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: 0.2rem 0.7rem;
  margin: 0.35rem 0.35rem 0.25rem 0;
  border-radius: var(--radius-sm);
  background: var(--primary-soft);
  color: var(--primary-dark);
  border: 1px solid #bfd2ee;
  font-size: 0.88rem;
  font-weight: 650;
}

.empty {
  padding: 1.2rem;
  color: var(--muted);
  border-style: dashed;
}

.pager {
  margin-top: 1.25rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.pager a {
  min-width: 42px;
  min-height: 42px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.45rem 0.85rem;
  border-radius: 12px;
  text-decoration: none;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.9);
  color: var(--heading);
}

.pager a.active {
  background: var(--primary);
  color: var(--primary-contrast);
  border-color: var(--primary);
}

.site-footer {
  max-width: var(--max-width);
  margin: 0 auto 1.25rem;
  padding: 1.1rem;
  color: var(--muted);
}

.footer-top {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 2.5rem;
}

.footer-intro {
  max-width: 380px;
}

.footer-intro-title {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.1rem;
  letter-spacing: -0.02em;
  color: var(--heading);
  text-decoration: none;
}

.footer-intro-logo {
  width: 24px;
  height: 24px;
  object-fit: contain;
  flex: 0 0 auto;
}

.footer-intro-title:hover {
  color: var(--primary-dark);
}

.footer-intro p {
  margin: 0.5rem 0 1.1rem;
  color: var(--muted);
  font-size: 0.92rem;
  line-height: 1.5;
}

.footer-social {
  margin: 0;
}

/* Right-hand side: nav columns as a group, pushed to the right edge of
   the footer via the parent .footer-top's justify-content: space-between
   -- but the LINK TEXT inside each column stays left-aligned to its own
   column (not to the footer as a whole), which is what actually reads
   as tidy stacked lists rather than a ragged right edge. */
.footer-columns {
  display: flex;
  flex: 1 1 auto;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 2.5rem 3.5rem;
  text-align: left;
}

.footer-nav-group-stack {
  display: flex;
  flex-direction: column;
  gap: 1.8rem;
}

.footer-nav-column--stacked {
  gap: 0.45rem;
}

.footer-nav-column--right {
  text-align: left;
}

.footer-nav-column {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  min-width: 160px;
}

.footer-nav-column a {
  color: var(--muted);
  text-decoration: none;
  font-size: 0.92rem;
}

.footer-nav-column a:hover {
  color: var(--primary-dark);
  text-decoration: underline;
}

.social-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.footer-label {
  margin: 0 0 0.6rem;
  font-weight: 700;
  color: var(--heading);
}

.footer-label--sub {
  margin-top: 1.1rem;
}

.footer-label--follow {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted);
  margin-bottom: 0.5rem;
}

@media (max-width: 900px) {
  /* Real single-column stacking, not just wrapping -- .footer-columns
     uses justify-content:flex-end for its desktop right-aligned layout,
     and if that's still active while wrapping on a narrower screen,
     each wrapped row right-aligns independently at whatever width it
     happens to be, producing a staggered "staircase" effect (each row
     starting at a different horizontal position) instead of a clean
     stack. Once everything is flex-direction:column with align-items:
     flex-start, there's no horizontal justification left to cause that.
     900px rather than a typical mobile-only 640px breakpoint -- the
     footer's side-by-side layout (intro block + 3 link columns) needs
     more room than most tablets provide even in landscape, so a
     mobile-only breakpoint left tablets hitting the same staircase
     issue phones originally had. */
  .footer-top,
  .footer-columns,
  .footer-nav-group-stack {
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    width: 100%;
    gap: 1.75rem;
  }

  .footer-nav-column {
    min-width: 0;
    width: 100%;
  }

  .footer-intro {
    max-width: 100%;
  }
}

/* Minimal icon circles (line-icon SVGs, see footer.php) instead of the
   old text pill buttons -- consistent with the icon-first nav/share
   treatment used everywhere else. */
.social-links a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-alt);
  color: var(--muted);
  text-decoration: none;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}

.social-links a:hover {
  background: var(--primary-soft);
  color: var(--primary-dark);
  border-color: var(--primary);
  box-shadow: var(--glow);
}

.social-links svg {
  width: 17px;
  height: 17px;
}

/* --- Cookie consent banner --------------------------------------------
   Small, dismissible, sticky at the bottom of the viewport until a
   choice is made. JS in footer.php reads/writes a single localStorage
   flag -- no cookie itself is used to remember the choice (would be a
   little on-the-nose for a banner ABOUT cookies), and nothing is set
   until the person actually clicks accept.

   Deliberately plain block markup, not flexbox -- this banner went
   through several rounds chasing a real, reproducible Chrome-mobile bug
   (an oversized box with a large blank gap between the text and the
   buttons) under a few different theories (backdrop-filter, an animated
   transform, position:fixed itself), and the same symptom kept
   reappearing under completely different CSS each time, including a
   plain position:static version with no flexbox-adjacent properties
   removed at all. That ruled out every specific property those versions
   had in common except flex-wrap, so this version removes flexbox
   entirely as the next-most-conservative thing left to try: the
   paragraph and the button row are both just plain block-level
   elements, stacked top to bottom, no flex/wrap/basis anywhere. Also no
   show/hide animation this time (straight display:none/block toggle) --
   one less moving part. */
.cookie-banner {
  position: fixed;
  left: 1rem;
  right: 1rem;
  bottom: 1rem;
  z-index: 200;
  max-width: 560px;
  margin: 0 auto;
  padding: 0.9rem 1rem;
  background: var(--surface);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
  box-shadow: var(--glow-strong);
}

.cookie-banner.hidden {
  display: none;
}

.cookie-banner p {
  margin: 0 0 0.75rem;
  font-size: 0.85rem;
  color: var(--text);
  line-height: 1.4;
}

.cookie-banner a {
  font-weight: 600;
}

.cookie-banner-actions {
  display: block;
}

.cookie-banner-actions button {
  display: inline-block;
  min-height: 38px;
  padding: 0.5rem 0.9rem;
  font-size: 0.85rem;
  margin: 0 0.5rem 0 0;
}

.cookie-decline {
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--border);
}

.cookie-decline:hover {
  background: var(--surface-alt);
  box-shadow: none;
  color: var(--text);
}

@media (max-width: 640px) {
  .cookie-banner {
    left: 0.6rem;
    right: 0.6rem;
    bottom: 0.6rem;
  }

  .cookie-banner-actions button {
    display: block;
    width: 100%;
    margin: 0 0 0.5rem;
  }

  .cookie-banner-actions button:last-child {
    margin-bottom: 0;
  }
}

.bad {
  padding: 0.9rem 1rem;
  margin-bottom: 1rem;
  border-radius: 12px;
  background: #fff1f1;
  border: 1px solid #f0b7b7;
  color: #8a1f1f;
}

.danger-link {
  color: #8a1f1f;
  font-weight: 600;
}

.danger-link:hover {
  color: #d11149;
}

.danger-button {
  background: #fff1f1;
  color: #8a1f1f;
  border: 1px solid #f0b7b7;
}

.danger-button:hover {
  background: #f0b7b7;
}

.good {
  padding: 0.9rem 1rem;
  margin-bottom: 1rem;
  border-radius: 12px;
  background: #eef9f1;
  border: 1px solid #b8e0c1;
  color: #1e6a34;
}

/* SEO/readability traffic-light panel (edit_blog.php) -- reuses the
   .good/.bad color language above, plus a yellow "warn" tier those two
   don't cover. Each check row is a compact card so a dozen of them
   don't feel like a wall of text. */
.seo-panel {
  margin-bottom: 1.25rem;
}

.seo-check-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

.seo-check {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.6rem 0.8rem;
  border-radius: 10px;
  border: 1px solid #e2e2e2;
  background: #fafafa;
}

.seo-check-dot {
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  margin-top: 0.25rem;
  border-radius: 50%;
}

.seo-check-red {
  background: #fff1f1;
  border-color: #f0b7b7;
}

.seo-check-red .seo-check-dot {
  background: #c0392b;
}

.seo-check-yellow {
  background: #fffaf0;
  border-color: #f0dfa1;
}

.seo-check-yellow .seo-check-dot {
  background: #d4a017;
}

.seo-check-green {
  background: #eef9f1;
  border-color: #b8e0c1;
}

.seo-check-green .seo-check-dot {
  background: #1e6a34;
}

.seo-check-body {
  flex: 1 1 auto;
}

.seo-check-label {
  font-weight: 650;
  font-size: 0.92rem;
}

.seo-check-detail {
  font-size: 0.86rem;
  color: var(--muted);
}

/* Overall score headline row, above the individual keyword/readability
   checks -- deliberately larger/bolder than a .seo-check row so it
   reads as a summary, not just one more item in the list. */
.seo-overall-score {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.9rem 1rem;
  margin-bottom: 0.9rem;
  border-radius: 12px;
  border: 1px solid #e2e2e2;
}

.seo-overall-score .seo-check-dot {
  width: 18px;
  height: 18px;
  margin-top: 0.15rem;
}

.seo-overall-score-label {
  font-weight: 700;
  font-size: 1.05rem;
}

.seo-overall-score-detail {
  font-size: 0.88rem;
  color: var(--muted);
}

.map-link {
  margin: 0.75rem 0 1.25rem;
}

/* Secondary/ghost submit button -- used where a form has a primary
   action (Save) plus a lower-stakes secondary action (Preview) that
   shouldn't visually compete with it. */
.btn-secondary {
  background: #fff;
  color: var(--primary-dark);
  border: 1px solid #cfd8e3;
  margin-top: 0.6rem;
}

.btn-secondary:hover {
  background: var(--primary-soft);
}

.map-button {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0.7rem 1rem;
  border-radius: var(--radius-sm);
  background: var(--primary);
  color: var(--primary-contrast);
  text-decoration: none;
  font-weight: 700;
}

@media (max-width: 920px) {
  .search {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .site-header,
  .hero,
  .search,
  .card,
  .business-card,
  .site-footer,
  .form-card {
    padding: 1rem;
    border-radius: var(--radius-sm);
  }

  .site-title {
    font-size: 1.15rem;
  }

  .site-tagline {
    display: none;
  }

  .site-nav a {
    width: 100%;
  }

  .meta,
  .topbar {
    flex-direction: column;
    align-items: flex-start;
    /* Just a hair of breathing room off the edge -- NOT the 2.75rem
       clearance tried in an earlier round, which read as a full
       indentation. This is roughly one character's width, not a layout
       fix for the sidebar overlap. */
    padding-left: 1ch;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    transition: none !important;
    animation: none !important;
  }
}

@media (forced-colors: active) {
  .site-header,
  .card,
  .business-card,
  .site-footer,
  .search,
  .hero,
  .form-card,
  .empty {
    border: 1px solid CanvasText;
  }
}
.map-embed {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface);
}

.map-embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
.google-form-embed {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

.google-form-embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Utility classes — these replace scattered inline style="" attributes so
   the site can run a stricter Content-Security-Policy (style-src without
   'unsafe-inline'). */
.card-narrow-lg {
  max-width: 900px;
  margin: 0 auto;
}

.card-narrow-md {
  max-width: 560px;
  margin: 0 auto;
}

.card-narrow-sm {
  max-width: 420px;
  margin: 0 auto;
}

/* Full-width, consistently spaced submit buttons across the whole auth
   flow (login, signup, forgot/reset password, etc.) -- previously these
   sat at their native inline size while the fields above them were
   full-width, which read as visually mismatched/unfinished, especially
   on a narrow phone screen. */
.card-narrow-sm form button[type="submit"] {
  width: 100%;
  margin-top: 0.4rem;
}

.card-narrow-sm form button[type="submit"] + button[type="submit"] {
  margin-top: 0.6rem;
}

.mt-1 {
  margin-top: 1rem;
}

.mb-tight {
  margin: 0 0 10px;
}

.inline-link {
  color: inherit;
  text-decoration: underline;
}

.hidden {
  display: none;
}

.d-inline {
  display: inline;
}

.d-inline-block {
  display: inline-block;
}

.card-subtle {
  background: var(--surface-alt);
}

.map-popup-name {
  font-size: 1.1em;
}

.map-popup-label,
.map-popup-address {
  font-size: 0.85em;
  color: #555;
}

.map-popup-address {
  margin-top: 2px;
}

.map-popup-desc {
  font-size: 0.85em;
  color: #444;
  margin-top: 4px;
}

.map-popup-website,
.map-popup-link {
  margin-top: 2px;
}

.map-popup-link {
  margin-top: 4px;
}

.map-search-row-address {
  font-size: 0.85em;
  color: #666;
}

.mt-05 {
  margin-top: 0.5rem;
}

.mt-06 {
  margin-top: 0.6rem;
}

.mt-025 {
  margin-top: 0.25rem;
}

.mt-075 {
  margin-top: 0.75rem;
}

.mb-1 {
  margin-bottom: 1rem;
}

.location-row-pad {
  margin-bottom: 0.75rem;
  padding: 0.85rem;
}

.inline-flex-gap {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.location-type-choice {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin: 0.5rem 0 1rem;
}

.location-type-choice label {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  font-weight: normal;
}

.status-draft {
  background: #666;
  color: #fff;
}

/* Leaflet map containers -- used on business.php (small, single business)
   and business_map_leaflet.php (large, admin-only, every business). */
.db-map {
  width: 100%;
  height: 300px;
  border-radius: var(--radius-sm);
  margin-top: 0.5rem;
  background: var(--surface-alt);
  border: 1px solid var(--border);
}

.db-map-tall {
  height: 70vh;
  min-height: 420px;
}

.map-help {
  font-size: 0.85rem;
  color: var(--muted);
  margin-top: 0.4rem;
}

.db-map {
  width: 100%;
  height: 340px;
  border-radius: var(--radius-sm);
  margin-top: 0.5rem;
  background: var(--surface-alt);
  border: 1px solid var(--border);
}

.db-map-tall {
  height: 70vh;
  min-height: 420px;
}

.map-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  padding: 0.6rem;
  width: 280px;
  box-sizing: border-box;
}

.map-panel input[type="text"] {
  width: 100%;
  min-height: 40px;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
}

.loc-place-search {
  width: 100%;
  min-height: 44px;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
}

.loc-search-result {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--border);
}

.loc-search-result span {
  cursor: pointer;
  color: var(--primary);
  text-decoration: underline;
  flex: 1;
}

.loc-search-result span:hover {
  color: var(--primary-dark);
}

.loc-add-pin-here-btn {
  flex: 0 0 auto;
  min-height: 34px;
  padding: 0.35rem 0.7rem;
  font-size: 0.85rem;
}

.loc-remove-pin-btn {
  background: #fff1f1;
  color: #8a1f1f;
  border: 1px solid #f0b7b7;
}

.loc-remove-pin-btn:hover {
  background: #f0b7b7;
}

/* Heart button, all states explicitly reset background-color/box-shadow
   -- otherwise the generic `button:hover` rule (background: var(--
   primary-dark); box-shadow: var(--glow-strong)) wins on hover despite
   .favorite-heart's own background:none, because `button:hover`
   (type+pseudo-class) is MORE specific than a plain class selector for
   that one property. That mismatch was the strange blue halo around the
   heart on hover -- every state below is written explicitly enough to
   always out-specificity that global rule.

   Uses solid colors from the site's existing blue palette rather than a
   red gradient -- a background-clip:text gradient was tried here first
   but rendered as invisible/white in at least one browser (the Unicode
   heart glyph didn't composite with the clipped gradient reliably), so
   plain, robust solid colors it is. */
.favorite-heart {
  background-color: transparent;
  border: none;
  box-shadow: none;
  cursor: pointer;
  font-size: 1.4rem;
  line-height: 1;
  padding: 0 0.3rem;
  color: var(--silver);
  vertical-align: middle;
  transition: transform 0.2s ease, color 0.2s ease;
}

/* The heart is a real inline SVG (fill="currentColor") rather than the
   Unicode ♥ character. Some Android tablets' emoji font renders a bare
   U+2665 glyph as a fixed-color red emoji no matter what CSS `color` or
   even a U+FE0E text-presentation selector says -- that was the actual
   cause of the heart looking "stuck on red" and unresponsive to clicks
   on tablet (the class toggle was always working; the color just wasn't
   listening). An SVG path can't be swapped out by an emoji font the way
   a text character can, so `currentColor` is guaranteed to track
   .favorite-heart's own `color` on every device. */
.favorite-heart-icon {
  display: inline-block;
  vertical-align: middle;
  pointer-events: none;
}

/* :hover is scoped to hover-CAPABLE pointers only (a real mouse) --
   deliberately excluded on touch devices. Touchscreens don't have a
   real "hover" concept; tapping a button on mobile makes the browser
   apply :hover after the tap and then leave it stuck active until the
   next tap lands somewhere else ("sticky hover"). That was the actual
   cause of the reported mobile bug: tapping heart X applied the
   *hover* color (--primary, bright blue) as a leftover sticky state,
   which looked confusingly similar to the real *favorited* color
   (--primary-dark) -- so a heart could look "already blue" from
   leftover hover alone, independent of whether it was truly favorited.
   Tapping heart Y then cleared X's stuck hover and revealed its real
   state underneath, which is what looked like an unexplained
   color flip. Restricting :hover this way means touch devices only
   ever show the real, class-driven color (default gray or
   .is-favorited blue) plus a brief :active tap flash -- no more
   leftover states to confuse with the real one. */
@media (hover: hover) and (pointer: fine) {
  .favorite-heart:hover,
  .favorite-heart:focus-visible {
    background-color: transparent;
    box-shadow: none;
    color: var(--primary);
    transform: scale(1.1);
  }

  .favorite-heart.is-favorited:hover,
  .favorite-heart.is-favorited:focus-visible {
    background-color: transparent;
    box-shadow: none;
    color: var(--deep);
    transform: scale(1.1);
  }
}

/* :focus-visible stays available on all devices outside the media
   query too (keyboard/assistive-tech navigation isn't pointer-based),
   just without the hover-only rules above layering on top of it. */
.favorite-heart:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.favorite-heart:active {
  background-color: transparent;
  box-shadow: none;
  color: var(--primary-dark);
  transform: scale(1.1);
}

.favorite-heart.is-favorited {
  background-color: transparent;
  box-shadow: none;
  color: var(--primary-dark);
  transform: none;
}

.favorite-heart.is-favorited:active {
  background-color: transparent;
  box-shadow: none;
  color: var(--deep);
  transform: scale(1.1);
}

/* --- Search result cards ----------------------------------------------
   Whole card is a click target via .card-stretched-link (a transparent
   <a> stretched to fill the card with position:absolute; inset:0), so
   clicking anywhere -- not just the name text -- opens the business.
   The heart button and the two real text links need position:relative
   plus a higher z-index to remain independently clickable above that
   overlay; everything else just inherits the click-through. */
.business-result-card {
  position: relative;
  display: flex;
  flex-direction: column;
  cursor: pointer;
}

.card-stretched-link {
  position: absolute;
  inset: 0;
  z-index: 1;
}

.card-stretched-link:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: -3px;
  border-radius: var(--radius);
}

.hub-card {
  position: relative;
  cursor: pointer;
}

.hub-card-badge {
  position: absolute;
  top: 0.85rem;
  right: 0.85rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.5rem;
  height: 1.5rem;
  padding: 0 0.35rem;
  border-radius: 999px;
  background: var(--primary);
  color: #fff;
  font-size: 0.85rem;
  font-weight: 700;
  line-height: 1;
}

/* Read-only percentage-filled star rating (business/location ratings) --
   two identical 5-star rows stacked on top of each other: a gray
   background row, and a blue foreground row clipped via percentage
   width so a fractional rating like 4.8 shows 4 full stars plus a 5th
   star that's only 80% filled. Pure CSS, no images. */
.rating-stars-wrap {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
}

.rating-stars {
  position: relative;
  display: inline-block;
  font-size: 1.05rem;
  line-height: 1;
  letter-spacing: 0.1em;
}

.rating-stars-bg,
.rating-stars-fg {
  display: block;
  white-space: nowrap;
}

.rating-stars-bg {
  color: #dbe2ea;
}

.rating-stars-fg {
  position: absolute;
  top: 0;
  left: 0;
  overflow: hidden;
  color: var(--primary-dark);
  width: 0;
}

.rating-stars-value {
  font-weight: 700;
  font-size: 0.92rem;
  color: var(--heading);
}

.rating-stars-count {
  font-weight: 500;
  color: var(--muted);
}

.business-result-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.6rem;
}

.business-result-name {
  margin: 0;
}

.business-result-card .favorite-heart {
  position: relative;
  z-index: 2;
  flex: 0 0 auto;
  margin-top: -0.15rem;
}

.business-result-meta {
  margin-top: 0.4rem;
}

.business-result-teaser {
  margin-top: 0.5rem;
}

.business-result-view {
  position: relative;
  z-index: 2;
  white-space: nowrap;
  font-weight: 650;
}

@keyframes heart-pop {
  0% { transform: scale(1); }
  45% { transform: scale(1.5); }
  100% { transform: scale(1.05); }
}

.favorite-heart.just-toggled {
  animation: heart-pop 0.35s ease;
}

.review-helpful-row {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin-top: 0.6rem;
}

.review-helpful-row .review-helpful-heart,
.review-helpful-row .comment-helpful-heart {
  padding: 0 0.15rem;
}

.review-helpful-label {
  font-weight: 650;
}

.review-helpful-login-prompt {
  font-weight: 650;
}

/* 5-star toggle rating widget -- real radio inputs underneath (so it's
   keyboard/screen-reader accessible and needs no JS), visually reversed
   via flex-direction so hovering/selecting a star also highlights every
   star to its left. Used anywhere a star rating gets picked: writing a
   review, and the staff trusted-review forms. */
.star-rating {
  display: inline-flex;
  flex-direction: row-reverse;
  justify-content: flex-end;
  gap: 0.15rem;
}

.star-rating input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.star-rating label {
  display: inline-block;
  margin: 0;
  font-size: 2.2rem;
  line-height: 1;
  color: #d8d8d8;
  cursor: pointer;
  transition: color 0.1s ease, transform 0.1s ease;
}

.star-rating input:checked ~ label,
.star-rating label:hover,
.star-rating label:hover ~ label {
  color: #f5a623;
}

.star-rating--trusted input:checked ~ label,
.star-rating--trusted label:hover,
.star-rating--trusted label:hover ~ label {
  color: var(--primary-dark);
}

.star-rating input:focus-visible ~ label {
  outline: 2px solid #245ea8;
  outline-offset: 2px;
  border-radius: 4px;
}

.star-rating-value {
  font-size: 0.9rem;
  color: var(--muted);
  margin-left: 0.5rem;
  vertical-align: middle;
}

.map-search-results {
  max-height: 260px;
  overflow-y: auto;
  margin-top: 0.4rem;
}

.map-search-results:empty {
  margin-top: 0;
  max-height: 0;
}

.map-near-me-wrap {
  position: relative;
}

/* Deliberately understated -- a small icon circle, not a labeled
   button, sitting quietly in its own corner rather than competing with
   the search panel for attention. */
.map-near-me-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  min-height: 34px;
  padding: 0;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--muted);
  box-shadow: var(--shadow);
}

.map-near-me-icon-btn:hover {
  background: var(--primary-soft);
  color: var(--primary-dark);
  border-color: var(--primary);
}

.map-near-me-icon-btn svg {
  width: 18px;
  height: 18px;
}

.map-near-me-status {
  position: absolute;
  right: 0;
  bottom: calc(100% + 0.4rem);
  width: 200px;
  margin: 0;
  padding: 0.5rem 0.7rem;
  border-radius: var(--radius-sm);
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
  font-size: 0.8rem;
  color: var(--muted);
}

/* "You are here" marker -- a pulsing blue dot, deliberately distinct
   from the business pins (which are solid colored circles with no
   animation), so it reads unambiguously as "this is you," not another
   listing. */
.user-location-marker {
  position: relative;
}

.user-location-dot {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 14px;
  height: 14px;
  margin: -7px 0 0 -7px;
  border-radius: 50%;
  background: var(--primary);
  border: 2px solid #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
}

.user-location-pulse {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 22px;
  height: 22px;
  margin: -11px 0 0 -11px;
  border-radius: 50%;
  background: var(--primary);
  opacity: 0.35;
  animation: user-location-pulse 1.8s ease-out infinite;
}

@keyframes user-location-pulse {
  0% {
    transform: scale(0.5);
    opacity: 0.45;
  }
  100% {
    transform: scale(1.8);
    opacity: 0;
  }
}

.map-search-results > div {
  padding: 0.4rem 0.5rem;
  border-radius: 6px;
  font-size: 0.9rem;
  cursor: pointer;
}

.map-search-results > div:hover {
  background: var(--primary-soft);
}

.map-pick-btn {
  min-height: 38px;
  padding: 0.5rem 0.9rem;
  font-size: 0.9rem;
}

#faqSearch {
  width: 100%;
  min-height: 46px;
  padding: 0.7rem 1rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  font-size: 1rem;
}

.faq-item {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  margin-top: 0.6rem;
  padding: 0.9rem 1.1rem;
}

.faq-item summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--heading);
  list-style: none;
}

.faq-item summary::-webkit-details-marker {
  display: none;
}

.faq-item summary::before {
  content: '+';
  display: inline-block;
  width: 1.2rem;
  color: var(--primary);
  font-weight: 700;
}

.faq-item[open] summary::before {
  content: '−';
}

.faq-answer {
  margin-top: 0.6rem;
  padding-left: 1.2rem;
  color: var(--text);
  line-height: 1.5;
}

.nav-badge-anchor {
  position: relative;
  display: inline-block;
  padding-right: 0.9rem;
}

.notify-count {
  position: absolute;
  top: -0.5rem;
  right: -0.2rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.15rem;
  height: 1.15rem;
  padding: 0 0.25rem;
  border-radius: 999px;
  background: var(--primary);
  color: #fff;
  font-size: 0.68rem;
  font-weight: 700;
  line-height: 1;
}

/* Subtle highlight on notifications.php for items added since the
   person's last visit -- the .tag "New" label inside the card is the
   primary signal (works fine without color), this is just a light
   assist so a long list still visually separates old from new items
   without relying on color alone (a plain left-border accent, not a
   background fill, so it stays readable at a glance either way). */
.notification-card--new {
  border-left: 3px solid var(--primary);
}

.pagination-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.25rem;
  margin: 1.5rem 0;
}

.pagination-disabled {
  color: var(--silver);
}

.pagination-status {
  font-size: 0.9rem;
  color: var(--muted, inherit);
}

.location-card-mappable {
  transition: box-shadow 0.15s ease, border-color 0.15s ease;
}

.location-card-mappable:hover {
  box-shadow: 0 0 0 2px var(--primary-soft);
}

.location-card-selected {
  border-color: var(--primary);
  box-shadow: 0 0 0 2px var(--primary);
}

.share-widget {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

/* Small icon circles shown directly, side by side -- no hidden dropdown
   to discover. Real platform icons (Facebook/X/email) plus a copy-link
   utility icon; see share_button.php. */
.share-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  min-height: 32px;
  padding: 0;
  border-radius: var(--radius-sm);
  border: 1px solid var(--glass-border);
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  color: var(--heading);
  box-shadow: var(--shadow-soft);
}

@supports not (backdrop-filter: blur(1px)) {
  .share-icon {
    background: var(--surface);
  }
}

.share-icon svg {
  width: 15px;
  height: 15px;
}

.share-icon:hover {
  background: var(--primary-soft);
  color: var(--primary-dark);
  border-color: var(--primary);
  box-shadow: var(--glow);
}

/* Site-wide fixed share sidebar (see footer.php) -- one instance per
   page, pinned to the left edge and vertically centered in the
   viewport, so it stays visible on scroll instead of disappearing once
   you scroll past whatever card it used to live inside. */
.share-widget-fixed {
  position: fixed;
  left: 1rem;
  top: 50%;
  transform: translateY(-50%);
  z-index: 60;
  opacity: 1;
  transition: opacity 0.2s ease;
}

/* Toggled by an IntersectionObserver in footer.php while the footer is
   on screen -- see the comment there for why this exists. */
.share-widget-fixed.is-hidden-for-footer {
  opacity: 0;
  pointer-events: none;
}

.share-widget-fixed .share-widget {
  flex-direction: column;
}

@media (max-width: 640px) {
  .share-widget-fixed {
    left: 0.5rem;
  }
}

.search-input-wrap {
  position: relative;
}

.search-suggestions {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% + 0.3rem);
  max-height: min(60vh, 420px);
  overflow-y: auto;
  overflow-x: hidden;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  z-index: 30;
  /* Explicit, always-visible scrollbar rather than relying on the OS/
     browser default -- some platforms (notably Firefox on Linux) use an
     auto-hiding overlay scrollbar that's only drawn while actively
     scrolling, which is easy to mistake for "there's no scrollbar" the
     rest of the time. */
  scrollbar-width: auto;
  scrollbar-color: var(--deep) var(--surface-alt);
}

.search-suggestions::-webkit-scrollbar {
  width: 12px;
}

.search-suggestions::-webkit-scrollbar-track {
  background: var(--surface-alt);
}

.search-suggestions::-webkit-scrollbar-thumb {
  background: var(--deep);
  border-radius: 6px;
  border: 2px solid var(--surface-alt);
}

.search-suggestions.hidden {
  display: none;
}

@media (max-width: 640px) {
  /* Belt-and-suspenders alongside the JS guard above (input width check)
     -- this box must never be able to render on a phone, full stop. */
  .search-suggestions {
    display: none !important;
  }
}

.search-suggestion-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 0.6rem 0.8rem;
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--border);
  min-height: auto;
  box-shadow: none;
  font-size: 0.9rem;
  color: var(--heading);
  cursor: pointer;
}

.search-suggestion-item:last-child {
  border-bottom: none;
}

.search-suggestion-item:hover,
.search-suggestion-item.is-active {
  background: var(--primary-soft);
  color: var(--primary-dark);
  box-shadow: none;
}

.search-suggestion-count {
  font-weight: 650;
}

.search-suggestion-business {
  display: flex;
  align-items: center;
}

.search-suggestion-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.search-suggestion-label {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  padding: 0.4rem 0.8rem 0.2rem;
}

.search-suggestion-show-more {
  color: var(--primary-dark);
  font-weight: 650;
}

/* --- About page: "How We're Different" comparison chart --------------
   Two side-by-side cards (flex, not a real <table>) so it reflows to a
   single stacked column on tablet/mobile automatically via flex-wrap,
   rather than needing a separate mobile-only layout. "Us" column uses
   the primary blue + checkmarks, "Other sites" column uses a neutral
   silver/gray + x-marks, so the contrast reads at a glance even before
   reading the text. Content itself is placeholder text -- ok/expected
   for it to look repetitive until the real wording is dropped in. */
.compare-chart {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  margin: 1.25rem 0 1.75rem;
}

.compare-col {
  flex: 1 1 300px;
  border-radius: var(--radius);
  padding: 1.5rem 1.75rem;
  box-shadow: var(--shadow-soft);
}

.compare-col h3 {
  margin-top: 0;
  font-size: 1.15rem;
}

.compare-col ul {
  list-style: none;
  margin: 0.75rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
}

.compare-col li {
  padding-left: 1.75rem;
  position: relative;
  line-height: 1.45;
}

.compare-col--us {
  background: var(--primary-soft);
  border: 2px solid var(--primary);
}

.compare-col--us h3 {
  color: var(--primary-dark);
}

.compare-col--us li::before {
  content: "\2713";
  position: absolute;
  left: 0;
  top: 0;
  font-weight: 700;
  color: var(--primary-dark);
}

.compare-col--other {
  background: var(--surface-alt);
  border: 2px solid var(--border-strong);
}

.compare-col--other h3 {
  color: var(--muted);
}

.compare-col--other li {
  color: var(--muted);
}

.compare-col--other li::before {
  content: "\2715";
  position: absolute;
  left: 0;
  top: 0;
  font-weight: 700;
  color: var(--muted);
}

/* Below ~640px (site's standard mobile breakpoint) the two 300px-basis
   columns no longer fit side by side and flex-wrap already stacks them
   -- this just tightens padding so they don't feel oversized on a
   narrow phone screen. */
@media (max-width: 640px) {
  .compare-col {
    flex-basis: 100%;
    padding: 1.25rem 1.35rem;
  }
}

/* The manual-entry TOTP setup key (see admin_2fa_setup.php) -- a
   monospace, letter-spaced, boxed display so a string of 32 random
   characters is actually legible/typeable rather than running together
   as one dense blob. */
.totp-key-display {
  font-family: monospace;
  font-size: 1.1rem;
  letter-spacing: 0.05em;
  padding: 0.9rem 1rem;
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-sm);
  background: var(--surface-alt);
  word-break: break-all;
}
