/* ═══════════════════════════════════════════════════════════════════════════
   rzv.studio — work.css  (WORK agent: #work grid + work/*.html case pages)

   Loaded last, after base.css. base.css already owns the grid geometry, the
   .wt-media / .case-* boxes, the mesh recipe and the tilt sheen contract —
   everything here is choreography on top of it:

     · per-tile mesh glow that breathes, and blooms through the cover on hover
     · the cursor sheen (RZV.attachTilt writes --mx/--my onto the tile; the
       gradient lives here because .work-tile is not a .tilt card)
     · the hover reel <video> layer (work.js builds it, fine-pointer only)
     · case-page editorial flow: full-bleed / two-up figures, reel block,
       next-project link with its own mesh
     · the hover thumbnails work.js injects into #purpose rows

   THREE RULES OBEYED THROUGHOUT (base.css header):
   1. No backdrop-filter above a mix-blend-mode child — the ≤760px block drops
      every blend on this page too.
   2. GSAP owns `transform` on tiles and on parallaxed media. `transform` is
      deliberately absent from those transition lists.
   3. NOTHING here starts hidden. Every reveal is set up at runtime by work.js
      and only when RZV.motionOK() — so with motion off, or if work.js never
      runs, the grid and every case page are already fully composed.
   ═══════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════
   #work — the grid
   ═══════════════════════════════════════════════════════════════════════ */

/* The tile becomes the positioning + stacking context for its own glow.
   `transform` stays out of the transition list: RZV.attachTilt writes
   rotationX/rotationY/y/scale every frame (trap 2). */
.work-tile{
  isolation:isolate;
  transition:opacity .4s var(--ease);
}

/* ── Outer mesh glow ──
   Parked behind the media on z-index:-1 (attachTilt's transformPerspective
   makes the tile a stacking context, so this stays inside the tile). It
   breathes on a long alternate loop, per-tile phase-shifted by --breathe,
   which work.js writes so eight tiles never pulse in lockstep. */
.work-tile::before{
  content:'';position:absolute;z-index:-1;pointer-events:none;
  left:4%;right:4%;top:1%;height:74%;border-radius:50%;
  background:
    radial-gradient(58% 62% at 28% 38%, color-mix(in srgb, var(--m1,transparent) 62%, transparent), transparent 72%),
    radial-gradient(52% 58% at 74% 56%, color-mix(in srgb, var(--m3,transparent) 50%, transparent), transparent 72%),
    radial-gradient(46% 50% at 52% 82%, color-mix(in srgb, var(--m2,transparent) 40%, transparent), transparent 74%);
  filter:blur(48px);
  opacity:0.24;
  animation:wtBreathe 11s ease-in-out infinite alternate;
  animation-delay:var(--breathe,0s);
  transition:opacity .65s var(--ease);
}
.work-tile:hover::before,
.work-tile:focus-visible::before{opacity:0.62}
/* Scale ONLY. A running animation beats a plain declaration, so an `opacity`
   keyframe here would silently swallow the hover bump above — the swell is
   what reads as breathing anyway, and the drifting .mb blobs underneath carry
   the brightness variation. */
@keyframes wtBreathe{
  0%{transform:scale(0.94)}
  100%{transform:scale(1.08)}
}
body.phase-light .work-tile::before{filter:blur(54px) saturate(1.2)}

/* ── The media box ──
   Layer order inside .wt-media (base.css sets the box itself):
     0  .mesh          the three drifting blobs
     1  .wt-img        the cover
     2  .wt-reel       the hover video, when the project has one
     3  ::before       mesh tint blooming through the image (screen)
     4  ::after        cursor sheen
   `transform` is on the transition list for the IMAGE only — GSAP never
   touches it, CSS owns the 1.03 hover scale. */
.wt-media{
  transition:border-color .45s var(--ease), box-shadow .55s var(--ease);
}
.work-tile:hover .wt-media{
  border-color:var(--border-hi);
  box-shadow:0 26px 60px -34px rgba(0,0,0,0.75);
}
body.phase-light .work-tile:hover .wt-media{
  box-shadow:0 26px 60px -34px rgba(10,8,10,0.28);
}

.wt-img{
  transform:scale(1.001);
  transform-origin:center center;
  transition:transform .85s var(--ease), opacity .6s var(--ease), filter .7s var(--ease);
}
.work-tile:hover .wt-img{transform:scale(1.03)}

/* The tint. Screen, so it reads as light thrown across the artwork rather
   than paint laid over it — same reasoning as .mb in base.css. Its own slow
   drift keeps it from looking like a flat wash. */
.wt-media::before{
  content:'';position:absolute;inset:-12%;z-index:3;pointer-events:none;
  mix-blend-mode:screen;opacity:0;
  background:
    radial-gradient(42% 52% at 22% 26%, color-mix(in srgb, var(--m1,transparent) 78%, transparent), transparent 68%),
    radial-gradient(38% 48% at 78% 40%, color-mix(in srgb, var(--m3,transparent) 66%, transparent), transparent 70%),
    radial-gradient(46% 46% at 52% 92%, color-mix(in srgb, var(--m2,transparent) 58%, transparent), transparent 72%);
  filter:blur(26px);
  animation:wtTintDrift 24s ease-in-out infinite alternate;
  animation-delay:var(--breathe,0s);
  transition:opacity .6s var(--ease);
}
.work-tile:hover .wt-media::before{opacity:0.52}
@keyframes wtTintDrift{
  0%{transform:translate3d(0,0,0) scale(1)}
  100%{transform:translate3d(4%,-3%,0) scale(1.12)}
}

/* Cursor sheen — the .tilt::before recipe from base.css, re-pointed at
   .wt-media because a work tile is a picture + a caption, not a glass card.
   --mx/--my are written on .work-tile by RZV.attachTilt and inherit down. */
.wt-media::after{
  content:'';position:absolute;inset:0;z-index:4;pointer-events:none;opacity:0;
  background:radial-gradient(150% 150% at var(--mx,50%) var(--my,50%),
    var(--tile-sheen), rgba(255,255,255,0.05) 26%, rgba(255,255,255,0.014) 54%, transparent 80%);
  transition:opacity .45s var(--ease);
}
.work-tile:hover .wt-media::after{opacity:0.85}

/* ── Hover reel ──
   work.js creates this on first hover (fine pointer only, preload="none")
   and removes it on leave, so eight videos never sit in memory at once. It
   fades over the cover; the cover stays put underneath as the poster. */
.wt-reel{
  position:absolute;inset:0;z-index:2;
  width:100%;height:100%;object-fit:cover;
  opacity:0;pointer-events:none;
  transition:opacity .5s var(--ease);
}
.wt-reel.is-on{opacity:1}

/* Caption. The name lifts a hair with the tile; the meta warms from --dim. */
.wt-text{transition:transform .5s var(--ease)}
.wt-name{transition:color .35s var(--ease), letter-spacing .5s var(--ease)}
.work-tile:hover .wt-meta{color:var(--muted)}

/* Keyboard parity — a tab-focused tile gets the same bloom as a hovered one. */
.work-tile:focus-visible{outline:none}
.work-tile:focus-visible .wt-media{
  border-color:var(--border-hi);
  outline:2px solid var(--a2);outline-offset:4px;
}
.work-tile:focus-visible .wt-img{transform:scale(1.03)}
.work-tile:focus-visible .wt-media::before{opacity:0.52}

/* ═══════════════════════════════════════════════════════════════════════
   #purpose — hover thumbnails (DOM injected by work.js)

   index.html is FOUNDATION's file, so the markup for these is built at
   runtime and only on a fine pointer with motion on. The row stays exactly
   as shipped everywhere else.
   ═══════════════════════════════════════════════════════════════════════ */
.pp-row{position:relative}
/* A two-card deck that trails the cursor across the row. GSAP owns every
   transform on both the wrapper (x/y follow) and the cards (rest pose +
   entrance), so neither carries a CSS transform (trap 2). */
.pp-thumbs{
  position:absolute;top:0;left:0;z-index:2;
  width:clamp(150px,13vw,210px);aspect-ratio:4/3;
  pointer-events:none;opacity:0;will-change:transform;
}
.pp-thumb{
  position:absolute;inset:0;overflow:hidden;
  border-radius:var(--r-card);border:1px solid var(--border-hi);
  background:var(--bg-2);
  box-shadow:0 22px 48px -26px rgba(0,0,0,0.8);
}
body.phase-light .pp-thumb{box-shadow:0 22px 48px -28px rgba(10,8,10,0.3)}
.pp-thumb img{width:100%;height:100%;object-fit:cover}
/* Who is Hussain, The Muslim Vibe and Muslim Family Hub all have case pages
   now, so their names are links. Deliberately quiet — a hairline that only
   appears on hover, never a button: this section's whole character is that it
   does not sell. */
.pp-link{color:inherit;border-bottom:1px solid transparent;padding-bottom:2px;
  transition:border-color .35s var(--ease),color .35s var(--ease)}
.pp-link:hover,.pp-link:focus-visible{border-bottom-color:var(--border-hi);color:var(--text)}
.pp-link:focus-visible{outline:none;border-bottom-color:var(--a2)}

/* Name reacts even when the thumbnails are not built (coarse pointer). */
.pp-row .pp-name{transition:color .4s var(--ease), opacity .4s var(--ease)}
.purpose-projects.is-hovering .pp-row:not(.is-hover) .pp-name{opacity:0.42}

/* ═══════════════════════════════════════════════════════════════════════
   CASE PAGES — work/*.html

   base.css already lays out .case-hero / .case-cover / .case-meta /
   .case-body / .case-next. This section adds the editorial image flow and
   the treatments that make the page feel like a spread rather than a form.
   ═══════════════════════════════════════════════════════════════════════ */

/* A page-wide mesh backdrop, tinted by the same --m1/2/3 as the tile that
   led here, so the case page keeps the colour the transition arrived with.
   .mesh--page is base.css's fixed + masked variant; the .mesh.on compound is
   needed to out-specify base.css's own `.mesh.on{opacity:1}`. */
.mesh.case-atmos{--mesh-blur:90px;opacity:0.32;z-index:0}
body.phase-light .mesh.case-atmos{opacity:0.2}

/* The backdrop is fixed at z-index 0, so both the article and the footer have
   to be positioned to stay above it. */
.case-main{position:relative;z-index:1}
body[data-page="case"] .site-footer{position:relative;z-index:1}

/* ── Hero ── */
.case-hero .overline{margin-bottom:clamp(18px,2.4vw,30px)}
/* Each title line rides inside its own clipping box so the SplitText-style
   rise has an edge to come from. Overflow is the only thing the mask needs;
   the line itself is never hidden by CSS (rule 3). */
.case-title .line{display:block;overflow:hidden;padding-bottom:0.08em;margin-bottom:-0.08em}
.case-title .line > span{display:block}

/* ── Cover ──
   Deliberately NOT full-bleed to the viewport edge: it is the receiving half
   of the workhero morph and the tile it grows from is inside the same 1360px
   wrap, so keeping both in the same measure makes the interpolation read as
   one object expanding instead of two rectangles crossing. */
.case-cover{box-shadow:0 40px 90px -50px rgba(0,0,0,0.9)}
body.phase-light .case-cover{box-shadow:0 40px 90px -52px rgba(10,8,10,0.3)}
.case-cover .mesh{opacity:0.9}
.case-cover.no-img .mesh{opacity:1}

/* ── Meta strip ── */
.cm-item .cm-value{font-weight:300;letter-spacing:-0.01em;line-height:1.45}
.cm-list{display:flex;flex-wrap:wrap;gap:4px 0}
.cm-list span{font-size:14.5px;color:var(--text);font-weight:300;letter-spacing:-0.01em}
.cm-list span + span::before{content:'·';color:var(--dim);margin:0 9px}
.cm-visit{
  display:inline-flex;align-items:center;gap:8px;width:fit-content;
  font-size:14.5px;color:var(--text);
  border-bottom:1px solid var(--border-hi);padding-bottom:3px;
  transition:color .3s var(--ease),border-color .3s var(--ease),gap .3s var(--ease);
}
.cm-visit:hover{color:var(--a2);border-color:var(--a2);gap:12px}
.cm-visit .cm-arrow{font-family:var(--mono);font-size:11px;line-height:1}

/* ── The editorial flow ──
   Text blocks live in .wrap; full-bleed figures are siblings of it, so they
   are genuinely viewport-wide without any negative-margin arithmetic against
   a 70ch measure. ── */
/* One rhythm for the whole spread. `gap` rather than per-element margins
   because .case-flow is a flex column — margins would not collapse, so a text
   block's bottom and a figure's top would stack into a canyon. */
.case-flow{position:relative;display:flex;flex-direction:column;
  gap:clamp(48px,6.5vw,104px);padding-top:clamp(16px,2.5vw,36px)}
.case-flow .case-block{max-width:70ch}
.case-flow .case-block .t-body{font-size:clamp(1rem,1.15vw,1.125rem);line-height:1.72;color:var(--muted)}
.case-flow .case-block .overline{margin-bottom:6px}

/* Figures. `.cg-media` is the element GSAP parallaxes — it is over-tall by
   design (112%) so the scrubbed drift never exposes an edge. */
.cg{position:relative;margin:0;overflow:hidden}
.cg-full{width:100%;background:var(--bg-2)}
/* <span>, so it needs blockifying — a <div> inside <figure> would be legal
   too, but the span keeps the figure's only child a phrasing wrapper. */
.cg-full .cg-media{display:block;position:relative;width:100%;aspect-ratio:16/9;overflow:hidden}
.cg-full .cg-media img{position:absolute;inset:-6% 0;width:100%;height:112%;object-fit:cover}

.cg-two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:clamp(18px,2.4vw,36px);overflow:visible}
.cg-two .cg-cell{position:relative;overflow:hidden;border-radius:var(--r-tile);
  border:1px solid var(--border);background:var(--bg-2);aspect-ratio:4/3}
.cg-two .cg-cell img{position:absolute;inset:-5% 0;width:100%;height:110%;object-fit:cover}
/* Second cell drops, so a two-up row reads as a spread rather than a table. */
.cg-two .cg-cell:nth-child(2){margin-top:clamp(0px,4vw,64px)}

/* Transparent-PNG marks (Ni'mah Health's 05) get air and a contained box
   instead of a bleed — object-fit:cover on a logo is vandalism. */
.cg-mark .cg-cell{display:flex;align-items:center;justify-content:center;
  padding:clamp(44px,7vw,110px) clamp(24px,4vw,64px);
  border-radius:var(--r-tile);border:1px solid var(--border);background:var(--surface)}
.cg-mark img{width:min(340px,60%);height:auto;object-fit:contain}

/* Reel block — autoplay/muted/loop/playsinline. Sits in the wrap so it reads
   as a piece of evidence in the argument, not another bleed. */
.cg-reel{border-radius:var(--r-tile);overflow:hidden;
  border:1px solid var(--border);background:var(--bg-2)}
.cg-reel video{width:100%;height:auto;display:block}

/* ── Next project ──
   Its own mesh, its own thumbnail, and the thumbnail is where work.js stamps
   view-transition-name:workhero for a case → case morph. ── */
.case-next{
  position:relative;isolation:isolate;
  display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;
  gap:clamp(20px,3vw,48px);
}
.case-next::before{
  content:'';position:absolute;z-index:-1;pointer-events:none;
  left:-4%;right:-4%;top:-10%;bottom:-10%;border-radius:50%;
  background:
    radial-gradient(46% 62% at 22% 44%, color-mix(in srgb, var(--m1,transparent) 55%, transparent), transparent 72%),
    radial-gradient(42% 58% at 76% 56%, color-mix(in srgb, var(--m3,transparent) 45%, transparent), transparent 72%);
  filter:blur(56px);opacity:0;
  transition:opacity .6s var(--ease);
}
.case-next:hover::before,.case-next:focus-visible::before{opacity:0.5}
.case-next-copy{display:flex;flex-direction:column;gap:10px}
.case-next-name{transition:color .35s var(--ease)}

.case-next-media{
  position:relative;overflow:hidden;
  width:clamp(140px,16vw,220px);aspect-ratio:4/3;
  border-radius:16px;border:1px solid var(--border);background:var(--bg-2);
  --mesh-blur:30px;
  transition:border-color .4s var(--ease),box-shadow .5s var(--ease);
}
.case-next-media .mesh{opacity:0.85}
.case-next-media.no-img .mesh{opacity:1}
.case-next-media .wt-img{
  position:relative;z-index:1;width:100%;height:100%;object-fit:cover;
  transform:scale(1.001);
  transition:transform .8s var(--ease);
}
.case-next:hover .case-next-media{border-color:var(--border-hi);
  box-shadow:0 22px 48px -28px rgba(0,0,0,0.7)}
.case-next:hover .case-next-media .wt-img{transform:scale(1.05)}
.case-next:focus-visible .case-next-media{outline:2px solid var(--a2);outline-offset:4px}
.case-next:focus-visible{outline:none}

/* ═══════════════════════════════════════════════════════════════════════
   FREEZE — reduced motion / motion switch off  (base.css trap 3)
   Everything above is already legible at rest; these rules only stop things
   travelling. Nothing is un-hidden here, because nothing was hidden.
   ═══════════════════════════════════════════════════════════════════════ */
html.rzv-no-motion .work-tile::before,
html.rzv-no-motion .wt-media::before,
html.rzv-no-motion .case-next::before{animation:none}
html.rzv-no-motion .work-tile::before{opacity:0.24}
html.rzv-no-motion .wt-img,
html.rzv-no-motion .case-next-media .wt-img{transition:none}
html.rzv-no-motion .wt-reel{display:none}

@media(prefers-reduced-motion:reduce){
  .work-tile::before,.wt-media::before,.case-next::before{animation:none}
  .wt-img,.case-next-media .wt-img,.wt-text,.wt-media,.pp-thumb{transition:none}
  .work-tile:hover .wt-img,.work-tile:focus-visible .wt-img{transform:none}
  .wt-reel{display:none}
}

/* ═══════════════════════════════════════════════════════════════════════
   MOBILE (≤760px) — mirrors base.css: no blend modes anywhere near a blurred
   ancestor, no hover-only affordances, single column.
   ═══════════════════════════════════════════════════════════════════════ */
@media(max-width:760px){
  .wt-media::before{mix-blend-mode:normal;opacity:0.1}
  .work-tile::before{filter:blur(38px);opacity:0.26;left:2%;right:2%}
  .wt-media::after{display:none}
  .wt-reel{display:none}
  .pp-thumbs{display:none}

  .case-flow{gap:clamp(38px,9vw,64px)}
  .cg-full .cg-media{aspect-ratio:4/3}
  .cg-two{grid-template-columns:1fr;gap:18px}
  .cg-two .cg-cell:nth-child(2){margin-top:0}
  .cg-mark .cg-cell{padding:38px 24px}
  .cg-mark img{width:70%}

  .case-next{grid-template-columns:1fr;align-items:flex-start;gap:22px}
  .case-next-media{width:100%;max-width:280px;aspect-ratio:16/9}
  .case-next::before{filter:blur(42px)}
  .mesh.case-atmos{--mesh-blur:60px;opacity:0.28}
}

/* Coarse pointers never get the hover-only layers, whatever the width. */
@media(hover:none){
  .wt-reel{display:none}
  .pp-thumbs{display:none}
}

/* ═══════════════════════════════════════════════════════════════════════════
   STANDALONE PAGES — /work/ /journey/ /services/ /purpose/ /contact/

   Five folder-style pages that share this stylesheet with the case pages.
   All of them are night phase, always: none of the index's phase choreography
   runs here, so there is no white takeover to style against.

   Same three rules as everything above: no blend mode under a blurred
   ancestor, GSAP owns transform on anything it animates, and NOTHING starts
   hidden — work.js hides and reveals at runtime, only when motion is on.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Header: five links instead of four ─────────────────────────────────
   The nav is now wide enough to crowd a phone. Below 620px it becomes a
   shrink-and-scroll strip rather than wrapping or overflowing the header —
   .nav-pill positions off offsetLeft, which stays correct inside a scroll
   container because the pill scrolls with the links. ── */
@media(max-width:620px){
  .header-inner{gap:10px}
  .nav-center{flex:1 1 auto;min-width:0;overflow-x:auto;overscroll-behavior-x:contain;
    scrollbar-width:none;-ms-overflow-style:none}
  .nav-center::-webkit-scrollbar{width:0;height:0}
  .nav-link{padding:0 9px;font-size:10px;flex:none}
}

/* ── Shared page hero ───────────────────────────────────────────────────
   Same masked line rise as .case-title; work.js wraps each .line's contents
   in a span to give it something to travel inside. ── */
.page-main{position:relative;z-index:1;padding-top:var(--header-h)}
body[data-page] .site-footer{position:relative;z-index:1}

/* ── TWO-ELEMENT HERO ───────────────────────────────────────────────────
   The pattern for every standalone page (and the /services/<slug>/ subpages):
   a mono page NAME, then one block of statement copy, and nothing else.

   The title is no longer display type — it is the page's label, so it takes
   the house overline spec and is the ONE element on the site that wears that
   spec at full --text rather than --muted. That also makes a separate
   .overline above it redundant, which is why the heroes no longer carry one:
   two mono uppercase labels stacked would read as a mistake. The <h1> stays
   an <h1> — this changes how the page's name looks, not what it is. ── */
.page-hero{padding:clamp(72px,13vh,170px) 0 clamp(34px,4.4vw,64px)}
.page-title{
  font-family:var(--mono);
  font-size:clamp(10.5px,0.82vw,12px);
  font-weight:700;                       /* Space Mono ships 400/700 only */
  letter-spacing:0.16em;
  text-transform:uppercase;
  color:var(--text);
  line-height:1.4;
}
.page-title .line{display:block;overflow:hidden;padding-bottom:0.14em;margin-bottom:-0.14em}
.page-title .line > span{display:block}

/* The lede is now the page's primary voice, so it carries the weight the
   title used to. It is either a <p> or a <div> of <p>s — the typography sits
   on the container either way, and the child rule handles the multi-block
   case where sentences genuinely do not join (an ellipsis, a line with no
   full stop). One visual block regardless. */
.page-lede{margin-top:clamp(20px,2.4vw,32px);max-width:34ch;
  font-size:clamp(1.5rem,3.1vw,2.55rem);font-weight:300;letter-spacing:-0.035em;
  line-height:1.16;color:var(--text)}
.page-lede > p{margin:0}
.page-lede > p + p{margin-top:clamp(14px,1.7vw,24px)}

/* Every standalone page carries the same fixed mesh backdrop as a case page,
   tinted per page so each has its own colour temperature. */
.mesh.page-atmos{--mesh-blur:90px;opacity:0.3;z-index:0}

/* ═══ /work/ — the portfolio index ══════════════════════════════════════ */
.pf-note{display:flex;flex-direction:column;gap:12px;max-width:64ch;
  margin-top:clamp(56px,7vw,110px);padding-top:clamp(30px,4vw,48px);border-top:1px solid var(--border)}

/* ═══ /journey/ — the timeline ══════════════════════════════════════════
   A left rail carrying the years and a hairline spine. --jn-rail is the one
   number that positions the whole thing: the first grid column's width, the
   spine's x, and the dots' x are all it. ═══ */
.jn-timeline{--jn-rail:clamp(74px,10vw,152px);position:relative;padding-top:clamp(16px,2.4vw,32px)}

.jn-begin{position:relative;padding-left:calc(var(--jn-rail) + clamp(20px,3vw,54px));
  margin-bottom:clamp(30px,4vw,56px);color:var(--dim)}

/* The spine. Its fill ships at full height and is only cut back by work.js
   when motion is on — with motion off the line is simply drawn. */
.jn-spine{position:absolute;left:var(--jn-rail);top:0;bottom:0;width:1px;
  background:var(--border);overflow:hidden;pointer-events:none}
.jn-spine-fill{position:absolute;inset:0;transform-origin:top center;
  background:linear-gradient(to bottom,
    var(--a1) 0%, var(--a2) 26%, var(--a3) 52%, var(--a4) 74%, var(--a5) 100%);
  opacity:0.85}

.jn-entry{position:relative;display:grid;
  grid-template-columns:var(--jn-rail) minmax(0,1fr);
  gap:clamp(22px,3vw,54px);
  padding-bottom:clamp(52px,7vw,116px)}
.jn-entry:last-child{padding-bottom:0}

/* The dot rides on .jn-entry, not inside .jn-year — the year text travels on
   reveal and the dot must stay welded to the spine while it does. */
.jn-entry::before{
  content:'';position:absolute;z-index:2;
  left:var(--jn-rail);top:0.42em;margin-left:-4.5px;
  width:9px;height:9px;border-radius:50%;
  background:var(--bg-2);border:1px solid var(--border-hi);
  transition:background .5s var(--ease),border-color .5s var(--ease),box-shadow .5s var(--ease);
}
.jn-entry.is-on::before{
  background:var(--a2);border-color:var(--a2);
  box-shadow:0 0 0 5px color-mix(in srgb, var(--a2) 16%, transparent), 0 0 18px color-mix(in srgb, var(--a2) 55%, transparent);
}

.jn-year{font-family:var(--mono);font-weight:700;
  font-size:clamp(12px,1.15vw,15px);letter-spacing:0.06em;
  color:var(--text);text-align:right;padding-right:clamp(14px,1.6vw,22px);
  line-height:1.35;white-space:nowrap;
  /* Years are a column of figures — they must align down the rail. */
  font-variant-numeric:tabular-nums}

.jn-body{display:flex;flex-direction:column;gap:16px;max-width:66ch}
.jn-title{font-size:clamp(1.35rem,2.6vw,2.15rem);font-weight:300;letter-spacing:-0.035em;
  line-height:1.1;color:var(--text)}
/* The lede is the live page's h5 — the sentence that carries the entry. */
.jn-lede{font-size:clamp(1.02rem,1.45vw,1.28rem);font-weight:300;letter-spacing:-0.018em;
  line-height:1.5;color:var(--text)}
.jn-body .t-body + .t-body{margin-top:2px}

/* ═══ /services/ ════════════════════════════════════════════════════════ */

/* ── The tile material (dashboard parity) ──
   The dashboard's answer to a quiet section is rows INSIDE a card tile —
   task-tile.css .tt-row at 14px sitting in a .widget-tile at 24px. So the
   pillar list takes base.css's .tilt surface (24px, sheen + outer glow) and
   each pillar becomes an inner row at --r-card.

   No RZV.attachTilt here, deliberately: these are tall reading panels, and
   rotating a paragraph in 3D as the cursor crosses it fights the text. The
   MATERIAL is the parity item; work.js attaches the cursor sheen ALONE
   (attachSheen) so the surface still tracks the pointer without the tilt. */
.sv-pillars{display:flex;flex-direction:column;gap:10px}
.sv-pillars.tilt{padding:clamp(12px,1.4vw,18px)}
.sv-pillar{position:relative;isolation:isolate;
  display:grid;grid-template-columns:minmax(0,0.9fr) minmax(0,1.1fr);
  gap:clamp(22px,4vw,72px);
  padding:clamp(30px,4vw,58px) clamp(20px,2.6vw,38px);
  border-radius:var(--r-card);
  border:1px solid transparent;background:var(--surface);
  transition:border-color .3s var(--ease),background .35s var(--ease)}
.sv-pillars.tilt .sv-pillar:hover{border-color:var(--border);background:var(--surface-2)}
/* The accent: a soft bloom anchored to the pillar's left edge. Breathing is
   deliberately absent here — this page is a reference, not a showreel. */
.sv-pillar::before{
  content:'';position:absolute;z-index:-1;pointer-events:none;
  left:-6%;top:6%;width:46%;height:88%;border-radius:50%;
  background:radial-gradient(circle at 40% 45%, color-mix(in srgb, var(--acc,var(--a1)) 42%, transparent), transparent 70%);
  filter:blur(64px);opacity:0.34;
  transition:opacity .6s var(--ease);
}
.sv-pillar:hover::before{opacity:0.55}

.sv-head{display:flex;flex-direction:column;gap:14px}
.sv-num{font-family:var(--mono);font-size:clamp(9px,0.7vw,11px);font-weight:700;
  letter-spacing:0.16em;text-transform:uppercase;
  color:color-mix(in srgb, var(--acc,var(--a1)) 74%, var(--text))}
.sv-name{font-size:clamp(1.5rem,3vw,2.5rem);font-weight:300;letter-spacing:-0.04em;line-height:1.06;color:var(--text)}
.sv-body{display:flex;flex-direction:column;gap:clamp(20px,2.4vw,32px);padding-top:clamp(4px,0.8vw,10px)}
.sv-desc{font-size:clamp(1rem,1.35vw,1.18rem);font-weight:300;letter-spacing:-0.015em;
  line-height:1.6;color:var(--muted);max-width:56ch}
.sv-subs{display:flex;flex-wrap:wrap;gap:8px}
.sv-subs li{font-family:var(--mono);font-size:9.5px;font-weight:700;letter-spacing:0.11em;
  text-transform:uppercase;color:var(--muted);
  border:1px solid var(--border);border-radius:var(--r-pill);padding:7px 13px;
  background:var(--surface);
  transition:color .3s var(--ease),border-color .3s var(--ease),background .3s var(--ease)}
.sv-pillar:hover .sv-subs li{border-color:var(--border-hi);color:var(--text)}

/* The tentative offering — kept quiet at 0.62, as it is on the page itself.
   This used to say "index.html marks it .cn-tentative", which was never true
   of any file in the repo: the constellation's own .cn-tentative rule had no
   element either and has since been deleted. The dimming is done HERE, by this
   rule, on the services pages that use it — there is no cross-file marker and
   nothing to keep in step. */
.sv-tentative{display:flex;flex-wrap:wrap;align-items:baseline;gap:14px;
  padding:clamp(26px,3vw,40px) 0;opacity:0.62}
.sv-tentative .sv-name{font-size:clamp(1.15rem,1.9vw,1.5rem)}

/* ── Process ── */
.sv-process{display:flex;flex-direction:column}
.sv-step{display:grid;grid-template-columns:clamp(58px,7vw,110px) minmax(0,1fr);
  gap:clamp(20px,3vw,52px);
  padding:clamp(30px,4vw,56px) 0;border-top:1px solid var(--border)}
.sv-step:last-child{border-bottom:1px solid var(--border)}
/* The dashboard's .tl-num treatment (perf-sections.css:24): a numeral is
   drawn light and tight, not bolded like a label. --faint at weight 300 would
   have vanished, so it sits at --dim: quiet, still legible as a figure. */
.sv-step-n{font-family:var(--mono);font-weight:300;font-size:clamp(15px,2.2vw,30px);
  letter-spacing:-0.04em;color:var(--dim);line-height:1;
  font-variant-numeric:tabular-nums}
.sv-step-c{display:flex;flex-direction:column;gap:14px;max-width:70ch}
.sv-step-t{font-size:clamp(1.15rem,1.9vw,1.6rem);font-weight:300;letter-spacing:-0.03em;
  line-height:1.15;color:var(--text)}

/* ── Closing statement ── */
/* ── /services/<slug>/ subpages ─────────────────────────────────────────
   Same tile material as the parent's pillar list, so a subpage reads as the
   same page one level down. Differences: no 01–04 numeral and no chip list,
   so the left column only has to hold a name and can be narrower. ── */
.sv-subpage .sv-pillar{grid-template-columns:minmax(0,0.62fr) minmax(0,1.38fr)}
.sv-copy{display:flex;flex-direction:column;gap:14px}
/* .sv-body's own gap is sized to separate copy from a chip list; two
   paragraphs of the SAME thought need to sit closer than that. */
.sv-copy .sv-desc{max-width:62ch}

/* The quiet pointer — used on the parent's pillars (to a subpage) and on each
   subpage (back to the process). Deliberately not a button: it is a sideways
   step, not the page's action. */
.sv-more{
  display:inline-flex;align-items:center;gap:8px;width:fit-content;
  font-family:var(--mono);font-size:10px;font-weight:700;letter-spacing:0.12em;
  text-transform:uppercase;color:var(--muted);
  transition:color .3s var(--ease),gap .3s var(--ease);
}
.sv-more:hover{color:var(--text);gap:13px}
.sv-pillar .sv-more{margin-top:clamp(6px,1vw,12px)}
.sv-process-link{margin-top:clamp(26px,3.4vw,44px)}

/* Related work: the same .work-tile machinery (tilt, mesh, hover reel, the
   workhero stamp into the case page), just a plainer grid — the editorial
   every-second-tile drop belongs to the eight-up portfolio, not to a
   two-or-three-tile footnote. */
.work-grid.sv-related{grid-template-columns:repeat(auto-fit,minmax(min(280px,100%),1fr));
  gap:clamp(20px,2.6vw,36px)}
.work-grid.sv-related > .work-tile:nth-child(even){margin-top:0}
.work-grid.sv-related .wt-media{aspect-ratio:16/10}
/* The doc's one-line reason this project is here. */
.wt-note{font-size:13.5px;line-height:1.6;color:var(--muted);max-width:44ch}

.sv-cta-row{display:flex;flex-wrap:wrap;align-items:center;gap:14px}

.sv-close{display:flex;flex-direction:column;align-items:flex-start;gap:clamp(26px,3.4vw,44px);
  padding:clamp(60px,8vw,130px) 0 0}
.sv-close-line{max-width:20ch}

/* ═══ /purpose/ ═════════════════════════════════════════════════════════ */
.pu-mission{display:flex;flex-direction:column;gap:clamp(16px,2vw,26px);max-width:60ch;
  padding:clamp(46px,6vw,96px) 0;border-top:1px solid var(--border);border-bottom:1px solid var(--border);
  margin:clamp(46px,6vw,90px) 0}
/* On /purpose/ each project row is a full spread: name + blurb, then its two
   images. The compact index.html row keeps its floating hover deck instead. */
.purpose-projects .pp-row .cg-two{margin-top:clamp(24px,3vw,40px);grid-column:1 / -1}

/* ── The tile material (dashboard parity) ──
   Same tile-with-rows structure as the services pillars.

   Scoped to `.tilt` — which /purpose/ opts into in its own markup — rather
   than to `.purpose-projects` outright, because index.html's copy of this
   list carries the cursor-following thumbnail deck: .tilt brings
   `overflow:hidden`, which would clip that deck the moment it neared a row
   edge. The standalone page has no deck (its images are in the row), so it
   takes the tile; index keeps its editorial hairline rows. */
.purpose-projects.tilt{padding:clamp(12px,1.4vw,18px);display:flex;flex-direction:column;gap:10px}
/* The `border` shorthand replaces base.css's border-top/last-child
   border-bottom hairlines outright — no need to unset them first. */
.purpose-projects.tilt .pp-row{
  border:1px solid transparent;border-radius:var(--r-card);
  background:var(--surface);
  padding:clamp(26px,3.2vw,42px) clamp(20px,2.6vw,34px);
  transition:border-color .3s var(--ease),background .35s var(--ease)}
.purpose-projects.tilt .pp-row:hover{border-color:var(--border);background:var(--surface-2)}
.pu-collab{display:flex;flex-direction:column;gap:clamp(20px,2.6vw,32px);align-items:flex-start;
  max-width:62ch;margin-top:clamp(52px,7vw,110px)}
.pu-mail{font-size:15px;color:var(--text);border-bottom:1px solid var(--border-hi);padding-bottom:3px;
  transition:color .3s var(--ease),border-color .3s var(--ease)}
.pu-mail:hover{color:var(--a2);border-color:var(--a2)}

/* ═══ /contact/ ═════════════════════════════════════════════════════════ */
.ct-actions{display:flex;flex-wrap:wrap;align-items:center;gap:clamp(18px,2.4vw,32px);
  margin-top:clamp(34px,4.4vw,60px)}
.ct-mail{font-family:var(--mono);font-size:clamp(12px,1.15vw,14.5px);letter-spacing:0.02em;
  color:var(--muted);border-bottom:1px solid var(--border);padding-bottom:4px;
  transition:color .3s var(--ease),border-color .3s var(--ease)}
.ct-mail:hover{color:var(--text);border-color:var(--border-hi)}

.ct-socials{display:flex;flex-wrap:wrap;gap:9px;margin-top:clamp(46px,6vw,90px)}

/* ═══════════════════════════════════════════════════════════════════════════
   PORTAL MATERIAL — lifted from ~/rzv-dashboard/assets/portal.css

   The dashboard's portal is the brand's most finished surface, so the two
   places on this site where someone actually ACTS — the Start project button
   and the enquiry form — are built from its literal recipes rather than an
   impression of them. Sources are credited per block; go read the original
   before changing a number.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── PRIMARY BUTTON  (portal.css .ptl-cbtn.primary:405–406,
      lift/press shadow stack from .ptl-newchat-pearl:164–200) ────────────
   base.css already carries the exact aurora face on .btn::before, but keeps
   it at opacity 0 until hover. The portal's primary button is not a hover
   trick — it wears the aurora at rest and reads as the one committed action
   on the page. So: face on, #0b0b14 text, the 0.42 white border, and the
   pearl's three-layer lift underneath.

   btnDrift (base.css) keeps animating background-position on the same
   pseudo-element; it never touches opacity, so it cannot fight the face
   being permanently on. ── */
.btn-primary{
  color:#0b0b14;
  border-color:rgba(255,255,255,0.42);
  box-shadow:
    0 1px 2px rgba(13,13,20,0.35),
    0 7px 14px -6px rgba(129,140,248,0.55),
    0 16px 32px -14px rgba(0,0,0,0.55);
  transition:
    transform .25s var(--ease-spring),
    box-shadow .25s var(--ease),
    filter .3s var(--ease),
    border-color .35s var(--ease);
}
.btn-primary::before{opacity:1}

/* Spring lift. The mid glow warms and widens; the contact shadow stays tight
   so the button reads as rising off the page rather than growing. */
.btn-primary:hover,
.btn-primary:focus-visible{
  color:#0b0b14;
  transform:translateY(-2px) scale(1.03);
  filter:brightness(1.06) saturate(1.05);
  border-color:rgba(255,255,255,0.55);
  box-shadow:
    0 2px 3px rgba(13,13,20,0.30),
    0 10px 20px -8px rgba(129,140,248,0.60),
    0 20px 40px -16px rgba(0,0,0,0.55);
}
/* Pressed: the lift collapses to a contact line and an inner shade takes
   over, so the button visibly sinks — .ptl-newchat-pearl:active, verbatim. */
.btn-primary:active{
  transform:translateY(1px) scale(0.985);
  filter:none;
  box-shadow:
    inset 0 2px 6px rgba(13,13,20,0.16),
    inset 0 -1px 1px rgba(255,255,255,0.7),
    inset 0 -3px 8px rgba(129,140,248,0.24),
    0 1px 2px rgba(13,13,20,0.30),
    0 3px 8px -4px rgba(129,140,248,0.40);
}
/* On white the indigo bloom disappears and the button loses its edge — the
   same swap base.css makes for the pearl. The aurora face is opaque enough
   that #0b0b14 stays legible on it in both phases. */
body.phase-light .btn-primary{
  border-color:rgba(10,8,10,0.16);
  box-shadow:
    0 1px 2px rgba(13,13,20,0.14),
    0 7px 14px -6px rgba(129,140,248,0.40),
    0 16px 32px -16px rgba(10,8,10,0.28);
}
body.phase-light .btn-primary:hover{border-color:rgba(10,8,10,0.26)}

/* ── CONTACT AURORA  (aurora spec; hosts built by RZV.aurora) ───────────
   The point of this is the glass: .pf-input and .pf-chip carry
   backdrop-filter, and until now they were blurring flat #08080A — a blur of
   nothing looks like nothing. These two hosts put light BEHIND the form so
   the composer surfaces finally have something to refract.

   FOUNDATION owns `.au` itself (position/overflow/opacity, and the six-blob
   DOM via RZV.aurora). Everything here is per-host placement only.

   Geometry is ratio-locked, so every host pins an aspect-ratio; the mask is
   what keeps a hard blob edge from ever meeting a text column. The hosts sit
   OUTSIDE .wrap and outside every reveal group on purpose — a `filter` on an
   ancestor changes what a descendant's backdrop-filter samples (the same
   lesson that put the blur:0 guard in work.js's revealItems). ── */
#enquiry > .au{
  left:0;top:clamp(-40px,-4vw,0px);
  width:min(980px,100%);aspect-ratio:4/5;
  --au-o:0.30;
  transform:translateX(-10%);
  -webkit-mask-image:radial-gradient(120% 90% at 22% 34%,#000 0%,rgba(0,0,0,0.66) 52%,transparent 92%);
  mask-image:radial-gradient(120% 90% at 22% 34%,#000 0%,rgba(0,0,0,0.66) 52%,transparent 92%);
}
#enquiry > .wrap{position:relative;z-index:1}

.page-hero > .au{
  right:0;top:0;
  width:min(620px,80%);aspect-ratio:1/1;
  --au-o:0.20;
  transform:translate(18%,-14%);
  -webkit-mask-image:radial-gradient(90% 90% at 70% 30%,#000,transparent 88%);
  mask-image:radial-gradient(90% 90% at 70% 30%,#000,transparent 88%);
}
.page-hero > .wrap{position:relative;z-index:1}

/* Half the blobs are display:none on a phone, so the hosts widen and lift
   their opacity to compensate for the light that goes with them. */
@media(max-width:760px){
  #enquiry > .au{--au-o:0.40;width:130%;aspect-ratio:3/4}
  .page-hero > .au{--au-o:0.26}
}

/* ── ENQUIRY FORM  (/contact/) ──────────────────────────────────────────
   The portal composer, unrolled into a form. Every surface here is the same
   glass: .ptl-bubble:422–429 for the fields, .ptl-glass:914–920 (== the
   ask-user chips in client-chat.js:375–386) for the options, .sd-seg
   (settings.js:28–31) for the two single-choice rows.

   No JS is required for any of it to work: the options are real checkboxes
   and radios, and :has() paints their checked state (base.css already
   depends on :has(), so this is not a new assumption). work.js only adds the
   sliding pill and the mailto compose. ── */
.pf-form{display:flex;flex-direction:column;gap:clamp(26px,3.4vw,40px);
  max-width:900px;margin-top:clamp(30px,4vw,48px)}
.pf-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(16px,2vw,24px)}
.pf-field{display:flex;flex-direction:column;gap:10px;min-width:0}
.pf-label{color:var(--muted)}
.pf-req{color:var(--a2)}

/* The composer bubble, as a field.
   ── COMPOSES base.css `.glass` ──  The markup is class="glass pf-input", so
   the border, the 160deg gradient, backdrop-filter and the box-shadow all
   come from there and are NOT repeated here. What follows is only what a
   FIELD adds on top.

   The transition is re-declared rather than inherited on purpose: .glass
   springs `transform`, because the dashboard's glass is a pressable surface
   and a text field is not one. This trades that for `box-shadow`, which is
   what animates the focus ring below. */
.pf-input{
  width:100%;display:block;
  border-radius:18px;                    /* tighter than .glass's --r-tile */
  padding:15px 18px;
  color:var(--text);font-family:inherit;font-size:15px;line-height:1.5;
  caret-color:#818CF8;outline:none;
  transition:border-color .35s var(--ease),background .35s var(--ease),box-shadow .35s var(--ease);
}
.pf-input::placeholder{color:var(--dim)}
/* Focus = the bubble's focus-within state plus a faint aurora ring. Same
   specificity as base.css's `.glass:hover` (0,2,0) and declared later, so a
   field that is hovered AND focused reads as focused. The drop shadow is
   restated because a box-shadow cannot be added to — the ring has to ship
   with the layers it sits alongside. */
.pf-input:focus{
  border-color:rgba(255,255,255,0.24);
  background:linear-gradient(160deg,rgba(255,255,255,0.13),rgba(255,255,255,0.04) 50%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.18),
    0 10px 28px -16px rgba(0,0,0,0.4),
    0 0 0 3px rgba(129,140,248,0.16),
    0 0 26px -8px rgba(129,140,248,0.55);
}
.pf-textarea{resize:vertical;min-height:150px;border-radius:var(--r-tile)}

.pf-set{border:none;padding:0;margin:0;display:flex;flex-direction:column;gap:14px;min-width:0}
.pf-set legend{padding:0}

/* ── option chips (client-chat.js .cc-chip — the ask-user recipe) ──
   A real <input> lives inside each label, visually hidden but focusable, so
   selection works, announces and persists with no JS at all. */
.pf-chips{display:flex;flex-wrap:wrap;gap:8px}
/* ── COMPOSES base.css `.glass` ──  class="glass pf-chip" in the markup, so
   the border, gradient, backdrop-filter, box-shadow AND the --r-tile radius
   all come from there.

   SHADOW: the dashboard genuinely disagrees with itself here — portal.css
   `.ptl-glass` ends its drop shadow on -16px, client-chat.js `.cc-chip` on
   -18px, and this chip was lifted from the latter. Consolidated onto .glass's
   -16px: keeping the 2px would mean re-declaring BOTH shadow layers just to
   move one spread value, which is the duplication this change removes, and
   2px of spread under a 28px blur at 40% alpha is not visible. */
.pf-chip{
  position:relative;display:inline-flex;align-items:center;gap:7px;max-width:100%;
  font:inherit;font-size:12.5px;line-height:1.35;color:var(--text);text-align:left;
  padding:9px 15px;cursor:pointer;
  /* Its own: .glass springs transform at .25s and does not transition color;
     a chip needs both, and slightly quicker. */
  transition:border-color .3s var(--ease),background .3s var(--ease),
             transform .22s var(--ease-spring),color .3s var(--ease);
}
.pf-chip input{position:absolute;opacity:0;width:1px;height:1px;margin:0;pointer-events:none}
.pf-chip:hover{border-color:rgba(255,255,255,0.24);
  background:linear-gradient(160deg,rgba(255,255,255,0.14),rgba(255,255,255,0.04) 50%);
  transform:translateY(-1px)}
.pf-chip:has(input:focus-visible){outline:2px solid var(--a2);outline-offset:3px}
/* Selected — .cc-chip.on: brighter border and an aurora tint through it. */
.pf-chip:has(input:checked){
  border-color:rgba(34,211,238,0.75);
  background:linear-gradient(160deg,rgba(34,211,238,0.18),rgba(129,140,248,0.06) 55%);
}
.pf-chip:has(input:checked)::after{content:'✓';font-size:11px;color:var(--a3);line-height:1}

/* ── segmented pills (settings.js .sd-seg) ──
   Without JS the checked segment simply takes the solid fill, exactly as the
   dashboard does it. With JS, .has-pill hands that job to a sliding element
   and the label only carries the inverted text colour. */
.pf-seg{position:relative;display:inline-flex;flex-wrap:wrap;gap:2px;padding:3px;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r-pill);
  max-width:100%}
.pf-seg-btn{
  position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;
  font-family:var(--mono);font-size:10px;font-weight:700;letter-spacing:0.1em;text-transform:uppercase;
  color:var(--muted);border-radius:var(--r-pill);padding:9px 14px;cursor:pointer;white-space:nowrap;
  transition:color .25s var(--ease),background .25s var(--ease);
}
.pf-seg-btn input{position:absolute;opacity:0;width:1px;height:1px;margin:0;pointer-events:none}
.pf-seg-btn:hover{color:var(--text)}
.pf-seg:not(.has-pill) .pf-seg-btn:has(input:checked){background:var(--text);color:var(--bg)}
.pf-seg.has-pill .pf-seg-btn:has(input:checked){color:var(--bg)}
.pf-seg-btn:has(input:focus-visible){outline:2px solid var(--a2);outline-offset:3px}
/* The slider. ALL four coordinates are written by work.js off the checked
   label's own box — top/height included, because the row WRAPS at narrow
   widths and a pill sized to the container would then span every line of it
   instead of sitting on its own. The values here are only the pre-JS resting
   state (invisible, so they are never seen). */
.pf-seg-pill{position:absolute;top:3px;height:0;left:3px;width:0;
  border-radius:var(--r-pill);background:var(--text);z-index:0;opacity:0;pointer-events:none;
  transition:left .38s var(--ease-spring),width .38s var(--ease-spring),
             top .38s var(--ease-spring),height .38s var(--ease-spring),opacity .3s var(--ease)}
.pf-seg.has-pill .pf-seg-pill{opacity:1}

.pf-submit{display:flex;flex-wrap:wrap;align-items:center;gap:clamp(16px,2.2vw,26px);
  margin-top:clamp(6px,1vw,14px)}
/* Says what the button does. No promise, no pitch. */
.pf-hint{color:var(--dim)}

/* ═══ FREEZE ════════════════════════════════════════════════════════════ */
html.rzv-no-motion .sv-pillar::before{opacity:0.34}
html.rzv-no-motion .jn-entry::before{background:var(--a2);border-color:var(--a2)}
html.rzv-no-motion .btn-primary:hover{transform:none}
html.rzv-no-motion .pf-seg-pill{transition:none}
@media(prefers-reduced-motion:reduce){
  .jn-entry::before{transition:none;background:var(--a2);border-color:var(--a2)}
  .sv-subs li,.sv-pillar::before,.pu-mail,.ct-mail{transition:none}
  .pf-chip,.pf-seg-btn,.pf-seg-pill,.pf-input{transition:none}
  .pf-chip:hover{transform:none}
  .btn-primary{transition:none}
  .btn-primary:hover,.btn-primary:focus-visible,.btn-primary:active{transform:none}
}

/* ═══ MOBILE ════════════════════════════════════════════════════════════ */
@media(max-width:900px){
  .sv-pillar,.sv-subpage .sv-pillar{grid-template-columns:1fr;gap:20px}
  .sv-pillar::before{width:70%;left:-14%}
}
@media(max-width:760px){
  .page-lede{max-width:none;font-size:clamp(1.3rem,6vw,1.9rem)}

  /* Rail collapses: the spine hugs the gutter and the year sits above its
     title rather than beside it. */
  .jn-timeline{--jn-rail:9px}
  .jn-entry{grid-template-columns:1fr;gap:10px;padding-left:30px;padding-bottom:clamp(42px,10vw,64px)}
  .jn-entry::before{top:0.3em;left:9px}
  .jn-year{text-align:left;padding-right:0;padding-left:0}
  .jn-begin{padding-left:30px}
  .jn-spine{left:9px}
  .jn-body{max-width:none}

  .sv-step{grid-template-columns:1fr;gap:10px}
  .sv-step-n{font-size:13px;color:var(--dim)}

  .purpose-projects .pp-row .cg-two{grid-template-columns:1fr}
  .ct-actions{gap:16px}

  /* ── iOS TRAP (base.css rule 1) ──
     The fields and chips are `.glass`, so base.css's own ≤760px block already
     strips their backdrop-filter and paints them var(--bg-2). Neither is
     repeated here — what remains is only what base.css does NOT cover:

       · the STATE backgrounds, which live on higher-specificity selectors
         (`.glass:hover` is 0,2,0 and would otherwise put the translucent
         gradient back over an un-blurred surface);
       · flattening the drop shadow — a 28px blurred shadow under every chip
         is real work for a phone GPU and the lift it buys is invisible there;
       · 16px text, or iOS Safari zooms the page on focus (portal.css:743). */
  .pf-input,.pf-chip{box-shadow:inset 0 1px 0 rgba(255,255,255,0.10)}
  .pf-input:focus{
    background:var(--bg-2);
    box-shadow:inset 0 1px 0 rgba(255,255,255,0.14), 0 0 0 3px rgba(129,140,248,0.18);
  }
  .pf-chip:hover{background:var(--bg-2)}
  .pf-chip:has(input:checked){background:color-mix(in srgb,#22D3EE 16%,var(--bg-2))}
  .pf-input{font-size:16px}

  .pf-grid{grid-template-columns:1fr}
  .pf-seg{width:100%}
  .pf-seg-btn{flex:1 1 auto;padding:9px 10px;font-size:9.5px}
  .pf-submit{gap:14px}
  .pf-submit .btn-primary{width:100%}
}
