/* The design uses Montserrat for latin display text and Noto Sans JP for
   Japanese; neither ships with the project, so this page loads both. */

/*
 * Styles for the Figma design concept at /preview
 * (file zsqdnkcUZh9nZRNu8PRu4R, node 131:234).
 *
 * Everything is scoped under .pv so this concept can sit alongside the live
 * site without touching its styles. The design is drawn on a 1440px canvas;
 * `--pv-gutter` holds the side margins the site uses throughout and keeps the
 * content inside that frame on wider screens. (The canvas has 120px margins,
 * 8.33%; the site started at 10%, then 8%, and the user settled on 5% for
 * every page on 2026-10-07.)
 *
 * Readable content — headings, body copy, buttons, cards — stays inside that
 * frame. Purely decorative layers (the stacked ABOUT US / SERVICE / WORKS
 * wordmarks, background artwork, the cycle diagram rings) are `aria-hidden`
 * and may still bleed past it, as they do in the design.
 */

.pv {
  --pv-red: #d92d32;
  --pv-teal: #18afa8;
  --pv-pink: #e75b86;
  --pv-orange: #f28a1a;
  --pv-grey: #b0b0b0;
  --pv-rule: #9c9c9c;
  --pv-gutter: 5vw;

  background: #fff;
  color: #000;
  font-family: "Noto Sans JP", "Hiragino Kaku Gothic ProN", sans-serif;
}

.pv *,
.pv *::before,
.pv *::after { box-sizing: border-box; }

:where(.pv) p { margin: 0; }

.pv-filter-defs {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* Montserrat carries every latin display string in the design. */
.pv-header-nav,
.pv-header-contact,
.pv-fv-title,
.pv-section-en,
.pv-contact-title,
.pv-stage-name,
.pv-members-title,
.pv-service-card-title,
.pv-button span,
.pv-news-meta time,
.pv-footer-contacts,
.pv-footer-nav,
.pv-footer-copy {
  font-family: Montserrat, "Helvetica Neue", Arial, sans-serif;
}

/* ---------- shared button ---------- */
/* 4rem of air above every CTA. The body copy that precedes one in normal flow
   carries margin-bottom: 56px, and adjacent margins collapse there, so this
   value wins outright rather than summing. Grid parents don't collapse, so the
   blocks that place their button on a grid subtract their gap from it below. */
.pv-button {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: 4rem;
  width: min(300px, 100%);
  justify-content: space-between;
  padding: 16px 32px;
  border: 1px solid #000;
  font-size: 16px;
  font-weight: 700;
  line-height: 1.8;
  white-space: nowrap;
}

.pv-button svg { flex: none; }
.pv-button-light { border-color: #fff; background: #fff; color: #000; }
.pv-button-dark { background: #000; color: #fff; }
.pv-button-outline { border-color: #fff; background: transparent; color: #fff; }

/* The Works and News blocks use a white button on white; keep the rule visible. */
.pv-works .pv-button-dark,
.pv-news .pv-button-dark,
.pv-journal .pv-button-dark { background: #000; color: #fff; }

/* ---------- header ---------- */
/* Fixed so the nav stays available past the FV. The bar itself is left
   transparent: a background here would give the nav a flat panel to blend
   against instead of the page scrolling underneath. */
.pv-header {
  position: fixed;
  top: 0;
  right: 0;
  left: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  height: 110px;
  justify-content: space-between;
  /* Half the page gutter: the header sits tighter to the viewport edge than the
     content columns below it. */
  padding-inline: calc(var(--pv-gutter) / 2);
  pointer-events: none;
}

.pv-header > * { pointer-events: auto; }

.pv-header-logo {
  display: flex;
  align-items: center;
  flex: none;
  padding: 3px 0;
}

.pv-header-logo img {
  width: 150px;
  height: 84px;
  object-fit: contain;
}

/* The nav sits between the logo and the Contact button, both of which already
   carry the gutter, so it needs no side padding of its own. .pv-nav-blend below
   is positioned onto this element by script and must keep matching metrics. */
.pv-header-nav {
  display: flex;
  flex: 1;
  gap: 32px;
  justify-content: flex-end;
  /* The nav runs flush to the contact button once it has no side padding of its
     own, so the last item tucks under it. This keeps them apart without
     reintroducing a gutter the button already carries. */
  padding: 24px 32px 24px 0;
  color: #fff;
  font-size: 16px;
  font-weight: 500;
  line-height: 1.6;
  white-space: nowrap;
}

/* The real menu stays for layout and clicks, but is drawn by .pv-nav-blend
   below, so its own type is hidden on the widths that overlay covers. */
@media (min-width: 1101px) {
  .pv-header-nav a { opacity: 0; }
}

/* Difference overlay: a fixed ancestor is its own stacking context, so type
   inside .pv-header cannot blend with the page behind it. Placing this copy
   outside the header lets the blend reach the sections scrolling underneath —
   white base, so dark grounds render it light and light grounds render it dark,
   inverting per pixel. PreviewNavBlend syncs it onto the real menu. */
.pv-nav-blend {
  position: fixed;
  z-index: 51;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 32px;
  /* Must match .pv-header-nav exactly: this is positioned onto that element's
     box, so a different inset slides the type out from under it and into the
     contact button. */
  padding: 24px 32px 24px 0;
  color: #fff;
  font-family: Montserrat, "Helvetica Neue", Arial, sans-serif;
  font-size: 16px;
  font-weight: 500;
  line-height: 1.6;
  mix-blend-mode: difference;
  pointer-events: none;
  white-space: nowrap;
}

@media (max-width: 1100px) {
  .pv-header-nav { display: none; }
  .pv-nav-blend { display: none; }
}

.pv-header-contact {
  display: flex;
  gap: 10px;
  align-items: center;
  flex: none;
  padding: 8px 16px;
  background: #000;
  color: #fff;
  font-size: 16px;
  font-weight: 500;
  line-height: 1.6;
}

.pv-header-contact img { width: 32px; height: 32px; }

/* ---------- FV ---------- */
/* Pinned for the handover to About: the frame holds at the top of the
   viewport while About rides up over it, and PreviewFvParallax feeds
   --pv-fv-progress (0 -> 1 over that stretch) so the footage drifts up more
   slowly than the page, dims, and the copy slips away, which reads as About
   overtaking the FV rather than the page simply scrolling. Later blocks are
   positioned (and filled where they relied on the page white) so they paint
   over the pinned frame; `main { overflow: clip }` does not break sticky. */
.pv-fv {
  --pv-fv-progress: 0;
  position: sticky;
  top: 0;
  z-index: 0;
  height: 890px;
  overflow: visible;
  background: #000;
}

/* Every block after the FV takes the same handover (see PreviewSectionPin):
   it pins at --pv-pin-top, measured per block, and the next one rides over
   it. Stacking order comes inline from the script. */
.pv-main[data-pinned] > * {
  position: sticky;
  top: var(--pv-pin-top, 0px);
}

/* Sits above the footage and the copy alike. */
.pv-fv::after {
  position: absolute;
  inset: 0;
  z-index: 5;
  background: #000;
  opacity: calc(var(--pv-fv-progress) * 0.6);
  content: "";
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .pv-fv { position: relative; }
  .pv-fv::after { display: none; }
}

.pv-fv-media,
.pv-fv-media img,
.pv-fv-media video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* Continue the footage behind the lowered arc's transparent corners. Keep
   clipping on the media so the headline retains its existing blend behavior. */
.pv-fv-media {
  height: calc(100% + 2.76vw);
  overflow: hidden;
  /* Drifts up at a fraction of the scroll speed while pinned; About covers
     the frame from below faster than the footage retreats, so the black
     behind never shows. */
  transform: translateY(calc(var(--pv-fv-progress) * -16%));
}

/* The footage runs as tall split cuts, edge to edge with no gutters, drifting
   left at a steady pace (see PreviewFvStrip). The track holds the cuts twice,
   so shifting it by half its width loops without a seam; the negative delay
   starts it mid-drift so the first frame already shows a cut entering. */
.pv-fv-strip {
  position: absolute;
  inset: 0;
}
.pv-fv-strip-track {
  display: flex;
  width: max-content;
  height: 100%;
  animation: pv-fv-strip-run 75s linear -6s infinite;
  will-change: transform;
}
.pv-fv-strip[data-covered="true"] .pv-fv-strip-track { animation-play-state: paused; }
@keyframes pv-fv-strip-run {
  to { transform: translate3d(-50%, 0, 0); }
}
.pv-fv-cut {
  position: relative;
  flex: none;
  width: 37.5vw;
  height: 100%;
  /* Each cut laps a pixel over the next: the track sits on fractional pixels
     while it drifts, and butted edges would let a hairline of the black
     behind show through at every seam. */
  margin-right: -1px;
  overflow: hidden;
  background: #16262b;
}
/* The grade is baked into the cuts (see scripts/fv-cut.sh), not applied here:
   it is a split tone done with per-channel curves, and the overlay layers that
   stood in for it only veiled the frame and drained the colour. */
.pv-fv-cut video {
  object-fit: cover;
  object-position: center;
}

/* Previous / next for the strip (see PreviewFvArrows): small outlined circles
   with a play-button triangle, pinned to the frame's left and right edges at mid-height. The row itself is
   click-through so only the two buttons take the pointer, and it fades with
   the copy as About rides over the frame. */
.pv-fv-arrows {
  position: absolute;
  inset: 0 28px;
  z-index: 4;
  display: flex;
  align-items: center;
  justify-content: space-between;
  opacity: calc(1 - var(--pv-fv-progress) * 1.6);
  pointer-events: none;
}
.pv-fv-arrow {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1px solid #fff;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.12);
  color: #fff;
  cursor: pointer;
  pointer-events: auto;
  transition: background-color 200ms ease, color 200ms ease;
}
.pv-fv-arrow:hover { background: #fff; color: #111; }
.pv-fv-arrow:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }

/* Fine line-art drawn over the footage. The artwork has a transparent
   background and pale strokes, so it sits at full opacity without washing the
   frame out. It spans the full width but only the lower half: the strokes
   are stretched flatter to fit that band, and the bottom is sunk below the
   media box so the tail hides under the About arc. The artwork's strokes end
   in a hard cut at its left edge, so a mask fades that edge out instead of
   showing it. The strokes ripple like a wave through the SVG filter from
   PreviewFvWave, and the whole sheet also drifts slowly, mirroring the
   contact wave, scaled from the bottom so it stays anchored. */
.pv-fv-media .pv-fv-wave {
  inset: auto 0 -14% 0;
  width: 100%;
  height: 58%;
  object-fit: fill;
  opacity: 1;
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 38%);
  mask-image: linear-gradient(to right, transparent 0%, #000 38%);
  pointer-events: none;
  transform-origin: center bottom;
  filter: url(#pv-fv-wave-filter);
  animation: pv-fv-drift 18s ease-in-out infinite;
}
/* Holds the filter only; it must not take part in the media box layout. */
.pv-fv-media .pv-fv-wave-defs { position: absolute; width: 0; height: 0; inset: auto; }

@keyframes pv-fv-drift {
  0%, 100% {
    transform: translate(-2%, 1.2%) rotate(-1deg) scale(1.08);
    opacity: 0.85;
  }
  50% {
    transform: translate(2%, -1.2%) rotate(1deg) scale(1.11);
    opacity: 1;
  }
}

/* The footage is bright, so a scrim keeps the white nav and headline legible.
   A light flat tint takes the edge off the whole frame without greying the
   grade; the gradients add the real weight at the top and bottom edges where
   the nav and lead sit. */
.pv-fv-media::after {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom, rgba(0, 0, 0, 0.34) 0%, rgba(0, 0, 0, 0) 20%),
    linear-gradient(to top, rgba(0, 0, 0, 0.44) 0%, rgba(0, 0, 0, 0) 42%),
    rgba(0, 0, 0, 0.08);
  content: "";
  pointer-events: none;
}

/* Set at the bottom right, on the header's half gutter rather than the content
   columns', so the lead and the headline end on the same line as the Contact
   button above them. */
.pv-fv-copy {
  /* Lifts away and fades ahead of the footage as About rises. */
  transform: translateY(calc(var(--pv-fv-progress) * -220px));
  opacity: calc(1 - var(--pv-fv-progress) * 1.6);
  position: absolute;
  right: calc(var(--pv-gutter) / 2);
  /* A touch below the designed 120px, lead and headline together; the drop
     scales with the headline's size. */
  bottom: calc(120px - clamp(6px, 1.1vw, 16px));
  width: max-content;
  max-width: calc(100vw - var(--pv-gutter));
  color: #fff;
}

/* One line, always: the lead is a single sentence in the design, so it sets no
   width of its own — a fixed width forced a wrap at every viewport — and the
   lower bound of the font clamp is sized so the string still fits at 400px. */
.pv p.pv-fv-lead {
  position: relative;
  z-index: 2;
  margin-bottom: 8px;
  white-space: nowrap;
  font-size: clamp(12px, 1.75vw, 24px);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-align: right;
  text-shadow: 0 2px 14px rgba(0, 0, 0, 0.3);
}

/* The per-character glow copy (.pv-fv-char::before) inherits the shadow above
   and would stack a second one under each letter, so the lead's halo read far
   darker than the single shadow it was set with. */
.pv-fv-lead .pv-fv-char::before { text-shadow: none; }

/* The headline is a soft overlay rather than flat white: a translucent white
   fill lets the footage read through the letterforms, and the blur behind it
   keeps them legible over busy frames without hiding the video. */
/* Blends with the footage rather than sitting on it: the letters pick up the
   colour behind them and their edges melt into the frame. */
.pv-fv-title {
  position: relative;
  width: 100%;
  max-width: none;
  margin: 0;
  /* A little under two thirds of the edge-to-edge size it had (10.2vw). The
     floor keeps the `nowrap` string a headline on the narrowest phones while
     still leaving it inside the gutters at 320px. */
  font-size: max(26px, 6vw);
  text-align: right;
  font-weight: 700;
  letter-spacing: 0.01em;
  line-height: 1.1;
  white-space: nowrap;
  color: #fff;
}

/* The shattered headline (see PreviewShatterTitle). The sizing copy holds
   the box; every shard is the full string clipped to its triangle, carrying
   the blend and glow itself so the tiles add up to the single overlaid word
   the design has. */
.pv-shatter {
  position: relative;
  display: block;
}
.pv-shatter-size {
  opacity: 0;
  color: #fff;
  mix-blend-mode: overlay;
  text-shadow: 0 0 24px rgba(255, 255, 255, 0.3);
  animation: pv-shatter-solid 200ms ease 0.85s forwards;
}
/* Assembled: the whole word fades in as the shards fade out (200ms), so
   the tile seams dissolve rather than sit there. The crossfade is a CSS
   animation on the same clock as the shards' flight, not a script-set state:
   a script only starts counting once the page has hydrated, which left the
   seams on show for however long that took. The script just drops the
   shards afterwards (data-phase="done"). */
.pv-shatter-shards { animation: pv-shatter-clear 200ms ease 0.85s forwards; }
@keyframes pv-shatter-solid {
  to { opacity: 1; }
}
@keyframes pv-shatter-clear {
  to { opacity: 0; visibility: hidden; }
}
.pv-shatter[data-phase="done"] .pv-shatter-shards { display: none; }
.pv-shard {
  position: absolute;
  inset: 0;
  color: #fff;
  mix-blend-mode: overlay;
  text-shadow: 0 0 24px rgba(255, 255, 255, 0.3);
  transform: translate(var(--sx), var(--sy)) rotate(var(--sr));
  opacity: 0;
  animation: pv-shard-in 0.9s cubic-bezier(0.22, 1, 0.36, 1) var(--delay, 0s) forwards;
  will-change: transform;
}
@keyframes pv-shard-in {
  to { transform: none; opacity: 1; }
}
/* Once the word has assembled, a glint crosses it left to right and comes
   back on a fixed beat. The word is already white, so a brighter band would
   not show: the glint is a third copy of the string, transparent but for one
   slanted band clipped to the letterforms, a translucent silver with only a
   trace of the wave art's blue and pink, so it reads as a soft sheen rather
   than a colour. The fill is 2.5x the word wide, so the band starts and ends
   off the text and the loop has no visible jump. The sweep takes the first
   2.4s of the 6s cycle, the rest is the pause. */
.pv-shatter-shine {
  position: absolute;
  inset: 0;
  background: linear-gradient(105deg, transparent 38%, rgba(196, 218, 232, 0.55) 46%, rgba(232, 212, 224, 0.55) 54%, transparent 62%) 100% 0 / 250% 100% no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  pointer-events: none;
  animation: pv-shatter-shine 6s linear 1.15s infinite;
}
@keyframes pv-shatter-shine {
  0% { background-position: 100% 0; }
  40%, 100% { background-position: 0 0; }
}
@media (prefers-reduced-motion: reduce) {
  .pv-shatter-size { opacity: 1; animation: none; }
  .pv-shatter-shards,
  .pv-shatter-shine { display: none; }
}

/* Each character lights up in turn from the left, surfacing out of a blur as
   if a beam were sweeping across the line (after spicato's "text09"); unlike
   the original, the letters stay lit at the end because this is the headline.
   The glow copy lives on each character so it fades in with its letter. */
.pv-fv-char {
  position: relative;
  display: inline-block;
  opacity: 0;
  filter: blur(8px);
  animation: pv-fv-char-in 0.6s cubic-bezier(0.77, 0, 0.175, 1)
    calc(var(--pv-char-start, 0s) + var(--pv-char-step, 0.08s) * var(--pv-char-index, 0))
    both;
}

/* The lead follows the headline with the same sweep, starting as the last
   letters of "Create The Next." are lighting up (the headline's final char
   starts at 15 * 0.08s = 1.2s). A tighter step keeps the longer Japanese
   line from dragging on well past the headline. */
.pv p.pv-fv-lead {
  --pv-char-start: 1s;
  --pv-char-step: 0.05s;
}

/* A soft, screened copy underneath gives the letters their glow and keeps them
   readable where the footage behind is dark. */
.pv-fv-char[data-char]::before {
  position: absolute;
  inset: 0;
  z-index: -1;
  color: rgba(255, 255, 255, 0.78);
  content: attr(data-char);
  mix-blend-mode: screen;
}

@keyframes pv-fv-char-in {
  0% {
    filter: blur(8px);
    opacity: 0;
  }
  55% {
    filter: blur(2px);
    opacity: 1;
  }
  100% {
    filter: blur(0);
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .pv-fv-char {
    animation: none;
    opacity: 1;
    filter: none;
  }
}

/* ---------- outline lettering ---------- */
.pv-about-word,
.pv-service-word,
.pv-works-word {
  position: absolute;
  display: grid;
  isolation: isolate;
  pointer-events: none;
  user-select: none;
}

.pv-about-word span,
.pv-service-word span,
.pv-works-word span {
  grid-area: 1 / 1;
  font-family: Montserrat, sans-serif;
  font-weight: 700;
  line-height: 1.2;
  white-space: nowrap;
  /* Each block sets --pv-word-stroke for its own ground: these copies are
     unfilled, so a `currentColor` stroke would resolve to `transparent` and the
     outlines would render as nothing at all. */
  -webkit-text-stroke: 1px var(--pv-word-stroke);
  color: transparent;
  z-index: 1;
}

/* Keep the solid word above every outline copy. This masks the portions of the
   offset outlines that overlap the glyphs, leaving the trails visible only on
   the outside of the solid lettering, as in Figma node 213:89. */
.pv-about-word span[data-layer="4"],
.pv-service-word span[data-layer="4"],
.pv-works-word span[data-layer="4"] {
  -webkit-text-stroke: 0;
  z-index: 5;
}

/* ---------- ABOUT ---------- */
/* Lower the arc without flattening it, leaving more of the FV visible. */
.pv-about {
  position: relative;
  z-index: 1;
  margin-top: -2.76vw;
  padding: calc(125px + 2.76vw) 0 0;
  color: #fff;
}

/* Preserve the original top arc depth independently of its overlap with the
   FV. The lower curve keeps its original share of the image height
   (107 / 3636), with a seamless filled centre between the two caps. */
.pv-about-bg {
  position: absolute;
  inset: 0;
  border-image-source: url("../preview/optimized/about-background-w1600-11cacffe9d.webp");
  border-image-slice: 107 0 107 0 fill;
  border-image-width: 5.52vw 0 2.942794% 0;
  border-image-repeat: stretch;
  pointer-events: none;
}

.pv-about-plane {
  position: absolute;
  z-index: 1;
  left: -14.5%;
  bottom: 3%;
  width: 57%;
  aspect-ratio: 1061 / 1183;
  --pv-plane-opacity: 0.6;
  opacity: var(--pv-plane-opacity);
  pointer-events: none;
}
/* Drawn in from below as the section scrolls into view: a clip wipe runs up
   the stack while the block rises a little and fades up from nothing, so the
   wipe's edge never reads as a hard line. The wipe lives on this inner layer
   because Chrome excludes a clipped target's hidden area from intersection,
   so a clip on the observed block itself never let PreviewReveal fire. The
   clip rests with a 6% margin so the idle drift and the slight overhang of the
   top piece are never cut. All three run the same 1200ms so the opacity
   transitionend that settles PreviewReveal lands after the wipe is at rest
   (and inside its 1400ms fallback). */
.pv-about-plane-clip {
  position: absolute;
  inset: 0;
  clip-path: inset(-6% -6% -6% -6%);
}
.pv .pv-about-plane[data-pv-reveal][data-reveal] {
  transition:
    transform 1200ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity 1200ms cubic-bezier(0.22, 1, 0.36, 1);
}
.pv .pv-about-plane[data-pv-reveal][data-reveal] .pv-about-plane-clip {
  transition: clip-path 1200ms cubic-bezier(0.22, 1, 0.36, 1);
}
.pv .pv-about-plane[data-pv-reveal][data-reveal="pending"] {
  opacity: 0;
  transform: translateY(6%);
}
.pv .pv-about-plane[data-pv-reveal][data-reveal="pending"] .pv-about-plane-clip {
  clip-path: inset(106% -6% -6% -6%);
}
/* The idle drift lives on this wrapper so its transform never fights the
   reveal transform on the block itself. */
.pv-about-plane-drift { position: absolute; inset: 0; }
.pv-about-plane span { position: absolute; background: center / 100% 100% no-repeat; }
.pv-about-plane span:nth-child(1) { inset: 0 -0.85% 49.91% 0; background-image: url("../preview/about-plane-1.svg"); }
.pv-about-plane span:nth-child(2) { left: 25.67%; top: 52.76%; width: 50.59%; height: 47.24%; background-image: url("../preview/about-plane-2.svg"); }
.pv-about-plane span:nth-child(3) { left: 13.53%; top: 41.39%; width: 63.22%; height: 21.08%; background-image: url("../preview/about-plane-3.svg"); }

/* Anchored to the content frame rather than the viewport edge, so the stack sits
   inside the panel as it does in the design, instead of being pinned to x=-14 of
   the canvas and cropped against the left edge of the screen. Dropped below the
   copy as well: at the canvas y=300 it cleared the copy only because it sat out
   at the edge, and the copy runs to ~419px once the type is at full size. */
/* Decorative, so it sits under the copy and the CTA it overlaps rather than
   competing with them. */
.pv-about-word {
  --pv-word-stroke: rgba(255, 255, 255, 0.3);

  z-index: 2;
  /* Starts below the CTA rather than running behind it: the section's own top
     padding plus the copy block (heading, body, and the button's 4rem lead-in)
     put the button's bottom edge near 690px at 1440, so the stack clears it.
     Kept as the padding expression plus an offset so it tracks that padding
     instead of drifting at other widths. */
  top: calc(125px + 2.76vw + 430px);
  left: calc(var(--pv-gutter) - 1vw);
  color: rgba(255, 255, 255, 0.85);
  writing-mode: vertical-rl;
  /* Flipped on both axes, the same way .pv-works-word turns its stack around. */
  transform: rotate(180deg);
}

/* Five copies offset along one axis, matching Figma node 213:89. The SVG
   filter subtracts the glyph itself from each expanded copy, so its hairline
   is drawn outside the letterform instead of eating into its interior. */
.pv-about-word span { font-size: clamp(72px, 9.7vw, 140px); }
.pv-about-word span:not([data-layer="4"]) {
  color: #d9d9d9;
  -webkit-text-stroke: 0;
  filter: url(#pv-outline-outside-light);
}
.pv-about-word span[data-layer="0"] { transform: translate(-0.836em); opacity: 0.1; }
.pv-about-word span[data-layer="1"] { transform: translate(-0.686em); opacity: 0.2; }
.pv-about-word span[data-layer="2"] { transform: translate(-0.486em); opacity: 0.3; }
.pv-about-word span[data-layer="3"] { transform: translate(-0.243em); opacity: 0.4; }
.pv-about-word span[data-layer="4"] { transform: translate(0); color: #d9d9d9; }

/* Fades the stack in one copy at a time as it scrolls into view, from the
   faintest, farthest-out trail to the solid word (see PreviewStackedWord).
   The copies are hidden only while pending, so the block renders whole for
   users without scripting or when the stack is already past the viewport. */
.pv-about-word[data-reveal="pending"] span[data-layer],
.pv-service-word[data-reveal="pending"] span[data-layer],
.pv-works-word[data-reveal="pending"] span[data-layer] { opacity: 0; }
.pv-about-word[data-reveal] span[data-layer],
.pv-service-word[data-reveal] span[data-layer],
.pv-works-word[data-reveal] span[data-layer] { transition: opacity 480ms ease-out; }
.pv-about-word[data-reveal] span[data-layer="1"],
.pv-service-word[data-reveal] span[data-layer="1"],
.pv-works-word[data-reveal] span[data-layer="1"] { transition-delay: 100ms; }
.pv-about-word[data-reveal] span[data-layer="2"],
.pv-service-word[data-reveal] span[data-layer="2"],
.pv-works-word[data-reveal] span[data-layer="2"] { transition-delay: 200ms; }
.pv-about-word[data-reveal] span[data-layer="3"],
.pv-service-word[data-reveal] span[data-layer="3"],
.pv-works-word[data-reveal] span[data-layer="3"] { transition-delay: 300ms; }
.pv-about-word[data-reveal] span[data-layer="4"],
.pv-service-word[data-reveal] span[data-layer="4"],
.pv-works-word[data-reveal] span[data-layer="4"] { transition-delay: 400ms; }
@media (prefers-reduced-motion: reduce) {
  .pv-about-word[data-reveal] span[data-layer],
  .pv-service-word[data-reveal] span[data-layer],
  .pv-works-word[data-reveal] span[data-layer] { transition: none; }
}

/* The copy (x=119) and the diagram (x=437, 1032 wide) overlap in the design, and
   the diagram bleeds past the right edge of the 1440 canvas — so this is an
   overlap, not a two-column grid. The diagram is out of flow, so the box has to
   reserve its height: 199px down plus the ring. That has to be derived from the
   frame's WIDTH — a percentage min-height would resolve against the parent's
   height, and .pv-about has none, so it would collapse to 199px and crop the
   bottom node away. Frame width is min(1440px, 100vw) - 2 * 10vw; the ring is
   71.67% of it and 999/1032 as tall, hence 0.6938. */
.pv-about-inner {
  position: relative;
  z-index: 2;
  --pv-cycle-width: calc((100vw - 2 * var(--pv-gutter)) * 0.78);
  min-height: calc(var(--pv-cycle-width) * 999 / 1032 + 199px);
  margin: 0 auto;
  /* Both sides, so the heading below cannot run past the right gutter. The
     diagram is positioned out of flow and still bleeds, as the design has it. */
  padding-right: var(--pv-gutter);
  padding-left: var(--pv-gutter);
}

.pv-about-copy {
  position: relative;
  z-index: 3;
  max-width: 602px;
  padding-top: 5px;
}

/* The heading's text box is 968px inside a 602px frame: it deliberately runs
   wider than the column so it holds two lines instead of wrapping to three. */
.pv-about-title {
  width: max-content;
  max-width: min(968px, calc(100vw - 2 * var(--pv-gutter)));
  margin: 0;
  font-weight: 500;
  letter-spacing: 0.08em;
  line-height: 1.8;
}

.pv-about-title-sm { font-size: clamp(26px, 2.8vw, 40px); }
.pv-about-title-lg { font-size: clamp(30px, 3.3vw, 48px); font-weight: 700; }

.pv-about-body {
  margin: 24px 0 0;
  font-size: clamp(15px, 1.4vw, 20px);
  font-weight: 500;
  letter-spacing: 0.08em;
  line-height: 2;
}

/* ---------- the four-stage cycle ---------- */
/* The frame is 1032x999 at x=437 on the 1440 canvas, so it overlaps the copy.
   Both the offset and the width are kept as percentages of the frame (437/1440
   and 1032/1440) so the diagram scales as one piece — deriving the width from
   `100% - 437px` instead mixed a fixed offset with a fluid width and shrank the
   ring to ~60% of its size, pushing DEFINE out of view. The pair sums to 102%,
   the slight right bleed the design has; all four nodes stay visible. */
.pv-cycle {
  position: absolute;
  top: 199px;
  right: auto;
  left: calc(var(--pv-gutter) - 1vw + clamp(146.592px, 19.7492vw, 285.04px) + 5vw);
  aspect-ratio: 1032 / 999;
  width: var(--pv-cycle-width);
}

.pv-cycle-rings {
  position: absolute;
  top: 9.71%;
  left: 14.06%;
  width: 76.15%;
  aspect-ratio: 1;
}

.pv-cycle-rings span {
  position: absolute;
  border: 1px dashed rgba(255, 255, 255, 0.32);
  border-radius: 50%;
}

.pv-cycle-rings span:nth-child(1) { inset: 0; }
.pv-cycle-rings span:nth-child(2) { inset: 3.36%; }
.pv-cycle-rings span:nth-child(3) { inset: 6.96%; }

.pv-cycle-plane {
  position: absolute;
  top: 31.93%;
  left: 30.23%;
  width: 43.6%;
  height: auto;
  color: rgba(255, 255, 255, 0.75);
}

.pv-stage {
  position: absolute;
  display: grid;
  gap: 0.7cqw;
  place-content: center;
  place-items: center;
  aspect-ratio: 1;
  padding: 3%;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, #e5e5e5 0%, #fff 100%);
  color: #000;
  text-align: center;
  container-type: inline-size;
  filter: drop-shadow(0 0 2.5px #fff) drop-shadow(0 0 7.5px #fff);
}

.pv-stage-name {
  font-family: Montserrat, sans-serif;
  font-size: 13.2cqw;
  font-weight: 700;
  line-height: 1.2;
}

.pv-stage-lead {
  margin-top: 0.4cqw;
  font-size: 5.9cqw;
  font-weight: 700;
  line-height: 1.7;
}

.pv-stage-body {
  display: grid;
  font-size: 4.6cqw;
  font-weight: 500;
  line-height: 1.55;
}

/* DISCOVER is the largest node and the only filled one. */
.pv-stage-discover {
  top: 28.6%;
  left: 0;
  width: 39.2%;
  background: radial-gradient(circle at 50% 50%, #ff7074 0%, #ec4e53 50%, #d92d32 100%);
  color: #fff;
  filter: drop-shadow(0 0 2.5px #d92d32) drop-shadow(0 0 10px #d92d32);
}

.pv-stage-discover .pv-stage-name { font-size: 11.9cqw; }
.pv-stage-discover .pv-stage-lead { font-size: 5.9cqw; }
.pv-stage-discover .pv-stage-body { font-size: 4.6cqw; }

.pv-stage-grow { top: 0; left: 37.3%; width: 29.5%; }
.pv-stage-grow .pv-stage-name,
.pv-stage-grow .pv-stage-lead { color: var(--pv-teal); }

.pv-stage-deliver { top: 33.7%; left: 71.8%; width: 29.5%; }
.pv-stage-deliver .pv-stage-name,
.pv-stage-deliver .pv-stage-lead { color: var(--pv-pink); }

.pv-stage-define { top: 69.4%; left: 37.3%; width: 29.5%; }
.pv-stage-define .pv-stage-name,
.pv-stage-define .pv-stage-lead { color: var(--pv-orange); }

/* ---------- members ---------- */
.pv-members {
  position: relative;
  z-index: 2;
  margin: 0 auto;
  padding: 150px var(--pv-gutter) 220px;
  overflow: hidden;
  text-align: center;
}

/* Starts at x=813 of the 1440 canvas and runs 507px wide, so it stays on one
   line rather than wrapping inside the remaining column. The offset is capped
   so the title cannot be pushed past the right gutter on wide screens. */
.pv-members-title {
  width: max-content;
  max-width: 100%;
  margin: 0 0 148px;
  margin-left: auto;
  font-size: clamp(32px, 4.4vw, 64px);
  font-weight: 400;
  letter-spacing: 0.02em;
  text-align: left;
  white-space: nowrap;
}

.pv-members-row {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 104px;
}

.pv-members-list {
  --pv-member-card-size: clamp(360px, 20vw, 400px);

  flex: none;
  display: grid;
  grid-template-columns: repeat(10, var(--pv-member-card-size));
  gap: 48px;
  align-items: start;
  width: max-content;
  max-width: none;
  margin: 0;
  padding: 0;
  list-style: none;
  transform: translateX(calc(clamp(180px, 10vw, 200px) + 24px));
}

.pv-member {
  position: relative;
  overflow: hidden;
  scale: 1;
  translate: 0;
  transform-origin: center;
  /* Every card but the center one is dimmed, labels included. */
  opacity: 0.65;
  transition: scale 600ms cubic-bezier(0.22, 1, 0.36, 1), translate 600ms cubic-bezier(0.22, 1, 0.36, 1), transform 600ms cubic-bezier(0.22, 1, 0.36, 1), opacity 600ms ease;
}

/* Reserve half of the center card's extra width on each side. */
.pv-member[data-side="left"] { translate: -15% 0; }
.pv-member[data-side="right"] { translate: 15% 0; }

.pv-member[data-center="true"] {
  scale: 1.3;
  z-index: 2;
  opacity: 1;
}

/* The center card is always raised; heights alternate by distance from center
   and re-flow with each slide. */
.pv-member[data-depth="1"],
.pv-member[data-depth="3"],
.pv-member[data-depth="5"] { transform: translateY(52px); }

.pv-member-photo {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  /* Portraits crop to the square from the top, so heads stay in frame. */
  object-position: top;
  transition: transform 600ms cubic-bezier(0.22, 1, 0.36, 1);
}

.pv-member-role {
  position: absolute;
  bottom: 64px;
  left: 24px;
  padding: 0 8px;
  background: #000;
  color: #fff;
  font-size: 16px;
  line-height: 1.8;
}

.pv-member-meta {
  position: absolute;
  bottom: 24px;
  left: 24px;
  display: flex;
  gap: 16px;
  color: #fff;
  font-size: 14px;
  line-height: 1.8;
}

.pv-member-arrow {
  position: absolute;
  right: 22px;
  bottom: 36px;
  width: 40px;
  height: 40px;
}

.pv-arrow {
  border: 0;
  background: none;
  color: #fff;
  cursor: pointer;
  font-size: 40px;
  line-height: 1;
}

/* Pinned to the section edges, matching x=120 / x=1320 in the design. */
.pv-members-row .pv-arrow {
  position: absolute;
  top: -130px;
}

.pv-members-row .pv-arrow-prev { right: 72px; left: auto; }
.pv-members-row .pv-arrow-next { right: 0; }

.pv-members .pv-button { margin-inline: auto; }

/* ---------- SERVICE ---------- */
/* Lifted over the About panel so its top edge bulges up into it, mirroring the
   way About overlaps the FV. Measured off the Figma render at 82px deep
   (5.69% of width), centre highest — the same quadratic as the top edge, so the
   same bezier is reused with the curve flipped. overflow stays visible so the
   raised bulge is not clipped back off. */
.pv-service {
  position: relative;
  z-index: 2;
  margin-top: -5.69vw;
  /* The photo band closes the block (see .pv-band), so there is no bottom
     padding: the network artwork runs right up to the photo. */
  padding: calc(113px + 5.69vw) 0 0;
  background: none;
  /* The lower rectangle overlaps the arc tile by 2px: the two tiles meet on a
     fractional pixel row and left a hairline the block behind showed through. */
  mask:
    url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="none"><path d="M 0 100 Q 50 -100 100 100 L 100 100 L 0 100 Z" fill="black"/></svg>')
      top / 100% 5.69vw no-repeat,
    linear-gradient(#000 0 0) bottom / 100% calc(100% - 5.69vw + 2px) no-repeat;
  -webkit-mask:
    url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="none"><path d="M 0 100 Q 50 -100 100 100 L 100 100 L 0 100 Z" fill="black"/></svg>')
      top / 100% 5.69vw no-repeat,
    linear-gradient(#000 0 0) bottom / 100% calc(100% - 5.69vw + 2px) no-repeat;
}

/* Plain white fill; the curve is cut by the mask on the section itself. */
.pv-service::before {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: #fff;
  content: "";
}

.pv-service-bg {
  position: absolute;
  top: 0;
  left: -6%;
  width: 78%;
  height: 100%;
  background: url("../preview/optimized/service-network-w901-5eeed162d8.webp") left center / contain no-repeat;
  opacity: 0.55;
}

/* A 910px block ending flush with the right gutter (x=413..1323 on the canvas);
   everything inside is flush right, so the body and button end on the same edge
   as the heading. Anchored to the gutter rather than the canvas x-offset, so the
   block stays inside the 10% frame instead of drifting out of it. */
.pv-service-head {
  position: relative;
  z-index: 2;
  width: 910px;
  max-width: calc(100% - 2 * var(--pv-gutter));
  margin: 0 var(--pv-gutter) 0 auto;
  text-align: right;
}

/* One line at the design width (86px tall in Figma), so it must not wrap. The
   upper bound is the size at which the 23-character string still fits the 910px
   block: at the design's 48px it needed ~1137px and overflowed to the right,
   pushing the heading's right edge past the 10% gutter to ~99% of the viewport. */
.pv-service-title {
  margin: 0;
  font-size: clamp(24px, 3.3vw, 38px);
  font-weight: 500;
  letter-spacing: 0.03em;
  line-height: 1.8;
  white-space: nowrap;
}

.pv-service-title strong { font-weight: 700; white-space: nowrap; }

.pv-service-body {
  margin: 24px 0 0;
  font-size: clamp(14px, 1.4vw, 20px);
  font-weight: 500;
  letter-spacing: 0.08em;
  line-height: 2;
}

.pv-service-head .pv-button { justify-content: center; margin-left: auto; }

.pv-service-stage {
  position: relative;
  z-index: 2;
  height: 752px;
  /* The cards inside are placed at canvas coordinates, so the stage carries the
     gutter and they measure from the 10% frame rather than the viewport edge. */
  width: calc(100% - 2 * var(--pv-gutter));
  margin: 64px auto 0;
}

.pv-service-slider { position: absolute; inset: 0; }
.pv-service-carousel { display: contents; }

.pv-service-card {
  position: absolute;
  top: 0;
  z-index: 2;
  /* Capped to the stage, which carries the gutter, so the card cannot run past
     the 10% frame in the band between the design width and the 900px layout. */
  width: min(785px, 70%);
  height: 628px;
  padding: 44px 49px;
  border-radius: 16px;
  background: var(--pv-service-card-color, var(--pv-red));
  color: #fff;
  filter: drop-shadow(0 0 10px rgba(0, 0, 0, 0.25));
}

.pv-service-card-prev {
  left: calc(10% + 13vw - 828px);
}

/* Flush with the left edge of the stage, which is the 10% frame. The design puts
   it at x=260 of the canvas, but that offset sits INSIDE the frame and pushed the
   card to ~29% of the viewport; the previous card still bleeds off to its left. */
.pv-service-card-active,
.pv-service-card-exit { left: calc(10% + 13vw); }

.pv-service-card-active { z-index: 3; }
.pv-service-card-exit {
  z-index: 4;
  pointer-events: none;
}

.pv-service-slider.is-next .pv-service-card-active {
  animation: pv-service-card-enter-next 620ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
.pv-service-slider.is-prev .pv-service-card-active {
  animation: pv-service-card-enter-prev 620ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
.pv-service-slider.is-next .pv-service-card-exit {
  animation: pv-service-card-exit-next 620ms cubic-bezier(0.4, 0, 0.2, 1) both;
}
.pv-service-slider.is-prev .pv-service-card-exit {
  animation: pv-service-card-exit-prev 620ms cubic-bezier(0.4, 0, 0.2, 1) both;
}

@keyframes pv-service-card-enter-next {
  0% { opacity: 0.45; translate: -18% 0; scale: 0.82; }
  45% { opacity: 0.85; scale: 0.9; }
  100% { opacity: 1; translate: 0 0; scale: 1; }
}
@keyframes pv-service-card-enter-prev {
  0% { opacity: 0.5; translate: 18% 0; scale: 0.82; }
  100% { opacity: 1; translate: 0 0; scale: 1; }
}
@keyframes pv-service-card-exit-next {
  0% { opacity: 1; translate: 0 0; scale: 1; }
  55% { opacity: 0.72; scale: 0.88; }
  100% { opacity: 0; translate: 72% 0; scale: 0.78; }
}
@keyframes pv-service-card-exit-prev {
  0% { opacity: 1; translate: 0 0; scale: 1; }
  100% { opacity: 0; translate: -72% 0; scale: 0.82; }
}

/* The arrows sit under the active card (x=901 on the canvas, 641px in from the
   card's own left edge), not centred. Measured from the card rather than the
   canvas now that the card starts at the frame edge. */
.pv-service-nav {
  position: absolute;
  top: 662px;
  bottom: auto;
  left: 0;
  z-index: 10;
  display: flex;
  gap: 88px;
  justify-content: flex-start;
  width: 100%;
  margin: 0;
  padding: 0;
  padding-left: min(calc(10% + 13vw + 641px), calc(100% - 150px));
}

.pv-service-nav button {
  border: 0;
  background: transparent;
  cursor: pointer;
}

.pv-service-card-title {
  font-size: clamp(28px, 3.3vw, 48px);
  font-weight: 700;
  line-height: 1.2;
}

.pv-service-card-sub {
  margin-top: 12px;
  font-size: clamp(16px, 1.7vw, 24px);
  line-height: 1.8;
}

@media (min-width: 901px) {
  .pv-service-card-title { min-height: 2.4em; }
  .pv-service-card-sub { min-height: 3.6em; }
}

.pv-service-card-media {
  display: block;
  width: 686px;
  max-width: 100%;
  height: 364px;
  margin: 44px auto 0;
  object-fit: cover;
  background: #d9d9d9;
}

@media (min-width: 901px) {
  .pv-service-card-media {
    height: 303px;
    margin-top: 24px;
  }
}

/* Sits to the right of the active card at x=1106, y=522 in the design, so the
   stack peeks out from behind the card rather than sitting under it. */
/* Right-aligned with the heading and lead above it rather than pinned to x=1106
   of the canvas. The five stacked copies are offset rightward by up to 0.836em
   (see the layer transforms below), so the box is held that much further in and
   the filled copy — the one that reads as the wordmark — lands on the gutter.
   The offset tracks the clamped type size, matching the em-based steps. */
.pv-service-word {
  --pv-word-stroke: rgba(0, 0, 0, 0.16);

  top: 0;
  right: calc(0.836 * clamp(72px, 9.7vw, 140px) - 1vw);
  z-index: 1;
  color: #c1c1c1;
  writing-mode: vertical-rl;
}

/* The steps were frozen at the 140px design size; kept in em on that basis
   (17/140 = 0.121em and so on) so the stack spreads with the type the way
   .pv-about-word's does, instead of crowding as the clamp scales down. */
.pv-service-word span { font-size: clamp(72px, 9.7vw, 140px); }
.pv-service-word span:not([data-layer="4"]) {
  color: #c1c1c1;
  -webkit-text-stroke: 0;
  filter: url(#pv-outline-outside-service);
}
.pv-service-word span[data-layer="0"] { transform: translate(0, 0); opacity: 0.2; }
.pv-service-word span[data-layer="1"] { transform: translate(0.15em, 0); opacity: 0.4; }
.pv-service-word span[data-layer="2"] { transform: translate(0.35em, 0); opacity: 0.6; }
.pv-service-word span[data-layer="3"] { transform: translate(0.593em, 0); opacity: 0.8; }
.pv-service-word span[data-layer="4"] { transform: translate(0.836em, 0); color: #c1c1c1; }

.pv-service-nav .pv-arrow { color: #000; }

/* ---------- photo band ---------- */
/* Sits at the foot of the Service block, 80px under the slider stage, and
   moves with it in the section handover. */
.pv-band {
  --pv-band-height: clamp(240px, 35.28vw, 508px);
  position: relative;
  margin-top: 80px;
  height: var(--pv-band-height);
  overflow: hidden;
  background: #fff;
}

/* Each strip reveals its own part of the same full-size photograph. */
.pv-band-piece {
  position: absolute;
  width: 100%;
  overflow: hidden;
}
.pv-band-piece::before {
  content: "";
  position: absolute;
  left: 0;
  top: calc(-1 * var(--pv-band-source-y));
  width: 100%;
  height: var(--pv-band-height);
  /* Anchored to the bottom edge, so any overflow is trimmed from the top and
     the hands are never cut; held left of centre so the phone stays in frame
     when narrow screens crop the sides. */
  background: url("../preview/optimized/band-phone-w1280-8493fff3d2.webp") 20% bottom / cover no-repeat;
}
.pv-band-strip-top {
  --pv-band-source-y: 0px;
  top: 0;
  left: -2.15%;
  height: 9.06%;
}
.pv-band-strip-middle {
  --pv-band-source-y: calc(var(--pv-band-height) * 0.126);
  top: 12.6%;
  left: 2.22%;
  height: 8.47%;
}
.pv-band-main {
  --pv-band-source-y: calc(var(--pv-band-height) * 0.246);
  top: 24.6%;
  left: 0;
  height: 75.4%;
}

/* ---------- WORKS ---------- */
/* The section is a three-column grid: the heading spans the first two columns,
   the stacked WORKS word sits under it in the first column, and the five cards
   fill the rest. Row heights come from the content, so the layout holds its
   shape at any desktop width instead of stretching with the viewport. */
.pv-works {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: 4%;
  padding: 120px var(--pv-gutter) 120px;
  overflow: hidden;
  /* A seam line against the photo band above, which otherwise runs
     straight into the grid with no visible edge. */
  border-top: 2px solid #000;
  /* Fine graph-paper ground: a single 1px grid every 28px in a very light grey
     that stays behind the white cards. */
  background-color: #fff;
  background-image:
    linear-gradient(to right, rgba(0, 0, 0, 0.04) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(0, 0, 0, 0.04) 1px, transparent 1px);
  background-size: 28px 28px;
}

.pv-works-head {
  position: relative;
  z-index: 3;
  grid-column: 1 / 3;
  grid-row: 1;
  align-self: start;
  max-width: 640px;
}

.pv-works-title {
  margin: 0;
  font-weight: 500;
  letter-spacing: 0.04em;
  line-height: 1.8;
  white-space: nowrap;
}

.pv-works-title-sm { font-size: clamp(22px, 2.2vw, 32px); }
.pv-works-title-lg { font-size: clamp(30px, 3.3vw, 48px); }

.pv-works-body {
  margin: 24px 0 0;
  font-size: clamp(14px, 1.4vw, 20px);
  font-weight: 500;
  letter-spacing: 0.08em;
  line-height: 2;
}

.pv-works-word {
  position: relative;
  grid-column: 1;
  grid-row: 2;
  justify-self: start;
  margin: 120px 0 0 -1vw;
  z-index: 1;
  color: #d9d9d9;
  writing-mode: vertical-rl;
  transform: rotate(180deg);
}

/* The solid word sits on the left and the four outside-only outlines extend to
   the right, fading from 40% to 10%. The parent is rotated 180deg, so the local
   translate signs below are the inverse of their visual direction. */
.pv-works-word span { font-size: clamp(72px, 9.7vw, 140px); }
.pv-works-word span:not([data-layer="4"]) {
  color: #c1c1c1;
  -webkit-text-stroke: 0;
  filter: url(#pv-outline-outside-service);
}
.pv-works-word span[data-layer="0"] { transform: translate(-0.686em, 0); opacity: 0.2; }
.pv-works-word span[data-layer="1"] { transform: translate(-0.443em, 0); opacity: 0.4; }
.pv-works-word span[data-layer="2"] { transform: translate(-0.2em, 0); opacity: 0.6; }
.pv-works-word span[data-layer="3"] { transform: translate(0, 0); opacity: 0.8; }
.pv-works-word span[data-layer="4"] { transform: translate(0.15em, 0); color: #c1c1c1; }

/* The list dissolves into the section grid so each card can take its own
   cell (see .pv-work-a..e). */
.pv-works-grid {
  display: contents;
  margin: 0;
  padding: 0;
  list-style: none;
}

.pv-work { position: relative; z-index: 2; }
.pv-work-link { display: block; color: #161616; text-decoration: none; }
/* Scroll drift lives on the wrapper, independently of the image reveal. */
.pv-work-visual {
  position: relative;
  transform: translate3d(var(--pv-scroll-x, 0px), var(--pv-scroll-y, 0px), 0);
}
.pv-work-link:focus-within .pv-work-visual { transform: none; }
.pv-work img {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  border-radius: 50%;
  object-fit: cover;
  transition: filter 300ms ease;
}
.pv-work-link:focus-visible { outline: 2px solid #222; outline-offset: 8px; border-radius: 8px; }
.pv-work-caption { padding: 24px 4% 0; }
.pv-work-category { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; font-size: 12px; line-height: 1.6; color: #606060; }
.pv-work-category span { font-size: 11px; letter-spacing: 0.12em; color: #161616; }
.pv-work-label { margin: 10px 0 12px; font-size: clamp(18px, 1.55vw, 24px); font-weight: 600; line-height: 1.65; text-wrap: pretty; }
.pv-work-services { padding-top: 12px; border-top: 1px solid #d9d9d9; font-size: 11px; letter-spacing: 0.05em; line-height: 1.8; color: #666; }

/* Two staircases that both step down toward the left: 01 -> 02 across the top
   and 03 -> 05 -> 04 across the bottom, with 04 under the stacked word. The
   negative margin lifts 03 into the space left under 01. */
.pv-work-a { grid-column: 3; grid-row: 1 / 3; }
.pv-work-b { grid-column: 2; grid-row: 2; margin-top: -110px; }
.pv-work-c { grid-column: 3; grid-row: 3; margin-top: -220px; }
.pv-work-e { grid-column: 2; grid-row: 3; margin-top: 0; }
.pv-work-d { grid-column: 1; grid-row: 3; margin-top: 160px; }
@media (prefers-reduced-motion: reduce) {
  .pv-work img { transition: none; }
  .pv-work-visual { transform: none; }
}

/* Progressive enhancement: cards remain visible without JavaScript. */
.pv-work[data-reveal="pending"] .pv-work-visual img {
  opacity: 0;
  transform: translateY(32px) scale(0.28);
}
.pv-work[data-reveal="pending"] .pv-work-caption {
  opacity: 0;
  transform: translateY(16px);
}
.pv-work[data-reveal="entered"] .pv-work-visual img {
  animation: pv-work-pop 850ms cubic-bezier(0.22, 0.75, 0.25, 1) var(--pv-pop-delay, 0ms) both;
}
.pv-work[data-reveal="entered"] .pv-work-caption {
  animation: pv-work-caption-in 440ms ease-out calc(var(--pv-pop-delay, 0ms) + 200ms) both;
}
@keyframes pv-work-pop {
  0% { opacity: 0; transform: translateY(32px) scale(0.28); }
  30% { opacity: 1; }
  56% { opacity: 1; transform: translateY(-9px) scale(1.07, 0.97); }
  76% { transform: translateY(3px) scale(0.98, 1.02); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes pv-work-caption-in {
  from { opacity: 0; transform: translateY(16px); }
  to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .pv-work[data-reveal] .pv-work-visual img,
  .pv-work[data-reveal] .pv-work-caption {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

/* ---------- JOURNAL ---------- */
.pv-journal {
  position: relative;
  display: grid;
  /* The drum on the left, the heading on the right; the drum column runs to
     the viewport edge so the arc can leave the frame there. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 27.8vw);
  gap: 0 3vw;
  align-items: center;
  padding: 100px var(--pv-gutter) 100px 0;
  overflow: hidden;
}

.pv-journal-bands {
  position: absolute;
  inset: 0;
  background: linear-gradient(135deg, #090909 0%, #252525 50%, #505050 100%);
  pointer-events: none;
}

.pv-journal-bands::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0.12;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180' viewBox='0 0 180 180'%3E%3Cfilter id='grain'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Cpath fill='%23fff' filter='url(%23grain)' d='M0 0h180v180H0z'/%3E%3C/svg%3E") repeat;
}

/* The drum (see PreviewJournalWheel): cards stood on a circle of radius
   --pv-wheel-r round a vertical axis, viewed with perspective. The radius is
   sized so neighbouring cards clear each other with a gap the far cards'
   backs show through. */
.pv-journal-stage {
  /* The radius sets the projected gap between the front card and its
     neighbours (about 45px here). */
  /* Sized in vw from the 1440px design (445 / 250 / 330px there), so the
     drum keeps its shape and its share of the row at any desktop width. */
  --pv-wheel-r: 30.9vw;
  --pv-card-w: 17.36vw;
  --pv-card-h: 22.92vw;
  position: relative;
  z-index: 2;
  grid-area: 1 / 1;
}

.pv-journal-wheel {
  position: relative;
  /* The perspective enlarges the front card by 2050 / (2050 - 445) = 1.28.
     The edge fade below is a mask, and a mask clips at the box, so the box
     carries 40px of headroom above and below the enlarged card. */
  height: calc(var(--pv-card-h) * 1.28 + 80px);
  perspective: 142.4vw;
  perspective-origin: 50% 50%;
  cursor: grab;
  touch-action: pan-y;
  user-select: none;
  /* Cards fade out at both edges: on the right before they reach the
     heading, and on the left so the arrows sit on faded ground either side.
     The fade is held low through the arrows' zone (they sit at ~11-16% of
     the width) so a white chevron stays readable over a passing card. */
  --pv-drum-fade:
    linear-gradient(to right,
      transparent, rgba(0, 0, 0, 0.04) 8%, rgba(0, 0, 0, 0.14) 14%, rgba(0, 0, 0, 0.3) 18%,
      rgba(0, 0, 0, 0.55) 22%, rgba(0, 0, 0, 0.8) 26%, rgba(0, 0, 0, 0.95) 29%, #000 32%,
      #000 68%, rgba(0, 0, 0, 0.95) 71%, rgba(0, 0, 0, 0.8) 74%, rgba(0, 0, 0, 0.55) 78%,
      rgba(0, 0, 0, 0.3) 82%, rgba(0, 0, 0, 0.14) 86%, rgba(0, 0, 0, 0.04) 92%, transparent);
  mask: var(--pv-drum-fade);
  -webkit-mask: var(--pv-drum-fade);
}
.pv-journal-wheel[data-dragging="true"] { cursor: grabbing; }

/* One arrow at each outer edge of the drum, on its vertical centre. */
.pv-journal-nav {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
}
.pv-journal-nav .pv-arrow {
  position: absolute;
  top: 50%;
  display: grid;
  place-items: center;
  width: 3.33vw;
  height: 3.33vw;
  font-size: 2.78vw;
  color: #fff;
  transform: translateY(-50%);
  pointer-events: auto;
  transition: opacity 200ms ease;
}
.pv-journal-nav .pv-arrow:hover { opacity: 0.6; }
/* Inset to the inner edge of the fade, so they sit close to the cards but
   still on faded ground. */
.pv-journal-nav .pv-arrow:first-child { left: 6.67vw; }
.pv-journal-nav .pv-arrow:last-child { right: 6.67vw; }

.pv-journal-ring {
  --pv-wheel-angle: 0deg;
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  transform-style: preserve-3d;
  transform: rotateY(var(--pv-wheel-angle));
}

/* Each card turns to its slot and stands out to the radius, so it stays
   face-out on the drum; the front and back faces are separate layers. */
.pv-journal-card {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--pv-card-w);
  height: var(--pv-card-h);
  transform:
    translate(-50%, -50%)
    rotateY(var(--pv-card-angle, 0deg))
    translateZ(var(--pv-wheel-r));
  transform-style: preserve-3d;
  will-change: transform;
}

.pv-journal-card-back {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  border-radius: 14px;
  background: #151515;
  color: rgba(255, 255, 255, 0.28);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.3em;
  /* Turned to face backwards and set 1px behind the front face, so the front
     hides it and, seen from behind, it hides the mirrored front. */
  transform: rotateY(180deg) translateZ(1px);
  backface-visibility: hidden;
}

.pv-journal-card-link {
  position: relative;
  display: grid;
  grid-template-rows: auto auto 1fr auto;
  gap: 10px;
  height: 100%;
  padding: 12px 14px 16px;
  border-radius: 14px;
  background: #fff;
  color: #111;
  text-decoration: none;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.35);
}
/* Hover colours the title rather than lifting the card, and opts out of the
   page-wide link fade (.pv a:hover): a translucent face would show the drum
   through the card. */
.pv .pv-journal-card-link:hover { opacity: 1; }
.pv-journal-card-link:hover .pv-journal-card-title { color: var(--pv-red); }
.pv-journal-card-title { transition: color 200ms ease; }

.pv-journal-card-image {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: 8px;
  pointer-events: none;
}

.pv-journal-card-category {
  margin: 0;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  line-height: 1.6;
  color: #777;
}

.pv-journal-card-title {
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  margin: 0;
  font-size: 15px;
  font-weight: 700;
  line-height: 1.7;
}

.pv-journal-card-date {
  margin: 0;
  font-size: 12px;
  font-weight: 500;
  line-height: 1.6;
  color: #999;
}

.pv-journal-head {
  position: relative;
  z-index: 2;
  /* Placed explicitly: the heading precedes the drum in the markup but sits
     to its right. */
  grid-area: 1 / 2;
  display: grid;
  justify-items: end;
  gap: 24px;
  color: #fff;
  text-align: right;
}

.pv-section-en {
  margin: 0;
  font-size: clamp(28px, 2.8vw, 40px);
  font-weight: 900;
  letter-spacing: 0.08em;
  line-height: 1.8;
}

.pv-section-lead {
  font-size: clamp(15px, 1.4vw, 20px);
  font-weight: 500;
  letter-spacing: 0.08em;
  line-height: 2;
}

/* Grid parent: the 24px gap is already there, so subtract it to land on 4rem. */
.pv-journal-head .pv-button { margin-top: calc(4rem - 24px); }

/* ---------- NEWS ---------- */
.pv-news {
  /* Positioned and filled so it covers the pinned FV (see .pv-fv). */
  position: relative;
  background: #fff;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 64px 10vw;
  align-items: center;
  margin: 0 auto;
  padding: 140px var(--pv-gutter);
}

.pv-news-head { display: grid; gap: 24px; justify-items: start; }
.pv-news-head .pv-button { margin-top: calc(4rem - 24px); }

.pv-news-list {
  display: grid;
  gap: 16px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.pv-news-item {
  display: grid;
  gap: 10px;
  padding: 16px 24px;
  border-bottom: 1px solid var(--pv-rule);
}

.pv-news-meta {
  display: flex;
  gap: 16px;
  align-items: center;
}

.pv-news-meta time {
  color: var(--pv-grey);
  font-size: 16px;
  font-weight: 700;
  line-height: 1.8;
}

.pv-news-tag {
  padding: 0 8px;
  background: #000;
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.8;
}

.pv-news-title {
  overflow: hidden;
  font-size: 16px;
  font-weight: 500;
  line-height: 1.8;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------- CONTACT ---------- */
.pv-contact {
  position: relative;
  display: grid;
  /* Centred in the fixed height: the panel is taller than the content, so this
     leaves a band of grey above the title and below the CTA rather than
     pinning the block to the top and crowding the bottom edge. */
  place-items: center;
  height: 615px;
  padding-block: 72px 96px;
  overflow: hidden;
  /* Rounds into the white section above; the bottom stays square so the panel
     meets the black footer flush. overflow:hidden clips the wave to the curve. */
  border-radius: 40px 40px 0 0;
  /* A white spread behind the panel fills the corner gaps outside the curve,
     which otherwise show whichever dark block is pinned behind this one. */
  box-shadow: 0 0 0 48px #fff;
  /* Keep that spread to the corner gaps only: unclipped it also reached 48px
     above the panel and wiped the News rows the panel was riding up over. */
  clip-path: inset(0);
  /* Light grey at the top-left falling to a darker grey bottom-right, which is
     what carries the white CONTACT US wordmark in the design. */
  background: linear-gradient(125deg, #c2c2c2, #a8a8a8 38%, #8a8a8a 70%, #6b6b6b);
}

/* Full-bleed team photo that sits directly above the CONTACT band. Its top
   corners are squared off and the white spread dropped so the panel meets the
   photo flush instead of leaving white notches over it. */
.pv-contact-photo {
  position: relative;
  z-index: 1;
  margin: 0;
  background: #fff;
  line-height: 0;
}
.pv-contact-photo img {
  display: block;
  width: 100%;
  height: auto;
  /* Wide crop: trims the empty backdrop above the heads and the legs below.
     object-position's y share sets how much comes off the top vs the bottom. */
  aspect-ratio: 2.2 / 1;
  object-fit: cover;
  object-position: center 40%;
}
@media (max-width: 900px) {
  .pv-contact-photo img { aspect-ratio: 16 / 9; }
}
.pv-contact-photo + .pv-contact {
  border-radius: 0;
  box-shadow: none;
  clip-path: none;
}

.pv-contact-wave {
  position: absolute;
  /* Sized from the panel height (the panel is a fixed 615px, so a width-based
     size overflowed the top on wide screens) and parked in the lower left,
     bleeding off the left and bottom edges and tipped about 20deg clockwise
     about its centre. The strokes reach up to about 15% from the top, so the
     art fills roughly the lower 85% of the panel, with the right side left
     free for the copy. */
  inset: auto auto -3% -5%;
  width: auto;
  height: 94%;
  /* Same transparent line-art as the FV: pale tinted strokes on normal
     blending read as light lines over the grey, so no multiply is needed. */
  opacity: 0.9;
  pointer-events: none;
  transform-origin: center;
  animation: pv-contact-drift 18s ease-in-out infinite;
}

@keyframes pv-contact-drift {
  0%, 100% {
    transform: translate(-1.5%, 1%) rotate(19deg) scale(1);
    opacity: 0.8;
  }
  50% {
    transform: translate(1.5%, -1%) rotate(21deg) scale(1.04);
    opacity: 1;
  }
}

.pv-contact-inner {
  position: relative;
  z-index: 2;
  display: grid;
  justify-items: end;
  /* Title -> body is the tighter of the two; body -> CTA opens up a little.
     A single 76px gap spaced all three too far apart. */
  gap: 34px;
  width: 100%;
  padding: 0 var(--pv-gutter);
  color: #fff;
  text-align: right;
}

.pv-contact-title {
  margin: 0;
  font-size: clamp(48px, 8.3vw, 120px);
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 1.2;
  /* The heading shrink-wraps its text, and while the typewriter runs each
     character sits on its own layer and rounds up a pixel, so "US" fell to a
     second line mid-animation. The wordmark always fits, so never wrap it. */
  white-space: nowrap;
}

.pv-contact-body {
  font-size: clamp(15px, 1.4vw, 20px);
  font-weight: 500;
  letter-spacing: 0.08em;
  line-height: 2;
}

/* The grid gap above already spaces the CTA, so the shared 4rem would stack on
   top of it. The button keeps its black border here: against the grey panel a
   white hairline would ring it. */
.pv-contact .pv-button { margin-top: 22px; }

/* ---------- footer ---------- */
.pv-footer {
  /* Positioned above every pinned block so it covers them (see .pv-fv). */
  position: relative;
  z-index: 20;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 87px;
  padding: 96px var(--pv-gutter) 40px;
  border-top: 0;
  background: #000;
  color: #fff;
}

.pv-footer-main {
  display: flex;
  gap: 72px;
  align-items: stretch;
  justify-content: space-between;
  width: 100%;
  margin: 0 auto;
}

.pv-footer-brand {
  display: grid;
  gap: 48px;
  align-content: center;
  /* Equal-width columns either side of the rule keep it on the page centre. */
  flex: 1 1 0;
  min-width: 0;
}

.pv-footer-logo {
  width: 237px;
  height: 133px;
  object-fit: contain;
  object-position: left center;
}

.pv-footer-contacts {
  display: grid;
  gap: 16px;
  font-size: 16px;
  font-weight: 500;
  line-height: 1.8;
  overflow-wrap: anywhere;
}

.pv-footer-line { display: flex; gap: 8px; align-items: center; }
.pv-footer-line img { flex: none; width: 24px; height: 24px; }

.pv-footer-rule {
  flex: none;
  align-self: center;
  width: 1px;
  height: 335px;
  background: rgba(255, 255, 255, 0.4);
}

/* The 64px column gap is the spacing in the design; the button's own top
   margin would double it. */
.pv-footer-nav .pv-button { margin-top: 0; }

/* Right-hand column: the sitemap row and the CONTACT button, both flush with
   the right gutter. */
.pv-footer-nav {
  display: grid;
  gap: 64px;
  align-content: center;
  justify-items: end;
  flex: 1 1 0;
  min-width: 0;
}

/* Three link columns spread across a 484px row (Figma 216:411). */
.pv-footer-sitemap {
  display: flex;
  gap: 32px;
  justify-content: space-between;
  width: 484px;
  max-width: 100%;
}

.pv-footer-sitemap ul {
  display: grid;
  gap: 16px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 16px;
  font-weight: 500;
  line-height: 1.8;
}

/* Privacy link and copyright stack centred under the main row. */
.pv-footer-legal {
  display: grid;
  gap: 24px;
  justify-items: center;
  width: 100%;
  text-align: center;
}

.pv-footer-privacy { font-size: 16px; font-weight: 500; line-height: 1.8; }

.pv-footer-copy {
  width: 100%;
  margin: 0 auto;
  color: #959595;
  text-align: center;
  font-size: 16px;
  font-weight: 500;
  line-height: 1.8;
}

.pv a:hover { opacity: 0.72; }

/* ---------- responsive ---------- */
@media (max-width: 1240px) {
  /* Below the design width the overlap is unstacked into a normal flow. */
  .pv-about-inner { min-height: 0; padding-right: var(--pv-gutter); }
  .pv-cycle { position: relative; top: auto; right: auto; left: auto; width: 98.7%; max-width: 720px; margin: 64px auto 0; }
  .pv-service-card-active { left: 50%; transform: translateX(-50%); }
  .pv-service-card-prev { display: none; }
  .pv-service-card { width: min(785px, calc(100vw - 2 * var(--pv-gutter))); }
  .pv-works-word { display: none; }
  /* Below the design width the staggered cards return to a plain grid. */
  .pv-works { display: block; }
  .pv-works-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 24px;
  }
  .pv-work-a, .pv-work-b, .pv-work-c, .pv-work-d, .pv-work-e {
    grid-column: auto;
    grid-row: auto;
    margin-top: 0;
  }
  .pv-works-head { max-width: none; margin: 0 0 64px; }
  .pv-journal { grid-template-columns: 1fr; gap: 40px; padding-left: var(--pv-gutter); }
  .pv-journal-head { grid-area: 1 / 1; justify-items: start; text-align: left; }
  .pv-journal-stage { grid-area: 2 / 1; }
  .pv-journal-stage { --pv-wheel-r: 370px; --pv-card-w: 200px; --pv-card-h: 270px; }
  .pv-journal-wheel { perspective: 1600px; mask: none; -webkit-mask: none; }
  .pv-journal-nav .pv-arrow { width: 48px; height: 48px; font-size: 40px; }
  .pv-journal-nav .pv-arrow:first-child { left: 24px; }
  .pv-journal-nav .pv-arrow:last-child { right: 24px; }
  .pv-news { grid-template-columns: 1fr; gap: 48px; }
  .pv-footer-main { flex-direction: column; align-items: stretch; gap: 48px; }
  .pv-footer-rule { display: none; }
  .pv-footer-nav { gap: 48px; justify-items: start; }
  .pv-footer-sitemap { width: 100%; justify-content: flex-start; gap: clamp(32px, 6vw, 120px); }
}

@media (max-width: 900px) {
  .pv-header-nav { display: none; }
  .pv-members-list { grid-template-columns: 1fr; gap: 24px; max-width: 100%; }
  .pv-members-list { transform: none; }
  .pv-member:nth-child(n + 4) { display: none; }
  .pv-members-title { margin-bottom: 60px; }
  .pv-members { padding-bottom: 140px; }
  .pv-about-plane { bottom: 4%; width: 100%; }
  .pv-member[data-depth="1"] { transform: none; }
  .pv-member-photo { height: auto; max-height: 600px; }
  .pv-about-word { display: none; }
  .pv-service-word { display: none; }
  .pv-service-head { width: calc(100% - 2 * var(--pv-gutter)); max-width: none; margin-inline: auto; }
  .pv-service-title { white-space: normal; }
  .pv-service-nav { justify-content: center; padding-left: 0; }
  .pv-service-card { height: auto; padding: 28px; }
  .pv-service-card-active { position: relative; left: auto; transform: none; width: 100%; }
  .pv-service-card-media { height: auto; aspect-ratio: 686 / 364; }
  .pv-service-stage { height: auto; margin-top: 64px; }
  .pv-works-grid { grid-template-columns: 1fr; gap: 56px; }
  .pv-members-title { margin-left: 0; white-space: normal; }
  .pv-button { width: 100%; max-width: 300px; }
  .pv-footer-sitemap { gap: 48px; }
  .pv-service-nav { gap: 48px; }
}

@media (max-width: 600px) {
  .pv-fv { height: 640px; }
  /* Wider cuts on a phone, so a single cut fills most of the frame. */
  .pv-fv-cut { width: 72vw; }
  .pv-fv-strip-track { animation-duration: 96s; }
  .pv-fv-arrows { inset: 0 12px; }
  .pv-fv-arrow { width: 32px; height: 32px; }
  .pv-news-title { white-space: normal; }
  .pv-contact { height: auto; padding-block: 64px 88px; border-radius: 24px 24px 0 0; }
  .pv-footer-logo { width: 200px; height: auto; }
}

@media (prefers-reduced-motion: reduce) {
  .pv * { transition: none !important; }
  .pv-service-card { animation: none !important; }
  .pv-contact-wave { animation: none; transform: rotate(20deg); opacity: 0.9; }
  .pv-fv-wave { animation: none; transform: none; opacity: 1; filter: none; }
}

/* Keep narrow layouts readable inside the same 10% page margins. */
@media (max-width: 600px) {
  .pv-header { height: 88px; }
  .pv-header-logo img { width: 104px; height: 64px; }
  .pv-header-contact { gap: 6px; padding: 8px 10px; font-size: 12px; }
  .pv-header-contact img { width: 24px; height: 24px; }
  .pv-about-title { width: auto; }
  .pv-about-title span { display: block; }
  .pv-cycle { display: grid; grid-template-columns: 1fr; gap: 24px; width: 100%; aspect-ratio: auto; }
  .pv-cycle-rings, .pv-cycle-plane { display: none; }
  .pv-stage { position: relative; inset: auto; width: 100%; max-width: 320px; margin-inline: auto; padding: 10%; }
  .pv-members { padding-top: 80px; }
  .pv-members-row .pv-arrow { display: none; }
  .pv-service-card-title { font-size: clamp(19px, 5.5vw, 28px); }
  .pv-members-title { font-size: clamp(24px, 6.5vw, 36px); }
  .pv-member-role { left: 12px; font-size: 12px; }
  .pv-member-meta { left: 12px; gap: 10px; font-size: 12px; }
  .pv-member-arrow { right: 12px; width: 28px; height: 28px; }
  .pv-button { padding-inline: 20px; font-size: 14px; }
  .pv-journal { gap: 32px; padding-block: 64px; }
  .pv-journal-stage { --pv-wheel-r: 305px; --pv-card-w: 170px; --pv-card-h: 236px; }
  .pv-journal-wheel { perspective: 1350px; }
  .pv-journal-nav .pv-arrow { width: 40px; height: 40px; font-size: 32px; }
  .pv-journal-nav .pv-arrow:first-child { left: 8px; }
  .pv-journal-nav .pv-arrow:last-child { right: 8px; }
  .pv-news { padding-block: 80px; }
  .pv-news-item { padding-inline: 0; }
  .pv-contact-title { font-size: 8vw; white-space: nowrap; }
  .pv-footer { gap: 48px; padding-top: 64px; }
  .pv-footer-contacts, .pv-footer-copy { font-size: 13px; }
}

.pv-contact::before {
  position: absolute;
  inset: 0;
  background: url("../preview/optimized/contact-background-w1440-e38ad0f70c.webp") center / 100% 100% no-repeat;
  opacity: 0.18;
  content: "";
  transform: scaleX(-1);
}
.pv section[id] { scroll-margin-top: 110px; }

/* The legacy global footer rules hide paragraphs on phones. */
.pv-footer p {
  display: block;
  color: inherit;
  font-size: inherit;
  letter-spacing: normal;
}
.pv-footer p.pv-footer-line { display: flex; }
.pv-footer .pv-footer-copy { color: #959595; font-size: 16px; }

/* Keep an actual 5vw channel between the carousel and the leftmost SERVICE
   outline. The card absorbs viewport changes instead of moving the wordmark. */
@media (min-width: 901px) {
  .pv-service-stage {
    --pv-service-type-size: clamp(72px, 9.7vw, 140px);
    --pv-service-art-width: clamp(146.592px, 19.7492vw, 285.04px);
    --pv-service-card-left: 10%;
    --pv-service-card-right: calc(var(--pv-service-art-width) + 5vw);
    --pv-service-card-width: min(
      785px,
      calc(100% - var(--pv-service-card-left) - var(--pv-service-card-right))
    );
    height: 802px;
  }

  .pv-service-stage::after {
    display: none;
  }

  .pv-service-card {
    width: var(--pv-service-card-width);
    height: 628px;
    aspect-ratio: auto;
    display: flex;
    flex-direction: column;
    padding: 44px clamp(28px, 3.4vw, 49px);
  }

  .pv-service-card-active,
  .pv-service-card-exit {
    right: var(--pv-service-card-right);
    left: auto;
    transform: none;
  }

  .pv-service-card-title {
    min-height: 2.3em;
    font-size: clamp(24px, 2.4vw, 35px);
    line-height: 1.15;
  }

  .pv-service-card-sub {
    min-height: 3.2em;
    margin-top: 4px;
    font-size: clamp(14px, 1.2vw, 18px);
    line-height: 1.6;
  }

  .pv-service-card-media {
    flex: 0 0 auto;
    width: 100%;
    height: auto;
    min-height: 0;
    aspect-ratio: 16 / 9;
    margin-top: 4px;
  }

  .pv-service-nav {
    top: 662px;
    bottom: auto;
    padding-left: min(
      calc(100% - var(--pv-service-card-right) - 128px),
      calc(100% - 168px)
    );
  }
}

@media (min-width: 1241px) {
  .pv-service-card-prev {
    left: calc(100% - var(--pv-service-card-right) - var(--pv-service-card-width) - var(--pv-service-card-width) - 43px);
  }
}

@media (max-width: 600px) {
  .pv-footer .pv-footer-copy { font-size: 13px; }
  .pv-about-plane { --pv-plane-opacity: 0.35; }
  .pv-service { padding-top: calc(64px + 5.69vw); }
}

/* Orbit the nodes while counter-rotating their text to remain upright. */
@media (min-width: 601px) {
  .pv-cycle-orbit {
    position: absolute;
    inset: 0;
    transform-origin: 54% 49%;
    transform: rotate(var(--orbit-angle));
    transition: transform 1200ms cubic-bezier(.45,0,.2,1);
    pointer-events: none;
  }
  .pv-cycle-orbit .pv-stage {
    top: 49%; left: 19.6%; width: 29.5%;
    transform: translate(-50%, -50%) rotate(var(--counter-angle));
    transition: transform 1200ms cubic-bezier(.45,0,.2,1), width 1200ms ease, background-color 800ms ease, color 800ms ease, box-shadow 800ms ease;
    pointer-events: auto;
  }
  .pv-cycle-orbit .pv-stage[data-slot="0"] { width: 39.2%; }
}
.pv-cycle-orbit .pv-stage {
  /* A box shadow follows the circular border without an offscreen filter crop. */
  filter: none;
  box-shadow: 0 0 2.5px #fff, 0 0 7.5px #fff;
  backface-visibility: hidden;
  border: 0; font-family: inherit; cursor: pointer;
  background: #fafafa; color: #000;
}
.pv-cycle-orbit .pv-stage-name,
.pv-cycle-orbit .pv-stage-lead { color: var(--pv-stage-color); }
.pv-cycle-orbit .pv-stage[data-slot="0"] {
  background: var(--pv-stage-color); color: #fff;
  box-shadow: 0 0 10px var(--pv-stage-color);
}
.pv-cycle-orbit .pv-stage[data-slot="0"] .pv-stage-name,
.pv-cycle-orbit .pv-stage[data-slot="0"] .pv-stage-lead { color: #fff; }
.pv-cycle-orbit .pv-stage[data-slot="0"] .pv-stage-name { font-size: 11.9cqw; }
.pv-cycle-orbit .pv-stage:focus-visible { outline: 3px solid #fff; outline-offset: 6px; }
@media (max-width: 600px) {
  .pv-cycle-orbit { display: contents; }
}
@media (prefers-reduced-motion: reduce) {
  .pv-cycle-orbit, .pv-cycle-orbit .pv-stage { transition: none; }
}

/* Shared square canvas keeps the four supplied illustrations aligned. */
.pv-cycle-symbols {
  top: 49%;
  left: 54%;
  transform: translate(-50%, -50%);
  aspect-ratio: 1;
  height: auto;
  pointer-events: none;
}
/* Erase the old drawing before tracing the next icon, stroke by stroke. */
.pv-cycle-symbol {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  color: #fff;
  filter: drop-shadow(0 0 3px rgba(255, 255, 255, 0.25));
  opacity: 0;
  transition: opacity 120ms ease 280ms;
}
.pv-cycle-symbol path {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset 280ms ease-in;
}
.pv-cycle-symbol[data-active="true"] {
  opacity: 0.65;
  transition: opacity 180ms ease 220ms;
}
.pv-cycle-symbol[data-active="true"] path {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 1000ms cubic-bezier(0.22, 1, 0.36, 1);
  transition-delay: calc(220ms + var(--stroke-order) * 75ms);
}
@media (prefers-reduced-motion: reduce) {
  .pv-cycle-symbol,
  .pv-cycle-symbol path,
  .pv-cycle-symbol[data-active="true"],
  .pv-cycle-symbol[data-active="true"] path { transition: none; }
}

/* Keep the moving strip centered; recycle offscreen cards after each step. */
.pv-members-slider .pv-members-list {
  --member-gap: 48px;
  grid-template-columns: repeat(11, var(--pv-member-card-size));
  gap: var(--member-gap);
  max-width: none;
  transform: translateX(calc(-1 * var(--member-move) * (var(--pv-member-card-size) + var(--member-gap))));
}
.pv-members-slider .pv-members-list[data-moving="true"] {
  transition: transform 600ms cubic-bezier(0.22, 1, 0.36, 1);
}
.pv-members-slider .pv-arrow { z-index: 3; }
@media (max-width: 900px) {
  .pv-members-slider .pv-members-list { --pv-member-card-size: min(70vw, 400px); --member-gap: 24px; }
  .pv-members-slider .pv-member:nth-child(n) { display: block; }
  .pv-members-slider .pv-member[data-depth="1"],
  .pv-members-slider .pv-member[data-depth="3"],
  .pv-members-slider .pv-member[data-depth="5"] { transform: translateY(52px); }
  .pv-members-slider .pv-arrow { display: block; top: -54px; }
}
@media (prefers-reduced-motion: reduce) {
  .pv-members-slider .pv-members-list[data-moving="true"],
  .pv-members-slider .pv-member,
  .pv-members-slider .pv-member-photo { transition: none; }
}

/* ---------- motion ---------- */
/* Scroll-in entrances (see PreviewReveal). Blocks start slightly displaced
   and transparent while pending, then ease into place; --pv-reveal-delay
   staggers siblings. The rules key on data-pv-reveal so they never touch the
   works cards or the outline stacks, which run their own reveals. */
.pv [data-pv-reveal][data-reveal] {
  transition:
    opacity 900ms cubic-bezier(0.22, 1, 0.36, 1) var(--pv-reveal-delay, 0ms),
    transform 900ms cubic-bezier(0.22, 1, 0.36, 1) var(--pv-reveal-delay, 0ms);
}
.pv [data-pv-reveal][data-reveal="pending"] {
  opacity: 0;
  transform: var(--pv-reveal-from, translateY(32px));
}
.pv [data-pv-reveal="fade"] { --pv-reveal-from: none; }
.pv [data-pv-reveal="left"] { --pv-reveal-from: translateX(-48px); }
.pv [data-pv-reveal="right"] { --pv-reveal-from: translateX(48px); }
.pv [data-pv-reveal="scale"] { --pv-reveal-from: scale(0.94); }

/* Buttons enter as a band drawn from the left edge: the box is wiped open
   left to right, and the label and arrow follow it in. The band must stay
   solid while it is drawn, so opacity switches on in a single step — it is
   still transitioned because PreviewReveal waits for its end to let go.
   The clip only starts once the button is visible: an element clipped away
   to nothing never intersects, so the observer would not see it arrive. */
.pv [data-pv-reveal="band"] { --pv-reveal-from: none; }
.pv [data-pv-reveal="band"][data-reveal] {
  transition: opacity 800ms steps(1, start) var(--pv-reveal-delay, 0ms);
}
.pv [data-pv-reveal="band"][data-reveal="visible"] {
  animation: pv-band-in 800ms cubic-bezier(0.65, 0, 0.35, 1) var(--pv-reveal-delay, 0ms) both;
}
.pv [data-pv-reveal="band"][data-reveal] > * {
  transition:
    opacity 400ms ease-out calc(var(--pv-reveal-delay, 0ms) + 400ms),
    transform 400ms cubic-bezier(0.22, 1, 0.36, 1) calc(var(--pv-reveal-delay, 0ms) + 400ms);
}
.pv [data-pv-reveal="band"][data-reveal="pending"] > * {
  opacity: 0;
  transform: translateX(-16px);
}

/* The header settles in from above as the page opens; its CONTACT button is
   drawn from the left like every other button. The blended nav copy fades
   only, since it is placed by script and must keep its metrics. */
.pv-header-logo {
  animation: pv-header-in 900ms cubic-bezier(0.22, 1, 0.36, 1) 300ms both;
}
.pv-header-contact {
  animation: pv-band-in 800ms cubic-bezier(0.65, 0, 0.35, 1) 300ms both;
}
.pv-header-contact > * {
  animation: pv-band-label-in 400ms cubic-bezier(0.22, 1, 0.36, 1) 700ms both;
}
.pv-nav-blend { animation: pv-fade-in 900ms ease-out 500ms both; }
@keyframes pv-band-in {
  from { clip-path: inset(0 100% 0 0); }
  to { clip-path: inset(0); }
}
@keyframes pv-band-label-in {
  from { opacity: 0; transform: translateX(-16px); }
  to { opacity: 1; transform: none; }
}
@keyframes pv-header-in {
  from { opacity: 0; transform: translateY(-14px); }
  to { opacity: 1; transform: none; }
}
@keyframes pv-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}


/* The paper plane drifts on the spot; it is decorative, so the loop is slow
   and small enough not to pull the eye from the copy. */
.pv-about-plane-drift { animation: pv-plane-drift 11s ease-in-out infinite; }
@keyframes pv-plane-drift {
  0%, 100% { transform: translate(0, 0) rotate(0deg); }
  50% { transform: translate(1.2%, -2.4%) rotate(-1.5deg); }
}

/* CTA arrows nudge forward on hover. */
.pv-button svg { transition: transform 260ms cubic-bezier(0.22, 1, 0.36, 1); }
.pv-button:hover svg { transform: translateX(6px); }

/* Hover glint: a slanted highlight crosses the button once, left to right.
   It waits off the left edge and is clipped by the button, so it only shows
   mid-sweep. --pv-shine is dark on the white button, where a white glint
   would not read. Touch screens have no hover to leave, so they skip it.
   Buttons opt out of the page-wide link fade (.pv a:hover): the glint is
   their hover, and it needs the face at full strength to show against. */
.pv-button,
.pv-header-contact {
  position: relative;
  overflow: hidden;
}
.pv .pv-button:hover,
.pv .pv-header-contact:hover { opacity: 1; }
.pv-button::after,
.pv-header-contact::after {
  position: absolute;
  inset: 0 auto 0 0;
  width: 45%;
  background: linear-gradient(90deg, transparent, var(--pv-shine, rgb(255 255 255 / 0.7)), transparent);
  transform: translateX(-115%) skewX(-24deg);
  pointer-events: none;
  content: "";
}
.pv-button-light { --pv-shine: rgb(0 0 0 / 0.18); }
@media (hover: hover) {
  .pv-button:hover::after,
  .pv-header-contact:hover::after { animation: pv-shine 750ms cubic-bezier(0.4, 0, 0.2, 1); }
}
.pv-button:focus-visible::after,
.pv-header-contact:focus-visible::after { animation: pv-shine 750ms cubic-bezier(0.4, 0, 0.2, 1); }
@keyframes pv-shine {
  to { transform: translateX(240%) skewX(-24deg); }
}

@media (prefers-reduced-motion: reduce) {
  .pv-header-logo,
  .pv-header-contact,
  .pv-header-contact > *,
  .pv-button::after,
  .pv-header-contact::after,
  .pv-nav-blend,
  .pv-fv-strip-track,
  .pv-about-plane-drift { animation: none; }
  /* The strip stands still here, so there is nothing to step through. */
  .pv-fv-arrows { display: none; }
  .pv [data-pv-reveal][data-reveal="pending"],
  .pv [data-pv-reveal="band"][data-reveal="pending"] > * { opacity: 1; transform: none; }
  .pv [data-pv-reveal="band"][data-reveal="visible"] { animation: none; }
}

/* Typed headings (see PreviewTypewriter): characters are hidden while
   pending and switch on one per step once typing starts. Each character
   carries a caret that shows for its own step, so the caret walks along
   behind the typing; the final character's caret then blinks until the
   heading is done. Characters and carets both animate opacity, so they run
   on the same thread and stay in step. Carets are absolutely positioned,
   so the text never moves. */
[data-pv-type] .pv-type-char { position: relative; }
[data-pv-type][data-typed="pending"] .pv-type-char { opacity: 0; }
[data-pv-type][data-typed="typing"] .pv-type-char {
  opacity: 0;
  animation: pv-type-in 1ms steps(1, end) calc(var(--pv-type-i, 0) * 45ms) forwards;
}
[data-pv-type][data-typed="typing"] .pv-type-char::after {
  content: "";
  position: absolute;
  top: 0.05em;
  left: calc(100% + 0.06em);
  width: 0.08em;
  height: 0.95em;
  background: currentColor;
  opacity: 0;
  animation: pv-type-caret 45ms steps(1, end) calc(var(--pv-type-i, 0) * 45ms) both;
}
/* The last caret: its step, then a blink that starts as the step ends and
   overrides it (later animation wins), until the heading is done. */
[data-pv-type][data-typed="typing"] .pv-type-last::after {
  animation:
    pv-type-caret 45ms steps(1, end) calc(var(--pv-type-i, 0) * 45ms) both,
    pv-type-blink 0.9s steps(1, end) calc(var(--pv-type-n, 0) * 45ms) infinite;
}
@keyframes pv-type-in {
  to { opacity: 1; }
}
@keyframes pv-type-caret {
  0% { opacity: 0; }
  1%, 99% { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes pv-type-blink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  [data-pv-type] .pv-type-char { opacity: 1; animation: none; }
  [data-pv-type] .pv-type-char::after { display: none; }
}

/* Desktop: the card's own metrics follow the viewport with the card (see
   .pv-journal-stage), 1vw = 14.4px at the design width. */
@media (min-width: 1241px) {
  .pv-journal-card-link { gap: 0.69vw; padding: 0.83vw 0.97vw 1.11vw; border-radius: 0.97vw; }
  .pv-journal-card-image { border-radius: 0.56vw; }
  .pv-journal-card-back { border-radius: 0.97vw; font-size: 0.83vw; }
  .pv-journal-card-category { font-size: 0.76vw; }
  .pv-journal-card-title { font-size: 1.04vw; }
  .pv-journal-card-date { font-size: 0.83vw; }
}
