/* ═══════════════════════════════════════════════════════════════
   Cleanly — base: tokens, reset, fluid type
   Type scales on calc(rem + vw) per JumpFactory, anchored so each
   token hits its Figma value at 1920px. Capped with min() so it
   stops growing past the design width — JumpFactory has no ceiling.
   ═══════════════════════════════════════════════════════════════ */

:root{
  /* — palette (Figma variables) — */
  --navy:      #151A5A;
  --blue:      #3847C3;
  --cream:     #FFFBED;
  --offwhite:  #FFFCF2;
  --white:     #FFFFFF;
  --pale-blue: #D2E7F1;
  --light-blue:#DAEBF3;
  --soft-blue: #BFCFEF;
  --near-white:#F7F7F7;
  --gray:      #D9D9D9;
  --stone:     #343330;   /* form placeholder ink */
  --error:     #B3261E;   /* invalid field state */

  /* — spacing (Figma spacing/*) — */
  --s-10:10px; --s-16:16px; --s-20:20px; --s-24:24px; --s-32:32px;
  --s-40:40px; --s-60:60px; --s-64:64px; --s-80:80px; --s-120:120px;
  --s-160:160px; --s-200:200px;

  /* page gutter: 80px at 1920, never below 24px.
     Mobile overrides this to 28px — see the 640px block below. */
  --gutter: clamp(24px, 4.1667vw, 80px);
  /* Section rhythm — ONE gap on the page stack, per JumpFactory
     (`flex flex-col gap-y-16 lg:gap-y-32`). Sections carry no vertical
     padding of their own; the gap is the entire spacing. Applying it as
     padding on each section double-counts it at every boundary. */
  /* 80px is the mobile artboard's flat section rhythm (1360:8132 — every
     boundary measures 80). Flooring the clamp there instead of 64 means the
     value holds from 390 up to 960 and then climbs to 160 at the design
     width, so there is no breakpoint to step across. */
  --section-gap: clamp(80px, 8.333vw, 160px);   /* 80 mobile -> 160 desktop */

  /* — fluid type —            Figma @1920      390px    1440px */
  --t-heading:    min(calc(1rem + 4.375vw), 120px);   /* 100px   33      79  */
  --t-heading-lg: min(calc(1rem + 5.469vw), 145px);   /* 121px — Figma Headings/desktop-large */
  --t-script:     min(calc(1rem + 5.417vw), 145px);   /* 120px   37      94  */
  --t-script-sm:  min(calc(0.88rem + 2.291vw), 63.8px); /* 58px  —  47  — services script, +10% */
  /* services list only — 20% under the heading scale */
  --t-service:    min(calc(0.88rem + 3.850vw), 106px); /*  88px   29      69  — +10% */
  --t-quote:      min(calc(1rem + 2.292vw),  66px);   /*  60px   25      49  — testimonial pull-quote */
  --t-body-lg:    min(calc(1rem + 1.25vw),  46px);    /*  40px   21      34  */
  --t-body:       min(calc(1rem + 0.729vw), 34px);    /*  30px   19      26  */
  --t-body-sm:    min(calc(1rem + 0.208vw), 22px);    /*  20px   17      19  — small body copy */
  --t-badge:      min(calc(1rem + 0.208vw), 22px);    /*  20px   17      19  */

  /* Every card-shaped image on the page uses this one radius. */
  --r-card: 10px;

  /* Every spinning star badge — hero, about, contact, case study, clients —
     is this one size. Set by the homepage hero (Figma 1360:8107), which is
     the reference the others were measured against. Change it here and all
     of them move together; nothing else should hard-code a badge width. */
  --badge-size: clamp(200px, 14.4vw, 276px);   /* floor was 150; 200 per the revisions — mobile keeps 150, below */
  /* The label rides the badge, not the viewport, so raising the floor
     scales the whole badge proportionally. 0.0779 is the Figma ratio,
     21.5px of 276, which the old clamp(11px, 1.12vw, 21.5px) also held at
     desktop widths — nothing changes there, only where the floor bites. */
  --badge-label: calc(var(--badge-size) * 0.0779);

  --ease: cubic-bezier(.4,0,.2,1);
  /* JumpFactory's `ease-custom-in-out` — slow ends, fast middle. Much less
     clunky than the standard curve for height/reveal transitions. */
  --ease-inout: cubic-bezier(.75,0,.15,1);
}

/* ── Mobile type scale ─────────────────────────────────────────
   Figma does NOT scale the desktop comp down — `Homepage / Mobile`
   (1360:8132) carries its own named styles:

     Headings/mobile        40px / 1   / -2%
     Headings/mobile-small  28px / 1   / -2%
     decorative/mobile      44px / 0.7 / -2%
     p/mobile               16px / 1.4
     badge/mobile           16px / 1.3

   The desktop formulas are anchored at 1920 and keep interpolating past
   the point the design agrees with them: at 390 they land the headings
   ~17% under and the body ~18% over, which is why mobile read flat —
   heading and body were converging on the same size.

   Each token below is re-anchored to hit the Figma value at 390 and to
   MEET the desktop curve exactly at 640, so there is no jump at the
   breakpoint. Both anchors are noted per line.
   ────────────────────────────────────────────────────────────── */
@media (max-width:640px){
  :root{
    --gutter: 28px;                              /* Figma mobile px-28 */

    --t-heading:   calc(2.110rem + 1.600vw);     /* 40 @390 -> 44.0 @640 */
    /* The mobile board uses Headings/mobile for the About hero and the
       FAQ title too — there is no separate large step below 640. */
    --t-heading-lg:calc(2.110rem + 1.600vw);     /* 40 @390 */
    --t-script:    calc(2.100rem + 2.668vw);     /* 44 @390 -> 50.7 @640 */
    --t-service:   calc(0.705rem + 4.288vw);     /* 28 @390 -> 38.7 @640 */
    --t-quote:     calc(1.490rem + 1.068vw);     /* 28 @390 -> 30.7 @640 */
    --t-body-lg:   calc(0.860rem + 1.600vw);     /* 20 @390 -> 24.0 @640 */
    --t-body:      calc(0.545rem + 1.868vw);     /* 16 @390 -> 20.7 @640 */
    --t-body-sm:   calc(0.870rem + 0.532vw);     /* 16 @390 -> 17.3 @640 */
    --t-badge:     calc(0.870rem + 0.532vw);     /* 16 @390 -> 17.3 @640 */

    /* The services script is sized to the IMAGE (31.2 of a 333px frame),
       not to the viewport, so it runs against the desktop curve rather
       than with it. Pinned rather than given a negative vw slope. */
    --t-script-sm: 34px;   /* Figma 31.2, +10% */

    /* Badges keep their pre-revision mobile size: the old clamp's 150px
       floor and 11px label, which is all it ever resolved to below 640. */
    --badge-size:  150px;
    --badge-label: 11px;
  }
}

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;
  background:var(--light-blue);   /* was --cream */
  color:var(--navy);
  font-family:var(--font-body,'PP Neue Montreal',system-ui,sans-serif);
  font-size:var(--t-body);
  line-height:1.3;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  overflow-x:hidden;
}
img,picture,video,svg{display:block;max-width:100%}
/* That `display:block` is an AUTHOR rule, so it outranks the UA stylesheet's
   [hidden]{display:none} and a hidden <img> keeps rendering. It has cost us
   the same bug repeatedly — the hero play/mute icons, the menu panel, the
   nav's bars/X pair — each patched with its own one-off guard. One rule
   instead, so `hidden` means hidden everywhere. */
[hidden]{display:none !important}
button{font:inherit;color:inherit;background:none;border:0;padding:0;cursor:pointer}
a{color:inherit}

/* Lenis needs these */
html.lenis,html.lenis body{height:auto}
.lenis.lenis-smooth{scroll-behavior:auto !important}
.lenis.lenis-stopped{overflow:hidden}

/* ── Type primitives ───────────────────────────────────────────
   line-height 1 on display type leaves half-leading above and below.
   The negative margins cancel it so headings sit optically correct
   in their box — scaled in vw so the correction tracks the font. */
.h-display{
  font-family:var(--font-display,'PP Neue Montreal Mono',ui-monospace,monospace);
  font-weight:700;
  font-size:var(--t-heading);
  line-height:1;
  letter-spacing:-0.02em;
  text-transform:uppercase;
  margin:calc(-0.08em) 0 calc(-0.06em);
}
.h-display--lg{font-size:var(--t-heading-lg)}   /* Headings/desktop-large, 121 @1920 */
.h-script{
  font-family:var(--font-script,'Brush Script Std',cursive);
  font-weight:500;
  font-size:var(--t-script);
  line-height:.8;
  letter-spacing:-0.02em;
  color:var(--blue);
  margin:0;
  /* paint-order draws the stroke first, so the fill covers its inner half
     and the whole 2px sits outside the letterform. Without it the stroke
     straddles the edge and thins the script. */
  -webkit-text-stroke:4px var(--cream);
  paint-order:stroke fill;
}
.t-badge{
  font-size:var(--t-badge);
  font-weight:500;
  line-height:1.3;
  margin:0;
}
.t-body{font-size:var(--t-body);line-height:1.3;margin:0}

/* ── Layout ────────────────────────────────────────────────────*/
.shell{padding-inline:var(--gutter)}

/* The page is a single vertical stack; its gap is the only inter-section
   spacing. Sections do not pad themselves. */
main{display:flex;flex-direction:column;gap:var(--section-gap)}
.section{padding-block:0}

/* within a section — well under the section gap, so the hierarchy reads.
   40px at every width now; it was 60 on desktop and tablet until the
   revisions tightened the inner rhythm to match phones. */
.stack{display:flex;flex-direction:column;gap:var(--s-40)}

.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;
}

/* ── Fly-in (elementBlurin) ────────────────────────────────────
   Elements are fully visible at rest. JS only writes opacity /
   transform above 1024px, so mobile renders the design statically.
   Never author a resting state of opacity:0 — mobile would blank. */
.js-blurin{will-change:transform,opacity}

/* ── Split lines ───────────────────────────────────────────────
   .js-split headings are cut into per-line elements at runtime.
   .line clips; .line__i is what moves, so text rises from behind
   its own edge. Padding + matching negative margin keep ascenders
   and descenders from being shaved by the clip. */
.line{
  display:block;
  /* No overflow clip. Uppercase mono at line-height 1 still pushes commas
     and rounded caps past the 1em box, and any padding generous enough to
     clear them let the neighbouring line peek through the mask anyway.
     The reveal is fade + rise, consistent with every other fly-in. */
}
.line__i{display:block;will-change:transform,opacity}
.w{display:inline-block}
.w--accent{color:var(--blue)}

/* Breakpoint-specific line breaks. splitLines() skips any BR that CSS
   has switched off, so a heading can carry both and use whichever
   applies at the current width. */
.br-m{display:none}

/* A split heading is fully visible before JS runs, and stays visible
   on mobile where the fly-in never executes. */
.js-split .line__i{opacity:1;translate:none}

/* ── Mobile primitives ─────────────────────────────────────────
   Sizes are handled by the token block at the top of the file; these
   are the properties that are declared on the primitives themselves
   and so have to be re-stated after their definitions.
   ────────────────────────────────────────────────────────────── */
@media (max-width:640px){
  /* Only what actually differs from the desktop primitives. Sizes come
     from the token block at the top of the file, so a rule that merely
     restates a primitive's own declaration does nothing — three such
     no-ops lived here and have been removed.

     UNRESOLVED: Figma's decorative/mobile is line-height 0.7, but
     .h-script sets .8 at every width and nothing has implemented 0.7. */
  /* p/mobile is 1.4, not the 1.3 the desktop styles use. This has to live
     HERE, not in the token block at the top: media queries add no
     specificity, so a body{} rule up there loses to the reset's own
     body{line-height:1.3} simply for being earlier in the file. */
  body{line-height:1.4}
  .t-body{line-height:1.4}

  /* Figma's mobile sections use two intra-section gaps: 40px between
     content groups — which is now the base .stack value at every width —
     and 20px under a section label. That second one is the only phone
     difference left. The label-led sections are the ones whose first child
     is .t-badge. */
  .stack:has(> .t-badge){gap:var(--s-20)}

  .br-m{display:inline}
  .br-d{display:none}
}
