/* =========================================================
   Studio IWC — design tokens
   ========================================================= */
:root{
  /* Monochrome palette, matched to trionn.com: near-black, off-white,
     one neutral mid-grey for alternating light scenes. No hue accent —
     hierarchy comes from type weight, scale and letter-spacing instead. */
  --paper:      #FAFAFA;
  --paper-2:    #D8D7D4;
  --ink:        #0A0A0A;
  --ink-2:      #161616;

  --on-paper:        #0A0A0A;
  --on-paper-muted:  rgba(10,10,10,.6);
  --on-paper-line:   rgba(10,10,10,.15);

  --on-dark:         #FAFAFA;
  --on-dark-muted:   rgba(250,250,250,.6);
  --on-dark-line:    rgba(250,250,250,.18);

  --font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
  --font-body: 'Schibsted Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  --maxw: 1400px;
  --gutter: clamp(20px, 5vw, 64px);

  --ease: cubic-bezier(.22,.68,.2,1);
  --ease-soft: cubic-bezier(.4,0,.2,1);
  --dur-1: .7s;
  --dur-2: 1.1s;
}

*{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
@media (prefers-reduced-motion: reduce){
  html{scroll-behavior:auto}
}

body{
  font-family:var(--font-body);
  background:var(--paper);
  color:var(--on-paper);
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  /* clip rather than hidden: per spec `overflow-x:hidden` forces the other
     axis from `visible` to `auto`, which makes <body> its own scroll
     container. That is a known footgun around position:sticky and pinned
     ScrollTriggers. `clip` suppresses horizontal overflow identically without
     creating a scroll container. (Verified here: with `hidden`, body computes
     overflow-y:auto; with `clip` it stays visible.) */
  overflow-x:clip;
}

a{color:inherit;text-decoration:none}
button{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}
ol,ul{list-style:none}
img,svg{display:block;max-width:100%}
::selection{background:var(--ink);color:var(--paper)}

:focus-visible{
  outline:2px solid currentColor;
  outline-offset:3px;
}

.skip-link{
  position:absolute;left:12px;top:-60px;
  background:var(--ink);color:var(--paper);
  padding:12px 18px;font-size:.85rem;z-index:999;
  transition:top .2s var(--ease-soft);
}
.skip-link:focus{top:12px}

.wrap{
  max-width:var(--maxw);
  margin:0 auto;
  padding:0 var(--gutter);
}

.eyebrow{
  font-family:var(--font-body);
  font-weight:600;
  font-size:.72rem;
  letter-spacing:.16em;
  text-transform:uppercase;
  color:currentColor;
  opacity:.6;
  margin-bottom:1.4rem;
}

h1,h2,h3{
  font-family:var(--font-display);
  font-weight:400;
  line-height:1.05;
  letter-spacing:-.01em;
}

.display-xl{font-size:clamp(2.6rem, 6.6vw, 6.4rem)}
.display-lg{font-size:clamp(2.2rem, 5vw, 4.4rem)}
.display-md{font-size:clamp(1.7rem, 3.2vw, 2.6rem)}

p{max-width:62ch}

/* Keeps a phrase from being split across lines. Used on the hero eyebrow so
   it breaks after the comma rather than orphaning a word. */
.nowrap{white-space:nowrap}

/* =========================================================
   Scenes (light/dark alternation)
   ========================================================= */
/* One standard section rhythm. Adjacent sections always differ in
   background colour, so the tone change already marks the boundary and
   the padding only has to give the content room — it doesn't have to
   carry the separation on its own. Two of these stack at every seam,
   so the real gap is double whatever is set here. */
.scene{
  position:relative;
  padding:clamp(64px, 8vw, 112px) 0;
}
.scene-paper{background:var(--paper);color:var(--on-paper)}
.scene-paper-2{background:var(--paper-2);color:var(--on-paper)}
.scene-dark{background:var(--ink);color:var(--on-dark)}

/* =========================================================
   Reveal-on-scroll (base state; JS adds .is-visible)
   No-JS / no-motion fallback: content is visible and static
   unless html.js is present (set by an early inline script).
   When GSAP is driving the choreography (html.gsap-on), these
   elements are animated entirely via inline styles from app.js —
   a competing CSS transition here would fight GSAP's own
   per-frame tweening and read as laggy, so it's switched off.
   ========================================================= */
html.js:not(.gsap-on) [data-reveal]{
  opacity:0;
  transform:translateY(24px);
  transition:opacity var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}
html.js:not(.gsap-on) [data-reveal].is-visible{opacity:1;transform:none}
html.gsap-on [data-reveal]{ opacity:0; }

/* The reveal mask. overflow:hidden is what lets each headline line slide up
   from nothing — but display line-height (1.05) is tighter than the glyphs'
   full descender depth, so "g", "y", "p" were being sliced flat by the mask.
   The padding extends the clipping box below the baseline and the equal
   negative margin removes it again from layout, so descenders are contained
   without loosening the leading. Descenders need ~.09em here; .18em leaves
   headroom across weights and sizes. The reveal's resting offset (135%)
   must stay larger than 100% + this padding or the glyph tops peek out. */
.line{
  display:block;
  overflow:hidden;
  padding-bottom:.18em;
  margin-bottom:-.18em;
}
html.js:not(.gsap-on) [data-reveal-line]{
  display:block;
  transform:translateY(135%);
  transition:transform var(--dur-2) var(--ease);
}
html.js:not(.gsap-on) [data-reveal-line].is-visible{transform:none}
html.gsap-on [data-reveal-line]{ display:block; }

html.js:not(.gsap-on) [data-draw]{
  transition:stroke-dashoffset 1.6s var(--ease);
}
html.gsap-on [data-draw]{
  transition:stroke-dashoffset 1.4s cubic-bezier(.16,1,.3,1);
}

@media (prefers-reduced-motion: reduce){
  html.js [data-reveal],html.js [data-reveal-line]{opacity:1;transform:none;transition:none}
  html.js [data-draw]{stroke-dashoffset:0 !important;transition:none}
}

/* =========================================================
   Loader
   ========================================================= */
.loader{
  display:none;
}
html.js .loader{
  position:fixed;inset:0;z-index:1000;
  background:var(--ink);color:var(--on-dark);
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:22px;
  transition:opacity .28s .38s cubic-bezier(.16,1,.3,1),visibility .66s;
}
html.js .loader.is-done{opacity:0;visibility:hidden;pointer-events:none}
.loader-inner{
  position:relative;width:clamp(270px,27vw,390px);height:240px;
  display:flex;align-items:center;justify-content:center;
  transform:scale(1);filter:blur(0);
  transition:opacity .65s var(--ease-soft),transform .9s cubic-bezier(.16,1,.3,1),filter .65s var(--ease-soft);
}
.loader.is-done .loader-inner{opacity:1;transform:none;filter:none}
.loader-logo{
  position:relative;z-index:1;width:100%;height:100%;
  clip-path:inset(0 0 0 0);
  transition:clip-path .44s cubic-bezier(.7,0,.3,1);
}

/* The supplied logo files are a rendered glow (soft bloom baked into the
   pixels, not clean vector art), so the honest, robust treatment is to
   animate it as what it actually is — a neon mark igniting — rather than
   fight the softness with hard-edged clipping or masking. One authentic
   flicker as it catches, then a confident surge past full brightness that
   settles, with a real drop-shadow glow that breathes on the same curve.
   No filter tricks needed since the artwork is already the right colour. */
.loader-logo-img{
  position:absolute;left:0;top:0;width:100%;height:100%;object-fit:contain;
  opacity:0;
  filter:brightness(.65) blur(7px);
  transform:translateY(8px) scale(.992);
  animation:loaderLogoFlow 1.2s .08s forwards;
}
.loader.is-done .loader-logo{
  clip-path:inset(100% 0 0 0);
}
@keyframes loaderLogoFlow{
  0%{opacity:0;filter:brightness(.65) blur(7px);transform:translateY(8px) scale(.992)}
  38%{opacity:.92;filter:brightness(1.05) blur(0);transform:translateY(0) scale(1)}
  50%{opacity:.38;filter:brightness(.72) blur(.6px)}
  62%{opacity:1;filter:brightness(1.12) blur(0)}
  100%{opacity:1;filter:brightness(1) blur(0);transform:translateY(0) scale(1)}
}
body.no-js .loader{display:none}
@media (prefers-reduced-motion: reduce){
  .loader{display:none}
}

/* =========================================================
   Nav
   ========================================================= */
.nav{
  position:fixed;top:0;left:0;right:0;z-index:200;
  padding:22px var(--gutter);
  display:flex;align-items:center;
  color:var(--on-dark);
  transition:padding .4s var(--ease-soft), background-color .4s var(--ease-soft);
}
/* At the top the nav sits transparent over the (dark) hero. Once scrolling
   starts it gains its own translucent bar, so the links never have to
   compete with whatever heading or photograph is passing underneath.
   This replaces mix-blend-mode:difference, which kept the text legible
   but inverted it against the content — over the hero photograph and over
   large headings that read as a collision rather than a nav. A bar of its
   own also means the white logo stays white everywhere instead of
   flipping dark over light sections. */
.nav.is-scrolled{
  padding:14px var(--gutter);
  background:rgba(10,10,10,.72);
  backdrop-filter:blur(14px) saturate(120%);
  -webkit-backdrop-filter:blur(14px) saturate(120%);
  border-bottom:1px solid rgba(250,250,250,.1);
}
/* Fallback for browsers without backdrop-filter: go more opaque instead,
   otherwise the bar is too see-through to do its job. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .nav.is-scrolled{background:rgba(10,10,10,.93)}
}
.nav-inner{
  width:100%;max-width:var(--maxw);margin:0 auto;
  display:flex;align-items:center;justify-content:space-between;
  gap:24px;
}
.nav-brand{
  display:flex;
  align-items:center;
  overflow:hidden;
}
.nav-brand img{
  height:51px;
  width:auto;
  display:block;
}
.nav-links{
  display:flex;gap:clamp(16px,2.4vw,32px);
  font-size:.82rem;letter-spacing:.03em;
}
.nav-links a{
  position:relative;padding:4px 0;
}
.nav-links a::after{
  content:"";position:absolute;left:0;right:100%;bottom:0;height:1px;
  background:currentColor;transition:right .35s var(--ease-soft);
}
.nav-links a:hover::after{right:0}

.nav-whatsapp{
  font-size:.78rem;letter-spacing:.03em;
  display:flex;align-items:center;gap:8px;
  border-bottom:1px solid currentColor;
  padding-bottom:2px;
}

/* ---- Inverted cursor blob -------------------------------------------------
   Hovering a [data-cursor-invert] target hides the system cursor and follows
   the pointer with an oversized solid disc. The disc is plain white with
   mix-blend-mode:difference, so it inverts whatever it passes over rather
   than being given a colour of its own — the label underneath flips to dark
   inside the circle and back outside it, with no second copy of the text.

   Fine-pointer only: on touch there is no hover, and a disc that never
   appears (or worse, sticks after a tap) is just a liability. */
.cursor-blob{ display:none; }

@media (hover:hover) and (pointer:fine){
  .cursor-blob{
    display:block;
    position:fixed;
    top:0;left:0;
    width:104px;height:104px;
    margin:-52px 0 0 -52px;      /* centre on the pointer without a transform */
    border-radius:50%;
    background:#FAFAFA;
    mix-blend-mode:difference;
    pointer-events:none;
    z-index:300;                 /* above the nav (200) */
    opacity:0;
    transform:translate3d(-200px,-200px,0) scale(.2);
    transition:opacity .28s var(--ease-soft), scale .38s cubic-bezier(.16,1,.3,1);
    will-change:transform;
  }
  .cursor-blob.is-on{ opacity:1; scale:1; }

  [data-cursor-invert]{ cursor:none; }
}

@media (prefers-reduced-motion: reduce){
  .cursor-blob{ transition:none; }
}

.nav-toggle{
  display:none;
  width:32px;height:20px;position:relative;
}
/* The bars read better small, but 32x20 is far under the ~44px minimum for a
   reliable thumb target — and this is the only navigation control on a phone.
   A transparent pseudo-element enlarges the hit area without touching the
   visual or the two spans the open/close animation transforms. (No effect on
   desktop, where the button is display:none.) */
.nav-toggle::after{
  content:"";
  position:absolute;left:50%;top:50%;
  width:46px;height:46px;
  transform:translate(-50%,-50%);
}
.nav-toggle span{
  position:absolute;left:0;right:0;height:1px;background:currentColor;
  transition:transform .3s var(--ease-soft), opacity .3s;
}
.nav-toggle span:first-child{top:0}
.nav-toggle span:last-child{bottom:0}
.nav-toggle[aria-expanded="true"] span:first-child{transform:translateY(9.5px) rotate(45deg)}
.nav-toggle[aria-expanded="true"] span:last-child{transform:translateY(-9.5px) rotate(-45deg)}

.mobile-menu{
  position:fixed;inset:0;z-index:190;
  background:var(--ink);color:var(--on-dark);
  display:flex;flex-direction:column;justify-content:center;gap:40px;
  padding:0 var(--gutter);
  opacity:0;visibility:hidden;
  transform:translateY(-12px);
  transition:opacity .4s var(--ease-soft), transform .4s var(--ease-soft), visibility .4s;
}
.mobile-menu.is-open{opacity:1;visibility:visible;transform:none}
.mobile-menu nav{display:flex;flex-direction:column;gap:18px}
.mobile-menu nav a{font-family:var(--font-display);font-size:2.2rem}
.mobile-whatsapp{
  align-self:flex-start;
  font-size:.9rem;letter-spacing:.03em;
  border-bottom:1px solid currentColor;padding-bottom:4px;
}

/* =========================================================
   Hero
   ========================================================= */
.hero{
  min-height:100svh;
  display:flex;flex-direction:column;justify-content:flex-end;
  padding-top:120px;
  overflow:hidden;
  isolation:isolate;
  background:#080604;
}
.hero::before{
  content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:
    linear-gradient(90deg,rgba(5,4,3,.94) 0%,rgba(5,4,3,.78) 30%,rgba(5,4,3,.2) 61%,rgba(5,4,3,.08) 100%),
    linear-gradient(180deg,rgba(5,4,3,.5) 0%,transparent 27%,transparent 70%,rgba(5,4,3,.56) 100%);
}
.hero::after{
  content:"";position:absolute;inset:0;z-index:3;pointer-events:none;
  box-shadow:inset 0 0 180px rgba(0,0,0,.48);
  opacity:.72;
}
.hero-media{
  position:absolute;inset:-2%;z-index:-2;overflow:hidden;
  background:#080604;
  will-change:transform,opacity;
}
.hero-media img{
  width:100%;height:100%;object-fit:cover;object-position:center;
  filter:saturate(.82) contrast(1.08) brightness(.8);
  will-change:transform;
}
.hero-plan{
  position:absolute;inset:-12%;z-index:0;
  opacity:.095;
  color:var(--on-dark);
  pointer-events:none;
  mix-blend-mode:screen;
}
.hero-plan svg{width:100%;height:100%;overflow:visible}
.hero-plan [data-draw]{
  vector-effect:non-scaling-stroke;
  shape-rendering:geometricPrecision;
  stroke-width:1.2px;
}
/* Hollow, not filled — echoes the open counter in the Studio IWC logo mark. */
.hero-plan [data-node]{
  opacity:.55;
  fill:none;
  stroke:currentColor;
  stroke-width:1.2px;
  vector-effect:non-scaling-stroke;
}

.hero-process{
  position:absolute;inset:0;z-index:1;pointer-events:none;
  max-width:var(--maxw);width:100%;margin:0 auto;
  padding:0 var(--gutter);
  color:rgba(250,250,250,.55);
  font-size:.62rem;font-weight:600;letter-spacing:.14em;text-transform:uppercase;
}
.hero-process span{position:absolute;opacity:0}
.hero-process span:nth-child(1){left:calc(var(--gutter) + 2%);top:29%}
.hero-process span:nth-child(2){left:48%;top:52%}
.hero-process span:nth-child(3){right:calc(var(--gutter) + 1%);bottom:19%}

/* The headline forms the dominant left mass. The eyebrow and supporting
   paragraph share the right column as one positioning statement, with the
   image left uninterrupted through the centre of the composition. */
.hero-content{
  position:relative;z-index:2;
  max-width:var(--maxw);
  margin:0 auto;width:100%;
  padding:0 var(--gutter) clamp(64px,11vh,120px);
  display:grid;
  grid-template-columns:repeat(12,1fr);
  column-gap:clamp(14px,1.8vw,26px);
  align-items:end;
}
.hero .eyebrow{
  grid-column:9 / -1;
  grid-row:1;
  align-self:end;
  margin:0 0 clamp(18px,2.2vh,26px);
}
.hero-title{
  grid-column:1 / span 8;
  grid-row:1 / 3;
  align-self:end;
  font-size:clamp(2.1rem, 5.75vw, 5.3rem);   /* 20% down from 7.2vw/6.6rem */
  font-weight:400;
  margin:0;
}
.hero-sub{
  grid-column:9 / -1;
  grid-row:2;
  align-self:end;
  max-width:330px;
  font-size:clamp(.92rem,1.1vw,1rem);
  /* Not --on-dark-muted (60% white). This line sits over a photograph rather
     than flat ink, so the muted token loses too much contrast to read
     comfortably. Still clearly subordinate to the headline. */
  color:rgba(250,250,250,.88);
}

/* The composition needs width to work. Below this it stacks plainly. */
@media (max-width:1024px){
  .hero-content{
    display:block;
    padding-bottom:clamp(56px,9vh,96px);
  }
  .hero .eyebrow{margin-bottom:20px}
  .hero-title{margin:0 0 26px}
  .hero-sub{max-width:42ch}
}

/* =========================================================
   About
   ========================================================= */

/* ---- Hero → About: the blinds ----------------------------------------
   Five slats, each a fifth of the viewport tall, each anchored at its own
   bottom edge and opening upward, staggered from the lowest slat to the
   highest. The hero is pinned and completely still underneath; About rises
   over it at normal scroll speed. Slats and About are the same paper, so
   the arriving section merges into the opened slats invisibly.

   These are opaque panels, not a mask on About. Reading the reference frames
   closely: the gap between the top of the *continuous* white area and its
   heading stays fixed (~250px) while the gap from the topmost white band to
   that heading changes by 160px between frames. A section's own padding
   cannot change — so the bands floating above the section are separate
   panels over the hero, not the section showing through slats.

   That also makes it cheap: scaleY on five solid divs is pure GPU
   compositing, where an animated multi-layer mask has to re-rasterise a
   full-viewport section every frame. Faithful and fast turn out to be the
   same thing here. */
.blinds{
  position:fixed; inset:0;
  z-index:3;
  pointer-events:none;
  /* layout+paint, deliberately not `strict`: strict adds size containment,
     and this element takes its size from inset:0 rather than its contents. */
  contain:layout paint;
}
.blind{
  position:absolute; left:0; right:0;
  height:20%;
  background:var(--paper);
  transform:scaleY(0);
  transform-origin:bottom center;
  will-change:transform;
}
/* About has to paint over the slats as it arrives. */
.about.is-revealing{ position:relative; z-index:4; }

/* The slats fill the viewport with paper by the end, so About must too —
   otherwise the moment the pin releases, the strip below About flips from
   paper to the next section's grey. Applies at every width now that the
   blinds run on touch as well; on a phone About's natural height (743px on a
   844px viewport) fell short and would have shown the same flash.
   Centring the copy splits the leftover space above and below rather than
   dumping all of it underneath. */
.about{
  min-height:100svh;
  display:flex;
  align-items:center;
}
.about > .wrap{ width:100%; }

.about-grid{
  display:grid;
  grid-template-columns:.9fr 1.1fr;
  gap:clamp(32px,6vw,96px);
  align-items:end;
}
.about,.about h2{font-family:var(--font-body)}
.about-head h2{
  margin-top:8px;
  font-size:clamp(2.35rem,4.8vw,4.6rem);
  line-height:.98;
  letter-spacing:-.045em;
  font-weight:400;
}
.about-body p{margin-bottom:1.4em;color:var(--on-paper)}
.about-body p:last-child{margin-bottom:0}

/* =========================================================
   Timeline — a drawn dimension line, not a bordered list.
   Desktop: a horizontal spine with tick marks at each year,
   labels alternating above/below like a measurement drawing.
   Mobile: the same tick-and-dot language, rotated vertical.
   ========================================================= */
.timeline h2{margin:0 0 40px}

.timeline-rail{
  position:relative;
  height:clamp(340px, 28vw, 420px);
  margin-top:48px;
}
/* Runs the full width of the viewport, not just the content column, so the
   line reads as continuing past the first and last milestone — the same
   device as the hero's plan lines, which also run off the edges. The rail
   is centred in the viewport, so a 100vw box centred on it spans edge to
   edge. overflow-x:clip on the section stops that becoming a scrollbar. */
.timeline{overflow-x:clip}
.timeline-spine{
  position:absolute; top:50%;
  left:50%; right:auto;
  width:100vw; margin-left:-50vw;
  max-width:none;   /* beats the img,svg{max-width:100%} reset, which would
                       otherwise clamp this back to the content column */
  height:2px;
  transform:translateY(-50%);
  color:var(--on-paper);
  opacity:.5;
}
.timeline-nodes{position:absolute; inset:0}

.tl-node{position:absolute; left:var(--tl-pos); top:50%; width:0; height:0}
.tl-tick{
  position:absolute; left:0; top:0;
  width:1px; height:16px;
  background:rgba(10,10,10,.4);
  transform:translate(-50%,-50%);
}
.tl-dot{
  position:absolute; left:50%; top:50%;
  width:8px; height:8px; border-radius:50%;
  transform:translate(-50%,-50%) scale(0);
  background:var(--on-paper);
}
.tl-node--placeholder .tl-dot{
  background:var(--paper-2);
  border:1.5px solid var(--on-paper);
  width:7px; height:7px;
}

.tl-content{
  position:absolute; left:0; width:190px;
  transform:translateX(-50%);
  text-align:center;
}
.tl-node--above .tl-content{bottom:22px}
.tl-node--below .tl-content{top:22px}

.tl-year{
  display:block;
  font-family:var(--font-display);
  font-size:1.5rem;
  margin-bottom:8px;
}
.tl-text{
  display:block;
  font-size:.84rem;
  line-height:1.5;
  color:var(--on-paper-muted);
}
.tl-flag{
  display:block;
  font-style:italic;
  font-size:.7rem;
  color:var(--on-paper-muted);
  opacity:.8;
  margin-top:6px;
}

@media (max-width:1024px){
  .timeline-rail{height:auto; margin-top:36px}
  .timeline-spine{display:none}
  .timeline-nodes{
    position:relative; inset:auto;
    display:flex; flex-direction:column;
    padding-left:26px;
  }
  /* left was 6px while the ticks and dots centre on 0, so the rail sat 6.5px
     to the right of every marker it was supposed to run through. */
  .timeline-nodes::before{
    content:"";
    position:absolute; left:0; top:8px; bottom:8px; width:1px;
    background:rgba(10,10,10,.4);
    transform:scaleY(0);
    transform-origin:top center;
    transition:transform 1.5s cubic-bezier(.16,1,.3,1);
  }
  /* JS adds .is-drawn when the section is reached, so the rail travels down
     to meet the markers instead of being there from the start — the vertical
     equivalent of the desktop spine drawing across. */
  .timeline-nodes.is-drawn::before{ transform:scaleY(1); }
  .tl-node{position:relative; left:auto; top:auto; width:auto; height:auto; padding:18px 0}
  .tl-tick{position:absolute; left:-26px; top:30px; transform:translate(-50%,-50%)}
  .tl-content{position:static; width:auto; text-align:left; transform:none}
}

/* =========================================================
   Services
   ========================================================= */
.services{position:relative}
.services-head{max-width:780px;margin-bottom:64px}
.services-intro{color:var(--on-paper-muted);margin-top:18px}

.services-spine{
  position:absolute;
  left:50%;top:clamp(260px,32vw,420px);bottom:80px;
  width:1px;
  color:var(--on-paper-line);
}
.services-spine svg{width:100%;height:100%}

.services-groups{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:clamp(32px,6vw,96px);
}
.service-group-label{
  font-family:var(--font-display);
  font-size:clamp(1.55rem,2.35vw,2.65rem);
  line-height:.98;
  letter-spacing:-.025em;
  white-space:nowrap;
  margin-bottom:18px;
  padding-bottom:24px;
  border-bottom:1px solid var(--on-paper-line);
}
.service-group-label span{
  display:block;
  font-family:var(--font-body);
  font-size:.68rem;
  letter-spacing:.1em;
  text-transform:uppercase;
  color:var(--on-paper-muted);
  margin-top:14px;
}
.service-item{
  display:block;
  margin-left:44px;
  padding:22px 0;
  border-bottom:0;
}
.service-item h3{
  font-family:var(--font-body);font-size:1rem;line-height:1.3;
  margin-bottom:10px;font-weight:500;
}
.service-item p{font-size:.88rem;color:var(--on-paper-muted);max-width:52ch}
.service-flag{
  font-style:italic;
  font-size:.82rem;
  color:var(--on-paper-muted);
  margin-top:8px;
}

/* =========================================================
   Team
   ========================================================= */
.team{min-height:100vh;min-height:100svh;overflow:hidden;padding:0}
.team-pin{
  min-height:100vh;min-height:100svh;
  display:grid;grid-template-columns:minmax(0,.9fr) minmax(360px,1.1fr);
  gap:clamp(48px,8vw,130px);align-items:center;
}
.team-intro{align-self:center}
.team-intro h2{margin:0;max-width:8.2ch}
.team-stack{position:relative;height:min(64vh,650px);min-height:430px;overflow:hidden}
.team-card{
  position:absolute;inset:0;
  display:flex;flex-direction:column;justify-content:flex-end;
  padding:clamp(28px,4vw,56px);
  padding-bottom:clamp(20px,2.4vw,34px);
  background:var(--ink-2);
  will-change:transform,opacity;
}
/* Portrait fills the card; the copy sits over it.
   Two of the four have no photograph yet and use the anonymous silhouette —
   same studio lighting and framing as the real ones, so it reads as a
   deliberate treatment rather than a missing asset. */
.team-card-photo{
  position:absolute;inset:0;z-index:0;
  width:100%;height:100%;
  object-fit:cover;
  object-position:center 25%;   /* keeps faces clear of the lower caption */
}
/* Legibility scrim. Heaviest at the bottom where the name sits, with a
   lighter lift at the very top for the index, and kept off the middle so
   the face stays open. */
.team-card::after{
  content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(to top,
    rgba(10,10,10,.88) 0%,
    rgba(10,10,10,.55) 26%,
    rgba(10,10,10,.10) 56%,
    rgba(10,10,10,.42) 100%);
}
.team-card-copy{position:relative;z-index:3}
.team-card h3{
  font-size:clamp(1.8rem,3.2vw,3.25rem);line-height:.96;
  max-width:none;white-space:nowrap;
}
.team-card p{margin-top:12px;font-size:.72rem;letter-spacing:.13em;text-transform:uppercase;color:var(--on-dark-muted)}
/* =========================================================
   Portfolio
   ========================================================= */
.portfolio{overflow:hidden}

/* Pinned while you step through the eight projects, which makes the vertical
   budget exact: the section must be one viewport tall (any more and the panel
   is cut off below the fold) while the heading still clears the fixed nav
   (any less and it sits behind it).

   So the two paddings are declared as variables and the sidebar's height is
   derived by subtracting them from the viewport. That keeps the three parts
   summing to exactly 100svh at every window size, instead of a hardcoded
   sidebar height that only happened to fit at one of them.

   Top padding starts at 104px deliberately: the nav is 80px once scrolled and
   ~96px before that, so this clears its tallest state with room to spare. */
@media (min-width: 1025px){
  .portfolio{
    --pf-pad-top:clamp(104px, 12vh, 128px);
    --pf-pad-bottom:clamp(28px, 3.5vh, 44px);
    min-height:100svh;
    display:flex;
    align-items:center;
    padding-top:var(--pf-pad-top);
    padding-bottom:var(--pf-pad-bottom);
  }
  .portfolio > .wrap{ width:100%; }
  /* Two classes deep on purpose: the base .portfolio-sidebar rule (with its
     hardcoded height and 780px cap) is declared further down this file, so a
     single-class override here would lose on source order. */
  .portfolio .portfolio-sidebar{
    height:calc(100svh - var(--pf-pad-top) - var(--pf-pad-bottom));
    max-height:none;
  }
}
.portfolio::before{
  content:"";position:absolute;inset:0;pointer-events:none;opacity:.42;
  background:
    linear-gradient(90deg,transparent calc(25% - .5px),var(--on-paper-line) 25%,transparent calc(25% + .5px)),
    linear-gradient(90deg,transparent calc(70% - .5px),var(--on-paper-line) 70%,transparent calc(70% + .5px));
}
.portfolio-shell{
  position:relative;display:grid;grid-template-columns:minmax(220px,25%) 1fr;
  gap:clamp(36px,5vw,84px);align-items:start;
}
.portfolio-sidebar{
  position:sticky;top:76px;align-self:start;
  display:flex;flex-direction:column;
  height:calc(100vh - 96px);max-height:780px;
}
.portfolio-heading{
  font-family:var(--font-display);font-size:clamp(2.3rem,4.2vw,4.5rem);
  font-weight:400;line-height:.96;margin:0 0 clamp(28px,3.5vw,48px);
}
.portfolio-index{
  min-height:0;overflow-y:auto;overscroll-behavior:contain;
  scrollbar-width:none;-ms-overflow-style:none;
  transition:-webkit-mask-image .35s var(--ease-soft),mask-image .35s var(--ease-soft);
}
.portfolio-index.has-content-above{
  -webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 42px,#000 100%);
  mask-image:linear-gradient(to bottom,transparent 0,#000 42px,#000 100%);
}
.portfolio-index::-webkit-scrollbar{display:none;width:0;height:0}
.portfolio-index::after{
  content:"";display:block;height:204px;pointer-events:none;
}
.portfolio-index li{border:0}
.project-index-button{
  width:100%;display:grid;grid-template-columns:28px 1fr;gap:3px 10px;
  padding:15px 14px;text-align:left;color:var(--on-paper-muted);
  transition:color .35s var(--ease-soft),background-color .35s var(--ease-soft),padding-left .35s var(--ease-soft);
}
.project-index-button > span{grid-row:1 / 3;font-size:.74rem;padding-top:2px}
.project-index-button strong{font-size:.9rem;font-weight:500;line-height:1.3}
.project-index-button small{font-size:.74rem;line-height:1.4;opacity:.76}
.project-index-button:hover{color:var(--on-paper);background:rgba(10,10,10,.07);padding-left:20px}
.project-index-button.is-active,.project-index-button.is-active:hover{
  color:var(--paper);background:var(--ink);padding-left:20px;
}

.portfolio-viewer{min-width:0}
.project-panel{
  display:grid;grid-template-columns:minmax(0,1.65fr) minmax(190px,.9fr);
  grid-template-rows:auto auto;gap:clamp(18px,2.2vw,34px);
  animation:projectPanelIn .75s cubic-bezier(.16,1,.3,1) both;
}
.project-panel[hidden]{display:none}
/* Wrapper around the two images. On desktop it is display:contents so the
   images stay direct grid children of .project-panel and the existing
   two-up layout is completely unaffected. Below 1025 it becomes the
   scroll-snap track for the swipe gallery. */
.project-media{display:contents}
.project-lead,.project-detail{position:relative;overflow:hidden;background:var(--paper-2)}
.project-lead{grid-column:1;grid-row:1;aspect-ratio:1.48/1}
.project-detail{grid-column:2;grid-row:1;aspect-ratio:3/4}
.project-lead img,.project-detail img{width:100%;height:100%;object-fit:cover;transition:transform 1.1s var(--ease-soft)}
.project-panel:hover .project-lead img{transform:scale(1.018)}
.project-copy{grid-column:1 / 3;display:grid;grid-template-columns:56px 1fr minmax(220px,.72fr);column-gap:18px;align-items:start;padding-top:6px}
.project-number{grid-column:1;font-size:.78rem;padding-top:.65em}
.project-copy h3{grid-column:2;font-size:clamp(2.4rem,5.2vw,5.6rem);line-height:.9;letter-spacing:-.035em}
.project-location{grid-column:2;margin-top:14px;font-size:1rem}
.project-spec{grid-column:2;margin-top:14px;font-size:.67rem!important;letter-spacing:.1em;text-transform:uppercase;color:#8b6848!important}
.project-copy > p:last-child{grid-column:3;grid-row:1 / 5;font-size:.9rem;line-height:1.65;color:var(--on-paper-muted);padding-top:8px}
.project-placeholder{
  display:flex;align-items:center;justify-content:center;
  background:
    linear-gradient(135deg,transparent 49.8%,var(--on-paper-line) 50%,transparent 50.2%),
    var(--paper-2);color:var(--on-paper-muted);
}
.project-placeholder span{font-size:.66rem;letter-spacing:.12em;text-transform:uppercase}
@keyframes projectPanelIn{
  from{opacity:0;transform:translateY(22px)}
  to{opacity:1;transform:none}
}

/* =========================================================
   FAQ
   ========================================================= */
.faq-grid{
  display:grid;
  grid-template-columns:.8fr 1.2fr;
  gap:clamp(32px,6vw,96px);
  align-items:start;
}
.faq-list{border-top:1px solid var(--on-paper-line)}
.faq-item{border-bottom:1px solid var(--on-paper-line)}
.faq-trigger{
  width:100%;
  display:flex;align-items:center;justify-content:space-between;
  gap:20px;
  padding:24px 0;
  text-align:left;
  font-family:var(--font-display);
  font-size:1.15rem;
}
.faq-icon{
  position:relative;flex:none;
  width:16px;height:16px;
}
.faq-icon::before,.faq-icon::after{
  content:"";position:absolute;top:50%;left:50%;
  background:currentColor;
  transform:translate(-50%,-50%);
}
.faq-icon::before{width:16px;height:1px}
.faq-icon::after{width:1px;height:16px;transition:transform .35s var(--ease-soft)}
.faq-trigger[aria-expanded="true"] .faq-icon::after{transform:translate(-50%,-50%) rotate(90deg)}

.faq-panel{
  max-height:0;overflow:hidden;
  transition:max-height .5s var(--ease-soft);
}
.faq-panel p{
  padding-bottom:24px;
  color:var(--on-paper-muted);
  max-width:60ch;
}

/* =========================================================
   Contact / Footer
   ========================================================= */
/* Both columns are bottom-aligned rather than top-aligned: it puts the
   contact details on the same line as the last line of the intro copy,
   and lands the CTA level with the second line of the heading, so the
   right column reads as attached to the sentence it answers instead of
   floating up beside the eyebrow. */
.contact-top{
  display:grid;
  grid-template-columns:1.3fr .8fr;
  gap:clamp(40px,6vw,100px);
  align-items:end;
  text-align:left;
  transform:translateY(clamp(20px,4vh,44px));
}
.contact-heading-col{max-width:640px}
.contact-sub{
  color:var(--on-dark-muted);
  margin:20px 0 0;
  max-width:44ch;
}

.contact-info-col{
  display:flex;flex-direction:column;
  gap:clamp(28px,4vw,48px);
}
.contact-cta{
  display:inline-flex;align-items:center;gap:10px;
  align-self:flex-start;
  font-size:.9rem;letter-spacing:.03em;
  padding-bottom:14px;
  border-bottom:1px solid var(--on-dark-line);
  transition:gap .35s var(--ease-soft), border-color .35s var(--ease-soft);
}
.contact-cta-arrow{transition:transform .35s var(--ease-soft)}
.contact-cta:hover{gap:16px;border-color:var(--on-dark)}
.contact-cta:hover .contact-cta-arrow{transform:translateX(4px)}

.contact-info-groups{display:flex;flex-wrap:wrap;gap:clamp(32px,5vw,64px)}
.contact-info-label{
  font-family:var(--font-body);
  font-weight:600;
  font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;
  color:var(--on-dark-muted);opacity:.6;
  margin-bottom:.9em;
}
.contact-info-group a{border-bottom:1px solid currentColor}

.footer-word{
  font-family:var(--font-display);
  font-size:clamp(2.8rem,9vw,9rem);
  line-height:.9;
  letter-spacing:-.02em;
  padding:0 var(--gutter);
  color:var(--on-dark);
  white-space:nowrap;
}
.footer-bottom{
  display:flex;justify-content:flex-start;flex-wrap:wrap;gap:12px;
  max-width:var(--maxw);margin:0 auto;
  padding:28px var(--gutter) 0;
  font-size:.76rem;
  color:var(--on-dark-muted);
  border-top:1px solid var(--on-dark-line);
}
.footer-bottom p{max-width:none}
/* Company registration number sits a step below the rest of the line —
   legally required to be there, but not something to read first.
   nowrap so a narrow screen can't break it mid-number ("1544118-" / "P"). */
.footer-reg{
  font-size:.88em;
  opacity:.62;
  white-space:nowrap;
}

/* The whole final section is sized to one viewport, like the reference —
   heading block and footer pinned to top/bottom of the screen with the
   leftover space between them, instead of the section's height being
   whatever its content naturally adds up to (which is how the old
   layout ended up with a big dead gap at the very end of the page). */
.contact.scene{
  min-height:100vh;
  min-height:100dvh;
  display:flex;
  flex-direction:column;
  justify-content:space-between;
  gap:clamp(32px,5vh,64px);
  padding:clamp(96px,10vh,130px) 0 clamp(28px,3.5vh,44px);
}

/* =========================================================
   Responsive
   ========================================================= */
/* =========================================================
   TABLET AND BELOW (<= 1024px)
   Desktop (>= 1025px) is deliberately untouched by everything from here on.

   The governing rule: no pinning or scroll-jacking below 1025px. All three
   pinned sequences — the hero blinds, the team card stack and the portfolio
   stepper — hold the page still and take over the scroll gesture, which is
   tolerable with a mouse and genuinely unpleasant on a touch screen where
   the scroll IS the interaction. Below the breakpoint every one of those
   sections falls back to plain, scrollable stacked content.
   ========================================================= */
@media (max-width:1024px){
  .about-grid,.faq-grid,.contact-top{grid-template-columns:1fr}
  .services-groups{grid-template-columns:1fr}
  .services-spine{display:none}

  /* ---- Team: unpinned. The base rules build a viewport-height stage with
     four absolutely-stacked cards for the scroll sequence; without the pin
     that collapses to a single card with three invisible ones behind it, so
     the whole stage has to be undone and the cards laid out in normal flow.
     Two up here, one up on phones. ---- */
  .team{min-height:0;height:auto;overflow:visible;padding:clamp(64px,8vw,112px) 0}
  .team-pin{min-height:0;display:block}
  .team-intro h2{max-width:14ch;margin-bottom:clamp(32px,5vw,52px)}
  .team-stack{
    position:static;height:auto;min-height:0;overflow:visible;
    display:grid;grid-template-columns:repeat(2,1fr);gap:clamp(12px,2vw,20px);
  }
  .team-card{position:relative;inset:auto;min-height:min(52vw,380px)}

  /* ---- Portfolio: still pinned and stepped on touch, because the eight
     projects have to be walked in order rather than skipped past. What
     changes is the index — a tall vertical list with its own inner scroll is
     a scroll trap on a phone, so it collapses to a single horizontal row of
     numbered chips above the project. One row, thumb-sized, swipeable. ---- */
  .portfolio::before{display:none}
  .portfolio{
    height:100svh;min-height:100svh;
    display:flex;flex-direction:column;justify-content:center;
    padding-top:clamp(84px,12vh,108px);
    padding-bottom:clamp(18px,3vh,30px);
  }
  .portfolio > .wrap{width:100%;height:100%}
  .portfolio-shell{
    grid-template-columns:1fr;
    grid-template-rows:auto auto minmax(0,1fr);
    gap:clamp(10px,2vw,18px);
    height:100%;
  }
  /* heading and index become direct grid rows of the shell */
  .portfolio-sidebar{display:contents}
  .portfolio-heading{
    font-size:clamp(1.45rem,5.6vw,2.1rem);
    margin:0;
  }
  .portfolio-index{
    display:flex;gap:8px;
    overflow-x:auto;overflow-y:hidden;
    height:auto;max-height:none;
    -webkit-mask-image:none;mask-image:none;
    scroll-snap-type:x proximity;
  }
  .portfolio-index::after{display:none}
  .portfolio-index li{flex:0 0 auto;scroll-snap-align:center}
  .project-index-button{
    display:flex;align-items:center;justify-content:center;
    width:44px;height:44px;padding:0;          /* full thumb target */
    border:1px solid var(--on-paper-line);
    font-size:.78rem;
  }
  .project-index-button > span{grid-row:auto;padding:0;font-size:.78rem}
  .project-index-button strong,.project-index-button small{display:none}
  /* Not yet reached: visibly inert, so the order reads as deliberate. */
  .project-index-button.is-locked{opacity:.3;pointer-events:none}

  .portfolio-viewer{min-height:0;height:100%}
  .project-panel{
    grid-template-columns:1fr;
    grid-template-rows:minmax(0,1fr) auto;
    gap:clamp(10px,2vw,16px);
    height:100%;
  }
  /* ---- Swipe gallery ----
     Native CSS scroll-snap rather than a JS swiper: real momentum, correct
     rubber-banding, and the browser disambiguates a horizontal drag from the
     vertical scroll on its own, so it cannot fight the pinned section.

     Slides are 88% wide on purpose. That deliberate sliver of the next image
     at the right edge is the affordance — it says "there is more, drag it"
     more directly than any arrow could, and it needs no control to tap. */
  .project-media{
    display:flex;
    grid-column:1;grid-row:1;
    min-height:0;
    gap:8px;
    overflow-x:auto;overflow-y:hidden;
    scroll-snap-type:x mandatory;
    overscroll-behavior-x:contain;   /* don't leak the swipe to page/back-nav */
    scrollbar-width:none;-ms-overflow-style:none;
  }
  .project-media::-webkit-scrollbar{display:none;width:0;height:0}
  .project-media > *{
    flex:0 0 88%;
    scroll-snap-align:center;
    aspect-ratio:auto;min-height:0;
  }
  .project-detail{display:block;grid-column:auto;grid-row:auto}
  .project-lead{grid-column:auto;grid-row:auto;aspect-ratio:auto;min-height:0}

  /* Pending projects have two identical placeholder tiles — one is enough. */
  .project-panel--pending .project-detail{display:none}
  .project-panel--pending .project-media > *{flex:0 0 100%}

  .project-copy{
    grid-column:1;grid-row:2;
    grid-template-columns:1fr;gap:2px;padding-top:0;
  }
  /* Setting the template to 1fr is not enough on its own — the children carry
     explicit grid-column assignments (h3 and the meta lines to column 2, the
     description to column 3) from the three-column desktop layout, and those
     alone recreate the extra columns implicitly.

     Each has to be named with matching specificity: a `.project-copy > *`
     catch-all is only (0,1,0) and loses to `.project-copy h3` at (0,1,1). */
  .project-copy .project-number,
  .project-copy h3,
  .project-copy .project-location,
  .project-copy .project-spec{
    grid-column:1;grid-row:auto;
  }
  .project-copy > p:last-child{grid-column:1;grid-row:auto}
  .project-copy .project-number{padding-top:0;margin-bottom:2px}
  .project-copy .project-location{margin-top:6px}
  .project-copy .project-spec{margin-top:8px}
  .project-copy > p:last-child{margin-top:10px;padding-top:0}
}

@media (max-width:760px){
  .nav-links,.nav-whatsapp{display:none}
  .nav-toggle{display:block}

  .hero{padding-top:100px}
  .hero-title{font-size:clamp(2.2rem,10vw,3.2rem)}
  .hero-process{display:none}

  .service-group-label{font-size:clamp(1.2rem,6vw,1.7rem)}
  .service-item{margin-left:28px}

  .timeline-list li{grid-template-columns:1fr}
  .team{min-height:auto;padding:90px 0}
  .team-pin{min-height:auto;display:block}
  .team-intro h2{max-width:10ch;margin-bottom:52px}
  /* Single column here. The tablet block above sets repeat(2,1fr), and left
     inherited at phone widths it produced two ~180px cards that overflowed
     the viewport — the columns have to be reset explicitly, not just the gap. */
  .team-stack{
    height:auto;min-height:0;overflow:visible;
    display:grid;grid-template-columns:1fr;gap:14px;
  }
  .team-card{position:relative;min-height:340px}

  /* Portfolio is fully handled by the <=1024 block above (pinned, chip row,
     single image). The old stacked rules that lived here were re-declaring
     display:grid on the index and winning on source order, which flattened
     the chip row back into a two-column list. Only phone-specific type
     sizing belongs here now. */
  .project-copy h3{font-size:clamp(1.9rem,8vw,2.6rem)}
  .project-copy .project-location{font-size:.82rem}
  .project-copy > p:last-child{font-size:.85rem}

  .footer-word{font-size:clamp(2.6rem,16vw,5rem);white-space:normal}
}

@media (max-width:480px){
  .portfolio-index{grid-template-columns:1fr}
}
