/* ════════════════════════════════════════════════════════════════
   Aarav Mehta — portfolio (rework 3: layered)
   Light warm-paper base + ONE dark inverted chapter (Atlas flagship).
   Strong grotesque + strict grid + grain + disciplined accents:
   green = availability (semantic), ice-blue = Atlas chapter only.
   Fonts: Instrument Sans (display/body) + Geist Mono (labels/meta).
   Radius: buttons pill, media 6px, chapter sheet 28px. Theme lock: light.
   ════════════════════════════════════════════════════════════════ */

@font-face {
  font-family: "Instrument Sans";
  src: url("../assets/fonts/instrument-sans-var-latin.woff2") format("woff2");
  font-weight: 400 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("../assets/fonts/geist-mono-var-latin.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap;
}

:root {
  --paper: #EAEAE4;
  --ink: #16150F;
  --ink-2: #55524A;
  --ink-3: #67635A;   /* AA on paper for small mono labels (~5:1) */
  --line: rgba(22, 21, 15, 0.14);
  --line-soft: rgba(22, 21, 15, 0.08);
  --avail: #47702F;   /* AA on paper when used as text ("Copied") */

  --night: #0E0E14;            /* Atlas chapter sheet */
  --night-raise: #16161e;
  --frost: #ECECE6;            /* text on night */
  --frost-2: #B9B8AE;
  --frost-3: #83828C;
  --ice: #A9C6E8;              /* Atlas accent, chapter-only */
  --line-night: rgba(236, 236, 230, 0.14);

  /* Zuned-style all-mono type system: one grotesque mono voice for
     everything (display, body, labels), not just labels. --sans is kept
     as an alias so every existing `var(--sans)` call site (form fields,
     buttons) picks this up for free without hunting down each rule. */
  --sans: "Geist Mono", ui-monospace, Consolas, monospace;
  --mono: "Geist Mono", ui-monospace, Consolas, monospace;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);

  --pad: clamp(1.25rem, 5vw, 5.5rem);
  --maxw: 1280px;
  --rail: 180px;
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }

body {
  font-family: var(--sans);
  background: var(--paper);
  color: var(--ink);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
body.is-loading { overflow: hidden; }

img { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }
ul, ol { list-style: none; }
::selection { background: var(--ink); color: var(--paper); }

.label {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.label-light { color: var(--frost-3); }

/* ── shared links & buttons ── */

.link-arrow {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
  font-size: 0.95rem;
  padding-bottom: 2px;
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0% 1px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  transition: background-size 0.4s var(--ease);
}
.link-arrow:hover, .link-arrow:focus-visible { background-size: 100% 1px; }
.link-arrow-light { color: var(--frost); }

.btn-primary {
  display: inline-block;
  padding: 0.85rem 2.1rem;
  border-radius: 999px;
  background: var(--ink);
  color: var(--paper);
  font-size: 0.92rem;
  font-weight: 500;
  transition: transform 0.25s var(--ease);
  will-change: transform;
}
.btn-primary:active { transform: scale(0.97); }

.btn-light {
  display: inline-block;
  padding: 0.8rem 1.9rem;
  border-radius: 999px;
  background: var(--frost);
  color: var(--night);
  font-size: 0.9rem;
  font-weight: 550;
  transition: transform 0.25s var(--ease), opacity 0.25s var(--ease);
}
.btn-light:hover { transform: translateY(-2px); }
.btn-light:active { transform: translateY(0) scale(0.98); }

/* ── reveal & stagger ── */

[data-reveal] {
  opacity: 0;
  transform: translateY(38px);
  transition: opacity 0.9s var(--ease), transform 0.9s var(--ease);
  transition-delay: calc(var(--i, 0) * 90ms);
}
[data-reveal].in { opacity: 1; transform: none; }

/* ── masked word-rise (Zuned-style line reveal) on section eyebrows ──
   JS wraps each word in an overflow-hidden mask with an inner that rises
   from below; staggered per word. Only applied on visible, motion-OK,
   IO-capable loads (see fx-linerise), so plain text is the safe fallback. */
.lr-ready { }
.lr-mask { display: inline-block; overflow: hidden; vertical-align: top; }
.lr-in {
  display: inline-block;
  transform: translateY(110%);
  transition: transform 0.72s var(--ease);
  transition-delay: calc(var(--li, 0) * 45ms);
}
.lr-ready.in .lr-in { transform: translateY(0); }

/* ── loader ── */

.loader {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: var(--ink);
  color: var(--paper);
  display: flex;
  align-items: flex-end;
  transform: translateY(0);
  transition: transform 0.7s cubic-bezier(0.76, 0, 0.24, 1);
}
.loader.done { transform: translateY(-100%); }
.loader-inner {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  padding: 2rem var(--pad);
}
.loader-name { font-size: 0.95rem; font-weight: 550; }
.loader-count {
  font-family: var(--mono);
  font-size: clamp(3rem, 9vw, 7rem);
  font-weight: 500;
  line-height: 0.9;
  letter-spacing: -0.03em;
}

/* ── grain ── */

.grain {
  position: fixed;
  inset: -50%;
  z-index: 90;
  pointer-events: none;
  opacity: 0.05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ═══════════════ TOP BAR ═══════════════ */

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1.5rem;
  height: 60px;
  padding: 0 var(--pad);
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line-soft);
}
.wordmark { font-size: 0.95rem; font-weight: 550; letter-spacing: -0.01em; }
.nav { display: flex; gap: clamp(0.9rem, 2.5vw, 1.8rem); }
.nav a {
  font-size: 0.88rem;
  color: var(--ink-2);
  padding-bottom: 2px;
  background-image: linear-gradient(var(--ink), var(--ink));
  background-size: 0% 1px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  transition: background-size 0.35s var(--ease), color 0.35s var(--ease);
}
.nav a:hover, .nav a:focus-visible,
.nav a.active { color: var(--ink); background-size: 100% 1px; }
.topbar-time { white-space: nowrap; }

/* ═══════════════ HERO ═══════════════ */

.hero {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: clamp(3rem, 11vh, 7rem) var(--pad) clamp(2.5rem, 7vh, 5rem);
}
.hero-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  color: var(--ink-2);
}
.hero-eyebrow::before {
  content: "";
  width: 7px; height: 7px;
  border-radius: 999px;
  background: var(--avail);
  animation: pulse 2.6s var(--ease) infinite;
}
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--avail) 55%, transparent); }
  70%  { box-shadow: 0 0 0 7px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

.hero-name {
  margin-top: clamp(1.25rem, 3vh, 2rem);
  font-size: clamp(3rem, 12.5vw, 13rem);
  font-weight: 560;
  letter-spacing: -0.04em;
  line-height: 0.94;
  white-space: nowrap;
}
.hero-name .line { display: flex; justify-content: space-between; overflow: hidden; }
.hero-name .w { display: inline-flex; }
.hero-name .ch {
  display: inline-block;
  transform: translateY(115%);
  animation: rise 0.9s var(--ease) forwards;
  animation-delay: calc(var(--c) * 35ms + 150ms);
}
@keyframes rise { to { transform: translateY(0); } }
/* until JS splits letters, words rise whole */
.hero-name:not(.split) .w {
  transform: translateY(115%);
  animation: rise 1s var(--ease) forwards;
}

.hero-foot {
  margin-top: clamp(2rem, 5vh, 3.5rem);
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: 1.5rem 3rem;
}
.hero-line {
  max-width: 44ch;
  font-size: clamp(1.05rem, 1.6vw, 1.35rem);
  color: var(--ink-2);
  line-height: 1.4;
}
.hero-ctas {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  white-space: nowrap;
}

.hero-facts {
  margin-top: clamp(2.5rem, 7vh, 4.5rem);
  padding-top: 1.5rem;
  border-top: 1px solid var(--line);
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.5rem;
}
.fact .label { display: block; margin-bottom: 0.4rem; }
.fact p { font-size: 0.95rem; }

/* ═══════════════ MARQUEE ═══════════════ */

.marquee {
  overflow: hidden;
  white-space: nowrap;
  border-block: 1px solid var(--line);
  padding: 0.9rem 0;
}
.marquee-track { display: inline-flex; animation: scroll-x 30s linear infinite; }
.marquee-track span {
  font-family: var(--mono);
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
}
@keyframes scroll-x { to { transform: translateX(-50%); } }

/* ═══════════════ ATLAS CHAPTER (dark) ═══════════════ */

.chapter {
  /* Zuned reference stays one continuous light-paper tone start to
     finish — no inverted "dark chapter" anywhere on the page. Re-point
     the chapter's own night/frost/ice palette to their light
     equivalents right here (same specificity as :root, later in source
     order = wins), so every existing var(--night)/var(--frost)/var(--ice)
     rule below — plus fx4/fx7 modules further down the file — falls in
     line automatically with zero changes to those rules. */
  --night: var(--paper);
  --night-raise: #E2E2DA;
  --frost: var(--ink);
  --frost-2: var(--ink-2);
  --frost-3: var(--ink-3);
  --ice: var(--ink);
  --line-night: var(--line);

  margin: clamp(2rem, 5vh, 3.5rem) clamp(0.6rem, 1.2vw, 1.25rem);
  background: var(--night);
  color: var(--frost);
  border-radius: clamp(16px, 2vw, 28px);
  padding: clamp(3.5rem, 9vh, 7rem) clamp(1.25rem, 4vw, 4.5rem);
  border: 1px solid var(--line);
  position: relative;
  overflow: hidden;
}
.chapter ::selection { background: var(--frost); color: var(--night); }

.chapter-head {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: start;
}
.chapter-index {
  font-family: var(--mono);
  font-size: clamp(4rem, 10vw, 10rem);
  font-weight: 500;
  line-height: 0.8;
  color: transparent;
  -webkit-text-stroke: 1px var(--frost-3);
}
.chapter-title h2 {
  margin-top: 0.9rem;
  font-size: clamp(2.6rem, 7vw, 7rem);
  font-weight: 580;
  letter-spacing: -0.03em;
  line-height: 0.95;
}
.chapter-sub {
  margin-top: 1.25rem;
  max-width: 52ch;
  color: var(--frost-2);
  font-size: clamp(1rem, 1.4vw, 1.2rem);
  line-height: 1.45;
}

.chapter-media {
  margin-top: clamp(2.5rem, 6vh, 4rem);
  overflow: hidden;
  border-radius: 6px;
  background: var(--night-raise);
}
.chapter-media img {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  transition: transform 0.9s var(--ease);
}
.chapter-media:hover img { transform: scale(1.03); }

.chapter-body {
  margin-top: clamp(2.5rem, 6vh, 4rem);
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: start;
}
.chapter-copy p {
  max-width: 52ch;
  color: var(--frost-2);
  font-size: 1rem;
  line-height: 1.55;
}

.stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.5rem 2rem;
}
.stat dt { margin-bottom: 0.4rem; }
.stat dd {
  font-family: var(--mono);
  font-size: clamp(2.2rem, 4vw, 3.6rem);
  font-weight: 500;
  letter-spacing: -0.03em;
  line-height: 1;
  color: var(--ice);
}
.stat-unit { font-size: 0.5em; color: var(--frost-3); margin-left: 0.15em; }

.feature-list {
  margin-top: clamp(3rem, 7vh, 5rem);
  border-top: 1px solid var(--line-night);
}
.feature {
  display: grid;
  grid-template-columns: minmax(60px, 180px) minmax(0, 320px) minmax(0, 1fr);
  gap: 1.5rem 2rem;
  padding: clamp(1.4rem, 3vh, 2rem) 0;
  border-bottom: 1px solid var(--line-night);
}
.feature-num {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--frost-3);
  padding-top: 0.5rem;
}
.feature h3 {
  font-size: clamp(1.25rem, 2vw, 1.7rem);
  font-weight: 550;
  letter-spacing: -0.01em;
}
.feature p {
  color: var(--frost-2);
  font-size: 0.93rem;
  max-width: 48ch;
  padding-top: 0.35rem;
}

.chapter-site {
  margin-top: clamp(3rem, 8vh, 5.5rem);
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: center;
}
.chapter-media-small { margin-top: 0; }
.chapter-media-small img { aspect-ratio: 4 / 3; }
.chapter-site-copy h3 {
  margin-top: 0.9rem;
  font-size: clamp(1.7rem, 3vw, 2.6rem);
  font-weight: 560;
  letter-spacing: -0.02em;
}
.chapter-site-copy > p {
  margin-top: 1rem;
  max-width: 46ch;
  color: var(--frost-2);
  font-size: 0.97rem;
}
.chapter-site-copy .work-meta dt { color: var(--frost-3); }
.chapter-site-copy .work-meta dd { color: var(--frost); }
.chapter-links {
  margin-top: 1.9rem;
  display: flex;
  align-items: center;
  gap: 1.5rem;
  flex-wrap: wrap;
}

.work-meta {
  margin-top: 1.6rem;
  display: flex;
  flex-wrap: wrap;
  gap: 1.75rem;
}
.work-meta dt {
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 0.3rem;
}
.work-meta dd { font-size: 0.9rem; }

/* ═══════════════ SHARED BLOCK LAYOUT (light) ═══════════════ */

.block {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: clamp(3.5rem, 9vh, 7rem) var(--pad);
  border-top: 1px solid var(--line);
}
.block:first-of-type { border-top: 0; }
.block-head {
  display: grid;
  grid-template-columns: var(--rail) minmax(0, 1fr);
  gap: 2rem;
  align-items: baseline;
  margin-bottom: clamp(2.5rem, 6vh, 4rem);
}
.block-note { max-width: 44ch; color: var(--ink-2); font-size: 0.95rem; }

/* ── services ── */

.service {
  display: grid;
  grid-template-columns: var(--rail) minmax(0, 1fr);
  gap: 2rem;
  padding: clamp(1.6rem, 3.5vh, 2.5rem) 0;
  border-top: 1px solid var(--line-soft);
}
.service:first-child { border-top: 0; }
.service-num {
  font-family: var(--mono);
  font-size: 0.8rem;
  color: var(--ink-3);
  padding-top: 0.4rem;
}
.service-body h3 {
  font-size: clamp(1.3rem, 2.2vw, 1.9rem);
  font-weight: 540;
  letter-spacing: -0.01em;
}
.service-body > p {
  margin-top: 0.6rem;
  max-width: 52ch;
  color: var(--ink-2);
  font-size: 0.95rem;
}
.tags {
  margin-top: 1.1rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}
.tags li {
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.28rem 0.7rem;
}

/* ── process steps ── */

.steps {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(1.5rem, 3vw, 2.5rem);
}
.step-num {
  display: block;
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--ink-3);
  padding-bottom: 0.9rem;
  margin-bottom: 1rem;
  border-bottom: 1px solid var(--line);
}
.step h3 { font-size: 1.05rem; font-weight: 560; letter-spacing: -0.01em; }
.step p { margin-top: 0.5rem; color: var(--ink-2); font-size: 0.88rem; }

/* ── about ── */

.about-body { display: grid; grid-template-columns: var(--rail) minmax(0, 1fr); gap: 0 2rem; }
.about-lead {
  grid-column: 2;
  font-size: clamp(1.4rem, 2.6vw, 2.3rem);
  font-weight: 480;
  letter-spacing: -0.015em;
  line-height: 1.3;
  max-width: 26ch;
}
.about-cols {
  grid-column: 2;
  margin-top: clamp(1.75rem, 4vh, 2.75rem);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 2rem 3rem;
  max-width: 66ch;
}
.about-cols p { color: var(--ink-2); font-size: 0.95rem; }
.currently {
  grid-column: 2;
  margin-top: clamp(2rem, 5vh, 3rem);
  padding-top: 1.5rem;
  border-top: 1px solid var(--line-soft);
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.5rem;
  max-width: 720px;
}

/* ── faq ── */

.faq-list { max-width: 760px; }
.faq { border-top: 1px solid var(--line-soft); }
.faq:last-child { border-bottom: 1px solid var(--line-soft); }
.faq summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1.5rem;
  padding: 1.25rem 0;
  font-size: clamp(1rem, 1.5vw, 1.2rem);
  font-weight: 520;
  transition: color 0.3s var(--ease);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--ink-2); }
.faq-icon {
  font-family: var(--mono);
  font-size: 1.1rem;
  color: var(--ink-3);
  transition: transform 0.4s var(--ease);
  flex-shrink: 0;
}
.faq[open] .faq-icon { transform: rotate(45deg); }
.faq-body { overflow: hidden; }
.faq-body p {
  max-width: 60ch;
  color: var(--ink-2);
  font-size: 0.95rem;
  padding: 0 0 1.4rem;
}

/* ── contact ── */

.contact-body { max-width: 680px; }
.contact-head {
  font-size: clamp(2.2rem, 5.5vw, 4.5rem);
  font-weight: 560;
  letter-spacing: -0.025em;
  line-height: 1.02;
}
.contact-sub {
  margin-top: 1.25rem;
  max-width: 46ch;
  color: var(--ink-2);
  font-size: 1rem;
}
.contact-mail {
  margin-top: 1.75rem;
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}
.contact-email {
  font-size: clamp(1.1rem, 2vw, 1.5rem);
  letter-spacing: -0.01em;
  padding-bottom: 3px;
  background-image: linear-gradient(var(--ink), var(--ink));
  background-size: 100% 1px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  transition: opacity 0.3s var(--ease);
}
.contact-email:hover { opacity: 0.6; }
.copy-btn {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-2);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.4rem 0.9rem;
  cursor: pointer;
  transition: background 0.3s var(--ease), color 0.3s var(--ease);
}
.copy-btn:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.copy-note { font-family: var(--mono); font-size: 0.7rem; color: var(--avail); }

.quote-form {
  margin-top: clamp(2.5rem, 6vh, 4rem);
  display: grid;
  gap: 1.25rem;
}
.field { display: grid; gap: 0.45rem; }
.field label {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.field input, .field textarea {
  width: 100%;
  padding: 0.7rem 0;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 1rem;
  resize: vertical;
  transition: border-color 0.3s var(--ease);
}
.field input:focus, .field textarea:focus { outline: none; border-bottom-color: var(--ink); }
.quote-form button {
  justify-self: start;
  margin-top: 0.5rem;
  padding: 0.8rem 2rem;
  border: 0;
  border-radius: 999px;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--sans);
  font-size: 0.9rem;
  font-weight: 500;
  cursor: pointer;
  transition: transform 0.25s var(--ease);
}
.quote-form button:hover { transform: translateY(-2px); }
.quote-form button:active { transform: translateY(0) scale(0.98); }
.form-note { min-height: 1.2em; font-family: var(--mono); font-size: 0.72rem; color: var(--ink-2); }

/* ═══════════════ FOOTER ═══════════════ */

.footer {
  border-top: 1px solid var(--line);
  padding: clamp(2.5rem, 6vh, 4rem) var(--pad) clamp(1.75rem, 4vh, 2.5rem);
  overflow: hidden;
}
.footer-word {
  font-size: clamp(3rem, 11.5vw, 12rem);
  font-weight: 600;
  letter-spacing: -0.04em;
  line-height: 0.9;
  white-space: nowrap;
  text-align: center;
  color: transparent;
  -webkit-text-stroke: 1px var(--ink-3);
  user-select: none;
}
.footer-row {
  margin-top: clamp(2rem, 5vh, 3rem);
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 2rem;
  flex-wrap: wrap;
}
.footer-tag { color: var(--ink-3); font-size: 0.85rem; }
.footer-right { display: grid; gap: 0.8rem; justify-items: end; text-align: right; }
.footer-links { display: flex; gap: 1.25rem; }
.footer-links a { font-size: 0.85rem; color: var(--ink-2); transition: color 0.25s var(--ease); }
.footer-links a:hover { color: var(--ink); }
.footer-time { font-family: var(--mono); font-size: 0.72rem; color: var(--ink-3); }
.to-top {
  width: 46px; height: 46px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--ink-2);
  transition: background 0.3s var(--ease), color 0.3s var(--ease), transform 0.3s var(--ease);
}
.to-top:hover { background: var(--ink); color: var(--paper); transform: translateY(-3px); }

/* ═══════════════ MOBILE ═══════════════ */

@media (max-width: 900px) {
  :root { --rail: 0px; }
  .topbar-time { display: none; }
  .hero-name { white-space: normal; }
  .hero-name .line { display: block; }
  .hero-foot { grid-template-columns: 1fr; align-items: start; }
  .hero-facts { grid-template-columns: 1fr; gap: 1.1rem; }

  .chapter-head { grid-template-columns: 1fr; gap: 1rem; }
  .chapter-body { grid-template-columns: 1fr; }
  .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .feature { grid-template-columns: 1fr; gap: 0.4rem; }
  .feature-num, .feature p { padding-top: 0; }
  .chapter-site { grid-template-columns: 1fr; }

  .block-head { grid-template-columns: 1fr; gap: 0.75rem; margin-bottom: 2rem; }
  .service { grid-template-columns: 1fr; gap: 0.5rem; }
  .service-num { padding-top: 0; }
  .steps { grid-template-columns: 1fr; }
  .about-body { grid-template-columns: 1fr; }
  .about-lead, .about-cols, .currently { grid-column: 1; }
  .about-cols { grid-template-columns: 1fr; }
  .currently { grid-template-columns: 1fr; gap: 1.1rem; }

  .footer-row { flex-direction: column; align-items: flex-start; }
  .footer-right { justify-items: start; text-align: left; }
}

@media (max-width: 640px) {
  .nav { gap: 0.75rem; }
  .nav a { font-size: 0.8rem; }
  .nav a[data-spy="process"], .nav a[data-spy="about"] { display: none; }
}

/* ═══════════════ REDUCED MOTION ═══════════════ */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .loader { display: none; }
  body.is-loading { overflow: auto; }
  .hero-name .w, .hero-name .ch { animation: none; transform: none; }
  .hero-name:not(.split) .w { animation: none; transform: none; }
  .hero-eyebrow::before { animation: none; }
  [data-reveal] { opacity: 1; transform: none; transition: none; }
  .marquee-track { animation: none; }
  .chapter-media img, .link-arrow, .nav a, .btn-primary, .btn-light,
  .quote-form button, .copy-btn, .to-top, .faq-icon { transition: none; }
}


/* ════════════════════════════════════════════════════════════════
   fx1-progress — scroll progress system (Layer 1)
   1) .fx1-progress-bar   2px ink hairline fixed at the very top,
      scaleX 0 → 1 via CSS scroll-driven animation. Zero JS.
      Browsers without animation-timeline support never see it.
   2) .fx1-progress-section   mono current-section readout inside
      .topbar-time; text is swapped by the existing section spy
      (see PATCH.md), so no second IntersectionObserver exists.
   Append this whole block to the END of css/style.css and keep its
   internal order: the reduced-motion block at the bottom must stay
   last so it wins the cascade against the @supports block.
   ════════════════════════════════════════════════════════════════ */

/* Default state: hidden. Only browsers that pass the @supports gate
   ever display the bar (brief: no JS fallback, they just don't get it). */
.fx1-progress-bar { display: none; }

@supports (animation-timeline: scroll(root)) {
  .fx1-progress-bar {
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 2px;
    z-index: 30;              /* above .topbar (20), below .grain (90) */
    pointer-events: none;
    background: var(--ink);
    transform: scaleX(0);
    transform-origin: 0 50%;
    /* Longhands on purpose: the `animation` shorthand resets
       animation-timeline back to `auto`, silently killing the effect. */
    animation-name: fx1-progress-grow;
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-timeline: scroll(root);
  }
  @keyframes fx1-progress-grow {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
  }
}

/* Current-section readout. Lives inside .topbar-time (which carries
   .label), so it inherits the mono / uppercase / letter-spacing /
   ink-3 styling for free. Starts empty: if JS never runs, :empty
   collapses it and nothing is ever stuck invisible or half-shown. */
.fx1-progress-section {
  display: inline-block;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.45s var(--ease), transform 0.45s var(--ease);
}
.fx1-progress-section::after {
  content: "\00B7";           /* middle dot separator before the clock */
  margin: 0 0.55em;
}
.fx1-progress-section:empty { display: none; }
.fx1-progress-section.fx1-progress-on { opacity: 1; transform: none; }

/* ── reduced motion: KEEP THIS BLOCK LAST ──
   Bar: removed outright (it is pure animation).
   Readout: kept, but swaps instantly — it is information (like the
   clock and the nav .active state), not motion, so hiding it would
   reduce content rather than motion. */
@media (prefers-reduced-motion: reduce) {
  .fx1-progress-bar { display: none; }
  .fx1-progress-section {
    transition: none;
    transform: none;
  }
}

/* ════════════════════════════════════════════════════════════════
   fx2-hero — HERO KINETIC EXIT  (append to css/style.css)
   Scroll-LINKED (scrubbed, fully reversible — not triggered) exit
   choreography as the visitor scrolls away from the hero:
     · .hero-eyebrow  fades first      (hero exit  0% → 14%)
     · .hero-facts    fade early       (hero exit  0% → 30%)
     · .hero-name     lingers: drifts up 6% of its own height and
                      fades to 0       (hero exit  0% → 45%),
                      while its two words drift apart ±1.4% — a
                      transform-only stand-in for tracking expansion.
   Engine: CSS scroll-driven animations. ONE named view-timeline on
   .hero; every target scrubs against the same timeline so the
   choreography stays relative regardless of viewport height.
   No JS, no scroll listeners, compositor-only props (transform,
   opacity), linear timing (scrubbed motion must track the thumb).
   Collapse:
     · prefers-reduced-motion: reduce  → entire layer absent
     · no animation-timeline support   → entire layer absent
       (hero simply rests in its normal state; nothing half-applied)
     · JS failure → layer is inert until scroll and never hides
       anything on its own (fill-mode: forwards = no before-phase
       fill, so entrance states remain owned by the existing code)
   ════════════════════════════════════════════════════════════════ */

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {

    /* .hero is the single timeline subject: progress runs 0 → 1 as
       the section's box exits the top of the scrollport. */
    .hero {
      view-timeline: --fx2-hero block;
    }

    /* ── giant name: lingering drift-up + fade ──────────────────
       Applied to the WRAPPER only. The load-in letter rise animates
       the .ch child spans (and, pre-split, the .w spans), so the
       two effects never share an element and cannot collide. */
    .hero-name {
      animation-name: fx2-hero-name-exit;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: forwards;
      animation-timeline: --fx2-hero;
      animation-range: exit 0% exit 45%;
    }

    /* ── word-level "tracking expansion" (transform-only) ───────
       Animating letter-spacing triggers layout every frame, so the
       expansion is expressed as the two words drifting apart by
       1.4% of their own width instead — pure transform, composited.
       Scoped to .split so it can never contest the `animation`
       property with the no-JS fallback rise on
       `.hero-name:not(.split) .w`. Desktop only: below 901px the
       .line becomes display:block and the words stack, where
       opposite drifts would read as shear, not tracking. */
    @media (min-width: 901px) {
      .hero-name.split .w:first-child {
        animation-name: fx2-hero-word-open-l;
        animation-duration: auto;
        animation-timing-function: linear;
        animation-fill-mode: forwards;
        animation-timeline: --fx2-hero;
        animation-range: exit 0% exit 45%;
      }
      .hero-name.split .w:last-child {
        animation-name: fx2-hero-word-open-r;
        animation-duration: auto;
        animation-timing-function: linear;
        animation-fill-mode: forwards;
        animation-timeline: --fx2-hero;
        animation-range: exit 0% exit 45%;
      }
    }

    /* ── eyebrow: first out, quietly ────────────────────────────
       Scoped to .in so the existing [data-reveal] entrance owns the
       element until it has fully appeared; forwards fill keeps the
       before-phase empty, so the reveal transition is untouched. */
    .hero-eyebrow.in {
      animation-name: fx2-hero-quiet-exit;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: forwards;
      animation-timeline: --fx2-hero;
      animation-range: exit 0% exit 14%;
    }

    /* ── facts row: early, quiet exit ───────────────────────────
       Animates the WRAPPER. The entrance stagger lives on the .fact
       CHILDREN (main.js moves data-reveal down onto them), so the
       exit opacity here simply multiplies with the children's reveal
       opacity — the two systems never write the same property on
       the same element. */
    .hero-facts {
      animation-name: fx2-hero-quiet-exit;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: forwards;
      animation-timeline: --fx2-hero;
      animation-range: exit 0% exit 30%;
    }

    @keyframes fx2-hero-name-exit {
      from { opacity: 1; transform: translateY(0); }
      to   { opacity: 0; transform: translateY(-6%); }
    }
    @keyframes fx2-hero-quiet-exit {
      from { opacity: 1; transform: translateY(0); }
      to   { opacity: 0; transform: translateY(-4%); }
    }
    @keyframes fx2-hero-word-open-l {
      from { transform: translateX(0); }
      to   { transform: translateX(-1.4%); }
    }
    @keyframes fx2-hero-word-open-r {
      from { transform: translateX(0); }
      to   { transform: translateX(1.4%); }
    }
  }
}

/* ════════════════════════════════════════════════════════════════
   fx3-headings — masked word-rise reveals for the big text moments
   Targets (split into word masks by fx3 JS):
     .chapter-title h2, .contact-head, .about-lead, .block-head .label
   Contract:
     · Default (no JS, or split failed): text renders exactly as
       authored — nothing in this file hides anything on its own.
     · Hidden state requires BOTH html.fx3-headings-js (added by the
       fx3 IIFE before first paint) AND .fx3-headings-split (added
       per element only AFTER a successful split). An element can
       therefore never be stranded invisible.
     · prefers-reduced-motion: reduce → words are simply visible.
   ════════════════════════════════════════════════════════════════ */

/* The mask: an overflow-hidden inline box around each word.
   The padding extends the clip window ~0.1em above and ~0.14em below
   the content box so tight line-heights (h2 0.95, contact 1.02) never
   shave ascenders or descenders at rest; the negative margins cancel
   that padding out of layout, so line boxes measure exactly as the
   unsplit text did. vertical-align: top keeps the row of same-height
   masks aligned despite the inline-block + overflow baseline quirk. */
.fx3-headings-mask {
  display: inline-block;
  vertical-align: top;
  overflow: hidden;
  padding: 0.1em 0 0.14em;
  margin: -0.1em 0 -0.14em;
}

.fx3-headings-word {
  display: inline-block;
}

/* Hidden + rising state — double-gated (html class AND split class). */
html.fx3-headings-js .fx3-headings-split .fx3-headings-word {
  transform: translateY(130%);
  transition: transform 0.8s var(--ease, cubic-bezier(0.16, 1, 0.3, 1));
  transition-delay: var(--fx3-d, 0ms);
}
html.fx3-headings-js .fx3-headings-split.fx3-headings-in .fx3-headings-word {
  transform: translateY(0);
}

/* Reduced motion: fully disabled, even if the classes were applied
   or the preference flips mid-session. */
@media (prefers-reduced-motion: reduce) {
  html.fx3-headings-js .fx3-headings-split .fx3-headings-word {
    transform: none !important;
    transition: none !important;
  }
}

/* ════════════════════════════════════════════════════════════════
   fx4-chapter — Atlas chapter cinematics
   APPEND TO THE VERY END of css/style.css. Position matters: several
   rules win by source order over equal-specificity site rules
   (.chapter-media:hover img) and over the site's reduced-motion block.

   (a) clip-path unmask on .chapter-media  — IO-armed, JS adds classes
   (b) parallax drift on .chapter-media img — CSS view() timeline only
   (c) slow drift on .chapter-index numeral — CSS view() timeline only
   (d) ice underline draw-in on each .stat  — rides the existing .in

   Zero HTML changes. All JS-added classes are fx4-chapter-*.
   ════════════════════════════════════════════════════════════════ */

/* ── (a) media unmask: bottom-up wipe, once.
   The masked class is added by JS only, so without JS nothing is ever
   clipped. The transition list re-declares the [data-reveal]
   opacity/transform pair because this rule takes over the `transition`
   shorthand for the figure — keep the 0.85s / 90ms values in step with
   the [data-reveal] block if that ever changes. ── */
.chapter .chapter-media.fx4-chapter-masked {
  clip-path: inset(100% 0 0 0);
  transition:
    opacity 0.85s var(--ease),
    transform 0.85s var(--ease),
    clip-path 1s var(--ease);
  transition-delay: calc(var(--i, 0) * 90ms);
}
.chapter .chapter-media.fx4-chapter-unmasked {
  clip-path: inset(0 0 0 0);
}

/* ── (d) stat underline: 1px ice rule that draws in left→right.
   Visible at rest (safe with no JS / no IO). JS arms the collapsed
   state; the site's own reveal observer adds .in to each .stat, which
   draws the line back in, loosely in step with the 1.1s count-up.
   --i is the site's own stagger index, inherited by the pseudo. ── */
.chapter .stat::after {
  content: "";
  display: block;
  height: 1px;
  margin-top: 0.7rem;
  background: color-mix(in srgb, var(--ice) 65%, transparent);
  transform: scaleX(1);
  transform-origin: 0 50%;
  transition: transform 1.05s var(--ease);
  transition-delay: calc(var(--i, 0) * 90ms + 120ms);
}
.chapter .fx4-chapter-stats-armed .stat::after { transform: scaleX(0); }
.chapter .fx4-chapter-stats-armed .stat.in::after { transform: scaleX(1); }

/* ── (b)+(c) scroll-driven drifts.
   Registered custom properties let the parallax (a scroll-driven
   animation writing --fx4-chapter-drift) and the hover zoom (a
   transition on --fx4-chapter-zoom) share one transform declaration
   without overwriting each other. These registrations are inert in
   browsers that never enter the @supports block below. ── */
@property --fx4-chapter-drift {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 0%;
}
@property --fx4-chapter-zoom {
  syntax: "<number>";
  inherits: false;
  initial-value: 1;
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {

    /* (b) image drift: -3.5% → 3.5% of the image's own height across
       its view range, inside a 1.08 zoom (4% headroom per edge), so an
       edge can never peek through even with subpixel rounding.
       The `transition` here deliberately replaces the site's
       `transition: transform 0.9s` — drift updates must land instantly
       while only the hover zoom eases. Declaration order matters:
       animation-timeline must come AFTER the animation shorthand. */
    .chapter .chapter-media img {
      --fx4-chapter-zoom: 1.08;
      transform: translate3d(0, var(--fx4-chapter-drift), 0)
                 scale(var(--fx4-chapter-zoom));
      transition: --fx4-chapter-zoom 0.9s var(--ease);
      animation: fx4-chapter-drift linear both;
      animation-timeline: view();
      will-change: transform;
    }
    /* preserves the site's hover feel: 1.08 → 1.11 ≈ the old 1 → 1.03 */
    .chapter .chapter-media:hover img { --fx4-chapter-zoom: 1.11; }

    /* (c) ghost numeral lags the content slightly: ±6% of its own
       height across its view range. Pure transform keyframes, so the
       browser can keep this off the main thread. */
    .chapter .chapter-index {
      animation: fx4-chapter-index-drift linear both;
      animation-timeline: view();
    }

    @keyframes fx4-chapter-drift {
      from { --fx4-chapter-drift: -3.5%; }
      to   { --fx4-chapter-drift: 3.5%; }
    }
    @keyframes fx4-chapter-index-drift {
      from { transform: translate3d(0, -6%, 0); }
      to   { transform: translate3d(0, 6%, 0); }
    }
  }
}

/* ── reduced motion: everything collapses to the resting state.
   JS also refuses to arm anything under reduced motion; this is the
   belt to that suspender (covers mid-session preference flips).
   Selector specificity matches the armed rules above (3 classes) and
   wins by source order. ── */
@media (prefers-reduced-motion: reduce) {
  .chapter .chapter-media.fx4-chapter-masked {
    clip-path: none;
    transition: none;
  }
  .chapter .stats .stat::after {
    transform: none;
    transition: none;
  }
}

/* ════════════════════════════════════════════════════════════════
   fx5-marquee — marquee hover pause + scroll-velocity skew
   Appended layer. Zero HTML changes.
   JS contract: sets --fx5-marquee-skew (deg) inline on .marquee and
   toggles .fx5-marquee-live on .marquee while its rAF loop is awake.
   If JS never runs, everything below renders the resting state.
   ════════════════════════════════════════════════════════════════ */

/* (a) hover pause — pointer devices only, so a stray touch-hover can
   never leave the marquee stuck paused on phones. */
@media (hover: hover) {
  .marquee:hover .marquee-track {
    animation-play-state: paused;
  }
}

/* (b) velocity skew — applied to the track's spans, not .marquee, so the
   strip's top/bottom border rules stay perfectly straight. The spans are
   flex items of .marquee-track (inline-flex), i.e. transformable boxes.
   Both spans get the identical skew, so the loop seam stays seamless.
   var() fallback = 0deg = resting state when JS is absent or asleep. */
.marquee-track > span {
  transform: skewX(var(--fx5-marquee-skew, 0deg));
}

/* Compositor hint only while the loop is actually live — never a
   permanent will-change layer at rest. */
.fx5-marquee-live .marquee-track > span {
  will-change: transform;
}

/* Collapse completely under reduced motion (base stylesheet already
   kills the marquee keyframes; this kills the skew too). */
@media (prefers-reduced-motion: reduce) {
  .marquee-track > span {
    transform: none;
  }
  .fx5-marquee-live .marquee-track > span {
    will-change: auto;
  }
}

/* ════════════════════════════════════════════════════════════════
   fx6-micro — Micro-interaction pack (Layer 6)
   Append to the END of css/style.css (order matters: these rules
   deliberately override earlier :active / transition declarations).
   (a) smooth FAQ open/close  (b) pointer light on .chapter-media
   (c) unified press physics  (d) .tags li hover ink fill
   Every effect collapses to the site's existing behavior when JS is
   absent, the pointer can't hover, or reduced motion is preferred.
   ════════════════════════════════════════════════════════════════ */

/* ── (a) FAQ smooth open/close ─────────────────────────────────────
   .fx6-micro-faq is added by JS only; without JS none of this
   applies and <details> keeps its untouched native behavior, so the
   answers can never be stuck hidden. While animating closed, the
   [open] attribute stays on until the transition ends (JS removes
   it), which is why the icon is driven off .fx6-micro-open below. */

.fx6-micro-faq .faq-body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.45s var(--ease, cubic-bezier(0.16, 1, 0.3, 1));
}
.fx6-micro-faq .faq-body > p {
  min-height: 0;
  overflow: hidden;
  opacity: 0;
  transition: opacity 0.35s var(--ease, cubic-bezier(0.16, 1, 0.3, 1));
}
.fx6-micro-faq.fx6-micro-open .faq-body { grid-template-rows: 1fr; }
.fx6-micro-faq.fx6-micro-open .faq-body > p {
  opacity: 1;
  transition-delay: 0.08s;
}
/* icon follows the animation state, not the (lagging) open attr */
.fx6-micro-faq[open]:not(.fx6-micro-open) .faq-icon { transform: rotate(0deg); }

/* ── (b) pointer-following light on .chapter-media only ────────────
   .fx6-micro-glow is added by JS only on hover-capable fine-pointer
   devices. The gradient itself never changes: a fixed 520px disc is
   moved with transform via CSS vars (compositor-only), clipped by
   the figure's existing overflow:hidden. Max 10% white alpha. */

@media (hover: hover) and (pointer: fine) {
  .fx6-micro-glow { position: relative; }
  .fx6-micro-glow::after {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 520px;
    height: 520px;
    border-radius: 50%;
    pointer-events: none;
    opacity: 0;
    background: radial-gradient(closest-side, rgba(255, 255, 255, 0.10), rgba(255, 255, 255, 0) 100%);
    transform: translate(var(--fx6-micro-x, 50%), var(--fx6-micro-y, 50%)) translate(-50%, -50%);
    transition: opacity 0.6s var(--ease, cubic-bezier(0.16, 1, 0.3, 1));
    will-change: transform, opacity;
  }
  .fx6-micro-glow:hover::after { opacity: 1; }
}

/* ── (c) press physics: consistent :active scale(0.98) ─────────────
   Same specificity as the base rules; appended later, so these win.
   !important on .btn-primary is required to beat the magnetic CTA's
   inline transform set by main.js — pressing settles the button,
   releasing hands it back to the magnet. */

.btn-primary:active { transform: scale(0.98) !important; }
.btn-light:active { transform: translateY(0) scale(0.98); }
.quote-form button:active { transform: translateY(0) scale(0.98); }
.copy-btn {
  transition: background 0.3s var(--ease, cubic-bezier(0.16, 1, 0.3, 1)),
              color 0.3s var(--ease, cubic-bezier(0.16, 1, 0.3, 1)),
              transform 0.2s var(--ease, cubic-bezier(0.16, 1, 0.3, 1));
}
.copy-btn:active { transform: scale(0.98); }
.to-top:active { transform: translateY(0) scale(0.98); }

/* ── (d) .tags li hover: ink fill, paper text, 200ms ────────────────
   Paint-only (background-color/color/border-color on a tiny pill,
   same idiom as the existing .copy-btn hover). Hover-capable
   pointers only, so touch never gets a sticky filled pill. */

.tags li {
  transition: background-color 0.2s var(--ease, cubic-bezier(0.16, 1, 0.3, 1)),
              color 0.2s var(--ease, cubic-bezier(0.16, 1, 0.3, 1)),
              border-color 0.2s var(--ease, cubic-bezier(0.16, 1, 0.3, 1));
}
@media (hover: hover) {
  .tags li:hover {
    background-color: var(--ink);
    color: var(--paper);
    border-color: var(--ink);
  }
}

/* ── reduced motion: the whole layer stands down ──────────────────
   FAQ: rows pinned open (content shows instantly whenever the
   details is open; JS also skips interception, so toggling is the
   instant native one). Light: gone. Press/tag transitions: none;
   the two pills the base site didn't scale stop scaling too. */

@media (prefers-reduced-motion: reduce) {
  .fx6-micro-faq .faq-body,
  .fx6-micro-faq .faq-body > p {
    transition: none;
    grid-template-rows: 1fr;
    opacity: 1;
  }
  .fx6-micro-glow::after { display: none; }
  .copy-btn, .tags li { transition: none; }
  .copy-btn:active, .to-top:active { transform: none; }
}

/* ════════════════════════════════════════════════════════════════
   fx7-footer — LAYER 7: footer wordmark ink-fill + to-top progress ring
   Pure CSS scroll-driven animations. No JS. No HTML changes.

   (a) The outlined .footer-word fills with ink as the footer scrolls
       into view: a background-clip:text gradient whose background-size
       is scrubbed by a named view() timeline on .footer (the taller
       subject gives a longer, calmer sweep than the word's own entry).
   (b) .to-top gets a hairline conic arc over its existing 1px border,
       showing overall page progress via a scroll(root) timeline that
       scrubs a registered custom property.

   Collapse behavior:
   - No scroll-driven-animation support → neither @supports block
     applies → static outlined word + plain circle (the site's existing
     resting design, which is the fallback this brief specifies).
   - prefers-reduced-motion: reduce → the entire layer is inside a
     no-preference media block, plus an explicit reduce override below
     as belt and braces. Nothing runs, nothing is hidden.
   - JS failure is irrelevant: this layer ships no JS. The word is
     never invisible — at 0% fill the -webkit-text-stroke outline
     still paints, exactly as the site does today.

   NOTE ON ORDER: the `animation` shorthand resets animation-timeline
   and animation-range, so those two longhands MUST stay declared
   after the shorthand. Do not reorder.
   ════════════════════════════════════════════════════════════════ */

/* Registered so the conic arc angle interpolates as a percentage.
   Browsers without @property ignore this rule; they also lack scroll
   timelines, so the ring never activates there anyway. */
@property --fx7-footer-ring-p {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 0%;
}

@media (prefers-reduced-motion: no-preference) {

  /* ── (a) footer wordmark: outline → ink fill ─────────────────── */

  @supports (animation-timeline: view()) {
    .footer {
      /* Named view timeline on the whole footer. Visible to
         descendants, so .footer-word can consume it directly —
         no timeline-scope needed. */
      view-timeline: --fx7-footer-word-tl block;
    }

    .footer-word {
      /* Soft 2rem leading edge so the sweep reads as ink bleeding
         in, not a hard wipe. Gradient percentages are relative to
         the animated background-size box, so the soft edge travels
         with the sweep and clears the glyphs at the final size. */
      background-image: linear-gradient(
        90deg,
        var(--ink) calc(100% - 2rem),
        rgba(22, 21, 15, 0) 100%
      );
      background-repeat: no-repeat;
      background-position: 0 0;
      /* Base = final resting state (fully filled). If the timeline
         were ever inactive, the word rests filled rather than in a
         half state; while active, fill-mode:both scrubs from 0%. */
      background-size: 125% 100%;
      -webkit-background-clip: text;
      background-clip: text;
      /* color stays transparent and -webkit-text-stroke stays on
         from the base styles: the outline remains visible under the
         fill at every point of the sweep. */
      animation: fx7-footer-word-fill linear both;
      animation-timeline: --fx7-footer-word-tl;
      /* entry range of the footer: starts as the footer edge rises
         past the viewport bottom, completes at 85% so the fill is
         done comfortably before absolute max scroll (no dead zone
         from rubber-banding or a short final page segment). */
      animation-range: entry 5% entry 85%;
    }

    @keyframes fx7-footer-word-fill {
      from { background-size: 0% 100%; }
      to   { background-size: 125% 100%; }
    }
  }

  /* ── (b) to-top: conic page-progress arc on the circle ───────── */

  @supports (animation-timeline: scroll()) {
    .to-top { position: relative; }

    .to-top::after {
      content: "";
      position: absolute;
      inset: -1px;                      /* sit exactly over the 1px border */
      border-radius: 999px;
      /* currentColor: ink-2 arc over the light border track at rest,
         and it flips to paper automatically on the button's existing
         hover invert — no extra hover rules needed. */
      background: conic-gradient(currentColor var(--fx7-footer-ring-p), transparent 0);
      /* Hollow the disc into a ~1.5px ring with a soft inner edge.
         The existing 1px border underneath acts as the track. */
      -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), #000 calc(100% - 1px));
      mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), #000 calc(100% - 1px));
      pointer-events: none;
      animation: fx7-footer-ring-progress linear both;
      animation-timeline: scroll(root);
    }

    @keyframes fx7-footer-ring-progress {
      from { --fx7-footer-ring-p: 0%; }
      to   { --fx7-footer-ring-p: 100%; }
    }
  }
}

/* Belt and braces: even if the no-preference wrapper above is ever
   unwrapped in a refactor, reduce still kills the whole layer. */
@media (prefers-reduced-motion: reduce) {
  .footer-word {
    animation: none;
    background-image: none;
  }
  .to-top::after {
    content: none;
    animation: none;
  }
}


/* ═══════════════ CONCEPT WORK GRID ═══════════════ */

.concept-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1.25rem, 2.5vw, 2rem);
}
.concept-card { display: flex; flex-direction: column; }
.concept-media {
  display: block;
  overflow: hidden;
  border-radius: 6px;
  background: #dfded7;
  aspect-ratio: 4 / 3;
}
.concept-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.9s var(--ease);
}
.concept-media:hover img,
.concept-media:focus-visible img { transform: scale(1.045); }
.concept-meta { padding-top: 0.9rem; }
.concept-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
}
.concept-line h3 {
  font-size: clamp(1.1rem, 1.7vw, 1.4rem);
  font-weight: 560;
  letter-spacing: -0.01em;
}
.concept-tag {
  flex-shrink: 0;
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.2rem 0.55rem;
}
.concept-type {
  margin-top: 0.35rem;
  color: var(--ink-2);
  font-size: 0.85rem;
}
.concept-meta .link-arrow { margin-top: 0.9rem; font-size: 0.85rem; }

@media (max-width: 900px) {
  .concept-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .concept-grid { grid-template-columns: 1fr; }
}

/* ════════════════════════════════════════════════════════════════
   fx5-parallax — concept-card thumbnail drift.
   Same zero-JS pattern as the Atlas chapter's fx4-chapter-drift:
   a CSS view() scroll-timeline animation, gated behind @supports and
   reduced-motion, never touching JS or the main thread. Each card
   gets a couple of extra % of headroom baked into a 1.06 zoom so the
   drift can never reveal an edge. ── */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .concept-media img {
      transform: translate3d(0, var(--fx5-drift, 0%), 0) scale(1.06);
      animation: fx5-thumb-drift linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
    }
    .concept-media:hover img,
    .concept-media:focus-visible img {
      transform: translate3d(0, var(--fx5-drift, 0%), 0) scale(1.1);
    }
    @keyframes fx5-thumb-drift {
      from { --fx5-drift: -2.5%; }
      to   { --fx5-drift: 2.5%; }
    }
  }
}

/* ════════════════════════════════════════════════════════════════
   fx7-cursor — custom pointer ring + dot.
   JS-armed only (adds html.has-cursor); with no JS or on touch/coarse
   pointers/reduced-motion the native cursor is untouched. A small ink
   dot leads, a larger ring trails via rAF-lerp (see fx7Cursor in
   main.js), both growing over interactive targets. Kept restrained —
   thin ring, no fill — to match the site's quiet ink/paper palette
   rather than importing a heavier "agency" cursor. ── */
.fx7-cursor-dot,
.fx7-cursor-ring {
  position: fixed;
  top: 0; left: 0;
  pointer-events: none;
  z-index: 999;
  border-radius: 50%;
  opacity: 0;
  transition: opacity 0.25s var(--ease), width 0.28s var(--ease), height 0.28s var(--ease), background 0.28s var(--ease), border-color 0.28s var(--ease);
  will-change: transform;
}
.fx7-cursor-dot {
  width: 6px; height: 6px;
  margin: -3px 0 0 -3px;
  background: var(--ink);
}
.fx7-cursor-ring {
  width: 34px; height: 34px;
  margin: -17px 0 0 -17px;
  border: 1px solid var(--ink);
  background: transparent;
}
html.has-cursor.fx7-cursor-on .fx7-cursor-dot,
html.has-cursor.fx7-cursor-on .fx7-cursor-ring { opacity: 1; }
html.has-cursor.fx7-cursor-hot .fx7-cursor-dot { width: 0; height: 0; margin: 0; opacity: 0; }
html.has-cursor.fx7-cursor-hot .fx7-cursor-ring {
  width: 54px; height: 54px;
  margin: -27px 0 0 -27px;
  background: color-mix(in srgb, var(--ink) 8%, transparent);
}
html.has-cursor.fx7-cursor-press .fx7-cursor-ring { width: 28px; height: 28px; margin: -14px 0 0 -14px; }
/* Hide the native cursor only once JS has proven a real ring is live,
   so a script failure never leaves a visitor pointer-less. */
html.has-cursor a, html.has-cursor button, html.has-cursor .btn-primary,
html.has-cursor { cursor: none; }
html.has-cursor input, html.has-cursor textarea, html.has-cursor select { cursor: text; }
@media (hover: none), (pointer: coarse) {
  html.has-cursor, html.has-cursor a, html.has-cursor button { cursor: auto; }
  .fx7-cursor-dot, .fx7-cursor-ring { display: none; }
}
