/* ============================================
   The stage — one canvas behind the whole page.

   Phase 1 of docs/immersive-plan.md §0: replace the section-by-section
   background lottery with a fixed two-plane stage. Act I (ink) runs from the
   hero through the showreel; one scrubbed crossfade in js/stage.js brings the
   paper up as the showreel exits, and the lights never go back down. The dark
   close (Act III) arrives with the news/FAQ/footer redesign, not before —
   those sections keep their own solid backgrounds until they are restyled to
   live on it.

   Everything here is inert until js/stage.js adds `js-stage` to <html>, and it
   only does that when motion is allowed, GSAP is present, and this stylesheet
   actually loaded (it checks the stage is position:fixed — a stale cached CSS
   file must not strip the sections' own backgrounds). No-JS and
   reduced-motion visitors keep today's solid sections.

   --color-paper is the current work-section warm tone on purpose. The closed
   set in css/variables.css defines the future --paper ground, but its own
   contract says the repaint is atomic: until every section's furniture moves
   to those tokens at once, the stage matches the furniture that exists. When
   the repaint lands, this token collapses into --paper and this comment goes.
   ============================================ */

:root {
  --color-paper: #f7f5f0;
}

.page-stage {
  display: none;
}

.js-stage .page-stage {
  display: block;
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

.page-stage__paper,
.page-stage__ink {
  position: absolute;
  inset: 0;
}

.page-stage__paper {
  background: var(--color-paper);
}

.page-stage__ink {
  background: var(--color-black);
}

/* The one sanctioned grain placement: a static feTurbulence tile on the fixed
   stage, never on a scrolling container. Barely-there by design — it reads as
   paper tooth, not as an effect. */
.page-stage::after {
  content: '';
  position: absolute;
  inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='128' height='128'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='128' height='128' filter='url(%23n)'/%3E%3C/svg%3E");
  opacity: 0.03;
}

/* Act I and Act II sections hand their backgrounds to the stage. The dark act
   (hero, showreel) sits on the ink plane; everything after the crossfade sits
   on paper. News, FAQ, footer and the concierge card keep their own paint
   until the dark close is designed. */
html.js-stage .hero,
html.js-stage .showreel,
html.js-stage .proof,
html.js-stage .capabilities,
html.js-stage .process,
html.js-stage .work,
html.js-stage .about,
html.js-stage .ai-automation {
  background: transparent;
}

/* ============================================
   The arrival (immersive-plan §3).

   Start states live behind `js-arrival`, which only the pre-paint bootstrap
   adds; js/arrival.js conducts the shot and adds `js-arrival-done` when it
   lands. Both classes stay on <html> afterwards — the done rules are written
   after the start rules on purpose, so at equal specificity they win, and a
   later EN/ES re-split of the headline renders instantly instead of replaying
   the legacy wordFadeIn.
   ============================================ */

/* The hairline the shot draws first. Always present, subtle enough to live
   under both the transparent and the frosted header states. */
.header__rule {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: linear-gradient(90deg, rgba(232, 200, 64, 0.55), rgba(232, 200, 64, 0) 70%);
  opacity: 0.5;
  transform-origin: left;
  pointer-events: none;
}

.js-arrival:not(.js-arrival-done) .header__rule {
  transform: scaleX(0);
}

/* The headline resolves from 1px gold stroke — the same weight and gold as
   the terrain's contour lines — to filled white. The timeline adds .word--in
   per word; the transition does the resolving. */
.js-arrival .hero__title .word {
  animation: none;
  opacity: 0;
  transform: translateY(0.35em);
  color: transparent;
  -webkit-text-stroke: 1px rgba(232, 200, 64, 0.9);
  transition: opacity 0.45s var(--ease-out),
              transform 0.45s var(--ease-out),
              color 0.45s var(--ease-out),
              -webkit-text-stroke-color 0.45s var(--ease-out);
}

.js-arrival .hero__title .word--in {
  opacity: 1;
  transform: translateY(0);
  color: #ffffff;
  -webkit-text-stroke-color: transparent;
}

.js-arrival:not(.js-arrival-done) .hero__eyebrow {
  opacity: 0;
}

.js-arrival:not(.js-arrival-done) .hero__lede,
.js-arrival:not(.js-arrival-done) .hero__actions {
  opacity: 0;
  transform: translateY(20px);
}

/* Done state: everything visible, nothing replays. */
.js-arrival-done .hero__title .word {
  animation: none;
  opacity: 1;
  transform: none;
  color: #ffffff;
  -webkit-text-stroke: 0 transparent;
}

.js-arrival-done .hero__lede,
.js-arrival-done .hero__actions {
  opacity: 1;
  transform: none;
}

/* ============================================
   De-slop: the eyebrow stops being a pill.

   The glass pill over a dark hero is the single most recognisable
   AI-template opener. A bare uppercase kicker with a short gold dash says
   the same thing with typography instead of chrome.
   ============================================ */
.hero__eyebrow {
  border: 0;
  border-radius: 0;
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  padding: 0;
  color: rgba(255, 255, 255, 0.72);
  letter-spacing: 0.14em;
}

.hero__eyebrow::before {
  content: '';
  display: inline-block;
  width: 24px;
  height: 1px;
  background: var(--color-primary);
  margin-right: 12px;
  vertical-align: middle;
}

/* ============================================
   The proof ledger (immersive-plan §4).

   Hairlines draw themselves, values rise behind them, staggered per cell via
   --i set in the markup. Everything is scoped to `.js-reveal`, the same class
   the reveal bootstrap owns: no JS, or reduced motion, means solid borders
   and visible values — today's static strip.
   ============================================ */

.js-reveal .proof__grid {
  position: relative;
  border-top-color: transparent;
}

/* The first rule: gold at its origin fading into ink — the hero's horizon
   handed to the ledger. Draws on its own trigger, so it works whether or not
   the terrain rewrite is on screen above it. */
.js-reveal .proof__grid::before {
  content: '';
  position: absolute;
  top: -1px;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg, rgba(232, 200, 64, 0.7), rgba(10, 10, 10, 0.15) 35%);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.9s var(--ease-out);
}

.js-reveal .proof__grid.revealed::before {
  transform: scaleX(1);
}

.js-reveal .proof__item {
  position: relative;
  border-right-color: transparent;
}

.js-reveal .proof__item::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 1px;
  background: rgba(10, 10, 10, 0.1);
  transform: scaleY(0);
  transform-origin: top;
  transition: transform 0.7s var(--ease-out);
  transition-delay: calc(var(--i, 0) * 90ms);
}

.js-reveal .proof__grid.revealed .proof__item::after {
  transform: scaleY(1);
}

.js-reveal .proof__item:last-child::after {
  display: none;
}

.js-reveal .proof__item span,
.js-reveal .proof__item strong {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.6s var(--ease-out), transform 0.6s var(--ease-out);
  transition-delay: calc(var(--i, 0) * 90ms + 180ms);
}

.js-reveal .proof__grid.revealed .proof__item span,
.js-reveal .proof__grid.revealed .proof__item strong {
  opacity: 1;
  transform: none;
}

/* The one real figure on the strip. Tabular so it cannot wobble, linked to
   the work ledger it corroborates. Aged gold on hover — raw gold fails
   contrast on paper (1.36:1); #7A5C12 is the same hue at 5.15:1. */
.proof__count {
  color: inherit;
  text-decoration: none;
}

.proof__digit {
  display: block;
  font-size: clamp(3rem, 5vw, 4.5rem);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  letter-spacing: -0.03em;
  transition: color 0.25s var(--ease-out);
}

.proof__count:hover .proof__digit,
.proof__count:focus-visible .proof__digit {
  color: #7A5C12;
}

/* ============================================
   The line that draws the method (immersive-plan §7).

   js/daylight.js adds `js-daylight` only when GSAP + ScrollTrigger are live
   and motion is allowed. Under it, the per-step border hands off to one SVG
   line laid over the grid, and the step numbers hollow to a navy outline
   that inks solid as the line reaches them. Without it: solid borders,
   solid numbers.
   ============================================ */

.process__grid {
  position: relative;
}

.process__line {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 48px;
  overflow: visible;
  pointer-events: none;
  display: none;
}

.js-daylight .process__line {
  display: block;
}

.js-daylight .process__step {
  border-top-color: transparent;
}

.process__line-trail,
.process__line-tip {
  fill: none;
  vector-effect: non-scaling-stroke;
}

.process__line-trail {
  stroke: var(--color-navy);
  stroke-width: 2;
}

.process__line-tip {
  stroke: var(--color-primary);
  stroke-width: 2;
}

.js-daylight .process__step span {
  font-size: clamp(2.2rem, 3vw, 3rem);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  line-height: 1;
  color: transparent;
  -webkit-text-stroke: 1px var(--color-navy);
  transition: color 0.35s var(--ease-out), -webkit-text-stroke-color 0.35s var(--ease-out);
}

.js-daylight .process__step.is-passed span {
  color: var(--color-navy);
  -webkit-text-stroke-color: transparent;
}

.process__step-meta {
  display: block;
  margin-top: var(--space-4);
  color: var(--color-gray-500);
  font-size: 11px;
  font-weight: var(--weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* ============================================
   The workflow trace (immersive-plan §10).

   Defaults below are the FINISHED drawing — path drawn, nodes full, labels
   active — so no-JS and reduced-motion visitors see the complete diagram.
   js/daylight.js hides the parts and redraws them once on entry.
   ============================================ */

.ai-automation__trace {
  display: grid;
  grid-template-columns: 48px 1fr;
  align-items: stretch;
  min-height: 220px;
}

.ai-trace__svg {
  width: 48px;
  height: 220px;
  overflow: visible;
}

.ai-trace__rail {
  stroke: rgba(42, 61, 102, 0.25);
  stroke-width: 1;
}

.ai-trace__path {
  stroke: var(--color-primary);
  stroke-width: 1.5;
  fill: none;
}

.ai-trace__node {
  fill: var(--color-primary);
  transform-box: fill-box;
  transform-origin: center;
}

.ai-trace__labels {
  display: grid;
  grid-template-rows: repeat(4, 1fr);
  align-items: center;
}

.ai-trace__label {
  color: rgba(255, 255, 255, 0.85);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  transition: color 0.3s var(--ease-out), transform 0.3s var(--ease-out);
}

.js-daylight .ai-trace__label.is-pending {
  color: rgba(255, 255, 255, 0.35);
}

/* ============================================
   Capabilities, machined (immersive-plan §6).

   The two-layer visual for the directional wipe, and the index column that
   turns the accordion from a stack of rows into a table of contents.
   ============================================ */

/* The rounded card moves from the <img> to this stack, which is also the
   positioning context for the hidden layer — so the wipe always aligns to
   the visible image, never to the sticky wrapper around it. */
.cap-stack {
  position: relative;
  width: 100%;
  max-width: 460px;
  border-radius: 28px;
  overflow: hidden;
  box-shadow: 0 30px 70px rgba(10, 10, 10, 0.12);
}

.cap-layer {
  display: block;
}

.cap-layer + .cap-layer {
  position: absolute;
  inset: 0;
}

.cap-layer img {
  width: 100%;
  height: auto;
  display: block;
  max-width: none;
  border-radius: 0;
  box-shadow: none;
}

.cap-layer + .cap-layer img {
  height: 100%;
  object-fit: cover;
}

/* The index column: tabular, quiet, machined. Scoped to the capabilities
   list — FAQ shares the accordion mechanism but not the index. */
#capabilities-list .accordion-header {
  display: grid;
  grid-template-columns: 2.6em 1fr auto;
  align-items: center;
  text-align: left;
}

.accordion-index {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--color-gray-400);
  letter-spacing: 0.04em;
  transition: color 0.25s var(--ease-out);
}

.accordion-item.active .accordion-index {
  color: #7A5C12;
}

/* ============================================
   The dark close (immersive-plan §11).

   News, FAQ and footer become one continuous closing act on a single navy —
   the mismatched #11151f / #17233f pair dies here — and the footer deepens
   to the page's opening black at the bottom bar, so the site closes the way
   it opened. Gold lives on dark, where it has its strongest legal contrast.
   ============================================ */

:root {
  --color-dark-panel: #17233f;
}

.news {
  background: var(--color-dark-panel);
}

.faq {
  background: var(--color-dark-panel);
}

.footer {
  background: linear-gradient(180deg, var(--color-dark-panel) 0%, #0a0a0a 100%);
  color: rgba(255, 255, 255, 0.82);
}

.footer__cta-label {
  color: rgba(255, 255, 255, 0.55);
}

.footer__contact-label,
.footer__column-title,
.footer__office-city {
  color: rgba(255, 255, 255, 0.55);
}

.footer__contact-value,
.footer__office-address {
  color: rgba(255, 255, 255, 0.88);
}

.footer__nav-link,
.footer__social-link,
.footer__legal-link {
  color: rgba(255, 255, 255, 0.72);
}

.footer__nav-link:hover,
.footer__social-link:hover,
.footer__legal-link:hover {
  color: var(--color-primary);
}

.footer__newsletter-text,
.footer__copyright {
  color: rgba(255, 255, 255, 0.6);
}

.footer__newsletter-input {
  background: rgba(255, 255, 255, 0.07);
  border-color: rgba(255, 255, 255, 0.18);
  color: #ffffff;
}

.footer__newsletter-input::placeholder {
  color: rgba(255, 255, 255, 0.45);
}

.footer__newsletter-btn {
  background: var(--color-primary);
  color: #0a0a0a;
}

.footer__newsletter-btn:hover {
  background: var(--color-primary-light);
}

.footer__bottom {
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}

/* ---- The finale: the CTA fills with gold over its other-language ghost ---- */

.footer__cta-stack {
  position: relative;
  display: block;
  width: max-content;
  max-width: 100%;
}

.footer__cta-title--ghost {
  position: absolute;
  top: 0;
  left: 0;
  color: transparent;
  -webkit-text-stroke: 1px rgba(232, 200, 64, 0.4);
  pointer-events: none;
  user-select: none;
  white-space: nowrap;
}

.footer__cta-title--fill {
  position: relative;
  color: var(--color-primary);
  /* Default is fully filled: no-JS and reduced-motion get the finished
     composition. js/daylight.js drives --cta-clip 100% -> 0% on scroll. */
  clip-path: inset(-0.2em calc(var(--cta-clip, 0%)) -0.2em 0);
}

.footer__cta-title--fill:hover {
  color: var(--color-primary-light);
}

/* ============================================
   Wayfinding (immersive-plan §12), and the last de-pill pass.
   ============================================ */

/* Every section kicker drops the pill chrome for the same grammar as the
   hero eyebrow: bare uppercase with a short gold dash. One kicker language
   across the whole page. */
.section-kicker {
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  letter-spacing: 0.14em;
}

.section-kicker::before {
  content: '';
  display: inline-block;
  width: 24px;
  height: 1px;
  background: var(--color-primary);
  margin-right: 12px;
  vertical-align: middle;
}

/* The sliding underline: one indicator, moved by js/daylight.js to the nav
   item whose section is on screen. Hidden until the spy has something to
   point at, and entirely absent without JS. */
.header__nav {
  position: relative;
}

.header__nav-underline {
  position: absolute;
  bottom: 2px;
  left: 0;
  width: 40px;
  height: 2px;
  background: var(--color-primary);
  border-radius: 1px;
  transform-origin: left;
  opacity: 0;
  pointer-events: none;
}

/* The timecode appears only once the video's real metadata exists — no
   instrument on this page reads a value that is not true. */
.showreel__timecode {
  visibility: hidden;
}

/* The redaction bars (immersive-plan §8): ink with a gold edge, drawn in when
   the ledger reveals. Removing one is how a case announces itself. */
.work__redaction {
  display: inline-block;
  width: min(14ch, 100%);
  height: 0.9em;
  background: #14120E;
  border-bottom: 2px solid var(--color-primary);
  vertical-align: baseline;
}

.js-reveal .work__redaction {
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.7s var(--ease-out) 0.25s;
}

.js-reveal .work__ledger.revealed .work__redaction {
  transform: scaleX(1);
}

/* ============================================
   Tissue between pages (audit gap 7): cross-document View Transitions.

   Pure CSS progressive enhancement — Chrome/Edge 126+ and Safari 18.2+ get a
   soft crossfade instead of a cold cut when navigating to contact and back;
   Firefox falls back to instant navigation. Works across the vanilla/React
   boundary because no shared runtime is involved: both documents just opt in.
   The contact page carries the same block inline (contact-src/index.html).
   ============================================ */

@view-transition {
  navigation: auto;
}

::view-transition-old(root) {
  animation-duration: 0.22s;
}

::view-transition-new(root) {
  animation-duration: 0.28s;
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

/* The persistent terrain canvas: painted on top of the planes when the GPU
   gate passes; the planes remain the fallback underneath it. */
.page-stage__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* The stage crossfades as its own view-transition layer, apart from the
   page group. The old document's last painted frame — field included —
   dissolves into the new one's first, and the WebGL context reboot that
   happens between them (/js/terrain.f65573ae16.js handoff) is invisible under the
   snapshot. Slightly longer than root so the ground settles after the
   content. The reduced-motion block above already cuts every group. */
.page-stage {
  view-transition-name: mk-stage;
}

::view-transition-old(mk-stage),
::view-transition-new(mk-stage) {
  animation-duration: 0.34s;
}

/* ============================================
   The gold cursor and the sound toggle (audit gap 10, the garnish).
   ============================================ */

.mk-cursor {
  position: fixed;
  top: -14px;
  left: -14px;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 1.5px solid rgba(232, 200, 64, 0.0);
  pointer-events: none;
  z-index: 2000;
  opacity: 0;
  transition: opacity 0.3s var(--ease-out),
              border-color 0.25s var(--ease-out),
              transform 0.25s var(--ease-out);
}

.mk-cursor::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 5px;
  height: 5px;
  margin: -2.5px 0 0 -2.5px;
  border-radius: 50%;
  background: var(--color-primary);
}

.mk-cursor--alive { opacity: 0.9; }

.mk-cursor--open {
  border-color: rgba(232, 200, 64, 0.7);
  transform: scale(1.4);
}

.mk-cursor--press { transform: scale(0.8); }

/* The sound toggle: three quiet bars that come alive when the acts do. */
.sound-toggle {
  display: inline-flex;
  align-items: flex-end;
  gap: 2.5px;
  height: 30px;
  padding: 0 6px 9px;
  background: transparent;
  border: 0;
  cursor: pointer;
}

.sound-toggle__bar {
  width: 2.5px;
  height: 6px;
  border-radius: 1px;
  background: currentColor;
  opacity: 0.55;
  transition: height 0.25s var(--ease-out), opacity 0.25s var(--ease-out);
}

.sound-toggle:hover .sound-toggle__bar { opacity: 0.9; }

.sound-toggle.is-on .sound-toggle__bar {
  background: var(--color-primary);
  opacity: 1;
  animation: mk-sound-bar 1.1s ease-in-out infinite;
}

.sound-toggle.is-on .sound-toggle__bar:nth-child(2) { animation-delay: 0.18s; }
.sound-toggle.is-on .sound-toggle__bar:nth-child(3) { animation-delay: 0.36s; }

@keyframes mk-sound-bar {
  0%, 100% { height: 5px; }
  50% { height: 12px; }
}

@media (prefers-reduced-motion: reduce) {
  .sound-toggle.is-on .sound-toggle__bar { animation: none; height: 10px; }
  .mk-cursor { display: none; }
}

/* The current page states itself without JavaScript — the animated underline
   is the homepage scroll-spy and cannot speak for a subpage. */
.header__nav-link[aria-current="page"] {
  color: #7A5C12;
  font-weight: var(--weight-semibold);
}

/* On the dark hero the frosted header has not engaged, so the brand gold is
   both legal and correct there. */
.header:not(.scrolled) .header__nav-link[aria-current="page"] {
  color: var(--color-primary);
}
