/* ==========================================================================
   Loop Recruitment: homepage stylesheet
   Section styles for index.html only: hero, Why Loop, marquee, team,
   contact and closer. Shared styles (reset, base typography, nav, footer,
   buttons, .container, .loop-mark) live in stylesheet.css.
   Must be linked AFTER stylesheet.css so these rules keep winning ties,
   matching the order of the inline block this was extracted from on
   2026-08-29.
   ========================================================================== */

/* The background is the canvas's own first two paints, in CSS.

   The canvas fills #0C0916 and then lays a vignette over it, so until the
   canvas painted the hero showed flat #0C0916 and the corners jumped from
   #0C0916 to #05030C the moment it did. On a slow connection that is a visible
   colour change after the page has already appeared to load.

   These stops are buildVignette() transcribed: same centre at 50% 46%, same
   radius of 0.70 of the width, same three stops. 70vw rather than 70% because
   a CSS circle takes a length and the hero spans the viewport, and the canvas
   radius is a fraction of ITS width. Beyond the last stop both extend the final
   colour, so the corners match too.

   If buildVignette changes, this has to change with it. */
.hero{
  position:relative;z-index:51;
  min-height:100vh;min-height:100dvh;
  overflow:hidden;display:flex;flex-direction:column;justify-content:flex-end;
  padding:140px 52px 80px;
  background:
    radial-gradient(circle 70vw at 50% 46%,
      rgba(16,8,30,0) 0%,
      rgba(8,4,18,0.28) 58%,
      rgba(3,1,8,0.75) 100%),
    #0C0916;
}

#c{position:absolute;inset:0;width:100%;height:100%;display:block;}

.hero-inner{max-width:1440px;margin:0 auto;width:100%;}

.hero-content{position:relative;z-index:10;max-width:1000px;display:flex;flex-direction:column;justify-content:flex-end;gap:36px;}

@media(min-width:769px){
  .hero-content{max-width:min(1000px, calc(100vw - 52px - 281px - 40px));}
}

.hero-logo{display:flex;align-items:center;gap:10px;text-decoration:none;}

.hero-logo-ring{
  width:22px;height:22px;
  border-radius:999px;
  border:1.5px solid transparent;
  background:
    linear-gradient(#0C0916,#0C0916) padding-box,
    linear-gradient(180deg,#c49aff 0%,#89c4ff 100%) border-box;
  box-shadow:0 0 8px 3px rgba(162,89,255,0.7),0 0 20px 6px rgba(162,89,255,0.3);
  flex-shrink:0;
  animation:ringBloom 2400ms ease forwards;
  animation-delay:0ms;
}

.hero-logo-text{font-size:2rem;font-weight:700;color:rgba(255,255,255,0.75);letter-spacing:-0.03em;transition:opacity 2000ms ease 300ms;}

h1{font-size:clamp(3.25rem,7vw,6rem);font-weight:800;line-height:1.0;letter-spacing:-0.04em;color:#fff;text-shadow:0 2px 40px rgba(0,0,0,0.9),0 4px 80px rgba(0,0,0,0.7);}

.circles-back-wrap{display:block;position:relative;line-height:1.0;}

.cb-shimmer{
  position:absolute;
  inset:0;
  display:flex;
  align-items:center;
  font-size:inherit;
  font-weight:800;
  letter-spacing:-0.04em;
  color:transparent;
  background:linear-gradient(105deg,
    rgba(255,255,255,0) 0%,
    rgba(255,255,255,0) 30%,
    rgba(255,255,255,0.85) 50%,
    rgba(255,255,255,0) 70%,
    rgba(255,255,255,0) 100%
  );
  background-size:200% 100%;
  -webkit-background-clip:text;
  background-clip:text;
  mix-blend-mode:screen;
  animation:cbShimmer 20s linear infinite;
  pointer-events:none;
}

@keyframes cbShimmer{
  0%  {background-position:150% 0;}
  100%{background-position:-150% 0;}
}

h1 svg{display:block;}

#pulse-text{}

.hero-foot{display:flex;flex-direction:row;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:24px 40px;}

.hero-sub{font-size:0.9375rem;color:rgba(255,255,255,0.7);font-weight:400;line-height:1.7;max-width:560px;min-width:260px;flex:1 1 260px;text-shadow:0 1px 20px rgba(0,0,0,0.9);}

.hero-actions{display:flex;align-items:center;gap:20px;flex-shrink:0;}

/* Shared max-width container, replaces inline styles on .s-specs wrapper */

/* 01.02 WHY LOOP, split scroll, globe version */

.why2{background:#fff;position:relative;padding:0 52px;}

.why2-inner{max-width:1440px;margin:0 auto;display:flex;}

.why2-left{width:42%;min-width:0;flex-shrink:0;position:sticky;top:61px;height:calc(100vh - 61px);min-height:560px;display:flex;flex-direction:column;justify-content:center;padding:160px 0 60px;transition:opacity 500ms ease;}

.why2-left-inner{width:100%;max-width:580px;}

.why2-right{flex:1;min-width:0;position:relative;}

.why2-driver{position:relative;height:850vh;}

.why2-sticky{position:sticky;top:61px;height:calc(100vh - 61px);min-height:560px;display:flex;align-items:center;justify-content:center;padding:40px;transition:opacity 500ms ease,transform 500ms ease;}

.why2-globe{width:min(72%,440px);opacity:0;will-change:transform,opacity;display:none;}

.why2-newgraphic{width:min(72%,440px);will-change:transform,opacity;}

.why2-newgraphic svg{width:100%;height:auto;display:block;overflow:visible;}

.why2-globe svg{width:100%;height:auto;display:block;filter:drop-shadow(0 30px 60px rgba(123,79,224,0.14));overflow:visible;}

/* individually controlled globe parts */

#ring-draw{transition:stroke-dashoffset 50ms linear;}

#ring-glint{transition:opacity 700ms ease;}

#ring-glint.visible{opacity:0.85;}

.why3-orbit{transform-box:view-box;transform-origin:206px 206px;transition:transform 950ms cubic-bezier(0.16,1,0.3,1);}

#badge-candidate-orbit{transform:rotate(-34deg);}

#badge-client-orbit{transform:rotate(34deg);}

.why3-orbit.visible{transform:rotate(0deg);}

.why3-pop{opacity:0;transform-box:fill-box;transform-origin:center;transform:scale(0.35);transition:opacity 550ms ease,transform 550ms cubic-bezier(0.34,1.56,0.64,1);}

.why3-pop.visible{opacity:1;transform:scale(1);}

.why2-heading{font-size:clamp(2rem,3.5vw,3.25rem);font-weight:800;letter-spacing:-0.04em;line-height:1.0;color:var(--dark);margin-bottom:32px;}

.why2-heading .accent{color:var(--purple);}

.why2-lines{opacity:0;transform:translateY(16px);transition:opacity 700ms ease,transform 700ms ease;margin-bottom:24px;max-width:540px;}

.why2-lines.visible{opacity:1;transform:translateY(0);}

.why2-lines p{font-size:clamp(0.9375rem,1.1vw,1rem);line-height:1.65;color:rgba(0,0,0,0.38);font-weight:500;letter-spacing:-0.005em;}

.why2-bold{opacity:0;transform:translateY(16px);transition:opacity 700ms ease,transform 700ms ease;font-size:clamp(1.25rem,1.8vw,1.625rem);font-weight:700;line-height:1.38;letter-spacing:-0.03em;color:var(--dark);margin-bottom:40px;max-width:600px;}

.why2-bold.visible{opacity:1;transform:translateY(0);}

.why2-bold .accent{color:var(--purple);}

.why2-founders{max-width:600px;}

.why2-founders-inner{display:flex;align-items:flex-start;gap:14px;margin-bottom:8px;}

.why2-portraits{display:flex;flex-shrink:0;opacity:0;transform:translateY(8px);transition:opacity 600ms ease,transform 600ms ease;}

.why2-portraits.visible{opacity:1;transform:translateY(0);}

.why2-quote-text{font-size:0.8125rem;line-height:1.65;color:rgba(0,0,0,0.38);font-weight:400;max-width:440px;opacity:0;transition:opacity 700ms ease 200ms;}

.why2-quote-text.visible{opacity:1;}

.why2-portrait{width:44px;height:44px;border-radius:50%;border:2px solid #fff;object-fit:cover;display:block;flex-shrink:0;}

.why2-portrait--offset{margin-left:-14px;}

.why2-quote-em{font-size:inherit;font-weight:600;color:var(--purple);display:inline;opacity:0;transition:opacity 700ms ease;}

.why2-quote-em.purple{opacity:1;}

.next-stub{height:100vh;background:var(--light);display:flex;align-items:center;justify-content:center;color:rgba(0,0,0,0.3);font-size:0.8125rem;letter-spacing:0.08em;text-transform:uppercase;}

@media(max-width:768px){
  .why2{padding:0 24px;}
  .why2-inner{flex-direction:column;}
  .why2-left{width:100%;position:static;height:auto;min-height:0;padding:56px 0 32px;}
  .why2-right{width:100%;}
  .why2-driver{height:auto;}
  .why2-sticky{position:static;height:auto;min-height:0;padding:0 0 56px;}
  .why2-globe{width:min(80%,340px);margin:0 auto;overflow:hidden;}
  .why2-newgraphic{width:min(80%,340px);margin:0 auto;overflow:hidden;}

  /* Belt-and-braces: force every scroll-gated element in Why Loop to its
     final visible state on mobile, in case JS hasn't run yet on first paint. */
  .why2-lines,.why2-bold,.why2-portraits,.why2-quote-text{opacity:1 !important;transform:none !important;}
  .why3-orbit{transform:rotate(0deg) !important;}

  /* The specs section below uses -100vh to sit flush under why2's pinned
     sticky on desktop. On mobile why2 un-pins to height:auto, so that fixed
     -100vh pull would drag specs up into whatever precedes it instead,
     that's the "glitchy" dark overlap. Neutralise it here. */
  .s-specs-section{margin-top:0 !important;}
  /* Same exact issue, same fix, audience-split has its own -100vh overlap
     against specs, and un-pins to height:auto on mobile too, but this one
     had no neutralization at all until now. */
  .s-aud-section{margin-top:0 !important;}

  /* Specs was the one major pinned section with NO mobile handling
     whatsoever, it stayed fully scroll-jacked on mobile while every
     other pinned section (why2, audience-split) was explicitly un-pinned
     there. Scroll-jacking is a poor fit for touch scroll gestures/momentum,
     so un-pin here too, matching the same pattern used everywhere else. */
  .s-specs-driver{height:auto !important;}
  .s-specs-sticky{position:static !important;height:auto !important;opacity:1 !important;padding:80px 24px 48px !important;}
}

/* 01.03 DISCIPLINES */

.s-specs-section{position:relative;z-index:10;}

.s-specs-sticky{
  position:sticky;top:0;height:100vh;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:48px;padding:160px 52px 60px;
  background:#fff;
  z-index:3;
}

.s-specs-title{
  font-size:clamp(2rem,4vw,3.5rem);font-weight:800;
  letter-spacing:-0.04em;line-height:1.0;color:var(--dark);
  opacity:0;transform:translateY(12px);
}

.s-specs-tags{display:flex;flex-wrap:wrap;gap:12px;justify-content:center;max-width:800px;}

.spec-bubble{
  position:relative;
  font-size:0.9375rem;font-weight:600;color:var(--dark);
  padding:10px 22px;border-radius:100px;
  border:1.5px solid transparent;
  background:#fff;
  opacity:0;transform:scale(0.5);
}

/* Gradient stroke, always active from the start now, no separate
   grey-to-gradient fade-in (simpler, and removes a chunk of the
   complexity that's been the main source of bugs in this feature).
   Standard "gradient border" technique: one background layer is the
   solid white fill (clipped to inside the border), the other is the
   travelling gradient (clipped to the border itself), using the hero
   canvas's own circle-animation palette. background-size is a FIXED
   pixel value, not a percentage, a percentage is relative to each
   bubble's own width, and since bubbles have different widths (different
   text lengths), that meant each one's gradient was actually a different
   physical scale, which is why it read as several unrelated gradients
   instead of one continuous strip. An identical absolute size for every
   bubble fixes that.
   z-index:-1 is required here, position:absolute descendants paint
   ABOVE static, non-positioned content by default regardless of DOM
   order, so this pseudo-element's own white fill layer was sitting
   directly on top of the bubble's text and hiding it completely. -1
   pushes it behind the text (and the bubble's own background) instead. */

.spec-bubble::before{
  content:'';
  position:absolute;inset:0;
  z-index:-1;
  border-radius:100px;
  border:1.5px solid transparent;
  background:
    linear-gradient(#fff,#fff) padding-box,
    linear-gradient(90deg, #A259FF, #6DD5ED, #FF7A7A, #FF43EC, #A259FF) border-box;
  background-size: 100% 100%, 1600px 100%;
  animation: bubbleGradientSweep 7s linear infinite;
  animation-delay: var(--gradient-delay, 0s);
  pointer-events: none;
}

@keyframes bubbleGradientSweep{
  from{ background-position: 0 0, 0% 0; }
  to{ background-position: 0 0, 100% 0; }
}

.s-specs-aside{
  font-size:0.9375rem;font-weight:400;color:rgba(0,0,0,0.4);
  letter-spacing:-0.01em;line-height:1.5;text-align:center;
  opacity:0;transform:translateY(6px);
}

.s-specs-more{
  font-size:0.9375rem;font-weight:700;letter-spacing:-0.01em;
  color:var(--purple);text-decoration:none;text-align:center;
  display:inline-flex;align-items:center;gap:6px;margin-top:-32px;
  opacity:0;transform:translateY(6px);
  transition:gap 160ms ease;
}

.s-specs-more:hover{gap:10px;}

/* 01.05 AUDIENCE SPLIT, scroll-driven title handoff */

.s-aud-section{position:relative;z-index:11;}

.s-aud-sticky{
  position:sticky;top:61px;min-height:calc(100vh - 61px);
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  padding:60px 52px;
  background:#fff;
  opacity:0;
  will-change:opacity;
}

.s-aud-inner{width:100%;}

/* Intro, headline line, then the split line directly beneath it, then a
   third "Loop is here with you" line that fades in after both, in place */

.aud-intro{
  position:absolute;left:50%;
  transform:translateX(-50%);width:100%;
  max-width:900px;margin:0 auto;text-align:center;
  user-select:none;cursor:default;
}

.aud-intro-head{
  will-change:opacity,transform;
}

.aud-intro-lead{
  font-size:clamp(2rem,4vw,3.5rem);font-weight:800;
  letter-spacing:-0.04em;line-height:1.0;color:var(--dark);
  margin-bottom:40px;
}

.aud-intro-split{
  font-size:clamp(1.375rem,2.4vw,2rem);font-weight:700;
  letter-spacing:-0.02em;line-height:1.3;color:var(--dark);
}

.aud-intro-or{
  color:rgba(0,0,0,0.3);font-weight:700;padding:0 10px;
}

.aud-ghost{
  visibility:hidden;display:inline-block;white-space:nowrap;
  user-select:none;
}

.aud-intro-tail{
  font-size:0.9375rem;font-weight:400;
  letter-spacing:-0.01em;line-height:1.5;color:rgba(0,0,0,0.4);
  margin-top:24px;
  opacity:0;
  will-change:opacity;
}

.aud-intro-tail-accent{color:var(--purple);}

.s-aud{display:grid;grid-template-columns:1fr 1fr;gap:32px;max-width:1440px;margin:0 auto;}

/* Col header, the title is the element that physically travels from the
   paragraph ghost to this resting spot. It has its OWN opacity/transform.
   Icon fades in together with the card, once the title has landed. */

.aud-col-header{display:flex;flex-direction:column;align-items:center;gap:14px;margin-bottom:24px;text-align:center;}

.aud-col-title{
  will-change:transform,opacity;
  font-size:clamp(1.375rem,2.4vw,2rem);font-weight:700;
  letter-spacing:-0.02em;line-height:1.3;color:var(--dark);
}

/* .aud-title-text is the positioning anchor, position:absolute on the "?"
   removes it from document flow entirely, so it can't affect the title's
   own measured width no matter what state it's in (unlike the earlier
   max-width:0 approach, which avoided width impact but clipped the glyph
   mid-reveal as it grew, a visible "wipe" rather than a clean fade). The
   full character always renders at its natural size here; only opacity
   animates. */

.aud-title-text{position:relative;display:inline-block;}

.aud-title-q{
  position:absolute;
  left:100%;
  top:0;
  margin-left:2px;
  opacity:0;
  white-space:nowrap;
}

/* Audience marker: icon and pill CTA. Homepage only again since the
   contact page was rebuilt around its own picker on 2026-08-29. */

.s-aud .aud{
  opacity:0;
  transform:translateY(24px);
}

.aud-col-icon{width:44px;height:44px;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0;}

.aud-col-icon--client{background:rgba(137,196,255,0.12);color:#89C4FF;}

.aud-col-icon--candidate{background:rgba(255,155,133,0.12);color:#FF9B85;}

/* CTA */

.aud-cta{display:inline-flex;align-items:center;justify-content:center;font-size:0.9375rem;font-weight:700;letter-spacing:-0.01em;text-decoration:none;padding:14px 28px;border-radius:100px;margin-top:auto;transition:opacity 200ms ease;}

.aud-cta:hover{opacity:0.85;}

.aud-cta--client{background:#89C4FF;color:#0C0916;}

.aud-cta--candidate{background:#FF9B85;color:#0C0916;}

.aud-col-header .aud-col-icon{opacity:0;}

/* Card */

.aud{background:#0C0916;border-radius:20px;padding:40px;display:flex;flex-direction:column;}

.aud-headline{font-size:clamp(1.375rem,2.2vw,1.875rem);font-weight:800;letter-spacing:-0.03em;line-height:1.15;color:#fff;margin-bottom:28px;}

.aud--client .aud-accent{color:#89C4FF;}

.aud--candidate .aud-accent{color:#FF9B85;}

.aud-divider{border:none;border-top:1px solid rgba(255,255,255,0.08);margin:0 0 24px;}

.aud-body{font-size:0.9375rem;line-height:1.7;color:#fff;margin-bottom:28px;}

.aud--candidate .aud-highlight{color:#FF9B85;font-weight:600;}

.aud--client .aud-highlight{color:#89C4FF;font-weight:600;}

/* Features */

.aud-features{display:grid;grid-template-columns:1fr 1fr 1fr;gap:0;margin-bottom:32px;}

.aud-feature{display:flex;align-items:flex-start;gap:10px;padding:0 16px;border-right:1px solid rgba(255,255,255,0.08);}

.aud-feature:first-child{padding-left:0;}

.aud-feature:last-child{border-right:none;padding-right:0;}

.aud-feature-icon{width:18px;height:18px;flex-shrink:0;opacity:0.35;margin-top:1px;}

.aud-feature-text{font-size:0.75rem;font-weight:500;color:rgba(255,255,255,0.4);line-height:1.45;}

@media(max-width:768px){
  .s-aud{grid-template-columns:1fr;}
  .s-aud-sticky{padding:56px 24px;}
  .aud{padding:28px;}

  /* Mobile: no scroll-jacked ghost-flip. Un-pin the section entirely, hide the
     intro sentence (nothing for it to hand off to once the cards are stacked
     instead of side-by-side), and force every element to its normal resting,
     fully-visible state regardless of scroll position. */
  .s-aud-driver{height:auto !important;}
  .s-aud-sticky{position:static !important;min-height:0 !important;opacity:1 !important;}
  .aud-intro{display:none !important;}
  .aud-col-icon{opacity:1 !important;}
  .aud-col-title{transform:none !important;opacity:1 !important;}
  .aud-title-q{opacity:1 !important;}
  .aud{opacity:1 !important;transform:none !important;}

  /* Social proof section had NO mobile handling at all, same 24px
     padding convention used by every other section on mobile. The
     copy/stats naturally stack via the existing flex-wrap, and the
     marquee's mask-image fade already uses percentages, so it scales
     with the container automatically, neither needs its own override. */
  .social-proof-top{padding:0 24px;}
}

/* ── 01.06 TEAM ── */

/* Placeholder, social proof section, content TBD, real logos to replace
   the "Logo" placeholders later */

.s-social-proof-placeholder{
  padding:80px 0 150px;
  background:linear-gradient(to bottom, #F5F5F7, #ffffff);
  overflow:hidden;
}

.social-proof-top{
  display:flex;align-items:flex-start;justify-content:space-between;
  gap:60px 40px;flex-wrap:wrap;
  max-width:1544px;margin:0 auto 120px;padding:0 52px;
}

.social-proof-copy{flex:1 1 420px;min-width:280px;}

.social-proof-title{
  font-size:clamp(2rem,3.5vw,3rem);font-weight:800;
  letter-spacing:-0.035em;line-height:1.08;color:var(--dark);
  margin-bottom:20px;
}

.social-proof-sub{
  font-size:1rem;line-height:1.75;color:#4a4a4a;max-width:520px;
}

.social-proof-stats-wrap{
  display:flex;flex-direction:column;align-items:flex-start;gap:28px;
  flex-shrink:0;padding-top:8px;
}

.social-proof-stats{
  display:flex;gap:48px;flex-wrap:wrap;
}

.social-proof-stat{display:flex;flex-direction:column;gap:8px;min-width:140px;}

.social-proof-stat-number{
  font-size:clamp(2rem,3vw,2.75rem);font-weight:800;
  letter-spacing:-0.03em;line-height:1;color:var(--dark);
}

.social-proof-stat-label{
  font-size:0.875rem;line-height:1.4;color:#6b6b6b;max-width:160px;
}

.social-proof-logos{
  max-width:1440px;margin:0 auto;
  overflow:hidden;
  position:relative;
  -webkit-mask-image:linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
  mask-image:linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
}

.social-proof-logos-track{
  display:flex;align-items:center;width:max-content;
  animation:socialProofScroll 28s linear infinite;
  color:#9aa0a6;
}

.social-proof-logo-img{
  height:28px;width:auto;flex-shrink:0;display:block;
}

.social-proof-logo-set{
  display:flex;align-items:center;gap:80px;flex-shrink:0;
  margin-right:80px;
}

.social-proof-logo{
  font-size:1.5rem;font-weight:800;letter-spacing:-0.02em;
  color:rgba(12,9,22,0.18);white-space:nowrap;flex-shrink:0;
}

@keyframes socialProofScroll{
  from{transform:translateX(0);}
  to{transform:translateX(-50%);}
}

.s-team{background:var(--light);padding:80px 52px 160px;overflow:hidden;}

.team-head{max-width:600px;margin-bottom:64px;}

.team-title{font-size:clamp(2rem,3.5vw,3rem);font-weight:800;letter-spacing:-0.035em;line-height:1.08;color:var(--dark);margin-bottom:20px;}

.team-sub{font-size:1rem;line-height:1.75;color:#4a4a4a;max-width:480px;}

.team-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:24px;margin-bottom:48px;}

.s-team .team-card{opacity:0.35;transform:translateY(12px);transition:opacity 900ms cubic-bezier(0.25,1,0.5,1), transform 900ms cubic-bezier(0.25,1,0.5,1);}

.s-team .team-card.is-visible{opacity:1;transform:translateY(0);}

/* The role sits grey, not the shared purple. Colour on this section is
   carried by the photo wash on hover, which is per person, so purple on
   every role read as a fifth colour competing with the four tints while
   telling you nothing about who you were looking at. Grey leaves the name
   as the only dark thing in the card and lets the four read as one set.
   5.08:1 on --light, and it keeps the step down to .team-spec's #888. */
.s-team .team-role{color:#6A6A6A;}

/* The shared card turns the name purple on hover. Overridden here for the
   same reason the role is grey: the hover colour on this section is the
   photo wash, which is the person's own, and a purple name landing beside
   a coral or cyan wash is a second hue saying nothing. The name stays dark
   and the photo does the talking. contact.css pins this to white for its
   own reasons, so the shared purple is now unused by both pages. */
.s-team .team-card:hover .team-name{color:var(--dark);}

/* LinkedIn badge on the photo, revealed with the wash.

   Every one of these cards is already a link to that person's LinkedIn,
   which the card never said anywhere. The badge makes the destination
   visible at the moment someone points at it, alongside the greyscale lift
   and the tint.

   Uses ::before because ::after is already the colour wash. It sits above
   that wash on z-index deliberately: the wash is mix-blend-mode:color, and
   anything painted below it gets tinted, which would drag a white glyph
   toward the person's hue and cost legibility. Painting above keeps it out
   of the wash's backdrop entirely.

   Decorative only. Content is empty and pointer-events are off, so it adds
   nothing for a screen reader and cannot swallow the click. With no disc
   behind it the white mark has nothing to sit on, so a drop-shadow does
   that job instead: these photos run from near black to pale lilac and the
   mark would disappear into the light ones unaided. Timing matches
   the wash and the greyscale lift at 400ms ease, so it reads as one gesture.
   Reduced motion collapses the transition via stylesheet.css and the badge
   simply appears. */
.s-team .team-photo::before{
  content:'';
  position:absolute;
  bottom:14px;right:14px;
  width:24px;height:24px;
  z-index:2;
  pointer-events:none;
  background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Cpath d='M20.45 20.45h-3.56v-5.57c0-1.33-.02-3.04-1.85-3.04-1.85 0-2.14 1.45-2.14 2.94v5.67H9.35V9h3.41v1.56h.05c.48-.9 1.64-1.85 3.37-1.85 3.6 0 4.27 2.37 4.27 5.46v6.28zM5.34 7.43a2.07 2.07 0 1 1 0-4.14 2.07 2.07 0 0 1 0 4.14zM7.12 20.45H3.55V9h3.57v11.45zM22.22 0H1.77C.79 0 0 .77 0 1.73v20.54C0 23.22.79 24 1.77 24h20.45c.98 0 1.78-.78 1.78-1.73V1.73C24 .77 23.2 0 22.22 0z'/%3E%3C/svg%3E") no-repeat center;
  background-size:contain;
  filter:drop-shadow(0 1px 3px rgba(12,9,22,0.5));
  opacity:0;
  transform:translateY(4px);
  transition:opacity 400ms ease, transform 400ms ease;
}

.s-team .team-card:hover .team-photo::before{opacity:1;transform:translateY(0);}

.team-link{font-size:0.875rem;font-weight:700;color:var(--purple);text-decoration:none;display:inline-flex;align-items:center;gap:6px;transition:gap 160ms;}

.team-link:hover{gap:10px;}

/* ── 01.07 CONTACT PROMPT ── */

.s-closer{min-height:calc(100vh - 61px);background:var(--dark);display:flex;flex-direction:column;}

.s-contact{background:var(--dark);padding:80px 52px;position:relative;overflow:hidden;flex:1;display:flex;align-items:center;justify-content:center;}

.contact-canvas{position:absolute;inset:0;width:100%;height:100%;z-index:0;}

.s-contact .container{position:relative;z-index:1;}

.contact-inner{max-width:640px;margin:0 auto;text-align:center;position:relative;z-index:2;}

.contact-title{font-size:clamp(2rem,4vw,3.5rem);font-weight:800;letter-spacing:-0.04em;line-height:1.0;color:#fff;margin-bottom:24px;text-align:center;text-wrap:balance;}

.contact-sub{font-size:1rem;line-height:1.75;color:rgba(255,255,255,0.5);margin-bottom:40px;max-width:480px;margin-left:auto;margin-right:auto;}

.contact-actions{display:flex;align-items:center;justify-content:center;gap:24px;margin-bottom:32px;}

/* 0.5, not 0.35. This rule sat orphaned while the markup was away, and at
   0.35 the lead-in measured 3.14:1 on --dark, under the 4.5:1 small text
   needs. 0.5 is 5.32:1 and is the value .contact-sub already uses in this
   same section, so the two quiet lines now match. The link itself was
   always fine at 0.6, which is 7.28:1. */
.contact-email{font-size:0.875rem;color:rgba(255,255,255,0.5);}

.contact-email a{color:rgba(255,255,255,0.6);text-decoration:none;border-bottom:1px solid rgba(255,255,255,0.2);transition:color 300ms ease,border-color 300ms ease;}

.contact-email a:hover{color:#fff;border-color:rgba(255,255,255,0.6);}

@media(max-width:768px){
  .team-grid{grid-template-columns:1fr 1fr;}
  .s-team,.s-contact{padding:80px 24px;}
  .hero{padding:100px 24px 40px;}
  .s-aud{grid-template-columns:1fr;}.aud{padding:52px 24px;}
}

/* ── HERO ENTER ANIMATION ── */

@keyframes logoIn{
  from{opacity:0;transform:translateY(6px);}
  to  {opacity:1;transform:translateY(0);}
}

@keyframes ringBloom{
  0%  {opacity:0;box-shadow:0 0 0px 0px rgba(162,89,255,0);}
  100%{opacity:1;box-shadow:0 0 8px 3px rgba(162,89,255,0.7),0 0 20px 6px rgba(162,89,255,0.3);}
}

@keyframes fadeIn{
  from{opacity:0;}
  to  {opacity:1;}
}

@keyframes fadeUp{
  from{opacity:0;transform:translateY(8px);}
  to  {opacity:1;transform:translateY(0);}
}

h1{opacity:1;}

/* Delays shifted +300ms (was 1800/2700/2700/3500/3800) so the logo
   animation (ring draws to 1250ms, then glow+letters build in, fully
   settled by ~2000ms) completes cleanly before anything else on the page
   starts its own entrance, h1-a previously started at 1800ms, 200ms
   before the logo had finished, now starts at 2100ms instead. Relative
   stagger between these elements is unchanged, just the whole group
   pushed later as one block. */

.h1-a{display:inline;transition:opacity 3600ms cubic-bezier(0.16,1,0.3,1) 2100ms, transform 3600ms cubic-bezier(0.16,1,0.3,1) 2100ms;}

.h1-b{display:inline;transition:opacity 3600ms cubic-bezier(0.16,1,0.3,1) 3000ms, transform 3600ms cubic-bezier(0.16,1,0.3,1) 3000ms;}

.h1-c{display:inline;transition:opacity 3600ms cubic-bezier(0.16,1,0.3,1) 3000ms, transform 3600ms cubic-bezier(0.16,1,0.3,1) 3000ms;}

.hero-foot{opacity:1;}

.hero-sub{transition:opacity 2000ms ease 3800ms, transform 2000ms ease 3800ms;}

.hero-actions{transition:opacity 1600ms ease 4100ms, transform 1600ms ease 4100ms;}

/* Start state only. Removed once the document is ready, so the transitions
   run toward the declared, visible values above. Nothing here owns
   visibility: strip this class, or kill transitions, and the hero renders
   finished. It used to be the other way round, with opacity:0 declared and
   an animation's forwards fill responsible for bringing it back, which left
   the hero blank both under reduced motion and in any screenshot tool that
   captures with animations stopped. */
.entrance .hero-logo-text{opacity:0;}

.entrance .h1-a,.entrance .h1-b,.entrance .h1-c,
.entrance .hero-sub,.entrance .hero-actions{opacity:0;transform:translateY(8px);}

/* Reduced motion: stop the pulse animation; canvas loop is also halted in JS */

@media(prefers-reduced-motion:reduce){
  /* Exception: this decorative logo marquee stays animating regardless,
     class selector beats the universal selector above even with matching
     !important on both sides, so this specifically wins. */
  .social-proof-logos-track{animation:socialProofScroll 28s linear infinite!important;}

  /* Restore the hero.

     These six all declare opacity:0 and rely on their animation's forwards
     fill to become visible. The universal animation:none in stylesheet.css
     strips the animation AND its fill, so the declared opacity:0 was left
     standing and the hero rendered with no logo text, no H1, no subtext and
     no buttons. Background and logo mark only.

     This restores the FINISHED state, not the starting one, so the hero
     simply appears already assembled instead of animating in. Nothing is
     lost but the entrance.

     Must stay below the .h1-a / .hero-sub / .hero-actions rules above:
     equal specificity, so source order decides. transform:none is belt and
     braces, fadeUp's 8px rise is only ever applied by the animation. */
  .hero-logo-text,
  .h1-a,.h1-b,.h1-c,
  .hero-sub,.hero-actions{opacity:1;transform:none;}
}

/* ===== Loop animated logo, scoped under .loop-mark ===== */

/* Source: LOOP-LOGO-HANDOFF.md, pasted in as-is except for the hero-specific
   size override immediately below it. */

/* 1.3264em (not the component's own default 1.35em), precisely computed
   so the ring's visible diameter (including its 3.4 stroke-width) exactly
   matches the cap-height of the "L" in Plus Jakarta Sans Bold, which
   measures 0.745 of font-size (measured directly via canvas glyph
   metrics, not estimated). Math: ring visible diameter in the 120
   viewBox = 2*(32+3.4/2) = 67.4, i.e. 0.5617 of the viewBox; solving
   svgSize * 0.5617 = loopSize * 0.745 gives svgSize = 1.3264em. */

@media(prefers-reduced-motion:reduce){
  .loop-mark *{animation:none!important;}
  .loop-mark__ring{stroke-dashoffset:0!important;}
  .loop-mark__glow{opacity:1!important;}
  .loop-mark__word span{opacity:0.9!important;transform:none!important;}
}

/* ── REDUCED MOTION ──────────────────────────────────────────────────────
   The three scroll-scrubbed sections take the same early-return path in JS
   as mobile does, so nothing scrubs them and every gated element would sit
   at its declared opacity:0 forever. This un-pins them and forces the
   settled state.

   Deliberately NOT a copy of the max-width:768px blocks. Those also stack
   the columns, shrink the paddings and centre the globe, which is right for
   a narrow screen and wrong on a wide one: it drops the globe below the
   text and strips the whitespace above the heading. The desktop layout is
   kept, and only the pinning and the visibility change.

   The two margin rules are the ones that matter most. .s-specs-section and
   .s-aud-section carry an inline margin-top:-100vh so they sit flush under
   the pinned stickies. Un-pin without cancelling that and they are dragged
   up over Why Loop. A first version of this block omitted them and broke
   the page badly. Do not remove them. */
@media(prefers-reduced-motion:reduce){

  /* Cancel the inline -100vh pulls. Without these, everything below
     overlaps. */
  .s-specs-section{margin-top:0 !important;}
  .s-aud-section{margin-top:0 !important;}

  /* ── Why Loop: un-pin, keep the two-column layout and its top padding ── */
  .why2-driver{height:auto;}
  /* height:auto rather than the pinned calc(), but the min-height is kept,
     not zeroed. Zeroing it collapsed every section to its content height and
     left them sitting almost on top of one another. Sections keep a screen
     each, which is the rhythm the pinned version had. What goes is the 850vh
     of dead scroll, not the breathing room. */
  .why2-left{position:static;height:auto;min-height:calc(100vh - 61px);}
  /* Same vertical padding as .why2-left (160/60), so both columns centre
     their contents on the same axis. Without it the sticky keeps its plain
     40px and the circle centres about 60px above the text. Horizontal
     padding stays 40px so the circle does not shift sideways. */
  .why2-sticky{position:static;height:auto;min-height:calc(100vh - 61px);padding:160px 40px 60px;}
  .why2-lines,.why2-bold,.why2-portraits,
  .why2-quote-text,.why2-quote-em{opacity:1 !important;transform:none !important;}
  .why3-orbit{transform:rotate(0deg) !important;}

  /* ── Disciplines ── */
  .s-specs-driver{height:auto !important;}
  /* Two trims, not one. The other two sections sit at calc(100vh - 61px),
     but this one holds the least content (roughly 270px of heading, bubbles
     and caption) so it needs less room. 70vh handles that.

     The bigger cause of the white space was the padding: 160px on top,
     which existed to clear the fixed nav while the section was pinned.
     Un-pinned there is nothing to clear, so it comes down to 80px. Both
     together, rather than pushing the min-height very low, which would have
     squeezed the bubbles against the caption instead. */
  .s-specs-sticky{position:static !important;height:auto !important;min-height:70vh;opacity:1 !important;padding:80px 52px 60px !important;}
  .s-specs-title,.s-specs-aside,.s-specs-more{opacity:1 !important;transform:none !important;}
  .spec-bubble{opacity:1 !important;transform:scale(1) !important;}

  /* Hold the gradient still at the position it would have swept to, so the
     row still reads as one continuous strip rather than seven identical
     purple outlines. --gradient-pos is set alongside --gradient-delay in
     the same JS loop. */
  .spec-bubble::before{background-position:0 0, var(--gradient-pos, 0%) 0 !important;}

  /* ── Audience split. The intro sentence is hidden, as on mobile: it exists
     to hand off into the ghost-flip, and without the flip it has nowhere to
     go. ── */
  .s-aud-driver{height:auto !important;}
  /* min-height not overridden: the rule already carries calc(100vh - 61px). */
  .s-aud-sticky{position:static !important;opacity:1 !important;}
  .aud-intro{display:none !important;}
  .aud-col-icon{opacity:1 !important;}
  .aud-col-title{transform:none !important;opacity:1 !important;}
  .aud-title-q{opacity:1 !important;}
  .aud{opacity:1 !important;transform:none !important;}

  /* ── Team cards: no intro at all ──
     These are IntersectionObserver driven rather than scroll-scrubbed, so
     they would still arrive without this: 0.35 opacity, then staggered to 1
     in 120ms steps. That is still an entrance. Reduced motion gets them
     already there. The JS keeps adding .is-visible and is simply a no-op,
     which is cheaper than special-casing the observer. */
  .s-team .team-card{opacity:1 !important;transform:none !important;}
}
