/* ============================================================================
   Step carousel  -  v2-stepcar.css
   ----------------------------------------------------------------------------
   One finite, full-bleed rail that replaces the five different numbered-step
   components the site had grown: .p2rows/.p2row__no, .case-steps, .pstep,
   .wwd, and .affiliate-step. A card is exactly a title, a hairline rule, and
   body text. There are no step numbers.

   WHY ITS OWN FILE
   lerws.com runs two page stacks: the homepage loads v2-home.css, every other
   page loads v2-pages.css. This component is needed by both, so it cannot live
   in either without duplicating it or dragging one stack's 3k lines onto the
   other. It is linked directly by every page that uses it.

   FULL BLEED
   The rail is a sibling of .wrap, not a child, so it spans the section's full
   width. The page gutter is carried by the first and last card as a margin,
   which keeps the rail aligned to the page grid while it runs to the screen
   edge. Putting that gutter on the track as padding instead is what made the
   testimonial rail's scrollLeft pin above zero, so its prev arrow could never
   disable - this component deliberately avoids that shape, and avoids
   scroll-snap for the same reason.

   VISIBLE CARDS
   3.1 on desktop, 2 on tablet, 1.2 on mobile. Percentages resolve against the
   track's content box, which is the full section width, so the arithmetic is
   "space after the gutter, divided by the number of cards on screen".
   ========================================================================== */

.stepcar {
  --stepcar-gap: clamp(16px, 2vw, 32px);
  /* The card rule owns its own colour instead of using --line. The 10 industry
     sub-pages redefine --line as rgba(255,255,255,.14) for their dark surfaces,
     which made the hairline invisible on their light background. */
  --stepcar-line: rgba(17, 17, 15, .16);
  position: relative;
}

/* ---- the rail ---- */
.stepcar__track {
  display: flex;
  gap: var(--stepcar-gap);
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  cursor: grab;
  -ms-overflow-style: none;
  /* room for the focus ring when the rail is keyboard-focused */
  padding: 2px 0 6px;
}

.stepcar__track::-webkit-scrollbar { display: none; }

/* while dragging: keep the cursor down and stop the pointer selecting the card
   text it is sweeping across */
.stepcar__track.is-dragging {
  cursor: grabbing;
  user-select: none;
  scroll-behavior: auto;
}
.stepcar__track.is-dragging * { pointer-events: none; }

/* a rail with nothing to scroll should not pretend to be draggable */
.stepcar.is-static .stepcar__track { cursor: default; }

.stepcar__track:focus-visible {
  outline: 2px solid var(--orange);
  outline-offset: 4px;
}

/* ---- cards ---- */
.stepcar__card {
  flex: 0 0 auto;
  /* desktop: 3.1 visible */
  width: calc((100% - var(--gutter)) / 3.1 - var(--stepcar-gap));
}

.stepcar__card:first-child { margin-left: var(--gutter); }
.stepcar__card:last-child { margin-right: var(--gutter); }

/* Industries renders its 10 cards as links. Without this they inherit the
   site's default underline, which propagates to the title and body text. */
a.stepcar__card {
  display: block;
  color: inherit;
  text-decoration: none;
}

a.stepcar__card .stepcar__cardtitle,
a.stepcar__card .stepcar__text { transition: color .2s ease, border-color .2s ease; }

a.stepcar__card:hover .stepcar__cardtitle { color: var(--orange); border-bottom-color: var(--orange); }
a.stepcar__card:hover .stepcar__text { color: var(--ink); }

a.stepcar__card:focus-visible {
  outline: 2px solid var(--orange);
  outline-offset: 6px;
  border-radius: 2px;
}

.stepcar__cardtitle {
  margin: 0 0 clamp(28px, 3.4vw, 52px);
  padding-bottom: clamp(12px, 1.2vw, 16px);
  border-bottom: 1px solid var(--stepcar-line);
  /* Titles vary from one word to a full phrase. Reserving two lines keeps every
     card's rule on the same baseline while cards sit side by side; below 601px
     only one card is really in view, so it is released and the title flows.
     The padding is added back because box-sizing is border-box here, so a bare
     2-line min-height would have the padding eaten out of the text room. */
  min-height: calc(1.12em + clamp(12px, 1.2vw, 16px));
  font-weight: 400;
  font-size: clamp(26px, 2.5vw, 40px);
  line-height: 1.12;
  letter-spacing: -0.03em;
  color: var(--ink);
}

.stepcar__text {
  margin: 0;
  color: var(--muted);
  font-size: clamp(14px, 1.05vw, 16px);
  line-height: 1.6;
}

/* ---- arrows: same affordance as the testimonial rail ---- */
.stepcar__nav {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  padding: clamp(20px, 2.4vw, 32px) var(--gutter) 0;
}

.stepcar.is-static .stepcar__nav { display: none; }

.stepcar__arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  padding: 0;
  border: 1px solid rgba(17, 17, 15, .22);
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  transition: background .2s ease, border-color .2s ease, opacity .2s ease;
}

.stepcar__arrow svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.stepcar__arrow:hover:not(:disabled) {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.stepcar__arrow:focus-visible {
  outline: 2px solid var(--orange);
  outline-offset: 3px;
}

.stepcar__arrow:disabled {
  opacity: .3;
  cursor: default;
}

/* ---- the one big title above the rail ---- */
.stepcar-head { padding-bottom: clamp(34px, 4.2vw, 68px); }

/* Scoped to .stepcar-head (0,0,2,0) rather than bare .stepcar-title (0,0,1,0)
   so it outranks the case template's `.case-block h2` (0,0,1,1), which would
   otherwise cap the title at 48px on all 45 case-study pages. */
.stepcar-head .stepcar-title {
  margin: 0;
  font-weight: 400;
  font-size: clamp(34px, 5.6vw, 88px);
  line-height: 1.03;
  letter-spacing: -0.04em;
  color: var(--ink);
  text-wrap: balance;
}

/* second half of the title drops back, as in the reference layout */
.stepcar-head .stepcar-title .o { color: var(--muted); }

/* dark sections invert both */
.p2sec--dark .stepcar-head .stepcar-title { color: #f6efec; }
.p2sec--dark .stepcar-head .stepcar-title .o { color: rgba(246, 239, 236, .55); }
.p2sec--dark .stepcar { --stepcar-line: rgba(246, 239, 236, .22); }
.p2sec--dark .stepcar__cardtitle { color: #f6efec; }
.p2sec--dark .stepcar__text { color: rgba(246, 239, 236, .72); }
.p2sec--dark .stepcar__arrow { border-color: rgba(246, 239, 236, .3); color: #f6efec; }
.p2sec--dark .stepcar__arrow:hover:not(:disabled) { background: #f6efec; border-color: #f6efec; color: var(--ink); }

/* ---- tablet: 2 cards, filling exactly to the right edge ---- */
@media (max-width: 1024px) {
  .stepcar__card {
    width: calc((100% - var(--gutter) - var(--stepcar-gap)) / 2);
  }
}

/* ---- mobile: 1.2 cards ---- */
@media (max-width: 600px) {
  .stepcar__card {
    width: calc((100% - var(--gutter)) / 1.2 - var(--stepcar-gap));
  }
  .stepcar__cardtitle { min-height: 0; }
  .stepcar__arrow { width: 42px; height: 42px; }
}

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

/* ---- full-bleed escape for rails that sit inside a constrained wrapper ----
   On p2sec pages the rail is a sibling of .wrap, so it is already full width.
   Case-study pages have no such seam: the rail lives inside the centred
   .case-wrap. This is the escape the site already ships for
   .project-detail-gallery (v2-pages.css:625), and it is safe here because both
   html and body set overflow-x:hidden (v2-pages.css:26-27), so the scrollbar
   sliver 100vw overshoots by is clipped rather than causing a sideways scroll.

   It only computes correctly from a full-width child of a CENTRED container,
   which is why the owning .case-block drops its two-column grid below. */
.stepcar--bleed {
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
}

/* .case-block is a .3fr/.7fr grid that puts the h2 in a side column. The rail
   needs the full measure, so the step-carousel variant stacks instead. */
/* .case-block is a .3fr/.7fr grid, so any child that needs the full width has
   to opt out of it. --stepcar did that for the carousel; --full is the same
   escape hatch under a name that does not imply a carousel. */
.case-block--stepcar,
.case-block--full {
  display: block;
  padding: clamp(64px, 8vw, 120px) 0 0;
}

/* ============================================================================
   Feature section with media  -  .tfx
   ----------------------------------------------------------------------------
   The "Before buyers trust it" / industry trust sections: one big title, a
   two-column grid of title/hairline/text items, and a square image beside them.
   Same card language as the rail above, so the two components stay consistent.
   ========================================================================== */

.tfx {
  --stepcar-line: rgba(17, 17, 15, .16);
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  gap: clamp(36px, 5vw, 96px);
  align-items: center;
}

.tfx__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(34px, 4.4vw, 72px) clamp(28px, 3.4vw, 64px);
}

.tfx__title {
  margin: 0 0 clamp(14px, 1.6vw, 22px);
  padding-bottom: clamp(10px, 1.1vw, 14px);
  border-bottom: 1px solid var(--stepcar-line);
  font-weight: 500;
  font-size: clamp(17px, 1.35vw, 20px);
  line-height: 1.25;
  letter-spacing: -0.015em;
  color: var(--ink);
}

.tfx__text {
  margin: 0;
  color: var(--muted);
  font-size: clamp(14px, 1.05vw, 16px);
  line-height: 1.6;
}

.tfx__p { margin: 0 0 12px; }
.tfx__p:last-child { margin-bottom: 0; }


/* square media, cropped - photos vary in aspect and the grid should not */
.tfx__media { margin: 0; }
.tfx__media img {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  height: auto;
  object-fit: cover;
  background: var(--stone, #e6e0d7);
}

@media (max-width: 1024px) {
  .tfx { grid-template-columns: 1fr; gap: clamp(32px, 5vw, 56px); }
  /* media first on tablet so the section still opens on the image */
  .tfx__media { order: -1; }
  /* Every image in this slot is now composed as a deliberate 1:1, so widening the
     box to 16/10 here cropped 38% off the picture and sliced devices in half.
     Cap the width instead: the square is kept whole and still does not dominate
     the stacked column. */
  .tfx__media img { max-width: 520px; margin-inline: auto; }
}

@media (max-width: 600px) {
  .tfx__grid { grid-template-columns: 1fr; gap: 30px; }
  .tfx__media img { max-width: 100%; }
}

/* ============================================================================
   Interactive deliverables list  -  .deliv
   ----------------------------------------------------------------------------
   The Business-outcomes section: a big title, a square image on the left, and a
   large list on the right where the active item is ink and the rest drop back.
   Hovering or focusing an item swaps the image.

   Degrades honestly without JS: the first item is active in the markup, every
   item's description is in the DOM, and the list is real buttons so keyboard
   and screen-reader users get the same behaviour as a mouse.
   ========================================================================== */

.deliv {
  --stepcar-line: rgba(17, 17, 15, .16);
  display: grid;
  grid-template-columns: minmax(0, .8fr) minmax(0, 1fr);
  gap: clamp(36px, 5vw, 92px);
  align-items: center;
}

.deliv__media { margin: 0; position: relative; }
.deliv__media img {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  height: auto;
  object-fit: cover;
  background: var(--stone, #e6e0d7);
  transition: opacity .25s ease;
}
.deliv__media.is-swapping img { opacity: 0; }

.deliv__list { margin: 0; padding: 0; list-style: none; }

.deliv__item + .deliv__item { margin-top: clamp(10px, 1.1vw, 18px); }

.deliv__btn {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  font: inherit;
  cursor: pointer;
  color: var(--muted);
  transition: color .2s ease;
}

.deliv__name {
  margin: 0;
  font-weight: 400;
  font-size: clamp(24px, 3.2vw, 50px);
  line-height: 1.2;
  letter-spacing: -0.03em;
  color: inherit;
}

.deliv__item.is-active .deliv__btn,
.deliv__btn:hover,
.deliv__btn:focus-visible { color: var(--ink); }

.deliv__btn:focus-visible { outline: 2px solid var(--orange); outline-offset: 4px; border-radius: 2px; }

/* The descriptions live under the picture, not in the list. Every one stays
   in the HTML; without JS they simply stack and all read normally. Once the
   script marks the caption enhanced they overlay in a single grid cell, so the
   block keeps the height of the longest and nothing shifts when it swaps. */
.deliv__caption { margin: clamp(16px, 1.8vw, 24px) 0 0; }

.deliv__desc {
  margin: 0;
  max-width: 46ch;
  color: var(--muted);
  font-size: clamp(14px, 1.05vw, 16px);
  line-height: 1.6;
}
.deliv__desc + .deliv__desc { margin-top: 10px; }

.deliv__caption.is-enhanced { display: grid; }
.deliv__caption.is-enhanced .deliv__desc {
  grid-area: 1 / 1;
  margin: 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity .25s ease;
}
.deliv__caption.is-enhanced .deliv__desc.is-active { opacity: 1; visibility: visible; }

.deliv__sub {
  margin: clamp(14px, 1.6vw, 22px) 0 0;
  max-width: 52ch;
  color: var(--muted);
  font-size: clamp(15px, 1.15vw, 18px);
  line-height: 1.55;
}

@media (max-width: 1024px) {
  .deliv { grid-template-columns: 1fr; gap: clamp(28px, 4vw, 48px); }
  .deliv__media { order: -1; }
  /* same reasoning as .tfx__media above: the four outcome images are composed
     1:1, so cap the width rather than crop them to a wider box */
  .deliv__media img { max-width: 520px; margin-inline: auto; }
  /* the caption sits under the picture and above the list on this breakpoint;
     tapping a title still swaps both, so the overlay behaviour is kept */
}

@media (max-width: 600px) {
  .deliv__media img { max-width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .deliv__media img,
  .deliv__caption.is-enhanced .deliv__desc { transition: none; }
}


/* ============================================================================
   Contained testimonial  -  .bigq
   ----------------------------------------------------------------------------
   One review, centred in a contained column on near-black #110101, filling the
   viewport height. Contrast against #f6efec is about 18:1.

   Attribution is only what the source review actually carries - initials,
   "Verified client", and the Google rating. The reviews have no names or job
   titles, so none are shown.
   ========================================================================== */

.bigq {
  background: #110101;
  color: #f6efec;
  padding: clamp(72px, 11vw, 150px) var(--gutter);
  /* full height, with the quote centred in it */
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  align-items: center;
}

.bigq__inner {
  width: 100%;
  max-width: min(1080px, 82vw);
  margin: 0 auto;
}

.bigq__quote {
  margin: 0;
  font-weight: 400;
  font-size: clamp(24px, 3vw, 46px);
  line-height: 1.26;
  letter-spacing: -0.026em;
  color: #f6efec;
  text-wrap: pretty;
}

.bigq__quote p { margin: 0 0 .7em; }
.bigq__quote p:last-child { margin-bottom: 0; }

.bigq__rule {
  margin: clamp(38px, 5vw, 76px) 0 clamp(18px, 2.2vw, 28px);
  border: 0;
  border-top: 1px solid rgba(246, 239, 236, .32);
}

.bigq__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
}

.bigq__who { display: flex; align-items: center; gap: 14px; }

.bigq__avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border-radius: 4px;
  background: rgba(246, 239, 236, .16);
  color: #f6efec;
  font-size: 13px;
  letter-spacing: .06em;
}

.bigq__name { margin: 0; font-size: clamp(14px, 1.05vw, 16px); color: #f6efec; }
.bigq__role { margin: 2px 0 0; font-size: clamp(13px, .95vw, 15px); color: rgba(246, 239, 236, .68); }

.bigq__rating { display: flex; align-items: center; gap: 8px; margin: 0; font-size: clamp(13px, .95vw, 15px); color: rgba(246, 239, 236, .68); }
.bigq__stars { color: #f6efec; letter-spacing: .12em; }

@media (max-width: 600px) {
  .bigq__inner { max-width: none; }
  /* svh already accounts for mobile browser chrome; the section can still grow
     past it if the quote is long, because min-height is a floor not a cap */
  .bigq { padding-top: 84px; padding-bottom: 84px; }
}

/* ============================================================================
   Scroll-driven project carousel  -  .pcar
   ----------------------------------------------------------------------------
   The slides advance sideways as the page scrolls down, with no buttons. The
   mechanism matches the reference: a tall outer track supplies the scroll
   distance, an inner frame is sticky at the top of the viewport, and the strip
   inside it is translated on the X axis in proportion to scroll progress.

   Track height is (slides x 100vh), so each slide gets one screen of scrolling.

   FALLBACKS, both real rather than decorative:
   - no JS: .pcar never gets .is-live, so the strip stays a normal horizontal
     scroll region the visitor can swipe or shift-scroll.
   - reduced motion, and small screens: the pin is switched off entirely and the
     slides stack vertically, because pinning the viewport is exactly the effect
     that setting asks us not to produce.
   ========================================================================== */

.pcar { position: relative; }

.pcar__scroll { position: relative; }

.pcar__frame { overflow: hidden; }

.pcar__strip {
  display: flex;
  /* before JS takes over this is just a scrollable row */
  overflow-x: auto;
  scrollbar-width: none;
}
.pcar__strip::-webkit-scrollbar { display: none; }

/* once the script pins the section the strip is driven by transform instead */
.pcar.is-live .pcar__scroll { height: calc(var(--pcar-slides, 3) * 100vh); }
.pcar.is-live .pcar__frame { position: sticky; top: 0; height: 100vh; }
.pcar.is-live .pcar__strip { overflow: visible; will-change: transform; }

.pcar__slide {
  position: relative;
  flex: 0 0 100%;
  display: block;
  overflow: hidden;
  min-height: min(92vh, 940px);
  color: #f6efec;
  text-decoration: none;
  background: var(--ink, #11110f);
}

.pcar.is-live .pcar__slide { min-height: 100vh; height: 100vh; }

.pcar__bg {
  /* These are light, busy website screenshots rather than photography, so the
     artwork is darkened before the scrim. Without it legibility depends on
     whatever each screenshot happens to show behind the title. */
  filter: brightness(.52) saturate(.85);
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.pcar__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(17, 17, 15, .92) 0%, rgba(17, 17, 15, .72) 38%, rgba(17, 17, 15, .46) 68%, rgba(17, 17, 15, .34) 100%);
}

.pcar__inner {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: inherit;
  height: 100%;
  padding: clamp(28px, 5vw, 76px) var(--gutter);
}

.pcar__title {
  display: block;
  max-width: 16ch;
  margin: 0 0 clamp(28px, 4vw, 64px);
  font-weight: 400;
  font-size: clamp(30px, 5vw, 78px);
  line-height: 1.04;
  letter-spacing: -0.038em;
  color: #f6efec;
}

.pcar__title .o { color: rgba(246, 239, 236, .46); }

.pcar__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: clamp(18px, 2.4vw, 34px);
  padding-top: clamp(16px, 1.8vw, 24px);
  border-top: 1px solid rgba(246, 239, 236, .3);
}

.pcar__logo { display: block; max-height: 40px; max-width: 170px; width: auto; height: auto; object-fit: contain; }
.pcar__logo--invert { filter: brightness(0) invert(1); opacity: .92; }

.pcar__rating { display: flex; align-items: center; gap: 8px; font-size: clamp(13px, .95vw, 15px); color: rgba(246, 239, 236, .62); white-space: nowrap; }
.pcar__stars { color: #f6efec; letter-spacing: .12em; }

/* ---- the label that follows the cursor ---- */
.pcar__cta {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  padding: 13px 24px;
  border-radius: 999px;
  background: #f6efec;
  color: var(--ink, #11110f);
  font-size: clamp(13px, 1vw, 15px);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transform: translate3d(-50%, -50%, 0) scale(.9);
  transition: opacity .18s ease, scale .18s ease;
}

.pcar__slide:hover .pcar__cta { opacity: 1; }

/* keyboard users get it too, parked in the corner since there is no pointer */
.pcar__slide:focus-visible .pcar__cta {
  opacity: 1;
  top: clamp(24px, 4vw, 56px);
  left: auto;
  right: var(--gutter);
  transform: none;
}

.pcar__slide:focus-visible { outline: 3px solid var(--orange, #d83a05); outline-offset: -3px; }

/* touch has no cursor to follow, so the label simply sits in the corner */
@media (hover: none) {
  .pcar__cta {
    opacity: 1;
    top: clamp(20px, 4vw, 40px);
    left: auto;
    right: var(--gutter);
    transform: none;
  }
}

/* ---- no pinning on small screens or for reduced motion ---- */
@media (max-width: 900px), (prefers-reduced-motion: reduce) {
  .pcar.is-live .pcar__scroll { height: auto; }
  .pcar.is-live .pcar__frame { position: static; height: auto; }
  .pcar.is-live .pcar__strip { display: block; transform: none !important; }
  .pcar.is-live .pcar__slide { height: auto; min-height: 78vh; }
  .pcar__strip { display: block; overflow: visible; }
  .pcar__slide + .pcar__slide { margin-top: 2px; }
}

@media (max-width: 600px) {
  .pcar__slide, .pcar.is-live .pcar__slide { min-height: 66vh; }
  .pcar__title { max-width: none; }
  .pcar__foot { flex-wrap: wrap; gap: 12px; }
}

/* The pinned frame needs no scroll-container between it and the document.
   .page-shell and .industry-body both set overflow:hidden, which makes them
   scroll containers, so a sticky descendant sticks inside THEM - and since they
   never scroll, it never appears to stick at all.

   overflow:clip clips identically but does NOT create a scroll container, so
   sticky resolves against the document. The class is applied by the script only
   on pages that actually contain a carousel, so no other page changes, and a
   browser without clip support simply keeps the non-pinned fallback. */
/* NEVER match html or body here: overflow:clip on both axes of the document
   stops the page scrolling altogether. Those two are handled below, where only
   the horizontal axis is clipped. */
.pcar-host:not(html):not(body) { overflow: clip !important; }

/* html and body compute to "hidden auto" from the site-wide overflow-x:hidden,
   which makes body a scroll container and defeats the pin. Clip horizontally
   without becoming one. */
/* body computes to "hidden auto" from the site-wide overflow-x:hidden, which
   makes it a scroll container and defeats the pin. Clip only the horizontal
   axis so it stops being one while the document keeps scrolling normally. */
body.pcar-host { overflow-x: clip !important; overflow-y: visible !important; }

/* ============================================================================
   Service accordion  -  .svcx
   ----------------------------------------------------------------------------
   Big category names in a ruled list. Opening one reveals two sentences, the
   sub-services as clickable pills, and an arrow through to the category page.

   INTERACTION, three ways in rather than hover only:
   - pointer devices open on :hover
   - keyboard opens on :focus-within, so tabbing into a pill reveals its panel
     instead of moving focus to something invisible
   - touch has no hover, so there the header is a real toggle button and the
     panel opens from .is-open, set by the script

   The panel animates with grid-template-rows 0fr -> 1fr, which transitions to
   content height without hard-coding one.
   ========================================================================== */

.svcx { border-top: 1px solid var(--line, #d9d2c7); }

.svcx__row {
  position: relative;
  border-bottom: 1px solid var(--line, #d9d2c7);
}

/* the rule under an open row darkens partway across, as in the reference */
.svcx__row::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -1px;
  width: 0;
  height: 1px;
  background: var(--ink, #11110f);
  transition: width .45s cubic-bezier(.22, .61, .36, 1);
}

/* A container, not a control: the panel holds real links (the pills and the
   category link), and a link cannot live inside a button. The toggle below is
   the only control, and it only matters on touch. */
.svcx__head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: clamp(24px, 4vw, 72px);
  align-items: start;
  width: 100%;
  padding: clamp(22px, 2.6vw, 40px) 0;
}

.svcx__toggle {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.svcx__toggle:focus-visible { outline: 2px solid var(--orange, #d83a05); outline-offset: 4px; }

.svcx__name {
  margin: 0;
  font-weight: 400;
  font-size: clamp(34px, 5vw, 76px);
  line-height: 1.02;
  letter-spacing: -0.04em;
  color: var(--ink, #11110f);
}

/* the disclosure itself */
.svcx__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows .45s cubic-bezier(.22, .61, .36, 1),
              opacity .3s ease;
  opacity: 0;
}

.svcx__panelinner { overflow: hidden; min-height: 0; }

.svcx__blurb {
  margin: 0 0 clamp(18px, 2.2vw, 28px);
  max-width: 46ch;
  color: var(--muted, #655f57);
  font-size: clamp(14px, 1.05vw, 16px);
  line-height: 1.6;
}

.svcx__pills { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 clamp(18px, 2vw, 26px); padding: 0; list-style: none; }

.svcx__pill {
  display: inline-flex;
  padding: 7px 14px;
  border: 1px solid var(--line, #d9d2c7);
  border-radius: 999px;
  color: var(--muted, #655f57);
  font-size: clamp(12px, .95vw, 14px);
  line-height: 1.3;
  text-decoration: none;
  transition: background .2s ease, border-color .2s ease, color .2s ease;
}

.svcx__pill:hover { background: var(--ink, #11110f); border-color: var(--ink, #11110f); color: var(--paper, #f9f5f3); }
.svcx__pill:focus-visible { outline: 2px solid var(--orange, #d83a05); outline-offset: 2px; }

.svcx__more {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--ink, #11110f);
  font-size: clamp(13px, 1vw, 15px);
  text-decoration: none;
}
.svcx__more:hover { color: var(--orange, #d83a05); }
.svcx__more:focus-visible { outline: 2px solid var(--orange, #d83a05); outline-offset: 3px; }
.svcx__more svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; transition: transform .25s ease; }
.svcx__more:hover svg { transform: translateX(4px); }

/* ---- open states ---- */
@media (hover: hover) {
  .svcx__row:hover .svcx__panel,
  .svcx__row:focus-within .svcx__panel { grid-template-rows: 1fr; opacity: 1; }
  .svcx__row:hover::after,
  .svcx__row:focus-within::after { width: 42%; }
}

.svcx__row.is-open .svcx__panel { grid-template-rows: 1fr; opacity: 1; }
.svcx__row.is-open::after { width: 42%; }

@media (max-width: 900px) {
  .svcx__head { grid-template-columns: 1fr; gap: 18px; }
  .svcx__row::after, .svcx__row.is-open::after { width: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .svcx__panel, .svcx__row::after, .svcx__more svg { transition: none; }
}

/* sub-service cards on a category hub page: the .tfx item as a link */
a.svcxcard { display: block; color: inherit; text-decoration: none; }
a.svcxcard .tfx__title, a.svcxcard .tfx__p { transition: color .2s ease, border-color .2s ease; }
a.svcxcard:hover .tfx__title { color: var(--orange, #d83a05); border-bottom-color: var(--orange, #d83a05); }
a.svcxcard:hover .tfx__p { color: var(--ink, #11110f); }
a.svcxcard:focus-visible { outline: 2px solid var(--orange, #d83a05); outline-offset: 6px; border-radius: 2px; }

/* Second block of a paired .tfx section: the image moves to the left and the
   item grid to the right, so two consecutive blocks alternate rather than
   stacking the same layout twice. */
.tfx--left { grid-template-columns: minmax(0, 1fr) minmax(0, 1.55fr); }
.tfx--left .tfx__media { order: -1; }

/* the gap between two blocks in one section */
.tfx + .tfx { margin-top: clamp(48px, 6vw, 104px); }

/* Two .tfx blocks inside one section, each with its own heading, the media
   alternating side. The gap between them is a section-sized gap rather than a
   block-sized one, so the pair still reads as two sections to someone scrolling
   even though there is only one section break in the markup. */
.tfxpair__block + .tfxpair__block { margin-top: clamp(72px, 9vw, 150px); }
/* the second heading is a peer of the first, not a subheading of the section */
.tfxpair__block .stepcar-head { padding-bottom: clamp(28px, 3.4vw, 52px); }


@media (max-width: 1024px) {
  /* both blocks already collapse to one column and lead with the image */
  .tfx--left { grid-template-columns: 1fr; }
}

/* ============================================================================
   Studio figures  -  .stats
   ----------------------------------------------------------------------------
   A full-height 2x2 grid of large figures, each sitting on a hairline rule with
   a short label beneath. No heading: the numbers are the section.
   ========================================================================== */

.stats {
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  align-items: center;
  /* only the vertical axis: .wrap owns the horizontal gutter, and a shorthand
     here would reset it to zero and push the figures to the screen edge */
  padding-block: clamp(72px, 10vh, 140px);
}

.stats__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(44px, 7vw, 120px) clamp(40px, 8vw, 150px);
  width: 100%;
}

.stat__value {
  margin: 0 0 clamp(16px, 1.8vw, 24px);
  padding-bottom: clamp(16px, 2vw, 28px);
  border-bottom: 1px solid var(--line, #d9d2c7);
  font-weight: 400;
  font-size: clamp(58px, 9vw, 132px);
  line-height: .96;
  letter-spacing: -0.045em;
  color: var(--ink, #11110f);
}

.stat__label {
  margin: 0;
  color: var(--muted, #655f57);
  font-size: clamp(14px, 1.15vw, 17px);
  line-height: 1.5;
}

@media (max-width: 600px) {
  .stats { min-height: 0; padding-block: 72px; }
  .stats__grid { grid-template-columns: 1fr; gap: 40px; }
}



/* The industry pages lay a decorative warm radial wash over the whole body via
   .industry-body::before. It starts at the very top edge, so it tints the first
   pixels under the hero and leaves a visible band there - and because it is
   radial it varies across the width, so no single flat --hero-end can match it.

   Fading the wash in over the first 240px lets the body meet the hero at its
   true gradient start, and the decoration still reads everywhere below. */
.industry-body::before {
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 240px);
  mask-image: linear-gradient(180deg, transparent 0, #000 240px);
}

/* ============================================================================
   Client feedback carousel  -  two up, drag only
   ----------------------------------------------------------------------------
   The prev/next buttons are gone: the rail is dragged. Two cards are in view at
   a time, sized from the track's own content box so the gutter is respected.
   ========================================================================== */

.testimonial-arrow { display: none; }

/* a video testimonial sits in the same rail as the written ones */
.tvid {
  position: relative;
  overflow: hidden;
  min-height: 438px;
  border-radius: 0;
  background: var(--ink, #11110f);
  scroll-snap-align: start;
}

.tvid__media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* She stands at roughly 67% across a 1280x720 frame, and cover crops from the
     middle, so the default 50% left her against the right edge. This shifts the
     crop window right until she lands on the card's centre; the left of the
     frame is empty wall, so nothing is lost. */
  object-position: 88% 50%;
}

/* the overlay keeps the name legible over any frame of the video */
.tvid__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(17, 17, 15, .88) 0%, rgba(17, 17, 15, .45) 30%, rgba(17, 17, 15, .06) 62%, rgba(17, 17, 15, 0) 100%);
  pointer-events: none;
}

.tvid__foot {
  position: absolute;
  left: clamp(20px, 2.4vw, 30px);
  right: clamp(20px, 2.4vw, 30px);
  bottom: clamp(20px, 2.4vw, 30px);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
}

.tvid__name { margin: 0; color: #f6efec; font-size: clamp(14px, 1.1vw, 16px); }
.tvid__role { margin: 3px 0 0; color: rgba(246, 239, 236, .66); font-size: clamp(13px, 1vw, 15px); }

.tvid__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 54px;
  height: 38px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgba(17, 17, 15, .55);
  color: #f6efec;
  cursor: pointer;
  transition: background .2s ease;
}
.tvid__toggle:hover { background: rgba(17, 17, 15, .8); }
.tvid__toggle:focus-visible { outline: 2px solid var(--orange, #d83a05); outline-offset: 3px; }
.tvid__toggle svg { width: 15px; height: 15px; fill: currentColor; }
.tvid__toggle .tvid__pause { display: none; }
.tvid.is-playing .tvid__toggle .tvid__play { display: none; }
.tvid.is-playing .tvid__toggle .tvid__pause { display: block; }

/* while it is playing with sound the overlay steps back */
.tvid.is-playing .tvid__scrim { opacity: .55; }

/* ---- how many cards are in view ----
   The track is flex here, not grid. A percentage in grid-auto-columns does not
   resolve against an implicit, overflowing column track, so the cards collapsed
   to min-content (145px, eight in view). Percentage flex-basis in a scrolling
   flex row resolves against the container content box reliably, which is the
   same pattern the project rail uses.

   desktop 2, iPad landscape 2.5 so the next card peeks, iPad portrait 2,
   phone 1 with pagination underneath. */
.testimonial-track {
  --tgap: clamp(16px, 1.8vw, 26px);
  display: flex;
  gap: var(--tgap);
  /* Without this, mandatory snapping resting against the track padding pins
     scrollLeft above zero and the first card sits partly off-screen. */
  scroll-padding-left: var(--gutter);
}

.testimonial-track > * {
  /* desktop: 3 across, so two gaps sit between three cards */
  flex: 0 0 calc((100% - 2 * var(--tgap)) / 3);
  min-width: 0;
}

/* iPad landscape: 2.5 so the next card peeks */
@media (max-width: 1024px) and (min-width: 901px) {
  .testimonial-track > * { flex-basis: calc((100% - 1.5 * var(--tgap)) / 2.5); }
}
/* iPad portrait: 2 across */
@media (max-width: 900px) and (min-width: 601px) {
  .testimonial-track > * { flex-basis: calc((100% - var(--tgap)) / 2); }
}
/* phone: one card, pagination underneath */
@media (max-width: 600px) {
  .testimonial-track > * { flex-basis: 100%; }
}

/* ---- pagination, phone only ---- */
.tdots { display: none; }

@media (max-width: 600px) {
  .tdots {
    display: flex;
    gap: 6px;
    align-items: center;
    margin: 22px var(--gutter) 0;
  }
  .tdots__dot {
    flex: 1 1 0;
    height: 3px;
    padding: 0;
    border: 0;
    border-radius: 2px;
    background: rgba(17, 17, 15, .16);
    cursor: pointer;
    transition: background .25s ease;
  }
  .tdots__dot.is-active { background: var(--ink, #11110f); }
  .tdots__dot:focus-visible { outline: 2px solid var(--orange, #d83a05); outline-offset: 4px; }
}

@media (prefers-reduced-motion: reduce) {
  .tvid__scrim, .tvid__toggle { transition: none; }
}

/* ============================================================================
   Device band  -  .devband
   ----------------------------------------------------------------------------
   A tilted tablet showing the client portal, bleeding off a warm gradient that
   sits between the stone section above and the paper feedback section below.

   The artwork arrives pre-tilted, so no CSS rotation is applied; that would
   compound the tilt already in the file. It is anchored to the section's
   bottom-right corner, where the laptop's own cut edges land on the boundary and
   read as the composition continuing past the frame.

   The gradient occupies the lower 75% of the section, not all of it, so the
   colour ramp stays concentrated instead of stretching. The band above it is
   painted by background-color in the gradient's own top colour, so the two meet
   with no seam and the join to the stone section above stays clean.
   ========================================================================== */

.devband {
  position: relative;
  overflow: hidden;
  /* no bottom padding: the artwork is pinned to the section's bottom edge */
  padding: clamp(48px, 7vw, 104px) 0 0;
  /* full height even on phones, where the artwork alone is short */
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  background-color: #e6e0d7;
  background-image: linear-gradient(0deg, #110101 20%, #6e0008 40%, #df6617 60%, #e6e0d7);
  background-repeat: no-repeat;
  background-position: bottom center;
  background-size: 100% 75%;
}

.devband__inner {
  position: relative;
  display: flex;
  /* right edge flush with the section's; the surplus width spills left, where
     the artwork has nothing that reads as a cut */
  justify-content: flex-end;
  width: 100%;
}

.devband__device {
  display: block;
  /* desktop only: the two media queries below keep their own widths. At this
     size the artwork no longer bleeds off the left, but it stays anchored
     bottom-right so the laptop's cut edges still land on the section edges. */
  width: 85%;
  /* a flex item shrinks to fit by default, which would quietly cap every
     over-width step below back to the container width */
  flex: 0 0 auto;
  max-width: none;
  height: auto;
  /* Sits on the dark end of the ramp, so the shadow only has to lift the
     mid-tones where the device crosses the orange. Kept directional: the blur
     minus the offset is how far it reaches ABOVE the device, and anything much
     larger hazes up into the top of the band and gets clipped by its overflow. */
  filter: drop-shadow(0 26px 40px rgba(17, 1, 1, .40));
}

/* Tablet and below the artwork is too short to carry the section on its own, so
   it is scaled up and allowed to bleed further right; the left tip stays on
   screen because that is where the device reads. */
@media (max-width: 900px) {
  .devband { padding-top: clamp(40px, 8vw, 72px); }
  .devband__device {
    width: 92%;
    /* shorter band here, so the upward reach drops to 10px */
    filter: drop-shadow(0 18px 28px rgba(17, 1, 1, .36));
  }
}

/* Tablet and phone. The viewport-height floor left most of the band as empty
   colour once the artwork was scaled down, so below 900px it hugs the artwork
   instead. Desktop keeps the full-height floor. */
@media (max-width: 900px) {
  .devband { min-height: 0; padding-bottom: 0; }
}

@media (max-width: 600px) {
  /* the artwork is landscape and the phone is narrow, so it needs a much closer
     crop or the dashboard is too small to read */
  .devband__device { width: 170%; }
  /* At 170% the artwork is wider than the screen either way, so both cut edges
     stay off-screen regardless. Centring the FILE is not the same as centring the
     DEVICE though: the tablet occupies roughly the left 40% of the artwork and the
     laptop fills the rest, so a mathematically centred file leaves the tablet
     sitting left of centre. The nudge right re-centres the tablet itself. */
  .devband__inner { justify-content: center; }
  .devband__device {
    transform: translateX(11%);
    /* the device top sits only ~40px below the section edge here, so the reach
       above it has to stay under that or the shadow meets the boundary */
    filter: drop-shadow(0 12px 20px rgba(17, 1, 1, .32));
  }
}

/* ============================================================================
   Numbers band  -  .statband
   ----------------------------------------------------------------------------
   Sits under the case-study carousel. Opens on the same near-black the carousel
   ends on and ramps through the brand orange to whatever follows, which is set
   per page as --stat-end: the stone FAQ section on service pages, the near-black
   footer where the carousel is the last thing in <main>.

   The figures sit in the top half, where the band is still dark. Letting them
   ride down into the orange and the pale tail is what makes the reference
   screenshot unreadable in its second row; the deep bottom padding keeps that
   ramp as empty colour instead.

   .stat, .stat__value and .stat__label are reused from the about-page stats, so
   the number-over-hairline treatment stays defined in one place.
   ========================================================================== */

.statband {
  position: relative;
  padding: clamp(76px, 9vw, 150px) 0 clamp(150px, 20vw, 330px);
  background: linear-gradient(180deg, #110101 0, #3a0206 30%, #6e0008 48%,
    #df6617 74%, var(--stat-end, #e6e0d7) 100%);
}

.statband__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(40px, 5vw, 86px) clamp(32px, 5vw, 96px);
  width: 100%;
}

/* on the dark band the shared rule and label colours have to invert */
.statband .stat__value { color: #fff; border-bottom-color: rgba(255,255,255,.26); }
.statband .stat__label { color: rgba(255,255,255,.62); }

@media (max-width: 900px) {
  .statband { padding: clamp(56px, 9vw, 96px) 0 clamp(96px, 22vw, 200px); }
  .statband__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 560px) {
  .statband__grid { grid-template-columns: 1fr; gap: clamp(30px, 7vw, 44px); }
}

/* ============================================================================
   How it works  -  .hiw   (/process/ "At a glance")
   ----------------------------------------------------------------------------
   Left column pins while the right column scrolls past it. Each step lights its
   own icon and fills its own rail segment as it arrives, so the line reads as a
   progress track rather than a static ornament.

   Sticky needs every ancestor to be free of overflow clipping; .p2sec and .wrap
   both are, which is why this works without a wrapper change. The stack falls
   back to a normal document flow under 900px, where a pinned column would eat
   most of the screen.
   ========================================================================== */

.hiw__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
  gap: clamp(36px, 5vw, 96px);
  align-items: start;
}

/* ---- sticky left ---- */
.hiw__side {
  position: sticky;
  /* clears the fixed header, plus a little air */
  top: clamp(96px, 11vh, 132px);
}

.hiw__title {
  margin: 0;
  font-weight: 400;
  font-size: clamp(34px, 4.2vw, 62px);
  line-height: 1.03;
  letter-spacing: -0.035em;
  color: var(--ink);
}

.hiw__lead {
  margin: clamp(16px, 1.8vw, 24px) 0 0;
  max-width: 46ch;
  font-size: clamp(15px, 1.15vw, 18px);
  line-height: 1.55;
  color: var(--muted);
}

/* Ed: completely rounded */
.hiw__cta {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: clamp(22px, 2.6vw, 34px);
  padding: 15px 28px;
  border: 0;
  border-radius: 999px;
  background: var(--orange);
  color: #fff;
  font-size: 15px;
  line-height: 1;
  text-decoration: none;
  transition: background .2s ease, transform .2s cubic-bezier(.2,.8,.2,1);
}
.hiw__cta:hover { background: var(--orange-dark, #b93104); transform: translateY(-2px); }
.hiw__cta:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.hiw__cta .site-icon { width: 15px; height: 15px; }

.hiw__media { margin: clamp(28px, 3.4vw, 46px) 0 0; }
.hiw__media img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  object-position: 50% 28%;
  border-radius: 0;
}

/* ---- scrolling right ---- */
.hiw__steps {
  position: relative;
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: clamp(28px, 4vh, 56px);
}

/* One continuous rail. Top and height are written by JS from the first and last
   dot, because the distance between two centred dots depends on card heights
   that CSS here cannot resolve. */
.hiw__track {
  position: absolute;
  left: 23px;
  top: 0;
  width: 2px;
  height: 0;
  transform: translateX(-50%);
  background: rgba(17, 17, 15, .12);
  pointer-events: none;
}

.hiw__track-fill {
  display: block;
  width: 100%;
  height: 0;
  background: var(--orange);
  transition: height .45s cubic-bezier(.2,.8,.2,1);
}

/* Each step claims a slice of the viewport so the column is long enough for the
   left one to stay pinned the whole way down. Without this the side column runs
   out of travel by the third step and starts scrolling away again. */
.hiw__step { min-height: clamp(280px, 52vh, 540px); }

.hiw__step {
  position: relative;
  display: grid;
  grid-template-columns: 46px minmax(0, 1fr);
  gap: clamp(16px, 1.8vw, 26px);
  /* centring both the dot and the card in the row makes their centres coincide,
     which is what puts the icon level with the middle of the card */
  align-items: center;
}

/* ---- the rail: one segment per step, so the fill is per-step ---- */
.hiw__dot {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--paper);
  color: var(--muted);
  transition: background .45s ease, border-color .45s ease, color .45s ease,
              transform .5s cubic-bezier(.2,.8,.2,1);
}
.hiw__dot .site-icon { width: 18px; height: 18px; }

/* the ring that pulses out once as a step lands */
.hiw__dot::after {
  content: "";
  position: absolute;
  inset: -6px;
  border: 1px solid var(--orange);
  border-radius: 999px;
  opacity: 0;
  transform: scale(.72);
}

/* no panel: the step is type on the section surface */
.hiw__card {
  padding: 0;
  transition: transform .7s cubic-bezier(.2,.8,.2,1), opacity .7s ease;
}

.hiw__no {
  margin: 0 0 10px;
  font-size: 12px;
  line-height: 1;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--orange);
}

.hiw__h {
  margin: 0 0 12px;
  font-weight: 400;
  /* stays clearly under the section title (62px) so the hierarchy holds, but
     matches the weight the step carousel titles now carry */
  font-size: clamp(25px, 2.3vw, 37px);
  line-height: 1.14;
  letter-spacing: -0.03em;
  color: var(--ink);
}

.hiw__p {
  margin: 0;
  font-size: clamp(14px, 1.05vw, 16px);
  line-height: 1.6;
  color: var(--muted);
}

.hiw__keys {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: clamp(14px, 1.6vw, 20px);
}
.hiw__keys span {
  padding: 6px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 12px;
  line-height: 1;
  color: var(--muted);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .5s ease, transform .5s cubic-bezier(.2,.8,.2,1);
}

/* ---- resting state, before a step has been reached ---- */
.hiw__step .hiw__card { opacity: .48; transform: translateY(18px); }

/* ---- arrived ---- */
.hiw__step.is-on .hiw__card { opacity: 1; transform: none; }
.hiw__step.is-on .hiw__dot {
  background: var(--orange);
  border-color: var(--orange);
  color: #fff;
  transform: scale(1.06);
}
.hiw__step.is-on .hiw__dot::after { animation: hiw-ring .9s ease-out 1; }
.hiw__step.is-on .hiw__keys span { opacity: 1; transform: none; }
/* the pills arrive one after another rather than all at once */
.hiw__step.is-on .hiw__keys span:nth-child(1) { transition-delay: .10s; }
.hiw__step.is-on .hiw__keys span:nth-child(2) { transition-delay: .18s; }
.hiw__step.is-on .hiw__keys span:nth-child(3) { transition-delay: .26s; }
.hiw__step.is-on .hiw__keys span:nth-child(4) { transition-delay: .34s; }

@keyframes hiw-ring {
  0%   { opacity: .55; transform: scale(.72); }
  100% { opacity: 0;   transform: scale(1.25); }
}

@media (max-width: 900px) {
  .hiw__grid { grid-template-columns: 1fr; gap: clamp(30px, 6vw, 48px); }
  /* a pinned column would take most of a phone screen, so it rejoins the flow */
  .hiw__side { position: static; top: auto; }
  /* the twelve process photos are now composed as native 4:3; widening the box
     here would crop 17% back off them, which is the bug this slot just had */
  .hiw__media img { aspect-ratio: 4 / 3; }
  .hiw__step { grid-template-columns: 38px minmax(0, 1fr); }
  .hiw__dot { width: 38px; height: 38px; }
  .hiw__track { left: 19px; }
}

@media (prefers-reduced-motion: reduce) {
  .hiw__card, .hiw__dot, .hiw__track-fill, .hiw__keys span { transition: none; }
  .hiw__step .hiw__card { opacity: 1; transform: none; }
  .hiw__keys span { opacity: 1; transform: none; }
  .hiw__dot::after { animation: none; }
  .hiw__cta:hover { transform: none; }
}

/* body inherits overflow-x:hidden site-wide (v2-pages.css:27), and a hidden axis
   forces the other one to compute as auto - which makes body a scroll container
   and stops position:sticky resolving against the viewport. clip does the same
   clipping without creating that scroll container. html stays the scroller, which
   is what sticky wants to measure against. */
body.has-sticky-col{overflow-x:clip;overflow-y:visible}

/* ============================================================================
   Blog  -  .blogfilters / .bfeat / .brow / .bpager / .bmeta / .bshare
   ----------------------------------------------------------------------------
   Index: filters in the hero, one spotlight post with the image on the left,
   then rows with the copy on the left and the image on the right. Paging and
   filtering are client-side, so every post stays in the DOM and crawlable.

   Post: title and byline in the hero with the lead image beneath them, a
   contained body, share links, then a drag-only "read next" rail that reuses
   the step carousel so there is one drag implementation on the site.
   ========================================================================== */

/* ---- shared byline ---- */
.bmeta { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 10px;
  font-size: 13px; line-height: 1; color: var(--muted); }
.bmeta__avatar { width: 22px; height: 22px; flex: none; object-fit: cover; }
.bmeta__sep { opacity: .45; }
.bmeta--onDark { color: rgba(255,255,255,.72); }

/* ---- filters in the hero ---- */
.blogfilters { display: flex; flex-wrap: wrap; gap: 8px;
  margin: clamp(30px,3.6vw,52px) 0 0; }
.blogfilter { appearance: none; padding: 9px 18px; border-radius: 999px; cursor: pointer;
  border: 1px solid rgba(255,255,255,.28); background: transparent;
  color: rgba(255,255,255,.82); font: inherit; font-size: 14px; line-height: 1;
  transition: background .18s ease, border-color .18s ease, color .18s ease; }
.blogfilter:hover { border-color: rgba(255,255,255,.6); color: #fff; }
.blogfilter.is-active { background: var(--paper); border-color: var(--paper); color: var(--ink); }
.blogfilter:focus-visible { outline: 2px solid var(--orange); outline-offset: 3px; }

/* ---- spotlight: image left, copy right ---- */
.bfeat { display: grid; grid-template-columns: minmax(0,1.05fr) minmax(0,.95fr);
  gap: clamp(24px,4vw,72px); align-items: center; text-decoration: none; color: inherit;
  padding-bottom: clamp(36px,4.5vw,66px); border-bottom: 1px solid var(--line); }
.bfeat__shot { display: block; overflow: hidden; background: #241310; }
.bfeat__shot img { display: block; width: 100%; height: 100%; aspect-ratio: 16/11;
  object-fit: cover; transition: transform .6s cubic-bezier(.2,.8,.2,1); }
.bfeat:hover .bfeat__shot img { transform: scale(1.03); }
.bfeat__title { margin: 0; font-weight: 400; font-size: clamp(26px,2.7vw,42px);
  line-height: 1.1; letter-spacing: -0.03em; color: var(--ink); }
.bfeat__desc { margin: clamp(12px,1.4vw,18px) 0 clamp(16px,2vw,26px); max-width: 52ch;
  font-size: clamp(15px,1.15vw,17px); line-height: 1.6; color: var(--muted); }

/* ---- rows: copy left, image right ---- */
.brows { display: grid; }
.brow { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,.5fr);
  gap: clamp(24px,4vw,72px); align-items: center; text-decoration: none; color: inherit;
  padding: clamp(30px,3.6vw,52px) 0; border-bottom: 1px solid var(--line); }
.brow__title { margin: 0; font-weight: 400; font-size: clamp(20px,1.9vw,29px);
  line-height: 1.16; letter-spacing: -0.028em; color: var(--ink); }
.brow__desc { margin: clamp(9px,1vw,13px) 0 clamp(13px,1.5vw,19px); max-width: 54ch;
  font-size: clamp(14px,1.05vw,16px); line-height: 1.6; color: var(--muted); }
.brow__shot { display: block; overflow: hidden; background: #241310; }
.brow__shot img { display: block; width: 100%; height: 100%; aspect-ratio: 16/10;
  object-fit: cover; transition: transform .6s cubic-bezier(.2,.8,.2,1); }
.brow:hover .brow__shot img { transform: scale(1.03); }
.brow[hidden], .bfeat[hidden] { display: none; }

/* ---- pager: numbers left, arrows right ---- */
.bpager { display: flex; align-items: center; justify-content: space-between; gap: 16px;
  margin-top: clamp(30px,3.6vw,52px); }
.bpager__nums { display: flex; gap: 8px; }
.bpager__num, .bpager__arrow { display: grid; place-items: center; min-width: 38px; height: 38px;
  padding: 0 12px; border: 1px solid var(--line); border-radius: 999px; background: transparent;
  color: var(--muted); font: inherit; font-size: 14px; cursor: pointer;
  transition: border-color .18s ease, color .18s ease, background .18s ease; }
.bpager__num:hover, .bpager__arrow:not(:disabled):hover { border-color: var(--ink); color: var(--ink); }
.bpager__num.is-active { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.bpager__arrow { width: 44px; padding: 0; }
.bpager__arrows { display: flex; gap: 8px; }
.bpager__arrow:disabled { opacity: .35; cursor: default; }
.bpager__num:focus-visible, .bpager__arrow:focus-visible { outline: 2px solid var(--orange); outline-offset: 3px; }
.bpager__arrow svg { width: 15px; height: 15px; fill: none; stroke: currentColor;
  stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }

/* ---- post page ---- */
.bpost-hero__meta { margin: clamp(16px,2vw,24px) 0 0; }
.bpost-lead { margin: clamp(28px,3.4vw,52px) 0 0; }
.bpost-lead img { display: block; width: 100%; height: auto; aspect-ratio: 16/9; object-fit: cover; }
/* Label above the icons rather than beside them, and the icons filled rather
   than outlined. The divider that used to sit above this row is gone: the author
   bio already draws one, so a second hairline read as an empty box. */
.bshare { display: flex; flex-direction: column; align-items: flex-start; gap: 14px;
  margin: clamp(30px,3.6vw,52px) 0 0; padding-top: 0; border-top: 0; }
.bshare__label { font-size: 13px; color: var(--muted); }
.bshare__links { display: flex; gap: 8px; }
.bshare a { display: grid; place-items: center; width: 38px; height: 38px;
  border: 0; border-radius: 999px; background: var(--ink); color: var(--paper);
  transition: background .18s ease, color .18s ease; }
.bshare a:hover { background: var(--orange); color: #fff; }
.bshare a:focus-visible { outline: 2px solid var(--orange); outline-offset: 3px; }
.bshare .site-icon { width: 16px; height: 16px; }

/* ---- read next: the step carousel with picture cards ---- */
.stepcar--rnext .stepcar__card { padding: 0; border: 0; background: transparent; }
.rnext__shot { display: block; overflow: hidden; background: #241310; margin-bottom: 16px; }
.rnext__shot img { display: block; width: 100%; height: 100%; aspect-ratio: 16/10; object-fit: cover; }
.rnext__title { margin: 0 0 10px; font-weight: 400; font-size: clamp(18px,1.5vw,23px);
  line-height: 1.2; letter-spacing: -0.025em; color: var(--ink); }

@media (max-width: 900px) {
  .bfeat { grid-template-columns: 1fr; gap: clamp(18px,4vw,28px); }
  .brow { grid-template-columns: 1fr; gap: clamp(16px,4vw,24px); }
  /* image first on a phone: the row reads as a card rather than a stranded caption */
  .brow__shot { order: -1; }
  .blogfilter { padding: 12px 18px; min-height: 44px; }
}

/* A post hero is a masthead, not a full screen: title, byline, lead image. The
   stock hero reserves 100vh and centres, which would strand the image far below
   the fold. */
.hero-shell--post .hero__viewport { min-height: 0; padding-top: clamp(120px,14vh,180px);
  padding-bottom: clamp(40px,6vh,90px); }
/* Hero only: title, byline and lead image run the full width of the stage,
   whose content box already matches the header (both sit inside the same
   gutter), and all three align left. The body below keeps its own centred
   reading column - that contrast is deliberate. */
.hero-shell--post .hero__stage { justify-content: flex-start; align-items: stretch; text-align: left; }
.hero-shell--post .hero__title { max-width: none; margin-inline: 0; }
.hero-shell--post .hero__line { text-wrap: balance; }
.hero-shell--post .bmeta { justify-content: flex-start; }
.hero-shell--post .bpost-lead { width: 100%; max-width: none; margin-inline: 0; }
/* no size override here: every hero on the site uses the contact-page scale */

/* ============================================================================
   Resources  -  .resources-page
   ----------------------------------------------------------------------------
   Scoped to the page rather than editing .info-card / .res-card / .article-card,
   which other pages still render with their card treatment. Here the chrome
   comes off: no border, no fill, no radius, no lift on hover. A hairline and the
   type do the separating, which is how the work grid and the blog rows read.
   ========================================================================== */

.resources-page .res-card,
.resources-page .info-card,
.resources-page .article-card,
.resources-page .resource-update-card {
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  overflow: visible;
  min-height: 0;
}
.resources-page a.res-card:hover,
.resources-page a.info-card:hover,
.resources-page a.article-card:hover,
.resources-page a.resource-update-card:hover { transform: none; background: transparent; }

/* the hairline replaces the border it lost */
.resources-page .res-card,
.resources-page .info-card,
.resources-page .resource-update-card {
  padding: clamp(20px, 2.2vw, 28px) 0 0;
  border-top: 1px solid var(--stepcar-line, rgba(17,17,15,.16));
}
.resources-page .res-cards,
.resources-page .card-grid { gap: clamp(30px, 3.4vw, 52px) clamp(26px, 3vw, 48px); }

/* the icon tile was the only other filled box in the row */
.resources-page .res-card__icon {
  width: auto; height: auto; margin-bottom: 16px;
  border-radius: 0; background: none; place-items: start;
}
.resources-page .res-card__icon svg { width: 26px; height: 26px; }

.resources-page .info-card h3,
.resources-page .res-card h3 { transition: color .2s ease; }
.resources-page a.info-card:hover h3,
.resources-page a.res-card:hover h3 { color: var(--orange); }

/* Articles: the image was letterboxed on black by object-fit:contain. Filling
   the frame instead is what makes this read like the rest of the site. */
.resources-page .article-card { gap: 0; }
.resources-page .article-card__image { background: none; }
.resources-page .article-card__image img {
  object-fit: cover;
  transition: transform .7s cubic-bezier(.2,.8,.2,1);
}
.resources-page .article-card:hover .article-card__image img { transform: scale(1.04); }
.resources-page .article-card__body { padding: clamp(18px, 1.9vw, 24px) 0 0; }
.resources-page .article-card h3 { transition: color .2s ease; }
.resources-page .article-card:hover h3 { color: var(--orange); }

/* ---- the two rails ---- */

/* Guides read as a list, not a panel, so the link owns the hairline and the
   stepcar card is only the width. The display has to be restated: a.stepcar__card
   sets block at (0,0,1,1), which outranks .res-howto's own flex at (0,0,1,0). */
/* Icon above the text rather than beside it, which is the treatment the category
   tiles at the top of the page use, and which gives the rail the vertical
   presence an inline row could not - these were 86px tall next to 253px cards. */
.stepcar--howto .stepcar__card.res-howto {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  min-height: clamp(180px, 17vw, 240px);
  padding: clamp(18px, 2vw, 26px) 0 0;
  border: 0;
  border-top: 1px solid var(--stepcar-line);
  border-radius: 0;
  background: transparent;
}
/* the base rule nudges sideways on hover, which fights a rail you are dragging */
.stepcar--howto .res-howto:hover { background: transparent; transform: none; }
.stepcar--howto .res-howto svg { width: 24px; height: 24px; margin-bottom: auto; }
.stepcar--howto .res-howto > span { padding-top: clamp(28px, 3vw, 44px); }
.stepcar--howto .res-howto b {
  font-size: clamp(19px, 1.5vw, 23px);
  line-height: 1.15;
  letter-spacing: -.02em;
  transition: color .2s ease;
}
.stepcar--howto .res-howto span span { margin-top: 10px; font-size: 15px; line-height: 1.45; }
.stepcar--howto .res-howto:hover b { color: var(--orange); }

/* The update card was a two-column date/body grid sized for one wide card. In a
   three-up rail the date becomes a line above the title instead. */
.stepcar--updates .stepcar__card.resource-update-card {
  display: block;
  padding: clamp(18px, 2vw, 26px) 0 0;
  border-top: 1px solid var(--stepcar-line);
}
/* The padding is restated because the site's <=560px rule gives this element
   22px 24px 0 for the old two-column card. In the rail that indented the date
   24px past its own title. */
.stepcar--updates .resource-update-card__date {
  flex-direction: row;
  align-items: baseline;
  justify-content: flex-start;
  gap: 7px;
  align-self: auto;
  padding: 0;
  margin-bottom: 18px;
  border-right: 0;
  color: var(--muted);
  text-align: left;
}
.stepcar--updates .resource-update-card__date span,
.stepcar--updates .resource-update-card__date b { font-size: 15px; }
.stepcar--updates .resource-update-card__date span { font-size: 15px; letter-spacing: 0; }
.stepcar--updates .resource-update-card__date b { margin-top: 0; font-size: 15px; font-weight: 400; }
.stepcar--updates .resource-update-card__body { padding: 0; }
.stepcar--updates .resource-update-card h3 {
  max-width: none;
  font-size: clamp(24px, 2.1vw, 32px);
  transition: color .2s ease;
}
.stepcar--updates .resource-update-card:hover h3 { color: var(--orange); }
.resources-page .resource-updates__head { padding-bottom: clamp(30px, 3.6vw, 56px); }
.resources-page .resource-updates__head .sec-lead { max-width: 52ch; margin-top: 22px; }

/* ---- reveal ----
   The shared observer watches .p2sec; this page uses .section, so v2-pages.js
   adds .resources-page .section to its selector. Guard the hidden state behind
   unconditionally, matching how .p2sec does it a few hundred lines up in v2-pages.css. */
.resources-page .section [data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity .7s ease, transform .7s cubic-bezier(.2,.8,.2,1);
}
.resources-page .section [data-reveal].is-in { opacity: 1; transform: none; }
.resources-page .section [data-reveal]:nth-child(2) { transition-delay: 80ms; }
.resources-page .section [data-reveal]:nth-child(3) { transition-delay: 160ms; }
.resources-page .section [data-reveal]:nth-child(4) { transition-delay: 240ms; }
.resources-page .section [data-reveal]:nth-child(5) { transition-delay: 320ms; }
.resources-page .section [data-reveal]:nth-child(6) { transition-delay: 400ms; }

@media (prefers-reduced-motion: reduce) {
  .resources-page .section [data-reveal] { opacity: 1; transform: none; transition: none; }
  .resources-page .article-card:hover .article-card__image img { transform: none; }
}

@media (max-width: 900px) {
  .resources-page .res-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
  .resources-page .res-cards,
  .resources-page .card-grid { grid-template-columns: minmax(0, 1fr); }
}

/* The three services category pages had no photography at all. The uploads are
   3:4, so this is a contained portrait inset rather than a wide band that would
   have to crop most of the frame away. Sharp edges, like every other image on
   the site. */
.svccat-media { margin: 0; max-width: 460px; }
.svccat-media img {
  display: block; width: 100%; height: auto;
  aspect-ratio: 3 / 4; object-fit: cover; border-radius: 0;
}
@media (max-width: 600px) { .svccat-media { max-width: 100%; } }
