*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  /* Black, not bone. Every stage paints the aperture's black and Contrast paints its own
     ivory on the SECTION, so the body is only ever seen through a gap — and the seam fades
     each stage in, which means for a moment there is a gap. On an ivory body that showed as
     a bright band flashing between two dark beats, at Position → Tension most visibly.
     Contrast is unaffected: it carries its own ground. */
  background: var(--c-aperture);
  color: var(--c-ink);
  font-family: var(--f-sans);
  font-size: var(--t-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1, h2, h3, p { margin: 0; }

/* Type hierarchy, per guidelines p.24: P22 Mackinac Pro leads through headlines
   and hero statements; CoFo Sans VF carries the second headline, subheads and
   body. Note the second-level headline is deliberately the sans, not the serif —
   the two faces alternate down the hierarchy rather than nesting. */
h1, .t-headline {
  font-family: var(--f-display);
  font-weight: 400;
  letter-spacing: -0.015em;
  line-height: 1.02;
  /* Fraunces ships soft, rounded serifs by default; Mackinac's are crisp
     wedges, so the softness and wonk axes are dialled out. */
  font-variation-settings: "SOFT" 0, "WONK" 0, "opsz" 60;
}

h2, .t-subheadline {
  font-family: var(--f-sans);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.12;
}

h3, .t-subhead {
  font-family: var(--f-sans);
  font-weight: 500;
  line-height: 1.25;
}
img, video { display: block; max-width: 100%; }

/* The navigation never blurs. It is the fixed point that teaches the rule
   by contrast — see MOTION-SPEC.md, "How the viewer learns the rule". */
.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.75rem var(--gutter);
  font-size: var(--t-nav);
  letter-spacing: var(--tracking-nav);
  text-transform: uppercase;
  mix-blend-mode: difference;
  color: #fff;
  pointer-events: none;
}

/* The bar is difference-blended, so a hover cannot use colour — whatever it picked would
   invert against half the page. It uses a rule instead: a hairline that draws itself in
   from the left, in currentColor, which is already solving legibility for the whole bar. */
.nav a {
  position: relative;
  color: inherit;
  text-decoration: none;
  pointer-events: auto;
  padding-bottom: 0.35em;
}

.nav a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.32s var(--ease-out);
}

.nav a:hover::after,
.nav a:focus-visible::after { transform: scaleX(1); }

/* The wordmark, knocked into a Golden Blaze chip — the centre of the nav made a small
   Portalith. The chip is opaque, which is the whole point: the nav bar solves legibility
   by inverting against its backdrop, and the mark cannot use that solution without being
   inverted itself. Carrying its own ground means it never depends on what is behind it —
   bone, black, film, or a client's own colour years after launch — and needs no
   per-section maintenance, which is the same argument that justifies the blend on the bar.

   The mark is supplied black, so it needs no filter here; it is ink on Golden Blaze,
   exactly as the guidelines set it. The global :focus-visible ring below covers it. */
.navmark {
  position: fixed;
  z-index: 101;
  top: 1.55rem;
  left: 50%;
  transform: translateX(-50%);
  display: block;
  line-height: 0;
  padding: 0.5rem 0.9rem;
  border-radius: 10px;
  background: var(--c-portalith);
}

.navmark img { display: block; width: 132px; height: auto; }

/* The chip lifts very slightly and brightens. It is the home link and the only persistent
   mark, so it should acknowledge a pointer — but it is also the brand, so barely. */
.navmark {
  transition: transform 0.3s var(--ease-out), box-shadow 0.3s var(--ease-out);
}

.navmark:hover,
.navmark:focus-visible {
  transform: translateX(-50%) translateY(-2px);
  box-shadow: 0 8px 22px rgb(255 191 49 / 0.28);
}

.motion-toggle {
  position: fixed;
  right: var(--gutter);
  bottom: 1.5rem;
  z-index: 100;
  padding: 0.55rem 1rem;
  border: 1px solid currentColor;
  border-radius: 999px;
  background: transparent;
  /* Difference blending for the same reason as the nav: this control sits over
     both the bone ground and the black aperture, and must stay legible on both
     without knowing which beat is underneath it. */
  mix-blend-mode: difference;
  color: #fff;
  font: inherit;
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  opacity: 0.5;
  transition: opacity 0.2s;
}
.motion-toggle:hover, .motion-toggle:focus-visible { opacity: 1; }

/* Techno Blue is the guidelines' assigned colour for digital and UI (p.19), so
   it carries interaction rather than Golden Blaze — which stays reserved for
   the Portalith and moments of emphasis, as the hero colour should be. */
a:focus-visible,
button:focus-visible {
  outline: 2px solid var(--c-interactive);
  outline-offset: 3px;
}

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The depth grammar. Every value is written by plane.js; CSS stays dumb on purpose, so
   there is exactly one place where depth is decided. */
.plane {
  filter: saturate(var(--p-sat, 1)) blur(var(--p-blur, 0px));
  transform: scale(var(--p-scale, 1));
  /* --in is the arrival gate, and it is separate from depth on purpose. Depth 0 is the
     BACKGROUND plane, not absence: it still paints at opacity 0.55. An element that has not
     arrived yet needs a channel that actually reaches zero, and folding it into the depth
     stops would break the grammar for everything that legitimately sits at depth 0.
     Multiply, never replace. Defaults to 1, so nothing that ignores it changes. */
  opacity: calc(var(--p-opacity, 1) * var(--in, 1));
}

.plane.is-active { will-change: filter, transform, opacity; }

/* A full-bleed plane must not take the scale channel. Scaling an inset:0 element DOWN
   reveals the stage behind it as black bars at the edges — measured ~86px at Position's
   rest. Depth on a backdrop is carried by saturation and the soft-still crossfade, which
   is more than enough; the scale channel exists for elements that have somewhere to
   recede *to*. */
.plane--bleed { transform: none; }

/* A film plane never blurs live — the blur channel is expressed as the coverage of a
   pre-blurred still instead. Saturation still applies directly, because it is cheap. */
.plane--film {
  filter: none;
  position: relative;
}

.plane--film > .plane__sharp,
.plane--film > .plane__soft {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.plane--film > .plane__sharp {
  filter: saturate(var(--p-sat, 1));
}

.plane--film > .plane__soft {
  opacity: var(--p-soft, 0);
  background-size: cover;
  background-position: center;
  pointer-events: none;
}


/* The page opens on black with nothing but the Portalith and the wordmark, so the
   navigation arrives rather than being there from the first frame.

   Note which way round this is: visible is the default and the at-top state is what JS
   adds. If the listener never runs the nav is simply present, which is the failure mode we
   want — the opposite arrangement is the one that has produced a blank page here three
   times. Reduced motion keeps the nav from the start; there is no arrival to stage. */
.nav, .navmark { transition: opacity 0.5s var(--ease-out); }

:root[data-motion="full"][data-at-top="true"] .nav,
:root[data-motion="full"][data-at-top="true"] .navmark {
  opacity: 0;
  pointer-events: none;
}

/* 06 Contrast is the only beat whose stage FLOWS. The other five are pinned 100vh
   surfaces, so their content is composed inside the viewport and never travels through
   the nav band. Contrast is 154vh of editorial copy that scrolls, so every line in it
   passes under the fixed bar on the way up — measured colliding with the three approach
   columns, "Our Approach" and the 06 rail over 15500-15800, and with the coda over 16600,
   where the navmark chip landed on "Scale" and left it reading "cale".

   The bar cannot solve this the way it solves legibility. mix-blend-mode inverts it
   against its backdrop, which works over a ground and does nothing when the backdrop is
   itself type — two texts in the same pixels stay two texts. Giving the bar its own ground
   would fix it and cost the blend the argument that justifies it.

   So the chrome leaves for the length of the ivory section. That is not a concession: the
   hard cut to Contrast is the principle the section performs, and the navigation going
   with it makes the cut read harder rather than softer. The mark returns for Close, which
   carries its own sign-off. Same arrangement as at-top above — visible is the default and
   this is the state we ADD, so a listener that never runs leaves the nav present. */
:root[data-at-contrast="true"] .nav,
:root[data-at-contrast="true"] .navmark {
  opacity: 0;
  pointer-events: none;
}


/* The seam, written by stage.js. Defaults to 1 so a section is fully present if the
   trigger never runs — the same way round as the nav, and for the same reason. Reduced
   motion never sets it, and the default carries the page. */
[data-beat] > * { opacity: var(--seam, 1); }
