/* ==========================================================================
   SECTIONS — page compositions. Vertical rhythm comes from .section only.
   ========================================================================== */

/* ---- Hero ----------------------------------------------------------------
   Type left, portrait right, on a pale faceted ground. The name is set solid
   rather than ghosted: at this weight it is the loudest thing on the page and
   does not need an effect to carry. */

/* Every value below is derived from the reference mock (2506x1616) and
   expressed in vw, so the composition holds at any width rather than only at
   the size it was drawn. Comments give the measured source figure. */

.hero {
  position: relative;
  overflow: clip;
  isolation: isolate;
  margin-top: calc(-1 * var(--nav-h));
  padding-top: var(--nav-h);
  min-height: min(64.5vw, 940px);       /* the mock's 1616/2506 aspect */
  display: flex;
  align-items: center;
  background: light-dark(#F2EBE1, #1F1B1A);   /* warm sand-white, not the old cool grey */
}

.hero__facets {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  /* The polygons are drawn in currentColor at low opacity, so one value tunes
     the whole facet field — grey on the pale ground, white on the dark one. */
  color: light-dark(#8A7461, #E4C59E);        /* facets tint with the palette */
  opacity: light-dark(1, .30);
}

.hero__inner {
  position: relative;
  z-index: 2;
  width: 100%;
  display: grid;
  align-items: center;
}
@media (min-width: 900px) {
  /* Text occupies the left half. Measured, not guessed: at 46% the column came
     out 541px and "BALMACEDA" broke onto a third line. */
  .hero__inner { grid-template-columns: 50% 50%; }
}

/* The one script flourish on the site. */
.hero__hi {
  font-family: var(--font-script);
  font-size: clamp(2rem, 5.6vw, 4.4rem);
  line-height: 1;
  /* Brick, so the hero carries two palette tones rather than one: the script
     warms up, the name below stays cocoa and holds the weight. */
  color: light-dark(#803D3B, #D9A87A);
  margin-bottom: .12em;
}

.hero__name {
  font-family: var(--font-display);
  font-size: clamp(2.6rem, 6.94vw, 5.85rem);   /* 174px of 2506, capped so the
                                                  longer line still sets on one */
  font-weight: 700;
  font-stretch: 100%;
  line-height: .75;                            /* 130px advance on a 174px face */
  letter-spacing: -.03em;
  text-transform: uppercase;
  /* #3A1011 is sampled from the shirt in the portrait, so the name and the
     figure carry the same colour. 13.6:1 on the hero ground. It flips to bone
     on dark, where that maroon measures 1.1:1 and is unreadable at any weight. */
  color: light-dark(#3A1011, #F3E7D6);
  margin-bottom: .34em;
}

.hero__roles {
  display: flex;
  align-items: center;
  gap: .7em;
  font-family: var(--font-text);
  font-size: clamp(.8125rem, 1.12vw, 1rem);
  font-weight: 700;
  letter-spacing: .085em;
  text-transform: uppercase;
  color: light-dark(#4A4240, #B9AAA3);
  margin-bottom: clamp(1.5rem, 3.4vw, 3rem);
}
/* The mock separates the two roles with a small solid tick, not a bullet. */
.hero__roles i {
  flex: none;
  width: 3px;
  height: 1em;
  background: currentColor;
  opacity: .75;
}

.hero__tagline {
  font-size: clamp(1rem, 1.35vw, 1.2rem);   /* eased down from 1.6vw / 1.45rem */
  line-height: 1.5;
  color: light-dark(#4A4240, #B9AAA3);
  max-width: 22ch;
  margin-bottom: clamp(1.5rem, 3.2vw, 2.75rem);
}

/* The mock uses underlined text links, not buttons. */
.hero__links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .55em 1.1em;
  font-size: clamp(.75rem, .95vw, .875rem);   /* eased down from 1.12vw / 1rem */
  letter-spacing: .045em;
  text-transform: uppercase;
  color: light-dark(#6B605D, #9A8B84);
}
.hero__links a {
  color: inherit;
  /* Padding, not font-size: the type stays at the reduced size you asked for
     while the tap target clears the 24px WCAG 2.5.8 minimum. The negative
     margin keeps the row's visual rhythm unchanged. */
  display: inline-block;
  padding-block: .45rem;
  margin-block: -.45rem;
  text-decoration: underline;
  text-underline-offset: .28em;
  text-decoration-thickness: 1px;
  transition: color var(--t-micro) var(--e-state);
}
.hero__links a:hover { color: light-dark(#803D3B, #D9A87A); }
.hero__links i { font-style: normal; opacity: .65; }

.hero__figure {
  position: relative;
  align-self: stretch;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  /* Reclaims the nav's padding so the figure spans the full hero box and the
     head sits under the transparent nav, as it does in the mock. Without this
     the hero grows by exactly the nav height. */
  margin-top: calc(-1 * var(--nav-h));
}
.hero__portrait {
  display: block;
  width: auto;
  height: min(64.5vw, 940px);      /* matches the hero box, so the figure lands
                                      at the mock's 523x929 at a 1440 viewport */
  max-width: 100%;
  object-fit: contain;
  object-position: bottom;
  /* Nudged below the nav so the links stay legible over the head. The size is
     unchanged from the mock — the surplus crops at the bottom, where the mock
     cuts the figure off regardless. */
  position: relative;
  top: calc(var(--nav-h) + 8px);
}

@media (max-width: 899px) {
  .hero { min-height: 0; align-items: stretch; padding-bottom: 0; }
  .hero__inner { grid-template-columns: 1fr; padding-top: var(--s8); }
  .hero__name { font-size: clamp(2.5rem, 12vw, 4rem); }
  .hero__hi { font-size: clamp(2rem, 10vw, 3.2rem); }
  .hero__tagline { max-width: none; }
  .hero__figure { margin-top: var(--s8); }
  .hero__portrait { height: clamp(300px, 78vw, 460px); max-height: none; }
}

/* The flagship card beside the intro copy. Capped, because at full height it
   strands the text column against a tall column of white. */
#flagship .split-7-5 { align-items: center; }
#flagship .hero__figure {
  max-width: 360px;
  margin-inline: auto;
  height: auto;
}

/* ---- Trusted by ----------------------------------------------------------
   Real client marks on the brand band. This is the one place logos beat type:
   these six are recognisable as shapes, and they are the client's own assets. */

/* Redesigned as an asymmetric band rather than a centred logo strip. Two
   reasons: every other section on the page is centred, so this breaks the
   rhythm and stops the page reading as one long column; and pairing the marks
   with a hard number turns decoration into evidence — six logos say "some
   clients", "six of seventy-nine since 2014" says something checkable. */

.trusted {
  background: var(--brand-deep);
  color: var(--ink-text);
  overflow: hidden;
}
.trusted__inner {
  padding-block: clamp(2.5rem, 5vw, 4rem);
  display: grid;
  gap: clamp(1.75rem, 4vw, 3.5rem);
  align-items: center;
}
@media (min-width: 860px) {
  .trusted__inner { grid-template-columns: auto 1fr; }
}

.trusted__claim { display: flex; flex-direction: column; gap: .35rem; }
.trusted__claim h2 {
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 2.2vw, 1.9rem);
  font-weight: 600;
  letter-spacing: -.025em;
  line-height: 1.1;
  color: var(--ink-text);
}
.trusted__claim span {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-text-dim);
}

/* Hairline-separated cells give the marks a shared baseline and a measured
   rhythm — closer to a colophon than a logo soup. */
.trusted__row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(90px, 1fr));
  align-items: center;
  gap: clamp(1rem, 2vw, 1.75rem);
}
.trusted__row li {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding-inline: clamp(.5rem, 1.4vw, 1.25rem);
  border-left: 1px solid rgba(242, 237, 228, .16);
}
.trusted__row li:first-child { border-left: 0; }
.trusted__row img {
  height: clamp(19px, 2.1vw, 27px);
  width: auto;
  max-width: 100%;
  object-fit: contain;
  /* Flattened to one tone so six unrelated logo palettes read as a set rather
     than six competing brands. */
  filter: brightness(0) invert(1);
  opacity: .82;
  transition: opacity var(--t-micro) var(--e-state);
}
.trusted__row li:hover img { opacity: 1; }

@media (max-width: 620px) {
  .trusted__row { grid-template-columns: repeat(3, 1fr); gap: var(--s4); }
  .trusted__row li { border-left: 0; }
}

/* ---- Tools marquee -------------------------------------------------------
   Two rows drifting in opposite directions. Same track mechanic as the work
   wall (sequence duplicated, translate -50%), without the rotation — these are
   wordmarks and must stay level to be readable. */

.tools { overflow: hidden; }
.tools__rows { display: grid; gap: clamp(1rem, 2vw, 1.5rem); margin-top: clamp(2rem, 4vw, 3rem); }

.tools__row {
  display: flex;
  overflow: hidden;
  mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
}
.tools__track {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  will-change: transform;
}
.tools__track--left  { animation: mq-left  var(--speed) linear infinite; }
.tools__track--right { animation: mq-right var(--speed) linear infinite; }
.tools__row:hover .tools__track { animation-play-state: paused; }

.tools__track li {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: clamp(150px, 15vw, 200px);
  height: clamp(64px, 7vw, 88px);
  padding-inline: var(--s6);
}
.tools__track img {
  max-height: clamp(26px, 2.6vw, 34px);
  max-width: 100%;
  width: auto;
  object-fit: contain;
  /* brightness(0), not grayscale(1). Grayscale keeps each logo's own luminance,
     so a pale mark like Shopify all but vanishes next to a black one like
     Elementor. Flattening to a single tone and letting opacity set the weight
     makes twenty unrelated marks read as one set. True colour on hover. */
  filter: brightness(0);
  opacity: .45;
  transition: filter var(--t-state) var(--e-state), opacity var(--t-state) var(--e-state);
}
/* filter has no light-dark() equivalent, so the dark case needs both the
   explicit toggle and the OS-preference fallback. */
[data-theme="dark"] .tools__track img { filter: brightness(0) invert(1); opacity: .55; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .tools__track img { filter: brightness(0) invert(1); opacity: .55; }
}
.tools__track li:hover img,
[data-theme="dark"] .tools__track li:hover img { filter: none; opacity: 1; }

.tools__note {
  margin-top: clamp(1.5rem, 3vw, 2.5rem);
  text-align: center;
  font-size: var(--fs-lede);
  color: var(--text-muted);
}

@media (prefers-reduced-motion: reduce) {
  .tools__row { overflow-x: auto; mask-image: none; -webkit-mask-image: none; }
  .tools__track { animation: none; }
}

/* ---- Diagonal marquee wall ------------------------------------------------
   Vanilla port of the React DiagonalMarqueeCarousel. Five rows on a -25deg
   rotation, alternating direction, staggered speeds, hover to pause. Pure CSS
   animation — the original needed React only to template the rows. */

.mq-wall {
  position: relative;
  width: 100%;
  height: min(88vh, 820px);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-tint);
}

.mq-wall__rows {
  position: absolute;
  z-index: 0;
  display: flex;
  flex-direction: column;
  gap: 2rem;
  width: 220vw;                 /* over-wide so the rotation never exposes an end */
  transform: rotate(-25deg);
}

.mq-row { display: flex; width: 100%; overflow: hidden; }

.mq-track {
  display: flex;
  flex-shrink: 0;
  will-change: transform;
}
/* The track holds the sequence twice, so translating exactly -50% lands on an
   identical frame and the loop is seamless. */
.mq-track--left  { animation: mq-left  var(--speed) linear infinite; }
.mq-track--right { animation: mq-right var(--speed) linear infinite; }

@keyframes mq-left {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}
@keyframes mq-right {
  from { transform: translate3d(-50%, 0, 0); }
  to   { transform: translate3d(0, 0, 0); }
}

/* Pause the row you are pointing at, so a card can actually be clicked. */
.mq-row:hover .mq-track,
.mq-row:focus-within .mq-track { animation-play-state: paused; }

.mq-card {
  position: relative;
  flex: none;
  width: 400px;
  height: 300px;
  margin-right: 2rem;
  overflow: hidden;
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, .45);
  text-decoration: none;
  transition: transform var(--t-state) var(--e-state),
              box-shadow var(--t-state) var(--e-state);
}
.mq-card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Top, not centre: these are tall boards, and the hero area is the part worth
     showing. object-fit alone would crop to the middle of a 2,000px board. */
  object-position: top;
}
/* The component ships bg-black/40, which suits the decorative stock photos it
   was demoed with. At 40% it buries actual portfolio work, so this sits at 22%
   for cohesion and clears almost entirely on hover. */
.mq-card::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, .22);
  transition: background var(--t-state) var(--e-state);
}
.mq-card:hover, .mq-card:focus-visible {
  transform: scale(1.03);
  box-shadow: 0 30px 60px -12px rgba(0, 0, 0, .6);
}
.mq-card:hover::after, .mq-card:focus-visible::after { background: rgba(0, 0, 0, .04); }

.mq-card__label {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 2;
  padding: 2.5rem .9rem .8rem;
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: #F3E7D6;
  background: linear-gradient(180deg, transparent, rgba(0, 0, 0, .7));
  opacity: 0;
  transition: opacity var(--t-state) var(--e-state);
}
.mq-card:hover .mq-card__label,
.mq-card:focus-visible .mq-card__label { opacity: 1; }

/* Edge fades, in the section's own ground colour. */
.mq-wall__fade {
  pointer-events: none;
  position: absolute;
  inset-inline: 0;
  z-index: 10;
  height: 25%;
}
.mq-wall__fade--top    { top: 0;    background: linear-gradient(180deg, var(--bg-tint), transparent); }
.mq-wall__fade--bottom { bottom: 0; background: linear-gradient(0deg,   var(--bg-tint), transparent); }

@media (max-width: 700px) {
  .mq-wall { height: min(70vh, 520px); }
  .mq-card { width: 260px; height: 195px; margin-right: 1rem; }
  .mq-wall__rows { gap: 1rem; width: 300vw; }
}

/* Reduced motion: stop the animation and un-rotate, then let each row scroll
   by hand. Freezing the rows in place would strand most cards off-screen. */
@media (prefers-reduced-motion: reduce) {
  .mq-wall { height: auto; padding-block: var(--s8); }
  .mq-wall__rows {
    position: static;
    transform: none;
    width: 100%;
    gap: var(--s4);
  }
  .mq-row { overflow-x: auto; scrollbar-width: thin; }
  .mq-track { animation: none; }
  .mq-wall__fade { display: none; }
}

/* ---- Video facade --------------------------------------------------------
   The <video> keeps preload="none" and its native controls, so with JS off it
   is an ordinary working player. JS reveals the poster overlay on top — which
   is why the button ships `hidden` rather than being added at runtime. */

.vfacade {
  position: relative;
  border-radius: var(--r-md);
  overflow: hidden;
  background: #000;
  aspect-ratio: 16 / 9;
  box-shadow: var(--shadow-lg);
}
.vfacade video { width: 100%; height: 100%; object-fit: cover; display: block; }

.vfacade__cover {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}
.vfacade__cover img {
  position: absolute;
  inset: 0;
  z-index: 0;              /* below the scrim */
  width: 100%; height: 100%;
  object-fit: cover;
}
/* z-index matters here: ::before is generated as the FIRST child, so without
   it the real <img> that follows paints straight over the scrim and the caption
   ends up sitting on the raw poster. */
.vfacade__cover::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  /* Weighted hard to the bottom: the caption sits there and the poster frame
     happens to be bright behind it, so a flat scrim left the name unreadable. */
  background: linear-gradient(180deg,
    rgba(0, 0, 0, .10) 0%, rgba(0, 0, 0, .18) 40%,
    rgba(50, 44, 43, .62) 72%, rgba(50, 44, 43, .90) 100%);
  transition: opacity var(--t-state) var(--e-state);
}
.vfacade__cover:hover::before { opacity: .85; }

.vfacade__play {
  position: relative;
  z-index: 2;
  display: grid;
  place-items: center;
  width: clamp(64px, 7vw, 88px);
  height: clamp(64px, 7vw, 88px);
  border-radius: var(--r-pill);
  background: rgba(242, 237, 228, .94);
  color: var(--brand-deep);
  transition: transform var(--t-state) var(--e-enter), background var(--t-state) var(--e-state);
}
.vfacade__cover:hover .vfacade__play { transform: scale(1.08); background: #FFF; }
.vfacade__play svg { width: 34%; height: 34%; fill: currentColor; margin-left: 8%; }

.vfacade__caption {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 2;
  padding: clamp(1.25rem, 3vw, 2rem);
  text-align: left;
  color: #F3E7D6;
}
.vfacade__caption strong {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 1.7vw, 1.4rem);
  font-weight: 600;
  letter-spacing: -.02em;
}
.vfacade__caption span {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: .06em;
  text-transform: uppercase;
  opacity: .8;
}

/* ---- Scroll timeline -----------------------------------------------------
   Vanilla port of the Framer Motion Timeline. The library was only being used
   to map scroll progress onto one element's height, which is a CSS custom
   property here — so this ships no runtime and keeps working without JS, where
   the rail simply stays unfilled and the entries read as a plain chronology. */

.tline {
  position: relative;
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: var(--page-pad);
  padding-bottom: var(--s16);
}

.tline__item {
  display: flex;
  justify-content: flex-start;
  padding-top: clamp(2.5rem, 6.5vw, 6.5rem);   /* eased down from 3/9vw/9 */
}
@media (min-width: 800px) { .tline__item { gap: var(--s12); } }

/* The year sticks while its entry scrolls past — the device that makes the
   whole thing read as a timeline rather than a stack of cards. */
.tline__side {
  position: sticky;
  top: calc(var(--nav-h) + 3rem);
  z-index: 4;
  align-self: flex-start;
  display: flex;
  align-items: center;
  flex: none;
  width: max-content;
}
@media (min-width: 800px) { .tline__side { width: 22rem; } }

.tline__dot {
  position: absolute;
  left: 0;
  display: grid;
  place-items: center;
  width: 40px; height: 40px;
  border-radius: var(--r-pill);
  background: var(--bg-base);
}
.tline__dot::after {
  content: "";
  width: 14px; height: 14px;
  border-radius: var(--r-pill);
  background: var(--bg-tint);
  border: 1px solid var(--border-strong);
  transition: background var(--t-state) var(--e-state), border-color var(--t-state) var(--e-state);
}
/* Marks every entry the rail has already reached. */
.tline__item.is-passed .tline__dot::after {
  background: var(--accent);
  border-color: var(--accent);
}

.tline__year {
  font-family: var(--font-display);
  font-size: clamp(2rem, 4.4vw, 3.4rem);
  font-weight: 700;
  letter-spacing: -.035em;
  line-height: 1;
  color: var(--text-muted);
  transition: color var(--t-state) var(--e-state);
}
.tline__item.is-passed .tline__year { color: var(--text-primary); }
/* Desktop shows the sticky year; mobile shows one inline in the body instead. */
.tline__year:not(.tline__year--m) { display: none; }
.tline__year--m { margin-bottom: var(--s4); }
@media (min-width: 800px) {
  .tline__side .tline__year { display: block; padding-left: 5rem; }
  .tline__year--m { display: none; }
}

.tline__body {
  position: relative;
  width: 100%;
  padding-left: 5rem;
  padding-right: var(--s2);
}
@media (min-width: 800px) { .tline__body { padding-left: var(--s4); } }
.tline__body > p { color: var(--text-secondary); max-width: 58ch; }
.tline__body > p + * { margin-top: var(--s6); }

.tline__list { list-style: none; padding: 0; margin-top: var(--s4); display: grid; gap: var(--s2); }
.tline__list li {
  display: flex;
  gap: .6rem;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}
.tline__list li::before { content: "\2713"; color: var(--accent); flex: none; }

.tline__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s4);
  margin-top: var(--s8);
}
.tline__grid img {
  width: 100%;
  height: clamp(90px, 22vw, 240px);
  object-fit: cover;
  object-position: top;      /* these are tall boards; the hero area is the part worth showing */
  border-radius: var(--r-md);
  border: 1px solid var(--border-hairline);
  box-shadow: var(--shadow-md);
}

/* The rail. A static track with a fill whose height JS drives from scroll
   progress; the mask fades both ends so it never terminates abruptly. */
.tline__rail {
  position: absolute;
  left: calc(var(--page-pad) + 20px);
  top: 0;
  bottom: var(--s16);
  width: 2px;
  overflow: hidden;
  background: linear-gradient(to bottom, transparent, var(--border-default) 12%,
                              var(--border-default) 88%, transparent);
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 10%, #000 90%, transparent);
  mask-image: linear-gradient(to bottom, transparent, #000 10%, #000 90%, transparent);
}
/* --fill is registered so it can be animated: an unregistered custom property
   is just a token to the engine and cannot interpolate. */
@property --fill {
  syntax: "<number>";
  initial-value: 0;
  inherits: false;
}

.tline__fill {
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 100%;
  transform-origin: top;
  transform: scaleY(var(--fill));   /* scale, not height — stays on the compositor */
  border-radius: var(--r-pill);
  /* The original ran purple -> blue; this is the palette's own brick -> camel. */
  background: linear-gradient(to top, var(--brick) 0%, var(--camel) 45%, transparent 100%);
}

/* Preferred path: a scroll-driven animation. No listener, no rAF, no main-thread
   work at all — the browser ties the fill directly to scroll position. The JS
   below only registers itself when this is unsupported. */
@supports (animation-timeline: view()) {
  .tline { view-timeline-name: --tline-view; }
  .tline__fill {
    animation: tline-fill linear both;
    animation-timeline: --tline-view;
    animation-range: entry 90% exit 45%;
  }
  @keyframes tline-fill { from { --fill: 0; } to { --fill: 1; } }

  /* Each marker lights as its own entry reaches the viewport, which removes the
     need for JS to track which dots the fill has passed. */
  .tline__dot::after,
  .tline__side .tline__year {
    animation: tline-on linear both;
    animation-timeline: view();
    animation-range: entry 55% cover 25%;
  }
  @keyframes tline-on {
    to { background: var(--accent); border-color: var(--accent); }
  }
  .tline__side .tline__year { animation-name: tline-year-on; }
  @keyframes tline-year-on { to { color: var(--text-primary); } }
}

@media (prefers-reduced-motion: reduce) {
  .tline__side { position: static; }
}

/* ---- Testimonials -------------------------------------------------------- */

.testi-grid {
  display: grid;
  gap: var(--s6);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
  margin-top: clamp(2.5rem, 5vw, 4rem);
}
.testi {
  background: var(--bg-card);
  border: 1px solid var(--border-hairline);
  border-radius: var(--r-md);
  padding: clamp(1.5rem, 3vw, 2rem);
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-sm), var(--inset-top);
}
.testi blockquote { margin: 0 0 var(--s6); color: var(--text-secondary); flex: 1; }
.testi blockquote p { margin: 0; }
.testi__mark {
  display: block;
  font-family: var(--font-display);
  font-size: 2.5rem;
  line-height: .6;
  color: var(--accent);
  opacity: .35;
  margin-bottom: var(--s3);
}
.testi__who { border-top: 1px solid var(--border-hairline); padding-top: var(--s4); }
.testi__who strong { display: block; font-size: var(--fs-body); }
.testi__who span { font-size: var(--fs-sm); color: var(--text-muted); }

/* ---- Stat row ------------------------------------------------------------ */

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 160px), 1fr));
  gap: var(--s8) var(--s6);
  padding-block: var(--s12);
  border-top: 1px solid var(--border-hairline);
}
.stat__n {
  display: block;
  font-family: var(--font-display);
  font-size: var(--fs-m);
  font-weight: 600;
  font-stretch: 108%;
  letter-spacing: -.02em;
  color: var(--text-primary);
}
.stat__l {
  display: block;
  margin-top: var(--s1);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/* ---- Name wall -----------------------------------------------------------
   ~100 client names as typography, not logos. Crawlable, weightless, and it
   needs no logo-usage rights. The set with a gallery piece is a live index. */

.namewall {
  font-family: var(--font-text);
  font-size: clamp(1.0625rem, 1.9vw, 1.5rem);
  line-height: 1.5;
  color: var(--text-secondary);
  letter-spacing: -.015em;
  text-wrap: pretty;
}
.namewall a {
  color: var(--text-primary);
  font-weight: 500;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color var(--t-micro) var(--e-state), color var(--t-micro) var(--e-state);
}
.namewall a:hover { border-bottom-color: var(--accent); color: var(--accent-text); }
.namewall i {
  font-style: normal;
  color: var(--accent);
  padding-inline: .3em;
  opacity: .55;
}

/* ---- Services ------------------------------------------------------------ */

.services {
  display: grid;
  gap: var(--s6);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
}
/* Two columns, not three: four project offers pair evenly, and the retainer
   below them gets the full width it needs to read as a different kind of thing. */
@media (min-width: 780px) {
  .services { grid-template-columns: 1fr 1fr; }
}
/* The retainer is different in kind from the four project offers, so it looks
   different rather than merely sitting last. */
.services > .card--wide { grid-column: 1 / -1; }
@media (min-width: 720px) {
  .services > .card--wide { display: grid; grid-template-columns: 1fr 1fr;
                            gap: var(--s8); align-items: start; }
  .services > .card--wide .card__tools { margin-top: 0; border-top: 0;
                                         padding-top: 0; }
}

/* ---- Stack spec sheet ----------------------------------------------------
   Not a logo grid: unlicensed, slow, and it reads as a WordPress plugin.
   A typeset spec sheet says "I know these" more credibly than borrowed marks. */

.stack__group { padding-block: var(--s6); border-top: 1px solid var(--border-hairline); }
.stack__group:first-child { border-top: 0; }
.stack__group h3 {
  font-family: var(--font-text);
  font-size: var(--fs-sm);
  font-style: italic;
  font-weight: 400;
  color: var(--text-muted);
  letter-spacing: 0;
  margin-bottom: var(--s4);
}
.stack__list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 190px), 1fr));
  gap: var(--s2) var(--s6);
  padding: 0;
  list-style: none;
}
.stack__list li {
  font-family: var(--font-mono);
  font-size: var(--fs-data);
  letter-spacing: .04em;
  color: var(--text-secondary);
  transition: color var(--t-micro) var(--e-state);
}
.stack__list li:hover { color: var(--text-primary); }

/* ---- Testimonial --------------------------------------------------------- */

.quote {
  font-family: var(--font-display);
  font-size: var(--fs-m);
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: -.02em;
  text-wrap: balance;
}
.quote__attr {
  margin-top: var(--s6);
  padding-top: var(--s4);
  border-top: 1px solid var(--border-hairline);
}
.quote__attr strong { display: block; font-family: var(--font-text); font-size: var(--fs-body); }

.video {
  position: relative;
  border-radius: var(--r-md);
  overflow: hidden;
  border: 1px solid var(--border-hairline);
  background: var(--bg-inset);
}
.video video { width: 100%; height: auto; display: block; }

/* ---- FAQ ---------------------------------------------------------------- */

.faq { border-top: 1px solid var(--border-hairline); }
.faq details { border-bottom: 1px solid var(--border-hairline); }
.faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  padding-block: var(--s6);
  font-family: var(--font-text);
  font-size: var(--fs-title);
  font-weight: 600;
  letter-spacing: -.01em;
  cursor: pointer;
  list-style: none;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: "";
  flex: none;
  width: 11px; height: 11px;
  border-right: 1.5px solid var(--accent);
  border-bottom: 1.5px solid var(--accent);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform var(--t-state) var(--e-state);
}
.faq details[open] summary::after { transform: rotate(225deg) translate(-3px, -3px); }
.faq details > p { padding-bottom: var(--s6); color: var(--text-secondary); max-width: var(--measure); }

/* ---- Inquiry form -------------------------------------------------------
   A left rail that fills as required fields complete: the momentum of a wizard
   without paginating a form this audience wants to see all of at once. */

.inquiry { position: relative; }
@media (min-width: 720px) {
  .inquiry { padding-left: var(--s8); }
  .inquiry::before {
    content: "";
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 2px;
    background: var(--border-hairline);
  }
  .inquiry::after {
    content: "";
    position: absolute;
    left: 0; top: 0;
    width: 2px;
    height: var(--filled, 0%);
    background: var(--accent);
    transition: height var(--t-state) var(--e-state);
  }
}

.fieldset { border: 0; padding: 0; margin: 0 0 var(--s12); }
.fieldset > legend {
  display: flex;
  align-items: baseline;
  gap: .55rem;
  padding: 0;
  margin-bottom: var(--s6);
  font-size: var(--fs-sm);
  font-style: italic;
  color: var(--text-muted);
}
.fieldset > legend b {
  font-family: var(--font-mono);
  font-style: normal;
  font-size: var(--fs-micro);
  font-weight: 400;
  color: var(--accent-text);
}

.form__nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s4);
  padding-top: var(--s6);
  border-top: 1px solid var(--border-hairline);
}
.form__count { font-family: var(--font-mono); font-size: var(--fs-data);
               letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted);
               margin-left: auto; }
.form__note  { font-size: var(--fs-sm); color: var(--text-muted); }
.form__error {
  margin-top: var(--s4);
  padding: var(--s3) var(--s4);
  border-left: 2px solid var(--state-error);
  background: var(--bg-inset);
  color: var(--state-error);
  font-size: var(--fs-sm);
}

/* ---- Stepped form --------------------------------------------------------
   Only paginated once JS confirms it can validate a step before advancing.
   Without JS every fieldset stays visible and the form submits in one go — the
   Netlify POST is identical either way, so no field can be lost. */

.steps {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s6);
  margin-bottom: var(--s8);
  padding: 0;
  list-style: none;
}
.steps__item {
  display: flex;
  align-items: center;
  gap: .55rem;
  font-size: var(--fs-sm);
  color: var(--text-muted);
  transition: color var(--t-state) var(--e-state);
}
.steps__item span {
  display: grid;
  place-items: center;
  width: 26px; height: 26px;
  border: 1px solid var(--border-default);
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  transition: background var(--t-state) var(--e-state),
              border-color var(--t-state) var(--e-state),
              color var(--t-state) var(--e-state);
}
.steps__item.is-current { color: var(--text-primary); font-weight: 600; }
.steps__item.is-current span {
  background: var(--brand-deep);
  border-color: var(--brand-deep);
  color: #FFF;
}
.steps__item.is-done span {
  background: var(--accent-wash);
  border-color: var(--accent);
  color: var(--accent-text);
}

/* Applied by JS only. The selector is scoped to .is-stepped so the no-JS path
   never hides a fieldset it cannot bring back. */
.is-stepped .step { display: none; }
.is-stepped .step.is-active { display: block; }
.is-stepped .steps { display: flex; }

/* Centred variant: the form column sits under the section title rather than
   beside it, so the whole block reads as one centred unit. */
.inquiry--mid {
  max-width: 44rem;
  margin-inline: auto;
  text-align: left;
}
@media (min-width: 720px) {
  .inquiry--mid { padding-left: var(--s8); }
}

/* ---- Footer --------------------------------------------------------------
   Stripped to two rows. The marquee, the glow and the three-column link grid
   all went: by this point the page has already made its case, and a loud
   footer competes with the inquiry form immediately above it. Bookends the
   Trusted By band by sitting on the same brand maroon. */

.footer {
  background: var(--brand-deep);
  color: var(--ink-text);
}
.footer__inner { padding-block: clamp(2.5rem, 5vw, 3.5rem); }

.footer__top {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4) var(--s8);
}
.footer__mark {
  font-family: var(--font-display);
  font-size: var(--fs-title);
  font-weight: 600;
  letter-spacing: -.02em;
  text-decoration: none;
  color: var(--ink-text);
}
.footer__nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3) var(--s6);
  list-style: none;
  padding: 0;
  margin: 0;
  font-size: var(--fs-sm);
}
.footer a { color: var(--ink-text-dim); text-decoration: none;
            transition: color var(--t-micro) var(--e-state); }
/* Same treatment for the footer list: 19px was under the minimum. */
.footer__nav a { display: inline-block; padding-block: .4rem; margin-block: -.4rem; }
.footer a:hover { color: var(--ink-text); }

.footer__base {
  text-align: center;
  margin-top: clamp(1.75rem, 3.5vw, 2.5rem);
  padding-top: var(--s6);
  border-top: 1px solid rgba(242, 237, 228, .14);
  color: var(--ink-text-dim);
}
.footer__base a { color: var(--ink-text); }

