/* ===========================================================
   EndlessLoop — Redesign
   Design system: type, color, layout, motion
   =========================================================== */

:root{
  /* Brand palette (from Endlessloop's design system) */
  --blue:#0349ff;
  --blue-dark:#001a5e;
  --blue-darker:#000d2f;
  --blue-light:#aec5ff;
  --blue-snowy:#f3f8ff;

  --ink:var(--blue-darker);
  --ink-soft:var(--blue-dark);
  --paper:var(--blue-snowy);
  --paper-dim:#e4edfb;
  --line: rgba(0,13,47,0.12);
  --line-invert: rgba(243,248,255,0.16);
  --accent:var(--blue);
  --accent-soft:var(--blue-light);
  --muted:#5c6579;
  --muted-invert:#9fb0cc;
  --radius:18px;
  --btn-radius:10px;
  --ease: cubic-bezier(.16,.84,.32,1);
  --dur-fast: .35s;
  --dur: .7s;
  --dur-slow: 1.1s;
  --maxw: 1240px;
}

*{box-sizing:border-box;}
html{scroll-behavior:smooth;}
body{
  margin:0;
  background:var(--paper);
  color:var(--ink);
  font-family:'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  font-size:16px;
  line-height:1.5;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
img{max-width:100%;display:block;}
a{color:inherit;text-decoration:none;}
h1,h2,h3,h4{
  font-family:'Inter', sans-serif;
  font-weight:600;
  line-height:1.06;
  letter-spacing:-0.02em;
  margin:0;
}
p{margin:0;}
ul{margin:0;padding:0;list-style:none;}
button{font-family:inherit;}

.wrap{max-width:var(--maxw);margin:0 auto;padding:0 32px;}
@media(max-width:720px){.wrap{padding:0 20px;}}

/* -------- custom cursor -------- */
.cursor{
  position:fixed;top:0;left:0;width:10px;height:10px;border-radius:50%;
  background:var(--accent);pointer-events:none;z-index:9998;
  transform:translate(-50%,-50%);
  transition:width .25s var(--ease), height .25s var(--ease), background .25s var(--ease), opacity .25s;
  mix-blend-mode:difference;
}
.cursor.hover{width:46px;height:46px;background:var(--paper);}
@media(hover:none){.cursor{display:none;}}

/* -------- nav -------- */
.nav{
  position:fixed;top:0;left:0;right:0;z-index:500;
  display:flex;align-items:center;justify-content:space-between;
  padding:22px 32px;
  transition:transform var(--dur-fast) var(--ease), background .4s ease, backdrop-filter .4s ease, border-color .4s ease;
  border-bottom:1px solid transparent;
}
.nav.scrolled{
  background:rgba(243,248,255,0.72);
  backdrop-filter:blur(14px) saturate(1.4);
  border-bottom:1px solid var(--line);
}
.nav.hide-nav{transform:translateY(-115%);}
.nav__logo{
  font-family:'Inter',sans-serif;
  font-weight:600;font-size:19px;letter-spacing:-0.01em;
  display:flex;align-items:center;gap:9px;
}
.nav__logo .logo-symbol{
  width:26px;height:26px;display:block;flex:none;
}
.nav__links{display:flex;gap:36px;align-items:center;}
.nav__links a{
  font-size:14.5px;font-weight:500;position:relative;padding:4px 0;
}
.nav__links a::after{
  content:'';position:absolute;left:0;bottom:0;width:0;height:1px;background:var(--ink);
  transition:width var(--dur-fast) var(--ease);
}
.nav__links a:hover::after{width:100%;}
.nav__links .btn{padding:15px 30px;}
.nav__links .btn::after{display:none;}
.nav__burger{display:none;}
@media(max-width:820px){
  .nav__links{
    position:fixed;inset:0;background:var(--ink);flex-direction:column;justify-content:center;
    transform:translateX(100%);transition:transform .5s var(--ease);gap:28px;
  }
  .nav__links.open{transform:translateX(0);}
  .nav__links a{color:var(--paper);font-size:26px;border-color:var(--line-invert);}
  .nav__links .btn{font-size:16px;}
  .nav__burger{display:block;z-index:600;background:none;border:none;cursor:pointer;padding:6px;}
  .nav__burger span{display:block;width:24px;height:2px;background:var(--ink);margin:5px 0;transition:.3s;}
}

/* -------- buttons -------- */
.btn{
  position:relative;overflow:hidden;display:inline-flex;align-items:center;gap:10px;
  border-radius:var(--btn-radius);padding:15px 30px;font-size:14.5px;font-weight:500;
  border:1px solid var(--ink);cursor:pointer;isolation:isolate;
}
.btn span{position:relative;z-index:2;transition:color var(--dur-fast) var(--ease);}
.btn::before{
  content:'';position:absolute;inset:0;background:var(--ink);
  transform:translateY(101%);transition:transform var(--dur) var(--ease);z-index:1;
}
.btn:hover::before{transform:translateY(0);}
.btn:hover span{color:var(--paper);}
.btn svg{position:relative;z-index:2;transition:transform var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);}
.btn:hover svg{transform:translate(3px,-3px);color:var(--paper);}
.btn--accent{background:var(--accent);border-color:var(--accent);}
.btn--accent::before{background:var(--ink);}
.btn--accent span{color:var(--paper);}

/* -------- hero -------- */
.hero{
  min-height:100vh;display:flex;flex-direction:column;justify-content:center;
  padding-top:140px;padding-bottom:80px;position:relative;
  overflow:hidden;
}
.hero__lottie{
  position:absolute;inset:0;z-index:0;overflow:hidden;pointer-events:none;
}
.hero__lottie svg{width:100%!important;height:100%!important;opacity:.1;}
.hero__lottie::after{
  content:'';position:absolute;inset:0;
  background:linear-gradient(to bottom, rgba(0,0,0,0) 45%, var(--paper) 96%);
}
.hero > .wrap{position:relative;z-index:2;}
.hero__eyebrow{
  display:inline-flex;align-items:center;gap:8px;font-size:13px;font-weight:500;
  color:var(--muted);text-transform:uppercase;letter-spacing:.08em;margin-bottom:26px;
}
.hero__eyebrow::before{content:'';width:6px;height:6px;border-radius:50%;background:var(--accent);}
.hero h1{
  font-size:clamp(40px,7vw,92px);max-width:16ch;
}
.hero h1 .line{display:block;overflow:hidden;}
.hero h1 .line span{
  display:inline-block;transform:translateY(-5px);
  filter:blur(10px);opacity:0;
  transition:transform .4s var(--ease), filter .4s var(--ease), opacity .4s var(--ease);
}
.hero.loaded h1 .line span{transform:translateY(0);filter:blur(0);opacity:1;}
.hero h1 .line:nth-child(1) span{transition-delay:.05s;}
.hero h1 .line:nth-child(2) span{transition-delay:.15s;}
.hero h1 .line:nth-child(3) span{transition-delay:.25s;}
.hero__sub{
  margin-top:32px;max-width:520px;font-size:18px;color:var(--muted);
  opacity:0;transform:translateY(16px);transition:opacity .9s var(--ease) .5s, transform .9s var(--ease) .5s;
}
.hero.loaded .hero__sub{opacity:1;transform:translateY(0);}
.hero__actions{
  margin-top:44px;display:flex;gap:16px;align-items:center;
  opacity:0;transform:translateY(16px);transition:opacity .9s var(--ease) .65s, transform .9s var(--ease) .65s;
}
.hero.loaded .hero__actions{opacity:1;transform:translateY(0);}
.hero__marquee{
  position:absolute;bottom:0;left:0;right:0;border-top:1px solid var(--line);
  padding:20px 0;overflow:hidden;white-space:nowrap;z-index:2;
}
.hero__marquee-track{display:flex;width:max-content;animation:marquee 26s linear infinite;}
.hero__marquee-group{display:flex;flex:none;gap:60px;padding-right:60px;}
.hero__marquee span{font-family:'Inter',sans-serif;font-size:14px;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;white-space:nowrap;}
@keyframes marquee{from{transform:translateX(0);}to{transform:translateX(-50%);}}

/* -------- reveal on scroll -------- */
.reveal{opacity:0;transform:translateY(36px);transition:opacity .9s var(--ease), transform .9s var(--ease);}
.reveal.in-view{opacity:1;transform:translateY(0);}
.reveal-stagger > *{opacity:0;transform:translateY(28px);transition:opacity .8s var(--ease), transform .8s var(--ease);}
.reveal-stagger.in-view > *{opacity:1;transform:translateY(0);}
.reveal-stagger.in-view > *:nth-child(1){transition-delay:0s;}
.reveal-stagger.in-view > *:nth-child(2){transition-delay:.08s;}
.reveal-stagger.in-view > *:nth-child(3){transition-delay:.16s;}
.reveal-stagger.in-view > *:nth-child(4){transition-delay:.24s;}
.reveal-stagger.in-view > *:nth-child(5){transition-delay:.32s;}

/* -------- section basics -------- */
section{padding:120px 0;}
@media(max-width:720px){section{padding:80px 0;}}

/* manifesto gets extra breathing room so the word-reveal scroll distance is longer */
.manifesto{padding:220px 0;}
@media(max-width:720px){.manifesto{padding:180px 0;}}
.section__head{display:flex;justify-content:space-between;align-items:flex-end;gap:40px;margin-bottom:64px;}
.section__head h2{font-size:clamp(28px,4vw,46px);max-width:14ch;}
.section__tag{font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--accent);margin-bottom:14px;display:block;}
.section__desc{max-width:420px;color:var(--muted);font-size:16px;}
@media(max-width:820px){.section__head{flex-direction:column;align-items:flex-start;gap:20px;}}

/* dark section variant */
.section--dark{background:var(--ink);color:var(--paper);}
.section--dark .muted{color:var(--muted-invert);}
.section--dark .section__desc{color:var(--muted-invert);}

/* -------- intro / manifesto -------- */
.manifesto p{
  font-family:'Inter',sans-serif;font-weight:500;
  font-size:clamp(24px,3.4vw,42px);line-height:1.28;max-width:1000px;letter-spacing:-0.01em;
}
.manifesto .word{
  color:var(--muted);opacity:.4;
  transition:color .5s var(--ease), opacity .5s var(--ease);
}
.manifesto .word.lit{color:var(--ink);opacity:1;}
.manifesto .word--accent.lit{color:var(--accent);}

/* -------- our approach: headline word-reveal + stacked content list -------- */
/* "full" / "product" / "lifecycle" in the h2 start dim and light up one by
   one in sync with the .approach-item list below (see script.js). */
.approach-word{
  display:inline-block;color:var(--paper);
  opacity:0;transform:translateY(-5px);filter:blur(10px);
}

/* This section's heading column stacks tag/h2/description and sits
   beside .approach-list as two top-aligned flex children (rather than
   the site's usual heading-left/description-right split), so item 1
   in the list reads as starting level with the headline. */
.approach-pin .section__head{align-items:flex-start;margin-bottom:0;}
.approach-pin .section__head .section__desc{margin-top:20px;}

/* Right-aligned vertical stack, no card fill — sits where the intro
   paragraph used to, roughly the same column width as .section__desc. */
.approach-list{display:flex;flex-direction:column;gap:32px;max-width:420px;text-align:right;margin-top:16px;}
.approach-item .num{font-family:'Inter',sans-serif;font-size:13px;color:var(--accent);font-weight:600;display:block;}
.approach-item h3{font-size:20px;margin:10px 0 8px;color:var(--paper);}
.approach-item p{font-size:14.5px;color:var(--muted-invert);}

/* -------- our approach: scroll-pinned reveal --------
   #approachPin is a tall track (its own height, well beyond one viewport)
   with .approach-pin__sticky pinned via position:sticky. Scrolling through
   that extra height is what drives the 4-state reveal in script.js — no
   scroll-jacking, the browser handles the actual pinning/release natively
   and the user can always scroll through normally.

   All original card copy (We strategise / We design & develop / We test &
   iterate / We launch & maintain) stays exactly as-is, just restyled as a
   plain right-aligned list with no background. As you scroll, "full",
   "product" and "lifecycle" light up in the headline above while the
   matching .approach-item fades/moves in below; the final stage reveals
   .section__desc alongside the fourth item.

   On a short/narrow viewport, the stacked list plus heading can be taller
   than the screen — pinning it would trap the lower items off-screen with
   no way to scroll to them. script.js detects that case at runtime (not
   just by width — a short *height* triggers it too) and adds .no-pin,
   switching to a normal per-item reveal-on-scroll instead (same sequence,
   just not pinned) so the storytelling survives without a broken layout. */
.approach-pin{position:relative;height:380vh;}
.approach-pin__sticky{position:sticky;top:0;min-height:100vh;display:flex;align-items:center;}
@media(max-width:900px){.approach-pin{height:320vh;}}
@media(max-width:560px){.approach-pin{height:260vh;}}
.approach-reveal{opacity:0;}

.approach-pin.no-pin{height:auto;}
.approach-pin.no-pin .approach-pin__sticky{position:static;min-height:0;display:block;}
.approach-pin.no-pin .approach-reveal{
  opacity:0;transform:translateY(24px);filter:blur(4px);
  transition:opacity .8s var(--ease), transform .8s var(--ease), filter .8s var(--ease);
}
.approach-pin.no-pin .approach-reveal.in-view{opacity:1;transform:translateY(0);filter:blur(0);}

.approach-pin.no-pin .approach-word{
  transition:opacity .8s var(--ease), transform .8s var(--ease), filter .8s var(--ease);
}
.approach-pin.no-pin .approach-word.lit{opacity:1;transform:translateY(0);filter:blur(0);}

@media(max-width:820px){
  .approach-list{margin-left:0;max-width:none;text-align:left;}
}

@media(prefers-reduced-motion:reduce){
  /* Respect the OS-level motion preference: skip the pin/scrub animation
     (and the no-pin fade-in) entirely and just show the finished state,
     still without touching any of the list's own visual design. */
  .approach-pin{height:auto;}
  .approach-pin__sticky{position:static;min-height:0;}
  .approach-reveal{opacity:1 !important;transform:none !important;filter:none !important;}
  .approach-word{opacity:1;transform:none;filter:none;}
}

/* -------- case study grid -------- */
.case-grid{display:grid;grid-template-columns:1fr 1fr;gap:2px;}
@media(max-width:820px){.case-grid{grid-template-columns:1fr;}}
.case-card{
  position:relative;display:block;padding:44px 40px;border:1px solid var(--line);
  overflow:hidden;isolation:isolate;min-height:340px;display:flex;flex-direction:column;justify-content:flex-end;
}
.case-card__bg{
  position:absolute;inset:0;z-index:0;background-size:cover;background-position:center;
  transform:scale(1.08);opacity:0;transition:opacity .6s var(--ease), transform 1.2s var(--ease);
}
.case-card:hover .case-card__bg{opacity:1;transform:scale(1);}
.case-card::after{
  content:'';position:absolute;inset:0;z-index:1;
  /* Full-height wash so the whole card darkens on hover, not just the
     bottom sliver — copy sitting higher up (meta/title) needs the same
     contrast boost as the paragraph and arrow underneath it. rgba values
     are --blue-dark / #001a5e (can't reference the CSS var directly with
     an alpha channel).
     Uses the normal blend mode at a high, near-opaque alpha rather than
     multiply: multiply's darkening is proportional to how bright the
     photo already is, so light case-study screenshots (white UI chrome,
     pale renders) stayed washed out and the copy on top was hard to read.
     Normal blend darkens predictably regardless of the photo's own
     brightness, while still leaving enough of the alpha channel open for
     the photo's texture/shapes to show through the tint. */
  background:linear-gradient(180deg, rgba(0,26,94,.9) 0%, rgba(0,26,94,.93) 45%, rgba(0,26,94,.97) 100%);
  mix-blend-mode:normal;
  opacity:0;transition:opacity .5s var(--ease);
}
.case-card:hover::after{opacity:1;}
.case-card__meta{position:relative;z-index:2;font-size:13px;color:var(--muted);display:flex;gap:10px;transition:color .4s;margin-bottom:14px;}
.case-card:hover .case-card__meta{color:var(--muted-invert);}
.case-card h3{position:relative;z-index:2;font-size:clamp(24px,2.6vw,32px);transition:color .4s;}
.case-card:hover h3{color:var(--paper);}
.case-card p{position:relative;z-index:2;margin-top:14px;max-width:46ch;font-size:14.5px;color:var(--muted);
  max-height:0;opacity:0;transition:max-height .5s var(--ease), opacity .4s var(--ease), margin .4s var(--ease);overflow:hidden;}
.case-card:hover p{max-height:80px;opacity:1;color:var(--muted-invert);}
.case-card__arrow{
  position:relative;z-index:2;display:inline-flex;align-items:center;justify-content:center;
  width:40px;height:40px;border-radius:50%;border:1px solid var(--line);margin-top:20px;
  transition:transform .5s var(--ease), background .4s, border-color .4s;
}
.case-card:hover .case-card__arrow{transform:rotate(45deg);background:var(--accent);border-color:var(--accent);color:var(--paper);}
.case-card__arrow svg{width:16px;height:16px;}

/* -------- footer -------- */
footer{background:var(--ink);color:var(--paper);padding:90px 0 40px;}
.footer__top{display:flex;justify-content:space-between;align-items:flex-end;gap:40px;flex-wrap:wrap;padding-bottom:70px;border-bottom:1px solid var(--line-invert);}
.footer__top h2{font-size:clamp(32px,5vw,64px);max-width:14ch;}
.footer__links{display:flex;gap:60px;flex-wrap:wrap;margin-top:60px;}
.footer__col h4{font-size:13px;text-transform:uppercase;letter-spacing:.08em;color:var(--muted-invert);margin-bottom:18px;font-weight:600;}
.footer__col a{display:block;font-size:15px;margin-bottom:12px;transition:opacity .3s, transform .3s;}
.footer__col a:hover{opacity:.6;transform:translateX(4px);}
.footer__bottom{display:flex;justify-content:space-between;margin-top:60px;font-size:13px;color:var(--muted-invert);flex-wrap:wrap;gap:14px;}

/* -------- page hero (inner pages) -------- */
.page-hero{padding:170px 0 70px;}
.page-hero__tag{color:var(--accent);font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;margin-bottom:20px;display:block;}
.page-hero h1{font-size:clamp(34px,5.5vw,64px);max-width:18ch;}
.page-hero p{margin-top:26px;max-width:560px;color:var(--muted);font-size:17px;}

/* -------- case study detail -------- */
.cs-hero__cover{margin-top:50px;border-radius:var(--radius);overflow:hidden;}
.cs-hero__cover img{width:100%;height:auto;transition:transform 1.4s var(--ease);}
.cs-hero__cover:hover img{transform:scale(1.03);}
.cs-nav{
  position:sticky;top:90px;display:flex;gap:28px;padding:16px 0;border-bottom:1px solid var(--line);margin-bottom:60px;z-index:50;background:var(--paper);flex-wrap:wrap;
  transition:top .3s var(--ease);
}
/* Tracks the site nav's own show/hide state (see .nav.hide-nav and the
   matching body.nav-hidden toggle in script.js): while the nav is
   hidden this sticks flush to the viewport top instead of leaving the
   90px gap the nav normally occupies; once the nav slides back in on
   scroll-up, this animates back down to sit under it. */
body.nav-hidden .cs-nav{top:0;}
.cs-nav a{font-size:14px;color:var(--muted);font-weight:500;position:relative;padding-bottom:4px;}
.cs-nav a::after{content:'';position:absolute;left:0;bottom:0;height:2px;width:0;background:var(--accent);transition:width .3s var(--ease);}
.cs-nav a:hover, .cs-nav a.active{color:var(--ink);}
.cs-nav a:hover::after, .cs-nav a.active::after{width:100%;}
.cs-body{display:grid;grid-template-columns:220px 1fr;gap:64px;}
@media(max-width:820px){.cs-body{grid-template-columns:1fr;}}
.cs-meta{align-self:start;position:sticky;top:170px;}
.cs-meta h4{font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin-bottom:10px;}
.cs-meta ul{margin-bottom:26px;}
.cs-meta li{font-size:14.5px;margin-bottom:6px;}
/* In the single-column mobile layout (see the .cs-body breakpoint
   above) .cs-meta (Services/Tech Stack/Design Stack) sits in the same
   scroll flow as the case-study content instead of beside it —
   position:sticky here made it stay pinned in place while that
   content scrolled underneath it, overlapping. Static on mobile only;
   placed after the base rule above so it actually overrides it
   (same specificity, so source order decides). The desktop
   two-column layout still wants .cs-meta sticky alongside the content. */
@media(max-width:820px){.cs-meta{position:static;}}
.cs-block{margin-bottom:90px;scroll-margin-top:150px;}
.cs-block h2{font-size:clamp(24px,3vw,34px);margin-bottom:24px;}
.cs-block p{color:var(--ink);font-size:16.5px;line-height:1.7;max-width:760px;margin-bottom:18px;}
.cs-block ul.bullets{margin:20px 0;max-width:760px;}
.cs-block ul.bullets li{position:relative;padding-left:26px;margin-bottom:12px;font-size:16px;color:var(--ink);line-height:1.6;}
.cs-block ul.bullets li::before{content:'';position:absolute;left:0;top:9px;width:8px;height:8px;border-radius:50%;background:var(--accent);}
.cs-images{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:32px;}
.cs-images img{border-radius:14px;transition:transform .6s var(--ease);}
.cs-images img:hover{transform:translateY(-6px);}
@media(max-width:640px){.cs-images{grid-template-columns:1fr;}}

/* -------- contact -------- */
.contact-grid{display:grid;grid-template-columns:1fr 1fr;gap:80px;}
@media(max-width:900px){.contact-grid{grid-template-columns:1fr;}}
.field{margin-bottom:26px;}
.field label{display:block;font-size:13px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);margin-bottom:10px;}
.field input, .field textarea{
  width:100%;background:transparent;border:none;border-bottom:1px solid var(--line);
  padding:12px 2px;font-size:17px;font-family:inherit;color:var(--ink);transition:border-color .3s;
}
.field input:focus, .field textarea:focus{outline:none;border-color:var(--ink);}
.contact-info dt{font-size:13px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);margin-top:26px;}
.contact-info dd{margin:8px 0 0;font-size:20px;font-family:'Inter',sans-serif;}

/* -------- careers -------- */
.perk{border-top:1px solid var(--line);padding:36px 0;display:grid;grid-template-columns:200px 1fr;gap:30px;}
.perk:last-child{border-bottom:1px solid var(--line);}
.perk h3{font-size:22px;}
.perk p{color:var(--muted);max-width:560px;}
@media(max-width:640px){.perk{grid-template-columns:1fr;gap:10px;}}
.job-row{
  display:flex;justify-content:space-between;align-items:center;padding:28px 0;border-top:1px solid var(--line);
  transition:padding-left .3s var(--ease);
}
.job-row:last-child{border-bottom:1px solid var(--line);}
.job-row:hover{padding-left:14px;color:var(--accent);}
.job-row__tag{font-size:13px;color:var(--muted);}

/* -------- misc -------- */
.pill{display:inline-block;border:1px solid var(--line);border-radius:999px;padding:6px 14px;font-size:13px;margin:0 8px 8px 0;}
.eyebrow-link{display:inline-flex;align-items:center;gap:8px;font-size:14px;font-weight:500;}
.eyebrow-link svg{transition:transform .4s var(--ease);}
.eyebrow-link:hover svg{transform:translate(4px,-4px);}

/* -------- CMS content: hide hardcoded copy until it's replaced --------
   An inline <script> at the top of <head> on every page adds
   .cms-loading to <html> before first paint (see index.html etc.).
   cms.js removes it once every CMS fetch on the page has resolved (or
   ~2.5s has passed, whichever comes first — see the DOMContentLoaded
   handler at the bottom of cms.js), so visitors go straight from
   nothing to the real CMS copy instead of seeing the old hardcoded
   text flash by first. If JS is disabled, .cms-loading is never added
   and this whole block is inert — the static fallback content just
   shows immediately, exactly as before.

   Scoped to the elements cms.js actually swaps text/lists into. Two
   deliberate exclusions:
   - Large rich-text regions (case study body sections, legal page
     body): hiding a big block of content for ~2.5s reads as a broken
     page, and those change far less often than the copy this was
     actually reported against.
   - The hero heading/subhead (#heroHeadline, #heroSub): these already
     have their own blur/translate entrance animation (see .hero h1
     .line span in this file, and the .hero.loaded trigger in
     script.js) that runs on a fixed, short timer independent of
     cms-loading. Hiding them here too would race that animation —
     on a slow CMS response it could finish revealing fallback copy
     while still invisible, then pop straight to the final state with
     no animation at all once cms-loading clears. */
html.cms-loading #manifestoText,
html.cms-loading #processSteps,
html.cms-loading #footerCtaHeading,
html.cms-loading #footerCtaSub,
html.cms-loading #caseGrid,
html.cms-loading #pageHeroTag,
html.cms-loading #pageHeroTitle,
html.cms-loading #pageHeroBody,
html.cms-loading #approachTag,
html.cms-loading #approachTitle,
html.cms-loading #approachDesc,
html.cms-loading #approachSteps,
html.cms-loading #midCtaTag,
html.cms-loading #midCtaHeading,
html.cms-loading #benefitsTag,
html.cms-loading #benefitsTitle,
html.cms-loading #perksList,
html.cms-loading #openPositionsTag,
html.cms-loading #openPositionsTitle,
html.cms-loading #openPositionsDesc,
html.cms-loading #jobList,
html.cms-loading #contactEmail,
html.cms-loading #contactEmailNote,
html.cms-loading #contactLocation,
html.cms-loading #csHeroTag,
html.cms-loading #csHeroTitle,
html.cms-loading #csHeroBody,
html.cms-loading #csCtaHeading,
html.cms-loading #csCtaSub,
html.cms-loading #legalTitle{
  opacity:0;
}
#manifestoText, #processSteps, #footerCtaHeading,
#footerCtaSub, #caseGrid, #pageHeroTag, #pageHeroTitle, #pageHeroBody,
#approachTag, #approachTitle, #approachDesc, #approachSteps, #midCtaTag,
#midCtaHeading, #benefitsTag, #benefitsTitle, #perksList, #openPositionsTag,
#openPositionsTitle, #openPositionsDesc, #jobList, #contactEmail,
#contactEmailNote, #contactLocation, #csHeroTag, #csHeroTitle, #csHeroBody,
#csCtaHeading, #csCtaSub, #legalTitle{
  transition:opacity .4s var(--ease);
}
