/* ============================================================
   Business Ocean — sections (hero, services, work, about dive…)
   ============================================================ */

/* -- hero (home) --------------------------------------------- */
.hero {
  padding-block-start: calc(var(--header-h) + clamp(2.5rem, 7vh, 5.5rem));
  padding-block-end: var(--space-block);
}

.hero__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  margin-block-end: var(--space-5);
}

.hero h1 {
  font-size: var(--size-6);
  max-width: 16ch;
}

/* the gradient half takes its own line: `ch` is measured on "0" and expanded
   caps run much wider, so leaving the break to the text width put "on" down
   with "every" and cost a third line */
.hero h1 em {
  font-style: normal;
  display: block;
  width: fit-content;
  background: var(--grad-metric);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.hero__lead {
  margin-block-start: var(--space-5);
  font-size: var(--size-1);
  color: var(--ink-70);
  max-width: 46ch;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-block-start: var(--space-6);
}

.hero__media {
  position: relative;
  margin-block-start: clamp(2.5rem, 6vh, 4.5rem);
}

.hero__media .frame { aspect-ratio: 16 / 10; }

@media (max-width: 639px) {
  .hero__media .frame { aspect-ratio: 4 / 5; }
}

/* floating metric chips over the hero photo */
.float-chip {
  position: absolute;
  display: flex;
  flex-direction: column;
  gap: 0.1em;
  /* Absolutely positioned from the right, the chip was shrink-to-fit against
     whatever space was left from its static position — on a 360px screen that
     squeezed it until "24h" and "manažér" broke in half. Size it to its content
     and cap it instead. */
  width: max-content;
  max-width: min(16rem, 78%);
  padding: 0.9em 1.2em;
  background: var(--paper-glass);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-radius: var(--radius-1);
  box-shadow: var(--shadow-2), inset 0 0 0 1px rgba(255, 255, 255, 0.5);
}

.float-chip b {
  font-family: var(--font-display);
  font-stretch: 116%;
  font-size: var(--size-2);
  line-height: 1;
  background: var(--grad-metric);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.float-chip span {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-70);
}

/* .float-chip--a/--b lived here until 11.08.2026: two chips pinned mid-photo
   over the hero image. They repeated the 3× and 97% standing right below them
   and read as stickers. Replaced by .hero__band. The base .float-chip is still
   used by the SEO page's hero collage. */

/* facts along the photo's top edge — deliberately the ones the proof strip
   below does NOT carry, so the two blocks stop saying the same thing twice.
   Top edge, not bottom: the bottom of the frame is where the faces are. */
.hero__media .hero__band {
  position: absolute;
  inset-inline: 0;
  top: 0;
  z-index: 1;
  list-style: none;
  display: grid;
  grid-auto-flow: column;
  /* auto columns spread by space-between, not three equal thirds: at 1fr the
     cells started at arbitrary points across a 1200px photo and the row read
     as unaligned. Now the outer two sit on the frame's own padding edges. */
  grid-auto-columns: auto;
  justify-content: space-between;
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: center;
  padding: 1.15rem clamp(1.5rem, 3vw, 2.5rem);
  background: var(--paper-glass);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border-start-start-radius: var(--radius-2);
  border-start-end-radius: var(--radius-2);
  box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.55);
}

.hero__band li {
  display: grid;
  gap: 0.2em;
  min-width: 0;
  position: relative;
}

/* hairline between cells so three facts read as one row, not three strays */
.hero__band li + li::before {
  content: "";
  position: absolute;
  inset-block: -0.2em;
  inset-inline-start: calc(clamp(1.5rem, 4vw, 4rem) / -2);
  border-inline-start: 1px solid var(--line);
}

.hero__band b {
  font-family: var(--font-display);
  font-stretch: 116%;
  font-weight: 800;
  font-size: var(--size-2);
  line-height: 1.1;
  color: var(--ink);
}

.hero__band span {
  font-family: var(--font-mono);
  font-size: 0.74rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-70);
}

/* hero stats row */
.statline {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-6) var(--space-5);
  padding-block: var(--space-6);
  border-block: 1px solid var(--line);
  margin-block-start: var(--space-block);
}

@media (min-width: 768px) {
  .statline { grid-template-columns: repeat(4, 1fr); }
}

/* -- services index (numbered rows) ---------------------------- */
.svc-index { border-block-start: 1px solid var(--line); }

.svc-row {
  display: grid;
  /* title | arrow — the leading numeral column went away with the numerals */
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--space-4) var(--space-6);
  padding-block: clamp(1.4rem, 3vh, 2.2rem);
  border-block-end: 1px solid var(--line);
  position: relative;
  transition: padding-inline-start var(--dur-2) var(--ease-out);
}

.svc-row__title {
  font-family: var(--font-display);
  font-stretch: 114%;
  font-weight: 800;
  text-transform: uppercase;
  font-size: var(--size-3);
  line-height: 1.05;
  letter-spacing: var(--track-tight);
  transition: color var(--dur-1) var(--ease-out);
}

.svc-row__meta {
  /* full width, not column 2: with the numeral gone column 2 is the arrow,
     and the chips would render in a ~30px strip under it */
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  max-height: 0;
  opacity: 0;
  overflow: clip;
  transition: max-height var(--dur-3) var(--ease-out), opacity var(--dur-3) var(--ease-out), margin var(--dur-3) var(--ease-out);
}

.svc-row .icon--go {
  width: 1.6em;
  height: 1.6em;
  color: var(--ink-30);
  transition: color var(--dur-1) var(--ease-out), transform var(--dur-2) var(--ease-spring);
}

.svc-row:hover { padding-inline-start: var(--space-3); }
.svc-row:hover .svc-row__title { color: var(--ocean); }
.svc-row:hover .icon--go { color: var(--ocean); transform: translate(4px, -4px); }

.svc-row:hover .svc-row__meta,
.svc-row:focus-within .svc-row__meta {
  max-height: 5rem;
  opacity: 1;
  margin-block-start: var(--space-2);
}

/* capability, not just width: a 900px touch screen has no hover either, and
   the chips were sealed shut there */
@media (max-width: 639px), (hover: none) {
  .svc-row__meta { max-height: none; opacity: 1; margin-block-start: var(--space-2); }
}

/* The boat's lane beside the services list. It lives in the page margin, so it
   only appears where the margin is actually wide enough — below this the boat
   keeps its single drifting waypoint (see boat.js railMin). */
@media (min-width: 1280px) {
  .svc-index { position: relative; }

  .svc-index::before {
    content: "";
    position: absolute;
    left: -2.5rem;
    top: 0.75rem;
    bottom: 0.75rem;
    border-inline-start: 2px dashed var(--line-strong);
  }

  .svc-row::before {
    content: "";
    position: absolute;
    left: calc(-2.5rem - 5px);
    top: 50%;
    translate: 0 -50%;
    width: 11px;
    height: 11px;
    border-radius: 50%;
    background: var(--paper);
    box-shadow: inset 0 0 0 2px var(--ink-30);
    transition: background-color var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out);
  }

  .svc-row.is-near::before {
    background: var(--ocean);
    box-shadow: 0 0 0 4px rgba(27, 102, 201, 0.16);
  }
}

/* -- selected work (horizontal lente) ---------------------------- */
.work-lente {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: min(86vw, 30rem);
  gap: var(--space-5);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  /* proximity, never mandatory: mandatory re-centres the nearest card the
     moment the pointer is released, which reads as the lente fighting back */
  scroll-snap-type: x proximity;
  padding-inline: var(--bleed-inset);
  /* keep snap positions inside the padding, otherwise the first card snaps
     flush to the screen edge and loses its gutter */
  scroll-padding-inline: var(--bleed-inset);
  padding-block: var(--space-2) var(--space-5);
  scrollbar-width: none;
  cursor: grab;
}

.work-lente::-webkit-scrollbar { display: none; }

.work-lente.is-dragging { cursor: grabbing; scroll-snap-type: none; user-select: none; }

.work-lente > * { scroll-snap-align: start; }

/* mouse: 1:1 with the cursor, no settling at all — the drag IS the control */
@media (hover: hover) and (pointer: fine) {
  .work-lente { scroll-snap-type: none; }
}

.case-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: clamp(1.5rem, 3vw, 2.25rem);
  min-height: 24rem;
}

.case-card__tags { display: flex; flex-wrap: wrap; gap: 0.5rem; }

.case-card__client {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--font-display);
  font-stretch: 114%;
  font-weight: 800;
  text-transform: uppercase;
  font-size: var(--size-2);
  line-height: 1.05;
  letter-spacing: var(--track-tight);
}

/* Slot for the client's own logo. Holds a monogram until the files and the
   permission to show them arrive — naming a client is one thing, putting
   their trademark on our marketing page is another. Swap the span for an
   <img> and nothing else moves. */
.case-card__logo {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.15rem;
  height: 2.15rem;
  border-radius: var(--radius-1);
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--line);
  font-family: var(--font-display);
  font-stretch: 100%;
  font-size: 0.8rem;
  letter-spacing: 0;
  color: var(--ink-50);
}

.case-card__metrics {
  display: grid;
  grid-template-columns: repeat(3, auto);
  justify-content: start;
  gap: var(--space-5) var(--space-6);
  margin-block-start: auto;
}

.case-card__metrics .metric__value { font-size: var(--size-3); }

.case-card__note { color: var(--ink-70); font-size: var(--size--1); max-width: 40ch; }

.case-card--all {
  align-items: flex-start;
  justify-content: space-between;
  background: var(--ink);
  color: #fff;
  box-shadow: none;
}

.case-card--all .case-card__client { color: #fff; }
.case-card--all p { color: rgba(255, 255, 255, 0.7); }
.case-card--all .action { color: #fff; }
.case-card--all:hover .action { color: var(--tide); }

/* -- the crew: a bento of named portraits ----------------------------- */
/* Not a rail. Every cell is the same 4:5 the photos are actually cropped to —
   the CEO tile used to take a double-width cell, which made it a 1.7:1
   letterbox, and a portrait cut that flat shows a forehead and a wall. The
   placeholders share the same cell size so the three missing shots read as
   people we haven't photographed yet, not as holes in the composition. */
.crew-bento {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(2, 1fr);
  grid-auto-rows: auto;
}

@media (min-width: 1024px) {
  .crew-bento {
    grid-template-columns: repeat(4, 1fr);
    grid-template-areas:
      "anna artem daria ak"
      "alx  pol   meet  meet";
  }
  .crew-bento > :nth-child(1) { grid-area: anna; }
  .crew-bento > :nth-child(2) { grid-area: artem; }
  .crew-bento > :nth-child(3) { grid-area: daria; }
  .crew-bento > :nth-child(4) { grid-area: ak; }
  .crew-bento > :nth-child(5) { grid-area: alx; }
  .crew-bento > :nth-child(6) { grid-area: pol; }
  .crew-bento > :nth-child(7) { grid-area: meet; }
}

.crew-tile {
  position: relative;
  display: block;
  /* the shot portraits are built at 4:5 by tools/build_images.py — matching
     the cell to the source is what keeps the crop off people's faces */
  aspect-ratio: 4 / 5;
  border-radius: var(--radius-2);
  overflow: clip;
  background: var(--mist);
  /* isolate so the media never paints over the rounded corner on Safari */
  isolation: isolate;
  transition: translate var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out);
}

/* the way through to the full team is a text tile, not a portrait */
.crew-tile--cta { aspect-ratio: auto; }

/* wide at every width below the 4-column grid: at 900 it sat half width and
   left the bento ending on an empty cell */
@media (max-width: 1023px) { .crew-tile--cta { grid-column: span 2; } }

@media (hover: hover) {
  .crew-tile:hover { translate: 0 -4px; box-shadow: var(--shadow-2); }
}

.crew-tile__media {
  position: absolute;
  inset: 0;
  border-radius: 0;
}

/* faces sit in the upper third of the source frames, so a landscape crop has
   to bias upward or the big tile decapitates the CEO */
.crew-tile__media img {
  object-position: 50% 18%;
  transition: transform var(--dur-3) var(--ease-out);
}

@media (hover: hover) {
  .crew-tile:hover .crew-tile__media img { transform: scale(1.04); }
}

/* the initials are centred in the frame, which puts them behind the name
   plate — lift them into the clear area above it */
.crew-tile--ph .crew-tile__media > span { translate: 0 -14%; }

/* stand-in until the portrait arrives: initials in the same ghost-outline
   treatment the site uses for numerals, so it reads as design, not as a gap.
   Shared by the home bento and the about-page grid. */
.portrait-ph {
  display: grid;
  place-items: center;
  background: linear-gradient(150deg, var(--tint-blue), var(--tint-violet));
}

.portrait-ph span {
  font-family: var(--font-display);
  font-stretch: 116%;
  font-weight: 800;
  font-size: clamp(2.25rem, 5vw, 3.25rem);
  line-height: 1;
  letter-spacing: var(--track-tight);
  color: transparent;
  -webkit-text-stroke: 1.5px var(--ocean);
}

/* the name plate: a glass chip at the foot of the tile, same material as the
   chip over the bento photo above, so both blocks read as one system.
   max-content, not a full-width bar — stretched edge to edge it stopped
   reading as a label and started reading as a band across the face. */
.crew-tile__id {
  position: absolute;
  left: var(--space-3);
  bottom: var(--space-3);
  width: max-content;
  max-width: calc(100% - var(--space-3) * 2);
  display: grid;
  gap: 0.25em;
  padding: 0.6em 0.85em;
  border-radius: var(--radius-1);
  /* denser than --paper-glass: the mono role line is small, and 72% white over
     a busy photo does not clear AA for it */
  background: rgba(255, 255, 255, 0.88);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.6);
}

/* on a placeholder there is no photo to sit on top of — the glass slab over a
   pale gradient just read as a white smear, the same way the rail edge fades did */
/* the glass plate goes (over a pale gradient it read as a white smear) but the
   padding stays: dropping it too pulled the placeholder names 12px left of the
   photographed ones, so the row of names no longer shared a left edge */
.crew-tile--ph .crew-tile__id {
  background: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow: none;
}

.crew-tile__name {
  font-family: var(--font-display);
  font-stretch: 114%;
  font-weight: 800;
  text-transform: uppercase;
  font-size: var(--size-0);
  line-height: 1.1;
  letter-spacing: var(--track-tight);
  transition: color var(--dur-1) var(--ease-out);
}

.crew-tile__role { color: var(--ink-70); font-size: 0.68rem; }

/* the big tile can carry a bigger name without crowding the plate */
@media (min-width: 1024px) {
  /* the CEO plate used to be scaled up for a double-width cell; all cells are
     the same 4:5 now, so the name plate is the same everywhere too */
}

.crew-tile__go {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  width: 1.4rem;
  height: 1.4rem;
  color: var(--ink);
  opacity: 0;
  translate: -4px 4px;
  transition: opacity var(--dur-2) var(--ease-out), translate var(--dur-2) var(--ease-spring);
}

@media (hover: hover) {
  .crew-tile:hover .crew-tile__name { color: var(--ocean); }
  .crew-tile:hover .crew-tile__go { opacity: 1; translate: 0 0; }
}

.crew-tile:focus-visible .crew-tile__go { opacity: 1; translate: 0 0; }

/* the seventh cell is the section's call to action, not a person. Foam rather
   than ink: --ink is reserved for buttons, and a tile is not a button. */
.crew-tile--cta {
  background: var(--foam);
  display: grid;
  /* centred, not space-between: this tile is two short lines and at the crew
     block's 4:5 height space-between pinned the link to the ceiling and the
     note to the floor with a hole in between */
  align-content: center;
  justify-items: start;
  gap: var(--space-3);
  padding: var(--space-6) var(--space-5);
}

.crew-tile--cta .crew-tile__cta {
  display: flex;
  align-items: center;
  gap: 0.5em;
  font-family: var(--font-display);
  font-stretch: 114%;
  font-weight: 800;
  text-transform: uppercase;
  font-size: var(--size-0);
  line-height: 1.1;
  letter-spacing: var(--track-tight);
  color: var(--ocean);
}

.crew-tile--cta .icon {
  width: 1.1rem;
  height: 1.1rem;
  transition: translate var(--dur-2) var(--ease-spring);
}

@media (hover: hover) {
  .crew-tile--cta:hover .icon { translate: 4px 0; }
}

.crew-tile__note {
  font-size: var(--size--1);
  color: var(--ink-70);
}

/* -- process (sticky number) ------------------------------------------ */
.process { display: grid; gap: var(--space-6); }

@media (min-width: 768px) {
  .process { grid-template-columns: 1fr 1.6fr; align-items: start; }
}

.process__rail { position: sticky; top: calc(var(--header-h) + 2rem); }

.process__rail .metric__value { font-size: var(--size-6); }

.process__steps { display: grid; gap: var(--space-5); counter-reset: step; }

.step {
  position: relative;
  padding: clamp(1.25rem, 2.5vw, 2rem);
  padding-inline-start: clamp(4rem, 7vw, 5.5rem);
  counter-increment: step;
}

.step::before {
  content: counter(step, decimal-leading-zero);
  position: absolute;
  left: clamp(1.25rem, 2.5vw, 2rem);
  top: clamp(1.35rem, 2.6vw, 2.1rem);
  font-family: var(--font-mono);
  font-size: var(--size--1);
  color: var(--ocean);
  letter-spacing: 0.08em;
}

.step h3 { font-size: var(--size-1); margin-block-end: 0.4em; }

.step p { color: var(--ink-70); font-size: var(--size--1); }

/* -- reviews ------------------------------------------------------------ */
.reviews-grid { display: grid; gap: var(--space-5); }

@media (min-width: 768px) { .reviews-grid { grid-template-columns: repeat(3, 1fr); } }

.review {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: clamp(1.5rem, 3vw, 2rem);
}

.review .icon--quote { width: 1.8rem; height: 1.8rem; color: var(--tide); }

.review blockquote { color: var(--ink-70); flex: 1; }

.review figcaption { display: grid; gap: 0.1em; }

.review figcaption b { font-size: var(--size--1); }

.review figcaption span {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-50);
}

.rating-line {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  font-weight: 700;
}

.rating-line .icon { width: 1.1em; height: 1.1em; color: #e8a33d; fill: #e8a33d; }

/* -- CTA band ------------------------------------------------------------- */
/* The page's highest-intent block, so it captures the lead here instead of
   handing off to another page. The boat still moors in .voyage-dock. */
.cta-band {
  display: grid;
  gap: var(--space-7);
  position: relative;
  /* start, not center: the pitch column is taller than the form and centring
     pushed the headline below the form's first field */
  align-items: start;
}

@media (min-width: 900px) {
  .cta-band {
    grid-template-columns: minmax(0, 1fr) minmax(0, 30rem);
    gap: var(--space-8);
  }
}

.cta-band__pitch {
  display: grid;
  justify-items: start;
  gap: var(--space-5);
}

.cta-band h2 {
  font-size: var(--size-5);
  max-width: 12ch;
}

.cta-band h2 em {
  font-style: normal;
  background: var(--grad-metric);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.cta-band p { color: var(--ink-70); }

/* only the lead gets a measure — the mono eyebrow must stay on one line */
.cta-band__pitch p:not(.mono-label) { max-width: 44ch; }

.cta-band__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* -- CTA panel, the solo variant (services, about, cases, blog) ----------
   Those four pages hand off to /contact instead of carrying the form, and
   they used to borrow .cta-band — whose two-column grid dropped their four
   children into a 2x2 lattice: eyebrow, headline, lead and button each alone
   in a quarter with holes between them. This is one centred column on a
   raised panel instead: an ocean/violet wash breathing behind it, a wave
   line running along the foot, and the reassurance chips spelled out. */
.cta-solo {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: grid;
  justify-items: center;
  gap: var(--space-4);
  text-align: center;
  border-radius: var(--radius-2);
  background: linear-gradient(180deg, var(--paper), var(--grad-depth-1));
  box-shadow: inset 0 0 0 1px var(--line), var(--shadow-2);
  padding: clamp(2.5rem, 6vw, 4.5rem) clamp(1.25rem, 5vw, 4rem) clamp(4rem, 9vw, 6rem);
}

/* the wash: two soft blobs behind the type, same breathing as the svc hero */
.cta-solo::before,
.cta-solo::after {
  content: "";
  position: absolute;
  z-index: -1;
  aspect-ratio: 1;
  border-radius: 50%;
  filter: blur(64px);
  pointer-events: none;
}

.cta-solo::before {
  width: 46%;
  left: -8%;
  top: -38%;
  background: radial-gradient(circle, rgba(27, 102, 201, 0.26), transparent 68%);
}

.cta-solo::after {
  width: 40%;
  right: -10%;
  bottom: -34%;
  background: radial-gradient(circle, rgba(124, 111, 242, 0.24), transparent 68%);
}

html:not(.no-motion) .cta-solo::before { animation: hero-wash 24s var(--ease-inout) infinite alternate; }
html:not(.no-motion) .cta-solo::after { animation: hero-wash-2 31s var(--ease-inout) infinite alternate; }

/* the waterline. The path repeats every 120 units, so a 240-unit shift is a
   seamless loop; the svg is drawn 1680 wide against a 1440 window to leave
   the drift somewhere to come from. Faded at both ends so it reads as water
   passing through the panel rather than a line that starts and stops. */
.cta-solo__waves {
  position: absolute;
  z-index: -1;
  inset-inline-start: 0;
  inset-block-end: 0;
  width: 100%;
  height: clamp(64px, 11vw, 120px);
  opacity: 0.5;
  pointer-events: none;
  mask-image: linear-gradient(90deg, transparent, #000 18%, #000 82%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 18%, #000 82%, transparent);
}

.cta-solo__wave--back { opacity: 0.55; }

html:not(.no-motion) .cta-solo__wave { animation: cta-wave-drift 14s linear infinite; }
html:not(.no-motion) .cta-solo__wave--back { animation-duration: 22s; animation-direction: reverse; }

@keyframes cta-wave-drift {
  from { transform: translateX(0); }
  to   { transform: translateX(-240px); }
}

.cta-solo .mono-label { margin-block-end: calc(var(--space-1) * -1); }

.cta-solo h2 {
  font-size: var(--size-5);
  max-width: 14ch;
  margin-inline: auto;
}

.cta-solo h2 em {
  font-style: normal;
  background: var(--grad-metric);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.cta-solo__lead {
  color: var(--ink-70);
  max-width: 52ch;
  margin-inline: auto;
}

.cta-solo__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-3) var(--space-4);
  margin-block-start: var(--space-2);
}

.cta-solo__meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
}

/* the berth: pier on the left, boat moored beside it, broken water under the
   hull. The waterline has to meet the hull, not the sails — drawn at the base
   of the masts it slices the boat in half. */
.berth {
  position: relative;
  display: block;
  width: 260px;
  height: 140px;
  margin-block-start: var(--space-2);
}

.berth__marks {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.cta-band .voyage-dock {
  position: absolute;
  left: 126px;
  bottom: 10px;
  margin-inline: 0;
}

/* The voyage ends level with the top of the form, not below it: the mooring is
   supposed to hand the reader to the first field, and parked at the foot of
   the column it landed opposite the send button instead. Only where the pitch
   column is wide enough to hold it clear of the headline. */
/* The mooring used to be pinned to the top-right corner of the pitch column at
   >=1200px, which is where the headline lives; `max-width: 12ch` was supposed to
   hold the words clear, but at 84px that computes to 734px — wider than the
   column itself, so it guarded nothing and the boat sat on the letters. Giving
   it a grid column instead stopped the overlap but squeezed the headline to
   387px. It stays in flow and moves to the right end of the column: the words
   keep the full width, and nothing can collide in any language. */
@media (min-width: 1200px) {
  .berth {
    margin-inline-start: auto;
    width: 208px;
    height: 112px;
  }

  .cta-band .voyage-dock {
    left: 100px;
    bottom: 8px;
    width: 96px;
    height: 96px;
  }
}

.cta-form {
  padding: clamp(1.5rem, 3vw, 2.25rem);
  display: grid;
  gap: var(--space-3);
}

/* one field per row by default: paired half-width fields made the form look
   like four different control sizes stacked on top of each other */
.cta-form .grid-2 { display: grid; gap: var(--space-3); }

/* the one exception — email and phone. Both are required, both are short, and
   they read as one "how do we reach you" line. Strictly equal halves: the old
   problem was ragged pairs, not pairs as such. */
@media (min-width: 640px) {
  .cta-form .grid-2--pair,
  .contact-form .grid-2--pair { grid-template-columns: 1fr 1fr; }
}

/* and one button the full width of the fields it submits */
.cta-form .btn {
  justify-self: stretch;
  width: 100%;
  justify-content: center;
  margin-block-start: var(--space-2);
}

/* -- inner page hero -------------------------------------------------------- */
.page-hero {
  padding-block-start: calc(var(--header-h) + clamp(2.5rem, 6vh, 4.5rem));
  padding-block-end: var(--space-block);
}

.page-hero .crumbs { margin-block-end: var(--space-6); }

.page-hero h1 { font-size: var(--size-5); max-width: 14ch; }

/* A reading page is not a landing page: the article title sits one step down
   the scale and takes a longer measure. Without this it renders at 84px. */
.page-hero--read h1 {
  font-size: var(--size-3);
  max-width: 24ch;
}

.page-hero__lead {
  margin-block-start: var(--space-5);
  font-size: var(--size-1);
  color: var(--ink-70);
  max-width: 52ch;
}

/* -- cases page -------------------------------------------------------------- */
.cases-grid { display: grid; gap: var(--space-5); }

@media (min-width: 768px) { .cases-grid { grid-template-columns: 1fr 1fr; } }

.case-big {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: clamp(1.5rem, 3.5vw, 2.75rem);
}

.case-big.is-hidden { display: none; }

.case-big__head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-4);
}

.case-big__client {
  font-family: var(--font-display);
  font-stretch: 114%;
  font-weight: 800;
  text-transform: uppercase;
  font-size: var(--size-3);
  letter-spacing: var(--track-tight);
  line-height: 1.05;
}

.case-big__duration {
  font-family: var(--font-mono);
  font-size: var(--size--1);
  color: var(--ink-50);
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
}

.case-big__metrics {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-5);
  padding-block: var(--space-5);
  border-block: 1px solid var(--line);
}

.case-big p { color: var(--ink-70); }

.case-big .action { margin-block-start: auto; }

/* -- about: intro collage ------------------------------------------------------ */
.collage {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--space-4);
  align-items: end;
}

.collage .frame:nth-child(1) { grid-column: 1 / 6; aspect-ratio: 4 / 5; }
.collage .frame:nth-child(2) { grid-column: 6 / 10; aspect-ratio: 3 / 4; translate: 0 var(--space-6); }
.collage .frame:nth-child(3) { grid-column: 10 / 13; aspect-ratio: 4 / 5; }

@media (max-width: 639px) {
  .collage .frame:nth-child(1) { grid-column: 1 / 8; }
  .collage .frame:nth-child(2) { grid-column: 8 / 13; }
  .collage .frame:nth-child(3) { display: none; }
}

/* -- about: the Dive (pinned scrollytelling) ----------------------------------- */
.dive { position: relative; }

.dive__track { height: 400vh; height: 400dvh; }

.dive__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100dvh;
  overflow: clip;
  display: grid;
  align-items: center;
  background: linear-gradient(var(--grad-depth-1), var(--grad-depth-2));
  transition: background 600ms linear;
}

.dive__stage::after {
  /* light rays */
  content: "";
  position: absolute;
  inset: -20% -10%;
  background:
    radial-gradient(60% 45% at 20% -10%, rgba(255, 255, 255, 0.55), transparent 70%),
    radial-gradient(50% 40% at 75% -15%, rgba(255, 255, 255, 0.35), transparent 70%);
  pointer-events: none;
}

.dive[data-chapter="1"] .dive__stage { background: linear-gradient(var(--grad-depth-2), var(--grad-depth-3)); }
.dive[data-chapter="2"] .dive__stage { background: linear-gradient(var(--grad-depth-3), var(--grad-depth-4)); }
.dive[data-chapter="3"] .dive__stage { background: linear-gradient(var(--grad-depth-2), var(--grad-depth-1)); }

/* depth gauge */
.dive__gauge {
  position: absolute;
  left: var(--bleed-inset);
  top: 50%;
  translate: 0 -50%;
  display: grid;
  gap: var(--space-3);
  justify-items: start;
  z-index: 2;
}

.dive__gauge-depth {
  font-family: var(--font-mono);
  font-size: var(--size-1);
  font-variant-numeric: tabular-nums;
  color: var(--ocean);
}

.dive__gauge-scale {
  width: 1px;
  height: 30vh;
  background: var(--line-strong);
  position: relative;
}

.dive__gauge-scale::after {
  content: "";
  position: absolute;
  inset-inline: -2px;
  top: 0;
  height: calc(var(--progress, 0) * 100%);
  background: var(--grad-metric);
  border-radius: 2px;
}

.dive__gauge .mono-label { writing-mode: vertical-rl; }

@media (max-width: 767px) { .dive__gauge { display: none; } }

/* chapters */
.dive__chapters { position: relative; z-index: 1; }

.dive__chapter {
  grid-area: 1 / 1;
  max-width: var(--container);
  margin-inline: auto;
  display: grid;
  justify-items: center;
  text-align: center;
  gap: var(--space-5);
  opacity: 0;
  transform: translateY(46px) scale(0.985);
  filter: blur(6px);
  transition:
    opacity 480ms var(--ease-out),
    transform 640ms var(--ease-out),
    filter 640ms var(--ease-out);
  pointer-events: none;
  position: absolute;
  inset-inline: 0;
  top: 50%;
  translate: 0 -50%;
  padding-inline: var(--gutter);
}

.dive__chapter.is-active {
  opacity: 1;
  transform: none;
  filter: none;
  pointer-events: auto;
}

.dive__chapter.is-passed {
  opacity: 0;
  transform: translateY(-46px) scale(0.985);
  filter: blur(6px);
}

.dive__chapter h2 { font-size: var(--size-5); max-width: 16ch; }

.dive__chapter > p {
  font-size: var(--size-1);
  color: var(--ink-70);
  max-width: 52ch;
}

.dive__chapter .statline {
  border: 0;
  padding-block: 0;
  margin: 0;
  grid-template-columns: repeat(3, auto);
  justify-content: center;
  gap: var(--space-6) clamp(2rem, 6vw, 5rem);
}

.dive__principles {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(3, minmax(0, 18rem));
}

@media (max-width: 767px) { .dive__principles { grid-template-columns: 1fr; gap: var(--space-3); } }

.dive__principles article {
  background: var(--paper-glass);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border-radius: var(--radius-2);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.6), var(--shadow-1);
  padding: var(--space-6);
  display: grid;
  gap: var(--space-3);
  justify-items: center;
}

.dive__principles .icon { width: 1.8rem; height: 1.8rem; color: var(--ocean); stroke-width: 1.5; }

.dive__principles h3 { font-size: var(--size-0); }

.dive__principles p { font-size: var(--size--1); color: var(--ink-70); }

/* static fallback (no-motion): chapters become stacked sections */
.no-motion .dive__track { height: auto; }
.no-motion .dive__stage {
  position: static;
  height: auto;
  padding-block: var(--space-section);
  background: linear-gradient(var(--grad-depth-1), var(--grad-depth-3));
}
.no-motion .dive__chapter {
  position: static;
  translate: none;
  opacity: 1;
  transform: none;
  filter: none;
  pointer-events: auto;
  padding-block: var(--space-7);
}
.no-motion .dive__gauge { display: none; }

/* -- team grid -------------------------------------------------------------------- */
.team-grid { display: grid; gap: var(--space-5); }

@media (min-width: 640px) { .team-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .team-grid { grid-template-columns: repeat(4, 1fr); } }

.mate { display: grid; gap: var(--space-4); }

.mate .frame { aspect-ratio: 4 / 5; position: relative; }

.mate .frame img { position: absolute; inset: 0; }

.mate .frame img + img {
  opacity: 0;
  transition: opacity var(--dur-3) var(--ease-out);
}

.mate:hover .frame img + img { opacity: 1; }

.mate h3 { font-size: var(--size-1); }

.mate .mono-label { margin-top: 0.25em; }

/* -- masonry (in the wild) ---------------------------------------------------------- */
.wild {
  columns: 2;
  column-gap: var(--space-4);
}

@media (min-width: 768px) { .wild { columns: 3; } }

.wild figure {
  break-inside: avoid;
  margin-block-end: var(--space-4);
  display: grid;
  gap: 0.5rem;
}

.wild figcaption {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-50);
}

.svc-block__head {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.svc-block__head .icon {
  width: 2.2rem;
  height: 2.2rem;
  color: var(--ocean);
  stroke-width: 1.5;
  align-self: center;
}

.svc-block__body { color: var(--ink-70); max-width: 60ch; }

.svc-block__chips { display: flex; flex-wrap: wrap; gap: 0.5rem; }

.svc-block__proof {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-5);
  flex-wrap: wrap;
}

.svc-block__proof .metric__value { font-size: var(--size-2); }

.svc-block__proof p { font-size: var(--size--1); color: var(--ink-70); max-width: 34ch; }

/* -- blog ------------------------------------------------------------------------------ */
/* The article's own cover is shown whole: it is usually a 1200x630 share image
   or a screenshot, and a crop would eat the top and bottom of it. `.frame`
   forces `height: 100%` on its image, so both have to be undone here. */
.post__cover {
  aspect-ratio: auto;
  background: transparent;
}

.post__cover img {
  height: auto;
  object-fit: contain;
}

/* Same for pictures inside the text — diagrams and screenshots, never crops. */
.article__body img {
  height: auto;
  border-radius: var(--radius-2);
}

.post-featured {
  display: grid;
  gap: var(--space-6);
  align-items: center;
}

@media (min-width: 768px) { .post-featured { grid-template-columns: 1.2fr 1fr; } }

.post-featured .frame { aspect-ratio: 3 / 2; }

.post-grid { display: grid; gap: var(--space-6); }

@media (min-width: 640px) { .post-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .post-grid { grid-template-columns: repeat(3, 1fr); } }

.post {
  display: grid;
  gap: var(--space-4);
  align-content: start;
}

.post .frame { aspect-ratio: 3 / 2; }

.post__meta {
  display: flex;
  gap: var(--space-4);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-50);
}

.post h3 { font-size: var(--size-1); transition: color var(--dur-1) var(--ease-out); }

.post:hover h3 { color: var(--ocean); }

.post p { font-size: var(--size--1); color: var(--ink-70); }

.cover {
  aspect-ratio: 3 / 2;
  border-radius: var(--radius-2);
  display: grid;
  place-items: center;
  padding: var(--space-5);
  position: relative;
  overflow: hidden;
}

.cover--a { background: linear-gradient(135deg, var(--grad-depth-2), #cfe4f7 55%, #dcd7fb); }
.cover--b { background: linear-gradient(135deg, #eaf3fa, #d3e8f8 50%, #cfe0f5); }
.cover--c { background: linear-gradient(150deg, #f3f0ff, #ddeafd 60%, #cfe6f9); }

.cover .icon {
  width: 3rem;
  height: 3rem;
  color: var(--ocean);
  stroke-width: 1.25;
  opacity: 0.85;
}

.cover .mono-label {
  position: absolute;
  bottom: var(--space-4);
  left: var(--space-4);
  color: var(--ink-50);
}

/* article */
.article {
  display: grid;
  gap: var(--space-7);
}

@media (min-width: 1024px) {
  .article { grid-template-columns: 15rem minmax(0, var(--measure-read)); justify-content: center; }
}

.article__toc {
  position: sticky;
  top: calc(var(--header-h) + 1.5rem);
  align-self: start;
  display: none;
  gap: 0.25rem;
}

@media (min-width: 1024px) { .article__toc { display: grid; } }

.article__toc a {
  padding: 0.45em 0.9em;
  border-radius: var(--radius-1);
  font-size: var(--size--1);
  color: var(--ink-50);
}

.article__toc a:hover { color: var(--ink); background: var(--foam); }

.article__body { display: grid; gap: var(--space-5); }

.article__body h2 {
  font-size: var(--size-2);
  margin-block-start: var(--space-5);
  scroll-margin-top: calc(var(--header-h) + 1rem);
}

.article__body ul { display: grid; gap: 0.5rem; padding-inline-start: 1.2em; list-style: disc; }

.article__body li::marker { color: var(--ocean); }

.article__note {
  padding: var(--space-5);
  border-inline-start: 3px solid var(--ocean);
  background: var(--mist);
  border-radius: 0 var(--radius-1) var(--radius-1) 0;
  color: var(--ink-70);
  font-size: var(--size--1);
}

.author-card {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  padding: var(--space-5);
  border-radius: var(--radius-2);
  background: var(--mist);
}

/* The rules used to target `b` and `span`; the template prints paragraphs, so
   the byline was rendering unstyled. */
.author-card__photo {
  flex: 0 0 auto;
  width: 4.5rem;
  height: 4.5rem;
  border-radius: 50%;
}

.author-card__name {
  font-weight: 600;
  color: var(--ink);
}

.author-card__role,
.author-card__bio {
  font-size: var(--size--1);
  color: var(--ink-70);
}

.author-card__bio { margin-block-start: 0.35em; max-width: 60ch; }

/* -- contact ----------------------------------------------------------------------------- */
.contact-split { display: grid; gap: var(--space-8); }

/* the aside carries the photo AND the three steps so both columns land at
   roughly the same height — the old layout left a hole beside the form */
@media (min-width: 1024px) { .contact-split { grid-template-columns: 1fr 1.05fr; align-items: start; } }

.contact-aside { display: grid; gap: var(--space-6); align-content: start; }

.contact-photo { aspect-ratio: 4 / 3; }

.contact-aside h2 { font-size: var(--size-2); }

.next-steps { display: grid; gap: var(--space-5); counter-reset: step; list-style: none; }

.next-steps li {
  counter-increment: step;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.35rem var(--space-4);
  align-items: baseline;
}

.next-steps li::before {
  content: counter(step, decimal-leading-zero);
  grid-row: span 2;
  font-family: var(--font-display);
  font-stretch: 116%;
  font-weight: 800;
  font-size: var(--size-2);
  line-height: 1;
  color: transparent;
  -webkit-text-stroke: 1.2px var(--ink-30);
}

.next-steps h3 { font-size: var(--size-1); }

.next-steps p { grid-column: 2; color: var(--ink-70); font-size: var(--size--1); }

/* four ways to reach us, as a card row instead of a stacked list */
.contact-rows {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}

.contact-row {
  display: grid;
  align-content: start;
  gap: 0.3rem;
  padding: var(--space-5);
  border-radius: var(--radius-2);
  box-shadow: inset 0 0 0 1px var(--line);
  transition: background-color var(--dur-1) var(--ease-out), box-shadow var(--dur-2) var(--ease-out);
}

a.contact-row:hover { background: var(--mist); box-shadow: inset 0 0 0 1px var(--line-strong); }

.contact-row .icon {
  color: var(--ocean);
  width: 1.5rem;
  height: 1.5rem;
  margin-block-end: var(--space-4);
}

.contact-row b { font-size: var(--size-0); line-height: 1.35; overflow-wrap: break-word; }

/* One column, deliberately. A two-column grid forces both columns onto the same
   row height, so opening one answer left a matching hole under the closed
   question beside it — measured at 108px. Full width, centred measure instead. */
.faq--split { width: min(100%, var(--container-read)); margin-inline: auto; }

.contact-form {
  padding: clamp(1.5rem, 3.5vw, 2.5rem);
  display: grid;
  gap: var(--space-4);
}

/* same rule as the CTA form: single column, and only .grid-2--pair splits
   (see the media query up by .cta-form) */
.contact-form .grid-2 { display: grid; gap: var(--space-4); }

.form-ok {
  display: none;
  padding: var(--space-5);
  border-radius: var(--radius-1);
  background: #e7f6ec;
  color: #14532d;
}

.form-ok.is-visible { display: block; }

/* -- 404 ----------------------------------------------------------------------------------- */
.lost {
  min-height: 70vh;
  display: grid;
  place-content: center;
  text-align: center;
  gap: var(--space-5);
  padding-block: calc(var(--header-h) + var(--space-8)) var(--space-8);
}

.lost h1 { font-size: var(--size-6); }

.lost p { color: var(--ink-70); margin-inline: auto; }

/* ============================================================
   v2: voyager (traveling 3D sail)
   The route map that used to share this layer became the course chart —
   see the v8 layer at the end of the file.
   ============================================================ */

/* -- voyager: the origami sail that rides the scroll ------------- */
.voyager {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 30;
  width: 120px;
  height: 120px;
  pointer-events: none;
  perspective: 900px;
  will-change: transform;
  /* soft appear/vanish at the dimmed stops of the phone lane, and over the course chart */
  transition: opacity 420ms var(--ease-out);
}

.voyager--docked { position: absolute; }

.voyager__float {
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
}

html:not(.no-motion) .voyager__float {
  animation: boat-bob 4.6s var(--ease-inout) infinite;
}

@keyframes boat-bob {
  0%, 100% { transform: translateY(0) rotate(-1.2deg); }
  50% { transform: translateY(-7px) rotate(1.4deg); }
}

.voyager__boat {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  transform: rotateY(var(--ry, -18deg)) rotateZ(var(--rz, 0deg));
  /* smooths the tack flips when the course line changes direction */
  transition: transform 380ms var(--ease-out);
}

.voyager i { position: absolute; display: block; }

/* The boat is one SVG now — the approved sailboat.svg, injected by boat.js.
   It used to be five CSS faces (.v-sail--a/b/c, .v-hull, .v-keel) with
   clip-path polygons: straight luffs approximating the logo's curved sails,
   plus a filled hull. Those faces carried translateZ for the spiral's depth
   cue, which is invisible at this size and not worth an approximate mark. */
.voyager__boat svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.voyager__shadow {
  position: absolute;
  left: 14%;
  right: 14%;
  bottom: 2px;
  height: 12px;
  border-radius: 50%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(11, 27, 43, 0.22), transparent 70%);
  filter: blur(3px);
  transform: translateZ(-20px);
}

html:not(.no-motion) .voyager__shadow {
  animation: boat-shadow 4.6s var(--ease-inout) infinite;
}

@keyframes boat-shadow {
  0%, 100% { scale: 1; opacity: 1; }
  50% { scale: 0.86; opacity: 0.7; }
}

/* dock slot inside the CTA band */
.voyage-dock {
  position: relative;
  display: block;
  width: 120px;
  height: 120px;
  margin-inline: auto;
}

.voyage-dock svg { width: 100%; height: 100%; }

/* static fallback boat (mobile / no-motion); hidden when the live one sails */
.voyager-live .voyage-dock svg { opacity: 0; }

/* ============================================================
   v3: human labels + redesigned home blocks
   ============================================================ */

/* The services list carried ghost numerals 01–06 until 11.08.2026. Numbering
   promises an order, and these six services are parallel — nothing says SEO
   comes before automation. Removed; the numerals stay on the course cards,
   where the sequence is real. */

.svc-block__num {
  font-family: var(--font-display);
  font-stretch: 116%;
  font-weight: 800;
  font-size: var(--size-3);
  line-height: 1;
  color: transparent;
  -webkit-text-stroke: 1.2px var(--ink-30);
}

.mm-link .idx {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--ink-30);
  transition: color var(--dur-1) var(--ease-out);
}

/* ============================================================
   Case study pages (case-*.html)
   ============================================================ */

/* -- device frames -------------------------------------------------------
   A bare screenshot floating on a page reads as a JPEG someone pasted in. The
   same image inside a laptop lid and a phone bezel reads as a product someone
   shipped, and it tells the reader at a glance which device they are looking
   at. Drawn in CSS rather than dropped in as a stock photo: it stays sharp at
   any pixel ratio, it costs no bytes, and nothing about the client's screen is
   obscured by a photographer's desk.

   The browser bar carries the real domain. It is the one piece of chrome that
   earns its place: it says "this is live" without a caption. */
.device {
  position: relative;
  display: block;
  isolation: isolate;
}

.device__frame {
  position: relative;
  display: block;
  overflow: hidden;
  background: var(--ink);
}

.device__screen {
  position: relative;
  display: block;
  overflow: hidden;
  background: var(--mist);
}

.device__screen img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 0;
}

/* -- laptop --------------------------------------------------------------- */
.device--desk .device__frame {
  border-radius: clamp(0.7rem, 1.1vw, 1.05rem);
  padding: clamp(0.5rem, 0.8vw, 0.8rem);
  padding-block-start: clamp(1.6rem, 2.2vw, 2.1rem);
  box-shadow: var(--shadow-3);
}

/* the browser bar: three lights and the live domain */
.device__bar {
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  height: clamp(1.6rem, 2.2vw, 2.1rem);
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding-inline: clamp(0.7rem, 1vw, 1rem);
}

.device__bar i {
  width: clamp(0.4rem, 0.55vw, 0.52rem);
  aspect-ratio: 1;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.28);
}

.device__url {
  margin-inline: auto;
  padding: 0.15em 0.9em;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.66);
  font-family: var(--font-mono);
  font-size: clamp(0.55rem, 0.72vw, 0.7rem);
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.device--desk .device__screen {
  border-radius: clamp(0.25rem, 0.4vw, 0.4rem);
  aspect-ratio: 16 / 10;
}

/* No laptop foot. The first version drew one 112% wide, and at container width
   that is a dark bar running the full page — it read as a stray divider, not as
   hardware. The bezel and the browser bar already say "screen"; the foot only
   said "we drew a laptop". */

/* -- phone ---------------------------------------------------------------- */
.device--phone .device__frame {
  border-radius: clamp(1.4rem, 2.4vw, 2rem);
  padding: clamp(0.3rem, 0.5vw, 0.45rem);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08) inset, var(--shadow-3);
}

.device--phone .device__screen {
  border-radius: clamp(1.1rem, 2vw, 1.6rem);
  aspect-ratio: 393 / 852;
}

/* the island, drawn not photographed */
.device--phone .device__frame::after {
  content: "";
  position: absolute;
  z-index: 2;
  inset-block-start: clamp(0.55rem, 0.9vw, 0.8rem);
  inset-inline-start: 50%;
  transform: translateX(-50%);
  width: 28%;
  height: clamp(0.5rem, 0.85vw, 0.75rem);
  border-radius: var(--radius-pill);
  background: var(--ink);
}

/* -- the pair on a case page --------------------------------------------- */
.case-screens {
  position: relative;
  isolation: isolate;
  margin-block-start: var(--space-block);
}

.case-screens::before {
  content: "";
  position: absolute;
  inset: -10% -6% -12% -6%;
  z-index: -1;
  background: radial-gradient(52% 52% at 28% 18%, rgba(27, 102, 201, 0.16), transparent 70%),
              radial-gradient(46% 46% at 84% 78%, rgba(124, 111, 242, 0.15), transparent 70%);
  pointer-events: none;
}

.case-screens__phone {
  display: none;
  position: absolute;
  z-index: 2;
  inset-inline-end: 3%;
  inset-block-end: -9%;
  width: 15.5%;
}

@media (min-width: 760px) {
  .case-screens__phone { display: block; }
}

/* -- more from the site: extra screens, in frames, with captions ---------- */
.case-more {
  display: grid;
  gap: var(--space-6);
  margin-block-start: var(--space-block);
}

@media (min-width: 860px) {
  .case-more { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-7); }
}

.case-more figure { margin: 0; display: grid; gap: var(--space-3); }

.case-more figcaption { color: var(--ink-70); font-size: var(--size--1); }

/* -- case pages: the reading rhythm --------------------------------------
   These pages are long on purpose, so the body copy has to carry a reader who
   is skimming. Two rules do most of the work: one measure (nothing wider than
   68 characters) and one size step up from the site default, because a case
   page is read, not scanned like a service grid. The bullet markers were the
   other half of the problem — .build-list was never given any, so every list
   on these pages arrived as unbroken lines of text. */
.case-prose { display: grid; gap: var(--space-4); max-width: 68ch; }

.case-prose > p { font-size: var(--size-1); line-height: 1.55; color: var(--ink-70); }

/* the opening sentence sets the scene: full ink, so the eye starts there */
.case-prose > p:first-child { color: var(--ink); }

.case-list {
  display: grid;
  gap: var(--space-3);
  margin-block-start: var(--space-5);
  list-style: none;
  max-width: 66ch;
}

.case-list > li {
  position: relative;
  padding-inline-start: 1.7rem;
  font-size: var(--size-0);
  line-height: 1.5;
  color: var(--ink-70);
}

.case-list > li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0.1rem;
  inset-block-start: 0.62em;
  width: 0.45rem;
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--grad-metric);
}

/* the cards and the plan step up too: --size--1 is a caption size, and these
   are paragraphs someone is expected to actually read */
.case-build__item p { font-size: var(--size-0); line-height: 1.5; }

.case-plan p { font-size: var(--size-0); line-height: 1.5; }

.case-ai p { font-size: var(--size-0); line-height: 1.55; }

/* -- at a glance: the facts a prospect scans before reading -------------- */
.case-glance {
  display: grid;
  gap: 0;
  border-block-start: 1px solid var(--line);
}

/* One column on a phone: at 375px the two-column form gave the label 224px
   of 335 and wrapped every value onto five lines. */
.case-glance > div {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-1);
}

@media (min-width: 640px) {
  .case-glance > div {
    grid-template-columns: minmax(9rem, 14rem) minmax(0, 1fr);
    gap: var(--space-3) var(--space-5);
  }
}

.case-glance > div {
  padding-block: var(--space-4);
  border-block-end: 1px solid var(--line);
}

.case-glance dt {
  font-family: var(--font-mono);
  font-size: var(--size--1);
  letter-spacing: var(--track-mono);
  text-transform: uppercase;
  color: var(--ink-70);
}

.case-glance dd { margin: 0; }

.case-stack {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block-start: var(--space-5);
}

/* -- what we built: the technology section ------------------------------ */
.case-build {
  display: grid;
  gap: var(--space-5);
  margin-block-start: var(--space-block);
}

@media (min-width: 760px) {
  .case-build { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* an odd last card spanning both columns looks like a mistake; let it sit
     in its own column and keep the rhythm */
}

.case-build__item {
  display: grid;
  gap: var(--space-3);
  align-content: start;
  padding: clamp(1.5rem, 2.6vw, 2.25rem);
  border-radius: var(--radius-2);
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--line);
}

.case-build__num {
  font-family: var(--font-mono);
  font-size: var(--size--1);
  letter-spacing: var(--track-mono);
  color: var(--ocean);
}

.case-build__item h3 { font-size: var(--size-1); }

.case-build__item p { color: var(--ink-70); font-size: var(--size--1); }

/* -- the split: design copy beside the phone shot ----------------------- */
.case-design { display: grid; gap: var(--space-6); align-items: center; }

@media (min-width: 900px) {
  .case-design { grid-template-columns: minmax(0, 1fr) minmax(0, 22rem); gap: var(--space-8); }
}

.case-design__phone { width: min(15rem, 70%); margin-inline: auto; }

/* The results prose used to ride in .section-head__action, a 34ch aside meant
   for one line. Four sentences in it left a column of text half a screen tall
   beside an empty half-screen of nothing. It gets a real measure now, and the
   numbers follow it instead of sitting above it. */
.case-lede {
  max-width: 64ch;
  font-size: var(--size-1);
  line-height: 1.5;
  color: var(--ink-70);
  margin-block-start: var(--space-5);
}

.case-lede strong { color: var(--ink); font-weight: 600; }

/* -- AI visibility: the section most agencies cannot write -------------- */
.case-ai {
  display: grid;
  gap: var(--space-4);
  padding: clamp(1.5rem, 3vw, 2.5rem);
  border-radius: var(--radius-2);
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--line);
  margin-block-start: var(--space-block);
}

.case-ai__head { display: flex; align-items: center; gap: var(--space-3); }

.case-ai__head .icon { width: 1.5rem; height: 1.5rem; color: var(--ocean); }

.case-ai h3 { font-size: var(--size-1); }

.case-ai p { color: var(--ink-70); max-width: 72ch; }

/* A case headline is a sentence, not a slogan: "A deck builder site with a page
   for Deck Academy" is 48 characters where the home page says "Ready to make
   waves". At the shared page-hero size that filled a whole phone screen before
   the reader reached a single fact, so case pages get one step down and a
   measure. */
.case-hero h1 { font-size: var(--size-3); max-width: 18ch; }

@media (min-width: 900px) {
  .case-hero h1 { font-size: var(--size-4); max-width: 16ch; }
}

.case-hero .page-hero__lead { max-width: 60ch; }

/* the accent tail of a case headline, same gradient as the service hero */
.case-hero h1 em {
  font-style: normal;
  background: var(--grad-metric);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* A four-metric row must not orphan the fourth item under the other three.
   Doubled class on purpose: the base .proof-strip three-column rule lives
   further down the file and would otherwise win on order. */
.proof-strip.proof-strip--four { grid-template-columns: repeat(2, minmax(0, 1fr)); }

@media (min-width: 1024px) {
  .proof-strip.proof-strip--four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* the source of a number is a footnote, not part of the sentence — and not in
   uppercase mono either, which shouted louder than the claim it qualifies */
.proof__note {
  display: block;
  margin-block-start: 0.4em;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  line-height: 1.4;
  color: var(--ink-70);
}

/* the design column stands alone where there is no phone shot beside it */
@media (min-width: 900px) {
  .case-design--solo { grid-template-columns: minmax(0, 1fr); }
  .case-design--solo > div { max-width: 68ch; }
}

/* evidence inside the AI panel runs full width: the numbers on it are the
   point, and a thumbnail hides them */
.case-ai__proof { margin: 0; display: grid; gap: var(--space-3); }

.case-ai__proof .frame { display: block; box-shadow: inset 0 0 0 1px var(--line); }

.case-ai__proof img { display: block; width: 100%; height: auto; }

/* -- the work plan: the transparency block ------------------------------ */
/* Hairline rows, no cards: this is a list of what happens in what order, and
   card chrome would turn a plan into a pricing table. */
.case-plan {
  display: grid;
  list-style: none;
  border-block-start: 1px solid var(--line);
  margin-block-start: var(--space-block);
}

.case-plan > li {
  display: grid;
  gap: var(--space-2) var(--space-6);
  padding-block: clamp(1.3rem, 2.4vh, 1.9rem);
  border-block-end: 1px solid var(--line);
}

@media (min-width: 800px) {
  .case-plan > li { grid-template-columns: 9rem minmax(0, 1fr); align-items: start; }
}

.case-plan__phase {
  font-family: var(--font-mono);
  font-size: var(--size--1);
  letter-spacing: var(--track-mono);
  text-transform: uppercase;
  color: var(--ocean);
}

.case-plan h3 { font-size: var(--size-1); margin-block-end: var(--space-2); }

.case-plan p { color: var(--ink-70); font-size: var(--size--1); max-width: 68ch; }

/* -- prev / next -------------------------------------------------------- */
.case-nav {
  display: grid;
  gap: var(--space-4);
  border-block-start: 1px solid var(--line);
  padding-block-start: var(--space-6);
  margin-block-start: var(--space-block);
}

@media (min-width: 700px) {
  .case-nav { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .case-nav__item--next { text-align: end; }
}

.case-nav__item {
  display: grid;
  gap: var(--space-2);
  text-decoration: none;
  color: inherit;
}

.case-nav__item b { font-family: var(--font-display); font-stretch: 116%; font-size: var(--size-1); }

.case-nav__item:hover b { color: var(--ocean); }

/* -- the index grid on cases.html --------------------------------------- */
.case-index {
  display: grid;
  gap: var(--space-6);
  margin-block-start: var(--space-block);
}

@media (min-width: 720px) { .case-index { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

@media (min-width: 1100px) { .case-index { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.case-tile {
  display: grid;
  grid-template-rows: auto 1fr;
  gap: var(--space-4);
  padding: 0;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
}

.case-tile__shot {
  display: block;
  aspect-ratio: 16 / 10;
  background: var(--mist);
  border-block-end: 1px solid var(--line);
}

.case-tile__shot img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 0; transition: transform var(--dur-3) var(--ease-out); }

@media (hover: hover) {
  .case-tile:hover .case-tile__shot img { transform: scale(1.03); }
}

.case-tile__body {
  display: grid;
  gap: var(--space-3);
  align-content: start;
  padding: 0 clamp(1.25rem, 2.4vw, 1.75rem) clamp(1.5rem, 2.6vw, 2rem);
}

.case-tile__tags { display: flex; flex-wrap: wrap; gap: 0.4rem; }

.case-tile h3 { font-size: var(--size-1); }

.case-tile p { color: var(--ink-70); font-size: var(--size--1); }

.case-tile__metrics { display: flex; flex-wrap: wrap; gap: var(--space-4); margin-block-start: auto; }

.case-tile__metric { display: grid; gap: 1px; }

.case-tile__metric b {
  font-family: var(--font-display);
  font-stretch: 116%;
  font-weight: 800;
  font-size: var(--size-1);
  line-height: 1;
  background: var(--grad-metric);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.case-tile__metric span { font-size: 0.75rem; color: var(--ink-70); }

/* -- case pages: the before/after pair -----------------------------------
   No drag-slider. A handle shows half of each screenshot at a time, it needs
   JS to say anything at all, and it reads as a tool demo — the exact species
   we deleted twice (SERP preview, Core Web Vitals). Both frames stand side by
   side, the newer one larger, and neither image is filtered: desaturating the
   "before" would be arguing with the evidence instead of showing it.

   The pair is honest only if both shots are taken the same way — same viewport
   width, same scroll position, same crop. tools/shoot_case.py does the taking;
   the manifest entry does the cropping (aspect 16:10, focus [0.5, 0], so the
   crop always keeps the top of the page). */
.case-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block-start: var(--space-5);
}

.case-ba { display: grid; gap: var(--space-6); }

.case-ba__pair { display: grid; gap: var(--space-5); align-items: end; }

/* 38/62: the new site gets the room, the old one stays legible enough to
   compare. Below 900 they stack, oldest first — the reading order is the
   chronology. */
@media (min-width: 900px) {
  .case-ba__pair { grid-template-columns: 38fr 62fr; gap: var(--space-6); }
}

.case-ba__shot { margin: 0; display: grid; gap: var(--space-3); }

.case-ba__plate {
  position: relative;
  display: block;
  overflow: hidden;
  border-radius: var(--radius-2);
  background: var(--mist);
  aspect-ratio: 16 / 10;
}

/* top-anchored: a site screenshot cropped from the middle is unrecognisable */
.case-ba__plate img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 0;
}

/* the older state sits quieter — a hairline; the shipped one is lifted */
.case-ba__shot--before .case-ba__plate { box-shadow: inset 0 0 0 1px var(--line-strong); }

.case-ba__shot--after .case-ba__plate { box-shadow: inset 0 0 0 1px var(--line), var(--shadow-3); }

/* placeholder until the screenshots land: the same mock-browser plate the
   home-page case covers use, so the layout is honest about its own gaps */
.case-ba__ph {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
}

/* the ghost word is borrowed from the home-page cover, where it is pinned to a
   corner; inside the plate it has to sit in the middle or it clips */
.case-ba__ph .case-card__ghost {
  position: static;
  /* the cover version is pinned to a corner and clipped at 90% width with
     nowrap — inside the plate that ate the last letter of the word */
  max-width: none;
  overflow: visible;
  font-size: clamp(1.75rem, 4vw, 2.5rem);
}

.case-ba__ph .case-card__chrome { top: 0.9rem; left: 1rem; }

.case-ba__cap {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
}

.case-ba__cap span:not(.mono-label) { color: var(--ink-70); font-size: var(--size--1); }

/* what actually changed: three hairline rows, ghost numeral, no cards */
.case-ba__diff {
  display: grid;
  list-style: none;
  border-block-start: 1px solid var(--line);
}

.case-ba__diff > li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-2) var(--space-5);
  padding-block: clamp(1.1rem, 2.2vh, 1.6rem);
  border-block-end: 1px solid var(--line);
}

.case-ba__diff-num {
  font-family: var(--font-display);
  font-stretch: 116%;
  font-weight: 800;
  font-size: var(--size-2);
  line-height: 0.9;
  color: transparent;
  -webkit-text-stroke: 1.1px var(--ink-30);
  font-variant-numeric: tabular-nums;
}

.case-ba__diff h3 { font-size: var(--size-1); margin-block-end: var(--space-2); }

.case-ba__diff p { color: var(--ink-70); font-size: var(--size--1); max-width: 62ch; }

/* -- proof strip: the human version of a stats row ---------------- */
.proof-strip {
  display: grid;
  gap: var(--space-5);
  padding-block: var(--space-6);
  border-block: 1px solid var(--line);
  margin-block-start: var(--space-block);
}

@media (min-width: 768px) {
  /* equal thirds: the wider first column made the row look like a lead
     item plus two footnotes, and the three claims carry the same weight */
  .proof-strip { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-7); }
}

.proof {
  display: grid;
  gap: var(--space-3);
  align-content: start;
  justify-items: start;
}

@media (min-width: 768px) {
  .proof + .proof { border-inline-start: 1px solid var(--line); padding-inline-start: var(--space-7); }
}

/* tablets: at --space-7 the three columns are too tight and the rating line
   in the last one ran into the container edge */
@media (min-width: 768px) and (max-width: 1023px) {
  .proof-strip { gap: var(--space-5); }
  .proof + .proof { padding-inline-start: var(--space-5); }
}

.proof__num {
  font-family: var(--font-display);
  font-stretch: 116%;
  font-weight: 800;
  font-size: var(--size-4);
  line-height: 1;
  letter-spacing: var(--track-tight);
  background: var(--grad-metric);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  font-variant-numeric: tabular-nums;
}

.proof p { color: var(--ink-70); font-size: var(--size--1); max-width: 30ch; }

.proof__stars {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  font-size: var(--size--1);
  font-weight: 700;
}

.proof__stars .icon { width: 1em; height: 1em; color: #e8a33d; fill: #e8a33d; }

/* -- selected work v2: big showcase cards -------------------------- */
/* 24rem, not 46rem. At 46rem one card ate half a laptop screen: 1.9 cards
   fitted, so the second was sliced by the viewport edge with nothing showing
   behind it and the whole rail read as broken layout rather than as something
   that scrolls. At 24rem three fit with a fourth clearly peeking, and eight
   cases take half the drag they used to. */
.work-lente {
  /* clamp, not a min() cap: at 1280 three cards plus their gaps filled the
     row exactly and the fourth peeked by 5px — with the arrows hidden on
     touch, that sliver was the only thing saying the rail scrolls */
  grid-auto-columns: clamp(17rem, 26vw, 24rem);
}

.case-card { min-height: 0; gap: var(--space-4); }

.case-card__visual {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-1);
  overflow: clip;
  margin-block-end: var(--space-2);
}

.case-card__visual--blue { background: linear-gradient(135deg, var(--tint-blue), #cfe3f7 70%, #c3d9f2); }
.case-card__visual--violet { background: linear-gradient(135deg, var(--tint-violet), #ded7fa 70%, #d2c9f7); }
.case-card__visual--sand { background: linear-gradient(135deg, var(--sand), #efe3cf 70%, #e9dabf); }
.case-card__visual--mist { background: linear-gradient(135deg, var(--mist), #e2eaf1 70%, #d8e3ec); }

.case-card__chrome {
  position: absolute;
  inset-inline: 0;
  top: 0;
  display: flex;
  gap: 5px;
  padding: 10px 12px;
  background: rgba(255, 255, 255, 0.55);
}

.case-card__chrome i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ink-30);
  opacity: 0.5;
}

/* sized to sit inside the cover now that the card is 24rem: at the old size
   the word overflowed the frame by a third of its height and got clipped, so
   it read as a stray letter fragment rather than a watermark */
.case-card__ghost {
  position: absolute;
  left: 5%;
  bottom: 6%;
  max-width: 90%;
  font-family: var(--font-display);
  font-stretch: 120%;
  font-weight: 800;
  font-size: clamp(2.5rem, 4.2vw, 3.125rem);
  line-height: 1;
  color: rgba(255, 255, 255, 0.78);
  letter-spacing: -0.03em;
  user-select: none;
  overflow: hidden;
  text-overflow: clip;
  white-space: nowrap;
}

/* top-right, clear of the ghost word: at the narrower card size the two
   overlapped and the watermark read as a smudged letter */
.case-card__visual .icon {
  position: absolute;
  right: 5%;
  top: 22%;
  width: 1.6rem;
  height: 1.6rem;
  color: var(--ink-50);
  stroke-width: 1.4;
}

/* auto, not a fixed gap: chips wrap on some cards and not others, which
   pushed the metric rows of neighbouring cards up to 70px apart */
.case-card__metrics { margin-block-start: auto; }

.case-card .action { margin-block-start: auto; }

/* -- bento: why Business Ocean -------------------------------------- */
.bento {
  display: grid;
  gap: var(--space-4);
}

@media (min-width: 640px) { .bento { grid-template-columns: repeat(2, 1fr); } }

@media (min-width: 1024px) {
  .bento {
    grid-template-columns: repeat(4, 1fr);
    grid-template-areas:
      "a a b c"
      "d e e f";
  }
  .bento > :nth-child(1) { grid-area: a; }
  .bento > :nth-child(2) { grid-area: b; }
  .bento > :nth-child(3) { grid-area: c; }
  .bento > :nth-child(4) { grid-area: d; }
  .bento > :nth-child(5) { grid-area: e; }
  .bento > :nth-child(6) { grid-area: f; }
}

.tile {
  border-radius: var(--radius-2);
  padding: clamp(1.5rem, 2.6vw, 2.25rem);
  display: grid;
  gap: var(--space-3);
  align-content: end;
  min-height: 15rem;
  position: relative;
  overflow: clip;
  transition: translate var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out);
}

@media (max-width: 639px) {
  .tile { min-height: 0; }
  .tile--photo { min-height: 14rem; }
  .tile > .icon,
  .tile__icons { margin-block-end: var(--space-2); }
}

@media (hover: hover) {
  .tile:hover { translate: 0 -4px; box-shadow: var(--shadow-2); }
}

.tile--blue { background: var(--tint-blue); }
.tile--violet { background: var(--tint-violet); }
.tile--sand { background: var(--sand); }
.tile--mist { background: var(--mist); }
.tile--line { box-shadow: inset 0 0 0 1px var(--line); background: var(--paper); }
.tile--line:hover { box-shadow: inset 0 0 0 1px var(--line), var(--shadow-2); }

.tile > .icon {
  width: 2.2rem;
  height: 2.2rem;
  color: var(--ocean);
  stroke-width: 1.5;
  margin-block-end: auto;
}

.tile h3 { font-size: var(--size-1); }

.tile p { color: var(--ink-70); font-size: var(--size--1); max-width: 42ch; }

.tile__big {
  font-family: var(--font-display);
  font-stretch: 116%;
  font-weight: 800;
  font-size: var(--size-4);
  line-height: 1;
  letter-spacing: var(--track-tight);
  background: var(--grad-metric);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.tile__icons { display: flex; gap: var(--space-3); margin-block-end: auto; flex-wrap: wrap; }

.tile__icons .icon { width: 1.5rem; height: 1.5rem; color: var(--ocean); stroke-width: 1.6; }

.tile--photo { padding: 0; align-content: stretch; min-height: 15rem; }

.tile--photo img { width: 100%; height: 100%; object-fit: cover; position: absolute; inset: 0; }

.tile--photo .chip {
  position: absolute;
  left: var(--space-4);
  bottom: var(--space-4);
  background: var(--paper-glass);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

/* -- talk wall: reviews --------------------------------------------- */
.talk-wall { display: grid; gap: var(--space-3); }

@media (min-width: 768px) {
  .talk-wall { grid-template-columns: repeat(3, 1fr); }
  .talk-wall > :first-child { grid-column: span 2; }
}

/* deliberately tight: five quotes should read as a wall of proof, not as five
   more sections to scroll past */
.talk {
  border-radius: var(--radius-2);
  padding: clamp(1.15rem, 2vw, 1.6rem);
  display: grid;
  gap: var(--space-3);
  align-content: start;
}

.talk--violet { background: var(--tint-violet); }
.talk--blue { background: var(--tint-blue); }
.talk--sand { background: var(--sand); }
.talk--line { box-shadow: inset 0 0 0 1px var(--line); background: var(--paper); }

.talk blockquote { color: var(--ink); font-size: var(--size--1); line-height: 1.5; text-wrap: pretty; }

.talk--violet blockquote { font-size: var(--size-0); max-width: 52ch; }

.talk__who { display: flex; align-items: center; gap: var(--space-3); margin-block-start: auto; }

.talk__ava {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--line-strong);
  font-family: var(--font-display);
  font-stretch: 114%;
  font-weight: 800;
  font-size: 0.95rem;
  color: var(--ocean);
  flex-shrink: 0;
}

.talk__who b { display: block; font-size: var(--size--1); line-height: 1.3; }

.talk__who span { font-size: 0.72rem; color: var(--ink-70); }

.talk__stars { display: inline-flex; gap: 2px; }

.talk__stars .icon { width: 0.9rem; height: 0.9rem; color: #e8a33d; fill: #e8a33d; }

.talk--summary { text-align: center; justify-items: center; align-content: center; gap: var(--space-2); }

.talk__g {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  box-shadow: var(--shadow-1), inset 0 0 0 1px var(--line);
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.4rem;
  color: var(--ocean);
  background: var(--paper);
}

.talk__score {
  font-family: var(--font-display);
  font-stretch: 116%;
  font-weight: 800;
  font-size: var(--size-3);
  line-height: 1;
}

.talk--summary p { color: var(--ink-70); font-size: var(--size--1); }

/* -- v4 polish: case metrics never overflow, labels compact -------- */
/* auto-fit, not three fixed columns: two of the eight cases report no numbers
   and carry two facts instead, which left an empty third column */
.case-card__metrics {
  grid-template-columns: repeat(auto-fit, minmax(5rem, 1fr));
  justify-content: stretch;
  width: 100%;
  gap: var(--space-4) var(--space-3);
}

/* the value scale was set when a card was 46rem wide. At 24rem it ran two
   metrics into each other — "2.5×" and "#1" printed as one word. */
.case-card__metrics .metric__value {
  font-size: var(--size-1);
  line-height: 1.15;
  overflow-wrap: break-word;
}

.case-card__metrics .metric__label {
  font-size: 0.7rem;
  line-height: 1.35;
}

.case-card { padding: clamp(1.6rem, 3vw, 2.5rem); }

/* ============================================================
   Service pages (seo-services.html and the siblings to come)
   ============================================================ */

/* -- hero ------------------------------------------------------------- */
.svc-hero__grid { display: grid; gap: var(--space-7); align-items: center; }

/* grid children default to min-width: auto — one wide child (the query lane)
   is enough to push the whole page sideways on a phone */
.svc-hero__grid > * { min-width: 0; }

@media (min-width: 900px) {
  /* the media column now outweighs the text column: a service page opens on a
     picture of the work, not on a thumbnail beside a paragraph */
  .svc-hero__grid { grid-template-columns: 1fr 1.05fr; gap: var(--space-8); }
}

/* one step down from the home hero: this column is half the width, and
   --size-5 here broke the headline into five two-word lines */
.svc-hero h1 { font-size: var(--size-4); max-width: 18ch; }

.svc-hero .section-head__eyebrow { margin-block: var(--space-5) var(--space-4); }

/* -- hero collage ------------------------------------------------------ */
.svc-hero__collage {
  position: relative;
  isolation: isolate;
}

/* a soft ocean wash behind the plates so they sit in air rather than on paper */
.svc-hero__collage::before {
  content: "";
  position: absolute;
  inset: -12% -8% -8% -12%;
  z-index: 0;
  background: radial-gradient(58% 58% at 72% 26%, rgba(27, 102, 201, 0.16), transparent 72%);
  pointer-events: none;
}

.svc-hero__shot { position: relative; z-index: 1; display: block; }

.svc-hero__shot--main { aspect-ratio: 4 / 3; box-shadow: var(--shadow-2); }

.svc-hero__chip {
  position: absolute;
  z-index: 2;
  top: 6%;
  right: -4%;
}

@media (min-width: 900px) {
  /* the main plate goes portrait and the second shot hangs off its lower-left
     corner — the overlap is what makes it read as depth instead of a grid */
  .svc-hero__collage { padding-block-end: 14%; padding-inline-start: 10%; }

  .svc-hero__shot--main { aspect-ratio: 4 / 4.4; }

  .svc-hero__shot--inset {
    position: absolute;
    z-index: 2;
    left: 0;
    bottom: 0;
    width: 52%;
    aspect-ratio: 4 / 3;
    /* a paper edge, not a border: keeps the radius and reads as a print stacked
       on top of another print */
    box-shadow: 0 0 0 6px var(--paper), var(--shadow-3);
  }
}

@media (max-width: 899px) {
  /* one photo on a phone. Stacked plates at this width are just clutter. */
  .svc-hero__shot--inset { display: none; }
  .svc-hero__chip { right: 4%; }
}

/* -- where it stalls --------------------------------------------------- */
.stall-grid { display: grid; gap: var(--space-5); }

@media (min-width: 768px) { .stall-grid { grid-template-columns: repeat(3, 1fr); } }

.stall__idx {
  font-family: var(--font-display);
  font-stretch: 116%;
  font-weight: 800;
  font-size: var(--size-2);
  line-height: 1;
  color: transparent;
  -webkit-text-stroke: 1.2px var(--ink-30);
}

/* -- tool marks -------------------------------------------------------- */
.toolwall { display: flex; flex-wrap: wrap; gap: var(--space-3); }

.tool {
  display: inline-flex;
  align-items: center;
  gap: 0.6em;
  padding: 0.7em 1.1em;
  border-radius: var(--radius-pill);
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--line-strong);
  font-size: var(--size--1);
  font-weight: 500;
  color: var(--ink-70);
  transition: color var(--dur-1) var(--ease-out), box-shadow var(--dur-2) var(--ease-out);
}

.tool:hover { color: var(--ink); box-shadow: inset 0 0 0 1px var(--ink-30); }

.tool .brand-icon { width: 1.15rem; height: 1.15rem; color: var(--ocean); }

.toolwall__also { margin-block-start: var(--space-5); color: var(--ink-70); }

/* a long capability wall: same chip, tighter, so sixteen of them still scan */
.toolwall--dense { gap: 0.45rem; }

.toolwall--dense .tool { padding: 0.5em 0.85em; gap: 0.45em; font-size: 0.8rem; }

.toolwall--dense .brand-icon { width: 1rem; height: 1rem; }

/* -- «How we work»: six steps on a compass rose --------------------------
   Replaces five hairline rows of three paragraphs each: accurate, and a wall
   of text. Same six-step story, one step at a time.

   THREE STATES, one skeleton:
   • no JS / under 360px — the stacked list at the top of this block. Every
     panel visible, in order, nothing behind a tap.
   • phone and tablet — .hiw--wheel in one column: the rose on top, the panel
     under it. The ring carries numbers only; the step's name is hidden (it is
     still the tab's accessible name) because «DISCOVERY» cannot be legible
     inside a 58px circle, and the panel right below says it anyway.
   • >=1024px — two columns, rose beside the panel, names back in the ring.

   Geometry is three custom properties: --n (node count, set in the markup),
   --wheel (the rose's box) and --r (ring radius, derived). A seventh step
   needs no CSS change. Node sizes are deliberately close to the block this
   was modelled on: 102px circles read as pushbuttons next to the type. */
.hiw { display: grid; gap: var(--space-6); }

/* the rose stays out of the document until JS confirms it works: a ring of
   buttons that switches nothing is worse than no ring */
.hiw__rose { display: none; }

/* -- the stacked list: the base state, and the fallback everywhere --------- */
.hiw__panels { display: grid; border-block-start: 1px solid var(--line); }

.hiw__panel {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-2) var(--space-5);
  padding-block: clamp(1.4rem, 2.6vh, 2rem);
  border-block-end: 1px solid var(--line);
}

/* ghost contour numeral, the house numbering */
.hiw__panel-num {
  font-family: var(--font-display);
  font-stretch: 116%;
  font-weight: 800;
  font-size: var(--size-3);
  line-height: 0.9;
  color: transparent;
  -webkit-text-stroke: 1.2px var(--ink-30);
  font-variant-numeric: tabular-nums;
}

.hiw__panel-body { display: grid; gap: var(--space-2); }

.hiw__panel h3 { font-size: var(--size-1); }

.hiw__panel p { color: var(--ink-70); font-size: var(--size--1); max-width: 62ch; }

.hiw__out b { color: var(--ink); }

.hiw__panel .roles { margin-block-start: var(--space-2); }

/* -- wheel mode ----------------------------------------------------------- */
.hiw--wheel {
  --wheel: min(84vw, 21rem);
  --r: calc(var(--wheel) * 0.375);
  --node: min(15.5vw, 3.9rem);
  gap: var(--space-5);
  justify-items: center;
}

.hiw--wheel .hiw__rose {
  display: block;
  position: relative;
  width: var(--wheel);
  height: var(--wheel);
}

.hiw__ring { position: absolute; inset: 0; width: 100%; height: 100%; }

/* the ring the steps sit on: dotted, so it reads as a bearing scale rather
   than a border drawn around a widget */
.hiw__ring-dots {
  fill: none;
  stroke: var(--ink-30);
  stroke-width: 0.9;
  stroke-dasharray: 0.9 5.2;
  stroke-linecap: round;
}

/* progress: pathLength=100 turns the dash maths into plain percentages, so
   --p is the only number JS has to write */
.hiw__ring-arc {
  fill: none;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-dasharray: 100;
  stroke-dashoffset: calc(100 - var(--p, 16.66));
  transform: rotate(-90deg);
  transform-origin: 50% 50%;
}

html:not(.no-motion) .hiw__ring-arc {
  transition: stroke-dashoffset var(--dur-3) var(--ease-out);
}

/* the needle pivots on the hub and points at the live step. Its base sits
   under the hub, so only the tip shows: a bearing, not an arrow. */
.hiw__needle {
  position: absolute;
  left: 50%;
  bottom: 50%;
  z-index: 1;
  width: 0.9rem;
  height: calc(var(--r) - var(--node) * 0.42);
  margin-inline-start: -0.45rem;
  transform-origin: 50% 100%;
  transform: rotate(var(--needle, 0deg));
  background: linear-gradient(180deg, var(--wave), var(--ocean));
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
  opacity: 0.85;
}

html:not(.no-motion) .hiw__needle {
  transition: transform var(--dur-3) var(--ease-spring);
}

.hiw__hub {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 2;
  transform: translate(-50%, -50%);
  width: min(23vw, 5.6rem);
  aspect-ratio: 1;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 0.1rem;
  border-radius: 50%;
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--line), 0 0 0 0.4rem var(--paper), var(--shadow-2);
}

.hiw__hub-num {
  font-family: var(--font-display);
  font-stretch: 116%;
  font-weight: 800;
  font-size: var(--size-2);
  line-height: 1;
  letter-spacing: var(--track-tight);
  background: var(--grad-metric);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  font-variant-numeric: tabular-nums;
}

.hiw__hub .mono-label { font-size: 0.62rem; }

/* -- the six steps -------------------------------------------------------- */
.hiw__node {
  /* placement kept in a variable so hover and the live state can add scale
     without restating the whole rotate-translate-rotate chain */
  --place: rotate(calc(var(--i) * (360deg / var(--n))))
           translateY(calc(var(--r) * -1))
           rotate(calc(var(--i) * (-360deg / var(--n))));
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 1;
  width: var(--node);
  height: var(--node);
  /* never under the 44px finger: the ring shrinks with the viewport, the
     target does not */
  min-width: 2.75rem;
  min-height: 2.75rem;
  margin: calc(var(--node) / -2);
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 0.05rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--line-strong), var(--shadow-1);
  color: var(--ink-70);
  text-align: center;
  cursor: pointer;
  transform: var(--place);
  isolation: isolate;
}

/* the live step fills with the deep gradient. It rides a pseudo-element
   because background-image cannot cross-fade, and it is the deep gradient
   because white on --wave alone would not clear AA at this size. */
.hiw__node::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: 0;
  border-radius: 50%;
  background: var(--grad-deep);
  opacity: 0;
}

html:not(.no-motion) .hiw__node {
  transition: transform var(--dur-2) var(--ease-spring),
              box-shadow var(--dur-2) var(--ease-out),
              color var(--dur-2) var(--ease-out);
}

html:not(.no-motion) .hiw__node::before { transition: opacity var(--dur-2) var(--ease-out); }

.hiw__node-num {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
}

/* the name is set in the body face, not the display one: Archivo Expanded is
   what forced a 102px circle to fit nine letters */
.hiw__node-name {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.7rem;
  line-height: 1.05;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

/* numbers-only ring below the desktop layout. Hidden, not removed: this span
   is the accessible name of the tab. */
@media (max-width: 1023px) {
  .hiw--wheel .hiw__node-name {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
}

@media (hover: hover) {
  .hiw__node:hover {
    transform: var(--place) scale(1.05);
    color: var(--ink);
    box-shadow: inset 0 0 0 1px var(--ink-30), var(--shadow-2);
  }

  .hiw__node.is-active:hover { color: var(--paper); }
}

.hiw__node:focus-visible { outline: 2px solid var(--ocean); outline-offset: 4px; }

.hiw__node.is-active {
  transform: var(--place) scale(1.06);
  color: var(--paper);
  box-shadow: 0 12px 26px -12px rgba(27, 102, 201, 0.6);
}

.hiw__node.is-active::before { opacity: 1; }

/* -- the panel side ------------------------------------------------------- */
.hiw--wheel .hiw__panels {
  border-block-start: 0;
  width: 100%;
}

.hiw--wheel .hiw__panel {
  /* one cell for all six: the column keeps the height of the tallest panel,
     so stepping through them never shifts the page */
  grid-area: 1 / 1;
  align-items: start;
  align-content: center;
  gap: var(--space-3) var(--space-5);
  padding: clamp(1.35rem, 4vw, 2.25rem);
  border: 0;
  border-radius: var(--radius-2);
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--line), var(--shadow-2);
  opacity: 0;
  visibility: hidden;
}

.hiw--wheel .hiw__panel.is-active { opacity: 1; visibility: visible; }

.hiw.is-teleport .hiw__panel,
.hiw.is-teleport .hiw__node,
.hiw.is-teleport .hiw__node::before,
.hiw.is-teleport .hiw__needle,
.hiw.is-teleport .hiw__ring-arc { transition: none; }

/* visibility waits out the fade on the way off, and flips immediately on the
   way in — hidden panels must leave the accessibility tree, but they still
   have to hold the column's height */
html:not(.no-motion) .hiw--wheel .hiw__panel {
  transform: translateY(10px);
  transition: opacity var(--dur-2) var(--ease-out),
              transform var(--dur-3) var(--ease-out),
              visibility 0s linear var(--dur-2);
}

html:not(.no-motion) .hiw--wheel .hiw__panel.is-active {
  transform: none;
  transition-delay: 0s;
}

/* on a phone the ghost numeral is the third copy of the same digit (ring, hub,
   card) and it costs the text a whole column of width */
@media (max-width: 1023px) {
  .hiw--wheel .hiw__panel { grid-template-columns: minmax(0, 1fr); }

  .hiw--wheel .hiw__panel-num { display: none; }
}

/* -- desktop: two columns, and the names come back ------------------------ */
@media (min-width: 1024px) {
  .hiw--wheel {
    --wheel: clamp(20rem, 29vw, 28rem);
    --node: clamp(4.4rem, 6vw, 5.4rem);
    grid-template-columns: auto minmax(0, 1fr);
    gap: clamp(2rem, 4vw, 3.5rem);
    align-items: center;
    justify-items: stretch;
  }

  .hiw__hub { width: clamp(5.6rem, 7vw, 6.8rem); }

  .hiw__node-num { font-size: 0.64rem; }

  .hiw__node-name { font-size: 0.74rem; }

  .hiw--wheel .hiw__panel { gap: var(--space-3) var(--space-6); }

  .hiw--wheel .hiw__panel-num { font-size: var(--size-4); -webkit-text-stroke-width: 1.5px; }

  .hiw--wheel .hiw__panel h3 { font-size: var(--size-2); }

  .hiw--wheel .hiw__panel p { font-size: var(--size-0); max-width: 46ch; }

  .hiw--wheel .hiw__out { font-size: var(--size--1); }
}

/* who touches this step — the point of the whole section */
.roles { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-block-start: var(--space-4); }

.role {
  padding: 0.35em 0.85em;
  border-radius: var(--radius-pill);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: var(--track-mono);
  text-transform: uppercase;
}

.role--seo { background: var(--tint-blue); color: var(--ocean); }
.role--dev { background: var(--tint-violet); color: #5b4ee0; }
.role--design { background: var(--sand); color: #8a6a2f; }
.role--pr { background: var(--foam); color: var(--ink-70); }
.role--data { background: var(--mist); color: var(--ink-70); }

/* -- explainers: alternating image/text splits --------------------------- */
/* Same skeleton three times over, sides alternating — ready for a foreach when
   this moves to PHP. The picture is the point of the block, so it takes the
   larger half rather than sitting beside the copy as a thumbnail. */
.explain-set { display: grid; gap: var(--space-9); }

.explain { display: grid; gap: var(--space-6); align-items: center; }

.explain > * { min-width: 0; }

@media (min-width: 900px) {
  .explain { grid-template-columns: 1.05fr 1fr; gap: var(--space-8); }

  /* the flip is visual only: source order stays image-then-text so the reading
     order is identical on every block for screen readers and on mobile */
  .explain--flip .explain__media { order: 2; }
}

.explain__media {
  aspect-ratio: 4 / 3;
  box-shadow: var(--shadow-2);
}

.explain__body { display: grid; gap: var(--space-4); align-content: start; }

.explain__body h3 { font-size: var(--size-2); max-width: 20ch; }

.explain__lead { color: var(--ink-70); max-width: 48ch; }

.explain .build-list { margin-block-start: var(--space-2); }

.build-split__media { aspect-ratio: 4 / 3; }

/* service tag on a case card */
.chip--service { background: var(--ink); color: #fff; }

/* -- case filters on two axes -------------------------------------------- */
.filters-2 { display: grid; gap: var(--space-4); }

.filters__axis { color: var(--ink-50); align-self: center; margin-inline-end: var(--space-2); }

.filters__empty { margin-block-start: var(--space-6); }

.filters__empty[hidden] { display: none; }

/* -- who answers the form ------------------------------------------------ */
/* A face next to the submit button: the visitor sees who picks the request up
   before they hand over their details. */
.who {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4);
  border-radius: var(--radius-2);
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--line);
  max-width: 24rem;
}

.who__photo {
  width: 72px;
  height: 72px;
  flex-shrink: 0;
  border-radius: 50%;
  overflow: clip;
}

.who__photo img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 28%; }

.who__id { display: grid; gap: 0.15em; min-width: 0; }

.who__id b { font-size: var(--size-0); line-height: 1.2; }

.who__id > span:last-child { font-size: var(--size--1); color: var(--ink-70); }

/* -- file dropzone -------------------------------------------------------- */
/* The input stays in the tab order and keeps its accessible name; the label is
   the visible target, so a keyboard user gets the same control as a mouse. */
/* `.field input { width: 100% }` outranks a bare class, and the input then
   measured 335px at its static position — 4px of page overflow. */
.field .dropzone__input,
.dropzone__input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.dropzone {
  display: grid;
  justify-items: center;
  gap: 0.3rem;
  /* it is a direct child <label> of .field, which is styled as a mono uppercase
     caption — undo that, this one is a surface, not a caption */
  font-family: var(--font-body);
  text-transform: none;
  letter-spacing: normal;
  color: var(--ink);
  /* an optional field should not be the tallest thing on the form */
  padding: var(--space-4);
  border-radius: var(--radius-1);
  border: 1.5px dashed var(--line-strong);
  background: var(--paper);
  text-align: center;
  cursor: pointer;
  transition:
    border-color var(--dur-2) var(--ease-out),
    background-color var(--dur-2) var(--ease-out),
    transform var(--dur-2) var(--ease-spring);
}

.dropzone:hover { border-color: var(--ocean); background: var(--tint-blue); }

.dropzone.is-over {
  border-style: solid;
  border-color: var(--ocean);
  background: var(--tint-blue);
  transform: scale(1.015);
}

.dropzone__input:focus-visible + .dropzone { outline: 2px solid var(--ocean); outline-offset: 3px; }

.dropzone__icon {
  width: 1.75rem;
  height: 1.75rem;
  color: var(--ocean);
  transition: transform var(--dur-2) var(--ease-spring);
}

.dropzone:hover .dropzone__icon,
.dropzone.is-over .dropzone__icon { transform: translateY(-4px); }

.dropzone__text { font-size: var(--size--1); color: var(--ink-70); }
.dropzone__text b { color: var(--ink); }
.dropzone__hint { color: var(--ink-50); font-size: 0.68rem; }

.dropzone__list { display: grid; gap: 0.5rem; list-style: none; margin-block-start: var(--space-3); }
.dropzone__list:empty { display: none; }

.dropzone__file {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--space-3);
  padding: 0.5rem 0.6rem 0.5rem 0.8rem;
  border-radius: var(--radius-1);
  background: var(--mist);
  font-size: var(--size--1);
}

html:not(.no-motion) .dropzone__file { animation: file-in 340ms var(--ease-out) both; }

@keyframes file-in {
  from { opacity: 0; transform: translateY(-8px) scale(0.98); }
  to { opacity: 1; transform: none; }
}

.dropzone__file .icon { width: 1rem; height: 1rem; color: var(--ocean); }

.dropzone__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.dropzone__size {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  color: var(--ink-50);
  font-variant-numeric: tabular-nums;
}

.dropzone__x {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  color: var(--ink-50);
  transition: background-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
}

.dropzone__x:hover { background: var(--paper); color: var(--state-bad); }
.dropzone__x .icon { width: 0.9rem; height: 0.9rem; color: currentColor; }

.dropzone__file.is-bad { background: var(--state-bad-bg); color: var(--state-bad); }
.dropzone__file.is-bad .icon { color: var(--state-bad); }
.dropzone__file.is-bad .dropzone__size { color: var(--state-bad); }

/* the lead card (GEO) sits on a foam wash so the deck has a clear first read */
.svc-card--lead { background: linear-gradient(135deg, var(--foam), var(--paper) 72%); }

.svc-card__num {
  position: absolute;
  top: clamp(1.1rem, 2vw, 1.5rem);
  right: clamp(1.25rem, 2.2vw, 1.9rem);
  font-family: var(--font-display);
  font-stretch: 116%;
  font-weight: 800;
  font-size: var(--size-2);
  line-height: 1;
  color: transparent;
  -webkit-text-stroke: 1.2px var(--ink-30);
  transition: -webkit-text-stroke-color var(--dur-2) var(--ease-out);
}

.svc-card__meta { color: var(--ocean); }

.svc-card__title {
  font-family: var(--font-display);
  font-stretch: 114%;
  font-weight: 800;
  text-transform: uppercase;
  font-size: var(--size-2);
  line-height: 1.05;
  letter-spacing: var(--track-tight);
  max-width: 16ch;   /* keeps clear of the ghost numeral */
}

.svc-card__intro { color: var(--ink-70); max-width: 52ch; }

.svc-card__list { display: grid; gap: 0.55rem; list-style: none; }

.svc-card__list li {
  position: relative;
  padding-inline-start: 1.4em;
  font-size: var(--size--1);
  color: var(--ink-70);
}

.svc-card__list li::before {
  content: "";
  position: absolute;
  left: 0.35em;
  top: 0.62em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ocean);
}

.svc-card__list code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  padding: 0.1em 0.4em;
  border-radius: 4px;
  background: var(--mist);
}

.svc-card__engines {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block-start: var(--space-2);
}

/* -- FAQ: sticky intro + question column --------------------------------- */
.faq-cols { display: grid; gap: var(--space-6); align-items: start; }

@media (min-width: 1024px) {
  .faq-cols { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); gap: var(--space-8); }
  /* align-items: start above is load-bearing: a stretched column never sticks */
  .faq-cols__intro { position: sticky; top: calc(var(--header-h) + 2rem); }
}

.faq-cols__intro { display: grid; gap: var(--space-4); justify-items: start; }

.faq-cols__intro h2 { max-width: 14ch; }

.faq-cols__intro .muted { max-width: 34ch; }

/* ============================================================
   v6: mobile (11.08.2026)
   One layer at the very end of the file on purpose. This stylesheet already
   carries several selectors declared two or three times at equal specificity
   (.work-lente, .case-card, .case-card__metrics), and the live
   one is always the last. Keeping every phone rule here means "last wins"
   comes for free instead of being hunted for.
   ============================================================ */

@media (max-width: 767px) {
  /* -- services: a dashed rail in the left margin, so the boat has somewhere
     to sail on a phone. Without it the hull had no waypoints before the
     process block and appeared at the very end out of nowhere. ---------- */
  .svc-index {
    position: relative;
    padding-inline-start: 2.9rem;
  }

  .svc-index::before {
    content: "";
    position: absolute;
    left: 1.1rem;
    top: 0.5rem;
    bottom: 0.5rem;
    border-inline-start: 2px dashed var(--line-strong);
  }

  .svc-row::before {
    content: "";
    position: absolute;
    left: -2.25rem;
    top: 1.7rem;
    width: 11px;
    height: 11px;
    border-radius: 50%;
    background: var(--paper);
    box-shadow: inset 0 0 0 2px var(--ink-30);
    transition: background-color var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out);
  }

  .svc-row.is-near::before {
    background: var(--ocean);
    box-shadow: 0 0 0 4px rgba(27, 102, 201, 0.16);
  }
}

@media (max-width: 639px) {
  /* -- hero ------------------------------------------------------------ */
  .hero__actions {
    display: grid;
    gap: var(--space-4);
    justify-items: stretch;
  }

  .hero__actions .btn--primary {
    width: 100%;
    justify-content: center;
  }

  .hero__actions .link-cta {
    justify-self: start;
    min-height: 44px;
  }

  /* the band drops below the photo: three cells cannot fit a 320px column,
     and pinned to the top edge it would sit right on the faces */
  /* back onto the photo as a frosted plate — stacked below it the three rows
     ate 150px of a 390px screen. Two columns, the third cell taking the whole
     second row so the plate stays a tidy rectangle. */
  .hero__media .hero__band {
    position: absolute;
    inset-inline: 0;
    top: 0;
    grid-auto-flow: row;
    grid-template-columns: 1fr 1fr;
    justify-content: stretch;
    gap: 0.55rem var(--space-4);
    padding: 0.75rem var(--space-4);
    background: var(--paper-glass);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
    border-start-start-radius: var(--radius-2);
    border-start-end-radius: var(--radius-2);
    box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.55);
  }

  .hero__band li {
    display: grid;
    gap: 0.1em;
    padding-block: 0;
    border-block-start: 0;
    min-width: 0;
  }

  .hero__band li:nth-child(3) { grid-column: 1 / -1; }
  .hero__band li + li::before { content: none; }

  .hero__band b {
    font-size: var(--size-0);
    line-height: 1.15;
  }

  .hero__band span {
    text-align: start;
    font-size: 0.6rem;
  }

  /* the proof strip stacks, so a shared baseline has nothing left to share:
     the alignment device rotates 90 degrees into a shared left measure */
  .proof {
    grid-template-columns: minmax(4.25rem, auto) 1fr;
    column-gap: var(--space-4);
    justify-items: stretch;
  }

  .proof__num {
    grid-column: 1;
    grid-row: 1 / span 2;
    align-self: start;
    font-size: var(--size-3);
    font-variant-numeric: tabular-nums;
  }

  .proof > :not(.proof__num) { grid-column: 2; }

  /* A value that is a phrase ("3 weeks", "In the browser") does not fit the
     4.25rem numeral column: it was chopped mid-word. Those rows stack instead,
     so the phrase gets the full width and wraps at spaces. */
  .proof:has(.proof__num--word) { grid-template-columns: minmax(0, 1fr); }
  .proof:has(.proof__num--word) > * { grid-column: 1; }
  .proof:has(.proof__num--word) .proof__num { grid-row: auto; }

  .proof + .proof {
    border-block-start: 1px solid var(--line);
    padding-block-start: var(--space-5);
  }

  /* -- services -------------------------------------------------------- */
  .svc-row__meta .chip {
    font-size: 0.72rem;
    padding: 0.35em 0.7em;
  }

  /* -- cases ----------------------------------------------------------- */
  /* 78vw, not 90: at 90vw the next card starts past the screen edge and the
     rail shows no peek at all, which is the only thing on a phone that says
     "this scrolls" */
  .work-lente {
    grid-auto-columns: min(78vw, 20rem);
    gap: var(--space-4);
  }

  /* auto-fit, or the cards that carry two metrics leave an empty column */
  .case-card__metrics {
    grid-template-columns: repeat(auto-fit, minmax(4.5rem, 1fr));
    gap: var(--space-3);
  }

  .case-card__metrics .metric__value { font-size: var(--size-1); }
  .case-card__ghost { font-size: clamp(2rem, 11vw, 2.5rem); }

  /* -- why bento ------------------------------------------------------- */
  /* the photo tile keeps a min-height declared after the phone relaxation,
     and its image is absolutely positioned — without a ratio it collapses to
     a 240px letterbox of a group shot */
  .tile--photo {
    min-height: 0;
    aspect-ratio: 16 / 10;
  }

  /* -- crew ------------------------------------------------------------ */
  /* the CEO tile no longer spans: with seven tiles in two columns the span left
     a blank half beside Polina. Six 4:5 tiles now fill three whole rows. */
  .crew-bento > :nth-child(1) { grid-column: auto; }

  /* in a half-width tile the plate wrapped to three lines and covered the
     face it was labelling */
  .crew-tile__name { font-size: 0.82rem; }
  .crew-tile__role { font-size: 0.62rem; }
  .crew-tile__id {
    left: var(--space-2);
    bottom: var(--space-2);
    padding: 0.45em 0.6em;
    max-width: calc(100% - 1rem);
  }

  /* -- cta ------------------------------------------------------------- */
  .berth {
    width: 208px;
    height: 112px;
  }

  .cta-band .voyage-dock {
    left: 100px;
    width: 96px;
    height: 96px;
  }

  /* the upload stays on phones — people do send a brief from a handset, and a
     field that exists on desktop but vanishes here is a missing feature, not a
     simplification. Compacted instead of removed. */
  .cta-form .dropzone {
    padding-block: var(--space-4);
    gap: var(--space-2);
  }

  .checkline input {
    width: 1.35rem;
    height: 1.35rem;
  }

  /* -- touch affordances ----------------------------------------------- */
  /* glass is a compositing layer re-blurred every scroll frame; the header
     pill keeps it, everything else goes solid */
  .crew-tile__id,
  .tile--photo .chip {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    background: rgba(255, 255, 255, 0.92);
  }
}

@media (hover: none) {
  /* seven crew tiles are links whose only affordance appears on hover */
  .crew-tile__go { opacity: 1; translate: 0 0; }
  .svc-row:active { background: var(--mist); }
}

/* -- touch targets ------------------------------------------------
   Audit at 390px found 25 hit areas under 40px: the menu's service
   links sat at 33px, the header pill at 34px and inline arrow-links at
   20px. Grown with padding rather than height so nothing moves
   visually — the negative margin gives the finger room back. */
@media (hover: none) {
  .mm-link,
  .mm-services a {
    min-height: 44px;
    display: flex;
    align-items: center;
  }

  .btn--sm { min-height: 44px; }

  .action,
  .mm-contact a,
  .footer a {
    padding-block: 0.7rem;
    margin-block: -0.7rem;
  }

  /* the consent box shipped at 15x22 — the smallest control on the page */
  .checkline { min-height: 44px; align-items: center; }
  .checkline input[type="checkbox"] { width: 1.35rem; height: 1.35rem; flex: none; }
}

/* ============================================================
   v7: services zigzag + seo-services v2 (industries, hdeck, sim)
   Appended after the v6 mobile layer on purpose — several selectors
   in this file are declared more than once at equal specificity and
   the LAST one wins, so anything below may override v6. Keep every
   new phone rule inside this layer for the same reason.
   ============================================================ */

/* -- services.html: anchor chips under the page hero ------------------- */
.svc-anchors {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block-start: var(--space-5);
}

.svc-anchors .chip { transition: color var(--dur-1) var(--ease-out), box-shadow var(--dur-1) var(--ease-out); }

.svc-anchors .chip:hover,
.svc-anchors .chip:focus-visible {
  color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink-30);
}

/* -- services.html: the zigzag split cards ----------------------------- */
/* .explain--svc rides the .explain/.explain--flip skeleton; the page-level
   h2 needs more room than the seo-page h3 the base pattern was sized for */
.explain--svc { scroll-margin-top: calc(var(--header-h) + 1rem); }

.explain--svc h2 { font-size: var(--size-3); max-width: 14ch; }

.explain__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.explain__actions {
  display: flex;
  align-items: center;
  column-gap: var(--space-5);
  row-gap: var(--space-3);
  flex-wrap: wrap;
}

/* -- seo-services hero: motion on the first screen ---------------------
   Three quiet moving parts instead of one static collage: the ocean wash
   behind the plates breathes, the plates drift apart on scroll (motion.js),
   and a lane of real buyer queries slides under the buttons. All gated. */
html:not(.no-motion) .svc-hero__collage::before {
  animation: hero-wash 26s var(--ease-inout) infinite alternate;
}

/* a second, violet counter-blob so the wash has depth rather than one blur */
.svc-hero__collage::after {
  content: "";
  position: absolute;
  inset: 6% -14% -14% 2%;
  z-index: 0;
  background: radial-gradient(50% 50% at 32% 74%, rgba(124, 111, 242, 0.14), transparent 70%);
  pointer-events: none;
}

html:not(.no-motion) .svc-hero__collage::after {
  animation: hero-wash-2 34s var(--ease-inout) infinite alternate;
}

@keyframes hero-wash {
  from { transform: translate3d(-2%, -1%, 0) scale(1); }
  to   { transform: translate3d(3%, 2%, 0) scale(1.09); }
}

@keyframes hero-wash-2 {
  from { transform: translate3d(2%, 2%, 0) scale(1.05); }
  to   { transform: translate3d(-3%, -2%, 0) scale(1); }
}

/* same accent as the home hero: the last phrase carries the gradient */
.svc-hero h1 em {
  font-style: normal;
  display: block;
  width: fit-content;
  background: var(--grad-metric);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* -- seo-services: industries, two drifting lanes ----------------------
   Built on the .marquee component (marquee.js clones the track and derives
   the duration from its width). The second lane runs backwards so the block
   reads as a current, not a conveyor belt; both pause on hover so a card
   can actually be read, and no-motion wraps them into a static grid. */
.ind-lanes {
  display: grid;
  gap: var(--space-4);
  margin-block-start: var(--space-6);
}

/* .marquee .ind-lane__track, not .ind-lane__track alone: the track is a <ul>
   and base.css resets ul[class] { padding: 0 } at a higher specificity —
   that reset ate the end padding, so the last card and the clone's first card
   sat flush and the lane looked "glued" once per loop */
.marquee .ind-lane__track {
  gap: var(--space-4);
  padding-inline-end: var(--space-4);
  padding-block: 2px; /* room for the hover lift, otherwise it clips */
  margin: 0;
  list-style: none;
  align-items: stretch;
}

html:not(.no-motion) .ind-lane--back .marquee__track { animation-direction: reverse; }

/* the component only pauses on hover, which a touch screen never fires —
   holding a finger on a card (:active) stops it too, so a phone reader can
   catch one. Reduced-motion still gets the fully static grid. */
html:not(.no-motion) .ind-lane:active .marquee__track,
html:not(.no-motion) .ind-lane:focus-within .marquee__track { animation-play-state: paused; }

.ind-card {
  display: grid;
  align-content: start;
  gap: 0.35rem;
  width: min(19rem, 74vw);
  flex: none;
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-2);
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--line);
  white-space: normal;
  transition: box-shadow var(--dur-2) var(--ease-out), translate var(--dur-2) var(--ease-out);
}

.ind-card .icon {
  width: 1.6rem;
  height: 1.6rem;
  color: var(--ocean);
  stroke-width: 1.5;
  margin-block-end: var(--space-2);
}

.ind-card b { font-size: var(--size-0); font-weight: 600; }

.ind-card span { font-size: var(--size--1); color: var(--ink-70); }

@media (hover: hover) {
  .ind-card:hover { translate: 0 -3px; box-shadow: var(--shadow-2), inset 0 0 0 1px var(--line-strong); }
}

.ind-note { margin-block-start: var(--space-6); color: var(--ink-70); }

/* static fallback: the lanes become a plain wrapped grid of cards */
.no-motion .ind-lane__track { gap: var(--space-4); }

.no-motion .ind-card { width: min(19rem, 100%); }

/* -- seo-services: why choose us --------------------------------------
   Two roomy columns, not four narrow ones: at four across every card was a
   column of two-word lines. Cards match the page's paper+hairline system. */
.why-grid { display: grid; gap: var(--space-4); }

@media (min-width: 768px) { .why-grid { grid-template-columns: repeat(2, 1fr); } }

.why-card {
  display: grid;
  align-content: start;
  gap: var(--space-3);
  padding: clamp(1.75rem, 3.2vw, 2.75rem);
  border-radius: var(--radius-2);
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--line);
}

.why-card > .icon {
  width: 2.1rem;
  height: 2.1rem;
  color: var(--ocean);
  stroke-width: 1.5;
  margin-block-end: var(--space-2);
}

.why-card__num {
  font-family: var(--font-display);
  font-stretch: 116%;
  font-weight: 800;
  font-size: var(--size-4);
  line-height: 0.9;
  letter-spacing: var(--track-tight);
  background: var(--grad-metric);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  margin-block-end: var(--space-2);
}

.why-card h3 { font-size: var(--size-1); max-width: 20ch; }

.why-card p { color: var(--ink-70); max-width: 46ch; }

@media (hover: hover) {
  .why-card { transition: box-shadow var(--dur-2) var(--ease-out), translate var(--dur-2) var(--ease-out); }
  .why-card:hover { translate: 0 -3px; box-shadow: var(--shadow-2), inset 0 0 0 1px var(--line-strong); }
}

/* -- seo-services: additional services + platforms wall ---------------- */
.more-grid { display: grid; gap: var(--space-4); }

@media (min-width: 900px) { .more-grid { grid-template-columns: repeat(3, 1fr); } }

.more-card {
  display: grid;
  align-content: start;
  gap: var(--space-3);
  padding: clamp(1.5rem, 2.4vw, 2rem);
}

.more-card > .icon {
  width: 2rem;
  height: 2rem;
  color: var(--ocean);
  stroke-width: 1.5;
}

.more-card h3 { font-size: var(--size-1); }

.more-card p { font-size: var(--size--1); color: var(--ink-70); }

.more-card .action { margin-block-start: auto; }

@media (hover: hover) {
  .more-card { transition: translate var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out); }
  .more-card:hover { translate: 0 -4px; box-shadow: var(--shadow-2); }
}

.more-stack__label { margin-block: var(--space-7) var(--space-4); }

/* -- hdeck: the services deck ------------------------------------------
   Base state = a native horizontal rail (mirrors .work-lente): phones,
   no-JS, ?static=1 and reduced-motion all read this. hdeck.js adds
   .hdeck--pinned at >=1024 with motion and drives the row's transform. */
.hdeck__row {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: min(86vw, 24rem);
  gap: var(--space-4);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity; /* never mandatory — it reads as a jump */
  padding-inline: var(--bleed-inset);
  scroll-padding-inline: var(--bleed-inset);
  padding-block: var(--space-2) var(--space-5);
  scrollbar-width: none;
  cursor: grab;
}

.hdeck__row::-webkit-scrollbar { display: none; }

.hdeck__row.is-dragging { cursor: grabbing; scroll-snap-type: none; user-select: none; }

.hdeck__row > * { scroll-snap-align: start; }

@media (hover: hover) and (pointer: fine) { .hdeck__row { scroll-snap-type: none; } }

/* rail.js appends its progress bar after the row — give it the page gutter */
.hdeck__viewport > .rail-progress { margin-inline: var(--bleed-inset); }

.hdeck__panel {
  position: relative;
  display: grid;
  align-content: start;
  gap: var(--space-4);
  padding: clamp(1.5rem, 2.4vw, 2.1rem);
  border-radius: var(--radius-2);
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--line);
  min-height: 20rem;
}

.hdeck__chip {
  width: 52px;
  height: 52px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-1);
  background: var(--foam);
  color: var(--ocean);
}

.hdeck__chip .icon { width: 1.6rem; height: 1.6rem; stroke-width: 1.5; }

.hdeck__idx {
  /* ghost numeral — same recipe as .svc-card__num */
  position: absolute;
  top: 1.4rem;
  right: 1.5rem;
  font-family: var(--font-display);
  font-stretch: 116%;
  font-weight: 800;
  font-size: var(--size-2);
  line-height: 1;
  color: transparent;
  -webkit-text-stroke: 1.2px var(--ink-30);
}

.hdeck__panel h3 { font-size: var(--size-1); max-width: 14ch; }

.hdeck__intro { font-size: var(--size--1); color: var(--ink-70); }

.hdeck__list {
  display: grid;
  gap: 0.55rem;
  padding: 0;
  margin: 0;
  list-style: none;
  font-size: var(--size--1);
  color: var(--ink-70);
}

.hdeck__list li {
  padding-inline-start: 1.1rem;
  position: relative;
}

.hdeck__list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--ocean);
}

.hdeck__engines { display: flex; flex-wrap: wrap; gap: 0.45rem; }

.hdeck__engines .tool { padding: 0.4em 0.75em; font-size: 0.78rem; }

.hdeck__count { display: none; font-variant-numeric: tabular-nums; color: var(--ocean); }

.hdeck__meter { display: none; }

/* -- hdeck: pinned scene (class added by hdeck.js only) ---------------- */
.hdeck--pinned { padding-block: 0; }

.hdeck--pinned .hdeck__track { height: var(--hdeck-track, 320vh); }

.hdeck--pinned .hdeck__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100dvh;
  overflow: clip;
  display: grid;
  /* minmax(0, 1fr): an auto track would grow to the row's max-content width
     and the viewport would stop overflowing — maxShift collapses to 0 */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto 1fr auto;
  align-content: center;
  /* the whole scene has to fit an 800px laptop: pill + head + a card that
     grows with its copy + the meter. Tight top and bottom, and the head's
     block margin is halved inside the pin */
  padding-block: var(--header-h) var(--space-4);
}

.hdeck--pinned .section-head { margin-block-end: var(--space-4); }

.hdeck--pinned .hdeck__viewport { align-self: center; }

.hdeck--pinned .hdeck__row {
  overflow: visible;
  width: max-content;
  /* wider than the rail's cards: at 26vw the copy wrapped into a 547px card
     that no laptop screen could hold under the pill and the head */
  grid-auto-columns: clamp(22rem, 30vw, 28rem);
  cursor: default;
  will-change: transform;
  /* the stylesheet owns the transition; hdeck.js only retargets transform.
     .is-teleport kills it for measured jumps (init, resize, refresh). */
  transition: transform 340ms var(--ease-out);
}

.hdeck--pinned .hdeck__row.is-teleport { transition: none; }

/* grows with its copy: a fixed height clipped the longer service decks
   (477px of content in a 448px box on an 800px screen) */
.hdeck--pinned .hdeck__panel { height: auto; min-height: min(48vh, 26rem); }
.hdeck--pinned .hdeck__row { align-items: start; }

/* the pinned scene's vertical budget on an 800px laptop: pill 112 + head +
   card + meter. The title takes one line, the card sits a little tighter. */
.hdeck--pinned .section-head h2 { max-width: none; font-size: var(--size-3); }
.hdeck--pinned .hdeck__panel { padding: clamp(1.25rem, 2vw, 1.75rem); gap: var(--space-3); }
.hdeck--pinned .hdeck__chip { width: 44px; height: 44px; }
.hdeck--pinned .hdeck__chip .icon { width: 1.4rem; height: 1.4rem; }

.hdeck--pinned .hdeck__count { display: block; }

.hdeck--pinned .hdeck__meter {
  display: block;
  height: 3px;
  border-radius: var(--radius-pill);
  background: var(--line);
  overflow: hidden;
  margin-block-start: var(--space-5);
}

.hdeck--pinned .hdeck__meter i {
  display: block;
  height: 100%;
  width: calc(var(--progress, 0) * 100%);
  background: var(--grad-metric);
  border-radius: inherit;
}

/* touch: the audit rule from v6 — nothing tappable under 44px. */
@media (hover: none) {
  .svc-anchors .chip {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
}

/* ============================================================
   v8: course chart (06.09.2026)
   «Course to growth» on the home page, rebuilt as a stepper in the shape of
   «How we work» (Artem: interactive, the boat sails to the chosen stage, no
   scroll pin, convenient on desktop and phone). One swell, four stations on
   it, one card under it. The scroll-pinned deck it replaces is gone with
   route.js; the site-wide voyager dissolves over this block (boat.js) so the
   chart's own marker is the only hull on screen here.

   Base state — no JS, under 360px — is the stacked list: the chart is not
   in the document at all, and all four cards read in order. course.js adds
   .course--live from 360px up.
   ============================================================ */

/* the chart stays out of the document until JS confirms it works: a row of
   buttons that switches nothing is worse than no row */
.course__chart { display: none; }

/* -- the stacked list: the base state, and the fallback everywhere --------- */
.course__panels { display: grid; border-block-start: 1px solid var(--line); }

.course__panel {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-2) var(--space-5);
  padding-block: clamp(1.4rem, 2.6vh, 2rem);
  border-block-end: 1px solid var(--line);
}

/* ghost contour numeral, the house numbering */
.course__panel-num {
  font-family: var(--font-display);
  font-stretch: 116%;
  font-weight: 800;
  font-size: var(--size-3);
  line-height: 0.9;
  color: transparent;
  -webkit-text-stroke: 1.2px var(--ink-30);
  font-variant-numeric: tabular-nums;
}

.course__panel-body { display: grid; gap: var(--space-2); }

.course__panel h3 { font-size: var(--size-1); }

.course__panel p { color: var(--ink-70); font-size: var(--size--1); max-width: 62ch; }

/* stacked, the deliverables sit under the prose in the text column */
.course__panel-side {
  grid-column: 2;
  display: grid;
  gap: var(--space-3);
  align-content: start;
}

.course__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-2);
  font-size: var(--size--1);
  color: var(--ink-70);
}

.course__list li {
  position: relative;
  padding-inline-start: 1.1rem;
}

.course__list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--ocean);
}

.course__out { color: var(--ink-70); font-size: var(--size--1); }

.course__out b { color: var(--ink); }

/* -- live: the chart ------------------------------------------------------ */
.course--live { --node: clamp(2.75rem, 5.5vw, 3.4rem); }

/* The swell. Its HEIGHT is the only size knob: course.js rescales the path
   into the box, places the stations in percent of it and the marker in pixels
   of it, so a taller box is a taller wave and nothing else has to move. The
   bottom margin keeps the trough stations' names clear of the card; the top
   margin is headroom for the marker over a crest — it rides a node above the
   line plus its own height, and bobs 7px on top of that, so without it the
   sail touched the section head. */
.course--live .course__chart {
  display: block;
  position: relative;
  height: clamp(6.5rem, 13vw, 11rem);
  margin-block-start: calc(var(--node) * 0.35);
  margin-block-end: calc(var(--node) * 0.9);
}

.course__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.course__line {
  fill: none;
  stroke: var(--line-strong);
  stroke-width: 2;
  stroke-dasharray: 3 10;
  stroke-linecap: round;
}

/* pathLength="100" makes the dash units percent: --p is the drawn length.
   This only holds because course.js keeps the svg's viewBox equal to the
   box in pixels — never add vector-effect: non-scaling-stroke here, Chrome
   stops scaling the dash by pathLength under it. */
.course__progress {
  fill: none;
  stroke: url(#course-grad);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-dasharray: 100;
  stroke-dashoffset: calc(100 - var(--p, 10));
}

/* no transition on the dashoffset: --p is painted every frame from the same
   spring as the marker (course.js paintBoat), and a transition on top of a
   per-frame value would only put the line's tip behind the hull */

.course__stations { position: absolute; inset: 0; }

/* a station: a round button on the line, placed by course.js in percent of
   the chart box, centred on its point */
.course__station {
  position: absolute;
  left: var(--x, 0%);
  top: var(--y, 50%);
  translate: -50% -50%;
  width: var(--node);
  height: var(--node);
  min-width: 44px;
  min-height: 44px;
  display: grid;
  place-content: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--line-strong), var(--shadow-1);
  color: var(--ink-70);
  font-family: var(--font-mono);
  font-size: 0.82rem;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  isolation: isolate;
}

/* the live station fills with the deep gradient. It rides a pseudo-element
   because background-image cannot cross-fade, and it is the deep gradient
   because white on --wave alone would not clear AA at this size. */
.course__station::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: 0;
  border-radius: 50%;
  background: var(--grad-deep);
  opacity: 0;
}

html:not(.no-motion) .course__station {
  transition: scale var(--dur-2) var(--ease-spring),
              box-shadow var(--dur-2) var(--ease-out),
              color var(--dur-2) var(--ease-out);
}

html:not(.no-motion) .course__station::before { transition: opacity var(--dur-2) var(--ease-out); }

/* the name hangs under the node, in the body face: Archivo Expanded would
   need a far bigger circle to fit nine letters, and the node is the number */
.course__station-name {
  position: absolute;
  top: calc(100% + 0.45rem);
  left: 50%;
  translate: -50% 0;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.7rem;
  line-height: 1;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--ink-70);
}

html:not(.no-motion) .course__station-name { transition: color var(--dur-2) var(--ease-out); }

@media (hover: hover) {
  .course__station:hover {
    scale: 1.06;
    color: var(--ink);
    box-shadow: inset 0 0 0 1px var(--ink-30), var(--shadow-2);
  }

  .course__station.is-active:hover { color: var(--paper); }
}

.course__station:focus-visible { outline: 2px solid var(--ocean); outline-offset: 4px; }

.course__station.is-active {
  scale: 1.08;
  color: var(--paper);
  box-shadow: 0 12px 26px -12px rgba(27, 102, 201, 0.6);
}

.course__station.is-active::before { opacity: 1; }

.course__station.is-active .course__station-name { color: var(--ink); }

/* the chart's own hull. course.js writes --bx/--by (px in the box) and --tilt
   every frame of the spring, so there is NO transition on the transform: one
   on top of a per-frame value would only add lag. It floats ABOVE the
   station, never on it: the box's bottom centre is anchored 0.64 node above
   the point on the line and the hull turns about that anchor (transform-
   origin at the bottom centre, so a tilt swings the sails, not the keel).
   The hull arc ends at y 83 of the 84-unit box, i.e. at the box's bottom;
   the active node is scale 1.08, radius 0.54 node — that leaves ~0.1 node of
   water between hull and circle. Sitting on the line it covered the active
   number with its sails. */
.course__boat {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 2;
  pointer-events: none;
  width: clamp(2.6rem, 4vw, 3.4rem);
  aspect-ratio: 1;
  transform-origin: 50% 100%;
  transform: translate3d(var(--bx, 0px), calc(var(--by, 0px) - var(--node) * 0.64), 0) translate(-50%, -100%) rotate(var(--tilt, 0deg));
  filter: drop-shadow(0 6px 10px rgba(11, 27, 43, 0.14));
}

.course__boat-float { display: block; width: 100%; height: 100%; }

.course__boat-float svg { display: block; width: 100%; height: 100%; }

html:not(.no-motion) .course__boat-float { animation: boat-bob 4.6s var(--ease-inout) infinite; }

/* -- live: the card ------------------------------------------------------- */
.course--live .course__panels { border-block-start: 0; }

.course--live .course__panel {
  /* one cell for all four: the box keeps the height of the tallest card, so
     stepping through them never shifts the page */
  grid-area: 1 / 1;
  grid-template-columns: auto minmax(0, 1.3fr) minmax(0, 1fr);
  align-items: start;
  gap: var(--space-3) var(--space-6);
  padding: clamp(1.35rem, 3vw, 2.25rem);
  border: 0;
  border-radius: var(--radius-2);
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--line), var(--shadow-2);
  opacity: 0;
  visibility: hidden;
}

.course--live .course__panel-side { grid-column: auto; }

.course--live .course__panel.is-active { opacity: 1; visibility: visible; }

/* visibility waits out the fade on the way off, and flips immediately on the
   way in — hidden cards must leave the accessibility tree, but they still
   have to hold the box's height */
html:not(.no-motion) .course--live .course__panel {
  transform: translateY(10px);
  transition: opacity var(--dur-2) var(--ease-out),
              transform var(--dur-3) var(--ease-out),
              visibility 0s linear var(--dur-2);
}

html:not(.no-motion) .course--live .course__panel.is-active {
  transform: none;
  transition-delay: 0s;
}

.course.is-teleport .course__panel,
.course.is-teleport .course__station,
.course.is-teleport .course__station::before,
.course.is-teleport .course__station-name,
.course.is-teleport .course__progress { transition: none; }

@media (min-width: 1024px) {
  .course--live .course__panel-num { font-size: var(--size-4); -webkit-text-stroke-width: 1.5px; }

  .course--live .course__panel h3 { font-size: var(--size-2); }

  .course--live .course__panel p { font-size: var(--size-0); max-width: 46ch; }

  .course--live .course__panel-side { padding-block-start: 0.35rem; }
}

/* phones and tablets: the same chart, the card in one column. The ghost
   numeral would be the second copy of the digit on the station, and it costs
   the text a whole column of width. */
@media (max-width: 1023px) {
  .course--live .course__panel { grid-template-columns: minmax(0, 1fr); }

  .course--live .course__panel-num { display: none; }

  .course__station-name { font-size: 0.62rem; }
}

/* -- v8: case pages, uniform skeleton (06.09.2026) -- */
/* Staged here for the merge into sections.css. Tokens only. */

/* the hero names the service tags right under the lead, the same chips the
   tile on cases.html carries, so a reader arriving from the index sees the
   card they clicked */
.case-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block-start: var(--space-5);
}

/* two metrics side by side above 768, stacked below like the base strip.
   Doubled class for the same reason as --four: the base three-column rule
   sits later in the file and would win on order. */
@media (min-width: 768px) {
  .proof-strip.proof-strip--two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* a footnote under a block, held to a short measure so it reads as a note
   and not as a paragraph */
.case-note { max-width: 32ch; }

/* the design section carries its own section-head now; the prose column
   keeps a breath below it, the list follows at the prose gap */
.case-design .case-prose { margin-block-start: var(--space-4); }

/* sub-blocks folded into a parent section (before/after inside "The starting
   point", extra screens inside "Design and devices"): an h3 with room above
   it, so it reads as a second movement of the same section, not a new one */
.case-ba__title,
.case-more__title {
  font-size: var(--size-2);
  margin-block: var(--space-7) var(--space-4);
}

/* .case-ba is a grid with its own gap; the title's bottom margin would stack
   on top of it */
.case-ba > .case-ba__title { margin-block-end: 0; }

/* the same note under the before/after pair sits in the grid flow */
.case-ba > .case-note { margin: 0; }

/* filtered-out tiles leave the grid entirely; .case-big had this rule, the
   tile that replaced it did not, so the filter bar toggled nothing */
.case-tile.is-hidden { display: none; }

/* the count line under the index grid */
.case-index__note { margin-block-start: var(--space-6); }

/* ==== v8: service pages from one generator (06.09.2026) =================
   Six service pages share one skeleton (tools/build_services.py). The blocks
   below are the parts that changed with it: generated icon tiles in the
   industry lanes and the other-services grid, a lede under the deck title,
   and the three proof layouts a service can carry depending on how many
   case studies it has (lente of tiles, two-up grid, one featured case).
   ======================================================================== */

/* -- industries: the image is the tile ------------------------------------
   The generated icon carries its own flat foam field, so the <img> is the
   chip — same recipe as .hdeck__chip, one size up. The title sits on a
   hairline so the card reads top-down: object · name · what it gets you. */
.ind-card { gap: var(--space-2); padding: var(--space-5); }

/* the object alone, no tile (Artem, 06.09.2026): the field is keyed out by
   tools/key_icons.py and the icon is cropped to itself, so it sits a size up
   to read as large as it did on the plate */
.ind-card__pic {
  width: 4.5rem;
  height: 4.5rem;
  object-fit: contain;
  margin-block-end: var(--space-3);
}

.ind-card b {
  padding-block-start: var(--space-3);
  border-block-start: 1px solid var(--line);
}

/* -- the deck: one line under the title says what every service includes -- */
.hdeck__lede { margin-block-start: var(--space-3); max-width: 44ch; }

/* -- other services: five tiles, every page shows the ones it is not ------- */
.more-card__pic {
  width: 5rem;
  height: 5rem;
  object-fit: contain;
}

.more-grid--five .more-card { padding: clamp(1.25rem, 2vw, 1.75rem); }

.more-grid--five .more-card h3 { font-size: var(--size-0); }

@media (min-width: 600px) and (max-width: 1023px) {
  .more-grid--five { grid-template-columns: repeat(2, 1fr); }
  /* a lone fifth tile spans the row: full width reads as intended, a single
     card in a two-column row reads as one that went missing */
  .more-grid--five > :last-child:nth-child(odd) { grid-column: 1 / -1; }
}

@media (min-width: 1024px) {
  .more-grid--five { grid-template-columns: repeat(5, 1fr); gap: var(--space-3); }
}

/* -- proof: a lente of case tiles (three or more cases) -------------------- */
.work-lente--tiles { grid-auto-columns: clamp(19rem, 30vw, 26rem); }

.work-lente--tiles .case-tile { height: 100%; }

/* -- proof: two cases side by side ------------------------------------------ */
@media (min-width: 720px) {
  .case-index--two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* -- proof: one featured case, tile beside its numbers ---------------------- */
.case-feature {
  display: grid;
  gap: var(--space-6);
  margin-block-start: var(--space-block);
  align-items: center;
}

.case-feature .case-lede { margin-block-start: 0; }

/* the featured case's numbers stack under the lede: three columns of 164px
   could not hold a word like "Cheaper" at display size */
.case-feature .proof-strip { margin-block-start: var(--space-5); grid-template-columns: minmax(0, 1fr); gap: var(--space-4); }
.case-feature .proof + .proof { border-inline-start: 0; padding-inline-start: 0; border-block-start: 1px solid var(--line); padding-block-start: var(--space-4); }

/* -- proof numbers that are words --------------------------------------------
   "First month", "No developer", "3-4 weeks" are results too, but at the
   numeral's size they ran past the column; the generator marks them and they
   step down one size. Any value may still wrap rather than overflow. */
/* `.proof { justify-items: start }` sizes each value to its own content, and
   `overflow-wrap` then shrinks that to the widest breakable fragment — the
   box collapsed to 68px inside a 156px column and split "weeks" in half.
   Stretching the value to its column lets it wrap at spaces instead. */
.proof__num { overflow-wrap: break-word; justify-self: stretch; }
.proof__num--word { font-size: var(--size-3); line-height: 1.05; }
.case-tile__metric b { overflow-wrap: break-word; }

@media (min-width: 900px) {
  .case-feature { grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); gap: var(--space-8); }
}

/* -- headline measure on phones ------------------------------------------- */
/* Every display headline carries a `ch` measure so a line does not run too long
   on a wide screen. On a phone the viewport is already the measure, and the cap
   is narrower than a single Slovak word: "POROZPRÁVAJME" is 342px against a
   309px cap, so the word was split in half. Drop the cap where it only harms. */
@media (max-width: 767px) {
  .hero h1,
  .page-hero h1,
  .section-head h2,
  .cta-band h2,
  .cta-solo h2,
  .dive__chapter h2,
  .case-hero h1,
  .svc-hero h1,
  .explain--svc h2,
  .faq-cols__intro h2 {
    max-width: none;
  }
}
