* {
  box-sizing: border-box;
}

/* Native cross-document View Transitions — every link on this site is a plain <a href>
   to a real, separate static page (no client-side router, no fetch-and-swap SPA
   navigation), so this changes nothing about what gets served or crawled: each URL is
   still its own full HTTP request/response with its own complete HTML, <title>,
   canonical tag and JSON-LD, exactly as before. The transition itself is a browser
   compositor effect that runs *after* the destination page has already loaded and
   painted normally — it has no bearing on what Googlebot/AI crawlers fetch (they don't
   run this CSS, and wouldn't see anything different if they did) or on Core Web Vitals,
   since nothing here delays first paint/LCP of the new page. Unsupported browsers
   (anything but current Chromium as of writing) just navigate instantly, same as
   always — this is pure progressive enhancement, not something any page depends on. */
@view-transition {
  navigation: auto;
}

/* Keeps the (harmless) mechanism enabled rather than disabling view-transitions
   outright, but drops the actual cross-fade to instant — same "still correct, just not
   animated" treatment motion preference gets elsewhere on the site (see site.js's own
   prefersReducedMotion). */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

/* Long-form policy pages (cookies, privacy — see templates/legal.njk) — plain markdown
   body content rendered straight into <main>, not the card/section marketing system
   the rest of the site uses, so it needs its own basic prose styling instead of
   inheriting section-specific rules. */
.legal-content {
  max-width: 760px;
  margin: 0 auto;
  padding: 4rem 2rem 6rem;
}

.legal-content h1 {
  margin: 0 0 0.5rem;
}

.legal-content h2 {
  margin: 2.5rem 0 1rem;
}

.legal-content p,
.legal-content ul,
.legal-content ol {
  margin: 0 0 1rem;
  line-height: 1.7;
}

.legal-content ul,
.legal-content ol {
  padding-left: 1.5rem;
}

.legal-content table {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 1.5rem;
  font-size: 0.9rem;
}

.legal-content th,
.legal-content td {
  text-align: left;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--color-border);
  vertical-align: top;
}

.legal-content th {
  background: var(--color-bg-alt);
}

:root {
  /* Fallback for before site.js measures the map image; site.js overwrites this with
     max(200vh, the map image's actual rendered height) so the backdrop/fade always
     covers the whole image instead of clipping it when it renders taller than 2 screen
     heights (e.g. very wide/short viewports). */
  --backdrop-height: 200vh;
}

/* iOS Safari/Chrome (same WebKit engine) paint the rubber-band overscroll area — pulling
   past the top, or scrolling past a short page's bottom — in the root element's own
   background, not body's. html has no background of its own below, so that area showed
   through as plain white regardless of theme. Matching it to --color-bg (the tone body's
   own gradient already fades to) fixes that — safe to add now (an earlier attempt at this
   regressed the backdrop photo entirely) only because `isolation: isolate` below gives
   body its own stacking context first, so .page-backdrop's z-index: -1 stays scoped
   inside body instead of comparing against this new html background as a root-level
   sibling and losing. */
html {
  background: var(--color-bg);
}

body {
  position: relative;
  /* Establishes body's own stacking context (see the html rule above) without any of the
     side effects a manual z-index would have. */
  isolation: isolate;
  margin: 0;
  font-family: var(--font-body);
  color: var(--color-text);
  line-height: 1.5;
  /* Backdrop gradient lives on the page itself (not the hero box) so it can fade out
     over --backdrop-height regardless of how tall the hero section is. The hero section
     sits on top of this with a transparent background so it shows through, and so does
     .page-backdrop's map photo below. */
  background: linear-gradient(180deg, var(--color-backdrop-top) 0vh, var(--color-backdrop-mid) 80vh, var(--color-bg) var(--backdrop-height));
}

/* Page-level backdrop layer, spanning --backdrop-height (matching the gradient above),
   sitting behind all real content (z-index: -1) but above body's own background
   gradient. */
.page-backdrop {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: var(--backdrop-height);
  overflow: hidden;
  z-index: -1;
  pointer-events: none;
}

/* Aerial map image — like the ground seen from a camera looking straight down. Full
   viewport width at its native aspect ratio (not stretched to cover the full backdrop
   height, which would zoom in too far to stay recognisable), anchored to the top,
   fading to transparent via a mask so it blends into the backdrop gradient rather than
   cutting off abruptly. */
.page-backdrop__map-wrap {
  position: absolute;
  /* Not 0 — backgroundImageBlur (site.css's .page-backdrop__map filter, below) has
     nothing above the image's own top edge to blur into, and browsers sample that
     missing region as black/transparent rather than extending the real edge pixel,
     which shows up as a thin dark seam right across the top of the page. Shifting the
     whole image up by more than any blur radius actually in use moves that same seam
     (an artifact of wherever this box's own top edge lands, not of the image's pixel
     content) up into the negative-y region .page-backdrop's own overflow: hidden
     already clips — the image itself just appears a few dozen px "further up" than
     before, imperceptible for a decorative backdrop photo. */
  top: -48px;
  /* Below 1200px viewport width, the image would otherwise keep shrinking with it
     (width: 100%) until it's too small to read. min-width holds it at 1200px and
     left/margin-left centre the overflow instead of letting it hang off one edge —
     using margin-left rather than a transform for the centering, since JS (site.js)
     already drives this element's `transform` for scroll parallax and would otherwise
     clobber a transform-based centering on every scroll update. Both sides of the
     centering must stay in sync with the actual rendered width, hence the matching
     max(100%, 1200px) in each.

     The +96px on every one of those three numbers is the same fix as the -48px top
     offset above, just left/right instead of up/down — at ANY viewport width the box
     needs to be wider than what's actually visible, or blur has no real pixel data to
     sample past its own left/right edge, and this same site got that exact seam down
     both sides (worse in light mode, where the "missing" edge sample reads as a
     visible bright/white fringe instead of a dark one). Below 1200px, min-width's own
     overflow already covered this; at/above it (width: 100% exactly matched the
     viewport, zero overflow) it didn't — 96px total (48px each side, matching the
     vertical fix) now applies unconditionally, in both branches, so the seam has
     nowhere left to appear regardless of viewport width. */
  left: 50%;
  width: calc(100% + 96px);
  min-width: calc(1200px + 96px);
  margin-left: calc(max(100% + 96px, 1200px + 96px) / -2);
  will-change: transform;
}

.page-backdrop__map {
  display: block;
  width: 100%;
  height: auto;
  /* Fade starts at a fixed 50vh and ends by 100vh at the latest — both relative to the
     window height, not a percentage of the image's own (much taller) box, so it lines
     up with the viewport regardless of the photo's aspect ratio or how tall
     --backdrop-height ends up being. min(100vh, 100%) caps the end point at the image's
     own bottom edge for any photo shorter than 100vh, so the fade never tries to extend
     past the image itself.

     --mask-scroll-shift (0px until site.js's scroll handler starts updating it) pulls
     both stops upward as the page scrolls. The image itself already drifts slower than
     the page via its own parallax transform (data-parallax on .page-backdrop__map-wrap),
     which on its own leaves the fade lagging behind the hero text above it — the text
     scrolls at the normal 1x rate while the fade (baked into the image) only moves at
     the image's much slower rate, so the boundary drifts out from under the text almost
     as soon as you start scrolling. This shift is a second, independent, closer-to-1x
     (but still "slightly slower", see MASK_SCROLL_FACTOR in site.js) adjustment on top
     of that, keeping the fade under the text for longer without changing the image's
     own parallax speed. */
  -webkit-mask-image: linear-gradient(to bottom, black 0%, black calc(50vh - var(--mask-scroll-shift, 0px)), transparent min(calc(100vh - var(--mask-scroll-shift, 0px)), 100%));
  mask-image: linear-gradient(to bottom, black 0%, black calc(50vh - var(--mask-scroll-shift, 0px)), transparent min(calc(100vh - var(--mask-scroll-shift, 0px)), 100%));
  /* --backdrop-darkness is set inline per-page from front matter's backgroundImageDarkness
     (0-100). Missing/0 (the var's own default) leaves brightness at 1, i.e. unchanged.
     --backdrop-blur (px) is its sibling, from backgroundImageBlur — a soft focal blur
     for a premium, ambient-photo look rather than a sharp backdrop competing with
     foreground content. Safe at any value here: the wrap above is already wider than
     the viewport below 1200px (min-width: 1200px, centred) so a blur never reveals a
     hard left/right cutoff, the bottom edge already fades via the mask above, and the
     top edge sits behind the site header — the one truly exposed edge, at large
     viewports where the wrap is exactly viewport-width, is the same full-bleed-photo
     blur treatment used everywhere from Spotify to Apple, not a defect to engineer
     around. */
  filter: brightness(calc(1 - (var(--backdrop-darkness, 0) / 100))) blur(var(--backdrop-blur, 0px));
}

h1, h2, h3 {
  font-family: var(--font-heading);
  line-height: 1.2;
}

/* A real \n in a section's `subheading` front matter (e.g. "First line.\nSecond
   line." in a double-quoted YAML string) renders as an actual line break — no `| safe`
   filter/HTML needed in the template, so content authors don't need to think about
   escaping. Only breaks where a literal newline is actually present; other whitespace
   still collapses normally. */
.section__subheading {
  white-space: pre-line;
}

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

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

.btn {
  display: inline-block;
  padding: 0.75rem 1.5rem;
  border-radius: var(--radius-pill);
  text-decoration: none;
  font-weight: 600;
}

.btn--primary {
  background: var(--color-primary);
  color: var(--color-on-primary);
  transition: background-color 0.25s ease;
}

.btn--primary:hover {
  background: var(--color-primary-dark);
}

.site-header__actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  /* Explicit column 3, the 1fr track — justify-self pushes this to the far right edge
     of it instead of stretching to fill it. */
  grid-column: 3;
  justify-self: end;
}

/* Menu-bar buttons read a bit large next to the nav links — sized down from the
   default .btn without touching buttons used elsewhere on the page (hero CTAs,
   pricing, etc). */
.site-header__actions .btn {
  display: inline-flex;
  align-items: center;
  height: var(--header-control-height);
  padding: 0 1.1rem;
  font-size: 0.9rem;
  white-space: nowrap;
}

/* Header — two independent bars, not one that changes state:
   - .site-header: tall (double-size logo), normal document flow, always transparent.
     Scrolls away with the page like any other content — never fixed.
   - .site-header-sticky: compact (normal-size logo), always position: fixed and
     always solid, hidden until .site-header has fully scrolled past the top of the
     viewport (site.js compares scrollY to .site-header's measured height), then
     "drops back in" and stays pinned from there on.
   Both share the same grid (see the shared rule below): logo and nav are auto-sized
   and sit tight together on the left, actions take the remaining 1fr and are pushed
   to the right edge. */
:root {
  --header-height: 68px;
  --header-height-tall: 132px;
  /* Shared by every header pill/button (nav links, dropdown toggles, Get Mapzest,
     theme toggle, hamburger) so they all line up at the same height regardless of
     their own font-size/padding. */
  --header-control-height: 38px;
}

.site-header,
.site-header-sticky {
  display: grid;
  grid-template-columns: auto auto 1fr;
  align-items: center;
  gap: 1.5rem;
  left: 0;
  right: 0;
  z-index: 100;
}

.site-header {
  position: relative;
  padding: 1.5rem 2rem;
  min-height: var(--header-height-tall);
  background: transparent;
  /* Tighter than the shared 1.5rem — only for the tall, transparent (no solid
     background) header; .site-header-sticky keeps the wider gap. */
  gap: 0.5rem;
}

.site-header-sticky {
  position: fixed;
  top: 0;
  padding: 0.6rem 2rem;
  min-height: var(--header-height);
  /* Frosted glass, not flat opaque — theme-aware, 0.8 opacity (less see-through than
     the small header pills' own glass, since this is a full-width bar with page
     content scrolling behind it), plus a blur so that content reads as softened
     rather than sharply visible through it. */
  background: var(--color-header-sticky-bg);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--color-border);
  transform: translateY(-100%);
  transition: transform 0.35s ease;
}

.site-header-sticky.is-visible {
  transform: translateY(0);
}

.site-header__logo {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  text-decoration: none;
  /* Explicit column (not auto-placement) so this stays column 1 even when .site-nav
     is display: none under .is-nav-collapsed and would otherwise shift later items
     left into its slot. */
  grid-column: 1;
  justify-self: start;
}

.site-header__logo img {
  height: 40px;
  width: 40px;
}

/* Double size in the tall header specifically (not the sticky bar, and not the
   mobile-menu header, which both keep the normal 40px above). */
.site-header .site-header__logo img {
  height: 80px;
  width: 80px;
}

.site-header__wordmark {
  font-family: var(--font-heading);
  font-size: 1.25rem;
  font-weight: 700;
  white-space: nowrap;
  color: var(--color-primary);
  /* Centering a 20px Poppins run and a 14.4px Inter run each within their own flex
     row (align-items: center) centers their *line boxes*, not their baselines —
     different typefaces/sizes have different ascent-descent ratios, so the two still
     land a couple of px apart. A measured nudge, not a layout-wide align-items:
     baseline switch (which would also touch the icon-only theme/hamburger buttons
     sharing this same grid row, with no text baseline of their own to align against). */
  position: relative;
  top: -1.4px;
}

/* The tall header has no wordmark markup at all (logo mark only) — a text colour
   that's guaranteed legible over an arbitrary backdrop photo isn't guaranteed, and
   unlike the old single-header design this one never gains a solid background to
   make that safe. The sticky bar is always solid, so its wordmark can safely track
   the theme (near-black in light mode, near-white in dark) instead of staying the
   fixed teal default above. */
.site-header-sticky .site-header__wordmark {
  color: var(--color-text);
}

.site-nav {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  grid-column: 2;
  /* Without this, a grid item's automatic minimum size defaults to its content's
     min-content size — for a nowrap flex row of pills, that's the same as its full
     max-content width, so the track couldn't shrink at all under space pressure, and
     the .is-nav-collapsed measurement above (which compares this header's actual
     current width against its own natural/expanded width) would never see a
     "doesn't fit" gap to react to in the first place. Note this doesn't stop the
     pills themselves from overflowing this row's own box once truly squeezed — they
     still don't wrap or shrink (white-space: nowrap) — it only lets the track size
     down; .is-nav-collapsed is what actually prevents that overflow from ever
     becoming visible, by switching to the hamburger before it happens. */
  min-width: 0;
}

/* Pill background on the nav items themselves — needed in the tall header, which has
   no background of its own and sits over the hero photo, so they stay legible over
   whatever's behind them. Not needed in the sticky bar, which is always solid already
   (see the override below) — redundant contrast at that point. */
.site-nav__link,
.site-nav__dropdown-toggle {
  display: inline-flex;
  align-items: center;
  height: var(--header-control-height);
  padding: 0 0.85rem;
  font-size: 0.9rem;
  white-space: nowrap;
  border-radius: var(--radius-pill);
  background: var(--color-glass-bg);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  transition: background-color 0.35s ease, backdrop-filter 0.35s ease;
}

.site-header-sticky .site-nav__link,
.site-header-sticky .site-nav__dropdown-toggle {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.site-nav__link {
  text-decoration: none;
  color: var(--color-text);
  font-weight: 500;
}

.site-nav__dropdown {
  position: relative;
}

.site-nav__dropdown-toggle {
  border: none;
  /* font-family only, not the `font` shorthand — a <button>'s default UA font-family
     is what actually needs overriding here, but the shorthand also resets font-size
     (and line-height/weight) back to inherited, clobbering the 0.9rem set above and
     leaving this button's text visibly larger than the nav links/CTA button beside
     it, with a baseline that no longer lines up with them. */
  font-family: inherit;
  /* <button> defaults to its own UA line-height (usually "normal"), not the page's
     inherited 1.5 — a mismatch too small to see as a size difference, but enough to
     shift this button's text a fraction of a pixel off the nav links/CTA button's
     baseline either side of it. */
  line-height: inherit;
  font-weight: 500;
  color: var(--color-text);
  cursor: pointer;
}

.site-nav__dropdown-menu {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  /* Breathing room between the toggle button and the panel — flush (margin-top: 0)
     read as too tight. */
  margin-top: 0.5rem;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 0.5rem 0;
  min-width: 220px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
  z-index: 10;
}

.site-nav__dropdown-menu a {
  display: block;
  padding: 0.5rem 1rem;
  text-decoration: none;
  color: var(--color-text);
}

.site-nav__dropdown-menu a:hover {
  background: var(--color-bg-alt);
}

/* Marks an item as a different kind of link from the rest of the dropdown (e.g. Pricing
   sitting among business categories, or Contact Us among destinations/organisations) —
   coloured with the theme-aware --color-primary rather than a fixed accent colour, and
   set apart with a divider since it's always the last item in these lists. */
.site-nav__dropdown-menu a.site-nav__dropdown-menu-item--accent {
  margin-top: 0.5rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--color-border);
  color: var(--color-primary);
  font-weight: 600;
}

.site-nav__dropdown.is-open .site-nav__dropdown-menu {
  display: block;
}

/* "Get Mapzest" — a .site-nav__dropdown-toggle (for the shared open/close JS) that
   still needs to read as the site's primary CTA button, not the plain glass-pill look
   every other dropdown toggle above uses. Restates the .btn--primary look explicitly
   here (rather than relying on class order) since .site-nav__dropdown-toggle's own
   rules above are both later in this file and equal specificity, so they'd otherwise
   win over .btn--primary for every property the two share. Covers the sticky header's
   transparent-background override the same way, for the same reason. */
.site-header__cta.site-nav__dropdown-toggle,
.site-header-sticky .site-header__cta.site-nav__dropdown-toggle {
  height: auto;
  padding: 0.75rem 1.5rem;
  background: var(--color-primary);
  color: var(--color-on-primary);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  font-weight: 600;
}

.site-header__cta.site-nav__dropdown-toggle:hover {
  background: var(--color-primary-dark);
}

/* Right-aligned, not the main nav dropdowns' left alignment — this sits at the
   header's right edge, where a left-anchored menu would overflow past it. */
.site-header__cta-dropdown .site-nav__dropdown-menu {
  left: auto;
  right: 0;
}

/* Dark mode toggle and the share button (site.js/macros/share-button.njk) share this
   same translucent pill / fade-on-scroll treatment — the theme toggle is hidden by
   .is-nav-collapsed (it has its own copy inside .mobile-menu instead), the share
   button never is (see that macro's own comment on why). */
.site-header__theme-toggle,
.site-header__share-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--header-control-height);
  height: var(--header-control-height);
  flex-shrink: 0;
  border: none;
  border-radius: var(--radius-pill);
  background: var(--color-glass-bg);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: var(--color-text);
  cursor: pointer;
  transition: background-color 0.35s ease, backdrop-filter 0.35s ease;
}

.site-header-sticky .site-header__theme-toggle,
.site-header-sticky .site-header__share-toggle {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.theme-icon,
.share-icon {
  width: 20px;
  height: 20px;
}

/* .is-copied (site.js, the clipboard-fallback branch of the share button's click
   handler — real navigator.share() browsers never set this) — a small bubble
   confirming the link was copied, since the button otherwise gives sighted visitors
   no feedback at all (the aria-label swap alongside this is the equivalent
   announcement for a screen reader). position: relative here, not further up with
   the rest of the button's layout rules, since it's only ever needed for this one
   state. */
.site-header__share-toggle {
  position: relative;
}

.site-header__share-toggle.is-copied::after {
  content: 'Link copied';
  position: absolute;
  top: calc(100% + 0.5rem);
  right: 0;
  padding: 0.4rem 0.75rem;
  border-radius: var(--radius-pill);
  background: var(--color-text);
  color: var(--color-bg);
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
  box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
}

/* Shows the icon for the mode a click would switch *to*, not the mode you're
   currently in — light mode shows the moon (tap to go dark), dark mode shows the sun
   (tap to go light). */
.theme-icon--sun {
  display: none;
}

[data-theme='dark'] .theme-icon--sun {
  display: block;
}

[data-theme='dark'] .theme-icon--moon {
  display: none;
}

/* Mobile nav toggle — hidden by default, shown only once the header carries
   .is-nav-collapsed (see its own rule further down, and site.js's
   setUpHeaderNavCollapse) where the full nav would otherwise collide with the
   actions column instead of fitting beside it. */
.site-header__menu-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: var(--header-control-height);
  height: var(--header-control-height);
  flex-shrink: 0;
  border: none;
  border-radius: var(--radius-pill);
  background: var(--color-glass-bg);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  cursor: pointer;
  transition: background-color 0.35s ease, backdrop-filter 0.35s ease;
}

.site-header-sticky .site-header__menu-toggle {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.site-header__menu-icon,
.site-header__menu-icon::before,
.site-header__menu-icon::after {
  display: block;
  width: 20px;
  height: 2px;
  border-radius: 2px;
  background: var(--color-text);
  transition: transform 0.25s ease, opacity 0.25s ease, background-color 0.25s ease;
}

.site-header__menu-icon {
  position: relative;
}

.site-header__menu-icon::before,
.site-header__menu-icon::after {
  content: '';
  position: absolute;
  left: 0;
}

.site-header__menu-icon::before {
  top: -6px;
}

.site-header__menu-icon::after {
  top: 6px;
}

/* Hamburger morphs into an X while the overlay is open. */
.site-header__menu-toggle[aria-expanded='true'] .site-header__menu-icon {
  background: transparent;
}

.site-header__menu-toggle[aria-expanded='true'] .site-header__menu-icon::before {
  top: 0;
  transform: rotate(45deg);
}

.site-header__menu-toggle[aria-expanded='true'] .site-header__menu-icon::after {
  top: 0;
  transform: rotate(-45deg);
}

/* Full-screen overlay menu — left-aligned hierarchical tree (plain links + two
   expandable groups mirroring the desktop dropdowns). Hidden via opacity/visibility
   (not display) so the open/close transition can animate. */
.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  flex-direction: column;
  /* Solid and theme-aware, not glass — unlike the header pills/hero scrim, this covers
     the whole screen with nothing else visible behind it while open, so it's a normal
     page surface that should follow the theme like any other. */
  background: var(--color-bg);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-12px);
  transition: opacity 0.3s ease, transform 0.3s ease, visibility 0s linear 0.3s;
  overflow-y: auto;
}

.mobile-menu.is-open {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.mobile-menu__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 2rem;
  min-height: var(--header-height);
  border-bottom: 1px solid var(--color-border);
}

.mobile-menu__header-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.mobile-menu__close {
  width: var(--header-control-height);
  height: var(--header-control-height);
  border: none;
  border-radius: var(--radius-pill);
  background: var(--color-bg-alt);
  color: var(--color-text);
  font-size: 1.75rem;
  line-height: 1;
  cursor: pointer;
}

/* The theme toggle and share button inside .mobile-menu aren't translucent glass
   like the header-strip ones — they sit on the menu's own solid background, not a
   photo, so they use the same flat treatment as .mobile-menu__close beside them
   instead. */
.mobile-menu__header-actions .site-header__theme-toggle,
.mobile-menu__header-actions .site-header__share-toggle {
  background: var(--color-bg-alt);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.mobile-menu__nav {
  display: flex;
  flex-direction: column;
  padding: 1rem 2rem 3rem;
  text-align: left;
}

.mobile-menu__link {
  display: block;
  padding: 1.1rem 0;
  font-family: var(--font-heading);
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--color-text);
  text-decoration: none;
  border-bottom: 1px solid var(--color-border);
}

.mobile-menu__group {
  border-bottom: 1px solid var(--color-border);
}

.mobile-menu__group-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 1.1rem 0;
  background: none;
  border: none;
  font-family: var(--font-heading);
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--color-text);
  text-align: left;
  cursor: pointer;
}

.mobile-menu__chevron {
  width: 9px;
  height: 9px;
  flex-shrink: 0;
  border-right: 2px solid var(--color-text-muted);
  border-bottom: 2px solid var(--color-text-muted);
  transform: rotate(45deg);
  transition: transform 0.25s ease;
}

.mobile-menu__group-toggle[aria-expanded='true'] .mobile-menu__chevron {
  transform: rotate(-135deg);
}

/* grid-template-rows 0fr -> 1fr is a CSS-only smooth height transition that doesn't
   need a fixed pixel height — .mobile-menu__submenu-inner's overflow: hidden is what
   actually clips it mid-transition. */
.mobile-menu__submenu {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.25s ease;
}

.mobile-menu__group.is-open .mobile-menu__submenu {
  grid-template-rows: 1fr;
}

.mobile-menu__submenu-inner {
  overflow: hidden;
  min-height: 0;
}

.mobile-menu__sublink {
  display: block;
  padding: 0.85rem 0 0.85rem 1.25rem;
  margin-left: 0.25rem;
  border-left: 2px solid var(--color-border);
  color: var(--color-text-muted);
  text-decoration: none;
  font-size: 1.05rem;
}

/* Marks an item as a different kind of link from the rest of the submenu (e.g. Pricing
   sitting among business categories, or Contact Us among destinations/organisations) —
   same theme-aware --color-primary treatment as the desktop dropdown's equivalent. */
.mobile-menu__sublink--accent {
  border-left-color: var(--color-primary);
  color: var(--color-primary);
  font-weight: 600;
}

/* Sections */
.section {
  padding: 4rem 2rem;
  max-width: 1100px;
  margin: 0 auto;
}

.section--hero {
  position: relative;
  display: flex;
  /* Bottom-left, not centred — the heading sits right on the backdrop photo now,
     anchored to the bottom-left corner of the screen. .hero-scroll-btn (bottom-right)
     used to be a second flex child here (positioned via justify-content:
     space-between), but that made it compete with .hero-content for the row's width
     — .hero-content's flex: 1 could only ever fill what was left after the button's
     own footprint. It's position: absolute now, nested inside .hero-content instead
     (see .hero-scroll-btn's own comment), so .hero-content can use the section's
     full width uncontested. */
  align-items: flex-end;
  text-align: left;
  /* .site-header sits above this in normal document flow, taking up its own real
     space (--header-height-tall) before the hero starts. Subtracting that once means
     the hero's box runs from right below the header down to exactly the bottom edge
     of the first screenful — so flex-end alignment lands the heading at the true
     bottom of the screen, not the bottom of some shorter/taller box.
     100vh on mobile Safari/Chrome-iOS (WebKit) is sized against the browser's largest
     possible viewport — as if the collapsible address bar were already gone — not what's
     actually visible on load. With content bottom-anchored via align-items: flex-end,
     that oversized box pushed the subheading/avatars below the real visible viewport.
     100svh (not 100dvh) fixes that the same way, but without dvh's own side effect:
     dvh tracks the *current* viewport continuously, so it changes live, mid-scroll, as
     the address/nav bar chrome animates away — exactly the moment this bottom-anchored
     content is moving too, so the two together read as a glitchy jump rather than a
     smooth scroll. svh is the *smallest* possible viewport (chrome fully shown) and,
     unlike dvh, never changes after load — it's a static value picked once, so this
     box's height (and the fold) can't shift under the visitor mid-scroll. It's also
     never larger than the real visible area regardless of chrome state, so the
     "pushed below the fold" problem dvh was originally added for stays fixed too.
     Browsers without svh support just ignore that line (an unsupported value voids
     the whole declaration) and keep the 100vh fallback above. */
  min-height: calc(100vh - var(--header-height-tall));
  min-height: calc(100svh - var(--header-height-tall));
  /* Transparent — the backdrop gradient lives on body/.page-backdrop so it can fade out
     over 1.5 screen heights independent of the hero's own height (see body rule above). */
  background: transparent;
  max-width: none;
  border-radius: 0;
  padding: 2rem 2.5rem 3rem;
}

/* The desktop paddings above (and on .legal-content, .site-footer, the cta banner/split
   bars, and .glass-panel below) were all fixed rem values with no narrower-viewport
   reduction. On a ~390px-wide phone that reads as excessive on its own, and compounds
   badly wherever a .glass-panel sits inside a .section — two nested 2rem paddings eating
   a third of the screen width before any content starts (e.g. the contact/download/404
   pages). Scaled down together here rather than per-selector, so every page's edge
   padding shrinks by roughly the same proportion instead of just one page looking fixed. */
@media (max-width: 640px) {
  .section {
    padding: 2.5rem 1.25rem;
  }

  .section--hero {
    padding: 1.5rem 1.25rem 2rem;
  }

  .section--cta-banner,
  .section--cta-split {
    padding: 2rem 1.25rem;
  }

  .legal-content {
    padding: 2.5rem 1.25rem 4rem;
  }

  .site-footer {
    padding: 2rem 1.25rem 1.5rem;
  }

  .glass-panel {
    padding: 1.5rem;
  }

  /* The header/menu strips are a separate rule family from .section (grid/flex bars,
     not page content), so they needed their own reduction here too — same 2rem gutter,
     same crowded-on-a-narrow-phone problem, on whichever of these is actually visible:
     .site-header (page top before scrolling), .site-header-sticky (after), and the
     hamburger's full-screen .mobile-menu once either header is collapsed. */
  .site-header,
  .site-header-sticky {
    padding-left: 1.25rem;
    padding-right: 1.25rem;
  }

  .mobile-menu__header {
    padding-left: 1.25rem;
    padding-right: 1.25rem;
  }

  .mobile-menu__nav {
    padding-left: 1.25rem;
    padding-right: 1.25rem;
  }

  /* .card-grid__card's normal 300px flex-basis is wider than a phone screen minus
     padding, so flex-wrap was dropping it to a single column there. Forcing exactly
     2-per-row instead (min-width: 0 overrides flex's default min-width: auto, which
     would otherwise refuse to shrink a card below its content's own intrinsic width) —
     any content that doesn't fit is fine to run tight/overlap a neighbour, since tapping
     a card already brings it visually forward (.card-grid__card--clickable:hover). */
  .card-grid__card {
    flex: 0 1 calc(50% - 1rem);
    min-width: 0;
  }
}

/* Wraps the heading + (optional) avatar row, stacked and left-aligned against
   .section--hero's own bottom-left anchor (align-items: flex-end there). flex: 1
   makes this the containing block .hero-scroll-btn's right: 0/bottom: 0 actually
   need — without it, this box (position: relative, for that same reason) only ever
   shrink-wraps to its own content's width (h1's own max-width, well short of the
   section's full width), which pulls the button's "right edge" inward with it
   instead of leaving it flush against the section's own true right edge. The
   heading/avatar row content itself stays left-aligned regardless (align-items:
   flex-start), so this extra width is otherwise invisible — it's here entirely for
   the button anchored inside it. */
.hero-content {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1.5rem;
  flex: 1;
  min-width: 0;
  /* Entrance: hidden (this is also the animation's own "from" state, so there's no
     unanimated flash while animation-delay holds), then fades/lifts in with a
     slight upward motion on load. The backdrop's own darkness/blur (.page-backdrop__map)
     has no matching entrance any more — baked in from first paint, no transition —
     so this is the only reveal left on the page. */
  opacity: 0;
  transform: translateY(20px);
  animation: hero-reveal 0.55s ease 0.2s forwards;
}

@keyframes hero-reveal {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero-content {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

/* Groups h1 + subheading with their own tighter gap, separate from .hero-content's
   larger gap down to the avatar row below. */
.hero-heading {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  /* Same reasoning as .hero-content's own min-width: 0 (and .site-nav's, elsewhere
     in this file) — without it, this flex item's automatic minimum width defaults
     to its content's min-content size (effectively the longest single word in the
     heading/subheading at the current font-size), which on the narrowest real
     phones can be wider than the viewport itself, pushing text off the right edge
     instead of letting it shrink to the space actually available. */
  min-width: 0;
  /* overflow-wrap: anywhere, not break-word — inherited, so it covers the h1 and
     .hero-subheading below in one place. A long single word (e.g. "Destination",
     "Accommodation") in a heading can be wider than the whole column at h1's 3.5rem
     mobile floor on the narrowest real phones (~320px). break-word only breaks a
     word as a display-time last resort; per spec it's explicitly NOT counted when
     an ancestor flex item like this one computes its own min-content width, so
     min-width: 0 above still wouldn't have helped — the flex algorithm would still
     size this box to fit the whole unbroken word, overflowing past .hero-content's
     edge exactly as it did before this line was added. anywhere makes the broken-word
     width available to that min-content calculation too, which is what actually
     lets this box (and the ones above it in the chain) shrink to the space they're
     really offered. */
  overflow-wrap: anywhere;
}

.section--hero h1 {
  margin: 0;
  max-width: 44rem;
  /* 3.5rem floor (was 2.5rem) — below ~933px viewport width, 6vw alone undershoots
     that, so narrow/phone widths were flooring out at a comparatively small 40px,
     nowhere near as visually dominant as the 80px ceiling wide desktops get. Bumped
     the floor so mobile's heading still reads as "the biggest thing on the screen"
     the way desktop's does, rather than leaving a big gap of bare photo above it. */
  font-size: clamp(3.5rem, 6vw, 5rem);
  line-height: 1.05;
  /* Fixed white + a shadow (not the theme-varying --color-text), and — unlike
     .hero-subheading below — not theme-aware at all: always this same white-on-dark-
     shadow treatment, deliberately, regardless of light/dark mode. This sits directly
     on the backdrop photo with no scrim behind it, so it needs to stay legible
     against arbitrary photo content either way, not flip to a black/light-shadow
     look in light mode that a busy enough photo could still fail to contrast with. */
  color: #fff;
  text-shadow: 0 18px 44px rgba(0, 0, 0, 0.45), 0 1px 6px rgba(0, 0, 0, 0.35);
}

.hero-subheading {
  margin: 0;
  max-width: 38rem;
  font-size: clamp(1.05rem, 1.8vw, 1.25rem);
  line-height: 1.5;
  /* Theme-aware, unlike .section--hero h1 above — the subheading sits low enough
     (flex-end-aligned, at the bottom of the first screenful) that the backdrop photo
     has mostly faded out behind it via the mask, so --color-text generally tracks
     whichever backdrop is actually behind it in each theme. */
  display: inline-block;
  border-radius: var(--radius-card);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: var(--color-text);
}

/* Every avatar is real, server-rendered markup (see macros/sections.njk's hero macro) —
   site.js only shows/hides/reorders these same elements, it never builds one. Without
   JS, every deduped avatar shows here in normal flex flow (wrapping if there isn't room
   in one row) — a graceful, still fully real-content degrade. perspective on each item
   (not the row) gives its own rotateY flip real 3D foreshortening, centred on that one
   avatar rather than the whole row. Rounding/clipping lives on .hero-avatars__img, not
   this wrapper — .hero-avatars__item itself stays overflow: visible, on purpose, so the
   tooltip below can pop up above the circle without getting clipped by it. */
.hero-avatars {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  /* Reserves at least one row's height up front, matching .hero-avatars__item's own
     height — avoids a layout jump when site.js hides most avatars down to whatever
     fits one row. */
  min-height: 100px;
}

/* Same 100px as .hero-scroll-btn — both sit on .section--hero's own bottom edge (this
   row as .hero-content's last child, flex-end-aligned; the button via its own
   bottom: 0), so matching their size is what keeps those two bottom edges reading as
   one shared baseline rather than two unrelated ones that happen to be close. */
.hero-avatars__item {
  position: relative;
  width: 100px;
  height: 100px;
  flex-shrink: 0;
  perspective: 400px;
  transition: opacity 0.4s ease, transform 0.4s ease;
}

/* Hidden by default (no JS needed for this rule — it's just how the page looks before
   any script runs) so a slow-to-execute site.js doesn't leave a frame where every real,
   server-rendered avatar candidate is visible before it narrows them down to however
   many actually fit. site.js's layout() adds .is-ready to the container in the same
   synchronous pass where it also decides is-hidden for every avatar — so by the time
   .is-ready can possibly be painted, the is-hidden bookkeeping below is already done
   too, with no in-between state for the browser to ever show. True no-JS visitors
   (script genuinely never runs, so .is-ready never gets added) still see every avatar,
   via the <noscript> override in layout.njk's <head> — this rule alone would otherwise
   hide them forever. */
.hero-avatars:not(.is-ready) .hero-avatars__item {
  display: none;
}

.hero-avatars.is-ready .hero-avatars__item:not(.is-hidden) {
  display: block;
}

/* Start state for the entrance fade/scale-in — site.js adds this to freshly-shown
   avatars (initial layout, or a resize revealing a different count), then removes it a
   frame later so the browser registers this state first instead of jumping straight to
   the resting one (which would look like nothing transitioned at all). */
.hero-avatars__item.is-entering {
  opacity: 0;
  transform: scale(0.85);
}

/* site.js's own visibility control — how many avatars fit the current viewport, and
   which subset is currently rotated into view. Never applied server-side. */
.hero-avatars__item.is-hidden {
  display: none;
}

.hero-avatars__img {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: cover;
  background: #fff;
  transition: transform 0.2s ease;
}

/* The periodic flip, driven by site.js's flipSwap: outgoing avatar rotates to 90deg
   (edge-on, invisible) then gets hidden; incoming avatar is jumped instantly to -90deg
   (transition disabled for that jump via is-flip-instant) then rotates back to 0 once
   the class is removed, with the transition re-enabled — reads as a single flip
   revealing a different real avatar. */
.hero-avatars__item.is-flipping-out .hero-avatars__img {
  transform: rotateY(90deg);
}

.hero-avatars__item.is-flip-instant .hero-avatars__img {
  transition: none;
  transform: rotateY(-90deg);
}

/* Tooltip — data-tooltip (set/updated in site.js) rather than the native title attribute,
   which renders as a plain, unstyleable, browser-default box. ::after is the bubble;
   ::before is the small downward-pointing arrow connecting it to the avatar below. Both
   sit above the circle (bottom: 100% of .hero-avatars__item) and fade/nudge in on hover. */
.hero-avatars__item::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  background: var(--color-text);
  color: var(--color-bg);
  font-size: 0.8rem;
  font-weight: 600;
  white-space: nowrap;
  padding: 0.4rem 0.75rem;
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 5;
}

.hero-avatars__item::before {
  content: '';
  position: absolute;
  bottom: calc(100% + 4px);
  left: 50%;
  transform: translateX(-50%);
  border: 6px solid transparent;
  border-top-color: var(--color-text);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.2s ease;
  z-index: 5;
}

.hero-avatars__item:hover::after {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
}

.hero-avatars__item:hover::before {
  opacity: 1;
  visibility: visible;
}

/* Scroll-to-next-section button — bottom-right corner of the hero. position: absolute
   relative to .hero-content itself (not .section--hero) so it stays baseline-aligned
   with the last real child of .hero-content — the avatar row when present, the
   heading row otherwise — at any width, the same as when it was a normal flex child
   sharing .hero-content's own bottom edge. Positioning it against .section--hero
   directly (a fixed rem offset from the section's own padding edge) briefly replaced
   this: it matched on wide screens by coincidence, but drifted out of alignment with
   the avatars wherever .hero-content's height changed relative to that fixed offset,
   e.g. narrower widths where the logo/heading stack taller. right/bottom: 0 here is
   flush with .hero-content's own box, which already sits inside .section--hero's
   padding (.hero-content is a padded section's normal flex child), so no separate
   mobile-breakpoint override is needed the way the old section-relative version
   required. Click handler in site.js scrolls the next section into view. */
.hero-scroll-btn {
  position: absolute;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 100px;
  height: 100px;
  border: none;
  border-radius: 50%;
  /* Theme-aware glass (same token as the header nav pills) rather than a fixed
     rgba(255,255,255,...) — a fixed white-tinted glass with a white icon on top reads
     fine over a dark photo in dark mode, but disappears against a lighter backdrop in
     light mode. --color-glass-bg/--color-text already invert per theme everywhere else
     in the header, so reusing them here keeps this button visible in both. */
  background: var(--color-glass-bg);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: var(--color-text);
  cursor: pointer;
  transition: background-color 0.25s ease, transform 0.25s ease;
}

.hero-scroll-btn:hover {
  background: color-mix(in srgb, var(--color-glass-bg) 100%, var(--color-text) 15%);
  transform: translateY(4px);
}

.hero-scroll-btn svg {
  width: 28px;
  height: 28px;
}

/* Two-phone product mockup, floated on the right side of the hero — makes the hero
   read as two columns (text left, taking up more than half the width; product shot
   right). A child of .hero-content, not .section--hero — .hero-content is the actual
   text block (heading/subheading, plus avatars when present) and is bottom-anchored
   inside a much taller section (align-items: flex-end on .section--hero), so its own
   height varies a lot page to page: tall on the homepage (heading + subheading +
   avatar row), short on pages with hideAvatars or no avatar data (heading + subheading
   only). Centering against .hero-content's own height — not the whole section's —
   means this tracks that: it sits higher, alongside the avatar row, on pages that have
   one, and lower, roughly level with the (shorter) text block, on pages that don't,
   rather than floating in the section's vertical middle regardless of how much text
   actually shares that space. Being a child (not a sibling) also means DOM order alone
   (first child, so it paints first) keeps it beneath the heading/avatars/button that
   follow it, with no explicit z-index needed. It can still safely span outside
   .hero-content's own box in either direction — position: absolute isn't clipped by a
   non-overflow-hidden ancestor. pointer-events: none because this is decorative — most
   of the image's own bounding box is transparent padding around the angled phones, and
   without this, a click landing on that transparent padding (rather than a visible
   pixel) would be swallowed by the <img> instead of reaching whatever's underneath.
   Hidden below 1100px — see that breakpoint's own comment. */
.hero-phones {
  position: absolute;
  top: 45%;
  /* Centred within the right-hand "column" (roughly the 55%-100% span left free of
     .hero-content's own text), not pinned to the section's right edge — a right: Npx
     inset reads as glued to the corner on a very wide screen instead of sitting in the
     middle of its own half. left: 76% + translateX(-50%) centers it there regardless
     of viewport width — .hero-content is the same full section width as before (see
     its own comment on flex: 1), so this computes the same as when it was positioned
     against .section--hero directly. */
  left: 76%;
  transform: translate(-50%, -50%);
  width: clamp(280px, 32vw, 520px);
  pointer-events: none;
}

.hero-phones__img {
  width: 100%;
  height: auto;
  /* Subtle floating-in-air drift — animated on the img itself, not .hero-phones,
     because .hero-phones's own transform already does the -50%/-50% centering above;
     animating a second transform there would just replace that each frame instead of
     combining with it. A small, slow, ease-in-out bob (translateY only — no rotation,
     which reads as tilting/unstable rather than floating) reads as gentle ambient
     motion rather than something demanding attention. */
  animation: hero-phones-float 6s ease-in-out infinite;
}

@keyframes hero-phones-float {
  0%, 100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-14px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero-phones__img {
    animation: none;
  }
}

/* 1100px, not 900 — the heading's own wrap width is fixed (max-width: 44rem) but its
   font-size keeps shrinking with vw below that, so narrower viewports fit MORE of the
   heading onto each line, not less; around 950-1000px that produces a line long enough
   to run under this now-centred (not edge-pinned) image and clip behind it. Checked
   visually down to 1050px (clean) before landing here for a safety margin. */
@media (max-width: 1100px) {
  .hero-phones {
    display: none;
  }
}

.section--text-image {
  display: flex;
  align-items: center;
  gap: 3rem;
  flex-wrap: wrap;
}

.section--image-left {
  flex-direction: row-reverse;
}

.section--text-image .section__content,
.section--text-image .section__media {
  flex: 1;
  min-width: 280px;
}

.section--text-image .section__media {
  /* The <img> has no explicit width, so without this it hugs the left edge of its own
     column (default block alignment) rather than the outer edge of the section — meaning
     an image-right section leaves a big gap to the actual right edge of the screen while
     an image-left section sits flush against the actual left edge. Centering it in its
     column keeps the same visual balance regardless of which side it's on. */
  display: flex;
  justify-content: center;
}

.section--text-image .section__media img {
  border-radius: var(--radius-card);
  /* Subtle scroll parallax — site.js nudges these with a translateY based on how far
     each image is from the viewport's centre as you scroll past it. */
  will-change: transform;
  max-width: 100%;
}

/* Opt-in per-section (s.imageCircle) — aspect-ratio forces a square box regardless of the
   source photo's own proportions, object-fit crops to fill it, then border-radius clips it
   into a circle. Works at any width (including s.imageWidth), so it stays responsive rather
   than depending on a fixed pixel size. */
.section--text-image .section__media img.section__media-img--circle {
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 50%;
}

/* s.video's media-slot alternative to s.image (see macros/sections.njk's textImage
   macro) — same card treatment as the image case, sized to fill its own column
   (unlike the standalone .section-video__player, which is centred at a fixed
   fraction of the whole viewport — this one lives in a two-column layout instead). */
.section--text-image .section__media-video {
  width: 100%;
  height: auto;
  border-radius: var(--radius-card);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
}

.feature-list {
  list-style: none;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1.5rem;
}

.feature-list__item {
  display: flex;
  gap: 0.75rem;
}

.feature-list__icon {
  font-size: 1.5rem;
}

/* Checklist (macros/sections.njk's checklist()) — "everything delivered", styled as its
   own reusable component rather than a plain bulleted list: each item is a small card
   with a filled brand-colour tick badge, not just an inline glyph, so a page with a
   dozen items still reads as a deliberate, tidy grid instead of a wall of text. */
.checklist {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1rem;
}

.checklist__item {
  display: flex;
  align-items: flex-start;
  gap: 0.85rem;
  background: var(--color-bg-alt);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  padding: 1rem 1.15rem;
}

/* Fixed white, not --color-on-primary — this sits on a *solid* --color-primary fill
   (unlike e.g. .blog-post__category, which pairs --color-on-primary with the same
   fill for exactly that reason), and on the one dark-mode brand colour in this
   codebase where on-primary isn't white (tokens.css's own dark --color-on-primary is
   a dark teal, chosen for *text* sitting on a bright fill) the tick glyph itself
   still needs to read as white, not that dark colour. */
.checklist__tick {
  flex-shrink: 0;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  background: var(--color-primary);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}

.checklist__tick svg {
  width: 1.05rem;
  height: 1.05rem;
}

.checklist__text {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding-top: 0.15rem;
}

.checklist__label {
  font-weight: 600;
  color: var(--color-text);
  line-height: 1.3;
}

.checklist__detail {
  font-size: 0.85rem;
  color: var(--color-text-muted);
  line-height: 1.4;
}

/* Blog feature showcase (macros/sections.njk's blogFeature()) — a full-bleed photo
   card with the title overlaid on a fade-to-dark scrim, the same visual language
   .blog-post__hero-title used before it moved below the image (see that page's own
   history) — reused here specifically *because* it already reads as "this is a
   featured piece of content", which is exactly the showcase effect a "Case Study"
   block on a landing page wants, even though the blog post itself moved on from it. */
.section--blog-feature {
  text-align: center;
}

/* justify-items: center (not the blog hub grid's own start — see .blog-list) is
   deliberate here: this section's heading is centred as a landing-page promo block,
   not a practical listing, so a single result (very likely, with a category+tag combo
   this specific) reads as one deliberate, centred feature card rather than an
   off-centre leftover. */
.blog-feature-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  justify-items: center;
  gap: 1.75rem;
  margin-top: 2.5rem;
  text-align: left;
}

.blog-feature-card {
  position: relative;
  display: block;
  width: 100%;
  /* Caps how wide a lone/leftover card's own auto-fit track can stretch it — this is
     a portrait (3/4 aspect-ratio) card, so without this a single match filling a wide
     row would also grow absurdly *tall* (aspect-ratio scales with the stretched
     width), not just wide. */
  max-width: 360px;
  aspect-ratio: 3 / 4;
  border-radius: var(--radius-card);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.18);
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.blog-feature-card:hover {
  transform: translateY(-6px);
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
}

.blog-feature-card__image {
  position: absolute;
  inset: 0;
}

.blog-feature-card__image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Slow, deliberate zoom on hover — a much longer duration than the card's own
     lift/shadow transition above (0.3s), so the two read as two distinct, layered
     motions rather than everything moving at once. */
  transition: transform 0.6s ease;
}

.blog-feature-card:hover .blog-feature-card__image img {
  transform: scale(1.06);
}

/* Stops well short of full opacity/full height (0.85 peak, faded out by 75% up) —
   only needs to guarantee the title/excerpt text at the bottom stays legible over an
   arbitrary photo, not darken the whole card the way a blog post's own hero once did
   (that one had nothing else on the card competing for attention; this one's the photo
   itself, on a page full of other cards, so keeping most of it visible matters more). */
.blog-feature-card__overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 1.5rem;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.85) 0%, rgba(0, 0, 0, 0.15) 55%, rgba(0, 0, 0, 0) 75%);
}

.blog-feature-card__category {
  align-self: flex-start;
  background: var(--color-primary);
  color: var(--color-on-primary);
  border-radius: var(--radius-pill);
  padding: 0.3rem 0.9rem;
  font-size: 0.75rem;
  font-weight: 600;
  margin-bottom: 0.75rem;
}

.blog-feature-card__title {
  color: #fff;
  margin: 0 0 0.4rem;
  font-size: 1.25rem;
  line-height: 1.3;
}

.blog-feature-card__excerpt {
  color: rgba(255, 255, 255, 0.85);
  font-size: 0.9rem;
  line-height: 1.4;
  margin: 0;
  /* Clamped to 2 lines so every card's text block ends up roughly the same height
     regardless of how long its own excerpt is — matches .blog-card__excerpt's own
     reasoning, just a tighter clamp since this card is portrait, not landscape. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* max-width: none (like .section--hero) so the video below can be sized relative to
   the actual viewport width ("two thirds of the screen"), not the 1100px column every
   other section is capped to. Heading/subheading stay capped and centred themselves,
   rather than stretching edge-to-edge along with the section. */
.section--video {
  max-width: none;
  text-align: center;
}

.section--video h2,
.section--video .section__subheading {
  max-width: 700px;
  margin-left: auto;
  margin-right: auto;
}

.section-video {
  display: flex;
  justify-content: center;
  margin-top: 2rem;
}

.section-video__player {
  width: 66.666%;
  max-width: 900px;
  border-radius: var(--radius-card);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
}

@media (max-width: 700px) {
  .section-video__player {
    width: 100%;
  }
}

.section--cta-banner {
  text-align: center;
  background: var(--color-primary);
  color: var(--color-on-primary);
  max-width: none;
  border-radius: 0;
  padding: 3rem 2rem;
}

.section--cta-banner h2 {
  color: var(--color-on-primary);
}

.section--cta-banner .btn--primary {
  /* --color-on-primary is guaranteed (by Material 3's own contrast rules) to stand out
     against --color-primary, which is this section's own background — so the button
     always reads clearly regardless of what theme/seedColour the page is using, instead
     of a fixed brand yellow that no longer relates to a per-page palette. --color-primary
     as the text colour ties it back to the theme rather than reading as a plain neutral. */
  background: var(--color-on-primary);
  color: var(--color-primary);
  margin-top: 1rem;
}

/* Same centred layout as .section--cta-banner, but no background/colour override —
   it just inherits the base .section box and normal text/button colours, so it reads
   as a quieter nudge rather than a second bold banner. */
.section--cta-mini-banner {
  text-align: center;
}

.section--cta-mini-banner .btn--primary {
  margin-top: 1rem;
}

/* Same bold full-bleed treatment as .section--cta-banner, just two options side by
   side with a dividing line instead of one centred message. */
.section--cta-split {
  max-width: none;
  border-radius: 0;
  padding: 3rem 2rem;
  background: var(--color-primary);
  color: var(--color-on-primary);
}

.section--cta-split h2 {
  color: var(--color-on-primary);
}

.section--cta-split .btn--primary {
  background: var(--color-on-primary);
  color: var(--color-primary);
  margin-top: 1rem;
}

/* The base .btn--primary:hover switches to --color-primary-dark — right for the usual
   colour-background/white-text button, but this one is inverted (white-ish background,
   coloured text), so that hover would swap in a dark background sitting behind text
   that's still --color-primary, two close shades of the same hue with too little
   contrast between them. Darkening the existing on-primary background slightly toward
   primary instead keeps the text colour valid throughout the hover, not just at rest. */
.section--cta-split .btn--primary:hover {
  background: color-mix(in srgb, var(--color-on-primary) 85%, var(--color-primary));
}

/* CSS subgrid, not flex — a flex column with align-items: stretch keeps both options
   the same *overall* height, but each option's own content still stacks
   independently, so a longer heading in one column pushes that column's subheading
   (and button) down further than the other's, leaving both misaligned. subgrid gives
   each option two shared row tracks (sized by .cta-split, the shared parent): one for
   heading+subheading together (as a single block, so they stay snug against each
   other regardless of any stretch — see .cta-split__option-header) and one filler row
   for the button. Any extra height one option needs over the other collects in that
   second row, between the subheading and the button, rather than wedging into the
   middle of the header block. */
.cta-split {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: auto 1fr;
  column-gap: 3rem;
  max-width: 1100px;
  margin: 0 auto;
}

.cta-split__option {
  display: grid;
  grid-row: 1 / -1;
  grid-template-rows: subgrid;
  text-align: center;
  padding: 0 3rem;
}

.cta-split__option-header {
  grid-row: 1;
}

.cta-split__option .btn {
  grid-row: 2;
  /* Row 2 is 1fr (fills whatever's left after the header block) — margin-top: auto
     within it pins the button to the bottom of that space, and justify-self: center
     stops it stretching to the column's full width (a grid item's default). */
  margin-top: auto;
  justify-self: center;
}

/* --color-on-primary, not a fixed white — this section's background (--color-primary)
   varies per page's seedColour and isn't guaranteed to be dark, so a fixed white line
   at low opacity could end up nearly invisible (or just wrong-looking) against a light
   primary. --color-on-primary is guaranteed by Material 3's own contrast rules to read
   against --color-primary regardless of which it is, same reasoning as this section's
   h2/button colours just above. Drawn once down .cta-split's own centre rather than as
   a border on one option, since there's no longer a shared box between them to hang a
   border off (each option is its own subgrid). */
.cta-split::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 1px;
  background: color-mix(in srgb, var(--color-on-primary) 30%, transparent);
}

@media (max-width: 700px) {
  .cta-split {
    grid-template-columns: 1fr;
    grid-template-rows: none;
    row-gap: 2rem;
  }

  .cta-split::after {
    display: none;
  }

  .cta-split__option {
    grid-row: auto;
    grid-template-rows: none;
    padding: 0;
  }

  .cta-split__option:not(:last-child) {
    border-bottom: 1px solid color-mix(in srgb, var(--color-on-primary) 30%, transparent);
    padding-bottom: 2rem;
  }
}

.pricing-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 1.5rem;
  margin: 2rem 0;
}

/* Sits inside the section's own outer .glass-panel now (heading + whole grid, see the
   pricingTable macro) — a plain, solid-ish card of its own rather than a second nested
   glass-panel, since stacking two backdrop-filter blurs on top of each other reads as
   muddy rather than "more glass". */
.pricing-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  background: var(--color-bg-alt);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  padding: 1.5rem;
}

.pricing-card ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Flex column children don't collapse margins the way normal block siblings do, so the
   h3's and rate's own default browser margins were adding together instead of
   overlapping — a much bigger gap than either alone. */
.pricing-card h3 {
  margin: 0;
}

.pricing-card__rate {
  margin: 0.25rem 0 1rem;
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--color-primary);
}

.section--pricing-table h1,
.section--pricing-table h2,
.section--pricing-table .section__subheading {
  text-align: center;
}

.section--pricing-table .section__subheading {
  max-width: 46rem;
  margin: 0.75rem auto 0;
}

/* Fixed, not the theme-varying --color-text — same reasoning as .section--hero h1: this
   is the first section on the page, sitting directly over the fading backdrop photo
   with no glass panel behind it any more (that was removed so the four cards below
   could fit on one row), so it needs to stay legible against arbitrary photo content
   in both themes rather than a colour that only contrasts with one of them. Covers the
   subheading too, for the same reason — it sits just as directly on the photo. */
.section--pricing-table h1,
.section--pricing-table .section__subheading {
  color: #fff;
}

.pricing-table__note {
  margin: 1rem 0 0;
  text-align: center;
  color: var(--color-text-muted);
  font-size: 0.85rem;
}

.pricing-table__cta {
  text-align: center;
  margin-top: 1rem;
}

.pricing-table__cta-body {
  max-width: 46rem;
  margin: 0.75rem auto 0;
  color: var(--color-text-muted);
  font-size: 0.9rem;
}

.card-grid__header {
  max-width: 640px;
  margin: 0 auto 3rem;
  text-align: center;
}

.card-grid__header .btn {
  margin-top: 1rem;
}

/* Flexbox (not CSS Grid) is what makes the last, partial row centre itself — a plain
   grid with 3 columns would leave a 2-card row sitting flush left in columns 1-2
   instead of centred. flex-wrap + justify-content: center centres the items on *each*
   wrapped line individually, which is exactly the "5 items = 3 then 2 centred"
   behaviour asked for, with no extra markup/JS needed. */
.card-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 2rem;
  margin: 0 auto;
}

/* s.overlay variant (see macros/sections.njk's cardGrid macro) — a gradient wash
   behind each *visually wrapped row* of cards, not the grid as a whole. Flex-wrap
   has no CSS concept of "a row" as its own box once items wrap onto multiple lines
   (and how many cards share a row is responsive), so site.js measures the actual
   rendered rows and injects one .card-grid__row-bg per row, sized to match. */
.card-grid--overlay {
  position: relative;
  z-index: 0;
}

.card-grid__row-bg {
  position: absolute;
  z-index: -1;
  pointer-events: none;
  /* Same vertical, bookended colour gradient .live-listing-tile uses (top/bottom at
     --color-bg, bulging through a softened --color-bg-alt at 70%) — then a horizontal
     mask fades this layer alone to transparent at its own row's left/right edges,
     reaching full opacity a quarter of the way in from either side. Two separate
     mechanisms because a single linear-gradient can't vary colour in one direction
     and alpha in another. */
  background: linear-gradient(180deg, var(--color-bg) 0%, var(--color-bg) 30%, color-mix(in srgb, var(--color-bg-alt) 50%, var(--color-bg)) 70%, var(--color-bg) 100%);
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, black 25%, black 75%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0%, black 25%, black 75%, transparent 100%);
}

.card-grid--overlay .card-grid__card {
  background: transparent;
  border: none;
}

/* flex-basis (not width) + the container's default align-items: stretch is what gives
   every card in a row the same height regardless of how much optional content
   (footer/cta) any single one is missing — stretch sizes every item on a line to the
   line's tallest item. flex-direction: column + the body's flex: 1 below then pushes
   each card's own footer to its bottom edge instead of leaving a ragged gap above it. */
.card-grid__card {
  flex: 0 1 300px;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  overflow: hidden;
}

/* item.image — see macros/sections.njk's cardGrid macro and site.js's lightbox. A
   small lift, deeper shadow and the theme's own primary colour on the border read as
   "this opens something" before the visitor ever taps it. */
.card-grid__card--clickable {
  cursor: pointer;
  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}

.card-grid__card--clickable:hover {
  transform: translateY(-4px);
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.12);
  border-color: var(--color-primary);
}

.card-grid__image {
  height: 180px;
}

.card-grid__image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* s.overlay variant (see macros/sections.njk's cardGrid macro) — a taller panel with
   the image sized to fit the panel's height rather than cropped to fill it. Padding
   keeps the image clear of the panel's edges — this is "the final position" the
   entrance animation below eases into. No background of its own — the row-level
   .card-grid--overlay gradient (above) shows through instead. */
.card-grid__image--overlay {
  position: relative;
  height: 340px;
  padding: 1.5rem;
  box-sizing: border-box;
  overflow: hidden;
}

.card-grid__image--overlay img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* --overlay-y/--overlay-scale are set per-image in site.js for the entrance's start
     state (below and slightly smaller than rest, so it rises up into place); the
     0%/1 fallbacks are the rest ("final") position, so the image still renders
     correctly without JS. site.js adds .is-visible to this specific image's own
     container once *it* (not the grid as a whole) crosses the vertical centre of the
     viewport — transition-delay is staggered per image so cards that become visible
     together still reveal one after another rather than all at once. */
  transform: translateY(var(--overlay-y, 0%)) scale(var(--overlay-scale, 1));
  transition: transform 0.9s cubic-bezier(0.22, 1, 0.36, 1);
}

.card-grid__image--overlay.is-visible img {
  transform: translateY(0) scale(1);
}

.card-grid__body {
  flex: 1;
  padding: 1.5rem;
}

.card-grid__footer {
  padding: 0 1.5rem 1.5rem;
}

.card-grid__footer-text {
  color: var(--color-text-muted);
  font-size: 0.9rem;
}

.section--data-cards {
  text-align: center;
}

/* Extra horizontal/vertical padding beyond the base .section padding — cards extend
   past their own box via rotation and negative-margin overlap (see .data-card below),
   so the row needs breathing room on all sides or the tilted/overlapping edges would
   look clipped against the section boundary. */
.data-cards__row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  padding: 1rem 2rem;
}

.data-card {
  position: relative;
  width: 220px;
  flex-shrink: 0;
  margin: 0 -40px 32px 0;
  text-align: left;
  background: linear-gradient(160deg, var(--color-bg) 0%, var(--color-bg-alt) 100%);
  border-radius: var(--radius-card);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.18);
  overflow: hidden;
  /* --rotate/--slide-from are set per-card in site.js: --rotate is a small fixed tilt
     (kept through both states below), --slide-from is how much closer to the row's
     centre this card starts, before .is-visible resets it to 0 — the "slide outwards"
     entrance. transform (not margin) drives that animation so it doesn't disturb the
     row's actual layout/overlap while sliding. */
  transform: translateX(var(--slide-from, 0px)) rotate(var(--rotate, 0deg));
  transition: transform 0.8s ease, box-shadow 0.2s ease;
}

.data-cards__row.is-visible .data-card {
  transform: translateX(0) rotate(var(--rotate, 0deg));
}

/* Every card is server-rendered (see macros/sections.njk) — this only visually hides
   cards site.js chose not to show for this page load (data-display-count), it never
   removes them from the DOM/HTML source. */
.data-card.is-hidden {
  display: none;
}

/* Specificity has to at least match .data-cards__row.is-visible .data-card (three
   class-level selectors) — a plain .data-card:hover would lose to it once the row is
   visible and the hover scale/shadow would silently never show. Its own faster
   transition (not the 0.8s entrance one) is what makes the hover feel snappy. */
.data-cards__row.is-visible .data-card:hover {
  transform: translateX(0) rotate(var(--rotate, 0deg)) scale(1.08);
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.25);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
  z-index: 1;
}

.data-card__image {
  display: block;
  width: 100%;
  height: 140px;
  object-fit: cover;
}

.data-card__body {
  padding: 1rem;
}

.data-card__name {
  margin: 0 0 0.5rem;
  font-size: 1rem;
}

/* Truncates to exactly 3 lines with an ellipsis — the standard line-clamp technique,
   still -webkit-prefixed but supported across all current browsers regardless. */
.data-card__intro {
  margin: 0;
  color: var(--color-text-muted);
  font-size: 0.875rem;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Below this, flex-wrap was putting one 220px card per line — there's no room for the
   fan/overlap effect (220px cards, -40px negative margin) to wrap two-up the way it
   does on wider screens, so it degraded to a long single-column list instead. Swapping
   to a horizontally swipeable strip fits the same 8-ish cards in far less vertical
   space, which is the actual goal here (not the overlap/tilt effect, which reads oddly
   in a scrolling strip anyway — neutralised below rather than in site.js, since the
   entrance/hover rules it fights are otherwise unchanged for desktop). */
@media (max-width: 640px) {
  .data-cards__row {
    flex-wrap: nowrap;
    justify-content: flex-start;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    padding: 1rem 1.25rem;
    margin: 0 -1.25rem;
  }

  .data-card {
    margin: 0 0.75rem 0 0;
    scroll-snap-align: start;
  }

  .data-card,
  .data-cards__row.is-visible .data-card,
  .data-cards__row.is-visible .data-card:hover {
    transform: none;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.18);
  }
}

/* Wide on purpose — the default .section max-width (1100px) read as "lost in the
   middle" of a genuinely wide screen, so this section opts out of it (like .section
   --hero/--cta-banner already do) and sizes itself off the real viewport instead,
   capped so it doesn't get absurd on ultra-wide monitors. */
.section--testimonials {
  max-width: none;
}

.testimonials__carousel {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
  width: min(66vw, 900px);
  margin: 0 auto;
}

.testimonials__arrow {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: none;
  /* Solid brand colour, not the bg/border-tinted button used elsewhere — sitting
     directly on the page background (same colour as the button's own previous fill),
     a subtle bordered circle barely read as a button at all. */
  background: var(--color-primary);
  color: var(--color-on-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background-color 0.2s ease;
  /* The shrunk peek cards (scale(0.7), then translated by a % of their *own* width)
     extend further sideways than they look, out past the track's edges and under
     these arrows — without a higher z-index than theirs, a semi-transparent peek card
     paints over the button and makes its solid colour look faded/alpha'd. */
  position: relative;
  z-index: 4;
}

.testimonials__arrow:hover {
  background: var(--color-primary-dark);
}

.testimonials__arrow svg {
  width: 20px;
  height: 20px;
}

/* Height is set explicitly by site.js, to whichever card is tallest — every card is
   absolutely positioned inset:0 within it (see below), and absolutely positioned
   children don't contribute to a parent's natural height themselves, so without this
   the track would collapse to 0. Fixing it to the tallest card is also what stops the
   arrows shifting position as you step between testimonials of different lengths. */
.testimonials__track {
  position: relative;
  flex: 1;
  min-width: 0;
}

/* Every card lives in the DOM (site.js shuffles + reorders them once on load) and
   stays there, stacked on top of each other — switching testimonials just fades the
   new one in and the old one out (opacity + visibility, the latter so a faded-out
   card stops intercepting clicks/hover once it's done transitioning) rather than
   changing which cards exist, so the track's height never has to recalculate mid-fade. */
/* No position/width/height here — site.js measures each card's natural height, then
   applies position: absolute + matching top/width/height inline once the track's own
   height is fixed to the tallest one (see sizeTestimonialTrack). Baking that in
   statically would make every card measure the *track's* height back instead of its
   own, since height:100% depends on the parent. `left` IS set here rather than by
   JS, since it's just a fixed centring point (50%) that combines with each card's own
   translateX/scale below — those are what actually vary per role (active/prev/next). */
.testimonial-card {
  left: 50%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  padding: 2rem;
  opacity: 0;
  visibility: hidden;
  transform: translateX(-50%) scale(0.85);
  filter: blur(0);
  transition: transform 0.35s ease, opacity 0.35s ease, filter 0.35s ease, visibility 0s linear 0.35s;
  z-index: 1;
}

.testimonial-card.is-active {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) scale(1);
  filter: blur(0);
  z-index: 3;
  transition: transform 0.35s ease, opacity 0.35s ease, filter 0.35s ease, visibility 0s linear 0s;
}

/* The previous/next testimonials, shrunk and peeking out from behind either side of
   the focused one — visible but not interactive (pointer-events: none; the arrows are
   the only way to navigate, not clicking the peeking card itself). */
.testimonial-card.is-prev,
.testimonial-card.is-next {
  background: var(--color-bg-alt);
  opacity: 0.45;
  visibility: visible;
  z-index: 2;
  pointer-events: none;
  filter: blur(3px);
  transition: transform 0.35s ease, opacity 0.35s ease, filter 0.35s ease, visibility 0s linear 0s;
}

.testimonial-card.is-prev {
  transform: translateX(calc(-50% - 55%)) scale(0.7);
}

.testimonial-card.is-next {
  transform: translateX(calc(-50% + 55%)) scale(0.7);
}

/* Below this, a 2/3-of-viewport carousel with peeking side cards doesn't have the
   room to work the way it does on desktop — narrower than its own peeking neighbours
   would either overflow the screen or crowd right up against the focused card, so
   this reverts to a single full-width, non-peeking card instead, same as before the
   peek treatment existed. The arrows also move from either side of the card (where
   they were eating a big chunk of an already-narrow screen, leaving little room for
   the actual quote) to a row of their own underneath it — a 2-row grid rather than
   flex's row layout, since grid-template-areas can place the (DOM-order) prev arrow,
   track, next arrow into "track track" / "prev next" without reordering markup. */
@media (max-width: 800px) {
  .testimonials__carousel {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-areas: "track track" "prev next";
    gap: 1rem;
    width: 100%;
  }

  .testimonials__track {
    grid-area: track;
  }

  .testimonials__arrow--prev {
    grid-area: prev;
    justify-self: end;
  }

  .testimonials__arrow--next {
    grid-area: next;
    justify-self: start;
  }

  .testimonial-card.is-prev,
  .testimonial-card.is-next {
    opacity: 0;
    visibility: hidden;
    transform: translateX(-50%) scale(1);
  }
}

.testimonial-card__header {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  margin-bottom: 1rem;
}

.testimonial-card__header h3 {
  margin: 0;
  /* Only visible once this wraps to more than one line (a single line is exactly as
     wide as its own text either way) — start-aligned reads better next to the logo
     than each wrapped line re-centering under a fixed-width circle beside it. */
  text-align: start;
}

.testimonial-card__logo {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  overflow: hidden;
  flex-shrink: 0;
}

.testimonial-card__logo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.testimonial-card__body {
  font-style: italic;
}

.testimonial-card__quote-by {
  margin-top: 1rem;
  color: var(--color-text-muted);
  font-size: 0.9rem;
  font-weight: 600;
}

/* Same image-beside-content shape as .section--text-image, but scoped under
   .section--faq specifically rather than reusing that class directly — the FAQ list
   isn't a couple of paragraphs, it's its own accordion component (.faq__list below),
   so it gets its own rule instead of overloading .section__content's meaning.
   .section--image-left is still reused as-is for the reversal, since that modifier
   was already generic/content-agnostic. */
.faq__layout {
  display: flex;
  align-items: flex-start;
  gap: 3rem;
  flex-wrap: wrap;
}

.section--faq .section__media {
  flex: 1;
  min-width: 280px;
  display: flex;
  justify-content: center;
}

.section--faq .section__media img {
  border-radius: var(--radius-card);
}

.faq__list {
  flex: 1;
  min-width: 280px;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.faq__item {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  overflow: hidden;
}

.faq__question {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 1.25rem;
  background: none;
  border: none;
  font: inherit;
  font-weight: 600;
  text-align: left;
  color: var(--color-text);
  cursor: pointer;
}

.faq__chevron {
  flex-shrink: 0;
  width: 10px;
  height: 10px;
  border-right: 2px solid var(--color-text-muted);
  border-bottom: 2px solid var(--color-text-muted);
  transform: rotate(45deg);
  transition: transform 0.2s ease;
}

.faq__item.is-open .faq__chevron {
  transform: rotate(-135deg);
}

/* max-height (not display/height) so it can transition smoothly — it needs a concrete
   pixel value to animate to though, not just "auto", so site.js sets it to the
   answer's own scrollHeight on open/close rather than toggling a class alone. */
.faq__answer {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease;
  padding: 0 1.25rem;
}

.faq__answer p {
  padding-bottom: 1.25rem;
  margin: 0;
  color: var(--color-text-muted);
}

.section--live-listings {
  text-align: center;
}

/* Only applies when there's no articlePool to draw from yet (see the macro's {% else %}
   branch) — the section keeps its old "coming soon" look in that case rather than
   rendering an empty grid. */
.section--live-listings:has(.section__placeholder) {
  background: var(--color-bg-alt);
  border-radius: var(--radius-card);
}

.section__placeholder {
  color: var(--color-text-muted);
  font-style: italic;
}

.live-listings__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
  text-align: left;
}

@media (max-width: 900px) {
  .live-listings__grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 560px) {
  .live-listings__grid {
    grid-template-columns: 1fr;
  }
}

/* Same background treatment as .data-card, for a consistent "card" language between the
   two Firestore-backed section types — different internal layout (logo-left, text-right
   here, vs image-on-top there) but the same surface. Lighter shadow than .data-card's,
   on purpose — these sit in a plain grid, not an overlapping fanned-out pile, so they
   don't need as much depth to read as separate tiles. */
.live-listing-tile {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1rem;
  /* Stays flat at --color-bg through the first 30%, only bulging through a softened
     --color-bg-alt (blended halfway back toward --color-bg via colour-mix, rather
     than the full-strength token) at 70% down, then settling back to --color-bg at
     the bottom edge — reads as a much subtler surface than a full top-to-bottom
     shift, and never reaches --color-bg-alt at full strength either. */
  background: linear-gradient(180deg, var(--color-bg) 0%, var(--color-bg) 30%, color-mix(in srgb, var(--color-bg-alt) 50%, var(--color-bg)) 70%, var(--color-bg) 100%);
  border-radius: var(--radius-card);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}

/* Every tile is server-rendered (see macros/sections.njk) — this only visually hides
   tiles site.js chose not to show for this page load (data-display-count), it never
   removes them from the DOM/HTML source. */
.live-listing-tile.is-hidden {
  display: none;
}

.live-listing-tile__logo {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}

.live-listing-tile__body {
  min-width: 0;
}

.live-listing-tile__name {
  margin: 0 0 0.25rem;
  font-size: 1rem;
}

/* Normalised to a single comma-separated line in site.js (source addresses are a mix of
   newline-separated and already-comma-separated lines) — clamped to 2 lines here so
   every tile's address block takes up the same amount of space regardless of how long
   the underlying address string is, which is what keeps the whole grid lining up. */
.live-listing-tile__address {
  margin: 0;
  color: var(--color-text-muted);
  font-size: 0.75rem;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* article.ctas — see macros/sections.njk's liveListings macro. A single small toggle
   (real links behind it, not something JS builds) rather than a row of buttons on
   every tile, so a listing with several links doesn't make its tile any bigger or
   busier than one with none — just an indicator that actions exist. */
.live-listing-tile__actions {
  position: relative;
  flex-shrink: 0;
  margin-left: auto;
}

.live-listing-tile__actions-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  background: var(--color-bg);
  color: var(--color-text-muted);
  cursor: pointer;
  transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}

.live-listing-tile__actions-toggle svg {
  width: 18px;
  height: 18px;
}

.live-listing-tile__actions-toggle:hover,
.live-listing-tile__actions.is-open .live-listing-tile__actions-toggle {
  background: var(--color-primary);
  color: var(--color-on-primary);
  border-color: var(--color-primary);
}

/* Same visual language as .site-nav__dropdown-menu (border/shadow/hover), just
   anchored under this tile's own toggle instead of a header nav item. */
.live-listing-tile__actions-menu {
  display: none;
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: 0.5rem;
  min-width: 180px;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 0.5rem 0;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
  z-index: 5;
}

.live-listing-tile__actions.is-open .live-listing-tile__actions-menu {
  display: block;
}

.live-listing-tile__actions-menu a {
  display: block;
  padding: 0.5rem 1rem;
  color: var(--color-text);
  text-decoration: none;
  font-size: 0.9rem;
  white-space: nowrap;
}

.live-listing-tile__actions-menu a:hover {
  background: var(--color-bg-alt);
}

/* Footer — repeats the header nav as a set of left-aligned columns, one per dropdown/
   link group, so every page/category is reachable from the footer too rather than
   only from the header's dropdowns. */
.site-footer {
  border-top: 1px solid var(--color-border);
  padding: 3rem 2rem 2rem;
  text-align: left;
  color: var(--color-text-muted);
}

.site-footer__inner {
  max-width: 1100px;
  margin: 0 auto;
}

.site-footer__nav {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 2rem;
  margin-bottom: 2.5rem;
}

.site-footer__nav-group {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

/* First column — Visitors, Creators and Resources are each small enough that they'd
   look sparse as their own full column, so they stack as sub-groups within one instead,
   each keeping its own heading. */
.site-footer__nav-group--stacked {
  gap: 1.5rem;
}

.site-footer__nav-subgroup {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.site-footer__nav-heading {
  font-family: var(--font-heading);
  font-weight: 600;
  color: var(--color-text);
  margin-bottom: 0.25rem;
}

.site-footer__nav-group a {
  color: var(--color-text-muted);
  text-decoration: none;
  font-size: 0.95rem;
}

.site-footer__nav-group a:hover {
  color: var(--color-primary);
}

/* Same treatment as the header dropdowns' equivalent items (Pricing, Contact Us) —
   theme-aware --color-primary rather than a fixed accent colour. */
.site-footer__nav-link--accent {
  color: var(--color-primary);
  font-weight: 600;
}

.site-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  padding-top: 2rem;
  border-top: 1px solid var(--color-border);
}

.site-footer__logo {
  /* Explicit width alongside height, not just height alone — this image sits inside
     a <picture> (see the image shortcode, .eleventy.js), and relying on the browser
     to infer width from the <img>'s width/height attributes is inconsistent across
     browsers specifically inside <picture>, unlike a bare <img>. mapzest_circle_logo.png
     is a perfect square, so width matches height 1:1. */
  width: 32px;
  height: 32px;
}

.site-footer__bottom p {
  margin: 0;
}

.site-footer__legal-links {
  display: flex;
  gap: 1rem;
  margin-left: auto;
  font-size: 0.85rem;
}

.site-footer__legal-links a {
  color: var(--color-text-muted);
}

.site-footer__legal-links a:hover {
  color: var(--color-primary);
}

.site-footer__social {
  display: flex;
  align-items: center;
  gap: 0.85rem;
}

.site-footer__social a {
  color: var(--color-text-muted);
  display: flex;
}

.site-footer__social a:hover {
  color: var(--color-primary);
}

.site-footer__social svg {
  width: 20px;
  height: 20px;
}

.site-footer__qr {
  height: 56px;
  width: 56px;
  flex-shrink: 0;
  border-radius: 4px;
}

/* Collapses to a hamburger + full-screen menu once the full nav (links + both
   dropdowns + Contact Us + Get Mapzest + theme toggle) would actually collide — not
   a fixed viewport-width breakpoint, which had to be re-measured and hand-tuned
   every time the header's own content changed (a stale one previously let Resources
   overlap Contact Us for a wide range of real widths). site.js's
   setUpHeaderNavCollapse measures each header's own true required width against a
   permanently off-screen clone of itself and toggles .is-nav-collapsed here
   accordingly — see its own comment for how. Get Mapzest stays visible in the bar at
   this tier (dropped only under .is-cta-collapsed below, on an even narrower
   screen); everything else, including the theme toggle (now inside
   .mobile-menu__header-actions instead — see layout.njk) and Contact Us (already
   reachable via the Resources group in the mobile menu, so not duplicated here),
   moves into .mobile-menu. */
.site-header.is-nav-collapsed .site-nav,
.site-header-sticky.is-nav-collapsed .site-nav {
  display: none;
}

.site-header.is-nav-collapsed .site-header__actions .site-nav__link,
.site-header.is-nav-collapsed .site-header__actions .site-header__theme-toggle,
.site-header-sticky.is-nav-collapsed .site-header__actions .site-nav__link,
.site-header-sticky.is-nav-collapsed .site-header__actions .site-header__theme-toggle {
  display: none;
}

.site-header.is-nav-collapsed .site-header__menu-toggle,
.site-header-sticky.is-nav-collapsed .site-header__menu-toggle {
  display: flex;
}

/* One tier narrower still (site.js's second, already-collapsed clone) — logo, share
   button and hamburger are the only things left that can't give up any more room,
   so Get Mapzest is what drops here instead of colliding with the logo. */
.site-header.is-cta-collapsed .site-header__cta-dropdown,
.site-header-sticky.is-cta-collapsed .site-header__cta-dropdown {
  display: none;
}

/* Footstep trail (built by site.js) — decorative marks connecting one section's text
   to the next as you scroll between them. Positioned in document coordinates by JS
   (top is set inline per trail), so only the cross-cutting styling lives here. */
.footstep-trail {
  position: absolute;
  left: 0;
  width: 100%;
  pointer-events: none;
  z-index: 5;
  overflow: visible;
}

.footstep {
  opacity: 0;
  /* --color-primary, not a fixed/muted tone — on pages with their own seedColour front
     matter, this is the Material-3 primary generated from that seed (see
     scripts/hero-seed-theme.js), so the trail picks up each page's own colour instead
     of a single fixed grey everywhere. */
  fill: var(--color-primary);
  /* No transform-origin here — the position/rotation is set entirely via the transform
     *attribute* in site.js (translate to the point, then rotate), and any CSS
     transform-origin other than the SVG default (0,0) changes what that rotation
     pivots around, throwing the translate off by hundreds/thousands of px. */
  transition: opacity 0.5s ease;
}

.footstep.is-visible {
  opacity: 0.45;
}

/* Card-grid lightbox — one shared full-screen overlay (markup in layout.njk), reused
   by every card-grid section on the page; site.js scopes which section's cards are
   currently navigable to whichever one was tapped. Fixed dark overlay + fixed white
   text, not theme-varying tokens, since it sits above arbitrary photo content rather
   than the page's own background — same reasoning as .mobile-menu and the hero h1's
   own fixed-white treatment elsewhere in this file. Built fresh for this site; not
   shared with tools/seo_microsite's own (different) lightbox implementation. */
.lightbox {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 200;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.85);
  padding: 2rem;
}

.lightbox.is-open {
  display: flex;
}

.lightbox__figure {
  margin: 0;
  max-width: 90vw;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
}

.lightbox__image {
  display: block;
  max-width: 90vw;
  max-height: 78vh;
  object-fit: contain;
  border-radius: var(--radius-card);
}

.lightbox__caption {
  margin: 0;
  color: #fff;
  font-family: var(--font-heading);
  font-weight: 600;
  text-align: center;
}

.lightbox__caption:empty {
  display: none;
}

.lightbox__close,
.lightbox__nav {
  position: absolute;
  background: rgba(255, 255, 255, 0.12);
  border: none;
  border-radius: 50%;
  color: #fff;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.2s ease;
}

.lightbox__close:hover,
.lightbox__nav:hover {
  background: rgba(255, 255, 255, 0.24);
}

.lightbox__close {
  top: 1.25rem;
  right: 1.25rem;
  width: 44px;
  height: 44px;
  font-size: 1.75rem;
  line-height: 1;
}

.lightbox__nav {
  top: 50%;
  transform: translateY(-50%);
  width: 48px;
  height: 48px;
}

.lightbox__nav svg {
  width: 24px;
  height: 24px;
}

.lightbox__nav--prev {
  left: 1.25rem;
}

.lightbox__nav--next {
  right: 1.25rem;
}

@media (max-width: 640px) {
  .lightbox__close,
  .lightbox__nav {
    width: 40px;
    height: 40px;
  }
}

/* Contact form — same two-column shape as .section--text-image (heading/copy one
   side, the actual content the other), rather than a hero followed by its own
   separate section. flex-start (not text-image's center) since a short heading
   shouldn't vertically centre against a much taller form. */
.section--contact-form {
  display: flex;
  align-items: flex-start;
  gap: 3rem;
  flex-wrap: wrap;
}

.section--contact-form .section__content,
.section--contact-form .contact-form__media {
  flex: 1;
  min-width: 280px;
}

.contact-form__connect-label {
  font-weight: 600;
  margin: 1.5rem 0 0.75rem;
}

.contact-form__founder {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.contact-form__founder-avatar {
  width: 64px;
  height: 64px;
  flex-shrink: 0;
  object-fit: cover;
  border-radius: 50%;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}

.contact-form__founder-info {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.contact-form__founder-name {
  font-size: 0.95rem;
}

.contact-form__founder-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
  color: var(--color-text-muted);
  text-decoration: none;
}

.contact-form__founder-link:hover {
  color: var(--color-primary);
}

.contact-form__founder-link svg {
  flex-shrink: 0;
}

/* Shared glass-card look — same --color-glass-bg + blur combination as the header nav
   pills, reused wherever a block needs to read as "part of that same family" rather
   than a plain white box (the contact form's two columns, .section--text-block, and
   anywhere else that opts in). On the contact form's right column this wraps both
   .contact-form and .contact-form__success (not just the <form> itself) so the panel
   stays put when one swaps for the other on submit. */
.glass-panel {
  background: var(--color-glass-bg);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  padding: 2rem;
}

/* Less top padding than the base .section's 4rem — when this follows straight after
   another section (pricing-table's enterprise block, contact.md's "Behind Mapzest"),
   the full amount reads as a bigger gap than the rhythm elsewhere on either page. */
.section--text-block {
  padding-top: 1.5rem;
}

.section--text-block h2 {
  margin-top: 0;
}

.section--text-block p {
  max-width: 70ch;
}

.section--text-block p + p {
  margin-top: 1rem;
}

.section--download {
  display: flex;
  justify-content: center;
}

.download-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.5rem;
  max-width: 480px;
  text-align: center;
}

.download-panel h1 {
  margin: 0;
}

.download-panel .section__subheading {
  margin: 0;
}

.download-panel__qr {
  width: 160px;
  height: 160px;
  border-radius: var(--radius-card);
}

.download-panel__stores {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1rem;
}

.download-panel__store img {
  height: 48px;
  width: auto;
}

.section--message {
  display: flex;
  justify-content: center;
}

.message-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  max-width: 480px;
  text-align: center;
}

.message-panel__eyebrow {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 3rem;
  color: var(--color-primary);
}

.message-panel h1 {
  margin: 0;
}

.message-panel .section__subheading {
  margin: 0;
}

.message-panel .btn--primary {
  margin-top: 0.5rem;
}

.contact-form__heading {
  margin: 0 0 1.25rem;
}

.contact-form {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

/* site.js sets the hidden attribute on submit success (form.hidden = true) — without
   this, the browser's default `[hidden] { display: none }` UA rule loses to the
   .contact-form class's own `display: flex` above (an author rule always beats a UA
   one, regardless of specificity/order), so the form stayed visible with its submit
   button stuck mid-"Sending…" even though the success message had already appeared
   next to it. The extra attribute selector here gives this rule the higher specificity
   it needs to actually win. */
.contact-form[hidden] {
  display: none;
}

.contact-form__field {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.contact-form__field label {
  font-weight: 600;
  font-size: 0.9rem;
}

.contact-form input,
.contact-form select,
.contact-form textarea {
  font: inherit;
  padding: 0.65rem 0.85rem;
  border-radius: 10px;
  border: 1px solid var(--color-border);
  background: var(--color-bg);
  color: var(--color-text);
}

.contact-form input:focus,
.contact-form select:focus,
.contact-form textarea:focus {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
}

.contact-form textarea {
  resize: vertical;
}

/* Companion "please specify" input for a select's otherOption — hidden by default,
   toggled by site.js when that specific option is chosen (see data-other-trigger). */
.contact-form__other {
  margin-top: -0.6rem;
}

.contact-form__other.is-hidden {
  display: none;
}

/* Off-screen, not display:none — a simple bot-filter some crawlers specifically skip
   display:none fields to avoid, so this stays in the accessibility tree but out of
   view/tab order (tabindex="-1" on the field itself handles the latter). */
.contact-form__honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.contact-form__submit {
  align-self: flex-start;
}

.contact-form__error {
  color: #c0392b;
  font-size: 0.9rem;
  margin: 0;
}

.contact-form__success h3 {
  margin-top: 0;
}

/* ==========================================================================
   Blog — templates/blog-post.njk, content/blog.njk, content/blog-category.njk,
   content/blog-tag.njk, macros/blog.njk, macros/sections.njk's blogImage/blogGallery
   macros. Reading-width prose reuses .legal-content's own 760px measure for
   consistency with the site's other long-form content.
   ========================================================================== */

/* The shared two-column container for all four blog page types above: a wide left
   column (the article, or a result grid) plus a narrower right column (category/tag
   links, and — post pages only — prev/next) that stays in view via position: sticky
   as the left column scrolls past it. Takes over the outer max-width/edge-gutter role
   .section normally plays — .blog-index and .blog-post__header/.blog-post__body below
   size themselves *within* .blog-layout__main rather than against the full viewport.
   align-items: flex-start (not the flex default, stretch) is what makes the sidebar's
   own sticky positioning work at all: stretch would size .blog-layout__sidebar to
   match .blog-layout__main's full height up front, leaving the sticky box nowhere
   left to travel before it "sticks". */
/* No top padding — every blog page's own text (the article header, or the plain
   listing/hub page's own h1) sits as high as the header/backdrop above it allows,
   rather than leaving a gap only this container was responsible for. The sidebar
   would otherwise rise by the same amount (it's this same flex container's own
   padding, shared by both columns) — .blog-layout__sidebar's own margin-top right
   below cancels that exactly (the full 2rem removed here), so it stays exactly
   where it was. */
.blog-layout {
  display: flex;
  align-items: flex-start;
  gap: 3rem;
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 2rem 2.5rem;
}

/* An individual post's own main column (the article) is narrower than a hub page's
   card grid, so the same 3rem gap above reads as a much bigger gulf here than it does
   next to a grid of cards — tightened for this page only, not the listing/hub pages
   which were already fine at 3rem. */
.blog-layout--post {
  gap: 2rem;
}

.blog-layout__main {
  flex: 1;
  min-width: 0;
}

.blog-layout__sidebar {
  flex: 0 0 280px;
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  /* Cancels .blog-layout's own padding-top: 0 above (see its comment) — without
     this the sidebar would rise by the same 2rem the text above it gained. */
  margin-top: 2rem;
  position: sticky;
  /* Clears the fixed .site-header-sticky bar once scrolled, plus a little breathing
     room — see --header-height in its own section above. */
  top: calc(var(--header-height) + 1.5rem);
}

/* Below this, a sidebar beside the main column no longer leaves either one enough
   room to be worth it — stacks instead, sidebar second (see markup) so it lands
   below the main column exactly as a narrow-screen reader would expect. */
@media (max-width: 900px) {
  .blog-layout {
    flex-direction: column;
    gap: 2.5rem;
  }

  .blog-layout__sidebar {
    position: static;
    width: 100%;
  }
}

@media (max-width: 640px) {
  .blog-layout {
    /* 0 first, not a value — keeps the base rule's padding-top: 0, same reasoning,
       at this breakpoint too, rather than a shorthand silently reintroducing it. */
    padding: 0 1.25rem 2rem;
  }
}

/* Same max-width as .blog-post__body .section below (760px) — the two used to disagree
   (this was capped at 696px, via width: calc() rather than max-width) so the header's
   own title/excerpt text sat 2rem further right than the body text directly beneath
   it. Matching the mechanism exactly, not just the number, keeps them aligned at every
   width: both fill 100% of .blog-layout__main and rely on its own edge padding once
   the column is narrower than 760px, rather than this panel keeping a separate gutter
   of its own. */
.blog-post__header {
  max-width: 760px;
  margin: 0 auto 3rem;
  text-align: left;
  /* Overrides glass-panel's own (shared-elsewhere) padding so this lines up exactly
     with .blog-post__body .section's own L/R padding (2rem, 1.25rem at ≤640px — see
     that rule's own media query) instead of glass-panel's generic value — top/bottom
     keeps glass-panel's 2rem, only L/R is pinned here. */
  padding-left: 2rem;
  padding-right: 2rem;
  /* .blog-post__image below deliberately bleeds past this padding on 3 sides via
     negative margin — overflow: hidden clips that bleed back to the panel's own
     rounded corners instead of the image's square ones poking out past them. */
  overflow: hidden;
  /* Replaces glass-panel's own border — a shadow reads as the photo lifting off the
     backdrop, where a hard border line just looked like a frame around it. */
  border: none;
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.12);
}

/* Image-first: the photo bleeds flush to all 4 of the panel's own edges (top included)
   — cancels glass-panel's 2rem padding on every side, then the bottom margin re-opens
   a normal gap before the title/excerpt/meta that follow underneath. overflow: hidden
   (inherited from .blog-post__header) clips this bleed back to the panel's own
   rounded corners. */
.blog-post__image {
  margin: -2rem -2rem 1.5rem -2rem;
}

.blog-post__image img {
  width: 100%;
  height: auto;
  display: block;
  /* Soft vignette — an inset shadow paints over the image's own content (same trick
     used for a purely-CSS vignette on any replaced element), fading the edges toward
     the card's frame rather than cutting them off sharply. Wide spread + low opacity
     keeps it gentle rather than a visible dark ring. */
  box-shadow: inset 0 0 100px 20px rgba(0, 0, 0, 0.3);
}

.blog-post__header h1 {
  margin: 0.75rem 0;
}

/* Wraps the h1 together with the optional event/organiser logo badge (front-matter
   `logoLight`/`logoDark`) so the two sit on one row, badge first — .blog-post__header
   h1's own margin above still applies (h1 is still a header descendant, just one
   level deeper), so this adds no vertical spacing of its own besides the row gap. */
.blog-post__title-row {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
}

/* No background/shadow — just the logo art itself, sized and laid out beside the
   title. Contrast across themes still comes from the light/dark split just below
   (dark-ink logoLight asset for the light theme, white-ink logoDark for dark),
   not from a coin-like backing behind it. */
.blog-post__logo-badge {
  flex: 0 0 auto;
  width: 6rem;
  height: 6rem;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.5rem;
}

.blog-post__logo-badge img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

/* Only one of the two logo variants is ever visible at a time — light is the default
   (shown whenever data-theme isn't 'dark', matching every other unscoped rule in this
   file), [data-theme='dark'] flips which, same pattern as .theme-icon--sun/--moon. */
.blog-post__logo-dark {
  display: none;
}

[data-theme='dark'] .blog-post__logo-dark {
  display: block;
}

[data-theme='dark'] .blog-post__logo-light {
  display: none;
}

/* Shared row for both category and tag pills (category pills first, see the
   template) — one flex container rather than two, so they sit together as a single
   line of labels instead of two separately-spaced groups. */
.blog-post__labels {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
  gap: 0.5rem;
  margin-top: 1rem;
}

/* Same size for both pill kinds — only colour/weight differs below, so a category
   and a tag read as two label *kinds* rather than two different sizes of the same
   thing. */
/* Both are links now — to that term's own hub page (content/blog-category.njk /
   content/blog-tag.njk, same destination the equivalent sidebar link points at) —
   so text-decoration/transition are shared here alongside the sizing they already
   shared. */
.blog-post__category,
.blog-post__tag {
  display: inline-block;
  border-radius: var(--radius-pill);
  padding: 0.3rem 0.9rem;
  font-size: 0.8rem;
  text-decoration: none;
  transition: opacity 0.15s ease;
}

.blog-post__category:hover,
.blog-post__tag:hover {
  opacity: 0.8;
}

/* Solid brand fill — distinguishes "category" (the post's own top-level section) from
   the plain outlined tags below. */
.blog-post__category {
  background: var(--color-primary);
  color: var(--color-on-primary);
  font-weight: 600;
}

/* Outlined/muted — kept as-is from before, just resized to match .blog-post__category
   above. */
.blog-post__tag {
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
}

.blog-post__excerpt {
  color: var(--color-text-muted);
  font-size: 1.1rem;
  margin: 0.5rem 0 1rem;
}

.blog-post__meta {
  display: flex;
  justify-content: flex-start;
  gap: 0.75rem;
  color: var(--color-text-muted);
  font-size: 0.8rem;
}

.blog-post__author::after {
  content: '·';
  margin-left: 0.75rem;
}

/* Every section inside a blog post — whatever type it is (text-block, blog-image,
   blog-gallery, video, or anything else the generic sections system supports) —
   shares this one measure, so text, photos and galleries all line up to the same
   left/right edge as you scroll, instead of each section type picking its own width.
   Overrides the base .section's 1100px, which is too wide for comfortable reading. */
.blog-post__body .section {
  max-width: 760px;
  margin-left: auto;
  margin-right: auto;
  /* The base .section's 4rem top+bottom padding is sized for landing-page-style
     sections (hero, pricing, card grids) — stacked one after another in a blog
     post, that's 8rem of dead space between one paragraph and the next image.
     1.5rem (matching .section--text-block's own existing top-padding reduction,
     applied here to every section, both edges) reads as a normal blog's rhythm
     instead. Overrides both the base .section and any section type's own padding
     tweak (e.g. text-block's) uniformly, at higher specificity either way. */
  padding-top: 1.5rem;
  padding-bottom: 1.5rem;
}

/* Same card treatment as .blog-post__header (glass-panel, applied in the template) —
   one continuous panel behind the whole article body, not just the header, so the
   post reads as a single card rather than a photo-backed header sitting above plain
   page background. Same max-width/alignment as the header so both panels' outer
   edges line up exactly, and the same border-replaced-by-shadow "lifted card" look.
   L/R padding is 0, not glass-panel's own 2rem — .blog-post__body .section below
   already carries its own 2rem (1.25rem ≤640px) L/R padding, so a second inset here
   would double it. Top is a small 0.5rem, not 0, because the first section's own
   1.5rem top padding only reaches glass-panel's usual 2rem inset with that added; the
   bottom is unchanged from before this panel existed — see the comment that used to
   sit here on why 2.5rem + the last section's own 1.5rem := 4rem total. */
.blog-post__body {
  max-width: 760px;
  margin: 0 auto;
  padding: 0.5rem 0 2.5rem;
  border: none;
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.12);
}

/* text-block's .glass-panel is a translucent card meant to sit legibly on top of a
   busy backdrop photo (see contact-form/download/message, which all stay on the
   backdrop) — a blog post's body has long since scrolled past that photo onto the
   plain page background, so the card reads as an arbitrary width/inset change
   instead. Stripped down to plain text here so its content sits flush with
   blog-image/blog-gallery's own edges, at the section's own padding. */
.blog-post .section--text-block .glass-panel {
  max-width: none;
  margin: 0;
  padding: 0;
  background: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border: none;
}

.blog-image {
  /* <figure> has a UA-stylesheet default margin (1em 40px in every mainstream
     browser) — left unreset, that 40px side margin was quietly shrinking this
     specific section's content well inside the 760px column every other blog
     section actually renders at (confirmed by measuring its rendered box: 616px vs
     760px at the same viewport). */
  margin: 0;
  text-align: center;
}

.blog-image__frame {
  cursor: pointer;
  border-radius: var(--radius-card);
  overflow: hidden;
  transition: box-shadow 0.2s ease;
}

.blog-image__frame:hover {
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.12);
}

.blog-image__frame img {
  width: 100%;
  /* Explicit, not left to the browser to infer from the width/height HTML
     attributes eleventy-img writes (see the image shortcode, .eleventy.js) — that
     implicit inference doesn't reliably apply once one CSS dimension (width, above)
     is set without the other, especially for an <img> inside a <picture> with
     multiple srcset candidates: confirmed by measurement, it was rendering at the
     *literal* height attribute's pixel value (from the largest generated variant)
     regardless of the actually-scaled width, distorting the image at every width
     except whichever one happened to match that literal value. height: auto is the
     plain, decades-old, unambiguous fix — same root cause as the footer logo's
     stretch bug (see static/site.css's own .site-footer__logo comment). */
  height: auto;
  display: block;
}

.blog-image__caption {
  color: var(--color-text-muted);
  font-size: 0.9rem;
  font-style: italic;
  margin-top: 0.75rem;
}

/* Grid, not flex — a fixed 220px flex-basis left real width unused whenever the row
   didn't divide evenly (e.g. 4 items in a 696px column: 2 per row at 220px each,
   with the rest of that width just sitting empty either side). auto-fit + minmax
   makes every item grow to actually fill its row, however many end up on it. */
.blog-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1.5rem;
}

.blog-gallery__item {
  cursor: pointer;
  margin: 0;
}

.blog-gallery__thumb {
  /* aspect-ratio, not a fixed pixel height — the grid above already makes each
     item's actual width vary with however many share its row, so a fixed height
     would leave wider items looking flatter/more cropped than narrower ones. */
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-card);
  overflow: hidden;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.blog-gallery__item:hover .blog-gallery__thumb {
  transform: translateY(-4px);
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.12);
}

.blog-gallery__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.blog-gallery__caption {
  color: var(--color-text-muted);
  font-size: 0.85rem;
  margin-top: 0.5rem;
  text-align: center;
}

/* Blog listing content (content/blog.njk, content/blog-category.njk, content/blog-tag.njk)
   — .blog-layout__main's own child, not a .section: the outer edge-gutter/max-width
   role .section normally plays is .blog-layout's job now (see above). */
.blog-index {
  text-align: left;
}

/* Zeroes the h1's own default UA top margin, which otherwise collapses straight
   through .blog-index (a plain div — nothing here stops that the way
   .blog-post__header's glass-panel padding does) and pushes the whole heading down
   below .blog-layout__main's own top edge — this and .blog-post__header now start
   at that exact same y regardless, so the discrepancy was this margin, not the
   layout container. */
.blog-index h1 {
  margin-top: 0;
}

/* Fixed, not the theme-varying --color-text — same reasoning as .section--pricing-table's
   own h1/subheading (this page uses the same backgroundImageDarkness: 50): it's the
   first section, sitting directly on the fading backdrop photo with no glass panel
   behind it, and that photo doesn't change with the theme toggle even though
   --color-text does — so a colour picked to contrast with one theme's background
   would just stop contrasting the moment the photo itself, unaffected, is what's
   actually behind the text in the other. */
.blog-index h1,
.blog-index .section__subheading {
  color: #fff;
}

/* Grid, not flex-wrap with a fixed flex-basis — that fixed every card at 320px
   regardless of how much row width was actually available, so on the narrower main
   column .blog-layout__sidebar leaves beside it (see .blog-layout), cards that could
   comfortably be 380-400px wide sat at 320px with dead space next to them instead.
   auto-fit (not auto-fill) is what lets a lone card, or the last odd one out, actually
   grow to fill its row rather than sitting narrow beside empty phantom tracks. 260px
   as the minimum is chosen to satisfy two widths at once: .blog-layout__main caps out
   at 808px (.blog-layout's own max-width: 1200px, minus its padding, minus the
   280px+3rem sidebar beside it) — the widest this ever gets on any screen, however
   wide the monitor — and 260px is just past the point 3 columns would fit there
   (3 × 260 + 2 × 32 gap = 844 > 808), so a full-width screen stays at 2 columns, not
   3; it's also just under the point a much narrower in-between width (e.g. 558px,
   sidebar still beside a narrower viewport) needs for 2 (2 × 260 + 32 = 552 ≤ 558),
   so that case still gets its second column rather than sitting at a stranded one.
   No max-width on .blog-card and no justify-items override below (both used to exist
   here, to stop a lone card growing edge-to-edge on a wide row) — removed because
   the max-width was the actual bug being fixed: on any width where the grid resolves
   to one column narrower than that cap, the card sat left-aligned at its capped
   width with dead space beside it, reading as a stray margin rather than a
   deliberately narrower card. A lone card now genuinely fills its row at any width. */
.blog-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 2rem;
  margin: 3rem 0 1.5rem;
  text-align: left;
}

/* Same glass-panel treatment as the contact page's own panels (contact-form/download/
   message use the class directly; this can't, since glass-panel's own padding: 2rem
   would fight .blog-card__image's full-bleed top image and .blog-card__body's own
   padding) — plain --color-glass-bg, standard border, no extra tint or resting shadow
   beyond what that shared look already gives every other glass panel on the site. */
.blog-card {
  width: 100%;
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-card);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
  background: var(--color-glass-bg);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  /* Same lift + no border as .blog-sidebar__section/.blog-post__header. */
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.12);
}

.blog-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.18);
}

.blog-card__image {
  height: 180px;
}

.blog-card__image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.blog-card__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 1.5rem;
}

.blog-card__category {
  color: var(--color-primary);
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.blog-card__title {
  margin: 0.4rem 0;
  font-size: 1.15rem;
}

.blog-card__excerpt {
  color: var(--color-text-muted);
  font-size: 0.95rem;
  flex: 1;
  /* Clamped to 3 lines so every card in a row lines up at the same height regardless
     of how long its own excerpt is. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.blog-card__date {
  color: var(--color-text-muted);
  font-size: 0.85rem;
  margin-top: 1rem;
}

@media (max-width: 640px) {
  /* Matches .section's own L/R padding at this breakpoint (1.25rem — see its own
     media query above), same reasoning as the base rule above pinning this to 2rem. */
  .blog-post__header {
    padding-left: 1.25rem;
    padding-right: 1.25rem;
    margin-bottom: 2rem;
  }

  /* Top bleed stays -2rem, not glass-panel's own 1.5rem at this breakpoint — despite
     that media query, .blog-post__header's actual computed padding-top never drops
     below the base 2rem (the unconditional .glass-panel rule sits *after* its own
     @media block in this file, so — same specificity, later source order — it wins
     outright regardless of viewport width; this is a pre-existing quirk in
     .glass-panel itself, not something to work around by chasing whatever value it
     "should" be). L/R bleeds past the 1.25rem this panel is actually pinned to above. */
  .blog-post__image {
    margin: -2rem -1.25rem 1.5rem -1.25rem;
  }
}

/* Blog sidebar (macros/blog.njk's sidebar()) — Categories, Tags and (post pages only)
   the prev/next mini-cards each get their own glass-panel'd section, stacked via
   .blog-layout__sidebar's own gap rather than one big panel, so a page with only
   e.g. categories (no adjacent posts) doesn't leave an empty gap where the others
   would have been. Same glass-panel legibility reasoning as .blog-post__header:
   the sidebar sits over the same fading backdrop photo for the first screenful. */
/* No overrides here — same plain glass-panel look as the contact page's own panels
   (background/border/blur/radius/padding all come from that shared class already
   applied in the markup). */
.blog-sidebar__section {
  width: 100%;
  /* Same lift + no border as .blog-post__header (the article hero panel) — overrides
     glass-panel's own border with the shadow above carrying the panel's edge instead. */
  border: none;
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.12);
}

/* Post profile panel (macros/blog.njk's sidebar(), front-matter `logoLight`/
   `logoDark` + `externalLinks`) — logo spans the panel's own content width (its
   L/R inset already comes from glass-panel's standard 2rem padding, not from
   anything on the logo itself), external links stacked underneath. The macro only
   ever renders this whole section when at least one of the two has content, so no
   empty-state CSS is needed here. */
.blog-sidebar__profile-logo {
  margin-bottom: 1rem;
}

.blog-sidebar__profile-logo img {
  /* No max-height cap — width:100% + height:auto (a plain responsive image, not
     object-fit, which only matters once both dimensions are constrained) is what
     keeps this filling the full width while preserving the source image's own
     aspect ratio, whatever that happens to be. */
  width: 100%;
  height: auto;
  display: block;
}

.blog-sidebar__external-links {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.blog-sidebar__external-links a {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  color: var(--color-text);
  text-decoration: none;
  font-size: 0.95rem;
}

.blog-sidebar__external-links a:hover {
  color: var(--color-primary);
}

.blog-sidebar__external-links svg {
  flex: 0 0 auto;
  width: 1.15rem;
  height: 1.15rem;
}

.blog-sidebar__heading {
  margin: 0 0 0.75rem;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-muted);
}

.blog-sidebar__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.blog-sidebar__link {
  display: block;
  color: var(--color-text);
  text-decoration: none;
  font-size: 0.95rem;
  padding: 0.3rem 0;
}

.blog-sidebar__link:hover {
  color: var(--color-primary);
}

.blog-sidebar__link--active {
  color: var(--color-primary);
  font-weight: 600;
}

.blog-sidebar__tag-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* Same pill treatment as .blog-post__tag (own header chips) — kept as a separate
   rule, not a shared selector, since the two live in unrelated markup (an <a> here
   vs. there) and this one additionally needs the active/current-page state below. */
.blog-sidebar__tag {
  display: inline-block;
  color: var(--color-text-muted);
  text-decoration: none;
  font-size: 0.8rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  padding: 0.3rem 0.9rem;
}

.blog-sidebar__tag:hover {
  border-color: var(--color-primary);
  color: var(--color-primary);
}

.blog-sidebar__tag--active {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-on-primary);
}

/* .blog-sidebar__recent-list (Recent Posts, macros/blog.njk) shares this exact layout
   — same adjacentCard() markup, just without the prev/next label/data-blog-nav. */
.blog-sidebar__adjacent,
.blog-sidebar__recent-list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.blog-sidebar__adjacent-link {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  text-decoration: none;
  color: inherit;
}

.blog-sidebar__adjacent-thumb {
  flex: 0 0 72px;
  width: 72px;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-card);
  overflow: hidden;
}

.blog-sidebar__adjacent-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.blog-sidebar__adjacent-caption {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}

.blog-sidebar__adjacent-label {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-muted);
}

.blog-sidebar__adjacent-title {
  color: var(--color-text);
  font-weight: 600;
  font-size: 0.9rem;
  line-height: 1.3;
}
