/* ==========================================================================
   ACS Yapı İnşaat
   Design system + page styles.

   Locked decisions (do not drift):
   - Theme: dark only. The brand identity is deep burgundy / navy. No section
     inverts to a light background mid page.
   - Accent: --rose (#c9a9b1). One accent, used on every page.
   - Radius: sharp (0) for surfaces and media. Pill (999px) for buttons and
     pill-shaped tags only. No other radii.
   - Motion: IntersectionObserver for reveals, native CSS scroll-driven
     animations for parallax and pinning. The only scroll listener in the
     codebase is the flythrough fallback in main.js, registered exclusively
     for engines without animation-timeline (currently Firefox).
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Fonts (self hosted, no third party connection)
   -------------------------------------------------------------------------- */
@font-face {
  font-family: 'Marcellus';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/marcellus-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Marcellus';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/marcellus-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/jost-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/jost-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* --------------------------------------------------------------------------
   2. Tokens
   -------------------------------------------------------------------------- */
:root {
  /* surfaces, darkest to lightest */
  --ink:        #0d1017;
  --ink-2:      #11151f;
  --ink-3:      #171c29;
  --navy:       #14192d;
  --wine:       #411f2d;
  --wine-2:     #55293a;

  /* content */
  --cream:      #f4ece9;
  --cream-dim:  #d8cac5;
  --rose:       #c9a9b1;
  --rose-dim:   rgba(201, 169, 177, 0.78);
  --line:       rgba(201, 169, 177, 0.18);
  /* Borders that ARE the control boundary need 3:1 (WCAG 1.4.11).
     --line is decorative only; --line-ui is for interactive controls. */
  --line-ui:    rgba(201, 169, 177, 0.42);
  --line-soft:  rgba(201, 169, 177, 0.10);

  /* type */
  --display: 'Marcellus', 'Times New Roman', serif;
  --sans: 'Jost', system-ui, -apple-system, 'Segoe UI', sans-serif;

  /* rhythm */
  --gutter: clamp(1.25rem, 5vw, 4rem);
  --shell: 1400px;
  --section-y: clamp(4.5rem, 9vw, 8rem);

  /* shape: sharp surfaces, pill buttons. Nothing else. */
  --r-flat: 0px;
  --r-pill: 999px;

  /* motion */
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-soft: cubic-bezier(0.4, 0, 0.2, 1);
  --dur: 0.75s;

  /* layers */
  --z-media: 0;
  --z-content: 1;
  --z-header: 60;
  --z-float: 70;
  --z-menu: 80;
  --z-lightbox: 90;
}

/* --------------------------------------------------------------------------
   3. Reset
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; padding: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* header height plus breathing room, so anchor targets are not hidden */
  scroll-padding-top: 6rem;
}

body {
  font-family: var(--sans);
  font-weight: 300;
  font-size: clamp(1rem, 0.96rem + 0.2vw, 1.075rem);
  line-height: 1.7;
  color: var(--cream-dim);
  background: var(--ink);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

body.is-locked { overflow: hidden; }

img, svg, video { display: block; max-width: 100%; }
img { height: auto; }

a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
ul, ol { list-style: none; }

::selection { background: var(--rose); color: var(--ink); }

:focus-visible {
  outline: 2px solid var(--rose);
  outline-offset: 3px;
}

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

.skip-link {
  position: absolute; top: 0; left: 50%;
  transform: translate(-50%, -110%);
  z-index: 200;
  background: var(--cream); color: var(--ink);
  padding: 0.75rem 1.5rem;
  font-size: 0.9rem; letter-spacing: 0.04em;
  transition: transform 0.2s var(--ease);
}
.skip-link:focus { transform: translate(-50%, 0); }
.skip-link:focus-visible { outline-offset: -4px; }

/* --------------------------------------------------------------------------
   4. Typography
   -------------------------------------------------------------------------- */
h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 400;
  color: var(--cream);
  line-height: 1.12;
  letter-spacing: -0.005em;
}

/* Marcellus has no italic and no bold. Never synthesise either. */
h1 { font-size: clamp(2.5rem, 1.4rem + 4.6vw, 5.25rem); }
h2 { font-size: clamp(2rem, 1.25rem + 3.1vw, 3.75rem); }
h3 { font-size: clamp(1.375rem, 1.1rem + 1.1vw, 1.875rem); }
h4 { font-size: clamp(1.125rem, 1.02rem + 0.45vw, 1.375rem); }

p { max-width: 62ch; }
p + p { margin-top: 1.1em; }

strong { color: var(--cream); font-weight: 500; }

.lede {
  font-size: clamp(1.075rem, 1rem + 0.5vw, 1.3rem);
  line-height: 1.65;
  color: var(--cream-dim);
}

/* the one small-caps label style. Rationed: see section budget in each page. */
.eyebrow {
  display: block;
  font-size: 0.75rem;
  font-weight: 400;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--rose);
  margin-bottom: 1.5rem;
}

/* --------------------------------------------------------------------------
   5. Layout utilities
   -------------------------------------------------------------------------- */
.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.shell--narrow { max-width: 980px; }

.section { padding-block: var(--section-y); position: relative; }
.section--tight { padding-block: clamp(3.5rem, 7vw, 6rem); }
.section--flush-top { padding-top: 0; }

.rule {
  height: 1px;
  background: var(--line);
  border: 0;
}

/* --------------------------------------------------------------------------
   6. Motion system
   -------------------------------------------------------------------------- */

/* 6a. Reveal on enter. Driven by IntersectionObserver adding .is-in. */
.js-ready [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 30px, 0);
  transition:
    opacity var(--dur) var(--ease),
    transform var(--dur) var(--ease);
  transition-delay: calc(var(--i, 0) * 90ms);
  will-change: opacity, transform;
}
.js-ready [data-reveal="left"]  { transform: translate3d(-36px, 0, 0); }
.js-ready [data-reveal="right"] { transform: translate3d(36px, 0, 0); }
.js-ready [data-reveal="scale"] { transform: scale(0.94); }
.js-ready [data-reveal="none"]  { transform: none; }

.js-ready [data-reveal].is-in {
  opacity: 1;
  transform: translate3d(0, 0, 0) scale(1);
  will-change: auto;
}

/* 6b. Media curtain. The image is uncovered rather than faded, which reads as
   a physical reveal and suits architectural photography. */
.curtain { overflow: hidden; position: relative; }
.js-ready .curtain > img,
.js-ready .curtain > picture > img {
  /* `scale` rather than `transform`: .parallax animates `transform`, and an
     animation declaration wins the cascade over any normal declaration
     regardless of specificity. These two must not share a property. */
  scale: 1.18;
  transition: scale 1.5s var(--ease);
  will-change: scale;
}
.js-ready .curtain::after {
  content: "";
  position: absolute; inset: 0;
  background: var(--ink);
  transform-origin: bottom;
  transition: transform 1.1s var(--ease);
  z-index: 2;
}
.js-ready .curtain.is-in > img,
.js-ready .curtain.is-in > picture > img { scale: 1; }
.js-ready .curtain.is-in::after { transform: scaleY(0); }

/* 6c. Line by line headline reveal. JS wraps each line in .line > span. */
.split-line { display: block; overflow: hidden; padding-bottom: 0.08em; }
.split-line > span {
  display: block;
  transform: translate3d(0, 105%, 0);
  transition: transform 0.95s var(--ease);
  transition-delay: calc(var(--i, 0) * 105ms);
}
.is-in .split-line > span,
.split-line.is-in > span { transform: translate3d(0, 0, 0); }

/* 6d. Counters */
.count { font-variant-numeric: tabular-nums; }

/* 6e. Native scroll driven effects. Progressive enhancement only. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {

    /* reading progress across the whole document */
    .progress-bar {
      transform-origin: 0 50%;
      animation: grow-x linear both;
      animation-timeline: scroll(root block);
    }
    @keyframes grow-x { from { transform: scaleX(0); } to { transform: scaleX(1); } }

    /* slow drift on section media as it crosses the viewport */
    .parallax > img,
    .parallax > picture > img {
      animation: drift linear both;
      animation-timeline: view(block);
      animation-range: entry 0% exit 100%;
    }
    @keyframes drift {
      from { transform: translate3d(0, -7%, 0) scale(1.16); }
      to   { transform: translate3d(0, 7%, 0)  scale(1.16); }
    }

    /* hero settles as you leave it */
    .hero-slides {
      animation: hero-settle linear both;
      animation-timeline: view(block);
      animation-range: exit 0% exit 100%;
    }
    @keyframes hero-settle {
      from { transform: scale(1.04) translate3d(0, 0, 0); opacity: 1; }
      to   { transform: scale(1.16) translate3d(0, 6%, 0); opacity: 0.35; }
    }

    /* the process timeline draws itself */
    .track-line::after {
      animation: grow-y linear both;
      animation-timeline: view(block);
      animation-range: entry 60% exit 40%;
    }
    @keyframes grow-y { from { transform: scaleY(0); } to { transform: scaleY(1); } }
  }
}

/* --------------------------------------------------------------------------
   7. Buttons
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  white-space: nowrap;          /* a wrapped CTA label is broken. Keep labels short. */
  padding: 0.95rem 1.9rem;
  border-radius: var(--r-pill);
  font-size: 0.9rem;
  font-weight: 400;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  border: 1px solid transparent;
  transform: translate3d(var(--mx, 0px), var(--my, 0px), 0);
  transition:
    background-color 0.35s var(--ease-soft),
    border-color 0.35s var(--ease-soft),
    color 0.35s var(--ease-soft),
    transform 0.28s var(--ease);
}
.btn:active { transform: translate3d(var(--mx, 0px), calc(var(--my, 0px) + 1px), 0) scale(0.985); }
.btn svg { width: 16px; height: 16px; flex: none; }

/* cream on ink, 16.3:1 */
.btn--solid { background: var(--cream); color: var(--ink); }
.btn--solid:hover { background: var(--rose); color: var(--ink); }

/* cream over dark, 16.3:1 against --ink */
.btn--ghost { border-color: var(--line-ui); color: var(--cream); }
.btn--ghost:hover { border-color: var(--rose); background: rgba(201, 169, 177, 0.08); }

.btn--sm { padding: 0.7rem 1.35rem; font-size: 0.78rem; }

/* architect credit, where the renders carry another studio's watermark */
.project-credit {
  margin-top: 2rem;
  font-size: 0.88rem;
  color: var(--rose-dim);
  letter-spacing: 0.04em;
}

/* back to the index, above a project title */
.back-link { margin-bottom: 1.25rem; }
.back-link a {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.82rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--rose);
  transition: color 0.25s linear;
}
.back-link a:hover { color: var(--cream); }
.back-link svg { width: 15px; height: 15px; transform: rotate(180deg); }

/* text link with a rule that wipes in */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  font-size: 0.85rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--cream);
  padding-bottom: 0.4rem;
  position: relative;
}
.link-arrow::after {
  content: "";
  position: absolute; left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: var(--rose);
  transform: scaleX(0); transform-origin: right;
  transition: transform 0.5s var(--ease);
}
.link-arrow:hover::after { transform: scaleX(1); transform-origin: left; }
.link-arrow svg {
  width: 15px; height: 15px;
  transition: transform 0.4s var(--ease);
}
.link-arrow:hover svg { transform: translate(3px, -3px); }

/* --------------------------------------------------------------------------
   8. Header
   -------------------------------------------------------------------------- */
.progress-bar {
  position: fixed; top: 0; left: 0;
  width: 100%; height: 2px;
  background: var(--rose);
  z-index: calc(var(--z-header) + 1);
  transform: scaleX(0);
  transform-origin: 0 50%;
}

.header {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: var(--z-header);
  transition: background-color 0.45s var(--ease-soft),
              border-color 0.45s var(--ease-soft),
              backdrop-filter 0.45s var(--ease-soft);
  border-bottom: 1px solid transparent;
}
/* The hero photograph has blown out highlights along its top edge, so the nav
   needs its own scrim from the very first painted frame, not only once stuck. */
.header::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, rgba(13, 16, 23, 0.88) 0%, rgba(13, 16, 23, 0.55) 55%, transparent 100%);
  pointer-events: none;
  transition: opacity 0.45s var(--ease-soft);
}
.header.is-stuck::before { opacity: 0; }
.header__inner { position: relative; }
.header.is-stuck {
  background: rgba(13, 16, 23, 0.82);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  border-bottom-color: var(--line-soft);
}
@media (prefers-reduced-transparency: reduce) {
  .header.is-stuck { background: var(--ink); backdrop-filter: none; -webkit-backdrop-filter: none; }
}

.header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  height: 72px;
}
@media (max-width: 900px) { .header__inner { height: 64px; } }

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  flex: none;
}
.brand img { width: 34px; height: 35px; object-fit: contain; }
.brand__text {
  font-family: var(--display);
  font-size: 0.95rem;
  letter-spacing: 0.22em;
  color: var(--cream);
  line-height: 1;
  white-space: nowrap;
}
@media (max-width: 400px) { .brand__text { letter-spacing: 0.12em; font-size: 0.85rem; } }

.nav { display: flex; align-items: center; gap: clamp(1.25rem, 2.4vw, 2.5rem); }
.nav__link {
  position: relative;
  font-size: 0.86rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--cream-dim);
  white-space: nowrap;
  padding-block: 0.4rem;
  transition: color 0.3s var(--ease-soft);
}
.nav__link::after {
  content: "";
  position: absolute; left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: var(--rose);
  transform: scaleX(0); transform-origin: right;
  transition: transform 0.45s var(--ease);
}
.nav__link:hover { color: var(--cream); }
.nav__link:hover::after { transform: scaleX(1); transform-origin: left; }
.nav__link[aria-current="page"] { color: var(--cream); }
.nav__link[aria-current="page"]::after { transform: scaleX(1); }

.header__cta { display: inline-flex; }

@media (max-width: 1020px) {
  .nav, .header__cta { display: none; }
}

/* burger */
.burger {
  display: none;
  width: 44px; height: 44px;
  align-items: center; justify-content: center;
  margin-right: -10px;
}
@media (max-width: 1020px) { .burger { display: inline-flex; } }
.burger span {
  display: block; position: relative;
  width: 22px; height: 1px; background: var(--cream);
  transition: background-color 0.2s linear;
}
.burger span::before, .burger span::after {
  content: ""; position: absolute; left: 0;
  width: 22px; height: 1px; background: var(--cream);
  transition: transform 0.4s var(--ease);
}
.burger span::before { top: -7px; }
.burger span::after  { top: 7px; }
.burger[aria-expanded="true"] span { background: transparent; }
.burger[aria-expanded="true"] span::before { transform: translateY(7px) rotate(45deg); }
.burger[aria-expanded="true"] span::after  { transform: translateY(-7px) rotate(-45deg); }

/* full screen menu */
.menu {
  position: fixed; inset: 0;
  z-index: var(--z-menu);
  background: var(--ink-2);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 6rem var(--gutter) 3rem;
  clip-path: inset(0 0 100% 0);
  transition: clip-path 0.7s var(--ease);
  visibility: hidden;
  overflow-y: auto;
}
.menu.is-open { clip-path: inset(0 0 0 0); visibility: visible; }

.menu__close {
  position: absolute;
  top: 1rem; right: var(--gutter);
  width: 44px; height: 44px;
  display: grid; place-items: center;
  border: 1px solid var(--line-ui);
  border-radius: var(--r-pill);
  color: var(--cream);
}
.menu__close:hover { border-color: var(--rose); }
.menu__close svg { width: 18px; height: 18px; }

.menu__list { display: flex; flex-direction: column; gap: 0.35rem; }
.menu__list li { overflow: hidden; }
.menu__list a {
  display: block;
  font-family: var(--display);
  font-size: clamp(2rem, 10vw, 3.25rem);
  color: var(--cream);
  padding-block: 0.35rem;
  transform: translateY(105%);
  transition: transform 0.7s var(--ease), color 0.3s linear;
  transition-delay: calc(var(--i, 0) * 70ms);
}
.menu.is-open .menu__list a { transform: translateY(0); }
.menu__list a:hover { color: var(--rose); }
.menu__list a[aria-current="page"] { color: var(--rose); }

.menu__foot {
  margin-top: 3rem;
  padding-top: 2rem;
  border-top: 1px solid var(--line-soft);
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 2.5rem;
  align-items: center;
  opacity: 0;
  transition: opacity 0.5s var(--ease) 0.35s;
}
.menu.is-open .menu__foot { opacity: 1; }
.menu__foot a {
  display: inline-flex; align-items: center; gap: 0.6rem;
  font-size: 0.95rem; letter-spacing: 0.06em; color: var(--cream-dim);
}
.menu__foot a:hover { color: var(--cream); }
.menu__foot svg { width: 17px; height: 17px; color: var(--rose); }

/* --------------------------------------------------------------------------
   9. Hero
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: 100dvh;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  isolation: isolate;
}

.hero-media {
  position: absolute; inset: 0;
  z-index: var(--z-media);
  overflow: hidden;
}
/* Auto crossfading slideshow. Pure CSS: five slides share one keyframe set
   and are offset with animation-delay, so there is no timer, no JS and
   nothing to get out of sync.

   The first slide gets a NEGATIVE delay equal to its own fade-in, which puts
   it at full opacity on the very first frame. Without that the hero would
   fade up from black and the largest paint would be pushed a second and a
   half later, costing LCP for no visual gain. */
.hero-slides {
  position: absolute;
  inset: 0;
  transform: scale(1.04);
}
.hero-slide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
}

/* Slide 1 alone until the rest have actually downloaded. main.js adds
   .is-cycling once they have, so a slow connection never crossfades to an
   empty frame, and a page without JS simply keeps the still hero. */
.hero-slide:first-child { opacity: 1; }

@media (prefers-reduced-motion: no-preference) {
  /* Two animations sharing one clock: the crossfade, and a slow push so a
     slide is never actually still during its six seconds on screen. They run
     off the same duration and the same per slide delay, so they stay locked
     together without any JS. Direction alternates, otherwise five identical
     pushes in a row start to read as a tic. */
  .hero-slides.is-cycling .hero-slide {
    animation: hero-fade 35s linear infinite, hero-push-in 35s linear infinite;
    will-change: opacity, transform;
  }
  .hero-slides.is-cycling .hero-slide:nth-child(even) {
    animation-name: hero-fade, hero-push-out;
  }
  /* delay = index * 7s, shifted back by the 1.4s fade so slide 1 opens solid */
  .hero-slides.is-cycling .hero-slide:nth-child(1) { animation-delay: -1.4s; }
  .hero-slides.is-cycling .hero-slide:nth-child(2) { animation-delay:  5.6s; }
  .hero-slides.is-cycling .hero-slide:nth-child(3) { animation-delay: 12.6s; }
  .hero-slides.is-cycling .hero-slide:nth-child(4) { animation-delay: 19.6s; }
  .hero-slides.is-cycling .hero-slide:nth-child(5) { animation-delay: 26.6s; }

  @keyframes hero-fade {
    0%   { opacity: 0; }
    4%   { opacity: 1; }   /* 1.4s fade in  */
    20%  { opacity: 1; }   /* 5.6s hold     */
    24%  { opacity: 0; }   /* 1.4s fade out */
    100% { opacity: 0; }
  }

  /* The push spans the whole visible window (0 to 24%) and then holds. The
     jump back to the start happens at 24% to 100%, while opacity is 0. */
  @keyframes hero-push-in {
    0%   { transform: scale(1); }
    24%  { transform: scale(1.085); }
    100% { transform: scale(1.085); }
  }
  @keyframes hero-push-out {
    0%   { transform: scale(1.085); }
    24%  { transform: scale(1); }
    100% { transform: scale(1); }
  }
}

/* One still image, no cycling. */
@media (prefers-reduced-motion: reduce) {
  .hero-slide { animation: none; opacity: 0; }
  .hero-slide:first-child { opacity: 1; }
}
/* Scrim. Two layers: a vertical wash for text legibility and a wine tint
   that pulls the photography toward the brand. */
.hero-media::after {
  content: "";
  position: absolute; inset: 0;
  background:
    radial-gradient(85% 85% at 6% 100%, rgba(13,16,23,0.86) 0%, transparent 72%),
    linear-gradient(to top, rgba(13,16,23,0.94) 0%, rgba(13,16,23,0.60) 30%, rgba(13,16,23,0.10) 62%, rgba(13,16,23,0.38) 100%),
    linear-gradient(112deg, rgba(65,31,45,0.40) 0%, rgba(20,25,45,0.16) 52%, transparent 92%);
}

.hero__inner {
  position: relative;
  z-index: var(--z-content);
  width: 100%;
  padding-bottom: clamp(5.5rem, 13vh, 9rem);
  padding-top: 6rem;
}

.hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2.5rem;
}
@media (min-width: 1024px) {
  .hero__grid {
    grid-template-columns: minmax(0, 1.35fr) minmax(0, 0.85fr);
    align-items: end;
    gap: clamp(3rem, 6vw, 6rem);
  }
}

.hero h1 { max-width: 15ch; }
.hero__sub {
  max-width: 46ch;
  font-size: clamp(1rem, 0.94rem + 0.35vw, 1.2rem);
  color: var(--cream-dim);
}
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.9rem;
  margin-top: 2.25rem;
}

/* Inner page hero: shorter, no full bleed photo. */
.page-hero {
  position: relative;
  padding-top: clamp(9rem, 16vh, 13rem);
  padding-bottom: clamp(3.5rem, 7vw, 6rem);
  background:
    radial-gradient(120% 100% at 12% 0%, rgba(65,31,45,0.75) 0%, transparent 62%),
    radial-gradient(90% 90% at 100% 10%, rgba(20,25,45,0.9) 0%, transparent 60%),
    var(--ink);
  border-bottom: 1px solid var(--line-soft);
  overflow: hidden;
}
.page-hero h1 { max-width: 18ch; }
.page-hero .lede { margin-top: 1.75rem; max-width: 56ch; }

/* --------------------------------------------------------------------------
   10. Floating actions
   -------------------------------------------------------------------------- */
.floats {
  position: fixed;
  right: clamp(1rem, 2.5vw, 2rem);
  bottom: clamp(1rem, 2.5vw, 2rem);
  z-index: var(--z-float);
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  align-items: flex-end;
}

.wa {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  height: 56px;
  padding-inline: 1.15rem;
  border-radius: var(--r-pill);
  background: #12833f;              /* darkened WhatsApp green: 4.83:1 vs white text */
  color: #fff;
  font-size: 0.9rem;
  letter-spacing: 0.08em;
  font-weight: 400;
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.45);
  transition: transform 0.3s var(--ease), background-color 0.3s var(--ease-soft);
}
.wa:hover { transform: translateY(-3px); background: #0d6631; }
.wa:active { transform: translateY(0); }
.wa svg { width: 25px; height: 25px; fill: currentColor; flex: none; }
.wa__label { display: none; }
@media (min-width: 640px) { .wa__label { display: inline; } }
@media (max-width: 639px) { .wa { width: 56px; padding-inline: 0; justify-content: center; } }

.to-top {
  width: 46px; height: 46px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-ui);
  background: rgba(13, 16, 23, 0.75);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: var(--cream);
  opacity: 0; pointer-events: none;
  visibility: hidden;                  /* opacity alone leaves it in the tab order */
  transform: translateY(10px);
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease),
              visibility 0.4s linear, border-color 0.3s linear;
}
.to-top.is-shown { opacity: 1; pointer-events: auto; visibility: visible; transform: translateY(0); }
.to-top:hover { border-color: var(--rose); }
.to-top svg { width: 17px; height: 17px; }

/* --------------------------------------------------------------------------
   11. Manifesto (editorial statement, no media)
   -------------------------------------------------------------------------- */
.manifesto { position: relative; }
.manifesto::before {
  content: "";
  position: absolute; inset: 0;
  background: radial-gradient(70% 120% at 50% 0%, rgba(65,31,45,0.4) 0%, transparent 70%);
  pointer-events: none;
}
.manifesto__body {
  position: relative;
  font-family: var(--display);
  font-size: clamp(1.55rem, 1rem + 2.5vw, 3rem);
  line-height: 1.32;
  color: var(--cream);
  max-width: 20ch;
}
.manifesto__body em {
  font-style: normal;
  color: var(--rose);
}
.manifesto__aside {
  margin-top: clamp(2.5rem, 5vw, 0);
  align-self: end;
}
.manifesto__grid {
  display: grid;
  gap: clamp(2rem, 5vw, 5rem);
  align-items: start;
}
@media (min-width: 1024px) {
  .manifesto__grid { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); }
}

/* --------------------------------------------------------------------------
   11b. Image filled type
   The heading is a window onto a render. A cream tint is layered over the
   photo INSIDE the glyphs so the letters stay light whatever part of the
   image sits behind them. At 0.52 the worst case (a pure black pixel behind
   a glyph) still composites to rgb(127,123,121), which is 4.5:1 against
   --ink. Lowering that alpha breaks the contrast guarantee.
   Not combined with [data-split]: that wraps lines in their own elements and
   the background would restart per line instead of spanning the block.
   -------------------------------------------------------------------------- */
.mask-text {
  /* fallback colour first, so unsupported engines get plain cream type */
  color: var(--cream);
}

@supports ((background-clip: text) or (-webkit-background-clip: text)) {
  .mask-text {
    background-image:
      linear-gradient(rgba(244, 236, 233, 0.52), rgba(244, 236, 233, 0.52)),
      url('/assets/renders/salon-2-1280.webp');
    background-size: cover;
    background-position: 32% 34%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    /* Safari needs this to avoid the fill bleeding past the glyphs */
    -webkit-text-fill-color: transparent;
  }

  @media (prefers-reduced-motion: no-preference) {
    @supports (animation-timeline: view()) {
      .mask-text {
        animation: mask-drift linear both;
        animation-timeline: view(block);
        animation-range: entry 20% exit 80%;
      }
      @keyframes mask-drift {
        from { background-position: 32% 20%; }
        to   { background-position: 46% 68%; }
      }
    }
  }
}

/* Larger than the plain manifesto type: the image needs room inside the
   letterforms to read as a photograph rather than as noise. */
.manifesto__body.mask-text {
  font-size: clamp(2.1rem, 1rem + 4.6vw, 4.6rem);
  line-height: 1.14;
  max-width: 15ch;
}

/* --------------------------------------------------------------------------
   12. Figures band (numbers, no card containers)
   -------------------------------------------------------------------------- */
.figures {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  background: var(--line-soft);
  border-block: 1px solid var(--line-soft);
}
@media (min-width: 860px) { .figures { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.figure {
  background: var(--ink);
  padding: clamp(2rem, 4vw, 3.25rem) clamp(1.25rem, 2.5vw, 2rem);
}
.figure__num {
  font-family: var(--display);
  font-size: clamp(2.5rem, 1.6rem + 3.4vw, 4.25rem);
  line-height: 1;
  color: var(--cream);
  display: flex;
  align-items: baseline;
  gap: 0.1em;
}
.figure__suffix { font-size: 0.42em; color: var(--rose); }
.figure__label {
  margin-top: 0.9rem;
  font-size: 0.82rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--rose-dim);
}

/* --------------------------------------------------------------------------
   13. Sticky project (text column holds while media scrolls)
   -------------------------------------------------------------------------- */
.sticky-split {
  display: grid;
  gap: clamp(2.5rem, 5vw, 5rem);
  align-items: start;
}
@media (min-width: 1024px) {
  .sticky-split { grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); }
}

.sticky-split__text { position: relative; }
@media (min-width: 1024px) {
  .sticky-split__text { position: sticky; top: clamp(7rem, 16vh, 10rem); }
}

.sticky-split__media { display: grid; gap: clamp(1rem, 2vw, 1.75rem); }
.sticky-split__media figure { position: relative; }
.sticky-split__media img { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; }
@media (min-width: 1024px) {
  /* offset the second frame so the column does not read as a plain stack */
  .sticky-split__media figure:nth-child(2) { margin-left: clamp(2rem, 8%, 5rem); }
}

.spec-list { margin-top: 2.5rem; display: grid; gap: 1.5rem; }
@media (min-width: 520px) { .spec-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.spec-list dt {
  font-size: 0.76rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--rose-dim);
  margin-bottom: 0.35rem;
}
.spec-list dd { color: var(--cream); font-size: 1.05rem; }

/* --------------------------------------------------------------------------
   14. Unit types (asymmetric feature rows)
   -------------------------------------------------------------------------- */
.unit {
  display: grid;
  gap: clamp(1.75rem, 4vw, 4rem);
  align-items: center;
}
@media (min-width: 900px) {
  .unit { grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); }
  .unit--flip .unit__media { order: 2; }
  .unit--flip .unit__text  { order: 1; }
}
.unit + .unit { margin-top: clamp(3.5rem, 7vw, 7rem); }
.unit__media img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
.unit__text h3 { margin-bottom: 1.1rem; }

.tag-row { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 1.75rem; }
.tag {
  border: 1px solid var(--line-ui);
  border-radius: var(--r-pill);
  padding: 0.4rem 0.95rem;
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  color: var(--cream-dim);
}

/* --------------------------------------------------------------------------
   15. Pinned horizontal gallery
   -------------------------------------------------------------------------- */
.pan {
  /* fallback: a normal swipeable strip. The pin is layered on in @supports. */
  position: relative;
}
.pan__viewport {
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
  padding-inline: var(--gutter);
}
.pan-track {
  display: flex;
  gap: clamp(1rem, 2vw, 1.75rem);
  width: max-content;
  padding-block: 0.5rem;
}
.pan-item {
  scroll-snap-align: center;
  flex: none;
  width: min(78vw, 620px);
  position: relative;
}
.pan-item img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}
.pan-item figcaption {
  margin-top: 0.9rem;
  font-size: 0.85rem;
  letter-spacing: 0.06em;
  color: var(--rose-dim);
}
.pan__viewport:focus-visible { outline: 2px solid var(--rose); outline-offset: -2px; }

.pan__hint {
  margin-top: 1.5rem;
  font-size: 0.8rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--rose-dim);
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 900px) {
    .pan {
      /* scroll distance that the pin consumes */
      height: 320vh;
      view-timeline-name: --pan;
      view-timeline-axis: block;
      view-timeline-inset: 0;   /* see the note on .fly--on: scroll-padding shifts ranges */
    }
    .pan__sticky {
      position: sticky;
      top: 0;
      height: 100dvh;
      display: flex;
      flex-direction: column;
      justify-content: center;
      overflow: hidden;
    }
    .pan__viewport { overflow: hidden; scroll-snap-type: none; }
    .pan-track { padding-block: 0; }
    .pan-item { width: min(46vw, 700px); }
    /* Height is driven by the viewport here, not the aspect ratio, so the
       heading, the frames and the captions all fit inside one pinned screen. */
    .pan-item img { aspect-ratio: auto; height: clamp(240px, 46vh, 500px); }
    .pan__sticky { padding-block: 0; }
    .pan__hint { display: none; }

    /* Declared here, under the same conditions that create the --pan
       timeline. Split across blocks it would bind to a null timeline. */
    .pan-track {
      animation: pan linear both;
      animation-timeline: --pan;
      animation-range: contain 0% contain 100%;
    }
    @keyframes pan {
      from { transform: translate3d(0, 0, 0); }
      to   { transform: translate3d(calc(-100% + 100vw - var(--gutter) * 2), 0, 0); }
    }
  }
}

/* --------------------------------------------------------------------------
   14b. Split scroll unit types
   The media column pins while the text column scrolls past it, and the image
   swaps when a unit block crosses the middle of the viewport. Driven by
   IntersectionObserver with a centre band rather than a scroll timeline, so
   it behaves identically in every engine. The .unit rows in section 14 are
   still used on Portfolyo and are left alone.
   -------------------------------------------------------------------------- */
.split__grid { display: grid; gap: clamp(2rem, 5vw, 4.5rem); }
@media (min-width: 900px) {
  .split__grid { grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); align-items: start; }
}

.split__media { position: relative; }
.split__img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}
.split__img + .split__img { margin-top: clamp(0.75rem, 1.6vw, 1.25rem); }

/* The stacked, cross-fading version needs JS to pick the active frame, so it
   is gated behind .js-ready. Without it both images simply sit in flow. */
@media (min-width: 900px) {
  .js-ready .split__media {
    position: sticky;
    top: clamp(6.5rem, 14vh, 9rem);
    height: min(70vh, 620px);
  }
  .js-ready .split__img {
    position: absolute;
    inset: 0;
    height: 100%;
    width: 100%;
    aspect-ratio: auto;
    margin-top: 0;
    opacity: 0;
    transform: scale(1.03);
    transition: opacity 0.7s var(--ease), transform 1.1s var(--ease);
  }
  .js-ready .split__img.is-active { opacity: 1; transform: scale(1); }
}

.split__unit + .split__unit { margin-top: clamp(3rem, 8vw, 6rem); }
@media (min-width: 900px) {
  .split__unit { min-height: 62vh; display: grid; align-content: center; }
  .split__unit + .split__unit { margin-top: 0; }
}
.split__unit h3 { margin-bottom: 1.1rem; }
.split__label {
  display: block;
  font-size: 0.78rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--rose-dim);
  margin-bottom: 0.9rem;
  font-variant-numeric: tabular-nums;
}
@media (min-width: 900px) {
  /* Marks the unit in focus with a rule that fills, not by dimming the other
     one. Fading the inactive block dropped its body copy to 3.3:1 and its
     label to 2.1:1, which is unreadable and fails AA. Never indicate state by
     lowering the opacity of text. */
  .js-ready .split__unit {
    position: relative;
    padding-left: clamp(1.25rem, 2.2vw, 2rem);
  }
  .js-ready .split__unit::before,
  .js-ready .split__unit::after {
    content: "";
    position: absolute;
    left: 0;
    top: 0.4em;
    width: 2px;
  }
  .js-ready .split__unit::before { bottom: 0.4em; background: var(--line); }
  .js-ready .split__unit::after {
    height: 0;
    background: var(--rose);
    transition: height 0.7s var(--ease);
  }
  .js-ready .split__unit.is-focus::after { height: calc(100% - 0.8em); }
  .js-ready .split__unit .split__label { transition: color 0.45s var(--ease-soft); }
  .js-ready .split__unit.is-focus .split__label { color: var(--rose); }
}
@media (prefers-reduced-motion: reduce) {
  .js-ready .split__media { position: static; height: auto; }
  .js-ready .split__img { position: static; opacity: 1 !important; transform: none !important; aspect-ratio: 4 / 3; height: auto; margin-top: 1rem; }
  .split__unit { min-height: 0; opacity: 1 !important; }
}

/* --------------------------------------------------------------------------
   15b. Scroll flythrough gallery
   Images drift in from the distance, come into focus near the camera, then
   fly past and fade. First and last are oversized and centred, so the section
   opens and closes on what reads as a full bleed photo.

   Ported from the pattern documented in Cortex (Lessons/scroll-flythrough-
   gallery-pattern). Four structural rules from that write up, do not "tidy"
   any of them away:
     1. The images must be DIRECT children of the element holding `perspective`.
        One wrapper div in between and translateZ stops scaling them, which
        turns the whole thing into flat blocks cross fading at constant size.
     2. The heading needs the HIGHEST z-index in the pinned box, plus
        pointer-events:none. Nothing else fixes text disappearing behind
        images, and no blend mode substitutes for correct stacking order.
     3. The pin is 100svh, never 100vh. On mobile Safari 100vh does not match
        the visible viewport once the address bar collapses and the pin jumps.
     4. Keyframe windows must stay inside [0,1] and increase monotonically, so
        the first and last image get their own one sided curves rather than a
        shared symmetric one that gets clamped.
   -------------------------------------------------------------------------- */

/* Base state is a plain readable grid. The pinned 3D version is opt in via
   .fly--on, which main.js adds only when it can actually be driven. */
.fly { position: relative; }
.fly__pin {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(0.75rem, 1.6vw, 1.25rem);
  padding-block: var(--section-y);
}
.fly__pin > .shell { grid-column: 1 / -1; }
.fly__img {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}
.fly__glow, .fly__vignette { display: none; }
.fly__heading { grid-column: 1 / -1; margin-bottom: 1rem; }
.fly__heading h2 { max-width: 18ch; }
.fly__heading p { margin-top: 1.25rem; }

/* ---- enhanced ---- */
.fly--on { height: 380vh; }
.fly--on .fly__pin {
  display: block;
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
  padding-block: 0;
  perspective: 1100px;
}
.fly--on .fly__glow {
  display: block;
  position: absolute;
  inset: 0;
  z-index: 1;
  /* trupasoul calibration: 0.08, not anaarch's 0.55. Restrained suits this brand. */
  background: radial-gradient(58% 58% at 50% 48%, rgba(201, 169, 177, 0.08) 0%, transparent 70%);
  pointer-events: none;
}
.fly--on .fly__img {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 2;
  width: var(--w);
  /* The reset sets img{max-width:100%}, which silently clamps the 140vw
     bookends to the viewport and kills the full bleed open and close. */
  max-width: none;
  min-width: 220px;
  aspect-ratio: var(--ar, 3 / 2);
  opacity: 0;
  transform: translate(-50%, -50%) translate3d(var(--x), var(--y), -2800px) rotateZ(var(--rot));
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55);
  will-change: transform, opacity;
}
.fly--on .fly__vignette {
  display: block;
  position: absolute;
  inset: 0;
  z-index: 3;
  background:
    linear-gradient(to top, rgba(13, 16, 23, 0.94) 0%, rgba(13, 16, 23, 0.55) 20%, transparent 44%),
    radial-gradient(72% 72% at 50% 42%, transparent 40%, rgba(13, 16, 23, 0.72) 100%);
  pointer-events: none;
}
/* Sits in the lower band rather than dead centre: the renders are brightest
   through the middle, and white type over a lit kitchen fails contrast no
   matter how much text-shadow is piled on. The scrim below does the work. */
.fly--on .fly__heading {
  position: absolute;
  left: 0; right: 0;
  bottom: clamp(2.5rem, 8vh, 5.5rem);
  z-index: 4;                    /* highest in the pin. See rule 2 above. */
  pointer-events: none;
  margin-bottom: 0;
  text-align: center;
  padding-inline: var(--gutter);
}
.fly--on .fly__heading h2 {
  max-width: 20ch;
  margin-inline: auto;
  text-shadow: 0 2px 30px rgba(13, 16, 23, 0.85);
}
.fly--on .fly__heading p {
  margin-inline: auto;
  color: var(--cream-dim);
  text-shadow: 0 2px 20px rgba(13, 16, 23, 0.9);
}

/* ---- the scroll driven curves ---- */
@keyframes fly-in-out {
  0%   { opacity: 0; transform: translate(-50%, -50%) translate3d(var(--x), var(--y), -2800px) rotateZ(var(--rot)); }
  50%  { opacity: 1; transform: translate(-50%, -50%) translate3d(var(--x), var(--y),  -150px) rotateZ(var(--rot)); }
  100% { opacity: 0; transform: translate(-50%, -50%) translate3d(var(--x), var(--y),   900px) rotateZ(var(--rot)); }
}
/* One sided: already arrived at scroll start, recedes and fades. */
@keyframes fly-open {
  0%   { opacity: 1; transform: translate(-50%, -50%) translate3d(var(--x), var(--y),  -150px) rotateZ(var(--rot)); }
  28%  { opacity: 1; transform: translate(-50%, -50%) translate3d(var(--x), var(--y),  -640px) rotateZ(var(--rot)); }
  100% { opacity: 0; transform: translate(-50%, -50%) translate3d(var(--x), var(--y), -2800px) rotateZ(var(--rot)); }
}
/* One sided: arrives centred exactly at scroll end. */
@keyframes fly-close {
  0%   { opacity: 0; transform: translate(-50%, -50%) translate3d(var(--x), var(--y), -2800px) rotateZ(var(--rot)); }
  72%  { opacity: 1; transform: translate(-50%, -50%) translate3d(var(--x), var(--y),  -150px) rotateZ(var(--rot)); }
  100% { opacity: 1; transform: translate(-50%, -50%) translate3d(var(--x), var(--y),  -150px) rotateZ(var(--rot)); }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .fly--on {
      view-timeline-name: --fly;
      view-timeline-axis: block;
      /* html has scroll-padding-top:6rem for anchor targets, and a view
         timeline insets its scrollport by scroll-padding. Left at `auto`
         that shifts every range ~96px early, so the section opens already
         part way into the first image's fade. Pin the inset to zero. */
      view-timeline-inset: 0;
    }
    .fly--on .fly__img {
      animation: fly-in-out linear both;
      animation-timeline: --fly;
      animation-range: contain calc(var(--from) * 100%) contain calc(var(--to) * 100%);
    }
    .fly--on .fly__img[data-role="open"]  { animation-name: fly-open; }
    .fly--on .fly__img[data-role="close"] { animation-name: fly-close; }
  }
}

@media (max-width: 700px) {
  /* Shorter throw, tighter scatter and wider frames. At 390px a 34vw image is
     130px across, which is too small to read before it has flown past. */
  .fly--on { height: 320vh; }
  .fly--on .fly__img {
    --x: var(--xm, 0vw);
    --y: var(--ym, 0vh);
    width: var(--wm, 78vw);
  }
}

/* --------------------------------------------------------------------------
   16. Process, as a sticky card stack
   Every card is sticky at top 0 inside the same parent, so each one slides up
   over the last and they physically pile. The card underneath recedes as the
   next arrives, which is what sells the depth: without it they just swap.

   The recede is driven from a named timeline on the parent and sliced per
   card with animation-range, rather than from each card's own view() timeline.
   A sticky element is pinned, so its own view timeline stalls while it is
   stuck and the animation would freeze with it.

   A card has to be a full 100dvh or it cannot cover the one beneath it. That
   makes the content band look marooned on tall screens, so the card is built
   as a framed panel on a backdrop rather than a line of text in a void: an
   oversized ghost numeral gives the empty area something to be, and the panel
   carries enough content to earn the space.
   -------------------------------------------------------------------------- */
.stack__intro { margin-bottom: clamp(2.5rem, 6vw, 4.5rem); }
.stack__intro .lede { margin-top: 1.5rem; }

.stack__cards { position: relative; }

.stack__card {
  position: sticky;
  top: 0;
  min-height: 100dvh;
  display: grid;
  align-content: center;
  padding-block: clamp(3rem, 8vh, 5rem);
  padding-inline: var(--gutter);
  background: var(--ink);
  overflow: hidden;
  isolation: isolate;
  will-change: transform, filter;
}
/* A quiet progression from ink toward the burgundy of the closing band. */
.stack__card:nth-child(1) { background: linear-gradient(160deg, var(--ink) 0%, var(--ink-2) 100%); }
.stack__card:nth-child(2) { background: linear-gradient(160deg, var(--ink-2) 0%, var(--navy) 120%); }
.stack__card:nth-child(3) { background: linear-gradient(160deg, var(--navy) 0%, #241627 130%); }
.stack__card:nth-child(4) { background: linear-gradient(160deg, #2c1a2a 0%, var(--wine) 120%); }

/* Oversized numeral bled off the edge. Decorative only, the real one is in
   the panel and is the one screen readers get. */
.stack__ghost {
  position: absolute;
  z-index: -1;
  right: -0.1em;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--display);
  font-size: clamp(18rem, 46vh, 40rem);
  line-height: 0.72;
  color: var(--cream);
  opacity: 0.06;
  pointer-events: none;
  user-select: none;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 820px) { .stack__ghost { opacity: 0.045; right: -0.16em; } }

.stack__panel {
  width: min(1080px, 100%);
  margin-inline: auto;
  border: 1px solid var(--line-soft);
  background: rgba(13, 16, 23, 0.34);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  padding: clamp(1.75rem, 4vh, 3.25rem) clamp(1.75rem, 4vw, 3.25rem);
  /* Grows with the viewport and pushes its own rows apart, so a 1500px tall
     screen gets a taller panel rather than the same small band floating in a
     bigger void. min-height, not height, so it can never clip. */
  min-height: min(72vh, 780px);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: clamp(1.5rem, 3vw, 2.5rem);
}
@media (prefers-reduced-transparency: reduce) {
  .stack__panel { background: rgba(13, 16, 23, 0.75); backdrop-filter: none; -webkit-backdrop-filter: none; }
}

.stack__head { display: grid; gap: clamp(0.75rem, 2vw, 1.25rem); }
@media (min-width: 820px) {
  .stack__head { grid-template-columns: auto minmax(0, 1fr) minmax(0, 1.15fr); align-items: start; gap: clamp(1.75rem, 4vw, 3.25rem); }
}

.stack__num {
  font-family: var(--display);
  font-size: clamp(2.5rem, 1.5rem + 3vw, 4.25rem);
  line-height: 0.95;
  color: var(--rose);
  font-variant-numeric: tabular-nums;
}
.stack__num small {
  display: block;
  font-family: var(--sans);
  font-size: 0.78rem;
  letter-spacing: 0.18em;
  color: var(--rose-dim);
  margin-top: 0.5rem;
}
.stack__title { font-size: clamp(1.5rem, 1.1rem + 1.6vw, 2.4rem); }
.stack__body { color: var(--cream-dim); }

/* What actually happens at this step. Three plain facts per card, which is
   what turns the panel from a caption into something worth reading. */
.stack__points {
  display: grid;
  gap: 1px;
  background: var(--line-soft);
  border-top: 1px solid var(--line-soft);
}
@media (min-width: 700px) { .stack__points { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.stack__point {
  background: transparent;
  padding: clamp(1.1rem, 2.4vw, 1.6rem) 0 0;
}
@media (min-width: 700px) {
  .stack__point { padding: clamp(1.1rem, 2.4vw, 1.6rem) clamp(1rem, 2vw, 1.5rem) 0 0; }
}
.stack__point dt {
  font-size: 0.76rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--rose);
  margin-bottom: 0.55rem;
}
.stack__point dd { color: var(--cream-dim); font-size: 0.98rem; line-height: 1.6; }

/* Where you are in the stack. Static per card, no JS needed. */
.stack__rail { display: flex; gap: 0.5rem; align-items: center; }
.stack__rail span {
  height: 2px;
  flex: 1;
  background: var(--line);
  max-width: 80px;
}
.stack__rail span[data-done] { background: var(--rose); }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .stack__cards {
      view-timeline-name: --stack;
      view-timeline-axis: block;
      view-timeline-inset: 0;
    }
    .stack__card[data-recede] {
      animation: stack-recede linear both;
      animation-timeline: --stack;
      animation-range: contain calc(var(--from) * 100%) contain calc(var(--to) * 100%);
    }
    /* `filter`, not `opacity`: a semi transparent card lets the one beneath it
       ghost through while it is still only partly covered. */
    @keyframes stack-recede {
      from { transform: scale(1) translateY(0);         filter: brightness(1); }
      to   { transform: scale(0.94) translateY(-1.5vh); filter: brightness(0.5); }
    }
  }
}

@media (prefers-reduced-motion: reduce) {
  /* Plain sequential panels. Pinning is motion too. */
  .stack__card { position: static; min-height: 0; padding-block: clamp(2.5rem, 6vw, 4rem); }
  .stack__ghost { display: none; }
}

/* --------------------------------------------------------------------------
   17. Closing call to action
   -------------------------------------------------------------------------- */
.closer {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  background: var(--wine);
}
.closer::before {
  content: "";
  position: absolute; inset: 0;
  background:
    radial-gradient(80% 120% at 85% 15%, rgba(20,25,45,0.85) 0%, transparent 65%),
    radial-gradient(60% 90% at 0% 100%, rgba(13,16,23,0.6) 0%, transparent 70%);
  z-index: -1;
}
.closer h2 { max-width: 16ch; }
.closer .lede { max-width: 48ch; margin-top: 1.5rem; color: #e6d6d2; }
.closer__actions { display: flex; flex-wrap: wrap; gap: 0.9rem; margin-top: 2.5rem; }
.closer .btn--ghost { border-color: rgba(244,236,233,0.42); color: var(--cream); }
.closer .btn--ghost:hover { border-color: var(--cream); background: rgba(244,236,233,0.12); }

/* --------------------------------------------------------------------------
   18. Footer
   -------------------------------------------------------------------------- */
.footer {
  background: var(--ink-2);
  border-top: 1px solid var(--line-soft);
  padding-top: clamp(3.5rem, 7vw, 6rem);
}
.footer__grid {
  display: grid;
  gap: clamp(2.5rem, 5vw, 4rem);
}
@media (min-width: 700px)  { .footer__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1060px) { .footer__grid { grid-template-columns: 1.5fr 1fr 1fr 1.2fr; } }

.footer__brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}
/* The 300px mark, not the 110px favicon asset: at this size the small one
   goes soft on a retina screen. */
.footer__brand img {
  width: 92px;
  height: auto;
  margin-bottom: 1.5rem;
}
.footer__tagline {
  font-family: var(--display);
  font-size: 1.35rem;
  color: var(--cream);
  line-height: 1.35;
  max-width: 22ch;
}
.footer h3 {
  font-family: var(--sans);
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--rose);
  margin-bottom: 1.35rem;
}
.footer__links { display: grid; gap: 0.85rem; }
.footer__links a {
  color: var(--cream-dim);
  font-size: 0.98rem;
  transition: color 0.25s linear, padding-left 0.3s var(--ease);
}
.footer__links a:hover { color: var(--cream); padding-left: 6px; }

.footer__contact { display: grid; gap: 1.1rem; }
.footer__contact a, .footer__contact p {
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
  color: var(--cream-dim);
  font-size: 0.98rem;
  line-height: 1.55;
}
.footer__contact a:hover { color: var(--cream); }
.footer__contact svg { width: 18px; height: 18px; color: var(--rose); flex: none; margin-top: 3px; }

.socials { display: flex; gap: 0.65rem; margin-top: 1.75rem; }
.socials a {
  width: 42px; height: 42px;
  display: grid; place-items: center;
  border: 1px solid var(--line-ui);
  border-radius: var(--r-pill);
  color: var(--cream-dim);
  transition: border-color 0.3s linear, color 0.3s linear, transform 0.3s var(--ease);
}
.socials a:hover { border-color: var(--rose); color: var(--cream); transform: translateY(-2px); }
.socials svg { width: 17px; height: 17px; fill: currentColor; }

.footer__base {
  margin-top: clamp(3rem, 6vw, 4.5rem);
  padding-block: 1.75rem;
  border-top: 1px solid var(--line-soft);
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 2rem;
  justify-content: space-between;
  align-items: center;
  font-size: 0.84rem;
  color: rgba(201, 169, 177, 0.75);
}
.footer__base a:hover { color: var(--cream-dim); }

/* --------------------------------------------------------------------------
   19. Portfolio grid
   -------------------------------------------------------------------------- */
.work-grid {
  display: grid;
  gap: clamp(1.25rem, 2.5vw, 2rem);
}
@media (min-width: 760px)  { .work-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1140px) { .work-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* the live project takes the full width of the first row */
.work-card--lead { grid-column: 1 / -1; }

.work-card {
  position: relative;
  display: block;
  overflow: hidden;
  background: var(--ink-3);
  isolation: isolate;
  transform: perspective(1000px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  transition: transform 0.5s var(--ease);
}
.work-card__media { overflow: hidden; position: relative; }
.work-card__media img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  transition: transform 0.9s var(--ease), filter 0.6s var(--ease-soft);
}
.work-card--lead .work-card__media img { aspect-ratio: 16 / 9; }
@media (max-width: 640px) { .work-card--lead .work-card__media img { aspect-ratio: 4 / 3; } }

a.work-card:hover .work-card__media img { transform: scale(1.05); }

.work-card__body {
  padding: clamp(1.25rem, 2.5vw, 2rem);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem 1.5rem;
  border-top: 1px solid var(--line-soft);
}
.work-card__title { font-family: var(--display); font-size: 1.45rem; color: var(--cream); }
.work-card__meta { font-size: 0.84rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--rose-dim); }

/* --------------------------------------------------------------------------
   20. Gallery grid (portfolio detail)
   -------------------------------------------------------------------------- */
.gallery {
  display: grid;
  gap: clamp(0.75rem, 1.6vw, 1.25rem);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (min-width: 900px) { .gallery { grid-template-columns: repeat(6, minmax(0, 1fr)); } }

.gallery button {
  position: relative;
  display: block;
  width: 100%;
  overflow: hidden;
  background: var(--ink-3);
}
.gallery img {
  width: 100%; height: 100%;
  object-fit: cover;
  aspect-ratio: 4 / 3;
  transition: transform 0.8s var(--ease), opacity 0.4s linear;
}
.gallery button:hover img { transform: scale(1.05); }
@media (min-width: 900px) {
  /* Six cell mosaic that repeats, so the pattern holds however many renders
     the project has. Every row adds to 6 columns: 4+2, 2+4, 3+3. */
  .gallery button:nth-child(6n + 1) { grid-column: span 4; }
  .gallery button:nth-child(6n + 1) img { aspect-ratio: 16 / 9; }
  .gallery button:nth-child(6n + 2) { grid-column: span 2; }
  .gallery button:nth-child(6n + 2) img { aspect-ratio: 4 / 5; }
  .gallery button:nth-child(6n + 3) { grid-column: span 2; }
  .gallery button:nth-child(6n + 3) img { aspect-ratio: 4 / 5; }
  .gallery button:nth-child(6n + 4) { grid-column: span 4; }
  .gallery button:nth-child(6n + 4) img { aspect-ratio: 16 / 9; }
  .gallery button:nth-child(6n + 5) { grid-column: span 3; }
  .gallery button:nth-child(6n + 6) { grid-column: span 3; }
}

/* --------------------------------------------------------------------------
   21. Lightbox
   -------------------------------------------------------------------------- */
.lightbox {
  position: fixed; inset: 0;
  z-index: var(--z-lightbox);
  background: rgba(8, 10, 15, 0.96);
  display: grid;
  place-items: center;
  padding: clamp(1rem, 4vw, 3rem);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.4s var(--ease-soft), visibility 0.4s;
}
.lightbox.is-open { opacity: 1; visibility: visible; }
.lightbox img {
  max-width: 100%;
  max-height: 82dvh;
  object-fit: contain;
  transform: scale(0.97);
  transition: transform 0.45s var(--ease);
}
.lightbox.is-open img { transform: scale(1); }

.lightbox__caption {
  margin-top: 1.25rem;
  text-align: center;
  font-size: 0.9rem;
  letter-spacing: 0.08em;
  color: var(--rose-dim);
}
.lightbox__close,
.lightbox__nav {
  position: absolute;
  width: 50px; height: 50px;
  display: grid; place-items: center;
  border: 1px solid var(--line-ui);
  border-radius: var(--r-pill);
  color: var(--cream);
  background: rgba(13, 16, 23, 0.7);
  transition: border-color 0.3s linear, background-color 0.3s linear;
}
.lightbox__close:hover, .lightbox__nav:hover { border-color: var(--rose); background: rgba(65, 31, 45, 0.7); }
.lightbox__close { top: clamp(1rem, 3vw, 2rem); right: clamp(1rem, 3vw, 2rem); }
.lightbox__nav--prev { left: clamp(0.5rem, 2vw, 2rem); top: 50%; transform: translateY(-50%); }
.lightbox__nav--next { right: clamp(0.5rem, 2vw, 2rem); top: 50%; transform: translateY(-50%); }
.lightbox__nav svg, .lightbox__close svg { width: 19px; height: 19px; }
.lightbox__nav--prev svg { transform: rotate(180deg); }

/* --------------------------------------------------------------------------
   22. Values (about page): bordered rows, no card containers
   -------------------------------------------------------------------------- */
.values { display: grid; gap: 0; border-top: 1px solid var(--line-soft); }
@media (min-width: 900px) { .values { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1px; background: var(--line-soft); } }

.value {
  background: var(--ink);
  padding: clamp(2rem, 4vw, 3rem) clamp(1.25rem, 2.5vw, 2.25rem);
  border-bottom: 1px solid var(--line-soft);
}
@media (min-width: 900px) { .value { border-bottom: 0; } }
.value svg { width: 30px; height: 30px; color: var(--rose); margin-bottom: 1.5rem; }
.value h3 { font-size: 1.3rem; margin-bottom: 0.85rem; }
.value p { font-size: 0.98rem; }

/* --------------------------------------------------------------------------
   23. Contact page
   -------------------------------------------------------------------------- */
.contact-grid {
  display: grid;
  gap: clamp(2.5rem, 5vw, 4.5rem);
  align-items: start;
}
@media (min-width: 1000px) { .contact-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr); } }

.field { display: grid; gap: 0.5rem; margin-bottom: 1.5rem; }
.field label {
  font-size: 0.8rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--cream-dim);      /* 11.9:1 on --ink */
}
.field input,
.field textarea,
.field select {
  width: 100%;
  background: var(--ink-3);
  border: 1px solid var(--line-ui);
  border-radius: var(--r-flat);
  padding: 0.95rem 1.1rem;
  color: var(--cream);
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 300;
  transition: border-color 0.3s linear, background-color 0.3s linear;
}
.field textarea { resize: vertical; min-height: 150px; }
.field input::placeholder,
.field textarea::placeholder {
  color: rgba(216, 202, 197, 0.66);   /* 5.0:1 on --ink-3 */
  opacity: 1;
}
.field input:focus, .field textarea:focus, .field select:focus {
  outline: none;
  border-color: var(--rose);
  background: var(--ink-2);
}
.field input:focus-visible, .field textarea:focus-visible, .field select:focus-visible {
  outline: 2px solid var(--rose);
  outline-offset: 2px;
}
.field__hint { font-size: 0.85rem; color: rgba(216, 202, 197, 0.7); }
.field__error {
  font-size: 0.85rem;
  color: #ffa8a8;                      /* 10.3:1 on --ink */
}
.field.has-error input, .field.has-error textarea { border-color: #ffa8a8; }
.field.has-error .field__error { display: block; }

.form-note {
  margin-top: 1.25rem;
  font-size: 0.88rem;
  color: rgba(216, 202, 197, 0.7);
}
/* Kept in the DOM at all times so the live region is registered before it is
   written to. Hiding with display:none makes screen readers miss the update. */
.form-status {
  margin-top: 1.5rem;
  padding: 1rem 1.25rem;
  border: 1px solid var(--line-ui);
  font-size: 0.95rem;
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}
.form-status.is-shown {
  position: static;
  width: auto; height: auto;
  overflow: visible;
  clip: auto;
  white-space: normal;
}
.form-status[data-state="ok"] { border-color: rgba(120, 200, 150, 0.5); color: #9fe0b8; }
.form-status[data-state="error"] { border-color: rgba(255, 168, 168, 0.5); color: #ffa8a8; }

.info-panel { background: var(--ink-2); border: 1px solid var(--line-soft); padding: clamp(1.75rem, 3.5vw, 2.75rem); }
.info-panel h3 { font-size: 1.4rem; margin-bottom: 1.75rem; }
.info-row { display: flex; gap: 1rem; align-items: flex-start; padding-block: 1.1rem; border-top: 1px solid var(--line-soft); }
.info-row:first-of-type { border-top: 0; padding-top: 0; }
.info-row svg { width: 19px; height: 19px; color: var(--rose); flex: none; margin-top: 4px; }
.info-row__label { font-size: 0.76rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--rose-dim); margin-bottom: 0.2rem; }
.info-row__value { color: var(--cream); font-size: 1.02rem; line-height: 1.5; }
.info-row a.info-row__value:hover { color: var(--rose); }

.map-frame {
  border: 1px solid var(--line-soft);
  overflow: hidden;
  background: var(--ink-3);
  aspect-ratio: 16 / 9;
}
.map-frame iframe { width: 100%; height: 100%; border: 0; display: block; filter: grayscale(1) invert(0.92) contrast(0.88); }

/* --------------------------------------------------------------------------
   23b. Pointer micro-interactions
   Desktop only, and only for a real pointer. Values come from JS as custom
   properties; the transitions here do the smoothing, so the JS only has to
   write two numbers per frame.
   -------------------------------------------------------------------------- */
@media (hover: hover) and (pointer: fine) {
  .value, .figure {
    position: relative;
    isolation: isolate;
  }
  /* a soft light that follows the cursor across the panel */
  .value::after, .figure::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background: radial-gradient(
      16rem 16rem at var(--px, 50%) var(--py, 50%),
      rgba(201, 169, 177, 0.10) 0%,
      transparent 70%
    );
    opacity: 0;
    transition: opacity 0.45s var(--ease-soft);
    pointer-events: none;
  }
  .value:hover::after, .figure:hover::after { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .btn { transform: none !important; }
  .work-card { transform: none !important; }
  .value::after, .figure::after { display: none; }
}

/* --------------------------------------------------------------------------
   23c. Ebru
   A combed Turkish marbling texture at very low opacity, used to give the
   large flat panels something to be. Sits above each surface's own gradient
   and below every piece of content.

   Generated on the local ComfyUI (FLUX.1 schnell), then desaturated to a pure
   luminance texture so the colour comes from the surface underneath and the
   brand palette stays locked. A procedural SVG version was tried first and is
   200x smaller, but side by side it reads as a repeating mechanical wave where
   this one has the organic variation of actual marbled paper.

   1000px at q34, 74 KB. Compression is invisible at this opacity: a ladder
   from 1600/q62 down to 900/q34 was indistinguishable composited at 11%, and
   the content is soft flowing lines that upscale without artefacts.
   Regenerate with build/gen-ebru.sh.
   -------------------------------------------------------------------------- */
.stack__card::before,
.manifesto::after,
.closer::after,
.page-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background: url('/assets/ebru.webp') center / cover no-repeat;
  opacity: var(--ebru, 0.14);
  pointer-events: none;
}
/* Behind the ghost numeral, which sits at -1. */
.stack__card::before { z-index: -2; }
.manifesto::after,
.closer::after,
.page-hero::after { z-index: 0; }

/* The wine and navy grounds carry the texture more strongly than the near
   black ones, so each surface gets its own strength. */
.manifesto { --ebru: 0.10; }
.closer    { --ebru: 0.09; }
.page-hero { --ebru: 0.11; }

/* Keep real content above the texture on the surfaces where it is z-index 0. */
.manifesto__grid,
.closer .shell,
.page-hero .shell { position: relative; z-index: 1; }

/* --------------------------------------------------------------------------
   24. Reduced motion. Everything collapses to a static, readable page.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
    animation-delay: 0s !important;
    scroll-behavior: auto !important;
  }

  .js-ready [data-reveal] { opacity: 1 !important; transform: none !important; }
  .js-ready .curtain > img, .js-ready .curtain > picture > img { scale: 1 !important; }
  .js-ready .curtain::after { display: none; }
  .split-line > span { transform: none !important; }
  .step__dot span { transform: scale(1) !important; }
  .track-line::after { transform: scaleY(1) !important; }
  .hero-slides { transform: none !important; }
  .menu { transition: none; }
  .menu__list a { transform: none !important; }
  .fly--on { height: auto !important; }
  .fly--on .fly__img { position: static !important; opacity: 1 !important; transform: none !important; }
  .menu__foot { opacity: 1 !important; }
  .pan__viewport { scroll-snap-type: none; }
}

/* --------------------------------------------------------------------------
   25. Print
   -------------------------------------------------------------------------- */
@media print {
  .header, .floats, .menu, .progress-bar, .lightbox, .closer { display: none !important; }
  body { background: #fff; color: #000; }
  h1, h2, h3, h4 { color: #000; }
  .js-ready [data-reveal] { opacity: 1 !important; transform: none !important; }
  .js-ready .curtain::after { display: none; }
}

.contact-cta { margin-top: 2rem; }
