/* The traverse. A tall .journey track drives one fixed WebGL canvas; the copy
   rides above it in fixed .ovl overlays whose windows the runtime opens and
   closes. Without the runtime (no JS, reduced motion, no WebGL) the same
   markup is an ordinary stack of readable sections with the sky plates as
   flat backgrounds, which is the whole point: motion is a layer, not a gate. */

/* the static home opens on the blue sky plate; the wordmark answers it */
body:not(.traverse-on):has(.ovl-bench) .shd { color: var(--paper); }

/* ---------- the canvas ---------- */
.world-canvas { position: fixed; inset: 0; width: 100%; height: 100%;
  display: block; z-index: 0; background: var(--paper); }
/* Past the final world only the cue leaves. The canvas and the last overlay
   HOLD: hiding them here blanked the final viewport of scroll to bare paper.
   The footer (z-index 3) outranks the canvas (0) and the journey (2), so it
   simply slides up over the held last screen. */
body.traverse-done .j-cue { display: none; }

/* ---------- static (fallback) presentation ---------- */
.journey { position: relative; z-index: 1; }
.ovl { min-height: 100svh; display: flex; align-items: center;
  padding: 120px clamp(20px, 5vw, 56px) 90px; }
.ovl-in { width: 100%; max-width: 1180px; margin: 0 auto; }

.ovl-bench   { background: #2b6cb8 url("sky-main.jpg") center/cover no-repeat; color: #fff;
  text-shadow: 0 1px 6px rgba(12,26,48,.28); }
.ovl-arch    { background: linear-gradient(180deg, #4a86c9, var(--cream)); color: var(--paper); }
.ovl-gallery { background: var(--paper); color: var(--ink); }
.ovl-sky     { background: #2b6cb8 url("sky-open.jpg") center/cover no-repeat; color: #fff; }
.ovl-lab     { background: #2b6cb8 url("sky-deck2.jpg") center/cover no-repeat; color: #fff; }
.ovl-demos   { background: #2b6cb8 url("sky-main.jpg") center/cover no-repeat; color: #fff;
  text-shadow: 0 1px 6px rgba(12,26,48,.28); }
.ovl-ground  { background: var(--paper); color: var(--ink); }

/* the static stack shows its own media; the traverse hides them (the canvas
   is already showing the real thing) */
.j-static { margin-top: 34px; }
.j-static img, .j-static video { width: 100%; border-radius: 14px; display: block;
  border: 1px solid var(--line); }
.j-shots { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
@media (max-width: 760px) { .j-shots { grid-template-columns: 1fr 1fr; } }

/* ---------- traverse presentation ---------- */
body.traverse-on .journey { position: relative; z-index: 2; pointer-events: none; }
body.traverse-on .ovl {
  position: fixed; inset: 0; min-height: 0; opacity: 0; visibility: hidden;
  background: none !important; align-items: flex-end;
  /* the left edge belongs to the journey rail; nothing else may use it */
  padding: 0 clamp(20px, 5vw, 56px) clamp(46px, 9vh, 96px) clamp(150px, 12vw, 196px);
  /* No will-change here. Promoting a full-viewport fixed overlay to its own
     composited layer made the compositor allocate a backing store for it over
     the WebGL canvas, and on integrated graphics that store gets rasterised
     as low-resolution tiles: the large pixelated squares. The opacity fade is
     cheap enough without the hint. */ }
@media (max-width: 760px) {
  /* the rail lives across the top on a phone, so the left reserve goes and
     the top clears the header plus the rail bar */
  body.traverse-on .ovl { padding: 178px 22px 64px; }
  /* the home scene keeps the gate's own state: bright clouds, copy centred.
     The heavy ink scrim and the bottom-anchored drop were the "skip". */
  body.traverse-on .ovl-bench { align-items: center; text-align: center; }
  body.traverse-on .ovl-bench .eyebrow { justify-content: center; }
  body.traverse-on .ovl-bench h1 { margin-inline: auto; }
  body.traverse-on .ovl-bench .lede { margin-inline: auto; }
  body.traverse-on .ovl-bench .j-offer { justify-items: center; }
  body.traverse-on .ovl-bench::after { height: 100%;
    background: linear-gradient(to top,
      rgba(14,24,44,.36) 0%, rgba(14,24,44,.12) 46%, rgba(14,24,44,0) 78%); }
}
body.traverse-on .ovl-in { pointer-events: auto; }
body.traverse-on .j-static { display: none; }
/* the track only needs height once the overlays are fixed */
body.traverse-on .j-step { height: var(--len-vh, 220vh); }
@supports (height: 1svh) { body.traverse-on .j-step { height: var(--len, 220svh); } }
.j-step { height: 0; }

/* ---------- type inside a world ---------- */
.ovl .eyebrow { display: flex; align-items: center; gap: 12px; font: 600 .72rem var(--body);
  letter-spacing: .16em; text-transform: uppercase; margin-bottom: 22px; opacity: .82; }
.ovl .eyebrow::before { content: ""; width: 34px; height: 5px; border-radius: 3px;
  background: var(--accent); flex: none; }
.ovl h1, .ovl h2 { font-family: var(--display); font-weight: 400; letter-spacing: -.025em;
  line-height: .99; margin: 0; max-width: 16ch; }
/* The vh term and the lowered caps are the fit guarantee: wide windows used
   to peg the old 6.4rem cap and the bottom-anchored worlds overflowed the
   top of shorter viewports. 11.5vh/7.8vh leave the tuned 1077x760 look
   untouched but scale the type with the window's height, never past it. */
.ovl h1 { font-size: clamp(2.4rem, min(8vw, 9.5vh), 5.6rem); }
.ovl h2 { font-size: clamp(2rem, min(5.4vw, 6.5vh), 3.7rem); }
.ovl .lede { font-size: clamp(1.02rem, 1.45vw, 1.24rem); line-height: 1.62;
  max-width: 36em; margin-top: 22px; }
.ovl em { font-style: italic; color: var(--accent); }

/* Legibility over a photographic sky is not negotiable, but the first attempt
   at it was a translucent rounded rectangle behind the copy: a glass card, one
   of the tells this studio does not ship. This is a full-bleed gradient
   anchored to the bottom edge instead, so it has no corners, no border and no
   perceptible edge, and reads as the light falling off rather than a panel. */
body.traverse-on .ovl { position: fixed; }
body.traverse-on .ovl::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 74%;
  z-index: -1; pointer-events: none; }
body.traverse-on .ovl-bench::after { height: 82%;
  background: linear-gradient(to top,
    rgba(10,16,28,.82) 0%, rgba(10,16,28,.62) 30%, rgba(10,16,28,.26) 62%, rgba(10,16,28,0) 100%); }
body.traverse-on .ovl-arch::after,
body.traverse-on .ovl-sky::after,
body.traverse-on .ovl-lab::after,
body.traverse-on .ovl-demos::after {
  background: linear-gradient(to top,
    rgba(10,13,18,.86) 0%, rgba(10,13,18,.72) 26%, rgba(10,13,18,.4) 58%, rgba(10,13,18,0) 100%); }
body.traverse-on .ovl-gallery::after {
  background: linear-gradient(to top,
    rgba(250,249,245,.98) 0%, rgba(250,249,245,.94) 34%, rgba(250,249,245,.72) 62%, rgba(250,249,245,0) 100%); }
/* The landing shows the actual photograph, full screen. A 98%-opaque ivory
   wash covered three quarters of it and the backyard may as well not have
   been there; this is a soft foot instead, just enough to seat the copy. */
body.traverse-on .ovl-ground::after {
  height: 88%;
  background: linear-gradient(to top,
    rgba(250,249,245,.90) 0%, rgba(250,249,245,.74) 26%, rgba(250,249,245,.40) 58%, rgba(250,249,245,0) 100%); }
body.traverse-on .ovl-gallery::after { height: 56%; }

/* the copy column stays narrow so the world it sits in is never fully hidden */
body.traverse-on .ovl-bench .ovl-in > *,
body.traverse-on .ovl-arch .ovl-in > *,
body.traverse-on .ovl-lab .ovl-in > * { max-width: 700px; }

/* world text colors under the traverse (the canvas owns the ground) */
body.traverse-on .ovl-bench, body.traverse-on .ovl-arch,
body.traverse-on .ovl-sky, body.traverse-on .ovl-lab,
body.traverse-on .ovl-me,
body.traverse-on .ovl-demos { color: #fff; }
body.traverse-on .ovl-bench .lede, body.traverse-on .ovl-arch .lede,
body.traverse-on .ovl-sky .lede, body.traverse-on .ovl-lab .lede,
body.traverse-on .ovl-demos .lede { color: rgba(255,255,255,.9); }
body.traverse-on .ovl-gallery, body.traverse-on .ovl-ground { color: var(--ink); }
body.traverse-on .ovl-gallery .lede, body.traverse-on .ovl-ground .lede { color: var(--stone); }
.ovl-bench .lede, .ovl-arch .lede, .ovl-sky .lede, .ovl-lab .lede, .ovl-demos .lede { color: rgba(255,255,255,.9); }
.ovl-gallery .lede, .ovl-ground .lede { color: var(--stone); }
.ovl-sky .eyebrow, .ovl-lab .eyebrow, .ovl-bench .eyebrow, .ovl-arch .eyebrow, .ovl-me .eyebrow, .ovl-demos .eyebrow { color: #fff; }

/* hand-written asides */
.j-note { font-family: 'Caveat', cursive; font-size: clamp(1.15rem, 2vw, 1.55rem);
  display: inline-block; transform: rotate(-2.2deg); color: var(--kraft); }
.ovl-gallery .j-note, .ovl-ground .j-note { color: var(--accent-deep); }
/* the static arch gradient runs to cream at its foot, where kraft all but
   disappears; the traverse keeps its own dark scrim and can stay warm */
.ovl-arch .j-note { color: var(--accent-deep); }
body.traverse-on .ovl-arch .j-note { color: var(--kraft); }

/* the loop line in the bench world */
.j-loop { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 26px;
  margin-top: 30px; font-family: var(--display); font-size: clamp(1.2rem, 2.6vw, 1.9rem); }
.j-loopword { transition: opacity .28s, color .28s; }
.j-again { font-family: 'Caveat', cursive; font-size: clamp(1.1rem, 2.2vw, 1.5rem);
  transition: opacity .3s; color: var(--kraft); }
/* Dimming is the traverse's doing, so it is scoped to the traverse. Declared
   the other way round, "until it is actually right" rendered at opacity 0
   for every reader without WebGL, which is the one reader this fallback is
   written for. */
body.traverse-on .j-loopword { opacity: .34; }
body.traverse-on .j-loopword.lit { opacity: 1; color: var(--accent); }
body.traverse-on .j-again { opacity: 0; }
body.traverse-on .j-again.lit { opacity: 1; }

/* buttons */
.j-cta { display: inline-flex; align-items: center; gap: 10px; text-decoration: none;
  font: 600 .86rem var(--body); letter-spacing: .03em; border-radius: 999px;
  padding: 15px 28px; min-height: 44px;
  transition: background .2s, color .2s, border-color .2s, transform .2s; }
.j-cta.solid { background: var(--paper); color: var(--ink); }
.j-cta.solid:hover { background: var(--accent); color: #fff; }
.ovl-gallery .j-cta.solid, .ovl-ground .j-cta.solid { background: var(--ink); color: var(--paper); }
.ovl-gallery .j-cta.solid:hover, .ovl-ground .j-cta.solid:hover { background: var(--accent-deep); color: #fff; }
.j-cta.line { border: 1.5px solid currentColor; color: inherit; }
.j-cta.line:hover { border-color: var(--accent); color: var(--accent); }
.j-ctas { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 30px; }

/* services list in the sky */
.j-offer { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px 34px; margin-top: 26px; max-width: 820px; }
.j-offer li { list-style: none; font: 500 .95rem var(--body); line-height: 1.5;
  padding-top: 12px; border-top: 1px solid rgba(255,255,255,.34); }
.j-offer b { display: block; font-family: var(--display); font-weight: 400;
  font-size: 1.24rem; margin-bottom: 3px; }
@media (max-width: 520px) { .j-offer { grid-template-columns: 1fr; gap: 8px 0; } }

/* pricing row on the ground */
.j-tiers { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-top: 30px; }
.j-tier { border: 1px solid var(--line); border-radius: 14px; padding: 20px 18px;
  background: rgba(255,255,255,.9); text-decoration: none; color: inherit;
  display: flex; flex-direction: column; gap: 6px;
  transition: border-color .2s, transform .2s; }
.j-tier:hover { border-color: var(--accent); transform: translateY(-2px); }
.j-tier .t-n { font-family: var(--display); font-size: 1.3rem; }
.j-tier .t-p { font: 600 .88rem var(--body); color: var(--accent-deep); }
.j-tier .t-l { font-size: .82rem; line-height: 1.45; color: var(--stone); }
@media (max-width: 900px) { .j-tiers { grid-template-columns: 1fr 1fr; } }
@media (max-width: 520px) { .j-tiers { grid-template-columns: 1fr; } }

/* the scroll cue, first world only */
.j-cue { position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%);
  z-index: 3; display: flex; flex-direction: column; align-items: center; gap: 6px;
  color: rgba(255,255,255,.8); pointer-events: none; }
.j-cue svg { width: 16px; height: 24px; }
body:not(.traverse-on) .j-cue { display: none; }

@media (max-width: 760px) {
  .ovl { padding: 104px 20px 80px; }
  /* under the traverse the phone padding lives in the phone pass at the end
     of this sheet (the top clears the header + the horizontal rail) */
  .ovl h1 { font-size: clamp(2.4rem, 11vw, 3.6rem); }
  .ovl h2 { font-size: clamp(1.9rem, 8.4vw, 2.8rem); }
  /* the copy was eating the whole phone screen and burying the world it
     describes; smaller type and a tighter measure give the view back */
  .ovl .lede { font-size: .96rem; line-height: 1.52; margin-top: 16px; }
  .ovl .eyebrow { margin-bottom: 14px; font-size: .66rem; }
  .j-ctas { margin-top: 20px; }
  body.traverse-on .ovl::after { height: 82%; }
}
@media (max-width: 420px) {
  .ovl .lede { font-size: .92rem; }
}

/* ============ radiating orbs ============
   A <details> underneath, so the answer is in the DOM, keyboard-reachable and
   findable by in-page search before any script runs. The dot only decorates
   the summary. */
.j-orbs { display: flex; flex-wrap: wrap; gap: 12px 30px; margin-top: 26px; }
.orb { position: relative; }
.orb-dot { display: inline-flex; align-items: center; gap: 11px; cursor: pointer;
  list-style: none; min-height: 44px; font: 500 .84rem var(--body); color: inherit;
  opacity: .82; transition: opacity .2s; }
.orb-dot::-webkit-details-marker { display: none; }
.orb-dot:hover, .orb[open] .orb-dot { opacity: 1; }
.orb-pulse { position: relative; flex: none; width: 11px; height: 11px; border-radius: 50%;
  background: #fff; box-shadow: 0 0 0 0 rgba(255,255,255,.55); }
.ovl-gallery .orb-pulse, .ovl-ground .orb-pulse { background: var(--accent);
  box-shadow: 0 0 0 0 rgba(217,119,87,.5); }
/* the radiate: one ring leaving the dot, not a glow sitting on it */
.orb-pulse::after { content: ""; position: absolute; inset: 0; border-radius: 50%;
  border: 1.5px solid currentColor; color: #fff; opacity: 0; animation: orbring 2.6s ease-out infinite; }
.ovl-gallery .orb-pulse::after, .ovl-ground .orb-pulse::after { color: var(--accent); }
@keyframes orbring {
  0%   { transform: scale(1);   opacity: .8; }
  70%  { transform: scale(3.1); opacity: 0; }
  100% { transform: scale(3.1); opacity: 0; }
}
.orb[open] .orb-pulse::after { animation: none; }
.orb-label { border-bottom: 1px dashed currentColor; padding-bottom: 2px; }
.orb-card { position: absolute; left: 0; bottom: calc(100% + 14px); z-index: 5;
  width: min(360px, 78vw); padding: 18px 20px; border-radius: 14px;
  background: var(--paper); color: var(--ink); box-shadow: 0 22px 60px rgba(10,13,18,.45);
  border: 1px solid var(--line); }
.orb-card p { margin: 0; font-size: .92rem; line-height: 1.62; color: var(--slate); }
.orb-card::after { content: ""; position: absolute; left: 14px; bottom: -7px; width: 12px; height: 12px;
  background: var(--paper); border-right: 1px solid var(--line); border-bottom: 1px solid var(--line);
  transform: rotate(45deg); }
@media (prefers-reduced-motion: reduce) { .orb-pulse::after { animation: none; } }
/* no-JS/static stack: the cards open downward with room to breathe */
body:not(.traverse-on) .orb-card { position: static; width: auto; max-width: 560px; margin-top: 12px; }
body:not(.traverse-on) .orb-card::after { display: none; }

/* ============ the gallery's per-project detail ============ */
/* the cards are absolutely stacked, so the well has to clear the tallest
   of them or the dot row lands on top of the case-study link */
body.traverse-on .ovl-gallery .j-gal-h { font-size: clamp(1.6rem, 3vw, 2.3rem); }
/* The work is a flat DOM stage: the active project dead centre and crisp, the
   others faded until they arrive. No camera, no tilt, so a node click and the
   scroll always land on the same thing. */
body.traverse-on .ovl-gallery::after { display: none; }
body.traverse-on .ovl-gallery { align-items: stretch; padding: 0; }
/* blue at the left where the work sits, resolving to solid white under the
   description. On .j-gal, not .ovl, because the base .ovl carries
   background:none !important and would otherwise let the warm canvas show. */
body.traverse-on .ovl-gallery .j-gal {
  /* light blue overhead, deeper blue down the left where the work sits, and
     a soft near-white pool behind the description: one radial doing the
     lifting so the transition never bands */
  background:
    radial-gradient(120% 96% at 82% 46%,
      rgba(255,255,255,.97) 0%, rgba(255,255,255,.9) 26%,
      rgba(240,247,253,.55) 48%, rgba(240,247,253,0) 72%),
    linear-gradient(168deg, #d3e7f8 0%, #b9d8f2 42%, #9dc8ec 78%, #8fc0e8 100%); }
/* real clouds, brightened and slowly drifting, kept to the left half so the
   info side stays clean white */
body.traverse-on .ovl-gallery .j-gal::before {
  content: ""; position: fixed; inset: 22% -4% -8% -6%; z-index: -1; pointer-events: none;
  background: url("sky-deck2.jpg") center 70% / cover no-repeat;
  /* real cloud bank sitting in the bottom right, dissolving upward into the
     blue and leftward before it reaches the work */
  -webkit-mask-image: radial-gradient(120% 118% at 78% 100%, #000 8%, rgba(0,0,0,.72) 38%, transparent 74%);
  mask-image: radial-gradient(120% 118% at 78% 100%, #000 8%, rgba(0,0,0,.72) 38%, transparent 74%);
  opacity: .8; animation: workdrift 52s ease-in-out infinite alternate; }
@keyframes workdrift {
  from { transform: scale(1.06) translate(-1.2%, .6%); }
  to   { transform: scale(1.15) translate(1.2%, -.6%); } }
@media (prefers-reduced-motion: reduce) {
  body.traverse-on .ovl-gallery .j-gal::before { animation: none; } }
body.traverse-on .ovl-gallery .j-gal { position: fixed; inset: 0;
  display: grid; grid-template-rows: auto 1fr auto;
  padding: 92px clamp(20px, 5vw, 56px) clamp(26px, 5vh, 54px) clamp(150px, 12vw, 196px);
  /* .journey carries pointer-events:none; the stage must claw them back or
     every dot, arrow and link in here is painted but untouchable. The rail
     and header sit above on z, so they lose nothing. */
  pointer-events: auto; }
.j-gal-head { max-width: 1180px; width: 100%; margin: 0 auto; }
body.traverse-on .ovl-gallery .j-gal-h { font-size: clamp(1.5rem, 2.6vw, 2rem); color: var(--ink); }
body.traverse-on .ovl-gallery .eyebrow { color: var(--accent-deep); }

/* The stage: slides stacked, only the active one lit and interactive.
   overflow is CLIP, not hidden: hidden boxes are still programmatically
   scrollable, and a scrollIntoView from anywhere (browsers do this on click
   and on focus) silently shifted the whole showcase out of its box. clip
   removes the scroll mechanism itself. */
.j-stage { position: relative; z-index: 1; max-width: 1180px; width: 100%;
  margin: 10px auto 0; min-height: 0; overflow: clip; }
.j-slide { position: absolute; inset: 0; overflow: clip;
  display: flex; gap: clamp(18px, 2.6vw, 40px); align-items: stretch;
  opacity: 0; transform: scale(.965); pointer-events: none;
  transition: opacity .5s ease, transform .5s cubic-bezier(.2, .8, .3, 1); }
.j-slide.on { opacity: 1; transform: none; pointer-events: auto; }

/* The frame is its own container and takes its WIDTH from the media: the
   piece is never cropped and never letterboxed, the frame simply hugs it at
   full height. A portrait phone capture narrows the frame; a wide site shot
   widens it; the info card takes whatever room remains, at the same height. */
.j-showcase { flex: 0 1 auto; min-width: 0; max-width: 68%; height: 100%; min-height: 0; }
.j-media-main { position: relative; height: 100%; width: fit-content; max-width: 100%;
  min-height: 0; border-radius: 6px; overflow: clip; }
.j-mainslot { display: block; height: 100%; width: fit-content; max-width: 100%; }
/* height fills the frame, width follows the media's own ratio: no crop, no
   letterbox, the frame simply becomes the shape of the work */
.j-mainslot img, .j-mainslot video { height: 100%; width: auto; max-width: 100%;
  object-fit: contain; display: block; }
.j-thumbstrip { position: absolute; left: 12px; bottom: 12px; z-index: 2;
  display: flex; gap: 8px; }
.j-thumb { position: relative; width: 86px; height: 58px; padding: 0; border: 0; cursor: pointer;
  border-radius: 7px; overflow: clip; background: #10141a;
  outline: 1.5px solid rgba(255, 255, 255, .65); outline-offset: 0;
  box-shadow: 0 8px 22px rgba(10, 15, 25, .35);
  transition: transform .2s, outline-color .2s; }
.j-thumb img, .j-thumb video { width: 100%; height: 100%; object-fit: cover; display: block; }
.j-thumb:hover { transform: translateY(-2px); }
.j-thumb.on { outline-color: var(--accent); }

/* the info card: its own container, right of the frame, same height */
.j-slide-info { flex: 1 1 300px; min-width: 280px; align-self: stretch; height: 100%;
  min-height: 0; overflow: clip;
  display: flex; flex-direction: column; justify-content: center;
  padding: 6px 4px; }
.j-projmeta { font: 600 .7rem var(--body); letter-spacing: .14em; text-transform: uppercase;
  color: var(--accent-deep); margin: 0 0 9px; }
.j-slide-info h3 { font-family: var(--display); font-weight: 400;
  font-size: clamp(1.6rem, 2.6vw, 2.2rem); line-height: 1.06; margin: 0 0 12px;
  letter-spacing: -.02em; color: var(--ink); }
.j-projsum { font: 400 .92rem/1.55 var(--body); color: var(--stone); margin: 0; text-transform: none; letter-spacing: 0; }
.j-projstats { display: flex; gap: 34px; margin: 18px 0 0; }
.j-projstats dt { font-family: var(--display); font-size: 1.4rem; line-height: 1; color: var(--ink); }
.j-projstats dd { margin: 4px 0 0; font: 400 .76rem/1.3 var(--body); color: var(--stone); max-width: 16ch; }
.j-projserv { margin: 16px 0 0; font: 500 .72rem var(--body); letter-spacing: .06em;
  text-transform: uppercase; color: var(--clay, #a3a094);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.j-projlink { display: inline-block; margin-top: 16px; font: 600 .84rem var(--body);
  color: var(--accent-deep); text-decoration: none; text-transform: none; letter-spacing: 0;
  border-bottom: 1px solid currentColor; padding-bottom: 2px; }

/* the node row at the stage's foot */
.j-dots { position: relative; z-index: 5; display: flex; align-items: center; gap: 16px;
  max-width: 1180px; width: 100%; margin: 10px auto 0; }
.j-dot { position: relative; padding: 0; border: 0; background: none; cursor: pointer;
  min-width: 40px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center; }
.j-dot span { width: 9px; height: 9px; border-radius: 50%; background: var(--ink);
  opacity: .28; transition: opacity .2s, transform .2s, background .2s; }
.j-dot.on span { opacity: 1; background: var(--accent); transform: scale(1.4); }
.j-dot:hover span { opacity: .7; }
.j-dot em { position: absolute; bottom: calc(100% - 2px); left: 50%;
  transform: translateX(-50%) translateY(4px); white-space: nowrap;
  font: 500 .74rem var(--body); font-style: normal; color: var(--ink);
  background: var(--paper); border: 1px solid var(--line); border-radius: 7px; padding: 5px 9px;
  opacity: 0; pointer-events: none; transition: opacity .18s, transform .18s; z-index: 3; }
.j-dot:hover em, .j-dot:focus-visible em { opacity: 1; transform: translateX(-50%); }

@media (max-width: 900px) {
  .j-slide { flex-direction: column; gap: 14px; }
  .j-showcase { max-width: 100%; flex: 1 1 auto; }
  .j-media-main, .j-mainslot { width: fit-content; max-width: 100%; margin: 0 auto; }
  .j-slide-info { flex: 0 0 auto; height: auto; padding: 16px 18px; }
  .j-thumb { width: 64px; height: 44px; }
  .j-projstats { gap: 24px; }
}
@media (max-width: 760px) {
  /* the rail bar owns the top of a phone screen; the stage starts under it
     and gives up the desktop left reserve */
  body.traverse-on .ovl-gallery .j-gal { padding: 170px 18px 22px; }
  .j-slide-info { padding: 12px 4px; }
  .j-slide-info h3 { font-size: 1.35rem; }
  .j-projsum, .j-comp-blurb { font-size: .85rem; }
  .j-arr { display: none; }  /* thumb travel: swipe-scroll and the dots */
  .j-dots { justify-content: center; gap: 10px; margin-top: 6px; }
}

/* static fallback: every project shown in full, stacked */
body:not(.traverse-on) .j-gal { display: block; }
body:not(.traverse-on) .j-stage { position: static; }
body:not(.traverse-on) .j-slide { position: static; opacity: 1; transform: none;
  pointer-events: auto; padding: 24px 0; border-top: 1px solid var(--line); }
body:not(.traverse-on) .j-showcase { height: auto; }
body:not(.traverse-on) .j-media-main { height: 320px; }
body:not(.traverse-on) .j-shot-thumbs { height: auto; }
body:not(.traverse-on) .j-dots { display: none; }

/* ============ product cards (lab + demos) ============ */
.j-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px;
  align-items: start; margin-top: 24px; max-width: 780px; }
.j-cards-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); max-width: 1080px; }
.j-card { display: flex; flex-direction: column; overflow: hidden; text-decoration: none;
  border-radius: 14px; border: 1px solid rgba(255,255,255,.26); background: rgba(12,18,28,.42);
  color: #fff; transition: border-color .2s, transform .2s, background .2s; }
.j-card:hover { border-color: #fff; transform: translateY(-2px); background: rgba(12,18,28,.6); }
.j-cardimg { display: block; aspect-ratio: 16/10; overflow: hidden; background: rgba(255,255,255,.08); }
.j-cardimg img { width: 100%; height: 100%; object-fit: cover; display: block; }
.j-cardbody { display: block; padding: 13px 15px 15px; }
.j-card:not(:has(.j-cardimg)) { background: rgba(12,18,28,.55); }
.j-card:not(:has(.j-cardimg)) .j-cardbody { padding: 17px 16px 18px; }
.j-cardtop { display: flex; align-items: baseline; gap: 9px; }
.j-cardtop b { font-family: var(--display); font-weight: 400; font-size: 1.22rem; letter-spacing: -.01em; }
.j-stagepill { font-style: normal; font: 600 .6rem var(--body); letter-spacing: .12em; text-transform: uppercase;
  border: 1px solid currentColor; border-radius: 99px; padding: 2px 7px; opacity: .8; }
.j-stagepill.live { color: #9fe0b4; }
.j-stagepill.lab { color: var(--kraft); }
.j-cardline { display: block; margin-top: 5px; font-size: .84rem; line-height: 1.45; color: rgba(255,255,255,.82); }
@media (max-width: 980px) { .j-cards-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) { .j-cards, .j-cards-4 { grid-template-columns: 1fr; }
  .j-cardimg { aspect-ratio: 21/9; } }

/* ============ the journey rail ============ */
.j-rail { position: fixed; left: clamp(14px, 2.2vw, 30px); top: 50%; transform: translateY(-50%);
  z-index: 40; display: flex; flex-direction: column; gap: 2px; padding: 6px 0; }
body:not(.traverse-on) .j-rail { display: none; }
/* the rail SURVIVES the end: hiding it there stranded the reader at the
   bottom with no way back to the start, which is the one job these two
   pinned buttons have. Over the ink footer it goes light again. */
body.traverse-done .j-rail { color: var(--paper); }
body.traverse-done .j-railtick { background: var(--ink); }
.j-rail-line { position: absolute; left: 5px; top: 10px; bottom: 10px; width: 1px;
  background: currentColor; opacity: .22; }
/* the fill gets BOTH axes from the runtime; each orientation pins the one
   that is not progress */
.j-rail-line i { position: absolute; left: -1px; top: 0; width: 3px; height: 0;
  background: var(--accent); border-radius: 2px; }
@media (min-width: 761px) { .j-rail-line i { width: 3px !important; } }
.j-railstop { display: flex; align-items: center; gap: 11px; background: none; border: 0;
  padding: 7px 6px 7px 0; cursor: pointer; color: inherit; min-height: 34px; text-align: left; }
.j-railtick { position: relative; z-index: 1; flex: none; width: 11px; height: 11px; border-radius: 50%;
  border: 1.5px solid currentColor; background: var(--ink); opacity: .45;
  transition: opacity .2s, transform .2s, background .2s, border-color .2s; }
.j-raillabel { font: 500 .62rem var(--body); letter-spacing: .08em; text-transform: uppercase;
  opacity: .55; white-space: nowrap;
  transition: opacity .2s, font-size .22s, letter-spacing .22s; }
.j-railstop:hover .j-railtick, .j-railstop:focus-visible .j-railtick { opacity: 1; }
.j-railstop:hover .j-raillabel, .j-railstop:focus-visible .j-raillabel { opacity: .9; }
.j-railstop.on .j-railtick { opacity: 1; background: var(--accent); border-color: var(--accent); transform: scale(1.15); }
.j-railstop.on .j-raillabel { opacity: 1; font-size: .82rem; font-weight: 600; letter-spacing: .1em; }
/* the rail rides the world it sits over */
.j-rail { color: var(--paper); }
body.rail-dark .j-rail { color: var(--ink); }
body.rail-dark .j-railtick { background: var(--paper); }
@media (max-width: 760px) {
  /* the whole rail turns a quarter clockwise on a phone: a bar across the
     top, under the header, ticks in a row, every title sideways below its
     tick, the active one larger. Progress fills left to right. */
  .j-rail { left: 0; right: 0; top: calc(54px + env(safe-area-inset-top, 0px));
    transform: none; flex-direction: row; align-items: flex-start;
    justify-content: space-between; gap: 0; padding: 8px 20px 0; }
  .j-rail-line { left: 26px; right: 26px; top: 13px; bottom: auto; width: auto; height: 1px; }
  .j-rail-line i { left: 0; top: -1px; width: 0; height: 3px !important; }
  .j-railstop { flex-direction: column; align-items: center; gap: 7px;
    padding: 0; min-height: 0; min-width: 30px; text-align: center; }
  .j-raillabel { writing-mode: vertical-rl; letter-spacing: .1em; font-size: .52rem;
    max-height: 86px; overflow: hidden; }
  .j-railstop.on .j-raillabel { font-size: .68rem; letter-spacing: .12em; }
}

/* travelling under a held pointer */
body.travelling { cursor: ns-resize; }
body.travelling .ovl-in { transition: none; }
/* the cue says so */
.j-cue .j-cue-note { font-family: 'Caveat', cursive; font-size: 1.05rem; letter-spacing: .01em; }

/* ============ waypoints: the glowing way on ============ */
.j-wp { display: inline-flex; align-items: center; gap: 11px; margin-top: 22px;
  background: none; border: 0; padding: 6px 4px; cursor: pointer; color: inherit;
  min-height: 44px; pointer-events: auto; }
.j-wp i { position: relative; flex: none; width: 13px; height: 13px; border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 12px currentColor; }
.j-wp i::after { content: ""; position: absolute; inset: 0; border-radius: 50%;
  border: 1.5px solid currentColor; opacity: 0; animation: orbring 2.2s ease-out infinite; }
.j-wp span { font: 600 .78rem var(--body); letter-spacing: .12em; text-transform: uppercase;
  border-bottom: 1px solid transparent; transition: border-color .2s; opacity: .88; }
.j-wp:hover span, .j-wp:focus-visible span { border-bottom-color: currentColor; opacity: 1; }
.ovl-gallery .j-wp, .ovl-ground .j-wp { color: var(--accent-deep); }
/* the doorway's waypoint stands in the opening itself */
.j-wp-door { position: absolute; left: 50%; bottom: 30vh; transform: translateX(-50%);
  margin: 0; flex-direction: column; gap: 9px; color: #fff; }
.j-wp-door i { width: 17px; height: 17px; }
.j-wp-door span { font-family: 'Caveat', cursive; text-transform: none; letter-spacing: .02em;
  font-size: 1.25rem; font-weight: 500; text-shadow: 0 1px 12px rgba(20,15,8,.6); }
/* motion affordances do not exist on the static page */
body:not(.traverse-on) .j-wp { display: none; }
@media (prefers-reduced-motion: reduce) { .j-wp i::after { animation: none; } }

/* ============ the stage arrows ============ */
.j-arr { position: absolute; top: 50%; transform: translateY(-50%); z-index: 6;
  width: 46px; height: 46px; border-radius: 50%; border: 1px solid rgba(25,25,23,.14);
  background: rgba(250,249,245,.92); color: var(--ink); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 10px 28px rgba(25,25,23,.16);
  transition: border-color .2s, color .2s, background .2s; }
.j-arr svg { width: 20px; height: 20px; }
.j-arr:hover { border-color: var(--accent); color: var(--accent-deep); background: #fff; }
body.traverse-on .ovl-gallery .j-arr.prev { left: clamp(96px, 9vw, 148px); }
body.traverse-on .ovl-gallery .j-arr.next { right: clamp(10px, 1.6vw, 26px); }
/* the arrow tooltips slide in beside the button, and slide back out on leave */
.j-arr em { position: absolute; top: 50%; transform: translateY(-50%) translateX(0);
  white-space: nowrap; font: 500 .74rem var(--body); font-style: normal; color: var(--ink);
  background: var(--paper); border: 1px solid var(--line); border-radius: 7px; padding: 5px 10px;
  opacity: 0; pointer-events: none; transition: opacity .2s, transform .25s; }
.j-arr.prev em { left: calc(100% + 10px); }
.j-arr.next em { right: calc(100% + 10px); }
.j-arr.prev:hover em, .j-arr.prev:focus-visible em { opacity: 1; transform: translateY(-50%) translateX(4px); }
.j-arr.next:hover em, .j-arr.next:focus-visible em { opacity: 1; transform: translateY(-50%) translateX(-4px); }
body:not(.traverse-on) .j-arr { display: none; }
@media (max-width: 900px) {
  body.traverse-on .ovl-gallery .j-arr.prev { left: 46px; }
  .j-arr { width: 42px; height: 42px; }
  .j-arr em { display: none; }
}

/* rail + waypoint labels share the slide-out manner of every other tooltip */
.j-raillabel, .j-wp span { transition-property: opacity, font-size, letter-spacing, transform; }

/* the tool-bench component panel: its own title + blurb, swapped per thumb */
.j-comp-title { font-family: var(--display); font-weight: 400; font-size: 1.3rem;
  color: var(--accent-deep); margin: 14px 0 6px; letter-spacing: -.01em;
  transition: opacity .2s; }
.j-comp-blurb { font: 400 .92rem/1.55 var(--body); color: var(--stone); margin: 0;
  transition: opacity .2s; }

/* ============ the phone pass ============
   Every world must FIT a phone screen under the top rail: the type steps
   down, cards drop their images and tighten, and the tier grid stays 2-up.
   Last in the sheet on purpose: the phone rules win every earlier cascade. */
@media (max-width: 760px) {
  body.traverse-on .ovl h1 { font-size: 2.35rem; }
  body.traverse-on .ovl h2 { font-size: 1.5rem; }
  body.traverse-on .ovl .lede { font-size: .93rem; line-height: 1.5; margin-top: 12px; }
  body.traverse-on .ovl .eyebrow { margin-bottom: 12px; }
  body.traverse-on .j-offer { gap: 8px 18px; margin-top: 16px; grid-template-columns: 1fr 1fr; }
  body.traverse-on .j-offer h3 { font-size: .98rem; }
  body.traverse-on .j-offer p { font-size: .78rem; line-height: 1.4; }
  body.traverse-on .j-cards, body.traverse-on .j-cards-4 { gap: 7px; margin-top: 12px; }
  body.traverse-on .j-cardimg { display: none; }
  body.traverse-on .j-card .j-cardbody { padding: 10px 13px 11px; }
  body.traverse-on .j-cardtop b { font-size: 1.02rem; }
  body.traverse-on .j-cardline { font-size: .76rem; line-height: 1.35; }
  body.traverse-on .j-stagepill { font-size: .5rem; padding: 2px 7px; }
  body.traverse-on .j-tiers { grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 16px; }
  body.traverse-on .j-tier { padding: 12px 12px 13px; border-radius: 10px; }
  body.traverse-on .j-tier .t-n { font-size: 1.05rem; }
  body.traverse-on .j-tier .t-l { font-size: .72rem; }
  body.traverse-on .j-orbs { margin-top: 12px; }
  body.traverse-on .j-ctas { margin-top: 14px; }
  body.traverse-on .j-cta { padding: 11px 18px; font-size: .86rem; }
  /* the dots carry project travel on a phone; the arrows collide with copy */
  body.traverse-on .j-arr { display: none; }
  body.traverse-on .j-dot { min-width: 30px; }
}

/* ============ the exit ============
   Past the last world the ground overlay RELEASES: it parks at the foot of
   the track and scrolls away with the page, so the footer arrives under it
   instead of sliding over the top of a held screen. */
body.traverse-done .ovl-ground { position: absolute; inset: auto 0 0 0;
  height: 100vh; height: 100svh; }

/* hover tooltips are a mouse affordance; a finger gets the action itself,
   and a tooltip that a tap left stuck open is worse than none */
@media (hover: none) {
  .j-dot em, .j-arr em { display: none; }
}

/* ============ the tour button ============
   Sits after The Cost on the rail: one press flies the whole journey at a
   steady pace; press again (or scroll) and it hands the wheel back. */
.j-railplay { display: flex; align-items: center; gap: 11px; background: none; border: 0;
  padding: 10px 6px 6px 0; cursor: pointer; color: inherit; text-align: left; }
.j-playring { position: relative; z-index: 1; flex: none; width: 22px; height: 22px;
  margin-left: -5.5px; border-radius: 50%; border: 1.5px solid currentColor;
  display: inline-flex; align-items: center; justify-content: center; opacity: .6;
  transition: opacity .2s, background .2s, border-color .2s, color .2s; }
.j-playring svg { width: 9px; height: 10px; margin-left: 1px; }
.j-playring .j-pauseglyph { display: none; margin-left: 0; }
.j-railplay .j-raillabel { opacity: .55; }
.j-railplay:hover .j-playring, .j-railplay:focus-visible .j-playring { opacity: 1; }
.j-railplay:hover .j-raillabel, .j-railplay:focus-visible .j-raillabel { opacity: .9; }
.j-railplay.on .j-playring { opacity: 1; background: var(--accent); border-color: var(--accent); color: #fff; }
.j-railplay.on svg { display: none; }
.j-railplay.on .j-pauseglyph { display: block; }
.j-railplay.on .j-raillabel { opacity: 1; }
@media (max-width: 760px) {
  .j-railplay { flex-direction: column; align-items: center; gap: 7px;
    padding: 0; min-width: 30px; text-align: center; }
  .j-playring { margin-left: 0; width: 20px; height: 20px; margin-top: 2px; }
  .j-railplay .j-raillabel { writing-mode: vertical-rl; }
}

/* the hard ends: Start above the first stop, End below the last */
.j-railend { display: flex; align-items: center; gap: 11px; background: none; border: 0;
  padding: 4px 6px 4px 0; cursor: pointer; color: inherit; text-align: left; }
.j-endglyph { position: relative; z-index: 1; flex: none; width: 11px; height: 11px;
  margin-left: 0; display: inline-flex; align-items: center; justify-content: center;
  opacity: .5; transition: opacity .2s; }
.j-endglyph svg { width: 11px; height: 11px; }
.j-railend .j-raillabel { opacity: .45; }
.j-railend:hover .j-endglyph, .j-railend:focus-visible .j-endglyph { opacity: 1; }
.j-railend:hover .j-raillabel, .j-railend:focus-visible .j-raillabel { opacity: .85; }
@media (max-width: 760px) {
  .j-railend { flex-direction: column; align-items: center; gap: 7px; padding: 0;
    min-width: 22px; text-align: center; }
  .j-railend .j-raillabel { writing-mode: vertical-rl; }
}

/* ============ the face, and the world about the person ============
   A studio that is one person should show the person early. The circle rides
   above the headline on the opening screen and opens the résumé. */
.j-face { display: inline-flex; align-items: center; gap: 14px; text-decoration: none;
  color: inherit; margin-bottom: 20px; }
.j-face img { width: 60px; height: 60px; border-radius: 50%; object-fit: cover;
  object-position: center top; display: block; flex: none;
  outline: 2px solid rgba(255,255,255,.55); outline-offset: 3px;
  transition: outline-color .25s; }
.j-face:hover img, .j-face:focus-visible img { outline-color: var(--accent); }
.j-face-say { font: 500 .82rem var(--body); letter-spacing: .01em; opacity: .9; }
body.traverse-on .ovl-gallery .j-face img, body.traverse-on .ovl-ground .j-face img {
  outline-color: rgba(25,25,23,.28); }

/* the me world: portrait left, the short version right */
.ovl-me { background: #2b6cb8 url("sky-open.jpg") center/cover no-repeat; color: #fff;
  text-shadow: 0 1px 6px rgba(12,26,48,.24); }
body.traverse-on .ovl-me { color: #fff; text-shadow: none; }
body.traverse-on .ovl-me::after {
  background: linear-gradient(to top,
    rgba(10,16,28,.80) 0%, rgba(10,16,28,.58) 32%, rgba(10,16,28,.22) 64%, rgba(10,16,28,0) 100%); }
body.traverse-on .ovl-me .lede { color: rgba(255,255,255,.9); }
.j-mecard { display: flex; align-items: center; gap: clamp(22px, 3.4vw, 52px); max-width: 1000px; }
.j-face-big { margin: 0; flex: none; }
.j-face-big img { width: clamp(130px, 15vw, 210px); height: clamp(130px, 15vw, 210px);
  outline-width: 3px; outline-offset: 5px; }
.j-mebody { min-width: 0; }
.j-mebody h2 { margin-bottom: 4px; }
.j-mestats { display: flex; gap: clamp(18px, 3vw, 42px); margin: 22px 0 0; flex-wrap: wrap; }
.j-mestats dt { font-family: var(--display); font-size: 1.5rem; line-height: 1; }
.j-mestats dd { margin: 5px 0 0; font: 400 .76rem/1.35 var(--body); opacity: .82; max-width: 17ch; }
@media (max-width: 860px) {
  .j-mecard { flex-direction: column; align-items: flex-start; gap: 16px; }
  .j-face-big img { width: 104px; height: 104px; }
}
@media (max-width: 760px) {
  body.traverse-on .ovl-bench .j-face { justify-content: center; margin-bottom: 14px; }
  .j-face img { width: 48px; height: 48px; }
  .j-face-say { font-size: .74rem; }
  .j-mestats { gap: 16px; margin-top: 14px; }
  .j-mestats dt { font-size: 1.15rem; }
  .j-mestats dd { font-size: .7rem; }
}

/* the frame holds its shape and draws a wireframe while a swapped-in piece
   decodes: the info column never moves, the picture simply arrives */
.j-mainslot.loading { position: relative; min-height: 120px; }
.j-mainslot.loading::before {
  content: ""; position: absolute; inset: 0; border-radius: 6px;
  border: 1px solid rgba(25,25,23,.16);
  background:
    linear-gradient(90deg, rgba(25,25,23,0) 0%, rgba(25,25,23,.05) 50%, rgba(25,25,23,0) 100%),
    repeating-linear-gradient(135deg, rgba(25,25,23,.045) 0 9px, rgba(25,25,23,0) 9px 18px);
  background-size: 220% 100%, auto;
  animation: jwire 1.1s ease-in-out infinite; }
@keyframes jwire { from { background-position: 120% 0, 0 0; } to { background-position: -120% 0, 0 0; } }
@media (prefers-reduced-motion: reduce) { .j-mainslot.loading::before { animation: none; } }

/* ============ the richer tier cards ============
   The cost world reads like the pricing page in miniature: kicker, price,
   the honest estimate line, and the first three things included. */
.j-tier .t-k { font: 600 .62rem var(--body); letter-spacing: .14em; text-transform: uppercase;
  color: var(--accent-deep); }
.j-tier .t-pn { font: 400 .68rem/1.35 var(--body); color: var(--stone); margin-top: -4px; }
.j-tier .t-inc { display: flex; flex-direction: column; gap: 6px; margin-top: 10px;
  padding-top: 10px; border-top: 1px solid var(--line); }
.j-tier .t-inc span { display: flex; gap: 7px; align-items: flex-start;
  font: 400 .74rem/1.4 var(--body); color: var(--ink); }
.j-tier .t-inc svg { flex: none; width: 10px; height: 10px; margin-top: 4px;
  color: var(--accent-deep); }
.j-tier .t-go { margin-top: auto; padding-top: 12px; font: 600 .74rem var(--body);
  color: var(--accent-deep); }
.j-tier { display: flex; flex-direction: column; gap: 7px; }
@media (max-width: 760px) {
  /* the phone keeps the essentials; the full sheet is one tap away */
  body.traverse-on .j-tier .t-inc span:nth-child(n+3) { display: none; }
  body.traverse-on .j-tier .t-inc { gap: 4px; margin-top: 7px; padding-top: 7px; }
  body.traverse-on .j-tier .t-pn { font-size: .62rem; }
  /* seventeen dots must wrap, not overflow the screen */
  body.traverse-on .j-dots { flex-wrap: wrap; gap: 2px 4px; }
  body.traverse-on .j-dot { min-width: 24px; min-height: 34px; }
}

/* ============ the runtime chrome frame ============
   Wide pieces that arrive without chrome in their pixels get the same
   browser bar the showcase composites carry, so the stage reads as one
   consistent set of framed screens. Portrait pieces (phone shots, reels)
   stay bare: a browser bar on a phone screen is a costume. */
.j-media-main.chrome { padding: 24px 0 0; background: linear-gradient(#272c36, #1f242c);
  border-radius: 10px; }
.j-media-main.chrome::before { content: ""; position: absolute; top: 8px; left: 12px;
  width: 8px; height: 8px; border-radius: 50%; background: #ff5f57;
  box-shadow: 14px 0 0 #febc2e, 28px 0 0 #28c840; z-index: 3; }
.j-media-main.chrome .j-mainslot,
.j-media-main.chrome .j-mainslot img,
.j-media-main.chrome .j-mainslot video { border-radius: 0 0 7px 7px; }

/* the showcase centres its (now exactly-sized) frame in the stage */
.j-showcase { display: flex; align-items: center; }
.j-media-main { height: auto; }

/* ============ curves and softness ============
   The worlds round off: gentler radii on every frame, thumb, card and tier,
   so the journey reads as soft objects in air rather than cut panels. */
.j-mainslot img, .j-mainslot video { border-radius: 10px; }
.j-media-main { border-radius: 12px; }
.j-media-main.chrome { border-radius: 14px 14px 11px 11px; }
.j-media-main.chrome .j-mainslot,
.j-media-main.chrome .j-mainslot img,
.j-media-main.chrome .j-mainslot video { border-radius: 0 0 9px 9px; }
.j-thumb { border-radius: 9px; }
.j-tier { border-radius: 18px; }
.j-card { border-radius: 14px; }
.j-cardimg { border-radius: 13px 13px 0 0; }
.orb-card { border-radius: 14px; }

/* the clip's own transport, bottom right of the frame */
.j-vbtn { position: absolute; right: 10px; bottom: 10px; z-index: 4;
  width: 36px; height: 36px; border-radius: 50%; border: 0; cursor: pointer;
  background: rgba(15,20,28,.6); color: #fff;
  display: inline-flex; align-items: center; justify-content: center;
  -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px);
  transition: background .2s, transform .2s; }
.j-vbtn:hover { background: rgba(15,20,28,.82); transform: scale(1.06); }
.j-vbtn svg { width: 12px; height: 12px; }
.j-vbtn .pp-play { display: none; }
.j-vbtn.paused .pp-play { display: block; }
.j-vbtn.paused .pp-pause { display: none; }
.j-vbtn[hidden] { display: none; }

/* the arrow pair rides under the dots, out of everyone's way */
.j-arrpair { position: relative; z-index: 5; display: flex; gap: 10px;
  justify-content: center; margin-top: 10px; }
.j-arrpair .j-arr { position: static; transform: none; }
@media (max-width: 900px) {
  .j-arrpair .j-arr { position: static; }
  body.traverse-on .ovl-gallery .j-arrpair .j-arr.prev { left: auto; }
}
@media (max-width: 760px) {
  /* below the dots they no longer collide with copy: back on the phone */
  body.traverse-on .j-arrpair .j-arr { display: inline-flex; width: 38px; height: 38px; }
}
body:not(.traverse-on) .j-arrpair { display: none; }

/* the subtle timeline: a hairline across the frame's foot */
.j-vtime { position: absolute; left: 0; right: 0; bottom: 0; height: 4px; z-index: 4;
  background: rgba(15,20,28,.18); border-radius: 0 0 7px 7px; overflow: hidden;
  cursor: pointer; touch-action: none; transition: height .15s; }
.j-media-main:hover .j-vtime, .j-vtime.scrubbing { height: 8px; }
.j-vtime i { display: block; height: 100%; width: 0; background: var(--accent);
  transition: width .15s linear; }
.j-vtime.scrubbing i { transition: none; }
.j-media-main:not(:has(video)) .j-vtime { display: none; }

/* ============ PHONE: the stripped build ============
   Last in the sheet BY DESIGN: on a phone these rules outrank everything
   above. The header and the rail strip form a hard top boundary no world
   may cross; the type steps well down; the portfolio sheds its overlay
   furniture and reads as a clean native card. */
@media (max-width: 760px) {
  :root { --topband: calc(env(safe-area-inset-top, 0px) + 96px); }
  /* the compact header */
  .shd { padding: 10px 16px; }
  .shd-brand { font-size: 1.02rem; gap: 6px; }
  .shd-mark { width: 22px; height: 13px; }
  .shd-menu { font-size: .66rem; padding: 8px 0; min-height: 36px; gap: 7px; }
  .shd-menu i { width: 20px; }

  /* the rail: one thin strip of ticks; only the active world is named,
     on its own line so nothing ever collides */
  .j-rail { top: calc(env(safe-area-inset-top, 0px) + 44px); padding: 6px 14px 0; align-items: center; }
  .j-rail-line { top: 12px; left: 22px; right: 22px; }
  .j-railstop { flex-direction: row; min-width: 0; gap: 0; padding: 5px 4px; min-height: 0; }
  .j-railtick { width: 8px; height: 8px; }
  .j-raillabel, .j-railstop.on .j-raillabel { writing-mode: horizontal-tb; max-height: none; }
  .j-raillabel { display: none; }
  .j-railstop.on .j-raillabel { display: block; position: fixed; left: 16px; right: 16px;
    top: calc(env(safe-area-inset-top, 0px) + 70px); text-align: center;
    font-size: .58rem; letter-spacing: .16em; }
  .j-railend { padding: 5px 3px; }
  .j-railend .j-raillabel { display: none; }
  .j-endglyph { width: 9px; height: 9px; }
  .j-railplay { padding: 3px; }
  .j-railplay .j-raillabel { display: none; }
  .j-playring { width: 18px; height: 18px; margin: 0; }

  /* the boundary: every world's box BEGINS below the band and scrolls
     inside itself when tall, so content can never ride over the nav */
  body.traverse-on .ovl { inset: var(--topband) 0 0 0; padding: 8px 18px 34px;
    align-items: flex-start; }
  body.traverse-on .ovl-in { max-height: 100%; overflow-y: auto; -webkit-overflow-scrolling: touch; }
  body.traverse-on .ovl-bench { align-items: flex-start; text-align: left; }
  body.traverse-on .ovl-bench .eyebrow { justify-content: flex-start; }

  /* type, well down */
  body.traverse-on .ovl h1 { font-size: 1.8rem; }
  body.traverse-on .ovl h2 { font-size: 1.28rem; }
  body.traverse-on .ovl .lede { font-size: .82rem; line-height: 1.5; margin-top: 8px; max-width: none; }
  body.traverse-on .ovl .eyebrow { font-size: .56rem; margin-bottom: 8px; }
  .j-face img { width: 40px; height: 40px; }
  .j-face-say { font-size: .64rem; }
  .j-face { margin-bottom: 10px; gap: 9px; }
  .j-mecard { gap: 12px; }
  .j-face-big img { width: 78px; height: 78px; }
  .j-mestats { gap: 14px; margin-top: 10px; }
  .j-mestats dt { font-size: .95rem; }
  .j-mestats dd { font-size: .58rem; margin-top: 2px; }
  body.traverse-on .j-cta { padding: 9px 14px; font-size: .74rem; }
  .j-ctas { gap: 8px; }
  .j-loop { gap: 10px; }
  .j-loopword { font-size: .88rem; }
  .j-again { font-size: .8rem; }
  .j-wp span, .orb-label { font-size: .6rem; }
  .j-note { font-size: .95rem; }

  /* the portfolio, clean: capped media, no floating strip, tiny dot row */
  body.traverse-on .ovl-gallery .j-gal { padding: calc(var(--topband) + 4px) 14px 12px; }
  body.traverse-on .ovl-gallery .j-gal-h { font-size: 1.15rem; }
  .j-gal-head .eyebrow { font-size: .56rem; }
  .j-slide { gap: 8px; }
  .j-showcase, .j-media-main, .j-mainslot { width: 100%; max-width: 100%; }
  .j-media-main { height: auto; margin: 0; }
  .j-mainslot img, .j-mainslot video { width: 100%; height: auto; max-height: 34vh; object-fit: contain; }
  .j-thumbstrip { display: none; }
  .j-slide-info { padding: 6px 0 0; min-width: 0; }
  .j-slide-info h3 { font-size: 1.02rem; margin-bottom: 5px; }
  .j-projmeta { font-size: .56rem; margin-bottom: 5px; }
  .j-projsum, .j-comp-blurb { font-size: .74rem; line-height: 1.45; }
  .j-comp-title { font-size: .92rem; margin: 6px 0 3px; }
  .j-projstats { gap: 14px; margin-top: 8px; }
  .j-projstats dt { font-size: .95rem; }
  .j-projstats dd { font-size: .56rem; max-width: 12ch; }
  .j-projserv { font-size: .54rem; margin-top: 7px; }
  .j-projlink { font-size: .7rem; margin-top: 8px; padding-bottom: 1px; }
  body.traverse-on .j-dots { flex-wrap: nowrap; gap: 0; overflow-x: auto; overscroll-behavior-x: contain;
    justify-content: flex-start; margin-top: 4px; scrollbar-width: none; }
  body.traverse-on .j-dots::-webkit-scrollbar { display: none; }
  body.traverse-on .j-dot { min-width: 20px; min-height: 30px; }
  .j-dot span { width: 6px; height: 6px; }
  .j-arrpair { margin-top: 4px; gap: 8px; }
  body.traverse-on .j-arrpair .j-arr { width: 32px; height: 32px; }
  .j-vbtn { width: 30px; height: 30px; right: 8px; bottom: 8px; }
  .j-vbtn svg { width: 10px; height: 10px; }

  /* tiers and cards: essentials only */
  body.traverse-on .j-tier .t-inc { display: none; }
  body.traverse-on .j-tier { padding: 9px 11px 10px; gap: 3px; border-radius: 12px; }
  .j-tier .t-k { font-size: .5rem; }
  .j-tier .t-n { font-size: .9rem; }
  .j-tier .t-p { font-size: .74rem; }
  .j-tier .t-pn { font-size: .52rem; margin-top: -2px; }
  .j-tier .t-l { font-size: .6rem; line-height: 1.35; }
  .j-tier .t-go { font-size: .58rem; padding-top: 4px; }
  body.traverse-on .j-tiers { gap: 7px; margin-top: 10px; }
  body.traverse-on .j-offer { gap: 6px 14px; margin-top: 10px; }
  body.traverse-on .j-offer h3 { font-size: .82rem; }
  body.traverse-on .j-offer p { font-size: .64rem; line-height: 1.4; }
  body.traverse-on .j-cards, body.traverse-on .j-cards-4 { gap: 6px; margin-top: 10px; }
  body.traverse-on .j-cardtop b { font-size: .86rem; }
  body.traverse-on .j-cardline { font-size: .64rem; line-height: 1.35; }
  body.traverse-on .j-card .j-cardbody { padding: 8px 11px 9px; }
  body.traverse-on .j-stagepill { font-size: .44rem; padding: 2px 6px; }
  .j-cue { display: none; }
  .theme-chip { top: auto; bottom: 16px; right: 14px; }
}

/* the work-world face is a phone-only lead */
.j-face-work { display: none; }
