/* NX Tech November — components. */

/* ── Nav ────────────────────────────────────────────────────────── */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 80;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.15rem var(--gutter);
  transition: background var(--travel) var(--ease), backdrop-filter var(--travel) var(--ease);
}
.nav.scrolled {
  background: rgba(248, 250, 252, .72);
  backdrop-filter: blur(16px) saturate(140%);
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  font-weight: 700;
  font-size: .9rem;
  letter-spacing: .26em;
  text-transform: uppercase;
}
.wordmark img { width: 22px; height: auto; }
.wordmark .tail { color: var(--nx-mid); }

.nav-right { display: flex; align-items: center; gap: 1.1rem; }

.nav-talk {
  font-size: .9rem;
  border-bottom: 1px solid var(--ink);
  padding-bottom: 2px;
  transition: border-color 260ms var(--ease), color 260ms var(--ease);
}
.nav-talk:hover { color: var(--nx-mid); border-color: var(--nx-mid); }
.nav-talk .arrow { color: var(--nx-mid); }

.nav-rule { width: 1px; height: 18px; background: var(--ink-hair); }

.burger {
  width: 44px; height: 32px;
  display: grid; align-content: center; gap: 6px;
  background: none; border: 0; padding: 0; cursor: pointer;
}
.burger span {
  height: 2px; width: 100%; background: var(--ink); border-radius: 2px;
  transition: transform 320ms var(--ease), opacity 220ms var(--ease);
}
/* scaleX rather than a width change, so both bars shorten by exactly the
   same amount and the cross stays square. */
/* nth-of-type, not first/last-child: the button also carries a visually
   hidden label, and :last-child was landing on that instead of the bar. */
.burger[aria-expanded="true"] span:nth-of-type(1) { transform: translateY(4px) rotate(45deg) scaleX(.62); }
.burger[aria-expanded="true"] span:nth-of-type(2) { transform: translateY(-4px) rotate(-45deg) scaleX(.62); }

/* Panel */
.menu {
  position: fixed;
  inset: 0;
  z-index: 79;
  background: var(--ink);
  color: var(--on-dark);
  display: grid;
  align-content: center;
  padding: 6rem var(--gutter) 3rem;
  opacity: 0;
  visibility: hidden;
  transition: opacity 420ms var(--ease), visibility 420ms;
}
.menu[data-open="true"] { opacity: 1; visibility: visible; }
/* The mark sits behind the links, which need their own stacking to stay on
   top of it. */
.menu > .shell { position: relative; z-index: 1; }
.menu ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .4rem; }
.menu a {
  font-size: clamp(2rem, 7vw, 4rem);
  letter-spacing: -.03em;
  color: var(--on-dark);
  transition: color 240ms var(--ease);
}
.menu a:hover { color: var(--nx); }
.menu .menu-foot {
  margin-top: 2.8rem;
  display: flex; flex-wrap: wrap; align-items: center; gap: 1.4rem;
  color: var(--on-dark-soft); font-size: var(--small);
}
/* The footer links inherit the panel's display size without this. */
.menu .menu-foot a { font-size: var(--small); color: var(--on-dark-soft); }
.menu .menu-foot a:hover { color: var(--nx); }

body[data-menu="open"] { overflow: hidden; }
/* The nav sits above the open panel, so it has to switch to white with it. */
body[data-menu="open"] .nav { color: var(--on-dark); background: none; backdrop-filter: none; }
body[data-menu="open"] .burger span { background: var(--on-dark); }
body[data-menu="open"] .nav-talk { border-color: var(--on-dark); }
body[data-menu="open"] .nav-rule { background: var(--on-dark-hair); }

/* ── Buttons ────────────────────────────────────────────────────── */

/* Apple pills: a lit, slightly glossy capsule that squashes under a press and
   wobbles back. The springs live in site.js and drive --sx/--sy/--tx/--ty;
   CSS owns the material and the light. */
.btn {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  gap: .7rem;
  padding: .95rem 1.6rem;
  border-radius: var(--pill);
  border: 1px solid transparent;
  font-size: .8125rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform 320ms var(--ease), background 320ms var(--ease),
              color 320ms var(--ease), border-color 320ms var(--ease),
              box-shadow 320ms var(--ease), filter 200ms var(--ease);
}
/* Without the springs (reduced motion, no JS) a pill still lifts on hover. */
.btn:not([data-liquid]):hover { transform: translateY(-2px); }

/* With them, the transform belongs to the springs alone. */
.btn[data-liquid] {
  transform: translate(var(--tx, 0px), var(--ty, 0px)) scale(var(--sx, 1), var(--sy, 1));
  transition: background 320ms var(--ease), color 320ms var(--ease),
              border-color 320ms var(--ease), box-shadow 320ms var(--ease),
              filter 200ms var(--ease);
  will-change: transform;
}

/* The light inside the liquid: it follows the pointer and swells on press.
   Negative z-index puts it above the pill's own background and below its
   label, which is why the button isolates itself. */
.btn::after {
  content: '';
  position: absolute;
  inset: -60%;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(circle at var(--mx, 50%) var(--my, 50%),
    rgba(255, 255, 255, .34), rgba(255, 255, 255, 0) 46%);
  opacity: calc(var(--g, 0) * .6);
  transition: opacity 280ms var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .btn[data-liquid]:hover::after { opacity: calc(.55 + var(--g, 0) * .45); }
}
.btn[data-pressed] { filter: brightness(.96); }

.btn .arrow { color: var(--nx); transition: transform 320ms var(--ease); }
.btn:hover .arrow { transform: translate(3px, -3px); }

.btn--primary {
  color: var(--on-dark);
  background: linear-gradient(180deg, #1F4163 0%, #11283E 46%, #0A1826 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .20),
    inset 0 -1px 0 rgba(0, 0, 0, .40),
    0 1px 2px rgba(10, 24, 38, .26),
    0 14px 30px -14px rgba(10, 24, 38, .66);
}
.btn--primary:hover {
  background: linear-gradient(180deg, #27507A 0%, #16314B 46%, #0D1E30 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .26),
    inset 0 -1px 0 rgba(0, 0, 0, .40),
    0 2px 4px rgba(10, 24, 38, .26),
    0 20px 40px -14px rgba(10, 24, 38, .7);
}

.btn--ghost {
  color: var(--ink);
  border-color: var(--ink-hair);
  background: linear-gradient(180deg, #FFFFFF 0%, #F2F7FB 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .9),
    inset 0 -1px 0 rgba(10, 24, 38, .05),
    0 1px 2px rgba(10, 24, 38, .08),
    0 12px 26px -14px rgba(10, 24, 38, .42);
}
.btn--ghost:hover { border-color: rgba(26, 111, 174, .35); }
.btn--ghost::after {
  background: radial-gradient(circle at var(--mx, 50%) var(--my, 50%),
    rgba(66, 181, 247, .30), rgba(66, 181, 247, 0) 46%);
}

/* Frosted on navy, the way a glass control behaves over a dark ground. */
.dark .btn--ghost {
  color: var(--on-dark);
  border-color: var(--on-dark-hair);
  background: linear-gradient(180deg, rgba(255, 255, 255, .13), rgba(255, 255, 255, .05));
  backdrop-filter: blur(10px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .22),
    0 12px 28px -16px rgba(0, 0, 0, .8);
}
.dark .btn--ghost::after {
  background: radial-gradient(circle at var(--mx, 50%) var(--my, 50%),
    rgba(255, 255, 255, .26), rgba(255, 255, 255, 0) 46%);
}

/* ── Chips ──────────────────────────────────────────────────────── */

.chips { display: flex; flex-wrap: wrap; gap: .5rem; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  padding: .42rem .9rem;
  border-radius: var(--pill);
  border: 1px solid var(--ink-hair);
  background: rgba(255, 255, 255, .6);
  backdrop-filter: blur(8px);
  font-size: .6875rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--nx-deep);
  white-space: nowrap;
}
.dark .chip { background: rgba(255, 255, 255, .05); border-color: var(--on-dark-hair); color: var(--on-dark); }

.chip--on { background: var(--nx); border-color: var(--nx); color: #06212F; }

/* ── Hero ───────────────────────────────────────────────────────── */

.hero {
  position: relative;
  min-height: 100svh;
  display: grid;
  grid-template-rows: 1fr auto;
  padding: 7rem var(--gutter) clamp(2rem, 5vh, 4rem);
  overflow: hidden;
}

.hero-light {
  position: absolute;
  inset: 0;
  z-index: 0;
  background:
    radial-gradient(60% 50% at 50% 42%, rgba(66, 181, 247, .20), transparent 70%),
    linear-gradient(180deg, #FFFFFF 0%, var(--canvas) 55%, #EEF4F9 100%);
}

/* The particle fields: the hero's mark and the values section's. The sphere
   below each is what shows if WebGL2 is not there, so a field only fades in
   once it has drawn. */
.field-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  opacity: 0;
  transition: opacity 1200ms var(--ease);
  pointer-events: none;
}
.has-field > .field-canvas { opacity: 1; }
/* Hidden unless the field says it cannot run. See hero-field.js. */
.orb-wrap { opacity: 0; transition: opacity 700ms var(--ease); }
.no-field .orb-wrap { opacity: 1; }

/* The sphere: lit in CSS, grained in canvas, breathing. It is the hero's
   fallback and the object in the values section. */
.orb-wrap {
  position: absolute;
  left: 50%;
  top: 44%;
  translate: -50% -50%;
  width: min(62vmin, 560px);
  aspect-ratio: 1;
  z-index: 0;
}
.orb {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background:
    radial-gradient(circle at 40% 31%,
      #FFFFFF 0%,
      rgba(255, 255, 255, .97) 24%,
      rgba(219, 240, 253, .96) 48%,
      rgba(128, 204, 248, .82) 76%,
      rgba(46, 155, 224, .58) 100%);
  box-shadow:
    inset -34px -44px 100px rgba(10, 24, 38, .13),
    inset 26px 32px 80px rgba(255, 255, 255, .95),
    0 70px 130px -50px rgba(26, 111, 174, .38);
  animation: breathe 14s var(--ease) infinite;
}
.orb-grain {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  mix-blend-mode: soft-light;
  opacity: .3;
  pointer-events: none;
}

/* The real object. The CSS sphere above is what shows when WebGL2 is not
   there, so the canvas only fades in once it has actually drawn. */
.orb-gl {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 900ms var(--ease);
  pointer-events: none;
}
.orb, .orb-grain { transition: opacity 600ms var(--ease); }
.orb-wrap.has-gl .orb-gl { opacity: 1; }
.orb-wrap.has-gl .orb,
.orb-wrap.has-gl .orb-grain { opacity: 0; animation: none; }
/* Bloom: the light the sphere throws onto the page behind it. */
.orb-wrap::after {
  content: '';
  position: absolute;
  inset: -20%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(66, 181, 247, .26), rgba(66, 181, 247, .07) 48%, transparent 66%);
  filter: blur(40px);
  z-index: -1;
}
.orb-shadow {
  position: absolute;
  left: 50%;
  bottom: -13%;
  translate: -50% 0;
  width: 78%;
  height: 10%;
  border-radius: 50%;
  background: radial-gradient(ellipse, rgba(10, 24, 38, .20), transparent 70%);
  filter: blur(10px);
}
@keyframes breathe {
  0%, 100% { transform: translateY(0) scale(1); }
  50%      { transform: translateY(-16px) scale(1.016); }
}

.hero-foot {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr);
  align-items: end;
  gap: clamp(1.5rem, 4vw, 4rem);
}
.hero-aside { justify-self: end; max-width: 32ch; }
.hero-aside .copy { font-size: .9375rem; margin-bottom: 1rem; }

/* The drawn headline. Filled from the first paint, so without JavaScript or
   under reduced motion it is simply the headline; the script switches it to
   an outline for the draw and fills it back in at the end. */
/* One line, so the width cap is what sets the type size: the glyphs are
   authored at 100 and the viewBox is fitted to them, so the rendered size is
   100 x (box width / viewBox width). Capped here to land near 66px on a
   desktop, smaller than the two line version it replaces. */
.headline__svg {
  display: block;
  width: 100%;
  max-width: 760px;
  height: auto;
  overflow: visible;
}
.headline__line {
  font-family: var(--font);
  font-size: 100px;
  font-weight: 400;
  letter-spacing: -.03em;
  fill: var(--heading);
  stroke: var(--heading);
  stroke-width: 1.1;
  stroke-linejoin: round;
  stroke-linecap: round;
  transition: fill 520ms var(--ease);
}
/* The emphasis is a presentation attribute on the tspan, not a class: the
   measurement serialises a clone of this SVG and loads it as an image, where
   the page's stylesheet does not reach, and a class would be ignored there.
   The dash would then be measured against the wrong weight. */
.headline[data-drawing] .headline__line { transition: none; }
/* While drawing there is nothing but the stroke. */
.headline[data-drawing] .headline__line { fill: none; }

.hero h1 { margin-bottom: 1.1rem; }
.hero .lede { margin-bottom: 1.8rem; }

@media (max-width: 860px) {
  .hero-foot { grid-template-columns: 1fr; }
  .hero-aside { justify-self: start; }
  .orb-wrap { top: 34%; width: min(74vmin, 400px); }
}

/* ── Statement section ──────────────────────────────────────────── */

/* White ground, fading into the page at top and bottom. The reference is
   pure white everywhere the glow is not, and that is what gives its mass an
   edge. Measured on our own blue-grey canvas the same mass had nothing to
   stand against and read as a wash rather than a sphere. */
.statement {
  text-align: center;
  background: linear-gradient(180deg,
    var(--canvas) 0%, #FFFFFF 16%, #FFFFFF 84%, var(--canvas) 100%);
}
.statement .display { margin-inline: auto; max-width: 17ch; }
.statement .copy { margin: 1.4rem auto 2rem; max-width: 52ch; }

/* ── Cards ──────────────────────────────────────────────────────── */

/* Two up, always. auto-fit gives three at desktop width and leaves the
   fourth card stranded on a row of its own. */
.grid-2 {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(1.2rem, 2.4vw, 2rem);
}
@media (min-width: 860px) { .grid-2 { grid-template-columns: 1fr 1fr; } }

.card {
  --card-pad: clamp(1.6rem, 3vw, 2.6rem);
  position: relative;
  display: flex;
  flex-direction: column;
  gap: .9rem;
  padding: var(--card-pad);
  border-radius: var(--radius);
  border: 1px solid var(--on-dark-hair);
  background: linear-gradient(170deg, rgba(255, 255, 255, .07), rgba(255, 255, 255, .02));
  transition: border-color 420ms var(--ease), transform 420ms var(--ease);
  min-height: 300px;
}
.card:hover { transform: translateY(-4px); border-color: rgba(66, 181, 247, .5); }
.card h3 { font-size: clamp(1.4rem, 2.4vw, 2rem); }
.card .card-link {
  margin-top: auto;
  padding-top: 1rem;
  font-size: var(--small);
  color: var(--nx);
  display: inline-flex;
  gap: .4rem;
}
.card .card-link .arrow { transition: transform 320ms var(--ease); }
.card:hover .card-link .arrow { transform: translate(3px, -3px); }

/* The photo band at the head of a division card. Scene setting only: it is
   decorative, so it carries no alt text and is never the thing a reader has
   to see. */
.card-media {
  margin: calc(var(--card-pad) * -1) calc(var(--card-pad) * -1) .4rem;
  border-radius: var(--radius) var(--radius) 0 0;
  overflow: hidden;
  aspect-ratio: 8 / 5;
  background: rgba(10, 24, 38, .06);
}
.card-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 900ms var(--ease);
}
.card:hover .card-media img { transform: scale(1.035); }
/* On the dark panels the photographs are the only bright thing on the page,
   so they are held back rather than left to shout over the type. */
.dark .card-media img { opacity: .78; }
.dark .card:hover .card-media img { opacity: .9; }

/* No box. The photograph, the label and the title stand on the page on their
   own, and the body copy folds away until the card is hovered or something
   inside it takes focus. */
.card--light {
  --card-pad: 0;
  border: 0;
  background: none;
  box-shadow: none;
  padding: 0;
  gap: 0;
  min-height: 0;
}
.card--light:hover { transform: none; }
.card--light .card-media {
  margin: 0 0 1.15rem;
  border-radius: var(--radius);
}
.card--light .label { margin-bottom: .5rem; }
.card--light .card-link { color: var(--nx-deep); padding-top: .9rem; }

/* The fold. Rows go 0fr to 1fr, so the copy collapses to nothing and opens
   to exactly its own height without anyone having to measure it. */
.card-fold {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 520ms var(--ease);
}
.card-fold__inner {
  overflow: hidden;
  opacity: 0;
  transition: opacity 340ms var(--ease);
}
.card-fold__inner > :first-child { margin-top: .85rem; }

.card--light:hover .card-fold,
.card--light:focus-within .card-fold { grid-template-rows: 1fr; }
.card--light:hover .card-fold__inner,
.card--light:focus-within .card-fold__inner { opacity: 1; }

/* The copy is still in the document while it is folded away, so tabbing to
   the link inside opens the card that holds it. */

@media (hover: none) {
  /* Nothing to hover with: a touch reader would never see the copy. */
  .card-fold { grid-template-rows: 1fr; }
  .card-fold__inner { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .card-fold, .card-fold__inner { transition: none; }
}

/* ── Ribbon ─────────────────────────────────────────────────────── */

.ribbon {
  overflow: hidden;
  border-block: 1px solid var(--ink-hair);
  padding-block: 1.3rem;
  background: var(--paper);
}
.ribbon-track {
  display: flex;
  width: max-content;
  animation: slide 38s linear infinite;
  font-size: clamp(1rem, 1.8vw, 1.5rem);
  letter-spacing: -.02em;
  white-space: nowrap;
}
.ribbon-track span { display: inline-flex; gap: 2.4rem; align-items: center; padding-right: 2.4rem; }
.ribbon-track i { color: var(--nx); font-style: normal; }
@keyframes slide { to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) { .ribbon-track { animation: none; } }

/* ── Rows ───────────────────────────────────────────────────────── */

/* Scroll folds. The copy is hidden and opens as the row is scrolled up the
   page, a little more with every pixel, rather than snapping open when it
   crosses a line. The height is driven from a measured natural height so
   the fold knows where it is going; --p is the row's progress, 0 to 1.
   Nothing applies until the script has measured and switched it on, so
   without JavaScript the copy is simply there. */
.rows[data-scroll-folds][data-ready] .scroll-fold {
  overflow: hidden;
  height: calc(var(--p, 0) * var(--h, 0px));
}
.rows[data-scroll-folds][data-ready] .scroll-fold__inner {
  opacity: var(--p, 0);
}
@media (prefers-reduced-motion: reduce) {
  .rows[data-scroll-folds][data-ready] .scroll-fold { height: auto; }
  .rows[data-scroll-folds][data-ready] .scroll-fold__inner { opacity: 1; }
}

.rows { display: grid; }
.row {
  display: grid;
  grid-template-columns: minmax(0, 24ch) minmax(0, 1fr);
  gap: clamp(1rem, 4vw, 4rem);
  padding-block: clamp(1.6rem, 3vw, 2.4rem);
  border-top: 1px solid var(--ink-hair);
  align-items: start;
}
.rows .row:last-child { border-bottom: 1px solid var(--ink-hair); }
.dark .row { border-color: var(--on-dark-hair); }
.row h3 { font-size: clamp(1.3rem, 2.2vw, 1.75rem); }
@media (max-width: 720px) { .row { grid-template-columns: 1fr; gap: .6rem; } }

/* ── Contact panel ──────────────────────────────────────────────── */

.panel {
  background: var(--paper);
  border-radius: var(--radius-lg);
  border: 1px solid var(--ink-hair);
  padding: clamp(1.8rem, 5vw, 4.5rem);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr));
  gap: clamp(2rem, 5vw, 5rem);
  box-shadow: 0 60px 120px -70px rgba(10, 24, 38, .5);
}
.dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--nx); margin-right: .5rem; vertical-align: middle;
}

.field { display: grid; gap: .3rem; margin-bottom: 1.4rem; }
.field label { font-size: var(--small); color: var(--ink-faint); }
.field input, .field textarea, .field select {
  border: 0;
  border-bottom: 1px solid var(--ink-hair);
  background: none;
  padding: .55rem 0;
  border-radius: 0;
  transition: border-color 280ms var(--ease);
}
.field input:focus, .field textarea:focus, .field select:focus {
  outline: none;
  border-bottom-color: var(--nx-mid);
}
.field textarea { resize: vertical; min-height: 92px; }
.field-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 1.4rem; }
@media (max-width: 520px) { .field-pair { grid-template-columns: 1fr; gap: 0; } }

.field-error { color: #B3261E; font-size: var(--small); min-height: 1.2em; }

.interest { display: flex; flex-wrap: wrap; gap: .5rem; margin: .4rem 0 1.8rem; }
.interest button { cursor: pointer; }

.send { width: 100%; justify-content: center; }

.contact-list { display: grid; gap: 1.4rem; margin-top: 2.4rem; }
.contact-list a { border-bottom: 1px solid var(--ink-hair); }
.contact-list a:hover { color: var(--nx-mid); border-color: var(--nx-mid); }

.form-done {
  border: 1px solid var(--ink-hair);
  border-radius: var(--radius);
  padding: 1.1rem 1.3rem;
  background: #F2F9FF;
  color: var(--nx-deep);
  margin-bottom: 1.2rem;
}
.form-done[hidden] { display: none; }

/* ── Footer ─────────────────────────────────────────────────────── */

.footer { padding-block: clamp(3.5rem, 7vw, 6rem) 2rem; }
.footer-top {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr));
  gap: 2rem;
  align-items: start;
  padding-bottom: 2.6rem;
}
.footer-mail {
  display: inline-flex; align-items: center; gap: .7rem;
  font-size: clamp(1.2rem, 2.6vw, 1.9rem);
  letter-spacing: -.02em;
}
.footer-mail:hover { color: var(--nx); }

.footer-cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 2rem;
  padding-block: 2.6rem;
  border-top: 1px solid var(--on-dark-hair);
}
.footer-cols ul { list-style: none; margin: .9rem 0 0; padding: 0; display: grid; gap: .55rem; }
.footer-cols a { color: var(--on-dark-soft); }
.footer-cols a:hover { color: var(--nx); }

.footer-mark { display: block; margin-block: 1rem 1.6rem; color: var(--on-dark); }
.footer-mark svg { width: 100%; height: auto; }

.footer-base {
  display: flex; flex-wrap: wrap; gap: 1rem 1.6rem;
  justify-content: space-between; align-items: center;
  padding-top: 1.6rem;
  border-top: 1px solid var(--on-dark-hair);
  font-size: var(--small);
  color: var(--on-dark-soft);
}

/* ── Our story ────────────────────────────────────────────────────── */
/* Headline left at its own size, copy right. The headline was briefly run up
   to 151px across the full shell and is back to `--head`: this section earns
   its weight from the cascade below it, not from display type.

   What is larger is the copy on the right, from .9375rem to about 21px. It
   is the longest read on the site and it was set smaller than the body text
   everywhere else on the page, which is backwards. */
.story-top {
  position: relative;
  display: grid;
  /* 1.15 / .85, as it was. The headline is set to break after "products.",
     and its first line needs about 700px at 49px to hold that break; a
     narrower column silently pushed it to three lines. */
  grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr);
  /* Top aligned. Bottom aligning them put the headline 494px down the
     section with nothing above it, because the copy column is much the
     taller of the two and the row is as tall as the copy. */
  align-items: start;
  gap: clamp(1.5rem, 4vw, 4rem);
}

.story-head { margin-top: 1rem; }

.story-copy { justify-self: end; max-width: 46ch; }

/* Above 1280 the copy comes out of the flow entirely. In flow, the row is as
   tall as the copy, so the cascade cannot begin until 458px of paragraph has
   finished, whatever the headline does. Out of flow, the headline alone sets
   where the pictures start, and they rise by about 250px.

   A wider copy is a shorter copy, so taking it to 43% of the shell rather
   than the 46ch the column allowed cuts roughly 90px off its height at the
   same 21px type. It is held clear of the headline's own first line, which
   runs to about 730px at 49px. */
@media (min-width: 1280px) {
  .story-top { display: block; }
  .story-copy {
    position: absolute;
    top: 0;
    right: 0;
    width: 43%;
    max-width: none;
  }
}
.story-copy .copy {
  font-size: clamp(1.0625rem, 1.45vw, 1.45rem);
  line-height: 1.5;
  max-width: none;
}

/* The headline flies in: the same gesture as the pictures, a touch further,
   because it arrives first and sets the one they follow.

   The transform is on this wrapper and not on the h2, because data-lean
   writes the h2's transform on every mouse move and would wipe this out
   between frames. */
.story-head {
  --p: 0;
  opacity: var(--p);
  will-change: transform, clip-path;
  transform: translateY(calc((1 - var(--p)) * 64px));
  clip-path: inset(calc((1 - var(--p)) * 100%) -14% -40% -14%);
}
.story-head.flow--entering {
  transition: transform 1000ms var(--ease),
              clip-path 1000ms var(--ease),
              opacity 680ms var(--ease);
}

@media (prefers-reduced-motion: reduce) {
  .story-head {
    --p: 1;
    opacity: 1;
    transform: none;
    clip-path: none;
    will-change: auto;
  }
}

@media (min-width: 1280px) {
  .cascade { margin-top: 7rem; }
}

@media (max-width: 860px) {
  .story-top { grid-template-columns: 1fr; }
  .story-copy { justify-self: start; }
}

/* ── The Titan imagery, cascading under Our story ─────────────────── */
/* Four renders, grouped rather than scattered: a fan that steps right and
   down below the Our story copy. They were in the page's side gutters first,
   which meant four small pictures spread over four sections. Grouped they
   can be half as big again and read as one thing.

   The ground here is --ink, and the renders are night scenes, so they need
   an edge: a hairline of light at the top and a deep shadow underneath, or
   dark navy on dark navy simply dissolves.

   Positions are percentages of the group's own box, so the whole cascade
   scales with the shell rather than needing a breakpoint per width. The box
   keeps its ratio, so nothing below it moves as the window changes. */

.cascade {
  position: relative;
  /* Wider than the text shell, out to 24px off the page edge. The four
     widths add up to more than any box they sit in, so the only ways to take
     overlap out are to give them a wider box and to steepen the stair. This
     is the first: it buys 64px at 1440, which is what evens the horizontal
     bite from 146 / 73 / 73px to 77px at all three joins. */
  --bleed: max(0px, calc(var(--gutter) - 24px));
  width: calc(100% + var(--bleed) * 2);
  margin-inline: calc(var(--bleed) * -1);
  /* And this is the second. The box was 1328:648, which forced a shallow
     stair and a deep vertical overlap. Taller box, steeper stair, less of
     each picture hidden by the next. */
  /* 1392:566, down from 1392:1058. The box held a four step stair; with the
     portrait and the watch gone it holds two, and a box sized for four would
     have left 490px of empty navy under the pair. The height is simply where
     the second picture now ends. */
  aspect-ratio: 1392 / 566;
  /* Close under the headline. Above 1280 the copy hangs down past the
     headline into this space, and the action picture is the first thing it
     could reach; 7rem is what keeps them apart at every width tested. */
  margin-top: clamp(1.5rem, 3vw, 3rem);
}

.cascade__item {
  position: absolute;
  margin: 0;
  overflow: hidden;
  border-radius: var(--radius);
  /* 0 is waiting, 1 is arrived. site.js drives it from scroll position. */
  --p: 0;
  opacity: var(--p);
  will-change: transform, clip-path;
  box-shadow: 0 40px 90px -30px rgba(0, 0, 0, .78);
  /* The hairline that stops a night scene dissolving into a navy page. */
  outline: 1px solid rgba(255, 255, 255, .09);
  outline-offset: -1px;
  /* The fan: each one rises and wipes up into place. */
  transform: translateY(calc((1 - var(--p)) * 46px));
  clip-path: inset(calc((1 - var(--p)) * 100%) -90px -90px -90px);
}

.cascade__item img { display: block; width: 100%; height: auto; }

/* Left to right, stepping down and over. Two now, not four: the venue and
   the action shot. The percentages are of the bled box, so they are the same
   size on the page as they were, 558 and 438px at 1440.

   The join gives up 67px across and 77px down, the same bite the four step
   version used, so the pair still reads as a cascade rather than two
   pictures that happen to touch. The pair spans two thirds of the box and
   sits bottom left, under a headline that is top left and a paragraph that
   is top right: the empty quarter under the copy is the point of the
   arrangement, not a gap in it. */
.cascade__item--venue  { left:  0%;   top:  0%;   width: 40.1%; z-index: 1; }
.cascade__item--action { left: 35.3%; top: 41.9%; width: 31.5%; z-index: 2; }

/* Only for a picture that loads already inside the arrival band. */
.cascade__item.flow--entering {
  transition: transform 900ms var(--ease),
              clip-path 900ms var(--ease),
              opacity 620ms var(--ease);
}

/* Narrow: the fan has nowhere to fan. It becomes two plain rows, still in
   their own sizes, still arriving one after another. Hiding them here would
   be wrong now they are content rather than decoration in a gutter. */
@media (max-width: 1023px) {
  .cascade {
    aspect-ratio: auto;
    width: 100%;
    margin-inline: 0;
    margin-top: clamp(2rem, 5vw, 3rem);
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: clamp(12px, 2.4vw, 22px);
    align-items: start;
  }
  .cascade__item {
    position: static;
    width: auto;
    transform: translateY(calc((1 - var(--p)) * 28px));
  }
}

@media (max-width: 560px) {
  .cascade { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  .cascade__item {
    --p: 1;
    opacity: 1;
    transform: none;
    clip-path: none;
    will-change: auto;
  }
}

/* The price sits outside the fold, not in it. A number that only appears on
   hover is a number nobody scanning the page will ever read. */
.card--light .card-price {
  margin: .45rem 0 0;
  font-size: .95rem;
  font-weight: 600;
  letter-spacing: -.01em;
  color: var(--nx-deep);
}

/* ── Coming soon ────────────────────────────────────────────────── */
/* Life Labs and Phantom Studio have nothing to visit yet. The card says so
   over the photograph, arriving on the same hover that unfolds the copy.
   The scrim is there because the photographs underneath are busy and the
   words have to hold against both of them. */
.card-media { position: relative; }
.coming {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: grid;
  place-items: center;
  margin: 0;
  background: rgba(6, 20, 34, .62);
  color: #fff;
  font-size: clamp(.78rem, 1.5vw, .95rem);
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  opacity: 0;
  transition: opacity 340ms var(--ease);
}
.card:hover .coming,
.card:focus-within .coming { opacity: 1; }
@media (hover: none) {
  /* Nothing to hover with, so the words stand on the photograph. */
  .coming { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .coming { transition: none; } }
