/* =========================================================
   Atelier AP — effects layer
   Everything here is progressive enhancement: without JS (no .js
   class) or with prefers-reduced-motion the site stays static.
   ========================================================= */

:root {
  --ease-out: cubic-bezier(.2, .8, .2, 1);
  --ease-io: cubic-bezier(.77, 0, .18, 1);
  --f-mono: "JetBrains Mono", ui-monospace, monospace;
  --btn-fill: var(--c-dark);
  --btn-fill-text: var(--c-white);
}

::selection { background: var(--c-accent); color: var(--c-white); }

@keyframes draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes fadeUp { from { opacity: 0; translate: 0 16px; } to { opacity: 1; translate: 0 0; } }
@keyframes spin { to { rotate: 360deg; } }

/* =========================================================
   Preloader
   ========================================================= */
.preloader {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: grid;
  place-items: center;
  background: #0b0d0d;
  color: var(--c-white);
  clip-path: inset(0 0 0 0);
  transition: clip-path 1.1s var(--ease-io), visibility 0s 1.1s;
}
html:not(.js) .preloader,
.no-preload .preloader { display: none; }
.preloader.is-done,
.is-ready .preloader { clip-path: inset(0 0 100% 0); visibility: hidden; }

.preloader-inner { width: min(440px, 80vw); }
.preloader-logo { width: 96px; display: block; margin: 0 auto 40px; overflow: visible; }
.preloader-logo path { stroke-dasharray: 1; stroke-dashoffset: 1; animation: draw 1.5s var(--ease-io) .15s forwards; }
.preloader-logo path + path { animation-delay: .6s; }
.preloader-meta { display: flex; justify-content: flex-end; margin-bottom: 12px; }
.preloader-num { font: 500 44px/1 var(--f-head); letter-spacing: 0; color: var(--c-white); font-variant-numeric: tabular-nums; }
.preloader-bar { height: 1px; background: rgba(255, 255, 255, .14); overflow: hidden; }
.preloader-bar span { display: block; height: 100%; background: var(--c-accent); transform-origin: left; transform: scaleX(0); }

/* =========================================================
   Page transition curtain
   ========================================================= */
.curtain {
  position: fixed;
  inset: 0;
  z-index: 250;
  background: #0b0d0d;
  pointer-events: none;
  transform: scaleY(0);
}
.curtain-line { position: absolute; left: 0; right: 0; height: 2px; background: var(--c-accent); bottom: 0; }
html.js .curtain { animation: curtainOut .95s var(--ease-io) .05s both; }
html.js .curtain.is-entering { animation: curtainIn .6s var(--ease-io) both; pointer-events: auto; }
@keyframes curtainOut { from { transform: scaleY(1); transform-origin: top; } to { transform: scaleY(0); transform-origin: top; } }
@keyframes curtainIn  { from { transform: scaleY(0); transform-origin: bottom; } to { transform: scaleY(1); transform-origin: bottom; } }
.curtain.is-entering .curtain-line { top: 0; bottom: auto; }

/* =========================================================
   Scroll progress + film grain
   ========================================================= */
.scroll-progress {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 2px;
  z-index: 120;
  background: linear-gradient(90deg, var(--c-accent), #7fe0b0);
  transform-origin: left;
  transform: scaleX(0);
  pointer-events: none;
}
.grain {
  position: fixed;
  inset: -50%;
  z-index: 90;
  pointer-events: none;
  opacity: .055;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: grain 1s steps(6) infinite;
}
@keyframes grain {
  0% { transform: translate(0, 0); } 20% { transform: translate(-3%, 2%); } 40% { transform: translate(2%, -3%); }
  60% { transform: translate(-2%, -1%); } 80% { transform: translate(3%, 3%); } 100% { transform: translate(0, 0); }
}

/* =========================================================
   Custom cursor
   ========================================================= */
.has-cursor,
.has-cursor * { cursor: none !important; }
.has-cursor input,
.has-cursor textarea,
.has-cursor select { cursor: auto !important; }

.cursor-dot,
.cursor-ring {
  position: fixed;
  top: 0; left: 0;
  z-index: 400;
  pointer-events: none;
  border-radius: 50%;
  will-change: transform;
}
.cursor-dot { width: 6px; height: 6px; margin: -3px 0 0 -3px; background: var(--c-accent); }
.cursor-ring {
  width: 34px; height: 34px;
  margin: -17px 0 0 -17px;
  border: 1px solid #fff;
  mix-blend-mode: difference;
  display: grid;
  place-items: center;
  transition: background-color .25s, border-color .25s, opacity .25s;
}
.cursor-ring.is-link { background: rgba(255, 255, 255, .15); }
.cursor-ring.is-big { background: rgba(255, 255, 255, .08); border-width: .5px; }
.cursor-ring.is-down { opacity: .55; }
.cursor-ring.is-hidden,
.cursor-out .cursor-ring,
.cursor-out .cursor-dot { opacity: 0; }

/* =========================================================
   Header: logo draw-on, nav entrance, letter roll
   ========================================================= */
.logo-mark path { stroke-dasharray: 1; stroke-dashoffset: 0; }
.js:not(.is-ready) .site-header .logo-mark path { stroke-dashoffset: 1; }
.js:not(.is-ready) .site-header .logo-type { opacity: 0; }
.is-ready .site-header .logo-mark path { animation: draw 1.4s var(--ease-io) both; }
.is-ready .site-header .logo-mark path + path { animation-delay: .35s; }
.is-ready .site-header .logo-type { animation: fadeUp .9s var(--ease-out) .9s both; }
.logo.redraw .logo-mark path { animation: draw 1.1s var(--ease-io) both; }
.logo.redraw .logo-mark path + path { animation-delay: .25s; }

.js:not(.is-ready) .nav a { opacity: 0; translate: 0 -12px; }
.nav a { transition: opacity .7s var(--ease-out), translate .7s var(--ease-out); }
.nav a:nth-child(2) { transition-delay: .07s; }
.nav a:nth-child(3) { transition-delay: .14s; }
.nav a:nth-child(4) { transition-delay: .21s; }
.nav a:nth-child(5) { transition-delay: .28s; }

.roll { position: relative; display: inline-block; overflow: hidden; vertical-align: top; line-height: 1.5; }
.roll > span,
.roll::after { display: block; transition: transform .5s var(--ease-io); }
.roll::after {
  content: attr(data-text);
  position: absolute;
  left: 0; top: 100%;
  color: #7fe0b0;
}
.nav a:hover .roll > span,
.nav a:hover .roll::after { transform: translateY(-100%); }

@media (max-width: 760px) {
  .nav-open .nav a { animation: fadeUp .6s var(--ease-out) both; }
  .nav-open .nav a:nth-child(2) { animation-delay: .06s; }
  .nav-open .nav a:nth-child(3) { animation-delay: .12s; }
  .nav-open .nav a:nth-child(4) { animation-delay: .18s; }
  .nav-open .nav a:nth-child(5) { animation-delay: .24s; }
}

/* =========================================================
   Split-text headings
   ========================================================= */
.is-split .w {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
  padding: .14em 0 .1em;
  margin: -.14em 0 -.1em;
}
.is-split .w-in {
  display: inline-block;
  transform: translateY(115%) rotate(5deg);
  transform-origin: 0 100%;
  transition: transform 1.1s var(--ease-out) calc(var(--i) * 60ms);
}
.is-split.split-in .w-in { transform: none; }

/* =========================================================
   Hero + page hero with WebGL
   ========================================================= */
.hero::after,
.page-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: radial-gradient(ellipse at 50% 45%, transparent 35%, rgba(0, 0, 0, .55) 100%);
}
.hero .hero-bg,
.page-hero .hero-bg { transition: opacity 1.6s ease; }
.has-3d .hero-bg { opacity: 0; }

.hero-3d {
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0;
  transition: opacity 1.8s ease;
  touch-action: pan-y;
}
.hero-3d canvas { display: block; width: 100%; height: 100%; }
.hero-3d.is-ready { opacity: 1; }
html.lenis.lenis-smooth { scroll-behavior: auto !important; }

.hero-content,
.page-hero-content { z-index: 2; pointer-events: none; text-shadow: 0 2px 30px rgba(0, 0, 0, .55); }
/* soft dark halo so the copy stays readable over the lit 3D scene */
.has-3d .hero-content::before,
.has-3d .page-hero-content::before {
  content: "";
  position: absolute;
  inset: -90px -8%;
  z-index: -1;
  background: radial-gradient(closest-side, rgba(4, 7, 7, .66), rgba(4, 7, 7, .3) 60%, transparent);
  pointer-events: none;
}
.hero-content a,
.page-hero-content a { pointer-events: auto; }

.js .hero p,
.js .hero .btn { opacity: 0; translate: 0 20px; }
.is-ready .hero p { animation: fadeUp 1s var(--ease-out) .55s forwards; }
.is-ready .hero .btn { animation: fadeUp 1s var(--ease-out) .75s forwards; }
.is-ready .page-hero .crumbs { animation: fadeUp .9s var(--ease-out) .1s both; }
.is-ready .page-hero p { animation: fadeUp .9s var(--ease-out) .5s both; }

/* With WebGL the home hero is full-height and stays pinned for a moment
   while the city morphs into a blueprint; sub-page heroes get taller. */
.has-webgl .hero { height: 100vh; height: 100svh; max-height: none; min-height: 560px; }
.has-webgl .hero-pin { height: calc(100svh + 80vh); }
.has-webgl .hero-pin .hero { position: sticky; top: 0; }
.has-webgl .page-hero { min-height: 66vh; }

/* =========================================================
   Buttons: fill sweep (magnetic motion is JS)
   ========================================================= */
.btn { position: relative; overflow: hidden; isolation: isolate; }
.btn::before {
  content: "";
  position: absolute;
  inset: -2px;
  z-index: -1;
  background: var(--btn-fill);
  border-radius: 50% 50% 0 0 / 60% 60% 0 0;
  transform: translateY(102%);
  transition: transform .55s var(--ease-io), border-radius .55s var(--ease-io);
}
.btn:hover::before { transform: translateY(0); border-radius: 0; }
.btn-primary:hover,
.btn-ghost:hover { color: var(--btn-fill-text); }
.hero, .page-hero, .cta, .lightbox { --btn-fill: #fff; --btn-fill-text: var(--c-dark); }

.link-arrow { background: linear-gradient(var(--c-accent), var(--c-accent)) left bottom / 0 1px no-repeat; transition: background-size .45s var(--ease-io); }
.link-arrow:hover { background-size: 100% 1px; }

/* =========================================================
   3D tilt with glare
   ========================================================= */
.tilt { position: relative; transform-style: preserve-3d; will-change: transform; }
.tilt-glare {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  border-radius: inherit;
  background: radial-gradient(circle at var(--gx, 50%) var(--gy, 50%), rgba(255, 255, 255, .35), transparent 55%);
  mix-blend-mode: overlay;
  opacity: 0;
  transition: opacity .35s;
}
.tilt-on .tilt-glare { opacity: 1; }
.card.tilt .card-body { transform: translateZ(28px); }
.value.tilt h3, .steps li.tilt h3 { transform: translateZ(22px); }

/* =========================================================
   Image wipe + zoom on reveal
   ========================================================= */
/* The wipe is always on a CHILD of the element the scroll observer watches.
   Chromium's IntersectionObserver treats a fully clipped target as not
   visible, so clipping the watched element itself would never reveal it. */
.js .card-img,
.js .split-img,
.js .service-img > *,
.js .project > :is(img, .model),
.js .project-gallery a > * {
  clip-path: inset(0 0 100% 0);
  transition: clip-path 1.3s var(--ease-io) .1s;
}
.js .service-img,
.js .project,
.js .project-gallery a { background: none; }
.js .project .project-caption { opacity: 0; transition: opacity .8s ease .7s; }
.js .project.visible .project-caption { opacity: 1; }

.js .reveal.visible .card-img,
.js .reveal.visible .split-img,
.js .service-img.visible > *,
.js .project.visible > :is(img, .model),
.js .project-gallery a.visible > * { clip-path: inset(0 0 0 0); }

.js .card-img img,
.js .split-img img,
.js .project img,
.js .project-gallery img { scale: 1.3; transition: scale 1.8s var(--ease-out), transform .7s ease, filter .6s ease; }
.js .visible .card-img img,
.js .visible .split-img img,
.js .project.visible img,
.js .project-gallery a.visible img { scale: 1; }

/* =========================================================
   Velocity marquee
   ========================================================= */
.marquee {
  overflow: hidden;
  padding: 38px 0;
  border-block: 1px solid var(--c-line);
  background: var(--c-bg);
}
.marquee-track { display: flex; align-items: center; width: max-content; will-change: transform; }
.marquee-item {
  margin-right: 44px;
  font: 500 clamp(40px, 7vw, 96px)/1 var(--f-head);
  letter-spacing: -.02em;
  white-space: nowrap;
}
.marquee-item { color: var(--c-text); }
.marquee-sep { margin-right: 44px; color: var(--c-accent); display: grid; }
.marquee-sep svg { width: 48px; height: 42px; animation: spin 9s linear infinite; }

/* =========================================================
   Spotlights + CTA grid
   ========================================================= */
.spotlight { position: relative; }
.spotlight::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(520px circle at var(--sx, 50%) var(--sy, 50%), rgba(31, 174, 105, .22), transparent 60%);
  opacity: 0;
  transition: opacity .45s;
}
.spotlight:hover::before { opacity: 1; }
.contact-form.spotlight::before { background: radial-gradient(420px circle at var(--sx, 50%) var(--sy, 50%), rgba(31, 174, 105, .1), transparent 60%); }
.contact-form > :not(.hp):not(.tilt-glare) { position: relative; z-index: 1; }

.cta { position: relative; overflow: hidden; }
.cta::after {
  content: "";
  position: absolute;
  inset: -40px;
  z-index: 0;
  pointer-events: none;
  background:
    linear-gradient(rgba(255, 255, 255, .05) 1px, transparent 1px) 0 0 / 40px 40px,
    linear-gradient(90deg, rgba(255, 255, 255, .05) 1px, transparent 1px) 0 0 / 40px 40px;
  -webkit-mask-image: radial-gradient(ellipse at center, #000 20%, transparent 75%);
  mask-image: radial-gradient(ellipse at center, #000 20%, transparent 75%);
  animation: gridDrift 18s linear infinite;
}
@keyframes gridDrift { to { transform: translate(40px, 40px); } }
.cta-inner { position: relative; z-index: 1; }

/* CTA with the particle monogram scene */
.cta-3d { position: absolute; inset: 0; z-index: 0; opacity: 0; transition: opacity 1.4s ease; }
.cta-3d canvas { display: block; width: 100%; height: 100%; }
.cta-3d.is-ready { opacity: 1; }
.cta.has-3d::before,
.cta.has-3d::after { display: none; }
.has-webgl .cta { padding: 170px 0; }
.has-webgl .cta-inner { pointer-events: none; text-shadow: 0 2px 24px rgba(0, 0, 0, .6); }
.has-webgl .cta-inner a { pointer-events: auto; }
@media (min-width: 901px) {
  .has-webgl .cta { text-align: left; }
  .has-webgl .cta h2 { margin: 0 0 14px; max-width: 470px; }
}

/* Giant footer wordmark lit by the pointer */
.footer-giant {
  font: 600 clamp(54px, 15.5vw, 230px)/.82 var(--f-logo);
  letter-spacing: -.01em;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  padding: 10px 0 0;
  color: transparent;
  -webkit-text-stroke: 1px rgba(255, 255, 255, .12);
  background: radial-gradient(340px circle at var(--sx, -999px) var(--sy, -999px), var(--c-accent), transparent 70%);
  -webkit-background-clip: text;
  background-clip: text;
  user-select: none;
}

/* =========================================================
   Pinned scroll-driven build section (home)
   ========================================================= */
.build { position: relative; background: #080b0b; color: var(--c-white); }
.has-webgl .build { height: 420vh; }
.build-sticky { position: relative; overflow: hidden; }
.has-webgl .build-sticky { position: sticky; top: 0; height: 100vh; height: 100svh; }
.build-3d { position: absolute; inset: 0; opacity: 0; transition: opacity 1.4s ease; }
.build-3d canvas { display: block; width: 100%; height: 100%; }
.build-3d.is-ready { opacity: 1; }

.build-inner { position: relative; z-index: 2; height: 100%; display: flex; align-items: center; pointer-events: none; padding-block: 96px; }
.build-copy { max-width: 400px; pointer-events: auto; }
.build-title { font-size: clamp(34px, 4.6vw, 56px); margin-bottom: 36px; }
.build-steps { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.build-step {
  display: grid;
  grid-template-columns: 40px 1fr;
  gap: 4px;
  padding: 14px 0;
  border-top: 1px solid rgba(255, 255, 255, .1);
  cursor: pointer;
  opacity: .32;
  transition: opacity .5s var(--ease-out);
}
.build-step h3 { font-size: 20px; margin: 0; transition: color .4s; }
.build-step p {
  margin: 0;
  font-size: 14px;
  color: rgba(255, 255, 255, .7);
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  transition: max-height .6s var(--ease-io), opacity .5s, margin .6s var(--ease-io);
}
.build-step .step-no { margin: 4px 0 0; }
.build-step.is-past { opacity: .55; }
.build-step.is-active { opacity: 1; }
.build-step.is-active h3 { color: #7fe0b0; }
.build-step.is-active p { max-height: 90px; opacity: 1; margin-top: 6px; }
html:not(.has-webgl) .build-step { opacity: 1; }
html:not(.has-webgl) .build-step p { max-height: none; opacity: 1; }

.build-progress {
  position: absolute;
  right: var(--gutter);
  top: 50%;
  width: 2px; height: 180px;
  margin-top: -90px;
  background: rgba(255, 255, 255, .12);
  border-radius: 2px;
  overflow: hidden;
}
.build-progress span { display: block; height: 100%; background: var(--c-accent); transform-origin: top; transform: scaleY(0); }

@media (max-width: 900px) {
  .build-inner { align-items: flex-end; padding-bottom: 70px; }
  .build-copy {
    max-width: none;
    width: 100%;
    background: linear-gradient(transparent, rgba(8, 11, 11, .92) 30%);
    padding-top: 40px;
  }
  .build-title { font-size: 30px; margin-bottom: 14px; }
  .build-step:not(.is-active) { display: none; }
  html:not(.has-webgl) .build-step { display: grid; }
  .build-progress { display: none; }
}

/* =========================================================
   Easter egg (Konami code)
   ========================================================= */
.architect-mode main img { filter: grayscale(1) sepia(1) hue-rotate(115deg) saturate(2.4) brightness(.8) !important; }
.architect-mode main section:not(.hero):not(.page-hero) * { outline: 1px dashed rgba(31, 174, 105, .3); }
.architect-mode .scroll-progress { background: linear-gradient(90deg, #ff3d6e, #ffd23d, #3dff8a, #3dc8ff, #b13dff); }

/* =========================================================
   Responsive + reduced motion
   ========================================================= */
@media (max-width: 760px) {
  .marquee { padding: 24px 0; }
  .has-webgl .hero-pin { height: calc(100svh + 45vh); }
}

/* Reduced motion (OS setting or the visitor's toggle, decided in <head>) */
.reduce-motion .preloader,
.reduce-motion .curtain,
.reduce-motion .grain { display: none !important; }
.reduce-motion .is-split .w-in { transform: none; }
html.reduce-motion.js .card-img, html.reduce-motion.js .split-img, html.reduce-motion.js .service-img > *,
html.reduce-motion.js .project > :is(img, .model), html.reduce-motion.js .project-gallery a > * { clip-path: none; }
html.reduce-motion.js .project .project-caption { opacity: 1; }
html.reduce-motion.js .card-img img, html.reduce-motion.js .split-img img,
html.reduce-motion.js .project img, html.reduce-motion.js .project-gallery img { scale: 1; }
html.reduce-motion.js .hero p, html.reduce-motion.js .hero .btn,
html.reduce-motion.js .nav a { opacity: 1 !important; translate: 0 0 !important; }
html.reduce-motion.js .site-header .logo-mark path { stroke-dashoffset: 0 !important; }
html.reduce-motion.js .site-header .logo-type { opacity: 1 !important; }

/* Effects switch: icon-only button in the footer bar */
.footer-social { align-items: center; }
.motion-toggle {
  display: grid;
  place-items: center;
  width: 30px; height: 30px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: 50%;
  background: none;
  color: rgba(255, 255, 255, .4);
  cursor: pointer;
  transition: color .25s, border-color .25s;
}
.motion-toggle svg { width: 15px; height: 15px; fill: currentColor; }
.motion-toggle:hover { color: #fff; border-color: var(--c-accent); }
.motion-toggle[aria-pressed="true"] { color: var(--c-accent); }

/* =========================================================
   Model tiles: procedural 3D models in the image slots
   (drawn by assets/js/three/tiles.js)
   ========================================================= */
.model {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  /* shown until the first frame arrives, and when WebGL is unavailable */
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='30 6 62 52'%3E%3Cg fill='none' stroke='%23ffffff' stroke-opacity='.14' stroke-width='1.6'%3E%3Cpath d='M36.2 53.8 L56.2 11.4 L75 53.8'/%3E%3Cpath d='M56.2 11.4 H73.4 A13.35 13.35 0 0 1 73.4 38.1 H43.6'/%3E%3C/g%3E%3C/svg%3E") center / 72px no-repeat,
    radial-gradient(120% 90% at 50% 35%, #1a2121, #0b0e0e);
}
.model canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  opacity: 0;
  transition: opacity .9s ease;
}
.model.is-live canvas { opacity: 1; }
/* soft vignette so tiles sit well on white sections */
.model::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(ellipse at 50% 45%, transparent 55%, rgba(0, 0, 0, .38) 100%);
}

.project > .model,
.project-gallery a > .model,
.map > .model { position: absolute; inset: 0; }
.project-gallery a,
.map { position: relative; }
.map { overflow: hidden; }
