/* base.css — shared by every page on jamresources.com.
   Extracted from index.html on 2026-07-27. Home-only styles (hero, ticker,
   services, lab, process) stayed inline; everything here is used by two or
   more pages. Load order matters: this file first, then any page-local
   <style> block, so page-local rules win ties. */

  /* ============ COLOUR SYSTEM ============
     Colour is a system, not a list. The previous palette ran seven fully
     saturated accents against near-black with no ranking, so nothing could
     be emphasised — everything already was. Four roles now:

       surfaces     an elevation ramp. Never pure black: saturated accents
                    on #000 make edges vibrate.
       text         four steps, every one AA on --s2 or darker.
       brand / heat one brand gradient plus exactly ONE loud accent,
                    reserved for CTAs and single-point emphasis.
       categorical  five service identifiers with lightness unified around
                    L≈72, so they identify without ranking each other. The
                    old set spanned a huge lightness range, which silently
                    made Marketing outrank Graphic design for no reason.

     Every legacy name still resolves, as an alias onto a role — so the
     ~143 existing var(--raspberry) style usages across the site re-skin
     from here rather than needing individual rewrites. Prefer role names
     in new code; the aliases exist for the back catalogue. */
  :root{
    /* ── surfaces ── */
    --s0:#08080d;            /* page            */
    --s1:#0d0d15;            /* band            */
    --s2:#12121c;            /* card            */
    --s3:#1a1a28;            /* raised / hover  */
    --line-1:rgba(244,242,238,.07);
    --line-2:rgba(244,242,238,.13);
    --line-3:rgba(244,242,238,.24);
    --line-4:rgba(244,242,238,.45);  /* strong border (pad hover) */
    --ring:rgba(244,242,238,.7);     /* selection ring (colorway chip) */

    /* ── scrims: --s0 black at fixed opacities ── */
    --scrim-1:rgba(8,8,13,.6);   /* ticker, soft glass */
    --scrim-2:rgba(8,8,13,.72);  /* nav, badges */
    --scrim-3:rgba(8,8,13,.82);  /* footer, heavy glass */
    --scrim-4:rgba(8,8,13,.96);  /* near-solid panels */


    /* ── text ── */
    --t-hi:#f4f2ee;
    --t-up:#c8c5d2;          /* hero + CTA subtitles: above body, below headline */
    --t-mid:#aeabba;         /* body — 8.3:1 on --s2 */
    --t-lo:#8b8799;          /* labels — 4.9:1 on --s3, worst case. Never darker. */

    /* ── brand ── */
    --brand-1:#2de2ff;
    --brand-2:#3a5bff;
    --brand-3:#1f31c9;

    /* ── heat: the only loud accent ── */
    --heat:#ff3d6e;

    /* ── categorical: service identity only ── */
    --cat-w:#ff6b86;         /* websites       */
    --cat-a:#57d9f2;         /* apps           */
    --cat-g:#a582ff;         /* graphic design */
    --cat-m:#f5bd5c;         /* marketing      */
    --cat-b:#ff78d2;         /* branding       */
    --cat-r:#6fdba8;         /* reporting & data — the one non-warm/violet hue left at this lightness */
    --ok:#8fe36b;

    /* ── legacy aliases ──
       --violet used to be #8a2eff, which measures 3.66:1 on a dark card and
       failed AA wherever it coloured text (index.html's service 03 hover).
       Pointing it at --cat-g retires that failure everywhere at once, and
       makes --violet / --violet-text the same safe colour. */
    --void:var(--s0);
    --void-soft:var(--s2);
    --panel:rgba(13,13,21,.92);
    --raised:var(--s3);
    --white:var(--t-hi);
    --dim:var(--t-mid);
    --dim-bright:var(--t-up);
    --raspberry:var(--heat);
    --cobalt:var(--brand-2);
    --cyan:var(--brand-1);
    --violet:var(--cat-g);
    --violet-text:var(--cat-g);
    --gold:var(--cat-m);
    --magenta:var(--cat-b);
    --lime:var(--ok);
    --cobalt-bright:var(--brand-2); /* h1 ink gradients */
    --cobalt-deep:var(--brand-3);   /* wordmark + ink gradient tail */
    --ember:#ff7a18;         /* CTA gradient */
    --crimson:#e8192c;       /* CTA gradient */
    --crimson-deep:#b3122f;  /* CTA gradient */


    /* ── geometry ── */
    --r-xs:3px;
    --r-sm:8px;
    --r-md:12px;
    --r-card:18px;
    --r-pill:100px;

    /* ── elevation ── */
    --shadow-lift:0 30px 80px -30px rgba(0,0,0,.9);
    --shadow-low:0 6px 18px -6px rgba(0,0,0,.8);

    /* ── motion curves ── */
    --ease-out:cubic-bezier(.2,.6,.2,1);    /* reveals, most transitions */
    --ease-sweep:cubic-bezier(.7,0,.2,1);   /* svc row wash */
    --ease-inout:cubic-bezier(.65,0,.35,1);
    --ease-flip:cubic-bezier(.6,.05,.25,1); /* lab card A/B flip */
    --ease-drift:cubic-bezier(.3,.7,.2,1);

    /* ── document layers ── */
    --z-bg:0;
    --z-content:10;
    --z-nav:40;
    --z-progress:50;
    --z-grain:60;
    --z-modal:200;
    --z-skip:300;
    --z-reveal:999;


    /* ── type scale (Phase 2/3) ── */
    --step-0:clamp(1rem,1.35vw,1.2rem);
    --step-1:clamp(1.25rem,2.2vw,1.65rem);
    --step-2:clamp(1.5rem,2.6vw,2.4rem);
    --step-3:clamp(1.8rem,4.6vw,3.4rem);
    --step-4:clamp(2.2rem,5.6vw,4.4rem);
    --step-5:clamp(2.4rem,7vw,4.6rem);
    --step-6:clamp(2rem,9vw,7rem); /* floor 2rem: 9-10 glyph Syne heroes must clear 375px */

    /* ── space scale ── */
    --sp-1:clamp(16px,2.3vw,26px);
    --sp-2:clamp(20px,3.2vw,40px);
    --sp-3:clamp(30px,4.6vw,58px);
    --sp-4:clamp(48px,7vw,84px);
    --sp-5:clamp(64px,9.5vw,116px);
    --sp-6:clamp(90px,11vw,150px);
    --sp-7:clamp(110px,14vw,190px);
    --gutter:clamp(20px,4vw,56px);   /* page gutter — shell + nav */
    --hero-pad:clamp(130px,16vw,175px); /* sub-page hero top clearance */

    --mono:'IBM Plex Mono',monospace;
    --display:'Syne','Syne Fallback',sans-serif;
    --body:'Manrope','Manrope Fallback',sans-serif;
  }

  /* ============ FONT FALLBACKS ============ */
  /* Metric-matched local fallbacks so the swap doesn't reflow the page.
     Numbers are measured, not guessed (2026-07-28): size-adjust is the
     rendered width ratio vs Arial at the weights the site actually uses
     (Syne 800 uppercase display strings: 1.650; Manrope ~500 body copy:
     1.04); ascent/descent come from each font's hhea table divided by
     size-adjust (Syne 925/-275 @1000upm, Manrope 2132/-600 @2000upm). */
  @font-face{
    font-family:'Syne Fallback';src:local('Arial');
    size-adjust:165%;ascent-override:56.1%;descent-override:16.7%;line-gap-override:0%;
  }
  @font-face{
    font-family:'Manrope Fallback';src:local('Arial');
    size-adjust:104%;ascent-override:102.5%;descent-override:28.9%;line-gap-override:0%;
  }

  /* ============ SHARED FRAME ============ */
  /* Native cross-document View Transitions — a Services tile morphs into its
     detail page's hero, matched by view-transition-name (set per-service in
     each page's own <style> block: the .svc tile here, .svc-hero there).
     Unsupported browsers ignore this at-rule entirely and just navigate —
     there is no fallback to write, that's the whole point of the spec. */
  @view-transition{
    navigation:auto;
  }

  *{margin:0;padding:0;box-sizing:border-box}
  /* scroll-padding-top clears the FIXED nav for every in-page anchor at once.
     It must match the SCROLLED nav height (14px padding, not the resting 22px),
     because by the time an anchor jump lands the nav has already shrunk.
     Per-anchor scroll-margin does not scale — there are ~20 anchors site-wide
     plus every ⌘K hash destination, and only [id^="lab-"] was ever covered. */
  html{scroll-behavior:smooth;overflow-x:hidden;scroll-padding-top:88px}
  /* dark-only site: tell the UA so native selects, scrollbars and form
     controls stop rendering light against it */
  :root{color-scheme:dark}
  body{
    background:var(--void);
    color:var(--white);
    font-family:var(--body);
    font-size:16px;
    line-height:1.6;
    -webkit-font-smoothing:antialiased;
  }
  /* NOTE — body must NOT carry overflow-x:hidden.
     html's overflow-x:hidden above already propagates to the viewport, so
     the copy here was redundant; what it was not was harmless. Any overflow
     value other than visible makes body a scroll container, and because body
     is exactly as tall as its own content it is a scroll container that can
     never scroll. Every view() timeline resolves to the nearest scrollport,
     found that dead one, and went permanently inactive — which silently
     disabled both the reveal engine and the process glow site-wide.
     If horizontal overflow ever returns, fix the element that overflows;
     do not put overflow-x back on body. */
  ::selection{background:var(--raspberry);color:var(--void)}
  a{color:inherit;text-decoration:none}
  :focus-visible{outline:2px solid var(--cyan);outline-offset:3px;border-radius:var(--r-xs)}
  /* No <img> ships today — every mark is inline SVG, gradient or canvas. This
     is the guard for the first photo that lands (a case study on /work/):
     width/height keep the box reserved so it can't shift layout on load. */
  img,video{max-width:100%;height:auto}

  /* ============ SKIP LINK ============
     WCAG 2.2 §2.4.1 Bypass Blocks, Level A. Every page repeats the same nav
     block above a full-height hero, so without this a keyboard user tabs the
     whole nav on every page.
     Hidden by TRANSFORM, never display:none or visibility:hidden — those also
     remove it from the tab order, which is the standard way to ship a skip
     link that silently does nothing. */
  .skip-link{
    position:fixed;top:12px;left:12px;z-index:var(--z-skip);
    font-family:var(--mono);font-size:.72rem;letter-spacing:.1em;text-transform:uppercase;
    background:var(--white);color:var(--void);
    padding:.85em 1.2em;border-radius:var(--r-pill);
    transform:translateY(calc(-100% - 24px));
    transition:transform .18s var(--ease-out);
  }
  .skip-link:focus{transform:translateY(0)}
  @media (prefers-reduced-motion:reduce){.skip-link{transition:none}}
  /* the jump target must not draw a ring just because we moved focus to it */
  main:focus{outline:none}

  /* ============ BACKGROUND ============
     Replaces the cursor-reactive WebGL ink sim (fluid.js) on the homepage
     and the drifting .blob-fallback everywhere else. The sim went because
     it was the only thing on the page that moved in response to the
     pointer, so the eye tracked it instead of the copy; the blobs went so
     that all eight pages share one treatment instead of two.

     One static layer, every page, no JS, no per-page body class. To change
     the treatment, change this one rule — nothing else references it. */
  .bg{
    position:fixed;inset:0;z-index:0;pointer-events:none;
    background:
      radial-gradient(120% 90% at 12% -10%, color-mix(in oklab, var(--brand-2) 22%, transparent) 0%, transparent 55%),
      radial-gradient(100% 80% at 92% 108%, color-mix(in oklab, var(--heat) 15%, transparent) 0%, transparent 55%);
  }

  /* film grain */
  .grain{
    position:fixed;inset:-50%;width:200%;height:200%;z-index:var(--z-grain);pointer-events:none;opacity:.05;
    background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 400 400' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
    animation:grainShift 1.2s steps(4) infinite;
  }
  @keyframes grainShift{
    0%{transform:translate(0,0)}25%{transform:translate(-2%,1%)}
    50%{transform:translate(1%,-2%)}75%{transform:translate(-1%,2%)}100%{transform:translate(2%,1%)}
  }

  /* ============ NAV ============ */
  nav{
    position:fixed;top:0;left:0;right:0;z-index:var(--z-nav);
    display:flex;align-items:center;justify-content:space-between;
    padding:22px var(--gutter);
    transition:background .4s,backdrop-filter .4s,padding .4s;
  }
  nav.scrolled{background:var(--scrim-2);backdrop-filter:blur(14px);padding:14px var(--gutter)}
  .wordmark{font-family:var(--display);font-weight:800;font-size:1.35rem;letter-spacing:.02em;display:flex;align-items:baseline;gap:.45em;line-height:1}
  .wordmark .jam{background:linear-gradient(100deg,var(--cyan),var(--cobalt) 60%,var(--cobalt-deep));-webkit-background-clip:text;background-clip:text;color:transparent}
  .wordmark .res{font-family:var(--mono);font-weight:400;font-size:.62rem;letter-spacing:.34em;color:var(--dim);text-transform:uppercase}
  .wordmark .dia{font-style:normal;color:var(--raspberry);font-size:.6em;line-height:1;transform:translateY(-.05em)}
  .nav-links{display:flex;gap:var(--sp-2);align-items:center;font-family:var(--mono);font-size:.78rem;letter-spacing:.12em;text-transform:uppercase}
  .nav-links a.nl{color:var(--dim);transition:color .25s;position:relative}
  @media (hover:hover){.nav-links a.nl:hover{color:var(--white)}}
  .nav-links a.nl::after{content:'';position:absolute;left:0;bottom:-5px;height:1px;width:0;background:var(--raspberry);transition:width .3s}
  @media (hover:hover){.nav-links a.nl:hover::after{width:100%}}
  .btn{
    display:inline-flex;align-items:center;gap:.6em;
    font-family:var(--mono);font-size:.78rem;letter-spacing:.12em;text-transform:uppercase;
    color:var(--void);background:var(--white);border:1px solid var(--white);
    padding:.85em 1.6em;border-radius:var(--r-pill);cursor:pointer;
    transition:background .3s,color .3s,transform .3s,box-shadow .3s;
  }
  @media (hover:hover){.btn:hover{background:var(--raspberry);border-color:var(--raspberry);color:var(--white);transform:translateY(-2px);box-shadow:0 12px 40px -8px rgba(255,46,99,.55)}}
  .btn.ghost{background:transparent;color:var(--white);border:1px solid var(--line-3)}
  @media (hover:hover){.btn.ghost:hover{border-color:var(--white);background:var(--line-1);color:var(--white);box-shadow:none}}
  .nav-burger{display:none}
  @media(max-width:760px){
    /* JS-dependent collapse only under html.js — no-JS mobile keeps real links */
    .js .nav-links a.nl{display:none}
    html:not(.js) .nav-links{flex-wrap:wrap;justify-content:flex-end;font-size:.62rem;gap:10px 14px}
    .btn{white-space:nowrap;font-size:.72rem;padding:.75em 1.3em}
    .js .nav-burger{
      display:flex;flex-direction:column;justify-content:center;gap:5px;
      width:44px;height:44px;padding:10px;margin-left:10px;flex:0 0 auto;
      background:none;border:0;cursor:pointer;position:relative;z-index:2;
    }
    .nav-burger span{display:block;height:2px;width:100%;background:var(--white);border-radius:var(--r-xs);transition:transform .3s,opacity .3s}
    #nav.menu-open{background:var(--scrim-4);backdrop-filter:blur(14px)}
    #nav.menu-open .nav-burger span:nth-child(1){transform:translateY(7px) rotate(45deg)}
    #nav.menu-open .nav-burger span:nth-child(2){opacity:0}
    #nav.menu-open .nav-burger span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}
    #nav.menu-open .nav-links{
      position:absolute;top:100%;left:0;right:0;
      flex-direction:column;align-items:stretch;gap:0;
      padding:8px var(--gutter) 24px;
      background:var(--scrim-4);backdrop-filter:blur(14px);
      border-bottom:1px solid var(--line-1);
    }
    #nav.menu-open .nav-links a.nl{display:block;font-size:1rem;padding:14px 0;border-bottom:1px solid var(--line-1)}
    #nav.menu-open .nav-links .btn{margin-top:18px;justify-content:center}
  }
  /* The header row is logo + CTA + ⌘K + burger. Adding the ⌘K trigger pushed it past the
     viewport at every width up to 467px — the burger clipped 32px off-screen at 431px and
     10px at 360px, silently masked by the overflow-x:hidden above. Drop the header CTA
     below 560px: that clears the overflow band with margin, and the logo/CTA gap only
     becomes comfortable (24px) from ~560px up. The CTA is the safe one to cut — it still
     appears in the open drawer below, and in every page hero. */
  @media(max-width:560px){
    #nav:not(.menu-open) .nav-links .btn{display:none}
  }
  /* below ~430px the wordmark suffix doesn't fit either */
  @media(max-width:430px){
    .wordmark .res{display:none}
    .nav-burger{margin-left:4px}
  }

  /* ============ LAYOUT ============ */
  main{position:relative;z-index:var(--z-content)}
  section{position:relative}
  .shell{max-width:1280px;margin:0 auto;padding:0 var(--gutter)}
  /* .hero is display:flex;flex-direction:column, which makes its horizontal
     margins the CROSS-axis margins — an "auto" cross-axis margin disables
     stretch per spec, so .shell fell back to sizing off its own content
     (the unbroken word "impossible" in the h1) instead of the container.
     Forcing the width here restores the fill-then-cap-at-1280px behavior
     every other .shell gets for free in normal block flow. */
  .hero .shell{width:100%}
  .eyebrow{
    font-family:var(--mono);font-size:.75rem;letter-spacing:.3em;text-transform:uppercase;color:var(--dim);
    display:flex;align-items:center;gap:14px;margin-bottom:26px;
  }
  .eyebrow::before{content:'';width:34px;height:1px;background:var(--raspberry)}

  /* ============ SECTION CHROME ============ */
  .band{background:var(--panel);padding:var(--sp-6) 0}
  .band.translucent{background:rgba(8,8,13,.66);backdrop-filter:blur(4px)}
  .sec-head{display:flex;align-items:flex-end;justify-content:space-between;gap:30px;margin-bottom:var(--sp-4);flex-wrap:wrap}
  .sec-head>div{min-width:0}
  .sec-head h2{
    font-family:var(--display);font-weight:800;text-transform:uppercase;
    /* the 8vw cap keeps the longest single word (DISCIPLINES. — 380px at
       --step-4's 2.2rem floor) inside a 375px viewport's 335px content box;
       min() hands back to the token as soon as the viewport affords it */
    font-size:min(var(--step-4),8vw);line-height:1;letter-spacing:-.01em;
  }
  .sec-head .kicker{max-width:340px;color:var(--dim);font-size:.98rem;font-weight:500}

  /* ============ CTA ============ */
  .cta-sec{padding:var(--sp-7) 0;text-align:center}
  .cta-sec h2{
    font-family:var(--display);font-weight:800;text-transform:uppercase;
    font-size:var(--step-6);line-height:.96;letter-spacing:-.015em;
    text-shadow:0 2px 30px var(--scrim-1);
  }
  .cta-sec h2 .ink{
    background:linear-gradient(95deg,var(--gold),var(--ember) 40%,var(--crimson) 80%,var(--crimson-deep));
    -webkit-background-clip:text;background-clip:text;color:transparent;
  }
  .cta-sec .sub{color:var(--dim-bright);max-width:520px;margin:26px auto 0;font-weight:500;text-shadow:0 1px 18px var(--scrim-3)}
  .cta-sec .btn{margin-top:42px;font-size:.9rem;padding:1.1em 2.4em}
  .cta-sec .mail{
    display:block;margin-top:26px;font-family:var(--mono);font-size:.8rem;letter-spacing:.14em;color:var(--dim);
    transition:color .3s;
  }
  @media (hover:hover){.cta-sec .mail:hover{color:var(--cyan)}}

  /* ============ FOOTER ============ */
  footer{
    position:relative;z-index:var(--z-content);border-top:1px solid var(--line-2);
    background:var(--scrim-3);backdrop-filter:blur(8px);
    padding:34px 0;
  }
  .foot-row{display:flex;justify-content:space-between;align-items:center;gap:20px;flex-wrap:wrap}
  .foot-row .fm{font-family:var(--mono);font-size:.68rem;letter-spacing:.16em;text-transform:uppercase;color:var(--dim)}
  @media (hover:hover){.foot-row .fm a:hover{color:var(--white)}}

  /* ============ PROCESS STEPS (canonical — used by home and service pages) ============ */
  .steps{display:grid;grid-template-columns:1fr;gap:var(--sp-1)}
  @media(min-width:640px){.steps{grid-template-columns:repeat(2,1fr)}}
  @media(min-width:1080px){.steps{grid-template-columns:repeat(4,1fr)}}
  .step{
    position:relative;border:1px solid var(--line-2);border-radius:var(--r-card);padding:30px 26px 34px;
    background:rgba(11,11,18,.7);overflow:hidden;transition:border-color .3s;
    /* query container: a .step is ~590px wide in a single-column stack but
       ~290px in the 4-up grid — its internal rhythm follows ITS width, not
       the viewport's. Base styles are the no-@container fallback. */
    container-type:inline-size;
  }
  /* informational cards: hover tints the border, no lift — lifts are for links */
  @media (hover:hover){.step:hover{border-color:var(--sc,var(--cyan))}}
  .step::before{
    content:'';position:absolute;top:0;left:0;right:0;height:3px;
    background:linear-gradient(90deg,var(--sc,var(--cyan)),transparent);opacity:.85;
  }
  .step .s-num{
    font-family:var(--mono);font-size:.72rem;letter-spacing:.2em;color:var(--sc,var(--cyan));margin-bottom:46px;display:block;
  }
  .step h3{font-family:var(--display);font-weight:800;font-size:1.5rem;text-transform:uppercase;margin-bottom:12px}
  .step p{color:var(--dim);font-size:.94rem;font-weight:500}
  .step .ghost-num{
    position:absolute;right:-6px;bottom:-34px;font-family:var(--display);font-weight:800;font-size:7rem;line-height:1;
    color:transparent;-webkit-text-stroke:1px var(--line-1);pointer-events:none;
  }
  /* container-driven rhythm — must sit AFTER the base .step descendant
     rules above: same specificity, so source order decides. */
  @container (min-width:480px){
    .step .s-num{margin-bottom:18px}
    .step h3{font-size:1.75rem}
    .step p{max-width:60ch;font-size:1rem}
  }

  /* ============ PROCESS PROGRESS GLOW ============ */
  /* Each step glows up in its own --sc color as it crosses into view, so
     "no mystery" is demonstrated on scroll rather than only written in the
     kicker above it. The entrance fade itself belongs to .reveal — and
     because every .step on the page is also a .reveal, the combined rule
     that runs both timelines together lives in the REVEAL block below.
     This rule is the one that covers a .step that is NOT also a .reveal. */
  @supports (animation-timeline: view()){
    @media (prefers-reduced-motion: no-preference){
      .step{
        /* Longhands, not the shorthand — see the note in KINETIC REVEAL
           ENGINE below. The shorthand was resetting animation-duration to
           0s, which is why this glow never actually ran. */
        animation-name:step-glow-in;
        animation-duration:auto;
        animation-timing-function:linear;
        animation-fill-mode:both;
        animation-timeline:view();
        animation-range:entry 0% cover 35%;
      }
    }
  }
  @keyframes step-glow-in{
    from{box-shadow:none;border-color:var(--line-2)}
    to{box-shadow:0 0 34px -8px var(--sc,var(--cyan));border-color:var(--sc,var(--cyan))}
  }

  /* ============ POINTER-AWARE VERBS ============ */
  .verb-coarse{display:none}
  @media (hover:none){.verb-fine{display:none}.verb-coarse{display:inline}}

  /* ============ NAV CURRENT PAGE ============ */
  .nav-links a.nl[aria-current="page"]{color:var(--white)}
  .nav-links a.nl[aria-current="page"]::after{width:100%}

  /* ============ SERVICE PARENT LINK ============ */
  /* The five service pages have no nav entry of their own (nav points at
     /#services), so this is the real way back up — aria-current on a nav
     link would assert something false here. */
  .svc-back{
    display:inline-flex;align-items:center;gap:.6em;min-height:44px;
    font-family:var(--mono);font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;
    color:var(--dim);margin-bottom:var(--sp-1);transition:color .3s;
  }
  .svc-back::before{content:'←';color:var(--sc,var(--cyan))}
  @media(hover:hover){.svc-back:hover{color:var(--white)}}

  /* ============ MORE SERVICES STRIP ============ */
  .more-svc{display:flex;gap:12px;flex-wrap:wrap;margin-top:var(--sp-4)}
  .more-svc a{
    font-family:var(--mono);font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;color:var(--dim);
    border:1px solid var(--line-3);border-radius:var(--r-pill);padding:.7em 1.4em;min-height:44px;
    display:inline-flex;align-items:center;gap:.6em;transition:color .3s,border-color .3s,transform .3s;
  }
  @media(hover:hover){.more-svc a:hover{color:var(--white);border-color:var(--sc,var(--cyan));transform:translateY(-2px)}}
  .more-svc a i{font-style:normal;color:var(--sc,var(--cyan))}

  /* ============ SCROLL PROGRESS ============ */
  .scroll-progress{
    position:fixed;top:0;left:0;height:2px;width:100%;z-index:var(--z-progress);
    transform-origin:0 50%;transform:scaleX(0);
    background:linear-gradient(90deg,var(--cyan),var(--cobalt),var(--violet),var(--raspberry));
    pointer-events:none;
  }

  /* ============ KINETIC REVEAL ENGINE ============ */
  /* Two engines, one look. Where the browser has element-driven scroll
     timelines, an element's own progress through the viewport IS the
     animation clock — no listener, no observer, no class toggle, and the
     reveal scrubs backwards if you scroll back up. Everywhere else,
     base.js falls back to the IntersectionObserver it has always used
     and toggles .show exactly as before.

     base.js picks the engine and marks the root: html.st means scroll
     timelines are live, so exactly ONE of the two blocks below applies.
     The hidden state still only exists under html.js — if the script
     never runs at all, every page stays fully visible. */

  /* fallback engine — IntersectionObserver toggles .show */
  .js:not(.st) .reveal{opacity:0;transform:translateY(34px);transition:opacity .8s var(--ease-out),transform .8s var(--ease-out)}
  .js:not(.st) .reveal.show{opacity:1;transform:none}
  .js:not(.st) .reveal.d1{transition-delay:.08s}
  .js:not(.st) .reveal.d2{transition-delay:.16s}
  .js:not(.st) .reveal.d3{transition-delay:.24s}
  .js:not(.st) .reveal.d4{transition-delay:.32s}

  /* native engine — no JS in the loop at all.
     The d1–d4 stagger can't be a transition-delay here (there is no
     transition, and a scroll timeline has no wall clock to delay
     against), so it becomes range: each step finishes a little deeper
     into the scroll, which reads as the same cascade. */
  /* Gated on no-preference, not just @supports. A scroll-driven animation
     takes its progress from the timeline rather than from a clock, so the
     global `animation-duration:.01ms` reduced-motion guard at the bottom of
     this file does NOT neutralize it. The motion has to be withheld by
     never declaring the animation in the first place — which also means the
     reduced-motion path needs no !important overrides and no hand-restating
     of the finished visual state: with no animation declared, .reveal and
     .step simply render at their natural, fully-arrived styles. */
  @supports (animation-timeline: view()){
   @media (prefers-reduced-motion: no-preference){
    .st .reveal{
      /* Longhands on purpose — never the `animation` shorthand here. The
         shorthand resets every longhand it omits, which drops
         animation-duration onto its initial 0s; a scroll-driven animation
         with a 0s active interval has nothing to scrub through and sticks
         on its from-keyframe forever. `auto` is what stretches the
         animation across the range below. */
      animation-name:reveal-in;
      animation-duration:auto;
      animation-timing-function:linear;
      animation-fill-mode:both;
      animation-timeline:view();
      animation-range:entry 0% cover 24%;
    }
    .st .reveal.d1{animation-range:entry 0% cover 30%}
    .st .reveal.d2{animation-range:entry 0% cover 36%}
    .st .reveal.d3{animation-range:entry 0% cover 42%}
    .st .reveal.d4{animation-range:entry 0% cover 48%}

    /* Process steps are BOTH .step and .reveal, and animation-name is a
       single property rather than an additive list — the .st .reveal rule
       above (0,0,2,0) outranks the .step glow rule (0,0,1,0), so without
       this block it would silently delete the glow instead of coexisting
       with it. Two scroll-driven animations on one element have to be
       composed by hand: one comma-separated list per longhand, same order
       throughout. They don't fight — reveal-in owns opacity/transform,
       step-glow-in owns box-shadow/border-color. */
    .st .step.reveal{
      animation-name:reveal-in,step-glow-in;
      animation-duration:auto,auto;
      animation-timing-function:linear,linear;
      animation-fill-mode:both,both;
      animation-timeline:view(),view();
      animation-range:entry 0% cover 24%,entry 0% cover 35%;
    }
    .st .step.reveal.d1{animation-range:entry 0% cover 30%,entry 0% cover 35%}
    .st .step.reveal.d2{animation-range:entry 0% cover 36%,entry 0% cover 35%}
    .st .step.reveal.d3{animation-range:entry 0% cover 42%,entry 0% cover 35%}
    .st .step.reveal.d4{animation-range:entry 0% cover 48%,entry 0% cover 35%}
   }
  }
  @keyframes reveal-in{
    from{opacity:0;transform:translateY(34px)}
    to{opacity:1;transform:none}
  }

  /* ============ TOUCH FEEDBACK (shared) ============ */
  /* nav/button press states — see index.html's inline
     TOUCH FEEDBACK block for the home-only svc/card rules. */
  .btn:active{transform:translateY(0) scale(.97)}

  /* ============ COMMAND DECK ============ */
  /* trigger lives in every nav, always visible (mobile and desktop) —
     unlike .nl links it never hides into the burger menu, since it IS
     the fast path. Markup + behavior in cmdk.js. */
  .cmdk-trigger{
    display:flex;align-items:center;gap:8px;background:transparent;cursor:pointer;
    border:1px solid var(--line-2);border-radius:var(--r-pill);color:var(--dim);
    min-height:44px; /* WCAG touch target — was 30px, the last sub-44 control */
    font-family:var(--mono);font-size:.68rem;letter-spacing:.08em;padding:.5em .8em;
    transition:border-color .25s,color .25s;position:relative;z-index:2;
  }
  @media (hover:hover){.cmdk-trigger:hover{color:var(--white);border-color:var(--cyan)}}
  .cmdk-trigger kbd{
    font-family:var(--mono);border:1px solid var(--line-3);border-radius:var(--r-xs);
    padding:.1em .4em;font-size:.9em;color:var(--dim);
  }
  @media(max-width:640px){.cmdk-trigger .cmdk-trigger-label{display:none}}

  .cmdk-root{position:fixed;inset:0;z-index:var(--z-modal);visibility:hidden;pointer-events:none}
  .cmdk-root.open{visibility:visible;pointer-events:auto}
  .cmdk-veil{
    position:absolute;inset:0;background:var(--scrim-2);backdrop-filter:blur(6px);
    opacity:0;transition:opacity .25s ease;
  }
  .cmdk-root.open .cmdk-veil{opacity:1}
  .cmdk-panel{
    position:relative;max-width:560px;margin:min(14vh,120px) auto 0;
    background:var(--panel);border:1px solid var(--line-2);border-radius:var(--r-md);
    box-shadow:var(--shadow-lift);overflow:hidden;
    opacity:0;transform:translateY(-10px) scale(.98);transition:opacity .25s ease,transform .25s var(--ease-out);
  }
  .cmdk-root.open .cmdk-panel{opacity:1;transform:none}
  .cmdk-row1{display:flex;align-items:center;gap:12px;padding:18px 20px;border-bottom:1px solid var(--line-2)}
  .cmdk-icon{color:var(--cyan);font-size:1.1rem}
  .cmdk-input{
    flex:1;background:none;border:0;color:var(--white);font-family:var(--body);
    font-size:1rem;font-weight:500;
  }
  .cmdk-input::placeholder{color:var(--dim)}
  /* The input is auto-focused on open, so its ring was suppressed as noise.
     That also removed the only cue when you Tab back to it from the list.
     :focus-visible keeps the mouse-open case clean and the keyboard case visible. */
  .cmdk-input:focus-visible{outline:2px solid var(--cyan);outline-offset:2px;border-radius:var(--r-xs)}
  .cmdk-esc{
    font-family:var(--mono);font-size:.68rem;color:var(--dim);border:1px solid var(--line-3);
    border-radius:var(--r-xs);padding:.2em .5em;flex:none;
  }
  .cmdk-list{max-height:min(50vh,420px);overflow-y:auto;padding:8px}
  .cmdk-group{
    font-family:var(--mono);font-size:.64rem;letter-spacing:.16em;text-transform:uppercase;
    color:var(--dim);padding:14px 12px 6px;
  }
  .cmdk-opt{
    display:flex;justify-content:space-between;align-items:baseline;gap:14px;
    padding:11px 12px;border-radius:var(--r-sm);color:var(--white);text-decoration:none;
  }
  .cmdk-opt.on{background:rgba(45,226,255,.1)}
  .cmdk-opt.on .cmdk-opt-label{color:var(--cyan)}
  .cmdk-opt-label{font-family:var(--display);font-weight:800;font-size:.95rem;text-transform:uppercase}
  .cmdk-opt-hint{font-size:.8rem;color:var(--dim);font-weight:500;text-align:right;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .cmdk-empty{display:none;padding:34px 20px;text-align:center;color:var(--dim);font-size:.88rem;font-weight:500}
  .cmdk-empty.show{display:block}
  @media(max-width:640px){
    .cmdk-panel{margin-top:8vh;max-width:calc(100% - 24px)}
    .cmdk-opt-hint{display:none}
  }

  @media (prefers-reduced-motion:reduce){
    *,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}
    html{scroll-behavior:auto}
    .reveal,.js .reveal{opacity:1;transform:none}
    /* Nothing to switch off here for the scroll-driven engine. Both it and
       the process-step glow are declared inside a
       `@media (prefers-reduced-motion: no-preference)` gate, so under this
       query they were never declared at all and both land at their natural
       finished styles on their own. The blanket animation-duration override
       above could not have stopped them anyway — a scroll-driven animation
       is clocked by position, not time. */
  }

  /* ============ MOTION TOGGLE (html.motion-off) ============ */
  /* The footer toggle's class-based mirror of the block above — a media
     query can't be OR'd with a selector, so the guard exists twice. KEEP
     THE TWO IN SYNC. One real difference: under OS-level reduce the
     scroll-driven animations were never declared, but under the toggle
     they were — and being position-clocked they shrug off the duration
     override, so they must be switched off by name. animation-fill-mode
     is 'both', so removing the name lands elements on their natural,
     fully-arrived styles. */
  html.motion-off *,html.motion-off *::before,html.motion-off *::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}
  html.motion-off{scroll-behavior:auto}
  html.motion-off .reveal{opacity:1;transform:none}
  html.motion-off :is(.reveal,.step){animation-name:none!important}

  .motion-toggle{
    font-family:var(--mono);font-size:.68rem;letter-spacing:.14em;text-transform:uppercase;
    background:transparent;color:var(--dim);border:1px solid var(--line-2);border-radius:var(--r-pill);
    padding:.5em 1.1em;min-height:44px;cursor:pointer;display:inline-flex;align-items:center;
    transition:color .25s,border-color .25s;
  }
  @media(hover:hover){.motion-toggle:hover{color:var(--white);border-color:var(--line-3)}}
  .motion-toggle[aria-pressed="true"]{color:var(--white);border-color:var(--cyan)}

  /* ============ WORDMARK MICROINTERACTION ============ */
  /* The ◆ flips on hover — one small, deliberate move on the mark itself.
     Rides the reduced-motion/motion-off guards (transition-duration). */
  .wordmark .dia{display:inline-block;transition:transform .5s var(--ease-out)}
  @media(hover:hover){.wordmark:hover .dia{transform:rotate(180deg) scale(1.2)}}
