/* ============================================================
   Shape Shopfitters — graphics layer
   Decorative artwork: grain, aurora, blueprint, rings,
   custom cursor, preloader, big type.

   PERFORMANCE RULES followed here:
   - no mix-blend-mode on full-screen layers
   - no backdrop-filter (near-opaque rgba instead)
   - no filter:blur() on animated elements; soft edges come from
     radial-gradient falloff, which rasterises once
   - only transform/opacity animate, so work stays on the
     compositor instead of triggering repaint
   ============================================================ */

/* ---------- film grain ----------
   Was a 4x-viewport fixed layer with mix-blend-mode:overlay plus a
   stepped transform animation, which forced the compositor to
   re-blend the entire screen every frame. Now viewport-sized,
   no blend mode, no animation, contained. */
.grain{
  position:fixed;inset:0;z-index:95;pointer-events:none;
  opacity:.03;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-repeat:repeat;
  contain:strict;
}

/* ---------- aurora / blobs ----------
   Soft edges now come from the gradient's own falloff. A transform
   animation on a plain gradient layer is fully composited. */
.aurora{
  position:absolute;border-radius:50%;pointer-events:none;
  will-change:transform;
}
.aurora--a{
  width:52vw;height:52vw;max-width:760px;max-height:760px;
  top:-16%;right:-12%;
  background:radial-gradient(circle closest-side,rgba(143,198,238,.30),rgba(143,198,238,.10) 45%,transparent 72%);
}
.aurora--b{
  width:44vw;height:44vw;max-width:640px;max-height:640px;
  bottom:-20%;left:-10%;
  background:radial-gradient(circle closest-side,rgba(90,168,220,.24),rgba(90,168,220,.08) 45%,transparent 72%);
}

.blob{
  position:absolute;border-radius:50%;pointer-events:none;z-index:0;
  will-change:transform;
}
.blob--about{
  width:560px;height:560px;top:-140px;left:-220px;
  background:radial-gradient(circle closest-side,rgba(143,198,238,.22),rgba(143,198,238,.07) 48%,transparent 74%);
}
.blob--svc{
  width:620px;height:620px;bottom:-200px;right:-240px;
  background:radial-gradient(circle closest-side,rgba(143,198,238,.18),rgba(143,198,238,.06) 48%,transparent 74%);
}
.blob--proc{
  width:660px;height:660px;top:10%;left:-260px;
  background:radial-gradient(circle closest-side,rgba(143,198,238,.17),rgba(143,198,238,.05) 48%,transparent 74%);
}

/* ---------- blueprint elevation (hero) ---------- */
.blueprint{
  position:absolute;right:2.5vw;bottom:16%;z-index:1;
  width:min(46vw,560px);pointer-events:none;
  opacity:0;transition:opacity 1s var(--ease) .5s;
}
.hero.is-ready .blueprint{opacity:.55}
.blueprint svg{width:100%;height:auto;overflow:visible}
.blueprint path,.blueprint circle{
  fill:none;stroke:var(--accent);
  vector-effect:non-scaling-stroke;
  stroke-linecap:round;stroke-linejoin:round;
}
.blueprint .bp-main path{stroke-width:1.6;opacity:.95}
.blueprint .bp-thin path{stroke-width:.8;opacity:.5;stroke-dasharray:1;stroke-dashoffset:0}
.blueprint .bp-detail path{stroke-width:.9;opacity:.62}
.blueprint .bp-dot{stroke-width:1.2;opacity:.9}
.blueprint__tag{
  position:absolute;right:4px;bottom:-26px;
  font-family:var(--font-mono);font-size:.64rem;font-weight:500;
  letter-spacing:.2em;text-transform:uppercase;color:var(--accent);opacity:.6;
}

/* ---------- floating spec cards (hero) ----------
   backdrop-filter removed; these sit over a dark scrim, so a
   near-opaque fill is visually equivalent. */
.spec-cards{position:absolute;inset:0;z-index:2;pointer-events:none}
.spec-card{
  position:absolute;display:flex;flex-direction:column;gap:2px;
  padding:14px 20px;border-radius:14px;
  background:rgba(12,16,20,.82);
  border:1px solid rgba(143,198,238,.28);
  box-shadow:0 18px 44px rgba(0,0,0,.4);
  opacity:0;transform:translateY(24px);
  transition:opacity .9s var(--ease),transform .9s var(--ease);
}
.spec-card i{
  font-family:var(--font-mono);
  font-style:normal;font-size:.6rem;letter-spacing:.18em;
  text-transform:uppercase;color:var(--accent);
}
.spec-card strong{
  font-family:var(--font-display);font-size:1.05rem;font-weight:800;color:#fff;
  white-space:nowrap;
}
.spec-card--1{top:20%;right:6vw;transition-delay:1.1s}
.spec-card--2{top:46%;right:30vw;transition-delay:1.25s}
.spec-card--3{bottom:28%;right:9vw;transition-delay:1.4s}
.hero.is-ready .spec-card{opacity:1;transform:none}

@media (max-width:1080px){
  .blueprint,.spec-cards{display:none}
}

/* ---------- SVG progress rings ---------- */
.ring-bg,.ring-fg{fill:none;stroke-linecap:round}
.ring-bg{stroke:rgba(255,255,255,.12);stroke-width:3}
.ring-fg{
  stroke:var(--accent);stroke-width:3;
  stroke-dasharray:1;stroke-dashoffset:1;
  transform:rotate(-90deg);transform-origin:center;
  transition:stroke-dashoffset 1.6s var(--ease) .2s;
}
.stat.is-visible .ring-fg{stroke-dashoffset:.08}

.stat__ring{
  position:relative;display:grid;place-items:center;
  width:76px;height:76px;margin-bottom:18px;
}
.stat__ring svg{position:absolute;inset:0;width:100%;height:100%}
.stat__ring i{
  font-family:var(--font-mono);font-style:normal;font-weight:700;
  font-size:.84rem;color:var(--accent);letter-spacing:.02em;
}

/* ---------- custom cursor ----------
   Sizing uses margin rather than scale so the animated transform
   stays a pure translate the compositor can reuse. */
.cursor{position:fixed;inset:0;z-index:9999;pointer-events:none;display:none}
.cursor__dot,.cursor__ring{
  position:absolute;top:0;left:0;border-radius:50%;
  will-change:transform;
}
.cursor__dot{width:7px;height:7px;background:var(--accent);margin:-3.5px 0 0 -3.5px}
.cursor__ring{
  width:38px;height:38px;margin:-19px 0 0 -19px;
  border:1.5px solid rgba(143,198,238,.7);
  display:grid;place-items:center;
  transition:width .35s var(--ease),height .35s var(--ease),margin .35s var(--ease),
             background .35s var(--ease),border-color .35s var(--ease),opacity .3s var(--ease);
}
.cursor__label{
  font-family:var(--font-mono);font-size:.64rem;font-weight:700;
  letter-spacing:.08em;text-transform:uppercase;color:#04121c;
  opacity:0;transform:scale(.6);
  transition:opacity .3s var(--ease),transform .35s var(--ease-out-back);
}
.cursor.is-link .cursor__ring{width:58px;height:58px;margin:-29px 0 0 -29px;background:rgba(143,198,238,.16);border-color:transparent}
.cursor.is-view .cursor__ring{width:86px;height:86px;margin:-43px 0 0 -43px;background:var(--accent);border-color:transparent}
.cursor.is-view .cursor__label{opacity:1;transform:none}
.cursor.is-view .cursor__dot{opacity:0}
.cursor.is-hidden .cursor__ring,.cursor.is-hidden .cursor__dot{opacity:0}
.cursor.is-down .cursor__ring{border-width:2.5px}

@media (hover:hover) and (pointer:fine){
  body.has-custom-cursor .cursor{display:block}
  body.has-custom-cursor,
  body.has-custom-cursor a,
  body.has-custom-cursor button{cursor:none}
}

/* ---------- preloader ---------- */
.preloader{
  position:fixed;inset:0;z-index:9998;
  opacity:0;visibility:hidden;
}
body.is-loading .preloader{opacity:1;visibility:visible}
.preloader__panel{
  position:absolute;top:0;bottom:0;width:25.2%;
  background:var(--ink-900);
  transform:translateY(0);
  transition:transform 1s var(--ease);
}
.preloader__panel:nth-child(1){left:0}
.preloader__panel:nth-child(2){left:25%}
.preloader__panel:nth-child(3){left:50%}
.preloader__panel:nth-child(4){left:74.8%}

.preloader__core{
  position:absolute;inset:0;display:grid;place-content:center;justify-items:center;gap:16px;
  transition:opacity .45s var(--ease),transform .6s var(--ease);
}
.preloader__mark{width:78px;height:78px;display:block}
.preloader__mark svg{width:100%;height:100%;overflow:visible}
.pl-shape{
  fill:none;stroke:var(--accent);stroke-width:2.5;
  stroke-linejoin:round;stroke-linecap:round;
  stroke-dasharray:1;stroke-dashoffset:1;
  animation:pl-draw 1.5s var(--ease) forwards;
}
.pl-shape:nth-child(2){animation-delay:.28s;opacity:.6}
@keyframes pl-draw{to{stroke-dashoffset:0}}

.preloader__label{
  font-family:var(--font-mono);font-size:.7rem;font-weight:500;
  letter-spacing:.32em;text-transform:uppercase;color:#fff;opacity:.75;
}
.preloader__bar{
  display:block;width:190px;height:2px;border-radius:2px;
  background:rgba(255,255,255,.16);overflow:hidden;
}
.preloader__bar i{
  display:block;height:100%;width:100%;background:var(--accent);
  transform:scaleX(0);transform-origin:left;
}
.preloader__count{
  font-family:var(--font-mono);font-size:.72rem;font-weight:500;
  letter-spacing:.14em;color:var(--accent);
}

.preloader.is-done .preloader__core{opacity:0;transform:translateY(-14px)}
.preloader.is-done .preloader__panel{transform:translateY(-101%)}
.preloader.is-done .preloader__panel:nth-child(1){transition-delay:.12s}
.preloader.is-done .preloader__panel:nth-child(2){transition-delay:.2s}
.preloader.is-done .preloader__panel:nth-child(3){transition-delay:.28s}
.preloader.is-done .preloader__panel:nth-child(4){transition-delay:.36s}

/* ---------- big outlined type marquee ----------
   text-stroke is expensive to paint, so the row is promoted once
   and paused when it scrolls out of view (see effects.js).
   No content-visibility here: the marquee measures its own width
   at init, and skipped content reports zero. */
.bigtext{
  padding:clamp(40px,6vw,72px) 0;
  background:var(--ink-900);color:#fff;overflow:hidden;
}
.bigtext__row{
  display:flex;width:max-content;
  animation:marquee-slide var(--marquee-speed,40s) linear infinite;
  will-change:transform;
}
.bigtext__row--rev{animation-direction:reverse}
.bigtext__set{display:flex;align-items:center;gap:clamp(20px,3vw,44px);padding-right:clamp(20px,3vw,44px)}
.bigtext__set span{
  font-family:var(--font-display);font-weight:var(--weight-display);
  font-size:clamp(2.4rem,7vw,6rem);line-height:1;letter-spacing:-.03em;
  text-transform:uppercase;
  color:transparent;-webkit-text-stroke:1.5px rgba(255,255,255,.32);
}
.bigtext__set b{
  font-size:clamp(1rem,2vw,1.7rem);color:var(--accent);opacity:.7;
}

/* ---------- hero ticker ----------
   backdrop-filter removed: it sat over the animating Ken Burns
   image, forcing a backdrop readback on every frame. */
.ticker{
  position:absolute;left:0;right:0;bottom:0;z-index:3;
  padding:14px 0;overflow:hidden;
  background:rgba(6,8,10,.82);
  border-top:1px solid rgba(143,198,238,.22);
}
.ticker__track{
  display:flex;width:max-content;
  animation:marquee-slide var(--marquee-speed,32s) linear infinite;
  will-change:transform;
}
.ticker__set{display:flex;align-items:center;gap:26px;padding-right:26px}
.ticker__set span{
  font-family:var(--font-mono);font-size:.72rem;font-weight:500;
  letter-spacing:.18em;text-transform:uppercase;color:rgba(255,255,255,.72);
  white-space:nowrap;
}
.ticker__set b{color:var(--accent);font-size:.6rem}

/* ---------- gradient sheen on text ----------
   background-position cannot be composited, so every frame of this
   animation repaints the text. As an infinite loop it burned CPU
   for as long as the page was open. It now plays three times as an
   entrance flourish and then holds, which is where the charm was
   anyway. The gradient itself stays, so the words keep the
   accent-to-white treatment permanently. */
.sheen{
  background:linear-gradient(100deg,var(--accent) 20%,#ffffff 42%,var(--accent) 62%);
  background-size:250% 100%;
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
  animation:sheen-slide 5s var(--ease) 1.4s 3 both;
}
@keyframes sheen-slide{
  0%,100%{background-position:120% 0}
  50%    {background-position:-20% 0}
}

/* ---------- image frame accent ---------- */
.about__frame{
  position:absolute;inset:14px;border-radius:calc(var(--radius-lg) - 8px);
  border:1px solid rgba(255,255,255,.22);pointer-events:none;
  opacity:0;transform:scale(1.04);
  transition:opacity .8s var(--ease) .5s,transform .8s var(--ease) .5s;
}
[data-reveal-mask].is-visible .about__frame{opacity:1;transform:none}

/* ---------- team values panel (replaces team photo) ---------- */
.team-panel{
  position:relative;border-radius:var(--radius-lg);overflow:hidden;
  aspect-ratio:4/3;box-shadow:var(--shadow-lg);isolation:isolate;
  background:linear-gradient(160deg,var(--ink-900),#141c24 60%,var(--ink-800));
  display:grid;place-items:center;
}
.team-panel__grid{
  position:absolute;inset:0;pointer-events:none;opacity:.5;
  background-image:
    linear-gradient(rgba(143,198,238,.09) 1px,transparent 1px),
    linear-gradient(90deg,rgba(143,198,238,.09) 1px,transparent 1px);
  background-size:38px 38px;
  mask-image:radial-gradient(120% 100% at 50% 40%,#000 40%,transparent 100%);
  -webkit-mask-image:radial-gradient(120% 100% at 50% 40%,#000 40%,transparent 100%);
}
.team-panel__glow{
  position:absolute;width:70%;height:70%;top:-15%;right:-15%;
  background:radial-gradient(circle,rgba(143,198,238,.28),transparent 65%);
  filter:blur(20px);pointer-events:none;
}
.team-panel__content{
  position:relative;z-index:2;width:100%;
  display:flex;flex-direction:column;align-items:center;gap:34px;
  padding:44px 40px;
}
/* Logo is a square lockup inside a 4:3 panel, so it has to be capped on
   BOTH axes or the tagline overflows and gets clipped at the bottom. */
.team-panel__content--logo{
  padding:0;height:100%;
  display:grid;place-items:center;
}
.team-panel__logo{
  width:auto;height:auto;
  max-width:76%;max-height:82%;
  object-fit:contain;
  /* centred glow rather than an offset shadow, so nothing reads as a
     drop shadow hanging off one side of the mark */
  filter:drop-shadow(0 0 34px rgba(143,198,238,.22));
}
.team-panel__mark{width:78px;height:78px;display:block}
.team-panel__mark svg{width:100%;height:100%;fill:none;stroke:var(--accent);stroke-width:2}
.team-panel__values{display:flex;flex-direction:column;gap:22px;width:100%;max-width:320px}
.tvalue{
  display:flex;flex-direction:column;gap:4px;
  padding-left:20px;border-left:2px solid var(--accent);
}
.tvalue strong{
  font-family:var(--font-display);font-size:1.5rem;font-weight:var(--weight-display);
  color:#fff;line-height:1;
}
.tvalue span{
  font-family:var(--font-mono);font-size:.72rem;letter-spacing:.1em;
  text-transform:uppercase;color:rgba(255,255,255,.6);
}

/* ---------- project scan sweep ---------- */
.proj__scan{
  position:absolute;inset:0;z-index:1;pointer-events:none;overflow:hidden;
  border-radius:inherit;
}
.proj__scan::before{
  content:"";position:absolute;top:0;bottom:0;width:45%;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.14),transparent);
  transform:translateX(-160%) skewX(-14deg);
}
.proj:hover .proj__scan::before{
  animation:scan-sweep 1.1s var(--ease) forwards;
}
@keyframes scan-sweep{to{transform:translateX(330%) skewX(-14deg)}}

/* ---------- CTA concentric rings ----------
   Was an infinite scale animation on a 1100px multi-gradient
   layer. Opacity-only now, which the compositor handles free. */
.cta__rings{
  position:absolute;top:50%;left:50%;z-index:1;
  width:min(120vw,1100px);aspect-ratio:1;
  transform:translate(-50%,-50%);pointer-events:none;
  background:
    radial-gradient(circle,transparent 22%,rgba(143,198,238,.10) 22.4%,transparent 23%),
    radial-gradient(circle,transparent 34%,rgba(143,198,238,.09) 34.4%,transparent 35%),
    radial-gradient(circle,transparent 46%,rgba(143,198,238,.07) 46.4%,transparent 47%);
  animation:ring-pulse 9s ease-in-out infinite;
}
@keyframes ring-pulse{
  0%,100%{opacity:.75}
  50%    {opacity:1}
}

/* ---------- service spotlight ----------
   A fixed-size pre-rendered gradient that is MOVED by transform.
   Previously the gradient centre was driven by custom properties,
   which repainted the whole card on every mousemove. */
.svc__glow{
  position:absolute;top:0;left:0;
  width:340px;height:340px;margin:-170px 0 0 -170px;
  z-index:0;pointer-events:none;border-radius:50%;
  background:radial-gradient(circle closest-side,rgba(143,198,238,.45),transparent 70%);
  opacity:0;transition:opacity .4s var(--ease);
}
.svc:hover .svc__glow{opacity:.4;will-change:transform}

/* animated service icon line-draw */
[data-draw-icon] path{
  stroke-dasharray:1;stroke-dashoffset:1;
  transition:stroke-dashoffset 1.4s var(--ease) .15s;
}
.is-visible [data-draw-icon] path,
.svc.is-visible [data-draw-icon] path{stroke-dashoffset:0}

/* ---------- footer wordmark ---------- */
.wordmark{
  font-family:var(--font-display);font-weight:var(--weight-display);
  font-size:clamp(5rem,22vw,17rem);line-height:.82;
  letter-spacing:-.05em;text-align:center;
  color:transparent;-webkit-text-stroke:1.5px rgba(255,255,255,.11);
  user-select:none;pointer-events:none;
  margin:0 auto;padding:0 12px;
}

/* ---------- back-to-top ring ---------- */
.to-top__ring{position:relative;display:grid;place-items:center;width:44px;height:44px}
.to-top__ring>svg:first-child{position:absolute;inset:0;width:100%;height:100%}
.to-top__ring .ring-bg{stroke:rgba(255,255,255,.16);stroke-width:2}
.to-top__ring .ring-fg{
  stroke:var(--accent);stroke-width:2;transition:none;
  stroke-dashoffset:1;
}
.to-top__arrow{width:16px;height:16px;position:relative;z-index:2}

/* ---------- process timeline graphics ---------- */
.process__meter{
  position:relative;display:block;width:100%;max-width:220px;height:3px;
  margin-top:34px;border-radius:3px;background:rgba(255,255,255,.14);overflow:hidden;
}
.process__meter i{
  position:absolute;inset:0;display:block;background:var(--accent);
  transform:scaleX(0);transform-origin:left;
}

/* ============================================================
   OFF-SCREEN PAUSE
   Infinite animations keep consuming compositor time even when
   scrolled out of view. effects.js toggles .is-paused through an
   IntersectionObserver.
   ============================================================ */
.is-paused,
.is-paused *{animation-play-state:paused !important}
