/*
 * Component styles we own.
 *
 * Rules here are ported out of the Frisk copy in style.css and renamed to our
 * convention: BEM, block named after the component, no prefix. Nothing in this
 * file should carry a theme name (`.style2`, `.header-layout2`, `.about-area-14`).
 *
 * Porting a component means: copy its rules here, rename, update the markup,
 * then delete the originals from style.css. That file shrinks with each pass
 * and gets deleted once it is empty — at which point this file can move into
 * app/ and be bundled by Next instead of served from public/.
 *
 * Behaviour hooks are deliberately NOT renamed to BEM. Classes like
 * `.menu-toggle`, `.popup-video`, `.jarallax` and `.wow` are contracts with
 * main.js and its plugins; they stay as bare behavioural classes and carry no
 * styling, so appearance and behaviour stay separable.
 *
 * Depends on the tokens in brand.css — load that first.
 */

/* ==========================================================================
 * Site header
 * Figma: solid Gunmetal bar 106px tall, white logo 144x56 at x=40, nav
 * centred on the viewport, nothing on the right.
 * ========================================================================== */

.site-header {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 41;
}

.site-header__bar {
  background: var(--brand-gunmetal);
  padding: 0 40px;
  transition: 0.4s ease-in-out;
}

/* Applied by main.js past 500px of scroll. */
.site-header__bar--stuck {
  position: fixed;
  top: 0;
  right: 0;
  left: 0;
  box-shadow: 0 0 10px 0 rgb(0 0 0 / 0.07);
  animation: site-header-drop 0.6s ease-in-out;
}

@keyframes site-header-drop {
  0% {
    transform: translate3d(0, -40px, 0) scaleY(0.8);
    opacity: 0.7;
  }
  100% {
    transform: translate3d(0, 0, 0) scaleY(1);
    opacity: 1;
  }
}

.site-header__inner {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 106px;
}

.site-header__logo img {
  height: 56px;
  width: auto;
  /* Render whatever logo is uploaded as a white silhouette. brightness(0)
   * flattens to black first, so this is idempotent — a logo that is already
   * white still comes out white. */
  filter: brightness(0) invert(1);
}

/* Text stand-in for a settings document with no logo uploaded. Sized to sit on
 * the same 56px line as the image it replaces, and it takes its colour from the
 * chrome around it, so it reads on the header's Gunmetal and on the menu
 * panel's white without either needing to know about it. */
.site-logo-text {
  display: inline-block;
  color: currentColor;
  font-family: var(--brand-font-heading);
  font-size: 24px;
  font-weight: 700;
  line-height: 56px;
  letter-spacing: -0.02em;
  white-space: nowrap;
}

/* The menu panel is white, so the logo is flattened to a BLACK silhouette —
 * the exact inverse of the header's, which sits on Gunmetal. The theme only
 * ever styled an inline `svg` here, so an `<img>` arrived unsized: the uploaded
 * wordmark is 2830x1096 and rendered at its natural size until it was given
 * one. */
.mobile-menu-wrapper .mobile-logo img {
  height: 48px;
  width: auto;
  filter: brightness(0);
}

/* The theme gives the panel `height: 100%` and no overflow, which was fine
 * while it held five links. It now carries the footer's columns too, so
 * anything past the fold was clipped with no way to reach it.
 *
 * `overscroll-behavior` keeps a flick at the end of the list from scrolling the
 * page behind the panel. The bottom padding is for iOS Safari, whose toolbar
 * would otherwise sit over the last row. */
.mobile-menu-wrapper .mobile-menu-area {
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-bottom: 40px;
}

/* The theme's own padding on the logo block is 35/50, which is a lot of dead
 * space at the top of a panel that now has real depth to it. */
.mobile-menu-wrapper .mobile-logo {
  padding: 28px 0 24px;
}

/* Secondary nav — the footer's columns.
 *
 * Deliberately quieter than the primary list above it: smaller, tighter, and in
 * the body face rather than the heading one, so the five main destinations
 * still read first on opening the panel. */
/* No rule of its own on top: `.mobile-menu` already ends in a 2px Gunmetal
 * border and 40px of margin, so a border here drew a second line just below
 * the theme's. */
.mobile-menu-more {
  margin: 0 0 28px;
}

.mobile-menu-more__group + .mobile-menu-more__group {
  margin-top: 22px;
}

.mobile-menu-more__title {
  display: block;
  margin: 0 0 8px;
  color: var(--brand-ink, #0a0c00);
  font-family: var(--brand-font-heading);
  font-size: 15px;
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.01em;
  text-transform: uppercase;
}

a.mobile-menu-more__title:hover,
a.mobile-menu-more__title:focus-visible {
  text-decoration: underline;
}

.mobile-menu-more__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.mobile-menu-more__item {
  list-style: none;
}

/* Carries the Footer Navigation document's own "separator above" flag, which is
 * what divides the campuses from the rest of the Visit column. */
.mobile-menu-more__item--separated {
  margin-top: 10px;
  border-top: 1px solid rgba(10, 12, 0, 0.12);
  padding-top: 10px;
}

.mobile-menu-more__link {
  display: block;
  padding: 6px 0;
  color: rgba(10, 12, 0, 0.75);
  font-family: var(--brand-font-body);
  font-size: 15px;
  line-height: 1.4;
}

a.mobile-menu-more__link:hover,
a.mobile-menu-more__link:focus-visible {
  color: var(--brand-ink, #0a0c00);
}

/* The footer's bottom-bar links (Events, Contact, Privacy Policy). Quietest of
 * the three tiers, and wrapped rather than stacked — they are short. */
.mobile-menu-utility {
  margin-bottom: 22px;
}

.mobile-menu-utility ul {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.mobile-menu-utility li {
  list-style: none;
}

.mobile-menu-utility__link {
  color: rgba(10, 12, 0, 0.6);
  font-family: var(--brand-font-body);
  font-size: 13px;
  line-height: 1.4;
}

.mobile-menu-utility__link:hover,
.mobile-menu-utility__link:focus-visible {
  color: var(--brand-ink, #0a0c00);
}

/* Absolute centring rather than flex centring: with the logo in flow, the nav
 * would otherwise centre in the *remaining* space and sit off-axis. The comp
 * puts it dead centre on the viewport. */
.site-header__nav {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
}

.site-header__menu ul {
  margin: 0;
  padding: 0;
}

.site-header__menu > ul {
  display: flex;
  align-items: center;
  margin: 0 -20px;
}

.site-header__menu ul li {
  list-style-type: none;
  display: inline-block;
  position: relative;
}

/* Figma: Rubik 14/600, uppercase, no tracking. The 44.5px vertical padding is
 * what yields the 106px bar height. */
.site-header__menu a {
  display: block;
  position: relative;
  font-family: var(--brand-font-heading);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: normal;
  text-transform: uppercase;
  color: var(--brand-white);
}

.site-header__menu > ul > li > a {
  padding: 44.5px 20px;
}

.site-header__menu > ul > li > a:hover .effect-1 {
  top: -100%;
}

/* --- Mobile toggle ---
 * `.menu-toggle` stays on the element as main.js's hook; the appearance lives
 * here. Frisk's `.sidebar-btn` supplied both and is dropped. */
.site-header__toggle {
  margin-left: auto;
}

.site-header__toggle-btn {
  background: transparent;
  position: relative;
  height: 60px;
  width: 60px;
  padding: 0;
  border: 2px solid var(--brand-white);
  border-radius: 50%;
  text-align: center;
  line-height: 54px;
}

.site-header__toggle-line {
  display: block;
  height: 2px;
  width: 18px;
  margin: auto;
  background: var(--brand-white);
  transition: 0.4s;
}

.site-header__toggle-line:not(:last-child) {
  margin-bottom: 4px;
}

.site-header__toggle-btn:hover .site-header__toggle-line:nth-child(2) {
  width: 10px;
}

@media (max-width: 575px) {
  .site-header__bar {
    padding: 15px 0;
  }
}

@media (max-width: 575px) {
  .site-header__toggle-btn {
    height: 50px;
    width: 50px;
  }
}

/* ==========================================================================
 * Video — "Media & Text" variation
 * Ported from Frisk's about-area-14 (home-14). Figma geometry on the 1440
 * frame: 100px top / 120px bottom padding; media 639x553 flush to the
 * viewport's left edge; copy column the right 633px of the 1296 container,
 * bottom-aligned with the media.
 * ========================================================================== */

.video-split {
  /* Asymmetric on purpose, so the theme's `.space` (a flat --section-space)
   * is deliberately not used here. */
  padding: 100px 0 120px;
  overflow: hidden;
}

.video-split__layout {
  position: relative;
  display: flex;
  justify-content: flex-end;
  align-items: flex-end;
  min-height: 553px;
}

/* The block is capped at 1440 and centred; the media sits flush to its left
 * edge with no gutter, which is the comp's arrangement (media 639x553 flush
 * left, copy the right 633 with a 72px margin). Below 1440 the container is
 * simply full width, so the media stays flush to the viewport edge and the
 * whole block scales — "flush left until it reaches 1440", as the designer put
 * it. Past 1440 it stops growing and centres, so on a wide monitor the media
 * is flush to the *block*, not stranded at the far left of the screen.
 *
 * Two earlier attempts at this were wrong and are worth not repeating: aligning
 * the media to `.container`'s left edge gave it a 12px gutter it should never
 * have, and widening `.container` to 1464 kept that gutter while merely making
 * it wider. The gutter has to go, not move. */
.video-split > .container {
  position: relative;
  max-width: 1440px;
  padding-left: 0;
  padding-right: 0;
}

/* The comp's white panel (Figma "Section - What to Expect" > "img", 1440x577).
 * Section-relative it spans y 196-773 against a 1440x773 frame, so it starts
 * 96px below the media's top edge (100) and runs to the section's bottom. The
 * media overhangs it and the section's own background — an IMAGE fill in the
 * comp, a Section wrapper's job here — peeks through that top strip.
 *
 * Measured from the container, which spans the 553px row: 96px down from its
 * top, and -120px past its bottom to reach through the slice's bottom padding.
 * `.video-split` clips with overflow:hidden, so the bleed stops at the slice.
 *
 * Only one of the five live instances sits on a background at all (/baptism,
 * inside a Section with a Cover image); on the other four the page behind is
 * already white, so this changes nothing there. */
.video-split > .container::before {
  content: "";
  position: absolute;
  top: var(--video-split-panel-top, 96px);
  right: 0;
  bottom: var(--video-split-panel-bleed, -120px);
  left: 0;
  z-index: 0;
  background-color: var(--brand-white);
}

/* 72/1440 — the comp's right margin on the copy. The media is absolute and
   anchored left, so this insets the copy without touching it. */
.video-split__layout {
  padding-right: 5%;
  /* Above the white panel behind it. */
  z-index: 1;
}

.video-split__media {
  position: absolute;
  top: 0;
  left: 0;
  /* 639/1440 of the comp. A percentage of the block, not a vw: sizing the
   * media against the viewport while the copy is sized against the block is
   * what let the two collide between ~992px and ~1400px. */
  width: min(639px, 44.375%);
  aspect-ratio: 639 / 553;
  overflow: hidden;
}

/* The two-class selector is deliberate: the theme's `img:not([draggable])`
 * rule is (0,1,1) and would otherwise win with `height: auto`, letting a
 * portrait upload stretch the box past its aspect-ratio. */
.video-split__media .video-split__thumb {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Coloured band — the `background_color` Select.
 *
 * The slice already draws a panel behind the copy that starts 96px below the
 * block's top edge, so the poster overhangs it. Choosing a fill just repaints
 * that panel, which means the "peek" the About offset variation gets from its
 * 120px band offset is already built in here — nothing has to move.
 *
 * Fills and ink follow CtaBar and the About offset exactly, contrast matrix
 * included: Refresh, Mint and Clay are light enough to keep the default dark
 * ink, while Gunmetal and Ignition need white. Flagstone uses
 * `--brand-flagstone-ink` (#4f7a64) rather than `--brand-flagstone` (#689881)
 * because white on the lighter value is 3.28:1 and fails AA for body text;
 * the deeper one reaches 4.88:1 and still reads as Flagstone.
 *
 * Unset stays on the white panel, so the five instances already authored are
 * untouched.
 */
.video-split--on-colour {
  --video-split-panel: var(--brand-refresh);
  --video-split-ink: var(--brand-ink);
}

.video-split--refresh {
  --video-split-panel: var(--brand-refresh);
}

.video-split--mint {
  --video-split-panel: var(--brand-mint);
}

.video-split--clay {
  --video-split-panel: var(--brand-clay);
}

.video-split--flagstone {
  --video-split-panel: var(--brand-flagstone-ink);
  --video-split-ink: var(--brand-white);
}

.video-split--gunmetal {
  --video-split-panel: var(--brand-gunmetal);
  --video-split-ink: var(--brand-white);
}

.video-split--ignition {
  --video-split-panel: var(--brand-ignition);
  --video-split-ink: var(--brand-white);
}

.video-split--on-colour > .container::before {
  background-color: var(--video-split-panel);
}

.video-split--on-colour .video-split__title,
.video-split--on-colour .video-split__text {
  color: var(--video-split-ink);
}

/* Full width — the `full_width` toggle on the slice.
 *
 * Same arrangement the About offset variation uses, so a full-width Video and
 * a full-width About read as one family down a page: the block loses its 1440
 * cap so the media bleeds to the viewport edge, and the copy is then held to
 * the site container so it does not stretch into one very long line on a wide
 * monitor.
 *
 * Scoped past 1440, which is the only place the toggle can mean anything: at
 * or below it the block already fills the viewport, so constrained and full
 * must render identically. Scoping this to 992 instead was wrong in both
 * directions — the centred frame shifted the poster 48px right at 1440 and
 * shrank it at 1200, and below 992 the media override beat the stacked
 * `width: 100%` and cut the poster to 44% of a phone.
 *
 * The media keeps its 639x553 comp size and is centred in the space left of
 * the copy rather than growing with the band. Letting it scale with the
 * viewport was the first attempt and it does not survive a wide monitor: at
 * 3200 the poster came out 1420x1229, taller than the copy beside it and far
 * past anything the source image can carry. So the frame stretches — it spans
 * the full row height and runs from the viewport edge to exactly where the
 * copy starts — and the thumbnail sits in the middle of it. The play button is
 * already centred on the frame, so it follows the thumbnail for free.
 *
 * The frame's right edge is the layout's own right inset plus the copy's
 * 633px. Percentages on an absolutely positioned box resolve against the
 * containing block's padding box, which here is the full-width container, so
 * this lands on the copy's left edge exactly.
 *
 * The copy is inset by widening the layout's own right padding to whatever the
 * container inset is, rather than by giving the copy a margin. Both the
 * padding and the copy's `min(633px, 46.272%)` then resolve against the same
 * box — the two-denominators trap this slice already documents above. A margin
 * on the copy looked equivalent and was not: its percentage resolved against
 * the already-padded box, which left the copy 43px past the container's edge
 * at 1920.
 *
 * The result is identical to the constrained arrangement at every width up to
 * 1440 and only diverges past it, which is what a layout toggle should do.
 */
@media (min-width: 1441px) {
  .video-split--full > .container {
    max-width: none;
  }

  .video-split--full .video-split__media {
    top: 0;
    bottom: 0;
    right: calc(max(5%, (100% - var(--main-container)) / 2) + 633px);
    width: auto;
    aspect-ratio: auto;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  /* On a coloured band the poster returns to the top of the row so it
     overhangs the fill — the peek is the whole point of the colour, and a
     vertically centred poster would sit inside the band instead. */
  .video-split--full.video-split--on-colour .video-split__media {
    align-items: flex-start;
  }

  /* Three classes, to beat the `.video-split__media .video-split__thumb` rule
     that stretches the image to fill its frame. */
  .video-split--full .video-split__media .video-split__thumb {
    width: min(639px, 100%);
    height: auto;
    aspect-ratio: 639 / 553;
  }

  .video-split--full .video-split__layout {
    padding-right: max(5%, calc((100% - var(--main-container)) / 2));
  }
}

/* Icon is exported from the Figma component `play-circle-outline` (120px on
 * the 1440 comp). Figma bakes the mark at 70% white; that opacity lives here
 * instead so hover can lift it to full. */
.video-split__play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: block;
  width: clamp(72px, 8.3vw, 120px);
  opacity: 0.7;
  transition: opacity ease 0.3s;
}

.video-split__play:hover {
  opacity: 1;
}

.video-split__play img {
  display: block;
  width: 100%;
  height: auto;
}

/* 633/1296 of the container. Proportional rather than fixed so it shrinks in
 * step with the media and never gets overlapped by it. */
.video-split__content {
  flex: 0 0 auto;
  /* 633/1368, not 633/1440. The copy is a normal flex item, so its percentage
   * resolves against the layout's *content* box — which the 5% right padding
   * has already narrowed to 1368. The media is absolutely positioned, so its
   * 44.375% resolves against the padding box (the full 1440). Same comp, two
   * different denominators. */
  width: min(633px, 46.272%);
}

/* Figma: Rubik 61/700 uppercase, 110% leading, -1.8px tracking (the Heading 3
 * token), then 24px gaps down to the paragraph and the button. */
.video-split__title {
  font-family: var(--brand-font-heading);
  font-size: clamp(32px, 4.24vw, 61px);
  font-weight: var(--brand-fw-heading-bold);
  line-height: 1.1;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  color: var(--brand-ink);
  margin-bottom: 0;
}

.video-split__text {
  margin-top: 24px;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  font-weight: var(--brand-fw-body);
  line-height: var(--brand-lh-body);
}

.video-split__text p:last-child {
  margin-bottom: 0;
}

.video-split .btn-wrap {
  margin-top: 24px;
}

/* Text alignment — Left is the default and needs no rule. The flex .btn-wrap
   follows via justify-content rather than text-align. */
.video-split--align-center .video-split__content {
  text-align: center;
}
.video-split--align-center .btn-wrap {
  justify-content: center;
}
.video-split--align-right .video-split__content {
  text-align: right;
}
.video-split--align-right .btn-wrap {
  justify-content: flex-end;
}

@media (max-width: 991px) {
  .video-split {
    padding: 80px 0;
  }

  .video-split__layout {
    display: block;
    min-height: 0;
  }

  /* The block carries no container padding now, so the media runs edge to edge
     here (as it does on the Form slice) and the copy brings its own gutter. */
  .video-split__layout {
    padding-right: 0;
  }

  /* Bottom padding drops to 80px at this width, so the panel's bleed follows
     it — otherwise it would overshoot and get clipped mid-section. */
  .video-split {
    --video-split-panel-bleed: -80px;
  }

  .video-split__media {
    position: relative;
    left: auto;
    width: 100%;
    margin-bottom: 40px;
  }

  .video-split__content {
    width: 100%;
    padding: 0 20px;
  }

}

/* ==========================================================================
 * LatestMessage — "Current Series"
 * Ported from Frisk's portfolio-wrap style15 (home-15 "Latest Projects").
 * Figma geometry on the 1440 frame: 722px tall (120px spacers top and bottom
 * around a 482px band); thumbnail 856x482 at the container's left edge; copy
 * column 384px at the right edge, vertically centred against the thumbnail.
 * ========================================================================== */

.latest-message {
  position: relative;
  padding: 120px 0;
  /* Fallback for an empty background_image — the comp's texture averages
   * #0a4049 with barely any variation, so a flat fill reads almost the same. */
  background-color: var(--brand-surface-dark-alt);
  overflow: hidden;
}

.latest-message__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.latest-message .container {
  position: relative;
  z-index: 1;
}

.latest-message__layout {
  display: flex;
  align-items: center;
  gap: 56px; /* 984 - (72 + 856) */
}

.latest-message__media {
  position: relative;
  display: block;
  flex: 0 0 auto;
  /* 856/1296 of the container, so it shrinks in step with the copy column. */
  width: min(856px, 66%);
  aspect-ratio: 856 / 482;
  overflow: hidden;
}

/* Two-class selector: the theme's `img:not([draggable])` is (0,1,1) and would
 * otherwise win with `height: auto`, letting the upload stretch the box. */
.latest-message__media .latest-message__thumb {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.latest-message__play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: clamp(72px, 8.3vw, 120px);
  height: auto;
  opacity: 0.7;
  transition: opacity ease 0.3s;
}

.latest-message__media:hover .latest-message__play {
  opacity: 1;
}

.latest-message__body {
  flex: 0 0 auto;
  width: min(384px, 29.6%);
}

/* Figma: Rubik 31/500, 110% leading, -1.8px tracking (the Heading 5 token). */
.latest-message__title {
  margin: 24px 0 0;
  font-family: var(--brand-font-heading);
  font-size: clamp(24px, 2.15vw, 31px);
  font-weight: var(--brand-fw-heading);
  line-height: 1.1;
  letter-spacing: -0.058em;
  color: var(--brand-white);
}

.latest-message__title a {
  color: inherit;
}

.latest-message__speaker {
  margin: 8px 0 0;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  font-weight: var(--brand-fw-body-medium);
  line-height: var(--brand-lh-body);
  color: var(--brand-refresh);
}

.latest-message__actions {
  margin-top: 40px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
}

/* The comp runs this button the full width of the copy column. */
.latest-message__series-btn {
  display: block;
  width: 100%;
  text-align: center;
}

@media (max-width: 991px) {
  .latest-message {
    padding: 80px 0;
  }

  .latest-message__layout {
    display: block;
  }

  .latest-message__media {
    width: 100%;
    margin-bottom: 40px;
  }

  .latest-message__body {
    width: 100%;
  }
}

/* ==========================================================================
 * Eyebrow — the "//// LABEL" kicker above a heading.
 *
 * Colour is section-dependent, so it is a modifier rather than a context
 * selector: Refresh on the dark bands, Flagstone in Where to Start, Ignition
 * in Locations. The leading slashes are decorative and belong here rather
 * than in each content field.
 * ========================================================================== */

.eyebrow {
  display: block;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-eyebrow);
  font-weight: var(--brand-fw-body-medium);
  line-height: var(--brand-lh-heading);
  letter-spacing: var(--brand-ls-eyebrow);
  text-transform: uppercase;
  color: var(--brand-refresh);
}

.eyebrow::before {
  content: "//// ";
}

.eyebrow--sage {
  color: var(--brand-flagstone);
}

.eyebrow--rust {
  color: var(--brand-ignition);
}

.eyebrow--sm {
  font-size: var(--brand-fs-eyebrow-s);
  font-weight: var(--brand-fw-link);
}

/* Slashes on both sides, for the centred eyebrows. In the comps this tracks
 * alignment rather than section: both centred eyebrows (home 158:420 and Visit
 * 291:549, the Locations sub-heading on each) are flanked, while all eleven
 * left-aligned ones carry only the leading mark. Figma sets three spaces either
 * side here against one on the left-aligned variant; CSS `content` preserves
 * them, so they are written literally. */
.eyebrow--flanked::before {
  content: "////   ";
}

.eyebrow--flanked::after {
  content: "   ////";
}

/* ==========================================================================
 * Feature — "Where would you like to start?"
 * Figma geometry on the 1440 frame: 3035px tall; 120px spacers around a 67px
 * centred heading; four 562px rows 120px apart. Each row is a 770px photo and
 * a 416px copy column with a 110px gutter, alternating sides.
 * ========================================================================== */

/* No background here — the comp's hex tile sits behind this section and behind
 * What to Expect, so it is owned by a wrapping Section instead. Figma tiles it
 * at scalingFactor 0.5, i.e. a 960px tile width from a 1920px source. */
.feature {
  padding: 120px 0;
}

.feature__heading {
  margin: 0 0 120px;
  text-align: center;
  font-family: var(--brand-font-heading);
  font-size: clamp(32px, 4.24vw, 61px);
  font-weight: var(--brand-fw-heading-bold);
  line-height: 1.1;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  color: var(--brand-ink);
}

.feature__list {
  display: flex;
  flex-direction: column;
  gap: 120px;
}

/* 59.4% + 8.49% + 32.1% = the comp's 770 / 110 / 416 across a 1296 container. */
.feature__row {
  display: flex;
  align-items: center;
  gap: 8.49%;
}

.feature__row--reversed {
  flex-direction: row-reverse;
}

/* Figma puts a 30px left-only stroke on the image frame, aligned INSIDE — so
 * with border-box the accent eats into the 770px rather than widening it,
 * which is what the comp shows. The colour cycles through the palette; see
 * ACCENT_CYCLE in the component for the unset fallback. */
.feature__media {
  flex: 0 0 59.4%;
  aspect-ratio: 770 / 562;
  overflow: hidden;
  border-left: 30px solid var(--feature-accent, transparent);
}

.feature__media--refresh {
  --feature-accent: var(--brand-refresh);
}

.feature__media--ignition {
  --feature-accent: var(--brand-ignition);
}

.feature__media--flagstone {
  --feature-accent: var(--brand-flagstone);
}

.feature__media--clay {
  --feature-accent: var(--brand-clay);
}

/* Two-class selector: the theme's `img:not([draggable])` is (0,1,1) and would
 * otherwise win with `height: auto`, letting the upload stretch the box. */
.feature__media .feature__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.feature__body {
  flex: 0 0 32.1%;
}

/* Optional card paragraph. The title carries a 24px bottom margin for the case
   where the link follows it directly; when copy sits between them that gap
   belongs above the paragraph instead, so the title's margin collapses here. */
.feature__title:not(:last-child) + .feature__text {
  margin-top: 0;
}

/* Ink, not Ignition. The rust reads well on a one-line intro — which is where
   the comps use it — but across a full paragraph it fights the heading and
   tires the eye. Body copy on light uses the same token as every other
   paragraph on the site. */
.feature__text {
  max-width: 46ch;
  margin: 0 0 28px;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  font-weight: 400;
  line-height: var(--brand-lh-body);
  letter-spacing: normal;
  color: var(--brand-ink);
}

.feature__title {
  margin: 16px 0 24px;
  font-family: var(--brand-font-heading);
  font-size: clamp(28px, 4.24vw, 61px);
  font-weight: var(--brand-fw-heading-bold);
  line-height: 1.1;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  color: var(--brand-ink);
}

@media (max-width: 991px) {
  .feature {
    padding: 80px 0;
  }

  .feature__heading {
    margin-bottom: 60px;
  }

  .feature__list {
    gap: 60px;
  }

  .feature__row,
  .feature__row--reversed {
    flex-direction: column;
    gap: 32px;
  }

  .feature__media,
  .feature__body {
    flex: 0 0 auto;
    width: 100%;
  }
}

/* ==========================================================================
 * Section / Spacer — layout primitives
 *
 * Section is a background wrapper produced by SectionZone, which nests the
 * slices between a Section and its SectionEnd. All of its background
 * properties are set inline from the slice's fields, so there is nothing to
 * declare here beyond making it a containing block.
 * ========================================================================== */

.section {
  position: relative;
}

.spacer {
  height: var(--spacer-height, 120px);
}

@media (max-width: 991px) {
  .spacer {
    height: var(--spacer-height-mobile, var(--spacer-height, 120px));
  }
}

/* ==========================================================================
 * Testimonials — "Stories of Life Change"
 * Figma geometry on the 1440 frame: 1030px tall; 120px padding; a 67px centred
 * heading; cards 526px wide with a 24px gutter, 48px inner padding, top
 * aligned and free to differ in height; dots 72px under the tallest card.
 * ========================================================================== */

.testimonials {
  padding: 120px 0;
  background-color: var(--brand-surface-muted);
  overflow: hidden;
}

/* This section is the one place the comps set headings in Black Coffee rather
 * than Gunmetal — it is the warm-toned band. */
.testimonials__heading {
  margin: 0 0 72px;
  text-align: center;
  font-family: var(--brand-font-heading);
  font-size: clamp(32px, 4.24vw, 61px);
  font-weight: var(--brand-fw-heading-bold);
  line-height: 1.1;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  color: var(--brand-ink-warm);
}

/* Two full cards plus a peek, matching the comp's 184px left offset on a
 * 1440 viewport: (1440 - (526*2 + 24)) / 2 = 182.
 *
 * The offset goes on .slick-list rather than the carousel element so it moves
 * the slides only — .slick-dots is a sibling of .slick-list, and offsetting
 * the whole element would drag the dots off-centre with it. */
/* Aligns the first card with the heading above it rather than centring a pair.
 *
 * This was `(100vw - 1076px) / 2`, and 1076 is 526 + 24 + 526 — two cards plus
 * their gutter. Centring that pair leaves the first card hanging off the
 * container's left edge, which reads as clipped. 1296 is the site's inner
 * container width, so the first card now starts exactly where the heading does
 * and runs off to the right instead. */
.testimonials__track .slick-list {
  padding-left: max(24px, calc((100vw - 1296px) / 2));
}

.testimonials__slide {
  width: 526px;
  max-width: calc(100vw - 48px);
  margin-right: 24px;
}

/* slick stretches slides to equal height by default; the comp lets each card
 * end with its own content. */
.testimonials__track .slick-track {
  display: flex;
  align-items: flex-start;
}

.testimonial-card {
  background-color: var(--brand-white);
  padding: 48px;
}

.testimonial-card__portrait {
  display: block;
  width: 110px;
  height: 110px;
  object-fit: cover;
}

.testimonial-card__quote {
  margin-top: 24px;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  font-weight: var(--brand-fw-body);
  line-height: var(--brand-lh-body);
  color: var(--brand-ink-warm);
}

.testimonial-card__quote p:last-child {
  margin-bottom: 0;
}

.testimonial-card__author {
  margin: 40px 0 0;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-eyebrow);
  font-weight: var(--brand-fw-body-medium);
  line-height: 1.2;
  text-transform: uppercase;
  color: var(--brand-ink-warm);
}

.testimonial-card__listen {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 40px;
}

/* Same masking trick as .link-btn__arrow: the exported icon is a fixed white,
 * so masking lets it take the button's currentColor instead. */
.btn__icon-play {
  display: inline-block;
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  background-color: currentColor;
  -webkit-mask: url("/assets/img/icons/play-circle.svg") no-repeat center /
    contain;
  mask: url("/assets/img/icons/play-circle.svg") no-repeat center / contain;
}

/* --- Carousel dots --- */
.testimonials__track .slick-dots {
  display: flex;
  justify-content: center;
  gap: 12px;
  margin: 72px 0 0;
  padding: 0;
  list-style: none;
}

.testimonials__track .slick-dots li {
  width: 10px;
  height: 10px;
}

.testimonials__track .slick-dots button {
  width: 10px;
  height: 10px;
  padding: 0;
  border: 1px solid var(--brand-ink-warm);
  border-radius: 50%;
  background: transparent;
  font-size: 0;
  color: transparent;
}

.testimonials__track .slick-dots .slick-active button {
  background: var(--brand-ink-warm);
}

@media (max-width: 991px) {
  .testimonials {
    padding: 80px 0;
  }

  .testimonials__heading {
    margin-bottom: 48px;
  }

  .testimonial-card {
    padding: 32px;
  }
}

/* ==========================================================================
 * Locations — "Find a Campus Near You"
 * Figma on the 1440 frame: 1034px tall (120px spacers); centred heading with
 * the eyebrow *below* it; four 306x431 Flagstone cards on a 24px gutter; a
 * full-width Refresh bar for the online service.
 * ========================================================================== */

.campuses {
  padding: 120px 0;
}

.campuses__heading {
  margin: 0;
  text-align: center;
  font-family: var(--brand-font-heading);
  font-size: clamp(32px, 4.24vw, 61px);
  font-weight: var(--brand-fw-heading-bold);
  line-height: 1.1;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  color: var(--brand-ink);
}

.campuses__eyebrow {
  margin-top: 24px;
  text-align: center;
}

.campuses__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 24px;
  margin-top: 80px;
}

.campus-card {
  background-color: var(--brand-flagstone);
  display: flex;
  flex-direction: column;
}

.campus-card__media {
  aspect-ratio: 306 / 249;
  overflow: hidden;
}

/* Two-class selector: the theme's `img:not([draggable])` is (0,1,1) and would
 * otherwise win with `height: auto`. */
.campus-card__media .campus-card__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.campus-card__body {
  padding: 40px 24px;
}

.campus-card__name {
  margin: 0;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-h6);
  font-weight: var(--brand-fw-heading);
  line-height: 1.2;
  text-transform: uppercase;
  color: var(--brand-ink);
}

.campus-card__times {
  margin: 16px 0 0;
  font-family: var(--brand-font-heading);
  font-size: 13px;
  font-weight: var(--brand-fw-link);
  line-height: 1.1;
  letter-spacing: var(--brand-ls-eyebrow);
  text-transform: uppercase;
  color: var(--brand-ink);
}

/* Smaller and lighter than the site-wide text link — the comp sets these at
 * 12/600 with 0.72 tracking, in white against the Flagstone card.
 *
 * The element now also carries `.link-btn`, so the underline is the theme's
 * sweep (a pair of 2px pseudo-elements that trade places on hover) rather than
 * `text-decoration`. That is the treatment CtaBar uses, and the reason the
 * designer flagged this one: `text-decoration` is never drawn through an
 * inline-block, and the arrow is one — so the rule stopped short of the arrow
 * here while CtaBar's bar ran under it. The pseudo-elements are the anchor's
 * full width, arrow included.
 *
 * Only the type scale stays local; that is the comp's, not an inconsistency. */
.campus-card__link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 27px;
  padding-bottom: 6px;
  font-family: var(--brand-font-heading);
  font-size: 12px;
  font-weight: var(--brand-fw-link);
  letter-spacing: 0.72px;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--brand-white);
}

.campus-card__link:hover {
  color: var(--brand-white);
}

/* The theme paints the sweep with `--title-color`, which is dark. These sit in
   white on a Flagstone card, so the bar follows the link's own colour. */
.campus-card__link::before,
.campus-card__link::after {
  background-color: currentColor;
}

/* ==========================================================================
 * CtaBar — a full-width coloured bar with a lockup, title and arrow links.
 * Split out of Locations so it can be reused on interior pages. Colour is a
 * palette modifier rather than a stored hex, so it follows a rebrand.
 * ========================================================================== */

.cta-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 40px;
  padding: 40px;
  background-color: var(--cta-bar-bg, var(--brand-refresh));
  color: var(--cta-bar-ink, var(--brand-ink));
}

.cta-bar--refresh {
  --cta-bar-bg: var(--brand-refresh);
}

.cta-bar--clay {
  --cta-bar-bg: var(--brand-clay);
}

/* Flagstone takes light ink here for the same reason it does on the About
 * offset band, and is kept in step with it deliberately: white on
 * `--brand-flagstone` (#689881) is 3.28:1, which fails AA for body text, while
 * `--brand-flagstone-ink` (#4f7a64) reaches 4.88:1 and still reads as
 * Flagstone. `__title`, `__subtitle` and `__link` are all `color: inherit`, so
 * they follow the ink without further rules. */
.cta-bar--flagstone {
  --cta-bar-bg: var(--brand-flagstone-ink);
  --cta-bar-ink: var(--brand-white);
}

/* The two dark fills need light ink to stay legible. */
.cta-bar--gunmetal {
  --cta-bar-bg: var(--brand-gunmetal);
  --cta-bar-ink: var(--brand-white);
}

.cta-bar--ignition {
  --cta-bar-bg: var(--brand-ignition);
  --cta-bar-ink: var(--brand-white);
}

.cta-bar__lead {
  display: flex;
  align-items: center;
  gap: 32px;
}

.cta-bar__logo {
  width: 55px;
  height: auto;
}

.cta-bar__title {
  margin: 0;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-h5);
  font-weight: var(--brand-fw-heading);
  line-height: 1.2;
  text-transform: uppercase;
  color: inherit;
}

.cta-bar__subtitle {
  margin: 14px 0 0;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-eyebrow);
  font-weight: var(--brand-fw-body-medium);
  line-height: 1.1;
  letter-spacing: var(--brand-ls-eyebrow);
  text-transform: uppercase;
  color: inherit;
}

.cta-bar__links {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 24px;
}

.cta-bar__link,
.cta-bar__link:hover {
  color: inherit;
}

/* On the homepage the bar sits tight under the campus grid — the comp's 24px
 * gap — so the two live inside one Section rather than being spaced apart. */
.campuses + .cta-bar-wrap {
  margin-top: -96px;
}

@media (max-width: 767px) {
  .cta-bar {
    flex-direction: column;
    align-items: flex-start;
    padding: 32px;
  }
}

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

@media (max-width: 767px) {
  .campuses {
    padding: 80px 0;
  }

  .campuses__grid {
    grid-template-columns: 1fr;
    margin-top: 48px;
  }
}

/* ==========================================================================
 * Mobile App — "Already Part of the Family? Download the app."
 * Figma 96:204 on the 1440 frame: 809px tall (120px spacers around a 569px
 * row); a 770x562 photo, a 110px gutter and a 416px copy column, centred on
 * each other. The comp draws a 1px Refresh stroke on the image frame but
 * switches it off, so there is deliberately no border here.
 *
 * No background: the Gunmetal belongs to a wrapping Section. That is also why
 * the ink is hard-coded to the on-dark pair (Flagstone heading, Refresh copy)
 * instead of reading a surface token.
 * ========================================================================== */

.mobile-app {
  padding: 120px 0;
}

/* Same 59.4% / 8.49% / 32.1% split as Feature's rows — 770 / 110 / 416 across
 * the 1296 container. */
.mobile-app__layout {
  display: flex;
  align-items: center;
  gap: 8.49%;
}

.mobile-app__media {
  flex: 0 0 59.4%;
  aspect-ratio: 770 / 562;
  overflow: hidden;
}

/* Two-class selector: the theme's `img:not([draggable])` is (0,1,1) and would
 * otherwise win with `height: auto`, letting the upload stretch the box. */
.mobile-app__media .mobile-app__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.mobile-app__body {
  flex: 0 0 32.1%;
}

/* The comp stacks eyebrow / heading / copy on a 16px rhythm, then leaves 24px
 * before the badges. */
.mobile-app__title {
  margin: 16px 0 0;
  font-family: var(--brand-font-heading);
  font-size: clamp(32px, 4.24vw, 61px);
  font-weight: var(--brand-fw-heading-bold);
  line-height: 1.1;
  /* Figma says -1.8px, but that is a fixed value against a clamped font size —
   * at the 32px floor it reads as -0.056em and the words crowd. -0.03em is
   * -1.83px at 61px (indistinguishable at the comp size) and scales down with
   * the heading, which is what Feature and Locations already do. */
  letter-spacing: -0.03em;
  text-transform: uppercase;
  color: var(--brand-flagstone-ink);
}

.mobile-app__text {
  margin-top: 16px;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  font-weight: var(--brand-fw-body);
  line-height: var(--brand-lh-body);
  color: var(--brand-refresh);
}

/* line-height has to be restated, not inherited: style.css carries a bare
 * `p { line-height: 1.75 }`, and any rule that matches the element beats a
 * value inherited from its parent no matter how specific the parent's is.
 * Left inherited, the copy sets at 35px instead of the comp's 32px. */
.mobile-app__text p {
  margin: 0;
  line-height: var(--brand-lh-body);
  color: inherit;
}

.mobile-app__stores {
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
  margin-top: 24px;
}

/* 12px/16px padding around a 40px-tall lockup gives the comp's 64px pill. */
.mobile-app__store {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 12px 16px;
  border-radius: 4px;
  background-color: var(--brand-white);
}

.mobile-app__store .mobile-app__badge {
  display: block;
  height: 40px;
  width: auto;
}

@media (max-width: 991px) {
  .mobile-app {
    padding: 80px 0;
  }

  .mobile-app__layout {
    flex-direction: column;
    gap: 32px;
  }

  .mobile-app__media,
  .mobile-app__body {
    flex: 0 0 auto;
    width: 100%;
  }
}

/* ==========================================================================
 * Cta / statement — the mission statement
 * Figma 7:3772 on the 1440 frame: 628px tall (180px top and bottom); a 304x161
 * mark, a 120px gutter and an 872px copy column (56px right padding, so the
 * text measures 816), both top-aligned.
 *
 * The 60% black wash IS this slice's own, unlike every other background in the
 * library: it is a legibility scrim over whatever photo the wrapping Section
 * supplies, not a section background. Keeping it here lets one Section image
 * run behind this slice and its neighbours while only this copy gets darkened.
 * ========================================================================== */

.cta-statement {
  position: relative;
  padding: 180px 0;
  /* The wash. Kept on the element itself for the no-image case, where the
     photograph belongs to a wrapping Section painting behind this. */
  background-color: rgb(0 0 0 / 60%);
  overflow: hidden;
}

/* --- Parallax background (theme cta-area-3, home-18) ---------------------
 * With a slice-level image the layering has to change: an absolutely
 * positioned photo would paint *over* a background-color on the same element,
 * so the wash moves to a pseudo-element stacked between the photo and the
 * copy. jarallax transforms this wrapper, hence `overflow: hidden` above. */
.cta-statement--has-image {
  background-color: transparent;
}

.cta-statement__media {
  position: absolute;
  inset: 0;
  z-index: 0;
}

/* Two-class selector so it beats the theme's `img:not([draggable])` at (0,1,1),
   which would otherwise let the upload set its own height. */
.cta-statement__media .cta-statement__bg {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.cta-statement--has-image::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background-color: rgb(0 0 0 / 60%);
}

.cta-statement > .container {
  position: relative;
  z-index: 2;
}

/* 23.46% + 9.26% + 67.28% = the comp's 304 / 120 / 872 across a 1296 container. */
.cta-statement__layout {
  display: flex;
  align-items: flex-start;
  gap: 9.26%;
}

.cta-statement__mark {
  flex: 0 0 23.46%;
  width: 23.46%;
  height: auto;
}

.cta-statement__text {
  flex: 0 0 67.28%;
  margin: 0;
  padding-right: 56px;
  font-family: var(--brand-font-heading);
  font-size: clamp(32px, 4.24vw, 61px);
  font-weight: var(--brand-fw-heading-bold);
  /* Restated, not inherited: style.css has a bare `p { line-height: 1.75 }`
   * and an element rule beats an inherited value. */
  line-height: 1.1;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  color: var(--brand-white);
}

@media (max-width: 991px) {
  .cta-statement {
    padding: 100px 0;
  }

  .cta-statement__layout {
    flex-direction: column;
    gap: 40px;
  }

  .cta-statement__mark {
    flex: 0 0 auto;
    width: 240px;
    max-width: 100%;
  }

  .cta-statement__text {
    flex: 0 0 auto;
    width: 100%;
    padding-right: 0;
  }
}

/* ==========================================================================
 * Site footer
 * Figma 191:400 on the 1440 frame: 591px tall on Gunmetal. A 64px-padded row
 * holds a 306px brand column (logo, social, newsletter) beside a 966px block
 * of five link columns that starts 88px lower; a bottom bar with a 1px hairline
 * closes it.
 *
 * The bottom bar is inset further than the columns above it (1232 vs 1296) —
 * that is what the comp draws, not a mistake in the port.
 * ========================================================================== */

.site-footer {
  background-color: var(--brand-gunmetal);
  color: var(--brand-white);
}

/* 23.61% + 1.85% + 74.54% = the comp's 306 / 24 / 966 across a 1296 container.
 * The brand column is centred against the taller link block, as in Figma. */
.site-footer__top {
  display: flex;
  align-items: center;
  gap: 1.85%;
  padding: 64px 0;
}

.site-footer__brand {
  flex: 0 0 23.61%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 24px;
  padding-right: 40px;
}

.site-footer__logo {
  display: block;
}

/* Same idempotent white-out as the header: brightness(0) flattens to black
 * first, so a logo that is already white still comes out white. */
.site-footer__logo img {
  height: 56px;
  width: auto;
  filter: brightness(0) invert(1);
}

/* --- Social --- */
.site-footer__social {
  display: flex;
  gap: 12px;
  margin: 0;
  padding: 0 0 32px;
  list-style: none;
}

.site-footer__social-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 1px solid rgb(255 255 255 / 20%);
  border-radius: 50%;
  font-size: 16px;
  color: var(--brand-white);
  transition: background-color ease 0.3s, color ease 0.3s,
    border-color ease 0.3s;
}

.site-footer__social-link:hover {
  background-color: var(--brand-refresh);
  border-color: var(--brand-refresh);
  color: var(--brand-gunmetal);
}

/* --- Newsletter --- */
.site-footer__newsletter {
  width: 100%;
}

.site-footer__newsletter-heading {
  margin: 0 0 24px;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-eyebrow);
  font-weight: var(--brand-fw-link);
  line-height: 1.185;
  letter-spacing: var(--brand-ls-link);
  text-transform: uppercase;
  color: var(--brand-refresh);
}

/* The comp draws the field as a rule, not a box: a 2px bottom border only. */
.site-footer__input {
  display: block;
  width: 100%;
  height: 55px;
  padding: 12.5px 0;
  background: transparent;
  border: 0;
  border-bottom: 2px solid var(--brand-white);
  border-radius: 0;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  font-weight: var(--brand-fw-body-medium);
  line-height: 1.6;
  color: var(--brand-white);
}

.site-footer__input::placeholder {
  color: var(--brand-white);
  opacity: 1;
}

.site-footer__input:focus {
  outline: none;
  border-bottom-color: var(--brand-refresh);
}

/* Off-canvas rather than display:none — some bots skip hidden inputs. */
.site-footer__honeypot {
  position: absolute;
  left: -5000px;
  overflow: hidden;
}

/* Success replaces the whole form, so it inherits the block's own top spacing;
 * the error sits between field and button and needs its own. */
.site-footer__success,
.site-footer__error {
  margin: 0;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  font-weight: var(--brand-fw-body-medium);
  line-height: 1.6;
  color: var(--brand-white);
}

.site-footer__error {
  margin-top: 12px;
  color: var(--brand-refresh);
}

.site-footer__submit:disabled {
  cursor: default;
  opacity: 0.7;
}

.site-footer__submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 40px;
  margin-top: 24px;
  /* Figma's 24px padding is measured from an INSIDE-aligned 2px stroke, so the
   * stroke eats into it — 22 + 2 reproduces the comp's 114px outer width. */
  padding: 0 22px;
  background: transparent;
  border: 2px solid var(--brand-refresh);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-eyebrow);
  font-weight: var(--brand-fw-link);
  letter-spacing: var(--brand-ls-link);
  text-transform: uppercase;
  color: var(--brand-refresh);
  transition: background-color ease 0.3s, color ease 0.3s;
}

.site-footer__submit:hover {
  background-color: var(--brand-refresh);
  color: var(--brand-gunmetal);
}

/* --- Link columns --- */
.site-footer__columns {
  flex: 1 1 74.54%;
  align-self: stretch;
  display: flex;
  justify-content: space-between;
  gap: 24px;
  padding-top: 88px;
}

/* Content-sized, so `space-between` distributes the slack rather than the
 * columns shrinking and wrapping their link text. */
.site-footer__column {
  flex: 0 0 auto;
}

.site-footer__column-title {
  display: block;
  margin: 0;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-eyebrow);
  font-weight: var(--brand-fw-link);
  line-height: 1.185;
  letter-spacing: var(--brand-ls-link);
  text-transform: uppercase;
  color: var(--brand-refresh);
}

.site-footer__list {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin: 16px 0 0;
  padding: 0;
  list-style: none;
}

/* Typography sits on the <li>, not just the <a> inside it: the list item would
 * otherwise keep the inherited 16px/1.6 body metrics and each row would stand
 * 25.6px tall instead of the comp's 22.4, silently inflating the whole footer. */
.site-footer__item {
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  font-weight: var(--brand-fw-body-medium);
  line-height: 1.6;
}

/* 12px gap + 1px rule + 12px padding reproduces the comp's 25px break, without
 * needing a divider element in the markup. */
.site-footer__item--separated {
  border-top: 1px solid rgb(255 255 255 / 20%);
  padding-top: 12px;
}

.site-footer__link {
  font: inherit;
  color: var(--brand-white);
  transition: color ease 0.3s;
}

a.site-footer__link:hover {
  color: var(--brand-refresh);
}

/* --- Bottom bar --- */
.site-footer__bar {
  border-top: 1px solid rgb(255 255 255 / 10%);
}

.site-footer__bar-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  max-width: 1280px;
  margin: 0 auto;
  padding: 24px;
}

.site-footer__copyright,
.site-footer__copyright p {
  margin: 0;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  font-weight: var(--brand-fw-body);
  line-height: 1.43;
  color: var(--brand-clay);
}

.site-footer__utility {
  display: flex;
  gap: 40px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.site-footer__utility-link {
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-base);
  font-weight: var(--brand-fw-body-medium);
  line-height: 1.6;
  color: var(--brand-refresh);
  transition: color ease 0.3s;
}

.site-footer__utility-link:hover {
  color: var(--brand-white);
}

@media (max-width: 1199px) {
  .site-footer__top {
    flex-direction: column;
    align-items: stretch;
    gap: 56px;
  }

  .site-footer__brand {
    flex: 0 0 auto;
    padding-right: 0;
  }

  .site-footer__columns {
    flex: 0 0 auto;
    flex-wrap: wrap;
    justify-content: flex-start;
    gap: 40px 56px;
    padding-top: 0;
  }
}

@media (max-width: 767px) {
  .site-footer__top {
    padding: 56px 0;
  }

  .site-footer__columns {
    gap: 32px 40px;
  }

  .site-footer__bar-inner {
    flex-direction: column;
    align-items: flex-start;
    gap: 16px;
  }

  .site-footer__utility {
    flex-wrap: wrap;
    gap: 16px 24px;
  }
}

/* ==========================================================================
 * Hero — banner variation (Hero/default, theme home-16)
 * Figma: Visit 291:848. Hero 1440x1120. A Gunmetal band covers the top 592
 * and is fully opaque, so it is a band rather than a scrim; the image runs
 * from y=96 to the hero's bottom edge (1024 tall) and is only visible below
 * the band. The badge's centre sits exactly on y=592 — the band's bottom
 * edge — which is what pins the band height to 592 of 1120.
 * ========================================================================== */

.hero-banner {
  --hero-banner-height: 1120px;
  --hero-banner-band: 592px;
  --hero-banner-media-top: 96px;
  --hero-banner-inner: 1296px;
  /* Badge's right edge sits 105px inside the container's right edge. */
  --hero-banner-badge: 200px;
  --hero-banner-badge-inset: 105px;

  height: var(--hero-banner-height);
}

/* `height: auto` is load-bearing. The theme's Video section styles a *bare*
 * `.jarallax` selector with `width: 100%; height: 800px` (style.css ~10110),
 * so adding the class as a behaviour hook also imports a fixed height — which
 * beat the top/bottom stretch and left 224px of empty hero under the image.
 * This wins on order, not specificity: components.css loads after style.css.
 * The theme's own home-16 thumb has no rule of its own and so genuinely is
 * 800px tall; the comp wants the image to run to the hero's bottom edge. */
.hero-banner__media {
  position: absolute;
  top: var(--hero-banner-media-top);
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 0;
  height: auto;
}

/* Two-class selector: the theme's `img:not([draggable]) { height: auto }` is
 * (0,1,1) and would otherwise stretch a portrait upload past this box. */
.hero-banner__media .hero-banner__image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The comp models the pair with both layers switched off, so this timing is
 * ours. Image 1 sits underneath; only the second layer animates. */
.hero-banner__media .hero-banner__image--fade {
  animation: hero-banner-fade 16s ease-in-out infinite;
}

@keyframes hero-banner-fade {
  0%,
  40% {
    opacity: 1;
  }
  50%,
  90% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero-banner__media .hero-banner__image--fade {
    animation: none;
    opacity: 0;
  }
}

.hero-banner__band {
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  z-index: 1;
  height: var(--hero-banner-band);
  background-color: var(--brand-gunmetal);
}

/* --- bare state: a hero with no background image authored ---
 *
 * The fixed 1120px above is built around a photograph: a 592px band over an
 * image well running to the bottom edge. With no image that well is 528px of
 * empty page, which is the state most of the site's heroes are in today — it
 * reads as a broken gap between the title and the first section.
 *
 * So the banner collapses to its content and the band becomes the whole
 * surface. This only ever applies when there is no image, so a hero that has
 * one renders exactly as before.
 */
.hero-banner--bare {
  height: auto;
}

.hero-banner--bare .hero-banner__band {
  bottom: 0;
  height: auto;
}

/* Clears the 106px fixed header and gives the title the presence a page
   masthead needs without the empty well underneath it. */
.hero-banner--bare .hero-banner__inner {
  padding-top: 208px;
  padding-bottom: 120px;
}

/* The badge is keyed to the seam between the band and the photo well
 * (`top: --hero-banner-band` + `translateY(-50%)`). A bare hero has no seam —
 * the band is the whole surface — so that offset lands past the collapsed
 * height and `overflow: hidden` slices the badge in half. Anchor it to the
 * bottom of the band instead so it stays whole.
 *
 * (0,3,0) on purpose: it has to beat the base rule *and* the <=1399px one, and
 * those are declared after this block. Below 992px the badge is `display:none`,
 * so there is nothing to correct there. */
.hero-banner.hero-banner--bare .hero-banner__badge {
  top: auto;
  bottom: 56px;
  transform: translateX(
    calc(var(--hero-banner-inner) / 2 - var(--hero-banner-badge-inset) - 100%)
  );
}

@media (max-width: 1399px) {
  /* `right: 5vw` already places it once the container goes full-bleed, so the
     horizontal translate has to be dropped or it shifts back off-position. */
  .hero-banner.hero-banner--bare .hero-banner__badge {
    transform: none;
  }
}

/* The one piece of decoration, built from the site's own signature: the ////
   ticks that open every eyebrow, blown up to architectural scale and bled off
   the right edge. Refresh at low opacity so it reads as texture on Gunmetal
   rather than as content, and masked to fade out before it reaches the title.
   No new asset, and it re-colours with the brand tokens. */
.hero-banner--bare::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 1; /* above the band, below the copy (which is z-index 2) */
  width: min(48%, 640px);
  pointer-events: none;
  background-image: repeating-linear-gradient(
    114deg,
    var(--brand-refresh) 0 12px,
    transparent 12px 52px
  );
  opacity: 0.13;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 60%);
  mask-image: linear-gradient(to right, transparent, #000 60%);
}

@media (max-width: 991px) {
  .hero-banner--bare .hero-banner__inner {
    padding-top: 168px;
    padding-bottom: 80px;
  }
  .hero-banner--bare::after {
    width: 62%;
    opacity: 0.1;
  }
}

/* The header grows to 136px at this width (the nav bar wraps), and it is
   absolutely positioned over the hero — 148px of padding left the title 12px
   clear of it, which reads as a collision. */
@media (max-width: 575px) {
  .hero-banner--bare .hero-banner__inner {
    padding-top: 176px;
    padding-bottom: 64px;
  }
}

/* Matches the theme's container maths: --main-container (1296) plus
 * --container-gutters (24), with half the gutter as padding each side. That
 * puts the content box's left edge on 72 at a 1440 viewport, in line with
 * every other slice on the page. --hero-banner-inner stays the *content*
 * width because the badge offset below is measured from it. */
.hero-banner__inner {
  position: relative;
  z-index: 2;
  max-width: calc(var(--hero-banner-inner) + 24px);
  margin: 0 auto;
  padding: 112px 12px 0;
}

.hero-banner__content {
  max-width: 1079px;
}

/* Figma: Rubik 600 95/104.5, tracking -3px. Expressed in em so the tracking
 * scales with the clamped size instead of crowding the words on mobile. */
.hero-banner__title {
  margin: 0;
  color: var(--brand-white);
  font-family: var(--brand-font-heading);
  font-size: clamp(40px, 6.6vw, 95px);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.0316em;
  text-transform: uppercase;
}

/* Figma: Barlow 600 25/40 in Clay. Metrics live on the <p> itself — the
 * theme's bare `p { line-height: 1.75 }` beats anything set on an ancestor. */
.hero-banner__text {
  margin: 24px 0 0;
  color: var(--brand-clay);
  font-family: var(--brand-font-body);
  font-size: clamp(18px, 1.74vw, 25px);
  font-weight: 600;
  line-height: 1.6;
}

.hero-banner__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
  margin-top: 56px;
}

/* Figma: 58px tall = 26px line box + 16px padding each side. No radius. */
.hero-banner__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 16px 24px;
  border: 0;
  background-color: var(--brand-refresh);
  color: var(--brand-gunmetal);
  font-family: var(--brand-font-heading);
  font-size: 16px;
  font-weight: 600;
  line-height: 26px;
  text-transform: uppercase;
  text-decoration: none;
  transition: background-color 0.2s ease;
}

.hero-banner__btn:hover,
.hero-banner__btn:focus-visible {
  background-color: var(--brand-white);
  color: var(--brand-gunmetal);
}

/* Centred on the band's bottom edge, and offset from the container's right
 * edge rather than the viewport's so it tracks the copy on wide screens. */
.hero-banner .hero-banner__badge {
  position: absolute;
  top: var(--hero-banner-band);
  left: 50%;
  z-index: 3;
  width: var(--hero-banner-badge);
  /* Two-class selector (0,2,0) so it outranks the theme's
   * `img:not([draggable]) { height: auto }` at (0,1,1) — otherwise a
   * non-square upload sets its own height and the circle collapses. */
  height: var(--hero-banner-badge);
  border-radius: 50%;
  object-fit: cover;
  transform: translate(
    calc(
      var(--hero-banner-inner) / 2 - var(--hero-banner-badge-inset) - 100%
    ),
    -50%
  );
}

/* Ratios are the comp's numbers over its 1440 frame: 1120/1440, 592/1440,
 * 96/1440, 112/1440. The badge stops tracking the container and is simply
 * inset from the right edge once the container is full-bleed. */
@media (max-width: 1399px) {
  .hero-banner {
    --hero-banner-height: 77.8vw;
    --hero-banner-band: 41.1vw;
    --hero-banner-media-top: 6.7vw;
    --hero-banner-badge: 148px;
  }

  .hero-banner__inner {
    padding-top: 7.8vw;
  }

  /* Must match the base rule's (0,2,0) or it loses to it. */
  .hero-banner .hero-banner__badge {
    left: auto;
    right: 5vw;
    transform: translateY(-50%);
  }
}

/* Below this the band can no longer hold the copy at a readable size, so the
 * hero stops being a fixed-height stack of absolute layers. The copy block
 * carries the Gunmetal itself and the image takes a fixed slice underneath —
 * the empty band element has no content of its own to give it height. */
@media (max-width: 991px) {
  /* Flex + order, because the media is first in the DOM (it has to be, so it
   * paints behind the band on desktop). Left in source order here it would put
   * the photo above the headline and invert the comp's hierarchy. */
  .hero-banner {
    display: flex;
    flex-direction: column;
    height: auto;
  }

  .hero-banner__inner {
    order: 0;
  }

  .hero-banner__media {
    order: 1;
  }

  .hero-banner__band {
    display: none;
  }

  /* `margin-inline: 0` is load-bearing, not tidying. The base rule centres this
   * with `margin: 0 auto`, which is right while the hero is a block. Here the
   * hero is a *column flex container*, and an auto margin on the cross axis
   * suppresses `align-items: stretch` — so the block shrink-wrapped its content
   * and centred, leaving a strip of page background down each side of the
   * Gunmetal. `max-width: none` alone does not undo it: the width was never
   * coming from a max-width. It only showed where the content was narrower than
   * the viewport, so it looked fine at 430 and wrong at 768. */
  .hero-banner__inner {
    max-width: none;
    margin-inline: 0;
    padding: 96px 20px 56px;
    background-color: var(--brand-gunmetal);
  }

  .hero-banner__media {
    position: relative;
    top: 0;
    aspect-ratio: 3 / 2;
  }

  .hero-banner .hero-banner__badge {
    display: none;
  }
}

/* ==========================================================================
 * Detail list — statement over a two-column icon/label/description list
 * Figma: Visit 291:815 ("What to Expect"). 120px top and bottom padding; the
 * 966-wide block is pinned to the right edge of the standard 1296 container
 * (its right edge falls on 626, exactly where that container ends), and the
 * 746-wide detail list is right-aligned again beneath it.
 *
 * The comp's #F2EEE9 fill is on the section frame, which is a Section
 * wrapper's job — this slice paints no background.
 * ========================================================================== */

.detail-list {
  padding: 120px 0;
}

/* 1296 content box + the theme's 24px gutter, half as padding each side. */
.detail-list__inner {
  display: flex;
  justify-content: flex-end;
  max-width: 1320px;
  margin: 0 auto;
  padding: 0 12px;
}

.detail-list__content {
  width: 100%;
  max-width: 966px;
}

/* Figma: Rubik 500 61/67.1, no tracking, Black Coffee. */
.detail-list__statement {
  margin: 0;
  color: var(--brand-black-coffee);
  font-family: var(--brand-font-heading);
  font-size: clamp(32px, 4.24vw, 61px);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: 0;
}

.detail-list__items {
  max-width: 746px;
  margin: 48px 0 0 auto;
  padding: 0;
  list-style: none;
}

/* Rows carry 16px above and below; the first has no top padding, which is
 * what makes the comp's 42 / 84 / 110 / 84 / 58 add up to 378.
 *
 * Each row has a 1px rule beneath it — including the last. Figma sets it as
 * `strokeAlign: INSIDE` with `individualStrokeWeights.bottom: 1`, so the line
 * sits *within* the row's height rather than adding to it: the bottom padding
 * is 15 + 1px border, not 16 + 1. Full-opacity Black Coffee, no tint. */
.detail-list__item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 16px 0 15px;
  border-bottom: 1px solid var(--brand-black-coffee);
}

.detail-list__item:first-child {
  padding-top: 0;
}

/* Two equal columns: (746 - 8) / 2 = 369 each. */
.detail-list__term,
.detail-list__text {
  flex: 1 1 0;
  min-width: 0;
}

.detail-list__term {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 24px;
}

/* Two-class selector: the theme's `img:not([draggable]) { height: auto }` is
 * (0,1,1) and would otherwise let the icon set its own height. */
.detail-list__term .detail-list__icon {
  flex: none;
  width: 24px;
  height: 24px;
  object-fit: contain;
}

/* Line-height is pinned to the icon's 24px rather than the comp's stated 40:
 * the label's own frame measures 24 tall in Figma, and a 40px line box would
 * push every row past its comp height. */
.detail-list__label {
  color: var(--brand-black-coffee);
  font-family: var(--brand-font-body);
  font-size: clamp(19px, 1.74vw, 25px);
  font-weight: 500;
  line-height: 24px;
}

/* Metrics on the <p> itself — the theme's bare `p { line-height: 1.75 }`
 * beats anything set on an ancestor however specific that ancestor is. */
.detail-list__text {
  margin: 0;
  color: var(--brand-black-coffee);
  font-family: var(--brand-font-body);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.6;
}

@media (max-width: 991px) {
  .detail-list {
    padding: 80px 0;
  }

  .detail-list__inner {
    padding: 0 20px;
  }

  .detail-list__items {
    margin-top: 40px;
  }

  /* Two 369px columns stop being readable well before the container does. */
  .detail-list__item {
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
  }

  .detail-list__term {
    flex: none;
    width: 100%;
  }

  .detail-list__text {
    flex: none;
    width: 100%;
  }
}

/* ==========================================================================
 * Sticky panels — sticky media column, text panels scrolling past it
 * Ported from Frisk's `.project-static-wrap13` (home-13). The designer's
 * annotation on Figma 291:530 says to use the theme's spacing and image-fade
 * transitions rather than what is modelled in the comp, so the gap (134 / 80)
 * and the 0.4s linear fade below are the theme's numbers, not Figma's.
 *
 * Multi-instance by design. The theme pairs panels to images through
 * `data-tab="tab-1"` + `#sticky_item`, which can only work once per page;
 * here the pairing is positional and scoped to the instance (see
 * app/components/layout/stickyPanels.ts). Nothing is addressed by id.
 *
 * No background of its own — the comp's dark ground belongs to a Section.
 * ========================================================================== */

/* Figma 291:530: two 120px spacers around a centred 61px heading in Refresh. */
.sticky-panels {
  padding: 120px 0;
}

.sticky-panels__title {
  margin: 0 0 120px;
  color: var(--brand-refresh);
  font-family: var(--brand-font-heading);
  font-size: clamp(34px, 4.24vw, 61px);
  font-weight: 700;
  line-height: 1.1;
  /* Figma 324:190 gives this -1.8px like the panel headings. brand.css only
   * sets letter-spacing on h1/h3, so an h2 that omits it silently renders at
   * `normal` rather than inheriting anything. */
  letter-spacing: -0.0295em;
  text-align: center;
}

/* Columns are Figma's, not the theme's: 818 media + 96 gap + 526 text sums to
 * exactly 1440, and the 454-wide copy ends on 1368 — the right edge of the
 * standard 1296 container. The theme's fixed 1058px column would leave 248px
 * for text. The designer's "use the theme's spacing" note is honoured in the
 * vertical rhythm and the fade instead. */
.sticky-panels__layout {
  display: flex;
  gap: 6.67%;
}

/* Figma 312:2843: the media box is a fixed 818x1024. Held to that ratio rather
 * than filling the viewport, then capped so it still fits a short window — the
 * image `cover`s the box, so clamping crops rather than distorts.
 *
 * `position: sticky` replaces the theme's sticky-kit binding, which could only
 * ever attach to a single id. */
/* Fills its sticky slot rather than holding the comp's 818x1024 ratio.
 *
 * With `aspect-ratio` the height came from the width (818 -> 1024), so once the
 * viewport grew past ~1104px tall the slot was taller than the image and left a
 * band of empty space beneath it. `max-height` only bound on shorter screens,
 * which is why it looked right at 900 and wrong on a large monitor. home-13
 * sizes its sticky image to the column instead; `object-fit: cover` on the
 * image below absorbs the ratio change.
 *
 * Full viewport height, pinned at 0. The first pass kept a 40px inset top and
 * bottom (`top: 40px` + `height: calc(100vh - 80px)`), which still left a
 * visible band of the section's background under every image — the gap the
 * designer flagged. home-13's sticky image is flush, so this is too. */
.sticky-panels__media {
  position: sticky;
  top: 0;
  align-self: flex-start;
  flex: none;
  width: 56.8%;
  height: 100vh;
  overflow: hidden;
}

/* Entrance fade for the image column, requested by the designer.
 *
 * Gated on `[data-sticky-panels-ready]` — the attribute the JS sets on mount —
 * so the hidden state only ever exists where something is guaranteed to switch
 * it back off. With JavaScript disabled the class never lands and the column
 * simply renders. The fade is on the column, not the images: the images use
 * `opacity` for the panel-to-panel cross-fade, and two effects writing one
 * property is how a fade-in ends up cancelling a cross-fade.
 *
 * Slower and eased where the cross-fade is 0.4s linear — an arrival should feel
 * unhurried next to a swap that has to keep up with the scroll. */
.sticky-panels[data-sticky-panels-ready] .sticky-panels__media {
  opacity: 0;
  transition: opacity 900ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

.sticky-panels[data-sticky-panels-ready] .sticky-panels__media.is-revealed {
  opacity: 1;
}

/* The JS honours this too and reveals immediately; the rule is the belt to that
 * braces, for a preference toggled after the observer has already been set up. */
@media (prefers-reduced-motion: reduce) {
  .sticky-panels[data-sticky-panels-ready] .sticky-panels__media {
    opacity: 1;
    transition: none;
  }
}

/* Layers stack in source order and only ever fade *in*; the newest reached
 * image therefore covers the ones before it. That stacking is what produces
 * the cross-fade — no per-image bookkeeping in the JS.
 *
 * Two-class selector on the *base* rule, not just the `.is-active` one: the
 * theme's `img:not([draggable]) { height: auto }` is (0,1,1) and beats a single
 * class, so `height: 100%` was being dropped and a tall photo grew to its
 * natural height and escaped the column. */
.sticky-panels__media .sticky-panels__image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  object-fit: cover;
  background-position: center;
  background-size: cover;
  transition: opacity 0.4s linear;
}

.sticky-panels__image:nth-child(1) { z-index: 0; }
.sticky-panels__image:nth-child(2) { z-index: 1; }
.sticky-panels__image:nth-child(3) { z-index: 2; }
.sticky-panels__image:nth-child(4) { z-index: 3; }
.sticky-panels__image:nth-child(5) { z-index: 4; }
.sticky-panels__image:nth-child(6) { z-index: 5; }
.sticky-panels__image:nth-child(7) { z-index: 6; }
.sticky-panels__image:nth-child(8) { z-index: 7; }

/* Two-class selector so it beats the theme's `img:not([draggable])` at
 * (0,1,1), which would otherwise let a portrait upload set its own height. */
.sticky-panels__media .sticky-panels__image.is-active {
  opacity: 1;
}

/* 5% is the comp's 72px right margin, which lands the 454-wide copy on 1368 —
 * the same right edge every other slice's container uses. */
.sticky-panels__content {
  flex: 1 1 auto;
  min-width: 0;
  padding-right: 5%;
}

/* Each panel is its own scroll "page" — this is what gives the sticky column
 * something to hold still against. */
.sticky-panels__panel {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 100vh;
}

/* Figma: Rubik 700 61/67.1, tracking -1.8px. In em so it does not crowd once
 * the size clamps down — a fixed -1.8px at 34px would read as -0.053em. */
.sticky-panels__heading {
  margin: 24px 0 0;
  color: var(--brand-surface-light);
  font-family: var(--brand-font-heading);
  font-size: clamp(34px, 4.24vw, 61px);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.0295em;
}

/* Metrics on the <p> itself: the theme's bare `p { line-height: 1.75 }` beats
 * anything set on an ancestor, however specific. */
.sticky-panels__text {
  max-width: 454px;
  margin: 24px 0 0;
  color: var(--brand-surface-muted);
  font-family: var(--brand-font-body);
  font-size: clamp(17px, 1.39vw, 20px);
  font-weight: 400;
  line-height: 1.6;
}

/* The wrapper the `rich-list` hook rides on.
 *
 * Colouring it is load-bearing, not decorative: `.rich-list p` is
 * `color: inherit` at (0,2,0) and outranks `.sticky-panels__text` at (0,1,0),
 * so the moment a panel gained the hook its paragraphs stopped being the warm
 * off-white and inherited from the body. Setting it here is what that
 * `inherit` picks up, and it feeds the list rules at the same time.
 *
 * The marker is left to the shared Refresh default — these panels sit on a
 * dark ground, which is the same pairing the About offset uses on its two dark
 * fills. */
.sticky-panels__copy {
  color: var(--brand-surface-muted);
  --rich-list-ink: var(--brand-surface-muted);
}

/* Both classes on purpose: `.rich-list li` is declared later in this file at
   the same (0,1,1) a single-class rule would have, and later wins. Matched to
   `.sticky-panels__text` so a bullet and a paragraph in one panel are the same
   size. */
.rich-list.sticky-panels__copy li {
  font-size: clamp(17px, 1.39vw, 20px);
  line-height: 1.6;
}

.rich-list.sticky-panels__copy ul,
.rich-list.sticky-panels__copy ol {
  max-width: 454px;
  margin-top: 24px;
}

/* --- per-panel CTA ---
 * These panels sit on the comp's dark ground, so both styles are built for
 * light-on-dark: Refresh for the text link, Refresh-filled for the button.
 */

/* A row, so a Button and a Text Link can sit side by side. `align-items:
   center` rather than baseline: the button's padding and the text link's
   underline give them different baselines, and aligning on those staggers the
   two labels vertically. */
.sticky-panels__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px 28px;
  margin-top: 32px;
}

/* Reboot styles `a` directly, so decoration and colour are restated. */
.sticky-panels__link {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding-bottom: 4px;
  border-bottom: 2px solid var(--brand-refresh);
  color: var(--brand-refresh);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-link);
  font-weight: var(--brand-fw-link);
  line-height: normal;
  letter-spacing: var(--brand-ls-link);
  text-transform: uppercase;
  text-decoration: none;
  transition:
    gap 0.25s ease,
    color 0.2s ease,
    border-color 0.2s ease;
}

.sticky-panels__link:hover,
.sticky-panels__link:focus-visible {
  gap: 16px;
  color: var(--brand-white);
  border-bottom-color: var(--brand-white);
}

.sticky-panels__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 16px 28px;
  border: 2px solid var(--brand-refresh);
  background-color: var(--brand-refresh);
  color: var(--brand-gunmetal);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-link);
  font-weight: var(--brand-fw-link);
  line-height: 26px;
  letter-spacing: normal;
  text-transform: uppercase;
  text-decoration: none;
  transition:
    background-color 0.2s ease,
    border-color 0.2s ease,
    color 0.2s ease;
}

.sticky-panels__btn:hover,
.sticky-panels__btn:focus-visible {
  background-color: var(--brand-white);
  border-color: var(--brand-white);
  color: var(--brand-gunmetal);
}

/* Secondary: the same button outlined rather than filled, so a pair reads as
   one primary action and one alternative instead of two competing ones. */
.sticky-panels__btn--ghost {
  background-color: transparent;
  border-color: rgba(255, 255, 255, 0.45);
  color: var(--brand-white);
}

.sticky-panels__btn--ghost:hover,
.sticky-panels__btn--ghost:focus-visible {
  background-color: var(--brand-white);
  border-color: var(--brand-white);
  color: var(--brand-gunmetal);
}

@media (prefers-reduced-motion: reduce) {
  .sticky-panels__link,
  .sticky-panels__btn {
    transition: none;
  }
  .sticky-panels__link:hover,
  .sticky-panels__link:focus-visible {
    gap: 10px;
  }
}

/* Shown only when the sticky column is dropped; see the mobile block. */
.sticky-panels__inline-image {
  display: none;
}

/* The theme drops its sticky column entirely below this, which would lose the
 * photographs — each panel carries its own copy of the image for that reason
 * (the theme does the same with `.hidden-img`). */
@media (max-width: 991px) {
  .sticky-panels {
    padding: 80px 0;
  }

  .sticky-panels__title {
    margin-bottom: 56px;
  }

  .sticky-panels__layout {
    display: block;
  }

  .sticky-panels__media {
    display: none;
  }

  .sticky-panels__content {
    padding: 0 20px;
  }

  .sticky-panels__panel {
    display: block;
    min-height: 0;
    padding: 56px 0;
  }

  .sticky-panels__inline-image {
    display: block;
    margin-bottom: 32px;
  }

  .sticky-panels__inline-image img {
    width: 100%;
    height: auto;
  }
}

/* ==========================================================================
 * Sticky cards — sticky intro column beside colour-blocked cards
 * Figma: Visit 291:654 ("Bringing Your Family?"), theme home-3.
 * On the 1296 container: intro 416 + 134 gutter + cards 746, cards 40 apart.
 * Card internals: 306 photo + 440 body (40 padding, so 360 of copy).
 *
 * No JS — unlike StickyPanels nothing cross-fades, so `position: sticky` is
 * the entire mechanism and any number of these can share a page.
 * No background of its own; the comp's white ground belongs to a Section.
 * ========================================================================== */

.sticky-cards {
  padding: 120px 0;
}

.sticky-cards__inner {
  max-width: 1320px;
  margin: 0 auto;
  padding: 0 12px;
}

.sticky-cards__layout {
  display: flex;
  gap: 10.34%;
}

.sticky-cards__intro {
  position: sticky;
  top: 120px;
  align-self: flex-start;
  flex: none;
  width: 32.1%;
}

/* Figma: Rubik 700 61/67.1, tracking -1.8px, in em so it does not crowd once
 * the size clamps down. 16px between all three intro elements. */
.sticky-cards__heading {
  margin: 16px 0 0;
  color: var(--brand-ink);
  font-family: var(--brand-font-heading);
  font-size: clamp(34px, 4.24vw, 61px);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.0295em;
}

/* Metrics on the <p>: the theme's bare `p { line-height: 1.75 }` beats
 * anything set on an ancestor. */
.sticky-cards__intro-text {
  margin: 16px 0 0;
  color: var(--brand-ignition);
  font-family: var(--brand-font-body);
  font-size: clamp(17px, 1.39vw, 20px);
  font-weight: 400;
  line-height: 1.6;
}

/* Bulleted and numbered lists in the same field as those paragraphs.
 *
 * The shared `.rich-list` rules take their colour from two custom properties
 * and otherwise default to Gunmetal ink with an amber marker, which is right on
 * the About bands and wrong here — this intro is entirely Ignition. Both are
 * pointed at Ignition so a list reads as the same voice as the sentence above
 * it rather than introducing a second accent next to the rust.
 *
 * The size and leading are restated because `.rich-list li` is written against
 * the base body scale (`--brand-fs-p-base`, `--brand-lh-body`) while this
 * column runs on its own clamp — matching them is the difference between a list
 * that belongs to this paragraph and one that merely sits under it. Top margin
 * drops 20px -> 16px for the same reason: it is the intro's own rhythm, the gap
 * every other element in this column already uses. */
.sticky-cards__intro-copy {
  /* Load-bearing, and not a duplicate of the rule above. `.rich-list p` is
   * `color: inherit` at (0,2,0), which outranks `.sticky-cards__intro-text` at
   * (0,1,0) — so the moment this column gained the `rich-list` hook its
   * paragraphs stopped being Ignition and inherited Gunmetal from the body.
   * Colouring the wrapper is what `inherit` then picks up, and it feeds the
   * list rules below at the same time. */
  color: var(--brand-ignition);
  --rich-list-ink: var(--brand-ignition);
  /* Marker only — the text stays Ignition. Refresh is the shared default, named
   * explicitly here so the split between the two is visible at a glance rather
   * than inferred from an absent declaration. */
  --rich-list-marker: var(--brand-refresh);
}

/* Paragraph rhythm. `.sticky-cards__intro-text` sets a top-only 16px, which is
 * the comp's spacing for the eyebrow/heading/text triplet and correct for the
 * *first* block — a heading should sit close to the text it introduces. Between
 * body blocks it is half a line at 20px/32px, so consecutive paragraphs read as
 * one run of text, which is what "no spacing between paragraphs" was.
 *
 * Applied to every subsequent sibling rather than `p + p` so a paragraph
 * following a list is spaced like any other; the list itself keeps its own
 * tighter margin below, which is more specific and therefore still wins. */
.rich-list.sticky-cards__intro-copy > * + * {
  margin-top: 28px;
}

/* Both classes on purpose. `.rich-list ul` and `.rich-list li` are declared
 * further down this file at the same (0,1,1) these would otherwise have, and
 * later wins — a single-class override here is silently ignored.
 *
 * The 16px is deliberately tighter than the 28px above: a list almost always
 * follows a lead-in sentence ("They can expect:"), and belongs to it. Pulling
 * it closer than a normal paragraph break is what shows that.
 *
 * The indent is on the list rather than the item: `.rich-list li` already
 * spends its own `padding-left: 30px` on the gap between marker and text, so
 * adding to that would push the words further from their bullet instead of
 * moving the block. Indenting the `ul` sets the whole thing in from the
 * paragraph edge, marker included, which is what reads as a nested list. */
.rich-list.sticky-cards__intro-copy ul,
.rich-list.sticky-cards__intro-copy ol {
  margin-top: 16px;
  padding-left: 24px;
}

.rich-list.sticky-cards__intro-copy li {
  font-size: clamp(17px, 1.39vw, 20px);
  line-height: 1.6;
}

.sticky-cards__list {
  display: flex;
  flex-direction: column;
  gap: 40px;
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Each card sticks at the same offset, so as you scroll one comes to rest and
 * the next rides up over it — the theme's `.blog-grid-static` behaviour from
 * home-3 ("Read Our Articles and News"), which is pure CSS with no JS behind
 * it. The intro column was already sticky; the cards were not, which is why
 * nothing appeared to replace anything.
 *
 * 120px matches the intro's offset so a resting card lines up with the sticky
 * heading beside it, and clears the fixed 106px header. Cards carry a solid
 * fill and a min-height, so the one on top covers the one beneath. */
.sticky-cards__card {
  position: sticky;
  top: 120px;
  display: flex;
  min-height: 314px;
  background-color: var(--brand-refresh);
  /* Read by the title, subtitle, text and link below so a dark fill can flip
     all four from one place. */
  --sticky-card-ink: var(--brand-ink);
}

.sticky-cards__card--refresh { background-color: var(--brand-refresh); }
.sticky-cards__card--mint { background-color: var(--brand-mint); }
.sticky-cards__card--coral { background-color: var(--brand-coral); }
/* Flagstone takes light ink, matching the About offset band, CtaBar and
 * PriceBar. It uses `--brand-flagstone-ink` (#4f7a64) rather than
 * `--brand-flagstone` (#689881) for the same reason they do: white on the
 * lighter value is 3.28:1 and fails AA, while the deeper one reaches 4.88:1.
 * That is also better than the 4.57:1 dark ink was managing here — this fill
 * was the weakest of the five. */
.sticky-cards__card--flagstone {
  background-color: var(--brand-flagstone-ink);
  --sticky-card-ink: var(--brand-white);
}
.sticky-cards__card--clay { background-color: var(--brand-clay); }

/* The text link's sweep-underline lives on its two pseudo-elements, which take
 * their colour from the theme rather than from the card — so on a dark fill it
 * stayed Gunmetal and drew a dark rule under white text (3.07:1). Pointing it
 * at the ink keeps the two in step; on the light fills this resolves to the
 * same Gunmetal it already used, so nothing changes there.
 * (0,2,0) to beat the theme's `.link-btn:before/:after` at (0,1,0). */
.sticky-cards__card .sticky-cards__link::before,
.sticky-cards__card .sticky-cards__link::after {
  background-color: var(--sticky-card-ink, var(--brand-ink));
}

.sticky-cards__media {
  position: relative;
  flex: none;
  width: 41.02%;
  overflow: hidden;
}

/* Two-class selector so it beats the theme's `img:not([draggable])` at
 * (0,1,1), which would otherwise let a portrait upload set its own height. */
.sticky-cards__media .sticky-cards__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.sticky-cards__media .sticky-cards__logo {
  position: absolute;
  bottom: 10px;
  left: 14px;
  width: 132px;
  height: auto;
}

.sticky-cards__body {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  flex-direction: column;
  gap: 24px;
  padding: 40px;
}

.sticky-cards__copy {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.sticky-cards__titles {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Figma: Rubik 500 31/37.2, no tracking. `letter-spacing: normal` is not
 * redundant — brand.css sets `h3 { letter-spacing: var(--brand-ls-h3) }`
 * (-1.8px) and this is an h3, so leaving the property undeclared lets the
 * element rule through. 2.153vw is 31/1440, so it lands on 31 at comp size. */
.sticky-cards__title {
  margin: 0;
  color: var(--sticky-card-ink, var(--brand-ink));
  font-family: var(--brand-font-heading);
  font-size: clamp(24px, 2.153vw, 31px);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: normal;
}

/* The "Eyebrow S" style (13/ls4, uppercase) but without the `////` — that
 * prefix belongs to `.eyebrow`, and the comp does not use it on cards. */
.sticky-cards__subtitle {
  color: var(--sticky-card-ink, var(--brand-ink));
  font-family: var(--brand-font-heading);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: 4px;
  text-transform: uppercase;
}

/* The card's copy is rich text now, so this is the wrapper the `rich-list`
 * hook rides on rather than the paragraph itself — `.sticky-cards__text p`
 * below carries what used to be here.
 *
 * Colouring the wrapper is load-bearing: `.rich-list p` is `color: inherit` at
 * (0,2,0) and outranks a single-class rule, so without this the copy would stop
 * following `--sticky-card-ink` and go black on the Flagstone fill.
 *
 * The marker follows the ink rather than the shared Refresh default, which is
 * the one place cards differ from the other bulleted slices: an amber marker is
 * invisible on a Refresh card, and these five fills already carry an ink chosen
 * to contrast with each of them. */
.sticky-cards__text {
  margin: 0;
  color: var(--sticky-card-ink, var(--brand-ink));
  --rich-list-ink: var(--sticky-card-ink, var(--brand-ink));
  --rich-list-marker: var(--sticky-card-ink, var(--brand-ink));
  font-family: var(--brand-font-body);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.6;
}

.sticky-cards__text p {
  margin: 0;
}

.sticky-cards__text > * + * {
  margin-top: 12px;
}

/* Both classes on purpose: `.rich-list li` is declared later in this file at
   the same (0,1,1) a single-class rule would have, and later wins. The card is
   a small surface, so the list sits tighter than the 16px the wider slices use
   and inherits the card's own weight. */
.rich-list.sticky-cards__text li {
  font-size: 16px;
  font-weight: 500;
  line-height: 1.6;
}

.rich-list.sticky-cards__text ul,
.rich-list.sticky-cards__text ol {
  margin: 0;
}

/* `align-self` is load-bearing: __body is a flex column, so the default
 * `stretch` made the anchor span the whole card — and the theme's sweep
 * underline (a pseudo-element sized to the element) stretched with it. Figma
 * has this hugging its text at 131px. */
.sticky-cards__link {
  align-self: flex-start;
  margin-top: auto;
  color: var(--sticky-card-ink, var(--brand-ink));
}

/* A card with no photo drops its media column, so the body's `flex: 1 1 auto`
   already fills the whole card; this only keeps a future `min-width` from
   clawing it back. Its 40px padding is unchanged, so the copy stays inset. */
.sticky-cards__card--no-media .sticky-cards__body {
  width: 100%;
}

/* Per-card text alignment. Left is the default and needs no rule. `text-align`
   handles the title/subtitle/copy; the links sit in their own flex row
   (`.sticky-cards__links`), which text-align does not reach, so its
   `justify-content` is moved in step. */
.sticky-cards__body--align-center {
  text-align: center;
}
.sticky-cards__body--align-center .sticky-cards__links {
  justify-content: center;
}
.sticky-cards__body--align-right {
  text-align: right;
}
.sticky-cards__body--align-right .sticky-cards__links {
  justify-content: flex-end;
}

@media (max-width: 991px) {
  .sticky-cards {
    padding: 80px 0;
  }

  .sticky-cards__inner {
    padding: 0 20px;
  }

  /* The sticky column stops earning its keep once it cannot sit beside the
   * cards; intro simply stacks above them. */
  .sticky-cards__layout {
    display: block;
  }

  .sticky-cards__intro {
    position: static;
    width: auto;
    margin-bottom: 48px;
  }

  /* Released for the same reason the intro is: with one column there is
     nothing for a card to slide over. */
  .sticky-cards__card {
    position: static;
    flex-direction: column;
  }

  .sticky-cards__media {
    width: 100%;
    aspect-ratio: 306 / 314;
  }

  .sticky-cards__body {
    padding: 32px;
  }
}

/* ==========================================================================
 * Form block — "Plan Your Visit"
 * Figma: Visit 370:317. Image 605 wide flush to the viewport's left edge and
 * starting at the section's top; Clay band inset 120 from that top so the
 * image overhangs it; form column 766 on the right with 72 of right padding,
 * landing its 694 of content on 1368 — the usual container right edge.
 *
 * The band is the slice's own, the one exception to slices-owning-no-
 * background: a Section cannot paint a band that an image overlaps. Same
 * reasoning as the hero's Gunmetal band.
 * ========================================================================== */

/* The comp is drawn at 1440 and this section is built from percentages of it
 * (42% media, 4.79% gap, 5% right pad), so past 1440 everything would scale up
 * with the viewport.
 *
 * The cap therefore stops at the *inner*, not the slice: the band runs the full
 * width of the screen while the image and the form stop growing at 1440 and
 * centre. That split is the designer's, taken from Frisk's `testimonial-area-1`
 * (the theme's index.html) — "at some point the image does stop expanding, but
 * the colour is kept full width end to end". It supersedes the earlier note
 * here, which capped the whole slice on the grounds that the comp never shows
 * Clay to the left of the image; on a wide monitor that left a gutter of page
 * background either side of the band, and Clay reaching the screen edges is
 * what the designer wants there instead.
 *
 * Below 1440 the percentages still flex and the band spans the slice anyway, so
 * nothing about the comp's own composition changes.
 *
 * NOTE: `.about-offset` further down deliberately copies this geometry so the
 * two read as one family. It has NOT been given the full-bleed band — that was
 * asked for on the form alone. If both land on one page and the mismatch shows,
 * they should be brought back together rather than left to drift. */
.form-block {
  position: relative;
}

.form-block::before {
  content: "";
  position: absolute;
  top: 120px;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: var(--brand-clay);
}

/* The 1440 the slice itself used to carry. `position: relative` keeps it above
 * the band, which is painted on the slice and so now runs the full width of the
 * screen behind it. */
.form-block__inner {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 4.79%;
  max-width: 1440px;
  margin-inline: auto;
}

.form-block__media {
  flex: none;
  width: 42%;
  aspect-ratio: 605 / 796;
  overflow: hidden;
}

/* Two-class selector: the theme's `img:not([draggable])` is (0,1,1) and would
 * otherwise let the upload set its own height. */
.form-block__media .form-block__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.form-block__content {
  flex: 1 1 auto;
  min-width: 0;
  padding: 200px 5% 120px 0;
}

/* Figma: Rubik 500 49/53.9. Declared letter-spacing on purpose — brand.css
 * sets it per element for h1/h3, so an h2 that omits it is silently `normal`
 * while an h3 would silently inherit -1.8px. */
.form-block__heading {
  margin: 0;
  color: var(--brand-black-coffee);
  font-family: var(--brand-font-heading);
  font-size: clamp(32px, 3.4vw, 49px);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: normal;
}

/* The comp specifies Poppins here — the Frisk default, not a brand font. Barlow
 * substituted, as on the FAQ answers. */
.form-block__text {
  margin: 22px 0 0;
  color: var(--brand-black-coffee);
  font-family: var(--brand-font-body);
  font-size: 16px;
  font-weight: 400;
  line-height: 1.75;
}

.form-block__form {
  display: flex;
  flex-wrap: wrap;
  gap: 16px 24px;
  margin-top: 30px;
}

.form-block__field {
  flex: 1 1 100%;
  min-width: 0;
}

/* 335 + 24 gap + 335 = the comp's 694 form width. */
.form-block__field--half {
  flex: 1 1 calc(50% - 12px);
}

/* Inputs are underlines, not boxes: 2px bottom rule, no other border.
 * `min-height` is stated rather than left to the padding: style.css sets
 * `input { height: 55px }`, which happened to match Figma's 55 — so this was
 * landing on the right number by accident, not by intent. */
.form-block__input {
  width: 100%;
  height: auto;
  min-height: 55px;
  padding: 12.5px 0;
  border: 0;
  border-bottom: 2px solid var(--brand-black-coffee);
  border-radius: 0;
  background-color: transparent;
  color: var(--brand-black-coffee);
  font-family: var(--brand-font-body);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.6;
}

.form-block__input::placeholder {
  color: var(--brand-black-coffee);
  opacity: 1;
}

.form-block__input:focus-visible {
  outline: 2px solid var(--brand-gunmetal);
  outline-offset: 4px;
}

/* --- Textarea (Figma 374:413 / 374:402) ---
 * The only boxed control in the form. The comp's wrapper adds 16px above the
 * box and 40px below, of which the help text occupies the first ~26. */
.form-block__boxed {
  padding: 16px 0 14px;
}

.form-block__box {
  position: relative;
  border: 2px solid var(--brand-black-coffee);
  border-radius: 4px;
}

/* The chip rides the top border and masks it, so its background has to match
 * the band behind it — Clay. If this slice is ever placed on a Section with a
 * different ground, this is the one rule that has to follow. */
/* -10/10 rather than the comp's -8/12: an absolutely positioned child is laid
 * out against the padding box, so the box's own 2px border shifts it. `margin`
 * is zeroed because the theme's bare `label` rule sets -0.3em on top. */
.form-block__box-label {
  position: absolute;
  top: -10px;
  left: 10px;
  margin: 0;
  padding: 0 4px;
  background-color: var(--brand-clay);
  color: var(--brand-black-coffee);
  font-family: var(--brand-font-body);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.6;
}

/* Deliberately NOT extending .form-block__input — that carries the 2px bottom
 * rule and a 55px min-height, both wrong inside a box. */
.form-block__input--textarea {
  display: block;
  width: 100%;
  height: 116px;
  min-height: 116px;
  padding: 8px 16px;
  border: 0;
  border-radius: 4px;
  background-color: transparent;
  color: var(--brand-black-coffee);
  font-family: var(--brand-font-body);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.6;
  resize: vertical;
}

.form-block__input--textarea:focus-visible {
  outline: 2px solid var(--brand-gunmetal);
  outline-offset: 4px;
}

/* Figma gives #474747 here — a Material default that only reaches 4.0:1 on
 * Clay, under AA for 14px. Black Coffee is 5.5:1 and is the form's ink. */
.form-block__help {
  margin: 4px 0 0;
  padding-left: 16px;
  color: var(--brand-black-coffee);
  font-family: var(--brand-font-body);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.6;
}

/* --- Radio groups (Figma 372:334: 20px buttons, 8px gaps) --- */
/* The radio group is an input like any other in the comp (372:334) — same
 * 12.5px vertical padding and same 2px bottom rule. Easy to miss: the rule
 * lives in `individualStrokeWeights`, not in a fills dump. */
.form-block__group {
  margin: 0;
  padding: 12.5px 0;
  border: 0;
  border-bottom: 2px solid var(--brand-black-coffee);
}

/* `float` and `width` are restated because Bootstrap's reboot floats every
 * legend left at 100%; `margin` because the theme's bare `label` rule sets
 * 0.5em bottom / -0.3em top. Both are (0,0,1) — they only won because these
 * properties went undeclared here. */
.form-block__legend {
  display: block;
  float: none;
  width: 100%;
  margin: 0;
  padding: 0;
  color: var(--brand-black-coffee);
  font-family: var(--brand-font-body);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.6;
}

/* The comp lays the four campuses as equal columns filling the 694 form width
 * (4 x 167.5 + 3 x 8). `flex: 1 1 140px` reproduces that exactly at four
 * options and still wraps sensibly at other counts and on mobile. */
.form-block__options {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 16px;
}

.form-block__option {
  display: flex;
  flex: 1 1 140px;
  min-width: 0;
  align-items: center;
  gap: 8px;
  margin: 0; /* the theme's bare `label` rule, again */
  color: var(--brand-black-coffee);
  font-family: var(--brand-font-body);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.6;
  cursor: pointer;
}

/* Two-class selectors, and every property restated. The theme hides native
 * controls so it can draw its own — `input[type="radio"]` in style.css sets
 * `display: none; visibility: hidden; opacity: 0; width: 0; height: 0` at
 * (0,1,1), which beat a single class and left the radios at zero size. */
.form-block__option .form-block__radio,
.form-block__option .form-block__checkbox {
  display: inline-block;
  flex: none;
  width: 20px;
  height: 20px;
  min-height: 0;
  padding: 0;
  visibility: visible;
  opacity: 1;
  accent-color: var(--brand-gunmetal);
}

/* --- Actions and states --- */
.form-block__submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 16px 24px;
  border: 0;
  background-color: var(--brand-refresh);
  color: var(--brand-gunmetal);
  font-family: var(--brand-font-heading);
  font-size: 16px;
  font-weight: 600;
  line-height: 26px;
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.form-block__submit:hover:not(:disabled) {
  background-color: var(--brand-white);
}

.form-block__submit:disabled {
  cursor: default;
  opacity: 0.7;
}

.form-block__error {
  flex: 1 1 100%;
  margin: 0;
  color: var(--brand-ignition);
  font-family: var(--brand-font-body);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.6;
}

.form-block__success-text {
  margin: 0;
  color: var(--brand-black-coffee);
  font-family: var(--brand-font-body);
  font-size: clamp(17px, 1.39vw, 20px);
  font-weight: 500;
  line-height: 1.6;
}

.form-block__success {
  margin-top: 30px;
}

/* Off-canvas rather than display:none — some bots skip hidden inputs. */
.form-block__honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

@media (max-width: 991px) {
  .form-block::before {
    top: 0;
  }

  .form-block__inner {
    display: block;
  }

  .form-block__media {
    width: 100%;
    aspect-ratio: 16 / 10;
  }

  .form-block__content {
    padding: 56px 20px 80px;
  }

  .form-block__field--half {
    flex: 1 1 100%;
  }
}

/* ==========================================================================
 * Series card — the /series index grid.
 *
 * Replaces Frisk's `portfolio-wrap style3`, which was wrong here on two
 * counts: it positions `.portfolio-details` absolutely at `opacity: 0` and only
 * reveals the title on hover, and it sizes the thumbnail with `width: 100%`
 * alone. The imported artwork is genuinely mixed — 16:9 screens, 1:1 app tiles,
 * 4:3 and 3:2 photos, plus some 336x188 YouTube grabs — so leaving the height
 * to the file makes every row ragged.
 * ========================================================================== */

.series-card {
  display: block;
  color: inherit;
  text-decoration: none;
}

.series-card__thumb {
  position: relative;
  display: block;
  width: 100%;
  /* 16:9 matches the artwork the church actually produces; squarer uploads
   * centre-crop rather than setting the row height. */
  aspect-ratio: 16 / 9;
  overflow: hidden;
  /* Shows through only for art with transparency. */
  background-color: var(--brand-surface-muted);
}

/* Two-class selector: the theme's `img:not([draggable])` is (0,1,1) and carries
 * `height: auto`, which would otherwise let the upload set the box height and
 * defeat the aspect-ratio above. */
.series-card__thumb .series-card__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.4s ease-in-out;
}

.series-card:hover .series-card__image {
  transform: scale(1.04);
}

.series-card__title {
  margin: 16px 0 0;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-p-m);
  font-weight: 500;
  line-height: 1.3;
  color: var(--brand-ink);
}

.series-card:hover .series-card__title {
  color: var(--brand-ignition);
}

/* Placeholder for a series with no artwork, so the grid keeps its rhythm. */
.series-card__thumb--empty {
  background-color: var(--brand-surface-dark-alt);
}

.series-card__date {
  margin: 4px 0 0;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  color: var(--brand-ink-warm);
  opacity: 0.75;
}

/* Breadcrumb banner carrying real artwork (series artwork, message thumbnail)
 * rather than the theme's stock photo. That photo was chosen to sit behind
 * white text and only needs `opacity: 0.2`; imported artwork is frequently
 * bright, or carries the series name in the image itself, so the title needs a
 * heavier scrim to stay legible on top of it. */
.breadcumb-wrapper--media {
  background-position: center;
  background-size: cover;
}

.breadcumb-wrapper--media:after {
  opacity: 0.55;
}

/* ==========================================================================
 * Message banner — the header on /series and each series page.
 *
 * Sits on the breadcumb-wrapper band (so it inherits the theme's height,
 * scrim and background handling) but replaces the breadcrumb's title-plus-menu
 * with the newest message: byline, title, and the actions available for it.
 * ========================================================================== */

.message-banner__content {
  position: relative;
  z-index: 1;
}

.message-banner__byline {
  margin: 0 0 12px;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  color: var(--brand-white);
  opacity: 0.85;
}

.message-banner__title {
  margin-bottom: 28px;
}

/* The theme colours breadcumb-title white already; the anchor needs telling. */
.message-banner__title a {
  color: inherit;
  text-decoration: none;
}

.message-banner__title a:hover {
  color: var(--brand-refresh);
}

.message-banner__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
}

.message-banner__action {
  display: inline-block;
  padding: 12px 24px;
  border: 1px solid rgba(255, 255, 255, 0.55);
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-base);
  line-height: 1.2;
  color: var(--brand-white);
  text-decoration: none;
  transition: background-color 0.3s, color 0.3s, border-color 0.3s;
}

.message-banner__action:hover {
  background-color: var(--brand-white);
  border-color: var(--brand-white);
  color: var(--brand-ink);
}

.message-banner__action--primary {
  background-color: var(--brand-refresh);
  border-color: var(--brand-refresh);
  color: var(--brand-ink);
}

.message-banner__action--primary:hover {
  background-color: var(--brand-white);
  border-color: var(--brand-white);
}

@media (max-width: 575px) {
  .message-banner__actions {
    flex-direction: column;
    align-items: stretch;
  }
}

/* ==========================================================================
 * Message grid — "Messages in this Series" on a series page.
 *
 * Mirrors the live site's `grid-1-3-3` tease-message grid: a 16:9 thumbnail
 * with the title beneath, three up. Thumbnails come from a mix of YouTube
 * grabs and produced artwork, so the box is fixed and the image is cropped to
 * it rather than letting each file set its own height.
 * ========================================================================== */

.message-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 32px 24px;
}

.message-tile {
  display: block;
  color: inherit;
  text-decoration: none;
}

.message-tile__thumb {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background-color: var(--brand-surface-muted);
}

/* Two-class selector: the theme's `img:not([draggable])` is (0,1,1) and carries
 * `height: auto`, which would otherwise defeat the aspect-ratio above. */
.message-tile__thumb .message-tile__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.4s ease-in-out;
}

.message-tile:hover .message-tile__image {
  transform: scale(1.04);
}

.message-tile__title {
  margin: 14px 0 0;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-p-m);
  font-weight: 500;
  line-height: 1.3;
  color: var(--brand-ink);
}

.message-tile:hover .message-tile__title {
  color: var(--brand-ignition);
}

.message-tile__meta {
  margin: 4px 0 0;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  color: var(--brand-ink-warm);
  opacity: 0.75;
}

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

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

/* --- Message tile: play affordance ---------------------------------------
 * These cards are videos — 97% of the archive has one — so the badge states a
 * fact about the content rather than decorating it, and it is omitted on the
 * handful of messages with no recording. Amber on hover picks up the same
 * Refresh accent the arrow links use, so the archive reads as one family.
 * ------------------------------------------------------------------------ */

.message-tile__play {
  position: absolute;
  left: 16px;
  bottom: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background-color: rgba(7, 43, 49, 0.72); /* Gunmetal, so it sits on any art */
  backdrop-filter: blur(2px);
  transition: background-color 0.3s ease, transform 0.3s ease;
}

/* CSS triangle — avoids shipping an icon for something this small, and
 * inherits colour changes without a second asset. */
.message-tile__play::before {
  content: "";
  display: block;
  margin-left: 3px; /* optical centring: a triangle's mass sits left */
  border-style: solid;
  border-width: 7px 0 7px 11px;
  border-color: transparent transparent transparent var(--brand-white);
  transition: border-left-color 0.3s ease;
}

.message-tile:hover .message-tile__play {
  background-color: var(--brand-refresh);
  transform: scale(1.08);
}

.message-tile:hover .message-tile__play::before {
  border-left-color: var(--brand-ink);
}

@media (prefers-reduced-motion: reduce) {
  .message-tile__image,
  .message-tile__play,
  .series-card__image {
    transition: none;
  }

  .message-tile:hover .message-tile__image,
  .message-tile:hover .message-tile__play,
  .series-card:hover .series-card__image {
    transform: none;
  }
}

/* The series a message belongs to — shown on /watch, where messages from 84
 * different series sit side by side, and omitted on a series page where it
 * would repeat the heading above every card. */
.message-tile__series {
  margin: 6px 0 0;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--brand-flagstone-ink);
}

/* --- Keyboard focus ------------------------------------------------------
 * The theme resets outlines globally, which leaves these card links with no
 * visible focus state — invisible to anyone navigating by keyboard. Restored
 * with `:focus-visible` so it appears for keyboard users without drawing a ring
 * on every mouse click.
 * ------------------------------------------------------------------------ */

/* Element-qualified so it outranks the theme's `a:focus { outline: none }`,
 * which is (0,1,1) and would otherwise tie-break against a bare class. */
a.message-tile:focus-visible,
a.series-card:focus-visible,
a.message-banner__action:focus-visible {
  outline: 3px solid var(--brand-refresh);
  outline-offset: 3px;
}

/* Series description, now the only thing above the message grid. Capped at a
 * comfortable measure — full container width would run to ~1296px, far past
 * the ~75 characters a line should carry. */
.series-detail__intro {
  max-width: 720px;
  margin-bottom: 8px;
}

/* --- Skip link -----------------------------------------------------------
 * Off-screen until focused, then pinned top-left over the sticky header.
 * `clip` rather than `display: none` so it stays in the focus order.
 * ------------------------------------------------------------------------ */

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 1000;
  padding: 12px 20px;
  background-color: var(--brand-gunmetal);
  color: var(--brand-white);
  font-family: var(--brand-font-body);
  text-decoration: none;
}

.skip-link:focus {
  left: 8px;
  top: 8px;
  outline: 3px solid var(--brand-refresh);
  outline-offset: 2px;
}

/* Available to assistive tech, absent from the visual design. Used for section
 * headings that the layout communicates by other means but that the document
 * outline still needs. `clip-path` with a 1px box rather than `display: none`,
 * which would remove it from the accessibility tree entirely. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- Reduced motion, site-wide -------------------------------------------
 * The theme animates on scroll through WOW.js, GSAP and animate.css, none of
 * which consult the user's motion preference — `prefers-reduced-motion` does
 * not appear anywhere in style.css or animate.min.css. This neutralises those
 * animations for people who have asked their OS for less motion, without
 * touching any slice: the rules only apply under the media query, so anyone
 * who has not opted out sees the design exactly as designed.
 *
 * `animation: none` on `.animated`/`.wow` matters as much as the durations —
 * animate.css sets starting states (opacity: 0, translate) that would otherwise
 * leave content invisible if the animation never ran.
 * ------------------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }

  .animated,
  .wow {
    animation: none !important;
    opacity: 1 !important;
    visibility: visible !important;
    transform: none !important;
  }
}

/* `.box-title` is used for the small labelled blocks on a campus page (Service
 * Times / Address / Contact) and inside the Contact slice. Both rendered as
 * `h4`, which skipped a level under their section heading. Metrics restated so
 * the level can change without a visual difference — measured identical in both
 * contexts before the swap. */
.box-title {
  font-size: 20px;
  font-weight: 600;
  line-height: 28.34px;
  margin-bottom: 5px;
}

/* ==========================================================================
 * Message detail — /watch/<uid>
 *
 * The video is the reason the page exists, so it leads and it is the widest
 * thing on it. Everything else is set at a reading measure beneath.
 * ========================================================================== */

/* Prismic stores YouTube's oEmbed markup verbatim, and YouTube answers with
 * width="200" height="113" when no maxwidth is requested. The theme stretches
 * the iframe to 100% width but leaves that 113px height, which is why the
 * player rendered as a full-width sliver. Sizing the wrapper and making the
 * iframe fill it ignores the attributes entirely. */
.message-detail__video {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  margin-bottom: 40px;
  background-color: var(--brand-gunmetal);
  overflow: hidden;
}

.message-detail__video iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.message-detail__series {
  margin: 0 0 10px;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--brand-flagstone-ink);
}

.message-detail__series a {
  color: inherit;
}

.message-detail__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  margin: 0 0 28px;
  padding: 0;
  list-style: none;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  color: var(--brand-ink-warm);
}

.message-detail__body {
  max-width: 720px;
  font-family: var(--brand-font-body);
}

.message-detail__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 36px;
}

/* Same geometry as the banner's actions, inverted for a light surface. */
.message-detail__action {
  display: inline-block;
  padding: 12px 24px;
  border: 1px solid var(--brand-ink);
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-base);
  line-height: 1.2;
  color: var(--brand-ink);
  text-decoration: none;
  transition: background-color 0.3s, color 0.3s, border-color 0.3s;
}

.message-detail__action:hover {
  background-color: var(--brand-ink);
  color: var(--brand-white);
}

.message-detail__action--primary {
  background-color: var(--brand-refresh);
  border-color: var(--brand-refresh);
}

.message-detail__action--primary:hover {
  background-color: var(--brand-ink);
  border-color: var(--brand-ink);
}

a.message-detail__action:focus-visible {
  outline: 3px solid var(--brand-refresh);
  outline-offset: 3px;
}

@media (max-width: 575px) {
  .message-detail__actions {
    flex-direction: column;
    align-items: stretch;
  }
}

/* Pricing card titles are `h3` so they sit one level under the section `h2`
 * rather than skipping to `h4`. Metrics restated from what the `h4` resolved
 * to, so the level change is invisible. */
.pricing-card_title {
  font-size: 24px;
  font-weight: 600;
  line-height: 26.4px;
  margin-bottom: 12px;
}

/* ==========================================================================
 * Message hero — /watch/<uid>
 *
 * Replaces the artwork breadcrumb with the flat Gunmetal band the Visit hero
 * established. The thumbnail was doing little work behind a 55% scrim, and the
 * video below is the real image on this page; a solid ground lets the title
 * carry it instead of competing with a cropped still.
 * ========================================================================== */

.message-hero {
  padding: 150px 0 100px;
  margin-top: 106px; /* clears the fixed header, as breadcumb-wrapper does */
  background-color: var(--brand-gunmetal);
}

.message-hero__title {
  margin: 0 0 20px;
  max-width: 20ch; /* keeps long titles from running the full 1296 */
  font-family: var(--brand-font-heading);
  /* Scales between mobile and the comp's display size rather than stepping. */
  font-size: clamp(40px, 6vw, 76px);
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--brand-refresh);
}

.message-hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-l);
  color: var(--brand-clay);
}

.message-hero__meta a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.message-hero__meta a:hover {
  color: var(--brand-refresh);
}

/* The body sits on cream so the video and copy read as one warm block against
 * the dark hero, rather than white-on-white with the page. */
.message-detail {
  padding: 90px 0 100px;
  background-color: var(--brand-surface-muted);
}

@media (max-width: 767px) {
  .message-hero {
    padding: 100px 0 70px;
  }

  .message-detail {
    padding: 60px 0 70px;
  }
}

/* ==========================================================================
 * More from this Series — the dark band closing a message page.
 *
 * Full-bleed Gunmetal, bookending the hero so the cream body sits between two
 * dark bands. Because the tiles were built for a light surface, their text
 * colours are re-pointed here rather than duplicated into a second component.
 * ========================================================================== */

.message-related {
  padding: 72px 0 80px;
  background-color: var(--brand-clay);
}

.message-related__title {
  margin: 0 0 40px;
  font-family: var(--brand-font-heading);
  /* Deliberately below the hero's display size — this is a section label, not
     the page's subject. */
  font-size: clamp(24px, 2.4vw, 32px);
  font-weight: 600;
  line-height: 1.2;
  color: var(--brand-black-coffee);
}

/* Tile text re-pointed for the Clay ground. Black Coffee measures 5.68:1 on
 * Clay, so it clears AA for the small meta line as well as the titles — which
 * is why the meta keeps full opacity rather than the light-surface fade. */
.message-related .message-tile__title,
.message-related .message-tile__meta {
  color: var(--brand-black-coffee);
  opacity: 1;
}

.message-related .message-tile:hover .message-tile__title {
  color: var(--brand-ignition);
}

.message-related .message-tile__thumb {
  background-color: var(--brand-surface-muted);
}

@media (max-width: 767px) {
  .message-related {
    padding: 60px 0;
  }
}

/* Circle bullet between hero meta items. A pseudo-element rather than a text
 * character so screen readers read "Patrick Conrad, August 9, 2026" instead of
 * announcing a middot between each pair. */
.message-hero__meta li {
  display: inline-flex;
  align-items: center;
  gap: 16px;
}

.message-hero__meta li:not(:last-child)::after {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background-color: currentColor;
  opacity: 0.7;
}

.message-hero__actions {
  margin-top: 34px;
}

@media (max-width: 575px) {
  /* The bullets read as separators on one line; stacked, they dangle. */
  .message-hero__meta li:not(:last-child)::after {
    display: none;
  }
}

/* ==========================================================================
 * Speaker detail — /speakers/<uid>
 *
 * Borrows the message page's flat Gunmetal hero rather than a breadcumb: only
 * 3 of 32 speakers carry a photo, so an image-backed banner would show the
 * theme's 1920x540 placeholder on the other 29. A solid ground needs no art.
 *
 * The portrait is therefore a modifier, not the default — the common case is
 * a name and a message count, and the layout must not leave a hole where an
 * absent photo would have gone.
 * ========================================================================== */

.speaker-hero {
  padding: 150px 0 100px;
  margin-top: 106px; /* clears the fixed header, as breadcumb-wrapper does */
  background-color: var(--brand-gunmetal);
}

.speaker-hero__inner {
  display: flex;
  align-items: center;
  gap: 48px;
}

.speaker-hero__portrait {
  flex: 0 0 auto;
  width: 180px;
  height: 180px;
  border-radius: 50%;
  overflow: hidden;
  background-color: var(--brand-surface-dark-alt);
}

/* Two-class selector: the theme's `img:not([draggable])` is (0,1,1) and
 * carries `height: auto`, which would otherwise collapse the circle. */
.speaker-hero__portrait .speaker-hero__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.speaker-hero__title {
  margin: 0 0 20px;
  font-family: var(--brand-font-heading);
  /* Scales rather than steps, matching .message-hero__title. */
  font-size: clamp(40px, 6vw, 76px);
  font-weight: var(--brand-fw-heading-strong);
  line-height: 1.05;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--brand-refresh);
}

.speaker-hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-l);
  color: var(--brand-clay);
}

/* Dot separator, as on the message hero. */
.speaker-hero__meta li {
  display: inline-flex;
  align-items: center;
  gap: 16px;
}

.speaker-hero__meta li:not(:last-child)::after {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background-color: currentColor;
  opacity: 0.7;
}

/* Cream body against the dark hero, so the copy and grid read as one warm
 * block rather than white-on-white with the page — as on /watch/<uid>. */
.speaker-detail {
  padding: 90px 0 100px;
  background-color: var(--brand-surface-muted);
}

/* Capped at a readable measure; the container runs to ~1296px, far past the
 * ~75 characters a line should carry. */
.speaker-detail__bio {
  max-width: 720px;
  margin-bottom: 60px;
}

@media (max-width: 767px) {
  .speaker-hero {
    padding: 100px 0 70px;
  }

  .speaker-hero__inner {
    flex-direction: column;
    align-items: flex-start;
    gap: 28px;
  }

  .speaker-hero__portrait {
    width: 120px;
    height: 120px;
  }

  .speaker-detail {
    padding: 60px 0 70px;
  }

  /* Stacked, the bullets dangle at the end of a line rather than separating. */
  .speaker-hero__meta li:not(:last-child)::after {
    display: none;
  }
}

/* ==========================================================================
 * Campus team email — the "Meet Our Team" cards on a location page.
 *
 * Staff are now references to a Staff & Speakers document, which carries an
 * email; the live site offers one on /our-staff, so it is surfaced here too.
 * Sits under the role at the smaller body size so the name still leads the
 * card, and only renders when the person actually has an address.
 * ========================================================================== */

.team-card_email {
  display: inline-block;
  margin-top: 6px;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  color: var(--brand-ink-warm);
  text-decoration: underline;
  text-underline-offset: 3px;
  word-break: break-word;
}

.team-card_email:hover {
  color: var(--brand-ignition);
}

/* ==========================================================================
 * Visit panel — service times, address and contact on a campus page.
 *
 * Replaces three stacked `contact-card` boxes whose only styling was a bold
 * label above plain text: the most consequential information on the page
 * (when to turn up, and where) read as the least important thing on it.
 *
 * Gunmetal, so it holds the column against the cream body the way the message
 * and speaker heroes hold their pages, and so the service times can carry
 * Refresh — the accent this site reserves for the thing it wants you to do.
 * Icons are CSS masks rather than <img>, so they follow `currentColor` through
 * hover and inversion instead of shipping a second asset per state.
 * ========================================================================== */

.visit-panel {
  padding: 40px;
  background-color: var(--brand-gunmetal);
  color: var(--brand-ink-inverse);
}

.visit-panel__eyebrow {
  display: block;
  margin-bottom: 28px;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-eyebrow-s);
  font-weight: var(--brand-fw-body-medium);
  letter-spacing: var(--brand-ls-eyebrow);
  text-transform: uppercase;
  color: var(--brand-clay);
}

.visit-panel__group + .visit-panel__group {
  margin-top: 32px;
  padding-top: 32px;
  border-top: 1px solid rgb(255 255 255 / 0.12);
}

.visit-panel__label {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 12px;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  font-weight: var(--brand-fw-body-medium);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--brand-clay);
}

/* Mask rather than <img>: the glyph inherits the label's colour, so one asset
 * serves the panel here and any inverted variant later. */
.visit-panel__icon {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  background-color: currentColor;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.visit-panel__icon--time {
  -webkit-mask-image: url("../img/icon/clock-outline.svg");
  mask-image: url("../img/icon/clock-outline.svg");
}

.visit-panel__icon--place {
  /* A solid silhouette with no counter, unlike the line-art clock and phone —
   * at 16px it read as an anonymous dot, so it gets a little more room. */
  width: 14px;
  height: 19px;
  -webkit-mask-image: url("../img/icon/location-pin-alt.svg");
  mask-image: url("../img/icon/location-pin-alt.svg");
}

.visit-panel__icon--contact {
  -webkit-mask-image: url("../img/icon/phone.svg");
  mask-image: url("../img/icon/phone.svg");
}

/* The times are the answer to "when can I come?" — sized to be read across a
 * room, not squinted at. */
.visit-panel__times {
  margin: 0;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-h5);
  font-weight: var(--brand-fw-heading);
  line-height: var(--brand-lh-heading-tight);
  color: var(--brand-refresh);
}

.visit-panel__day {
  display: block;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-base);
  font-weight: var(--brand-fw-body);
  letter-spacing: normal;
  text-transform: none;
  color: var(--brand-ink-inverse);
}

.visit-panel__address {
  margin: 0;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  line-height: 1.5;
  color: var(--brand-ink-inverse);
}

 /* Two-class selector: the theme colours `p` directly, and a colour inherited
  * from the container cannot beat that — the address rendered Gunmetal on
  * Gunmetal, invisible. Same trap as `.team-card_desig`. */
.visit-panel__address p {
  margin: 0;
  color: var(--brand-ink-inverse);
}

.visit-panel__link {
  display: inline-block;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-base);
  color: var(--brand-ink-inverse);
  text-decoration: underline;
  text-underline-offset: 3px;
  word-break: break-word;
}

.visit-panel__link:hover {
  color: var(--brand-refresh);
}

.visit-panel__directions {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: 18px;
  padding: 13px 26px;
  background-color: var(--brand-refresh);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-link);
  font-weight: var(--brand-fw-link);
  letter-spacing: var(--brand-ls-link);
  text-transform: uppercase;
  color: var(--brand-ink);
  transition: background-color 0.3s ease, transform 0.3s ease;
}

.visit-panel__directions:hover {
  background-color: var(--brand-white);
  color: var(--brand-ink);
  transform: translateY(-2px);
}

/* Welcome CTA — the live site offers one under the visitor copy, per campus. */
.campus-welcome__cta {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: 28px;
  padding: 15px 30px;
  background-color: var(--brand-gunmetal);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-link);
  font-weight: var(--brand-fw-link);
  letter-spacing: var(--brand-ls-link);
  text-transform: uppercase;
  color: var(--brand-ink-inverse);
  transition: background-color 0.3s ease, transform 0.3s ease;
}

.campus-welcome__cta:hover {
  background-color: var(--brand-ignition);
  color: var(--brand-ink-inverse);
  transform: translateY(-2px);
}

@media (max-width: 991px) {
  .visit-panel {
    padding: 32px 28px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .visit-panel__directions,
  .campus-welcome__cta {
    transition: none;
  }
  .visit-panel__directions:hover,
  .campus-welcome__cta:hover {
    transform: none;
  }
}

/* ==========================================================================
 * Section intro — the standing eyebrow / heading / text block.
 *
 * Owns no background and no outer spacing: a wrapping Section supplies the
 * ground and a Spacer the rhythm, so this can open any section without
 * fighting either.
 * ========================================================================== */

.section-intro__heading > :first-child {
  margin-top: 0;
}

.section-intro__heading > :last-child {
  margin-bottom: 0;
}

.section-intro .eyebrow + .section-intro__heading {
  margin-top: 14px;
}

.section-intro__text {
  margin-top: 20px;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  line-height: var(--brand-lh-body);
}

.section-intro__text > :last-child {
  margin-bottom: 0;
}

/* Capped at a readable measure — the 1296 container runs far past the ~75
 * characters a line should carry. Opt out with Text Width: Full when the block
 * sits in a column that is already narrow. */
.section-intro--measure .section-intro__heading,
.section-intro--measure .section-intro__text {
  max-width: 720px;
}

.section-intro--centre {
  text-align: center;
}

.section-intro--centre.section-intro--measure .section-intro__heading,
.section-intro--centre.section-intro--measure .section-intro__text {
  margin-left: auto;
  margin-right: auto;
}

/* Campus welcome heading — the eyebrow/heading pair opening the visitor copy,
 * matching the live campus pages' "What to Expect" over "Welcome to Compel
 * <campus>". Fields rather than a SectionIntro slice because this column is a
 * fixed two-column layout, not a slice zone. */
.campus-welcome__heading {
  margin: 14px 0 24px;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-h5);
  font-weight: var(--brand-fw-heading);
  line-height: var(--brand-lh-heading-tight);
  color: var(--brand-ink);
}

/* ==========================================================================
 * Campus team — the staff grid on a campus page.
 *
 * The theme already takes the portraits from grayscale to colour with a 1.05
 * zoom on hover; this adds the reveal and the lift. Cards come in from below in
 * a staggered sweep (WOW's `img-custom-anim-bottom`, delayed per column in the
 * component), then lift under the cursor so the whole card reads as the target
 * rather than just the photograph inside it.
 * ========================================================================== */

/* The eyebrow was sitting on the heading. `.sec-title` carries a
 * `margin-top: -9.6px` — the theme's correction for its 66px line-height — so
 * a margin here nets out ~10px smaller than it reads: 24px leaves ~14px of
 * actual gap. */
.campus-team .title-area .eyebrow {
  margin-bottom: 24px;
}

/* The slice is the last thing on a campus page, so with no margin of its own
 * the bottom row of cards butted straight onto the footer. `--section-space` is
 * the site's own 120px section spacer, which is what every neighbouring block
 * is already separated by. Halved on mobile, matching the theme's own
 * `--section-space-mobile`. */
.campus-team {
  margin-bottom: var(--section-space);
}

@media (max-width: 991px) {
  .campus-team {
    margin-bottom: 80px;
  }
}

/* `gy-4` is 24px, which is fine between two lines of text and tight between two
 * rows of cards — each is a photo with a name and role stacked under it, and
 * the hover lift raises a card 6px into the gap above it. Only applied where
 * the grid actually has rows: below `md` it is a single column, where the same
 * change would just lengthen the scroll.
 *
 * Set through Bootstrap's own `--bs-gutter-y` rather than a margin, so the
 * columns' padding maths stays the grid's. (0,2,0) beats `.gy-4`'s (0,1,0). */
@media (min-width: 768px) {
  .campus-team .row {
    --bs-gutter-y: 40px;
  }
}

.campus-team__card {
  transition: transform 0.4s ease, box-shadow 0.4s ease;
  will-change: transform;
}

.campus-team__card:hover {
  transform: translateY(-6px);
  box-shadow: 0 18px 40px rgb(7 43 49 / 0.18);
}

/* The name is the card's title; picking up the accent on hover ties it to the
 * arrow links and the play badge, which do the same. */
.campus-team__card:hover .team-card_title {
  color: var(--brand-refresh);
}

.campus-team__card .team-card_title {
  transition: color 0.3s ease;
}

@media (prefers-reduced-motion: reduce) {
  .campus-team__card,
  .campus-team__card .team-card_title {
    transition: none;
  }

  .campus-team__card:hover {
    transform: none;
  }

  /* WOW adds `animated` with a keyframed entrance; without this the cards
   * still fly in for someone who asked for no motion. */
  .campus-team__card.wow.animated {
    animation: none !important;
    opacity: 1 !important;
  }
}

/* ==========================================================================
 * EventList — upcoming events with an optional filter.
 *
 * Composition from Figma Visit 291:654 ("Bringing Your Family?"), the nearest
 * listing in the comps. On the 1440 frame: 72px page padding around a 1296
 * inner, split 416 (sticky text) / 134 gutter / 746 (cards). Cards pitch every
 * 354px at 315px tall, so a 39px gap; each is a 306px media panel butted
 * against a solid colour panel with 40px padding.
 *
 * Percentages rather than pixels so the split holds at the container's other
 * widths — same approach as Feature and MobileApp.
 * ========================================================================== */

.event-list {
  padding: 120px 0;
}

.event-list__layout {
  display: flex;
  align-items: flex-start;
  gap: 10.34%; /* 134 / 1296 */
}

.event-list__aside {
  flex: 0 0 32.1%; /* 416 / 1296 */
  position: sticky;
  /* Clears the fixed header and leaves the comp's breathing room above the
     first card. */
  top: 120px;
}

.event-list__aside-inner {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.event-list__heading {
  margin: 0;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-h3);
  font-weight: var(--brand-fw-heading-bold);
  line-height: var(--brand-lh-heading);
  /* Figma gives -1.8px against the 61px comp size. In em it survives the
     clamp down to mobile, where a fixed -1.8px crowds the words. */
  letter-spacing: -0.03em;
  text-transform: uppercase;
  color: var(--brand-ink);
}

/* The line box is this element, so the metrics belong here and not on the
   wrapper — a bare `p { line-height: 1.75 }` in the theme wins otherwise. */
.event-list__text {
  margin: 0;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  font-weight: 400;
  line-height: var(--brand-lh-body);
  letter-spacing: normal;
  color: var(--brand-ignition);
}

.event-list__main {
  flex: 1 1 auto;
  min-width: 0;
}

/* --- filter bar --- */

.event-list__filter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px 24px;
  margin-bottom: 32px;
}

.event-list__filter-label {
  margin: 0;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-eyebrow-s);
  font-weight: var(--brand-fw-link);
  line-height: var(--brand-lh-heading);
  letter-spacing: var(--brand-ls-eyebrow);
  text-transform: uppercase;
  color: var(--brand-ink);
}

.event-list__filter-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}

/* Bootstrap's reboot styles select elements directly, so margin, height and
   background are restated rather than left to be inherited. */
.event-list__select {
  margin: 0;
  height: 48px;
  min-width: 200px;
  padding: 0 40px 0 16px;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-base);
  line-height: 1.6;
  letter-spacing: normal;
  color: var(--brand-ink);
  background-color: var(--brand-white);
  border: 2px solid var(--brand-border);
  border-radius: 0;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='%23072b31' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 16px center;
  background-size: 12px 8px;
}

.event-list__select:focus-visible {
  outline: 2px solid var(--brand-ignition);
  outline-offset: 2px;
}

.event-list__count {
  margin: 0 0 24px;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  line-height: 1.6;
  letter-spacing: normal;
  color: var(--brand-ink);
  opacity: 0.7;
}

.event-list__empty {
  margin: 0;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  line-height: var(--brand-lh-body);
  letter-spacing: normal;
  color: var(--brand-ink);
}

/* --- cards --- */

.event-list__items {
  display: flex;
  flex-direction: column;
  gap: 39px;
  /* Reboot puts padding-left and a bottom margin on every ul. */
  margin: 0;
  padding: 0;
  list-style: none;
}

.event-card {
  position: relative; /* anchors the title link's full-card hit area */
  display: flex;
  align-items: stretch;
  min-height: 315px;
  overflow: hidden;
}

.event-card--mint {
  background-color: var(--brand-mint);
}

.event-card--refresh {
  background-color: var(--brand-refresh);
}

.event-card--coral {
  background-color: var(--brand-coral);
}

.event-card__media {
  flex: 0 0 41%; /* 306 / 746 */
  position: relative;
  overflow: hidden;
}

/* Two classes on purpose. The theme's `img:not([draggable]) { height: auto }`
   is (0,1,1) and beats any single-class rule, so a portrait upload would
   stretch this panel instead of being cropped inside it. */
.event-card__media .event-card__image--fill {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Scale past the box so the blur doesn't show a hard edge at the crop. */
  transform: scale(1.18);
  filter: blur(28px);
  pointer-events: none;
  user-select: none;
}

.event-card__media .event-card__image {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.event-card__body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 24px;
  padding: 40px;
}

.event-card__text {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.event-card__title {
  margin: 0;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-h5);
  font-weight: var(--brand-fw-heading);
  line-height: var(--brand-lh-heading-tight);
  /* Declared even though the answer is "none": this class is h3-based and
     brand.css sets h3 to -1.8px, which Figma's Heading 5 does not want. */
  letter-spacing: normal;
  color: var(--brand-gunmetal);
}

.event-card__link {
  color: inherit;
  text-decoration: none;
}

/* The card's only interactive element, so the hit area is the whole card
   rather than the title alone. */
.event-card__link::after {
  content: "";
  position: absolute;
  inset: 0;
}

.event-card__link:focus-visible::after {
  outline: 3px solid var(--brand-gunmetal);
  outline-offset: -3px;
}

.event-card__date {
  margin: 0;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-eyebrow-s);
  font-weight: var(--brand-fw-link);
  line-height: var(--brand-lh-heading);
  letter-spacing: var(--brand-ls-eyebrow);
  text-transform: uppercase;
  color: var(--brand-gunmetal);
}

.event-card__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  margin: 4px 0 0;
  padding: 0;
  list-style: none;
}

/* Metrics on the li, which forms the line box — styling only a child would
   leave these rows on the inherited 16px/1.6. */
.event-card__meta li {
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: normal;
  color: var(--brand-gunmetal);
  opacity: 0.8;
}

.event-card__meta li + li::before {
  content: "· ";
}

.event-card__excerpt {
  margin: 8px 0 0;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-base);
  font-weight: 500;
  line-height: var(--brand-lh-body);
  letter-spacing: normal;
  color: var(--brand-gunmetal);
}

/* Mirrors the comp's Text Link: uppercase label, arrow, 2px rule beneath. */
.event-card__cta {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  gap: 8px;
  padding-bottom: 4px;
  border-bottom: 2px solid var(--brand-gunmetal);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-link);
  font-weight: var(--brand-fw-link);
  line-height: normal;
  letter-spacing: 0.96px;
  text-transform: uppercase;
  color: var(--brand-gunmetal);
}

@media (max-width: 1199px) {
  .event-list__layout {
    gap: 6%;
  }
  .event-list__aside {
    flex-basis: 36%;
  }
}

/* The sticky column cannot hold once the layout stacks, and a 306px media
   panel beside 40px padding leaves nothing for the copy. */
@media (max-width: 991px) {
  .event-list {
    padding: 80px 0;
  }
  .event-list__layout {
    flex-direction: column;
    gap: 48px;
  }
  .event-list__aside {
    position: static;
    flex-basis: auto;
    width: 100%;
  }
  .event-card {
    flex-direction: column;
    min-height: 0;
  }
  .event-card__media {
    flex-basis: auto;
    aspect-ratio: 16 / 9;
  }
  /* Panel is already 16:9 here, so the fill would be fully covered. */
  .event-card__media .event-card__image--fill {
    display: none;
  }
}

@media (max-width: 575px) {
  .event-card__body {
    padding: 28px;
  }
  .event-list__select {
    min-width: 0;
    width: 100%;
  }
}

/* ==========================================================================
 * Event detail — /events/:uid
 *
 * No comp exists for this page, so it is assembled from the vocabulary the
 * comps already established: the Gunmetal masthead and clamped Rubik title
 * from the Hero banner, the solid amber panel and arrow link from the
 * EventList cards and CtaBar.
 *
 * Two columns on the 1296 inner — 818 of copy beside a 416 sticky panel with a
 * 62 gutter, the same split the Visit page uses for its sticky sections.
 * ========================================================================== */

/* `.site-header` is absolutely positioned over the top of the page and is
   106px tall, so anything above ~112px of padding renders underneath it. The
   Hero banner clears it the same way. */
.event-detail__masthead {
  padding: 138px 0 0;
  background-color: var(--brand-gunmetal);
}

/* Reboot styles `a` directly, so text-decoration and colour are restated. */
.event-detail__back {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 48px;
  color: var(--brand-refresh);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-p-s);
  font-weight: var(--brand-fw-link);
  line-height: 1.4;
  letter-spacing: var(--brand-ls-eyebrow);
  text-transform: uppercase;
  text-decoration: none;
  transition: color 0.2s ease;
}

.event-detail__back:hover,
.event-detail__back:focus-visible {
  color: var(--brand-white);
}

/* The shared arrow asset points up-and-right and means "go onward". Mirrored
   it points up-and-left, which reads as "back" — rotating it 180deg instead
   would point down-left, which reads as "descend". Same mask, no second file. */
.event-detail__back-arrow {
  display: inline-block;
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  transform: scaleX(-1);
  background-color: currentColor;
  -webkit-mask: url("/assets/img/icon/arrow-left-top.svg") no-repeat center /
    contain;
  mask: url("/assets/img/icon/arrow-left-top.svg") no-repeat center / contain;
}

.event-detail__masthead-layout {
  display: flex;
  align-items: flex-end;
  gap: 6.4%;
}

.event-detail__masthead-copy {
  flex: 1 1 auto;
  min-width: 0;
  padding-bottom: 96px;
}

/* Figma H2 at the comp size, clamped for mobile. Tracking in em so it scales
   with the clamp instead of crowding the words at 36px. */
.event-detail__title {
  margin: 16px 0 0;
  color: var(--brand-white);
  font-family: var(--brand-font-heading);
  font-size: clamp(36px, 5.3vw, 76px);
  font-weight: var(--brand-fw-heading-bold);
  line-height: var(--brand-lh-heading);
  letter-spacing: -0.024em;
  text-transform: uppercase;
}

.event-detail__when {
  margin: 24px 0 0;
  color: var(--brand-refresh);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-p-m);
  font-weight: var(--brand-fw-body-medium);
  line-height: 1.4;
  letter-spacing: normal;
}

.event-detail__when-day {
  color: var(--brand-clay);
}

.event-detail__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 24px;
  margin-top: 40px;
}

.event-detail__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 16px 24px;
  border: 0;
  background-color: var(--brand-refresh);
  color: var(--brand-gunmetal);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-link);
  font-weight: var(--brand-fw-link);
  line-height: 26px;
  letter-spacing: normal;
  text-transform: uppercase;
  text-decoration: none;
  transition: background-color 0.2s ease, color 0.2s ease;
}

.event-detail__btn:hover,
.event-detail__btn:focus-visible {
  background-color: var(--brand-white);
  color: var(--brand-gunmetal);
}

.event-detail__cost {
  color: var(--brand-clay);
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-base);
  font-weight: 500;
  line-height: 1.6;
  letter-spacing: normal;
}

/* The artwork sits flush with the band's bottom edge and overlaps the section
   beneath, so the masthead reads as one composition rather than two stacked
   strips. The negative margin is reclaimed by the body's top padding. */
.event-detail__art {
  flex: 0 0 46%;
  position: relative;
  margin-bottom: -80px;
  overflow: hidden;
}

/* Two classes: the theme's `img:not([draggable]) { height: auto }` is (0,1,1)
   and would stretch this box past its aspect-ratio on a non-16:9 upload. */
.event-detail__art .event-detail__art-image {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

.event-detail__body {
  padding: 136px 0 120px; /* 120 + the 16 the artwork overlaps */
  background-color: var(--brand-surface-light);
}

.event-detail__layout {
  display: flex;
  align-items: flex-start;
  gap: 4.8%; /* 62 / 1296 */
}

.event-detail__main {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 818px;
}

.event-detail__paragraph {
  margin: 0 0 24px;
  color: var(--brand-ink);
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  font-weight: 400;
  line-height: var(--brand-lh-body);
  letter-spacing: normal;
}

.event-detail__paragraph:last-child {
  margin-bottom: 0;
}

.event-detail__subheading {
  margin: 40px 0 16px;
  color: var(--brand-ink);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-h5);
  font-weight: var(--brand-fw-heading);
  line-height: var(--brand-lh-heading-tight);
  /* Declared even though the answer is "none" — brand.css sets h3 to -1.8px. */
  letter-spacing: normal;
}

.event-detail__list {
  margin: 0 0 24px;
  padding-left: 20px;
}

/* Metrics on the li, which forms the line box. */
.event-detail__list li {
  margin-bottom: 8px;
  color: var(--brand-ink);
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  line-height: var(--brand-lh-body);
  letter-spacing: normal;
}

/* --- sticky facts panel --- */

.event-detail__facts {
  flex: 0 0 32.1%; /* 416 / 1296 */
  position: sticky;
  top: 120px;
}

.event-detail__facts-inner {
  padding: 40px;
  background-color: var(--brand-refresh);
}

.event-detail__facts-list {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.event-detail__fact dt {
  margin: 0 0 6px;
  color: var(--brand-gunmetal);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-eyebrow-s);
  font-weight: var(--brand-fw-link);
  line-height: var(--brand-lh-heading);
  letter-spacing: var(--brand-ls-eyebrow);
  text-transform: uppercase;
  opacity: 0.75;
}

/* Reboot gives dd a left margin; it has to go or the values indent. */
.event-detail__fact dd {
  margin: 0;
  color: var(--brand-gunmetal);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-h6);
  font-weight: var(--brand-fw-heading);
  line-height: var(--brand-lh-heading-tight);
  letter-spacing: normal;
}

.event-detail__fact-sub {
  display: block;
  margin-top: 4px;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-base);
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: normal;
  opacity: 0.85;
}

.event-detail__fact-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  padding-bottom: 3px;
  border-bottom: 2px solid var(--brand-gunmetal);
  color: var(--brand-gunmetal);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-p-s);
  font-weight: var(--brand-fw-link);
  line-height: normal;
  letter-spacing: 0.96px;
  text-transform: uppercase;
  text-decoration: none;
}

.event-detail__facts-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 32px;
  padding: 16px 24px;
  border: 0;
  background-color: var(--brand-gunmetal);
  color: var(--brand-refresh);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-link);
  font-weight: var(--brand-fw-link);
  line-height: 26px;
  letter-spacing: normal;
  text-transform: uppercase;
  text-decoration: none;
  transition: background-color 0.2s ease, color 0.2s ease;
}

.event-detail__facts-btn:hover,
.event-detail__facts-btn:focus-visible {
  background-color: var(--brand-white);
  color: var(--brand-gunmetal);
}

@media (max-width: 1199px) {
  .event-detail__masthead-layout {
    gap: 4%;
  }
  .event-detail__art {
    flex-basis: 42%;
  }
  .event-detail__layout {
    gap: 3.5%;
  }
  .event-detail__facts {
    flex-basis: 36%;
  }
}

/* Stacked: the artwork leads, the copy follows, and the facts panel stops
   being sticky because it would otherwise pin above the description. */
@media (max-width: 991px) {
  .event-detail__masthead {
    padding-top: 124px;
  }
  .event-detail__masthead-layout {
    flex-direction: column-reverse;
    align-items: stretch;
    gap: 40px;
  }
  .event-detail__masthead-copy {
    padding-bottom: 64px;
  }
  .event-detail__art {
    flex-basis: auto;
    margin-bottom: 0;
  }
  .event-detail__body {
    padding: 64px 0 80px;
  }
  .event-detail__layout {
    flex-direction: column;
    gap: 48px;
  }
  .event-detail__facts {
    position: static;
    flex-basis: auto;
    width: 100%;
  }
}

@media (max-width: 575px) {
  .event-detail__facts-inner {
    padding: 28px;
  }
  .event-detail__actions {
    gap: 16px;
  }
}

/* Secondary action under the register button — "Add to Calendar". Quieter than
   the button but on the same amber panel, so it is an underlined link rather
   than a second solid block competing with the primary CTA. */
.event-detail__facts-secondary {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 20px;
  padding-bottom: 3px;
  border-bottom: 2px solid var(--brand-gunmetal);
  color: var(--brand-gunmetal);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-p-s);
  font-weight: var(--brand-fw-link);
  line-height: normal;
  letter-spacing: 0.96px;
  text-transform: uppercase;
  text-decoration: none;
  transition: opacity 0.2s ease;
}

.event-detail__facts-secondary:hover,
.event-detail__facts-secondary:focus-visible {
  color: var(--brand-gunmetal);
  opacity: 0.7;
}

/* ==========================================================================
 * Connect Group type colours — one source of truth.
 *
 * Set on any element carrying `data-group-type`, so a filter tile, a card and
 * a hero marquee plate for the same type cannot drift apart. Before this they
 * were assigned by position in three separate places, which is why the filters
 * did not match the cards they filtered to.
 *
 * Two tokens per type, because they do different jobs and have different
 * constraints:
 *   --cg-plate  a light fill, kept light enough that the hero's wordmark
 *               artwork stays legible through `mix-blend-mode: multiply`.
 *   --cg-ink    the same hue darkened until it passes WCAG AA as small text.
 *               Every plate colour fails that badly — Refresh is 1.78:1 on
 *               white, mint 1.57:1 — so the eyebrow and border need their own
 *               value. These were derived by dropping lightness at a fixed hue
 *               and measured: worst case is Serve at 4.97:1 on the band.
 * ========================================================================== */

[data-group-type] {
  --cg-plate: var(--brand-clay);
  --cg-ink: var(--brand-gunmetal);
}

[data-group-type="bible-study"] {
  --cg-plate: var(--brand-flagstone);
  --cg-ink: #3f6b57;
}

[data-group-type="finance"] {
  --cg-plate: var(--brand-refresh);
  --cg-ink: #8a5a12;
}

[data-group-type="marriage"] {
  --cg-plate: var(--brand-coral);
  --cg-ink: #b23a3e;
}

[data-group-type="serve"] {
  --cg-plate: var(--brand-mint);
  --cg-ink: #3c7a46;
}

[data-group-type="social"] {
  --cg-plate: var(--brand-clay);
  --cg-ink: #75604b;
}

[data-group-type="support"] {
  --cg-plate: color-mix(
    in srgb,
    var(--brand-ignition) 28%,
    var(--brand-surface-light)
  );
  --cg-ink: var(--brand-ignition);
}

/* ==========================================================================
 * ConnectGroupList — the filterable Connect Groups directory.
 *
 * Shares EventList's composition (Figma Visit 291:654): 72px page padding
 * around a 1296 inner, split 416 (sticky text) / 134 gutter / 746 (cards).
 *
 * Where it departs, and why: EventList runs one wide card per row because a
 * handful of events fit that way. There are 166 groups, so these go two-up in
 * the 746 column — 361px each — and the image/colour panels that EventList
 * butts side by side stack instead, since 361px cannot hold a 306px media
 * panel beside 40px of padding.
 *
 * Percentages rather than pixels so the split holds at the container's other
 * widths, as in EventList, Feature and MobileApp.
 * ========================================================================== */

.group-list {
  padding: 120px 0;
}

.group-list__layout {
  display: flex;
  align-items: flex-start;
  gap: 10.34%; /* 134 / 1296 */
}

.group-list__aside {
  flex: 0 0 32.1%; /* 416 / 1296 */
  position: sticky;
  /* Clears the fixed header, as EventList's does. */
  top: 120px;
}

.group-list__aside-inner {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.group-list__heading {
  margin: 0;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-h3);
  font-weight: var(--brand-fw-heading-bold);
  line-height: var(--brand-lh-heading);
  letter-spacing: -0.03em;
  text-transform: uppercase;
  color: var(--brand-ink);
}

/* The line box is this element, so the metrics belong here and not on the
   wrapper — a bare `p { line-height: 1.75 }` in the theme wins otherwise. */
.group-list__text {
  margin: 0;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  font-weight: 400;
  line-height: var(--brand-lh-body);
  letter-spacing: normal;
  color: var(--brand-ignition);
}

/* --- at-a-glance stats in the sticky column --- */

.group-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin: 16px 0 0;
}

.group-stat {
  flex: 1 1 0;
  min-width: 96px;
  padding: 16px 18px;
  background-color: var(--brand-surface-muted);
}

/* Label above figure, reading order unchanged: `dt` is ordered first in the
   DOM for assistive tech and flipped visually, rather than reversing the
   markup and leaving a definition list whose terms follow their values. */
.group-stat {
  display: flex;
  flex-direction: column-reverse;
  gap: 2px;
}

.group-stat dt {
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-eyebrow-s);
  font-weight: var(--brand-fw-link);
  line-height: 1.2;
  letter-spacing: var(--brand-ls-eyebrow);
  text-transform: uppercase;
  color: var(--brand-ink);
  opacity: 0.65;
}

/* Reboot gives dd a left margin; it has to go or the figures indent. */
.group-stat dd {
  margin: 0;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-h5);
  font-weight: var(--brand-fw-heading-bold);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--brand-gunmetal);
}

.group-list__main {
  flex: 1 1 auto;
  min-width: 0;
}

/* --- filter bar --- */

.group-filter {
  margin-bottom: 40px;
}

.group-filter__label {
  margin: 0 0 16px;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-eyebrow-s);
  font-weight: var(--brand-fw-link);
  line-height: var(--brand-lh-heading);
  letter-spacing: var(--brand-ls-eyebrow);
  text-transform: uppercase;
  color: var(--brand-ink);
}

/* --- group type quick-pick tiles ---
 *
 * Text, not the old site's wordmark graphics: those are 1920x540 raster banners
 * whose type lands around 11px in a tile this size, they carry the previous
 * site's styling rather than the brand's, and at six-up they were the heaviest
 * thing on the page. A tile is now the type set in Rubik with its count, in
 * that type's own ink.
 * --- */

.group-types {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  margin: 0 0 28px;
  padding: 0;
  list-style: none;
}

/* Reboot styles `a` directly, so decoration and colour are restated. */
.group-type {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 14px 16px;
  border: 2px solid var(--brand-border);
  border-left: 4px solid var(--cg-ink);
  background-color: var(--brand-white);
  color: var(--cg-ink);
  text-decoration: none;
  transition:
    background-color 0.2s ease,
    border-color 0.2s ease,
    color 0.2s ease,
    transform 0.25s cubic-bezier(0.2, 0.7, 0.3, 1);
}

.group-type:hover {
  transform: translateY(-2px);
  border-color: var(--cg-ink);
}

.group-type:focus-visible {
  outline: 3px solid var(--cg-ink);
  outline-offset: 2px;
}

.group-type__name {
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-eyebrow-s);
  font-weight: var(--brand-fw-link);
  line-height: 1.3;
  letter-spacing: var(--brand-ls-eyebrow);
  text-transform: uppercase;
}

.group-type__count {
  flex: 0 0 auto;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: normal;
  opacity: 0.7;
}

/* The selected type fills with its own ink, so the strip reads as one chosen
   tile among six. White on every one of these passes AA — they were derived
   against that requirement. */
.group-type--on {
  background-color: var(--cg-ink);
  border-color: var(--cg-ink);
  color: var(--brand-white);
}

.group-type--on .group-type__count {
  opacity: 0.85;
}

/* --- active filter chips --- */

.group-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Reboot gives buttons a background and padding of their own. */
.group-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  border: 2px solid var(--brand-gunmetal);
  border-radius: 999px;
  background: none;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: normal;
  color: var(--brand-gunmetal);
  cursor: pointer;
  transition:
    background-color 0.2s ease,
    color 0.2s ease,
    border-color 0.2s ease;
}

.group-chip:hover,
.group-chip:focus-visible {
  background-color: var(--brand-ignition);
  border-color: var(--brand-ignition);
  color: var(--brand-white);
}

.group-chip__x {
  font-size: 16px;
  line-height: 1;
  opacity: 0.7;
}

/* Six controls in a fixed three-up grid rather than auto-fit: six cells over
   four columns leaves a ragged 4 + 2, where three columns gives two even rows
   of three. Fixed tracks also keep the labels aligned across rows. */
.group-filter__controls {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px 20px;
}

.group-filter__field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

/* Visible labels rather than EventList's aria-label-only selects: at six
   controls, "Any / Any / Any" gives a sighted user nothing to aim at. */
.group-filter__name {
  margin: 0;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: normal;
  color: var(--brand-ink);
  opacity: 0.7;
}

/* Bootstrap's reboot styles select elements directly, so margin, height and
   background are restated rather than left to be inherited. */
.group-filter__select {
  margin: 0;
  width: 100%;
  height: 48px;
  padding: 0 40px 0 16px;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-base);
  line-height: 1.6;
  letter-spacing: normal;
  color: var(--brand-ink);
  background-color: var(--brand-white);
  border: 2px solid var(--brand-border);
  border-radius: 0;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='%23072b31' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 16px center;
  background-size: 12px 8px;
}

.group-filter__select:focus-visible {
  outline: 2px solid var(--brand-ignition);
  outline-offset: 2px;
}

/* The checkbox has no label row above it, so it aligns to the selects' boxes
   rather than to the top of the track. */
.group-filter__field--check {
  justify-content: flex-end;
}

/* --- childcare switch ---
 * A bare checkbox beside four styled selects read as an afterthought, and
 * "on/off" is what this control actually means. It matches the selects' 48px
 * box so the filter grid stays on one baseline.
 */

.group-toggle {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 48px;
  padding: 0 16px;
  margin: 0;
  border: 2px solid var(--brand-border);
  background-color: var(--brand-white);
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-base);
  line-height: 1.4;
  letter-spacing: normal;
  color: var(--brand-ink);
  cursor: pointer;
  transition:
    border-color 0.2s ease,
    background-color 0.2s ease;
}

.group-toggle:hover {
  border-color: var(--brand-gunmetal);
}

/* Hidden but not removed: `display: none` or `visibility: hidden` would take
   the checkbox out of the tab order and the accessibility tree along with it.
   This keeps both and moves the visible affordance to the track.
 *
 * Two classes, and display/visibility/size all restated. style.css hides every
 * native checkbox outright — `input[type=checkbox] { visibility: hidden;
 * width: 0; height: 0; display: none }` — so the theme can draw its own via
 * `~ label::before`. That selector is (0,1,1); a lone `.group-toggle__input` is
 * (0,1,0) and loses to it whatever the file order, which left this switch
 * unreachable by keyboard while looking perfectly fine. */
.group-toggle .group-toggle__input {
  position: absolute;
  display: block;
  visibility: visible;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  opacity: 0;
  pointer-events: none;
}

.group-toggle__track {
  position: relative;
  flex: 0 0 auto;
  width: 40px;
  height: 22px;
  border-radius: 999px;
  background-color: var(--brand-border);
  transition: background-color 0.2s ease;
}

.group-toggle__knob {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background-color: var(--brand-white);
  box-shadow: 0 1px 3px rgba(7, 43, 49, 0.35);
  transition: transform 0.2s cubic-bezier(0.2, 0.7, 0.3, 1);
}

.group-toggle__input:checked ~ .group-toggle__track {
  background-color: var(--brand-ignition);
}

/* 40 track − 16 knob − 3 left − 3 right = 18px of travel. */
.group-toggle__input:checked ~ .group-toggle__track .group-toggle__knob {
  transform: translateX(18px);
}

.group-toggle__input:checked ~ .group-toggle__label {
  font-weight: 600;
  color: var(--brand-ignition);
}

/* The input carries focus but is invisible, so the ring is drawn on the track
   the eye is actually following. */
.group-toggle__input:focus-visible ~ .group-toggle__track {
  outline: 3px solid var(--brand-ignition);
  outline-offset: 3px;
}

.group-toggle__label {
  transition: color 0.2s ease;
}

@media (prefers-reduced-motion: reduce) {
  .group-toggle,
  .group-toggle__track,
  .group-toggle__knob,
  .group-toggle__label {
    transition: none;
  }
}

.group-filter__status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 20px;
  margin-top: 24px;
}

.group-filter__count {
  margin: 0;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  line-height: 1.6;
  letter-spacing: normal;
  color: var(--brand-ink);
}

/* The running total is the one number on the page that moves, so it is set
   like a figure rather than like body copy. Tabular numerals stop the row
   jittering as it counts through different digit widths. */
.group-filter__count-num {
  margin-right: 6px;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-h6);
  font-weight: var(--brand-fw-heading);
  font-variant-numeric: tabular-nums;
  color: var(--brand-ignition);
}

/* Reboot gives buttons a background and padding; this is a text control. */
.group-filter__clear {
  padding: 0;
  border: 0;
  border-bottom: 2px solid currentColor;
  background: none;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-p-s);
  font-weight: var(--brand-fw-link);
  line-height: 1.4;
  letter-spacing: var(--brand-ls-eyebrow);
  text-transform: uppercase;
  color: var(--brand-ignition);
  cursor: pointer;
}

.group-filter__clear:focus-visible {
  outline: 2px solid var(--brand-ignition);
  outline-offset: 4px;
}

.group-list__empty {
  margin: 0;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  line-height: var(--brand-lh-body);
  letter-spacing: normal;
  color: var(--brand-ink);
}

/* --- cards --- */

.group-list__items {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 24px;
  /* Reboot puts padding-left and a bottom margin on every ul. */
  margin: 0;
  padding: 0;
  list-style: none;
}

/* A white card with its type as a coloured edge and eyebrow, rather than a
   solid block of colour. Two colours per card at full strength — a filled panel
   plus artwork — made a wall of 166 of them shout; the accent carries the same
   information at a fraction of the weight, and it now matches the filter tile
   that leads to it. */
.group-card {
  position: relative; /* anchors the title link's full-card hit area */
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background-color: var(--brand-white);
  border: 1px solid var(--brand-border);
  border-top: 4px solid var(--cg-ink);
  transition:
    box-shadow 0.3s ease,
    border-color 0.3s ease;
}

/* Hover raises the card with shadow and inner motion rather than a transform
   on the card itself — the reveal animation below owns `transform`, and a
   transition fighting a filling animation for the same property is a class of
   bug that only shows up on the frame the two meet. */
.group-card:hover {
  border-color: var(--cg-ink);
  box-shadow: 0 18px 40px -18px rgba(7, 43, 49, 0.28);
}

/* `js-reveal` is added by the grid on mount, so with JavaScript off — or if
   the component never hydrates — every card stays visible. The hidden state
   only exists once something is guaranteed to un-hide it. */
.group-list__items.js-reveal .group-card {
  opacity: 0;
}

.group-list__items.js-reveal .group-card.is-revealed {
  animation: group-card-in 0.5s cubic-bezier(0.2, 0.7, 0.3, 1) both;
  animation-delay: var(--reveal-delay, 0ms);
}

@keyframes group-card-in {
  from {
    opacity: 0;
    transform: translateY(28px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* 32:9, which is the native ratio of the six group-type graphics (1920x540).
   They are wordmark banners — "BIBLE + STUDY" set across the full width — so
   any taller box crops the words rather than the margins. A per-group image
   override of another shape is centre-cropped into the same strip, which keeps
   the grid's rhythm regular. */
/* Only a per-group photograph reaches this now, so it gets a photo's shape
   rather than the wordmark banner's 32:9 strip. */
.group-card__media {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
}

/* Two classes on purpose. The theme's `img:not([draggable]) { height: auto }`
   is (0,1,1) and beats any single-class rule, so the artwork would stretch the
   panel instead of being cropped inside it. */
.group-card__media .group-card__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.5s cubic-bezier(0.2, 0.7, 0.3, 1);
}

.group-card:hover .group-card__image {
  transform: scale(1.05);
}

.group-card__body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 24px;
  padding: 28px;
}

.group-card__text {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* The type, in that type's ink — the card's one piece of colour besides its
   top edge. */
.group-card__type {
  margin: 0 0 2px;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-eyebrow-s);
  font-weight: var(--brand-fw-link);
  line-height: 1.2;
  letter-spacing: var(--brand-ls-eyebrow);
  text-transform: uppercase;
  color: var(--cg-ink);
}

.group-card__title {
  margin: 0;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-h6);
  font-weight: var(--brand-fw-heading);
  line-height: var(--brand-lh-heading-tight);
  /* Declared even though the answer is "none": this class is h3-based and
     brand.css sets h3 to -1.8px, which the small headings do not want. */
  letter-spacing: normal;
  color: var(--brand-gunmetal);
}

.group-card__link {
  color: inherit;
  text-decoration: none;
}

/* The card's primary destination, so the hit area is the whole card. Sign Up
   sits above this — see .group-card__signup. */
.group-card__link::after {
  content: "";
  position: absolute;
  inset: 0;
}

.group-card__link:focus-visible::after {
  outline: 3px solid var(--brand-gunmetal);
  outline-offset: -3px;
}

.group-card__when {
  margin: 0;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-eyebrow-s);
  font-weight: var(--brand-fw-link);
  line-height: var(--brand-lh-heading);
  letter-spacing: var(--brand-ls-eyebrow);
  text-transform: uppercase;
  color: var(--brand-gunmetal);
}

.group-card__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin: 4px 0 0;
  padding: 0;
  list-style: none;
}

/* Metrics on the li, which forms the line box — styling only a child would
   leave these rows on the inherited 16px/1.6. */
.group-card__meta li {
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: normal;
  color: var(--brand-gunmetal);
  opacity: 0.8;
}

/* The separator hangs off the *preceding* item, not the following one. With
   three facets the row wraps often, and `li + li::before` would start the
   wrapped line with an orphaned "· ". */
.group-card__meta li:not(:last-child)::after {
  content: "·";
  margin-left: 12px;
}

/* Was a translucent white, which read as a chip on a coloured card and as
   nothing at all once the card went white. */
.group-card__badge {
  align-self: flex-start;
  margin: 4px 0 0;
  padding: 4px 10px;
  background-color: var(--brand-surface-muted);
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: normal;
  color: var(--brand-gunmetal);
}

.group-card__excerpt {
  margin: 8px 0 0;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-base);
  font-weight: 500;
  line-height: var(--brand-lh-body);
  letter-spacing: normal;
  color: var(--brand-gunmetal);
}

.group-card__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 20px;
}

/* Mirrors the comp's Text Link: uppercase label, arrow, 2px rule beneath. */
.group-card__cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  transition: gap 0.25s ease;
  padding-bottom: 4px;
  border-bottom: 2px solid var(--brand-gunmetal);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-link);
  font-weight: var(--brand-fw-link);
  line-height: normal;
  letter-spacing: 0.96px;
  text-transform: uppercase;
  color: var(--brand-gunmetal);
}

/* The one element that must escape the title link's full-card overlay: the
   overlay is a positioned ::after with no z-index, so any positioned sibling
   with z-index 1 wins the paint order and stays clickable. Reboot styles `a`
   directly, hence the restated decoration. */
.group-card__signup {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  height: 40px;
  padding: 0 20px;
  background-color: var(--brand-gunmetal);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-link);
  font-weight: var(--brand-fw-link);
  line-height: normal;
  letter-spacing: 0.96px;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--brand-white);
  transition: background-color 0.2s ease;
}

.group-card:hover .group-card__cta {
  gap: 14px;
}

.group-card__signup:hover,
.group-card__signup:focus-visible {
  background-color: var(--brand-ignition);
  color: var(--brand-white);
}

.group-card__signup:focus-visible {
  outline: 2px solid var(--brand-gunmetal);
  outline-offset: 2px;
}

/* A full group's CTA is a `span`, not a disabled link — so this only has to
   look closed, not suppress interaction. `default` rather than `not-allowed`:
   nothing was refused, the group is simply full. */
.group-card__signup--full {
  background-color: var(--brand-surface-muted);
  color: var(--brand-ink);
  opacity: 0.75;
  cursor: default;
}

.group-card__signup--full:hover {
  background-color: var(--brand-surface-muted);
  color: var(--brand-ink);
}

.group-list__more {
  display: flex;
  justify-content: center;
  margin-top: 40px;
}

.group-list__more-btn {
  height: 52px;
  padding: 0 32px;
  border: 2px solid var(--brand-gunmetal);
  border-radius: 0;
  background: none;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-link);
  font-weight: var(--brand-fw-link);
  line-height: normal;
  letter-spacing: 0.96px;
  text-transform: uppercase;
  color: var(--brand-gunmetal);
  cursor: pointer;
  transition:
    background-color 0.2s ease,
    color 0.2s ease;
}

.group-list__more-btn:hover,
.group-list__more-btn:focus-visible {
  background-color: var(--brand-gunmetal);
  color: var(--brand-white);
}

/* How many are left, so the button says what it will do rather than just
   inviting another click into the unknown. */
.group-list__more-rest {
  margin-left: 12px;
  padding-left: 12px;
  border-left: 1px solid currentColor;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  font-weight: 500;
  letter-spacing: normal;
  text-transform: none;
  opacity: 0.7;
}

@media (max-width: 1199px) {
  .group-list__layout {
    gap: 6%;
  }
  .group-list__aside {
    flex-basis: 36%;
  }
}

/* The sticky column cannot hold once the layout stacks. Cards stay two-up
   here — the full container width is behind them once the aside is above. */
@media (max-width: 991px) {
  .group-list {
    padding: 80px 0;
  }
  .group-list__layout {
    flex-direction: column;
    gap: 48px;
  }
  .group-list__aside {
    position: static;
    flex-basis: auto;
    width: 100%;
  }
}

@media (max-width: 767px) {
  .group-list__items {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 767px) {
  .group-filter__controls {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .group-types {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 575px) {
  .group-card__body {
    padding: 24px;
  }
  .group-filter__controls {
    grid-template-columns: minmax(0, 1fr);
  }
  .group-types {
    gap: 8px;
  }
  .group-type {
    padding: 12px 12px;
  }
}

/* The JS honours prefers-reduced-motion too — the reveal observer just marks
   every card visible — but the hover and hydration paths are CSS-only, so the
   contract is restated here rather than assumed. */
@media (prefers-reduced-motion: reduce) {
  .group-card,
  .group-card__image,
  .group-card__cta,
  .group-card__signup,
  .group-type,
  .group-chip,
  .group-list__more-btn {
    transition: none;
  }
  .group-list__items.js-reveal .group-card,
  .group-list__items.js-reveal .group-card.is-revealed {
    opacity: 1;
    animation: none;
  }
  .group-card:hover .group-card__image {
    transform: none;
  }
  .group-type:hover {
    transform: none;
  }
}

/* ==========================================================================
 * Connect Group detail — /connect-groups/:uid
 *
 * Same construction as the event detail page: a Gunmetal masthead whose
 * artwork overlaps the band below, then the description beside a sticky facts
 * panel on the 1296 inner — 818 of copy, a 62 gutter, a 416 panel.
 *
 * Kept as its own block rather than sharing event-detail's selectors: the two
 * pages have already diverged (no dates, no directions, no cost here) and
 * coupling them would mean every future event-detail tweak had to be checked
 * against a page about groups.
 * ========================================================================== */

/* `.site-header` is absolutely positioned over the page and is 106px tall, so
   anything above ~112px of padding renders underneath it. */
.group-detail__masthead {
  padding: 138px 0 0;
  background-color: var(--brand-gunmetal);
}

/* Reboot styles `a` directly, so text-decoration and colour are restated. */
.group-detail__back {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 48px;
  color: var(--brand-refresh);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-p-s);
  font-weight: var(--brand-fw-link);
  line-height: 1.4;
  letter-spacing: var(--brand-ls-eyebrow);
  text-transform: uppercase;
  text-decoration: none;
  transition: color 0.2s ease;
}

.group-detail__back:hover,
.group-detail__back:focus-visible {
  color: var(--brand-white);
}

/* The shared arrow points up-and-right and means "onward"; mirrored it points
   up-and-left, which reads as "back". Same mask, no second file. */
.group-detail__back-arrow {
  display: inline-block;
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  transform: scaleX(-1);
  background-color: currentColor;
  -webkit-mask: url("/assets/img/icon/arrow-left-top.svg") no-repeat center /
    contain;
  mask: url("/assets/img/icon/arrow-left-top.svg") no-repeat center / contain;
}

.group-detail__masthead-copy {
  padding-bottom: 64px;
}

/* Figma H2 at the comp size, clamped for mobile. Tracking in em so it scales
   with the clamp instead of crowding the words at 36px. Group names run long
   ("Wednesday Morning Women's Bible Study"), so this tops out below the event
   title's 76px. */
.group-detail__title {
  margin: 16px 0 0;
  color: var(--brand-white);
  font-family: var(--brand-font-heading);
  font-size: clamp(32px, 4.2vw, 60px);
  font-weight: var(--brand-fw-heading-bold);
  line-height: var(--brand-lh-heading);
  letter-spacing: -0.024em;
  text-transform: uppercase;
}

.group-detail__when {
  margin: 24px 0 0;
  color: var(--brand-refresh);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-p-m);
  font-weight: var(--brand-fw-body-medium);
  line-height: 1.4;
  letter-spacing: normal;
}

.group-detail__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 24px;
  margin-top: 40px;
}

.group-detail__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 16px 24px;
  border: 0;
  background-color: var(--brand-refresh);
  color: var(--brand-gunmetal);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-link);
  font-weight: var(--brand-fw-link);
  line-height: 26px;
  letter-spacing: normal;
  text-transform: uppercase;
  text-decoration: none;
  transition:
    background-color 0.2s ease,
    color 0.2s ease;
}

.group-detail__btn:hover,
.group-detail__btn:focus-visible {
  background-color: var(--brand-white);
  color: var(--brand-gunmetal);
}

/* Full: still reads as the CTA's slot so the layout does not jump, but plainly
   inert. Contrast is kept above AA — this is real information, not decoration,
   so it must not fade into the band behind it. */
.group-detail__btn--full,
.group-detail__btn--full:hover {
  background-color: rgba(255, 255, 255, 0.14);
  border-color: transparent;
  color: var(--brand-white);
  cursor: default;
}

.group-detail__btn--panel.group-detail__btn--full,
.group-detail__btn--panel.group-detail__btn--full:hover {
  background-color: rgba(7, 43, 49, 0.16);
  color: var(--brand-gunmetal);
}

/* Inside the facts panel the roles invert: the panel is already Refresh, so
   the button has to be the dark one to stay a button. */
.group-detail__btn--panel {
  display: flex;
  margin-top: 32px;
  background-color: var(--brand-gunmetal);
  color: var(--brand-refresh);
}

.group-detail__btn--panel:hover,
.group-detail__btn--panel:focus-visible {
  background-color: var(--brand-white);
  color: var(--brand-gunmetal);
}

.group-detail__note {
  color: var(--brand-clay);
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-base);
  font-weight: 500;
  line-height: 1.6;
  letter-spacing: normal;
}

/* The ended-semester page's "Stay on this page": a real button (it acts, it
   does not navigate) that reads as an inline link in the note's own ink. */
.group-detail__stay {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.group-detail__stay:hover,
.group-detail__stay:focus-visible {
  color: var(--brand-white);
}

/* The group-type graphic is a 1920x540 wordmark banner, so it runs the full
   width of the inner rather than sitting in a side column beside the title —
   at 38% it would be a 168px-tall slot holding centred type, which reads as a
   mistake. It still overlaps the section below, so the masthead is one
   composition; the negative margin is reclaimed by the body's top padding. */
.group-detail__art {
  position: relative;
  margin-bottom: -80px;
  overflow: hidden;
}

/* Two classes: the theme's `img:not([draggable]) { height: auto }` is (0,1,1)
   and would stretch this box past its aspect-ratio on an override upload of
   another shape. */
.group-detail__art .group-detail__art-image {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 32 / 9;
  object-fit: cover;
}

.group-detail__body {
  padding: 136px 0 120px; /* 120 + the 16 the artwork overlaps */
  background-color: var(--brand-surface-light);
}

.group-detail__layout {
  display: flex;
  align-items: flex-start;
  gap: 4.8%; /* 62 / 1296 */
}

.group-detail__main {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 818px;
}

.group-detail__paragraph {
  margin: 0 0 24px;
  color: var(--brand-ink);
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  font-weight: 400;
  line-height: var(--brand-lh-body);
  letter-spacing: normal;
}

.group-detail__paragraph:last-child {
  margin-bottom: 0;
}

.group-detail__subheading {
  margin: 40px 0 16px;
  color: var(--brand-ink);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-h5);
  font-weight: var(--brand-fw-heading);
  line-height: var(--brand-lh-heading-tight);
  /* Declared even though the answer is "none" — brand.css sets h3 to -1.8px. */
  letter-spacing: normal;
}

.group-detail__list {
  margin: 0 0 24px;
  padding-left: 20px;
}

/* Metrics on the li, which forms the line box. */
.group-detail__list li {
  margin-bottom: 8px;
  color: var(--brand-ink);
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  line-height: var(--brand-lh-body);
  letter-spacing: normal;
}

/* --- sticky facts panel --- */

.group-detail__facts {
  flex: 0 0 32.1%; /* 416 / 1296 */
  position: sticky;
  top: 120px;
}

.group-detail__facts-inner {
  padding: 40px;
  background-color: var(--brand-refresh);
}

.group-detail__facts-list {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.group-detail__fact dt {
  margin: 0 0 6px;
  color: var(--brand-gunmetal);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-eyebrow-s);
  font-weight: var(--brand-fw-link);
  line-height: var(--brand-lh-heading);
  letter-spacing: var(--brand-ls-eyebrow);
  text-transform: uppercase;
  opacity: 0.75;
}

/* Reboot gives dd a left margin; it has to go or the values indent. */
.group-detail__fact dd {
  margin: 0;
  color: var(--brand-gunmetal);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-h6);
  font-weight: var(--brand-fw-heading);
  line-height: var(--brand-lh-heading-tight);
  letter-spacing: normal;
}

.group-detail__fact-sub {
  display: block;
  margin-top: 4px;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-base);
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: normal;
  opacity: 0.85;
}

.group-detail__fact-link {
  color: var(--brand-gunmetal);
  text-decoration: none;
  border-bottom: 2px solid currentColor;
}

@media (max-width: 1199px) {
  .group-detail__layout {
    gap: 3.5%;
  }
  .group-detail__facts {
    flex-basis: 36%;
  }
}

/* The sticky panel cannot hold once the layout stacks, and the artwork's
   overlap has nothing left to overlap into. */
@media (max-width: 991px) {
  .group-detail__masthead-copy {
    padding-bottom: 48px;
  }
  .group-detail__art {
    margin-bottom: -60px;
  }
  .group-detail__body {
    padding: 100px 0 80px;
  }
  .group-detail__layout {
    flex-direction: column;
    gap: 48px;
  }
  .group-detail__facts {
    position: static;
    flex-basis: auto;
    width: 100%;
  }
}

@media (max-width: 575px) {
  .group-detail__facts-inner {
    padding: 28px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .group-detail__back,
  .group-detail__btn {
    transition: none;
  }
}

/* ==========================================================================
 * ConnectGroupHero — the Connect Groups masthead.
 *
 * No comp exists for this, and no photograph either, so it is built from the
 * vocabulary the rest of the page established: the Gunmetal band of the Hero
 * banner, the //// eyebrow, and the coloured plate + multiply blend the
 * directory uses to turn one near-white wordmark into six colours.
 *
 * Content-sized rather than a fixed height — the generic Hero's 1120px exists
 * to hold a photograph, and reserving that space with nothing in it is what
 * made this page look broken in the first place.
 * ========================================================================== */

.cg-hero {
  position: relative;
  overflow: hidden;
  /* Clears the 106px fixed header, which is absolutely positioned over it. */
  padding: 208px 0 0;
  background-color: var(--brand-gunmetal);
}

.cg-hero__copy {
  max-width: 900px;
}

.cg-hero__title {
  margin: 16px 0 0;
  color: var(--brand-white);
  font-family: var(--brand-font-heading);
  /* Matches the Hero banner's clamp so the two mastheads sit at one scale. */
  font-size: clamp(40px, 6.6vw, 95px);
  font-weight: var(--brand-fw-heading-bold);
  line-height: var(--brand-lh-heading);
  letter-spacing: -0.032em;
  text-transform: uppercase;
}

.cg-hero__text {
  margin: 24px 0 0;
  max-width: 620px;
  color: var(--brand-clay);
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  font-weight: 400;
  line-height: var(--brand-lh-body);
  letter-spacing: normal;
}

/* --- live stat line --- */

.cg-hero__stats {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 48px;
  margin: 40px 0 0;
}

/* Label under figure visually, `dt` first in the DOM — the reading order of a
   definition list should not be inverted just to put the number on top. */
.cg-hero__stat {
  display: flex;
  flex-direction: column-reverse;
  gap: 2px;
}

.cg-hero__stat dt {
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-eyebrow-s);
  font-weight: var(--brand-fw-link);
  line-height: 1.2;
  letter-spacing: var(--brand-ls-eyebrow);
  text-transform: uppercase;
  color: var(--brand-white);
  opacity: 0.6;
}

/* Reboot gives dd a left margin; it has to go or the figures indent. */
.cg-hero__stat dd {
  margin: 0;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-h3);
  font-weight: var(--brand-fw-heading-bold);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--brand-refresh);
}

.cg-hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-top: 40px;
}

/* Reboot styles `a` directly, so decoration and colour are restated. */
.cg-hero__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 16px 28px;
  border: 2px solid var(--brand-refresh);
  background-color: var(--brand-refresh);
  color: var(--brand-gunmetal);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-link);
  font-weight: var(--brand-fw-link);
  line-height: 26px;
  letter-spacing: normal;
  text-transform: uppercase;
  text-decoration: none;
  transition:
    background-color 0.2s ease,
    color 0.2s ease,
    border-color 0.2s ease;
}

.cg-hero__btn:hover,
.cg-hero__btn:focus-visible {
  background-color: var(--brand-white);
  border-color: var(--brand-white);
  color: var(--brand-gunmetal);
}

.cg-hero__btn--ghost {
  background-color: transparent;
  border-color: rgba(255, 255, 255, 0.4);
  color: var(--brand-white);
}

.cg-hero__btn--ghost:hover,
.cg-hero__btn--ghost:focus-visible {
  background-color: var(--brand-white);
  border-color: var(--brand-white);
  color: var(--brand-gunmetal);
}

/* --- the group-type marquee --- */

.cg-marquee {
  margin-top: 72px;
  padding-bottom: 56px;
  /* Full-bleed: the strip runs edge to edge, unlike the copy above it, so the
     loop has no visible start or end. */
  overflow: hidden;
  -webkit-mask-image: linear-gradient(
    to right,
    transparent,
    #000 6%,
    #000 94%,
    transparent
  );
  mask-image: linear-gradient(
    to right,
    transparent,
    #000 6%,
    #000 94%,
    transparent
  );
}

.cg-marquee__track {
  display: flex;
  width: max-content;
  animation: cg-marquee-scroll var(--marquee-duration, 40s) linear infinite;
}

/* Pause on hover *and* on focus-within: a moving link is unclickable, and a
   keyboard user tabbing into the strip needs it to stop too. Together with the
   reduced-motion rule below this is the WCAG 2.2.2 "pause" mechanism. */
.cg-marquee:hover .cg-marquee__track,
.cg-marquee__track:focus-within {
  animation-play-state: paused;
}

/* Exactly half the track is one full copy of the list, so translating by -50%
   lands the duplicate precisely where the original began. */
@keyframes cg-marquee-scroll {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

.cg-marquee__list {
  display: flex;
  flex: 0 0 auto;
  gap: 20px;
  margin: 0;
  padding: 0 10px 0 0;
  list-style: none;
}

.cg-marquee__item {
  flex: 0 0 auto;
}

/* No card background: on Gunmetal a white footer bar under each plate draws a
   hard horizontal line across the whole strip and competes with the colour.
   Letting the label sit directly on the band keeps the plates as the only
   bright thing in the hero. */
.cg-marquee__tile {
  display: block;
  width: 268px;
  overflow: hidden;
  color: var(--brand-white);
  text-decoration: none;
  transition:
    transform 0.3s cubic-bezier(0.2, 0.7, 0.3, 1),
    box-shadow 0.3s ease;
}

/* The shadow stays tight to the plate: an 18px drop reaches down over the
   label sitting directly beneath it and greys out the white text. */
.cg-marquee__tile:hover {
  transform: translateY(-4px);
  box-shadow: 0 8px 20px -12px rgba(0, 0, 0, 0.55);
}

.cg-marquee__tile:focus-visible {
  outline: 3px solid var(--brand-refresh);
  outline-offset: 3px;
}

/* The plate colour comes from the shared per-type tokens, keyed on the tile's
   own `data-group-type` — not from its position in the strip. Position-based
   colour is what let the hero, the filters and the cards disagree about what
   colour "Marriage" is. */
.cg-marquee__art {
  background-color: var(--cg-plate);
}

.cg-marquee__art {
  display: block;
  aspect-ratio: 32 / 9; /* the banners' native 1920x540 */
  overflow: hidden;
}

.cg-marquee__count {
  color: var(--brand-refresh);
}

/* Two classes: the theme's `img:not([draggable]) { height: auto }` is (0,1,1)
   and beats any single-class rule. */
.cg-marquee__art .cg-marquee__img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Dark wordmark on near-white multiplied over the plate colour — the same
     trick as the cards, so one asset yields six coloured plates. */
  mix-blend-mode: multiply;
}

/* Name and count sit together at the left, not pushed apart: across a 268px
   tile, space-between parks each count right beside the *next* tile's name,
   so the strip reads "91 FINANCE" instead of "BIBLE STUDY 91". */
.cg-marquee__meta {
  display: flex;
  align-items: baseline;
  justify-content: flex-start;
  gap: 10px;
  padding: 12px 2px 0;
}

.cg-marquee__name {
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-eyebrow-s);
  font-weight: var(--brand-fw-link);
  line-height: 1.3;
  letter-spacing: var(--brand-ls-eyebrow);
  text-transform: uppercase;
}

.cg-marquee__count {
  flex: 0 0 auto;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: normal;
  color: var(--brand-refresh);
}

@media (max-width: 991px) {
  .cg-hero {
    padding-top: 168px;
  }
  .cg-hero__stats {
    gap: 12px 32px;
  }
  .cg-marquee {
    margin-top: 56px;
    padding-bottom: 44px;
  }
  .cg-marquee__tile {
    width: 232px;
  }
}

/* The header grows to 136px here (the nav bar wraps) and is positioned over
   the hero, so the copy needs to start below it. */
/* Tightened rather than just scaled: at 375 the full stack (title, copy,
   stats, buttons, strip) ran past a single screen, which buries the directory
   the hero exists to introduce. */
@media (max-width: 575px) {
  .cg-hero {
    padding-top: 176px;
  }
  .cg-hero__stat dd {
    font-size: var(--brand-fs-h5);
  }
  .cg-hero__stats,
  .cg-hero__actions {
    margin-top: 28px;
  }
  .cg-marquee {
    margin-top: 40px;
    padding-bottom: 36px;
  }
  .cg-marquee__tile {
    width: 208px;
  }
}

/* Stop the strip entirely and let it be scrolled by hand instead, so every
   type is still reachable without any motion at all. */
@media (prefers-reduced-motion: reduce) {
  .cg-marquee__track {
    animation: none;
    width: 100%;
    overflow-x: auto;
  }
  .cg-marquee__tile {
    transition: none;
  }
  .cg-marquee__tile:hover {
    transform: none;
  }
}

/* ==========================================================================
 * /about — page-scoped treatment.
 *
 * Scoped to `.page--about` on purpose. The `about` slice appears 27 times
 * across 19 documents, so it cannot be restyled in place to suit one page.
 * Everything here is the same slice told to behave differently on this route;
 * every other page that uses it is untouched.
 *
 * The problem: four `about` blocks stacked, each a heading with its copy in a
 * right-hand column and the whole left half empty — about 2,600px of half-blank
 * page. Two things cause it. Bootstrap's `col-xxl-9` inside a
 * `justify-content-end` row parks the content in the right three quarters, and
 * `.about-content` sits *inside* `.title-area`, so heading and copy stack in
 * that narrow column instead of sharing the width.
 *
 * So: reclaim the full inner width, then split `.title-area` itself into the
 * two columns the markup never had — heading left and sticky, copy right — the
 * same idiom the Events and Connect Groups listings use.
 * ========================================================================== */

.page--about {
  counter-reset: about-section;
}

.page--about .about-area-16 {
  counter-increment: about-section;
  padding: 96px 0;
}

/* Reclaim the empty left quarter that `col-xxl-9` + `justify-content-end`
   leaves behind. */
.page--about .about-area-16 .col-xxl-9 {
  width: 100%;
  max-width: none;
  flex: 0 0 100%;
}

/* `.about-content` is a child of `.title-area`, not a sibling — so the split
   has to happen here, with the heading and the copy as the two flex items. */
.page--about .about-area-16 .title-area {
  display: flex;
  align-items: flex-start;
  gap: 8.5%;
}

/* Sticky heading: it holds the left column while its copy scrolls past, which
   is what gives the empty half something to do. */
.page--about .about-area-16 .sec-title {
  position: sticky;
  top: 120px;
  flex: 0 0 34%;
  min-width: 0;
  margin: 0;
  text-align: left;
}

/* The theme indents this by 330px to fake a second column inside the single
   `col-xxl-9`. Now that it *is* a column, that indent is what was squeezing the
   copy to 415px and pushing it against the right edge. */
.page--about .about-area-16 .about-content {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 660px;
  margin-left: 0;
}

/* The section numeral, set inside the heading rather than added to the markup:
   it costs no content change and vanishes with the scope. Numbering echoes the
   Service slice, which already labels its items 01, 02, 03. */
.page--about .about-area-16 .sec-title::before {
  content: "0" counter(about-section);
  display: block;
  width: 56px;
  margin-bottom: 20px;
  padding-bottom: 12px;
  border-bottom: 2px solid var(--brand-refresh);
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-eyebrow-s);
  font-weight: var(--brand-fw-link);
  line-height: 1;
  letter-spacing: var(--brand-ls-eyebrow);
  color: var(--brand-ignition);
}

@media (max-width: 1199px) {
  .page--about .about-area-16 .title-area {
    gap: 5%;
  }
  .page--about .about-area-16 .sec-title {
    flex-basis: 38%;
  }
}

/* The sticky column cannot hold once the layout stacks, and a 34% heading
   beside body copy leaves neither enough room. */
@media (max-width: 991px) {
  .page--about .about-area-16 {
    padding: 64px 0;
  }
  .page--about .about-area-16 .title-area {
    flex-direction: column;
    gap: 20px;
  }
  .page--about .about-area-16 .sec-title {
    position: static;
    flex-basis: auto;
    width: 100%;
  }
  .page--about .about-area-16 .about-content {
    max-width: none;
    margin-left: 0;
  }
}

/* --- /about, continued: hierarchy and rhythm ---
 *
 * Still page-scoped. The page carries no photography, so the only tools left
 * are typographic weight, colour and band rhythm. These three do the most per
 * line of CSS; none of them can substitute for actual images.
 */

/* 1. A lead paragraph. Every block opened at one uniform size, so nothing in
 *    2,600px of copy told you where to start reading. */
.page--about .about-area-16 .sec-text > p:first-child {
  margin-bottom: 24px;
  color: var(--brand-gunmetal);
  font-family: var(--brand-font-heading);
  font-size: clamp(21px, 1.9vw, 27px);
  font-weight: var(--brand-fw-heading);
  line-height: 1.45;
  letter-spacing: -0.01em;
}

/* 2. Bands. The page ran white from the hero to the footer; giving the closing
      sections their own ground breaks it into readable movements. */
.page--about .service-area-1:last-child {
  background-color: var(--brand-surface-light);
}

/* ==========================================================================
   About — optional image
   ==========================================================================
 * Only applies when the `about` slice actually has an image. Without the
 * modifier the slice keeps the theme's original single-column arrangement, so
 * the 21 existing instances are untouched.
 *
 * Two theme traps are handled here rather than worked around in the markup:
 * `.about-content` carries a hard `margin-left: 330px` in style.css faking a
 * second column, which has to go once it *is* one; and the global
 * `img:not([draggable]) { height: auto }` rule needs a two-class selector to
 * beat it, hence `.about__media .about__image`.
 */

.about-area-16.about--has-image .about-content {
  margin-left: 0;
}

.about-area-16.about--has-image .about__media {
  position: relative;
  overflow: hidden;
  border-radius: 8px;
}

/* Two classes on purpose — see the note above. */
.about-area-16.about--has-image .about__media .about__image {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
}

@media (max-width: 991px) {
  .about-area-16.about--has-image .about__media .about__image {
    aspect-ratio: 3 / 2;
  }
}

/* ==========================================================================
   About — bulleted / numbered content
   ==========================================================================
 * `about.text` now permits list-item and o-list-item, which makes this the
 * one place on the site where bulleted content can be authored. Before this,
 * `pricing.features` was the only list-capable rich-text field in the whole
 * library, which is why /equip's "What to Expect" and "Requirements" were
 * shoehorned into a three-tier pricing table with no prices in two of them.
 *
 * Styled as prose, not as feature checkboxes: this carries full sentences
 * ("Willingness to engage in the community and contribute to group learning"),
 * so it stays sentence-case at body size and leads with a quiet marker rather
 * than a loud one.
 */

.rich-list ul,
.rich-list ol {
  margin: 20px 0 0;
  padding: 0;
  list-style: none;
}

.rich-list li {
  position: relative;
  padding-left: 30px;
  margin-bottom: 12px;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-base);
  line-height: var(--brand-lh-body);
  color: var(--rich-list-ink, var(--brand-ink));
}

.rich-list li:last-child {
  margin-bottom: 0;
}

/* Bulleted: a small Refresh rule, matching the numeral rule used elsewhere. */
.rich-list ul > li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 14px;
  height: 2px;
  background: var(--rich-list-marker, var(--brand-refresh));
}

/* Numbered: a counter in the same amber, so ordered steps read as a sequence
   rather than borrowing the bullet's rule. */
.rich-list ol {
  counter-reset: about-list;
}

.rich-list ol > li {
  counter-increment: about-list;
}

.rich-list ol > li::before {
  content: counter(about-list) ".";
  position: absolute;
  left: 0;
  top: 0;
  font-family: var(--brand-font-heading);
  font-weight: var(--brand-fw-heading-bold);
  font-size: var(--brand-fs-p-s);
  color: var(--rich-list-marker, var(--brand-refresh));
}

/* Two columns once a list gets long enough that a single column would run
   past the photo beside it. `column-count` rather than grid: a list is a
   continuous flow, and grid would leave a ragged hole at the end. */
@media (min-width: 992px) {
  .rich-list ul.rich-list--split {
    column-count: 2;
    column-gap: 48px;
  }

  .rich-list ul.rich-list--split > li {
    break-inside: avoid;
  }
}

/* ==========================================================================
   About — "Offset Image on Colour" variation
   ==========================================================================
 * Geometry is deliberately identical to `.form-block` above (42% media at
 * 605/796, 4.79% gap, band starting 120px down, 200px of top padding on the
 * copy) so the two blocks read as one family when they appear on the same
 * page. The offset itself is the band starting lower than the image: the photo
 * overhangs the fill instead of sitting inside it.
 *
 * The fill follows CtaBar's palette *and* its convention — Gunmetal and
 * Ignition are dark enough to need light ink, the other three keep dark ink.
 * Bullet markers cannot simply stay Refresh: on the Refresh fill that is amber
 * on amber, so the marker is a variable, dark on light fills and amber on dark.
 */

.about-offset {
  --about-offset-bg: var(--brand-refresh);
  --about-offset-ink: var(--brand-ink);
  /* Consumed by the shared `.rich-list` list rules. */
  --rich-list-ink: var(--brand-ink);
  --rich-list-marker: var(--brand-gunmetal);
  position: relative;
}

.about-offset::before {
  content: "";
  position: absolute;
  top: 120px;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: var(--about-offset-bg);
}

.about-offset--refresh {
  --about-offset-bg: var(--brand-refresh);
}

/* Mint is a light fill, so it keeps the default dark ink — white on it is
 * only 1.54:1. Matches the Mint used on the sticky cards. */
.about-offset--mint {
  --about-offset-bg: var(--brand-mint);
}

.about-offset--clay {
  --about-offset-bg: var(--brand-clay);
}

/* Flagstone reads as a dark fill, so it takes light ink like the other two.
 *
 * It uses `--brand-flagstone-ink` (#4f7a64), the deeper Flagstone already in
 * brand.css, rather than `--brand-flagstone` (#689881): white on the lighter
 * value measures 3.28:1, which fails AA for body text, while the deeper one
 * reaches 4.88:1. The shift is small enough to still read as Flagstone.
 *
 * Its marker is white, not amber — amber on this fill is only 2.74:1, below
 * the 3:1 a graphic element needs. Gunmetal and Ignition keep amber markers
 * (8.41:1 and 3.60:1), which is where the brand warmth comes from.
 */
.about-offset--flagstone {
  --about-offset-bg: var(--brand-flagstone-ink);
  --about-offset-ink: var(--brand-white);
  --rich-list-ink: var(--brand-white);
  --rich-list-marker: var(--brand-white);
}

/* The two dark fills need light ink — and an amber marker, since gunmetal
   would disappear into them. */
.about-offset--gunmetal {
  --about-offset-bg: var(--brand-gunmetal);
  --about-offset-ink: var(--brand-white);
  --rich-list-ink: var(--brand-white);
  --rich-list-marker: var(--brand-refresh);
}

.about-offset--ignition {
  --about-offset-bg: var(--brand-ignition);
  --about-offset-ink: var(--brand-white);
  --rich-list-ink: var(--brand-white);
  --rich-list-marker: var(--brand-refresh);
}

.about-offset__inner {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 4.79%;
}

.about-offset__media {
  flex: none;
  width: 44%;
  aspect-ratio: 605 / 796;
  overflow: hidden;
}

/* The theme's own about-area-1 narrows the media below 1500px. */
@media (max-width: 1500px) {
  .about-offset__media {
    width: 42%;
  }
}

/* Two-class selector: the theme's `img:not([draggable])` is (0,1,1) and would
 * otherwise let the upload set its own height. */
.about-offset__media .about-offset__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The band and its photograph bleed to the viewport edges, but the copy is
 * held to the site container the way the theme's about-area-1 holds it: the
 * text block is half the container wide and its outer edge lines up with the
 * container's, so a 3200px monitor gets a full-width band and a readable
 * measure rather than one 1,500px line. `margin-left: auto` is what parks it
 * against that edge; `max()` keeps the outer margin sane below the container
 * width, where the subtraction would otherwise go negative. */
.about-offset__content {
  flex: 1 1 auto;
  min-width: 0;
  max-width: calc(var(--main-container) / 2);
  margin-left: auto;
  margin-right: max(5%, calc((100% - var(--main-container)) / 2));
  padding: 200px 0 120px 0;
  color: var(--about-offset-ink);
}

/* Matches .form-block__heading: an h2 must state its letter-spacing, since
 * brand.css sets it per element for h1/h3 only and an h2 that omits it is
 * silently `normal`. */
.about-offset__heading {
  margin: 0;
  color: var(--about-offset-ink);
  font-family: var(--brand-font-heading);
  font-size: clamp(32px, 3.4vw, 49px);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: normal;
}

/* One step up the brand scale (16 -> 20, Paragraph M) at the designer's
 * request. Set from the token rather than a loose 18px so the type stays on
 * the scale, and applied to the list items too — they read 16 from
 * `--brand-fs-p-base`, and copy and bullets in the same panel drifting apart
 * would be worse than either size. Scoped to the offset variation, so the
 * default About and the PriceBar keep theirs. */
.about-offset__text {
  margin-top: 22px;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  line-height: 1.7;
  color: var(--about-offset-ink);
}

.about-offset .rich-list li {
  font-size: var(--brand-fs-p-m);
}

.about-offset__text > p:last-child {
  margin-bottom: 0;
}

/* The theme's .btn-wrap rides along on this element and supplies the flex row,
   its wrap, and the 20/30px gap, so multiple links space themselves. */
.about-offset__actions {
  margin-top: 30px;
}

/* Text alignment — Left is the default and needs no rule. The actions row is a
   flex container (.btn-wrap), so its buttons follow via justify-content rather
   than text-align. */
.about-offset--align-center .about-offset__content {
  text-align: center;
}
.about-offset--align-center .about-offset__actions {
  justify-content: center;
}
.about-offset--align-right .about-offset__content {
  text-align: right;
}
.about-offset--align-right .about-offset__actions {
  justify-content: flex-end;
}

/* The band's button is now the standard primary rectangle, whose colours come
   from the --btn-* tokens each band re-points in brand.css. The old rule here
   painted a bordered button in the band's ink; it died with the ghost variant. */

/* No image: without the overhang there is nothing for the 120px inset to do,
   so the band becomes a plain one and the copy sits on it. */
.about-offset__inner:not(:has(.about-offset__media)) ~ *,
.about-offset:not(:has(.about-offset__media))::before {
  top: 0;
}

.about-offset:not(:has(.about-offset__media)) .about-offset__content {
  padding: 96px 5%;
}

@media (max-width: 991px) {
  .about-offset::before {
    top: 0;
  }

  .about-offset__inner {
    display: block;
  }

  .about-offset__media {
    width: 100%;
    aspect-ratio: 16 / 10;
  }

  /* The stacked band is edge to edge, so the container alignment the desktop
     copy carries has to be cleared or its outer margin doubles this padding. */
  .about-offset__content {
    max-width: none;
    margin: 0;
    padding: 56px 20px 80px;
  }
}

/* ==========================================================================
   About — bullet styles
   ==========================================================================
 * Chosen per instance via the slice's Bullet Style select. Dash is the default
 * and is the shape the base `.rich-list ul > li::before` rule above already
 * draws, so an unset field leaves existing content exactly as authored.
 *
 * Every marker is drawn with borders/background rather than a `content` glyph.
 * Two reasons: a glyph is announced by some screen readers (a 12-item list
 * would read "check, check, check…"), and a glyph takes its colour from `color`
 * rather than the `--rich-list-marker` variable the coloured bands override.
 */

/* Solid — filled disc. */
.rich-list--solid ul > li::before {
  top: 0.5em;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--rich-list-marker, var(--brand-refresh));
}

/* Open — the same disc as a ring, for a lighter texture on dense lists. */
.rich-list--open ul > li::before {
  top: 0.5em;
  width: 8px;
  height: 8px;
  border: 2px solid var(--rich-list-marker, var(--brand-refresh));
  border-radius: 50%;
  background: none;
}

/* Check — two borders rotated into a tick. Sized so its optical weight matches
   the disc rather than its bounding box. */
.rich-list--check ul > li::before {
  top: 0.42em;
  width: 11px;
  height: 6px;
  border: 0;
  border-left: 2px solid var(--rich-list-marker, var(--brand-refresh));
  border-bottom: 2px solid var(--rich-list-marker, var(--brand-refresh));
  border-radius: 0;
  background: none;
  transform: rotate(-45deg);
}

/* Arrow — a chevron, for lists that are steps rather than attributes. */
.rich-list--arrow ul > li::before {
  top: 0.52em;
  width: 7px;
  height: 7px;
  border: 0;
  border-top: 2px solid var(--rich-list-marker, var(--brand-refresh));
  border-right: 2px solid var(--rich-list-marker, var(--brand-refresh));
  border-radius: 0;
  background: none;
  transform: rotate(45deg);
}

/* ==========================================================================
   About — image position
   ==========================================================================
 * Left is the default and needs no rules; only Right does anything. Both are
 * scoped to the desktop breakpoint, because below 992px the two columns stack
 * and the image should stay above the copy either way — reversing a wrapped
 * flex row there would push the photo underneath the text it introduces.
 */

@media (min-width: 992px) {
  /* Default variation: the media and copy are Bootstrap columns, so reversing
     the row is enough — the gutters mirror with it. */
  .about--image-right .row {
    flex-direction: row-reverse;
  }

  .about-offset--image-right .about-offset__inner {
    flex-direction: row-reverse;
  }

  /* The copy is parked against one edge of the container, so the margins have
     to swap with the row or it ends up pinned to the side the photo is on. */
  .about-offset--image-right .about-offset__content {
    margin-left: max(5%, calc((100% - var(--main-container)) / 2));
    margin-right: auto;
  }
}

/* ==========================================================================
 * PriceBar — a price and what it buys, in one coloured band.
 *
 * Geometry reuses the 416 / 110 / 770 rhythm Feature and MobileApp already use
 * on the 1296 inner (32.1% / 8.49% / 59.4%), mirrored so the price takes the
 * narrow column. Fills and ink follow CtaBar exactly, Flagstone included, so a
 * PriceBar and a CtaBar on the same page are unmistakably the same family.
 *
 * The spine is the `////` mark from `.eyebrow::before` drawn at the 114deg the
 * bare hero established (66deg leans the wrong way and reads as `\\\\`). It
 * stands where a rule would, because the join between the price and the list
 * is the slice's whole subject. It is the one place this slice spends any
 * boldness — everything else is flat, square and quiet.
 * ========================================================================== */

.price-bar {
  display: flex;
  align-items: stretch;
  gap: 4.245%;
  padding: 56px;
  background-color: var(--price-bar-bg, var(--brand-refresh));
  color: var(--price-bar-ink, var(--brand-ink));
  /* Consumed by the shared `rich-list` rules. */
  --rich-list-ink: var(--price-bar-ink, var(--brand-ink));
  --rich-list-marker: var(--price-bar-marker, var(--brand-gunmetal));
}

.price-bar--refresh {
  --price-bar-bg: var(--brand-refresh);
}

.price-bar--clay {
  --price-bar-bg: var(--brand-clay);
}

/* Flagstone: white on #689881 is 3.28:1 and fails AA, so this uses the deeper
   `--brand-flagstone-ink` (#4f7a64) at 4.88:1, matching CtaBar and the About
   offset band. Its marker is white because amber on that fill is only 2.74:1. */
.price-bar--flagstone {
  --price-bar-bg: var(--brand-flagstone-ink);
  --price-bar-ink: var(--brand-white);
  --price-bar-marker: var(--brand-white);
}

.price-bar--gunmetal {
  --price-bar-bg: var(--brand-gunmetal);
  --price-bar-ink: var(--brand-white);
  --price-bar-marker: var(--brand-refresh);
}

.price-bar--ignition {
  --price-bar-bg: var(--brand-ignition);
  --price-bar-ink: var(--brand-white);
  --price-bar-marker: var(--brand-refresh);
}

/* 416/1296 */
.price-bar__cost {
  flex: 0 0 auto;
  width: 32.1%;
  min-width: 0;
}

.price-bar__eyebrow {
  color: inherit;
  opacity: 0.75;
}

/* The price is the one thing set large. H3's size and tracking, but tabular so
   a column of figures does not shift when the amount is edited. */
.price-bar__price {
  margin: 18px 0 0;
  font-family: var(--brand-font-heading);
  font-size: clamp(40px, 4.24vw, 61px);
  font-weight: var(--brand-fw-heading-bold);
  line-height: 1;
  letter-spacing: var(--brand-ls-h3);
  font-variant-numeric: tabular-nums;
  color: inherit;
}

/* The period is a label, not a subheading — it takes the eyebrow idiom so it
   reads as a unit of the price rather than a second line of copy. */
.price-bar__period {
  margin: 12px 0 0;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-eyebrow-s);
  font-weight: var(--brand-fw-body-medium);
  line-height: var(--brand-lh-heading);
  letter-spacing: var(--brand-ls-eyebrow);
  text-transform: uppercase;
  color: inherit;
  opacity: 0.75;
}

.price-bar__actions {
  margin-top: 32px;
}

/* The theme's `.btn.btn-border` (0,2,0) hardcodes Ignition for both border and
 * text, which loses the button entirely on some fills — Ignition on Ignition
 * measures 1.00:1. These need (0,3,0) to win, and the hover inverts to the
 * band colour so one rule covers all five fills without per-colour overrides. */
.price-bar .btn.price-bar__btn {
  background-color: transparent;
  border-color: currentColor;
  color: var(--price-bar-ink, var(--brand-ink));
}

.price-bar .btn.price-bar__btn:hover,
.price-bar .btn.price-bar__btn:focus-visible {
  background-color: var(--price-bar-ink, var(--brand-ink));
  border-color: var(--price-bar-ink, var(--brand-ink));
  color: var(--price-bar-bg, var(--brand-refresh));
}

/* The spine. 110/1296 is the system gap; the mark occupies the middle of it. */
.price-bar__spine {
  flex: 0 0 auto;
  align-self: stretch;
  width: 18px;
  background-image: repeating-linear-gradient(
    114deg,
    currentColor 0,
    currentColor 2px,
    transparent 2px,
    transparent 9px
  );
  opacity: 0.35;
}

/* 770/1296 */
.price-bar__includes {
  flex: 1 1 auto;
  min-width: 0;
}

.price-bar__includes-heading {
  margin: 0 0 20px;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-h6);
  font-weight: var(--brand-fw-heading);
  line-height: var(--brand-lh-heading-tight);
  letter-spacing: normal;
  color: inherit;
}

.price-bar__includes .rich-list ul {
  margin-top: 0;
}

.price-bar__note {
  margin: 24px 0 0;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-s);
  line-height: var(--brand-lh-body);
  color: inherit;
  opacity: 0.75;
}

@media (max-width: 991px) {
  .price-bar {
    display: block;
    padding: 40px 24px;
  }

  /* Stacked, the spine becomes the horizontal rule between the two halves —
     same mark, same angle, doing the same job in the new axis. */
  .price-bar__spine {
    width: auto;
    height: 12px;
    margin: 32px 0;
  }

  .price-bar__cost {
    width: 100%;
  }
}

/* ==========================================================================
 * Service — card link styles
 * ==========================================================================
 * The style is picked per slice, not per card: mixed link treatments inside
 * one grid read as a mistake rather than a choice.
 *
 * Primary and Ghost deliberately reuse `.btn` and `.btn.btn-border` from
 * brand.css instead of redrawing a button here, so all three follow a rebrand
 * from one place. Only Underline needs its own rule, and it borrows the text
 * link's type tokens so it sits in the same family as `.link-btn`.
 *
 * Scoped under `.service-cards` because `.feature-card` itself is declared in
 * the theme's style.css, which is copy-only.
 * ========================================================================== */

.service-cards .service-card__actions {
  margin-top: 20px;
}

/* No stretched-link overlay here, deliberately. The obvious way to make the
 * whole card clickable is `.service-card__link::after { inset: 0 }`, but
 * `.link-btn` is itself `position: relative` and owns BOTH pseudo-elements for
 * the theme's sweep-underline hover — so that overlay resolves against the
 * anchor rather than the card (no stretch) *and* overwrites the underline
 * animation on every arrow link. One anchor per card with a full accessible
 * name is the accessibility fix; a whole-card hit area is not worth breaking
 * the theme's hover for. */

/* --- Underline ------------------------------------------------------------
 * The text link's type tokens without the sweep animation or the arrow, for
 * grids where a row of arrows reads as clutter. `--brand-ls-link` is 0.96px,
 * not the Figma token's 6 — see the note in brand.css.
 *
 * Neutrally named because more than one slice offers these treatments now —
 * see app/components/StyledLink.tsx, which is what emits the class. */
.link-style--underline {
  display: inline-block;
  font-family: var(--brand-font-heading);
  font-size: var(--brand-fs-link);
  font-weight: var(--brand-fw-link);
  letter-spacing: var(--brand-ls-link);
  line-height: 1.625;
  text-transform: uppercase;
  color: var(--brand-ignition);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 6px;
  transition: text-decoration-thickness ease 0.2s, color ease 0.3s;
}

.link-style--underline:hover,
.link-style--underline:focus-visible {
  color: var(--brand-gunmetal);
  text-decoration-thickness: 2px;
}

/* --- Primary / Ghost ------------------------------------------------------
 * `.btn` is a block-level-ish control in the theme; in a card it should hug
 * its label rather than stretch the column. */
.service-cards .btn.service-card__link {
  display: inline-block;
  width: auto;
}

/* Keyboard focus must be visible on all four styles — the stretched ::after
   means the visible ring would otherwise sit on a zero-height inline box. */
.service-cards .service-card__link:focus-visible {
  outline: 2px solid var(--brand-ignition);
  outline-offset: 3px;
}

/* ==========================================================================
   Rich text on coloured bands — paragraphs and links must follow the ink
   ==========================================================================
 * The theme sets `p { color: var(--body-color) }` in style.css. An element
 * selector beats inheritance, so setting `color` on a container has no effect
 * on the paragraphs inside it — headings and list items followed the band's
 * ink while the body copy stayed Gunmetal, which is the mixed black-and-white
 * text on the dark fills (3.07:1 on Flagstone).
 *
 * Links get the same treatment for the same reason, and keep their underline
 * so they stay distinguishable from the copy without relying on colour.
 */

.about-offset__text p,
.price-bar__includes p,
.video-split__text p,
.rich-list p {
  color: inherit;
}

.about-offset__text a,
.price-bar__includes a,
.video-split__text a,
.rich-list a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 4px;
}

/* `strong` and `em` inherit already — no rule needed, and adding one would
   only be another thing to keep in step. */

/* ==========================================================================
   Sticky cards — multiple links per card
   ==========================================================================
 * The card's link field is repeatable, so a card can carry a row of calls to
 * action rather than one. They wrap, because a card is narrow and three
 * buttons will not sit on one line at every width.
 */

.sticky-cards__links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 24px;
}

/* The theme's `.btn.btn-border` (0,2,0) hardcodes Ignition for border and
 * text, which is illegible on the darker card fills — on Flagstone it is
 * 1.31:1. (0,3,0) to beat it, following the card's ink instead, with the hover
 * inverting to the fill so one rule covers every colour. */
.sticky-cards__card .btn.btn-border.sticky-cards__link {
  background-color: transparent;
  border-color: currentColor;
  color: var(--sticky-card-ink, var(--brand-ink));
}

.sticky-cards__card .btn.btn-border.sticky-cards__link:hover,
.sticky-cards__card .btn.btn-border.sticky-cards__link:focus-visible {
  background-color: var(--sticky-card-ink, var(--brand-ink));
  border-color: var(--sticky-card-ink, var(--brand-ink));
  color: var(--sticky-cards-card-bg, var(--brand-refresh));
}

/* Underline and arrow both take their colour from the card ink already; the
   solid Primary button keeps its Refresh fill on every card, which reads as a
   deliberate accent rather than an inherited one. */
.sticky-cards__card .link-style--underline.sticky-cards__link {
  color: var(--sticky-card-ink, var(--brand-ink));
}

/* Primary keeps its own fill AND its own ink. The card-ink rule on
 * `.sticky-cards__link` (0,1,0) otherwise turns this button's Gunmetal text
 * white while its Refresh fill stays put — 1.78:1, the worst contrast in the
 * component. Ghost and Underline *should* follow the card ink because they are
 * drawn in it; a solid button carries its own pair. */
.sticky-cards__card .btn.sticky-cards__link:not(.btn-border) {
  background-color: var(--brand-refresh);
  color: var(--brand-gunmetal);
}

.sticky-cards__card .btn.sticky-cards__link:not(.btn-border):hover,
.sticky-cards__card .btn.sticky-cards__link:not(.btn-border):focus-visible {
  background-color: var(--brand-gunmetal);
  color: var(--brand-refresh);
}

/* ==========================================================================
 * Marquee — a full-bleed band of one phrase looping across the screen.
 *
 * Ported from Frisk's `.marquee-wrap` (the band at the foot of project.html).
 * The theme's geometry is kept — 31px of padding around 36px type, an accent
 * mark between repeats — but the fill is a writer's choice and the motion is
 * CSS rather than jQuery.marquee. See slices/Marquee/MarqueeTrack.tsx for why
 * the plugin is not usable here.
 *
 * Unlike most slices this paints its own background: a band that does not run
 * edge to edge is not a marquee, and a Section wrapper cannot bleed past its
 * own container.
 * ========================================================================== */

.marquee-band {
  --marquee-bg: var(--brand-gunmetal);
  --marquee-ink: var(--brand-white);
  --marquee-mark: var(--brand-refresh);
  padding: 31px 0;
  overflow: hidden;
  background-color: var(--marquee-bg);
}

/* Colour names and values match StickyCards' own Select, so a page carrying
 * both does not have two vocabularies for the same palette.
 *
 * The split is contrast, not taste. Ink is white only where white passes on
 * the fill; the three light fills keep Gunmetal. Ratios below are against the
 * fill, for the 36px/600 type this band uses — large text, so the bar is 3:1,
 * but every pairing here clears 4.5:1 except where noted. */
.marquee-band--gunmetal {
  --marquee-bg: var(--brand-gunmetal);
  --marquee-ink: var(--brand-white);
  --marquee-mark: var(--brand-refresh);
}

/* Ignition is the other dark fill. Amber on it is 2.19:1, which fails even the
 * 3:1 for a graphic element, so the mark goes white here and the fill itself
 * carries the warmth. */
.marquee-band--ignition {
  --marquee-bg: var(--brand-ignition);
  --marquee-ink: var(--brand-white);
  --marquee-mark: var(--brand-white);
}

/* Flagstone uses the deeper `--brand-flagstone-ink` for the same reason
 * about-offset does: white on the lighter value is 3.28:1, which is under AA
 * for anything but large text and leaves no headroom. */
.marquee-band--flagstone {
  --marquee-bg: var(--brand-flagstone-ink);
  --marquee-ink: var(--brand-white);
  --marquee-mark: var(--brand-white);
}

/* The three light fills. White ink on any of them is between 1.3:1 and 1.9:1 —
 * invisible — so they keep Gunmetal, and the mark is Gunmetal too: amber on
 * Refresh is the same colour twice, and amber on Mint or Clay is under 2:1. */
.marquee-band--refresh {
  --marquee-bg: var(--brand-refresh);
  --marquee-ink: var(--brand-gunmetal);
  --marquee-mark: var(--brand-gunmetal);
}

.marquee-band--mint {
  --marquee-bg: var(--brand-mint);
  --marquee-ink: var(--brand-gunmetal);
  --marquee-mark: var(--brand-gunmetal);
}

.marquee-band--clay {
  --marquee-bg: var(--brand-clay);
  --marquee-ink: var(--brand-gunmetal);
  --marquee-mark: var(--brand-gunmetal);
}

/* Coral sits between the two groups: white on it is 2.71:1 and Gunmetal is
 * 4.79:1, so it takes dark ink like the other light fills. */
.marquee-band--coral {
  --marquee-bg: var(--brand-coral);
  --marquee-ink: var(--brand-gunmetal);
  --marquee-mark: var(--brand-gunmetal);
}

.marquee-band__track {
  display: flex;
  width: max-content;
  animation: marquee-band-scroll var(--marquee-duration, 40s) linear infinite;
}

/* Pause on hover *and* on focus-within, the WCAG 2.2.2 "pause" mechanism —
 * together with the reduced-motion rule at the end of this block. */
.marquee-band:hover .marquee-band__track,
.marquee-band__track:focus-within {
  animation-play-state: paused;
}

/* Exactly half the track is one full copy, so -50% lands the duplicate where
 * the original began. */
@keyframes marquee-band-scroll {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

.marquee-band__list {
  display: flex;
  flex: 0 0 auto;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The gap lives on the item rather than the list so it also falls *between*
 * the two copies — a `gap` on each list would leave the wrap seam short by one
 * space and the loop would visibly stutter once per cycle. */
.marquee-band__item {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 25px;
  padding-right: 25px;
  list-style: none;
}

/* Figma has no comp for this one; the metrics are the theme's (36/600) in the
 * brand heading face. Clamped down on small screens the way the theme steps
 * its own size at 767/575/375. */
.marquee-band__text {
  color: var(--marquee-ink);
  font-family: var(--brand-font-heading);
  font-size: clamp(22px, 3.4vw, 36px);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.01em;
  white-space: nowrap;
}

.marquee-band__separator {
  display: inline-flex;
  flex: none;
  align-items: center;
  color: var(--marquee-mark);
  font-size: clamp(16px, 2.2vw, 24px);
}

/* An uploaded separator is sized to the type it sits beside rather than to its
 * own pixel dimensions, so a 40px logo and a 400px one both land at the same
 * optical weight. `width: auto` keeps whatever aspect it was uploaded at, and
 * the two-class selector beats the theme's `img:not([draggable])`. */
.marquee-band__separator .marquee-band__separator-image {
  width: auto;
  height: clamp(24px, 3vw, 40px);
  object-fit: contain;
}

/* Stop it entirely and let it be read by hand instead. `justify-content` keeps
 * the phrase at the start rather than stranded mid-band, and only the first
 * copy is worth showing once nothing moves. */
@media (prefers-reduced-motion: reduce) {
  .marquee-band__track {
    width: 100%;
    animation: none;
    overflow-x: auto;
  }

  .marquee-band__list:nth-child(2) {
    display: none;
  }
}

/* ==========================================================================
 * Service — a grid of numbered cards.
 *
 * Rebuilt from the theme's `feature-area-1`, which rendered cream
 * `--smoke-color` panels with a 20px title and an unbounded icon slot. Nothing
 * of that language appears in the comps, which is why the slice read as a
 * different product to the sections around it.
 *
 * The vocabulary here is taken from Figma 291:476's card sections: solid brand
 * fills, hard square corners, Gunmetal ink, Barlow 500 16/26 body, and a text
 * link drawn as a label over a 2px rule. Nothing in that page is rounded or
 * shadowed, so nothing here is either.
 * ========================================================================== */

.service-cards {
  padding: var(--section-space) 0;
}

/* --- section header --- */

.service-cards__header {
  max-width: 720px;
  margin: 0 auto 64px;
  text-align: center;
}

/* Matches the other section headings: an h2 must state its own letter-spacing,
 * since brand.css only sets it for h1/h3 and an h2 that omits it silently
 * renders at `normal`. */
.service-cards__heading {
  margin: 0;
  color: var(--brand-ink);
  font-family: var(--brand-font-heading);
  font-size: clamp(32px, 3.4vw, 49px);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.0295em;
  /* Stops the centred headline stranding its last word on a line of its own,
   * which a fixed measure will do to any title whose length it was not chosen
   * for. Ignored by browsers that do not support it, which just get the ragging
   * they get today. */
  text-wrap: balance;
}

.service-cards__intro {
  margin-top: 16px;
}

.service-cards__intro p {
  margin: 0;
  color: var(--brand-ink);
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  font-weight: 500;
  line-height: var(--brand-lh-body);
}

/* --- card --- */

/* A flex column so the link sits on the bottom edge whatever the copy above it
 * does: with three cards side by side, links that float at different heights
 * are the thing that makes a grid look unconsidered. `height: 100%` is what
 * lets it fill the stretched column. */
.service-card {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 40px 36px;
  overflow: hidden;
  color: var(--brand-ink);
  background-color: var(--service-card-fill);
  transition: transform 0.4s ease;
}

/* All three fills are light, so all three keep Gunmetal ink — the same pairing
 * the comps use on these exact colours (#072b31 on Refresh and Coral). */
.service-card--mint {
  --service-card-fill: var(--brand-mint);
}

.service-card--refresh {
  --service-card-fill: var(--brand-refresh);
}

.service-card--coral {
  --service-card-fill: var(--brand-coral);
}

/* The same lift the campus team cards use, so a card behaves the way cards
 * elsewhere on the site already do. */
.service-card:hover {
  transform: translateY(-6px);
}

/* The card's index, as structure rather than information — it duplicates the
 * reading order, which is why it is `aria-hidden` in the markup. Set large and
 * held back to a whisper so it gives the card a corner without competing with
 * the title. Absolute so it never takes part in the flex column's spacing. */
.service-card__number {
  position: absolute;
  top: 24px;
  right: 28px;
  color: var(--brand-gunmetal);
  font-family: var(--brand-font-heading);
  font-size: 64px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.04em;
  opacity: 0.16;
  pointer-events: none;
}

/* A fixed box, because the field takes any upload. Unbounded, each icon set its
 * own width and the three cards disagreed by 60px across the row — the single
 * most visible fault in the old treatment. `contain` keeps a wide logo and a
 * square glyph on the same optical footing. */
.service-card__icon {
  width: 64px;
  height: 64px;
  margin-bottom: 32px;
}

/* Two-class selector: the theme's `img:not([draggable])` is (0,1,1) and would
 * otherwise let the upload set its own height straight back. */
.service-card__icon img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: left center;
}

/* Figma's card titles run Rubik at the Heading 5 weight. Letter-spacing is
 * declared because this is an `h3` and brand.css gives h3 -1.8px, which is a
 * display value and far too tight at 24px. */
.service-card__title {
  margin: 0;
  color: var(--brand-ink);
  font-family: var(--brand-font-heading);
  font-size: 24px;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.02em;
}

.service-card__title a {
  color: inherit;
}

/* Barlow 500 16/26 — the comp's body spec on these cards, to the number. */
.service-card__text {
  margin: 12px 0 0;
  color: var(--brand-ink);
  font-family: var(--brand-font-body);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.625;
}

/* `margin-top: auto` is what pins the link to the bottom of the tallest card in
 * the row; the 28px is its minimum breathing room when the copy is long enough
 * to reach it anyway. */
.service-card__actions {
  margin-top: auto;
  padding-top: 28px;
}

/* Only when a card carries more than one CTA: stack them with a gap and keep
   each shrunk to its own width (flex-start), so text links and buttons alike
   line up left. Gated on `--multi` so the 17 single-link instances render
   exactly as before — this is an addition, never a restyle. */
.service-card__actions--multi {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;
}

/* The link's own colours come from `StyledLink`'s variants, which paint with
 * the theme's dark `--title-color`. That is already correct on all three fills,
 * so nothing is overridden here — only the hit area is widened so a click just
 * outside the glyph still lands. */
.service-card__link {
  display: inline-flex;
  align-items: center;
}

@media (prefers-reduced-motion: reduce) {
  .service-card {
    transition: none;
  }

  .service-card:hover {
    transform: none;
  }
}

@media (max-width: 991px) {
  .service-cards {
    padding: 80px 0;
  }

  .service-cards__header {
    margin-bottom: 40px;
  }

  .service-card {
    padding: 32px 28px;
  }

  .service-card__number {
    font-size: 48px;
  }
}

/* ==========================================================================
   Site search — the header trigger, its panel, and the results page
   ==========================================================================
 * The trigger is an icon button in the header bar at every width. It sits
 * before the mobile menu toggle so the two read as one cluster of controls on
 * a phone, and it is the only control in the bar on desktop besides the nav.
 */

.site-header__actions {
  display: inline-flex;
  align-items: center;
  margin-left: auto;
  padding-left: 24px;
}

.site-search {
  position: relative;
}

.site-search__trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--brand-white);
  cursor: pointer;
  transition: background-color ease 0.25s, color ease 0.25s;
}

.site-search__trigger:hover,
.site-search__trigger:focus-visible {
  background-color: rgb(255 255 255 / 0.12);
}

.site-search__icon {
  display: block;
}

/* Above the header's own stacking so the panel is never clipped by it, and
   below the mobile menu, which is a full-screen surface. */
.site-search__scrim {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgb(7 43 49 / 0.45);
}

.site-search__panel {
  position: absolute;
  top: calc(100% + 12px);
  right: 0;
  z-index: 1001;
  width: min(420px, calc(100vw - 40px));
  padding: 24px;
  background-color: var(--brand-white);
  box-shadow: 0 24px 60px rgb(7 43 49 / 0.28);
}

.site-search__label,
.search-page__label {
  display: block;
  margin-bottom: 10px;
  font-family: var(--brand-font-body);
  font-size: 13px;
  font-weight: var(--brand-fw-body-bold, 600);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--brand-gunmetal);
}

.site-search__row,
.search-page__row {
  display: flex;
  gap: 8px;
}

/* A 2px bottom rule rather than a boxed field: it matches the form inputs the
   Form slice already uses, so the site has one input treatment, not two. */
.site-search__input,
.search-page__input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 12px 4px;
  border: 0;
  border-bottom: 2px solid var(--brand-gunmetal);
  background: transparent;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-base, 16px);
  color: var(--brand-gunmetal);
}

.site-search__input:focus,
.search-page__input:focus {
  outline: none;
  border-bottom-color: var(--brand-refresh);
}

.site-search__submit,
.search-page__submit {
  flex: none;
  padding: 12px 24px;
  border: 0;
  background-color: var(--btn-bg, var(--brand-refresh));
  color: var(--btn-ink, var(--brand-ink));
  font-family: var(--brand-font-body);
  font-size: 14px;
  font-weight: var(--brand-fw-body-bold, 600);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
}

/* --- results page --- */

.search-page {
  padding: 80px 0 120px;
}

.search-page__form {
  max-width: 640px;
  margin-bottom: 48px;
}

.search-page__count,
.search-page__note {
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  line-height: 1.7;
  color: var(--brand-ink);
}

.search-page__count {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 48px;
  padding-bottom: 20px;
  border-bottom: 1px solid rgb(7 43 49 / 0.12);
}

/* The number is the answer to "did it find anything", so it is the one thing
   here set at display scale rather than body scale. */
.search-page__count-num {
  font-family: var(--brand-font-heading);
  font-size: clamp(28px, 3vw, 40px);
  line-height: 1;
  color: var(--brand-ignition);
}

.search-page__count-query {
  font-weight: var(--brand-fw-body-bold, 600);
  color: var(--brand-gunmetal);
}

.search-page__note + .search-page__note {
  margin-top: 12px;
}

.search-group + .search-group {
  margin-top: 48px;
}

/* The first row's rule would otherwise sit tight under the eyebrow. */
.search-group__list {
  border-top: 1px solid rgb(7 43 49 / 0.1);
}

/* The count rides in the heading rather than under it: it answers "is what I
   want in here?" at the moment the reader is deciding whether to read on. */
/* An eyebrow rather than a second-tier headline: these label a list, they are
   not content in their own right, and at headline weight they competed with
   the results underneath them. The Refresh rule is the same accent the site
   uses to open a section elsewhere. */
.search-group__heading {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 8px;
  font-family: var(--brand-font-body);
  font-size: 13px;
  font-weight: var(--brand-fw-body-bold, 600);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--brand-gunmetal);
}

.search-group__heading::before {
  content: "";
  flex: none;
  width: 24px;
  height: 2px;
  background: var(--brand-refresh);
}

.search-group__count {
  font-size: 12px;
  font-weight: var(--brand-fw-body);
  letter-spacing: normal;
  color: var(--brand-ink);
  opacity: 0.6;
}

.search-group__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Rows are separated by hairlines rather than whitespace: a search result list
   is scanned, not read, and a rule gives the eye a consistent edge to run down.
   The row is the hit area — see the component. */
.search-result {
  border-bottom: 1px solid rgb(7 43 49 / 0.1);
}

.search-result__link {
  display: flex;
  align-items: center;
  gap: 24px;
  padding: 22px 16px 22px 0;
  color: inherit;
  text-decoration: none;
  transition: background-color ease 0.2s, padding-left ease 0.2s;
}

/* The row shifts right on hover and picks up a tinted ground — enough to say
   "this is the one", without a border that would fight the hairlines. */
.search-result__link:hover,
.search-result__link:focus-visible {
  background-color: rgb(255 178 91 / 0.1);
  padding-left: 16px;
}

/* Capped measure: the row runs the full container so the hit area and the
   hairlines do, but ~78 characters is already past comfortable for scanning
   and the summaries were running the whole width. */
.search-result__body {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 78ch;
}

.search-result__title {
  display: block;
  margin: 0 0 4px;
  font-family: var(--brand-font-heading);
  font-size: clamp(18px, 1.6vw, 21px);
  line-height: 1.3;
  color: var(--brand-gunmetal);
}

.search-result__link:hover .search-result__title,
.search-result__link:focus-visible .search-result__title {
  color: var(--brand-ignition);
}

.search-result__text {
  display: block;
  margin: 0;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-base, 16px);
  line-height: 1.6;
  color: var(--brand-ink);
  opacity: 0.85;
}

/* Drawn, not a glyph: a character would be announced by a screen reader and
   would take `color` from the theme rather than the row's own state. */
.search-result__arrow {
  flex: none;
  width: 10px;
  height: 10px;
  border-top: 2px solid var(--brand-refresh);
  border-right: 2px solid var(--brand-refresh);
  transform: rotate(45deg);
  opacity: 0;
  transition: opacity ease 0.2s, transform ease 0.2s;
}

.search-result__link:hover .search-result__arrow,
.search-result__link:focus-visible .search-result__arrow {
  opacity: 1;
  transform: rotate(45deg) translate(2px, -2px);
}

@media (prefers-reduced-motion: reduce) {
  .search-result__link,
  .search-result__arrow {
    transition-duration: 1ms;
  }
}

@media (max-width: 575px) {
  .site-search__panel {
    position: fixed;
    top: auto;
    right: 20px;
    left: 20px;
    width: auto;
  }

  .search-page {
    padding: 56px 0 80px;
  }
}

/* ==========================================================================
 * ModalPopup — a timed dialog any page can carry.
 *
 * Portalled onto document.body (see the component), so it escapes any Section
 * overflow/transform and is never clipped. The backdrop dims the whole page and
 * sits above every theme overlay — the menus top out at 999999 — so the dialog
 * genuinely owns focus. The dialog scrolls internally when the content is tall.
 * ========================================================================== */
.modal-popup {
  position: fixed;
  inset: 0;
  z-index: 1000000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.modal-popup__backdrop {
  position: absolute;
  inset: 0;
  /* Black Coffee at ~55% — a dim, not a blackout, so the page stays legible
     behind it and the focus shift reads as intentional. */
  background: rgba(10, 12, 0, 0.55);
  animation: modal-popup-fade 240ms ease both;
}

.modal-popup__dialog {
  position: relative;
  z-index: 1;
  width: min(520px, 100%);
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  background: var(--brand-surface-light);
  color: var(--brand-ink);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.35);
  animation: modal-popup-rise 280ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

.modal-popup__media {
  margin: 0;
}

/* Two classes to beat the theme's `img:not([draggable]) { height: auto }` and
   keep the poster filling the dialog's width. */
.modal-popup__media .modal-popup__image {
  display: block;
  width: 100%;
  height: auto;
}

.modal-popup__body {
  padding: 32px 32px 0;
  font-family: var(--brand-font-body);
  font-size: var(--brand-fs-p-m);
  line-height: var(--brand-lh-body);
}

/* With no image above it, the copy needs headroom to clear the close button. */
.modal-popup__dialog:not(:has(.modal-popup__media)) .modal-popup__body {
  padding-top: 56px;
}

.modal-popup__body p {
  margin: 0 0 16px;
}

.modal-popup__body p:last-child {
  margin-bottom: 0;
}

.modal-popup__body ul {
  margin: 0 0 16px;
  padding-left: 22px;
  list-style: disc;
}

.modal-popup__actions {
  padding: 24px 32px 32px;
}

/* No image and no copy above it (links only) — clear the close button. */
.modal-popup__dialog:not(:has(.modal-popup__media)):not(:has(.modal-popup__body))
  .modal-popup__actions {
  padding-top: 56px;
}

.modal-popup__close {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  font-size: 30px;
  line-height: 1;
  color: var(--brand-ink);
  background: rgba(255, 255, 255, 0.85);
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  transition: background 0.2s ease;
}

.modal-popup__close:hover,
.modal-popup__close:focus-visible {
  background: #fff;
}

@keyframes modal-popup-fade {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes modal-popup-rise {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .modal-popup__backdrop,
  .modal-popup__dialog {
    animation: none;
  }
}
