/* ==========================================================================
   Base — reset, typography, shared atoms, and the motion primitives that
   every section reuses. Section-specific layout lives in sections.css.
   ========================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 88px;
}

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.65;
  color: var(--ink-2);
  background: var(--ink);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

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

img {
  height: auto;
}

h1,
h2,
h3,
h4 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 1.02;
  letter-spacing: -0.01em;
  text-wrap: balance;
}

p {
  margin: 0;
  text-wrap: pretty;
}

ul,
ol,
dl,
dd {
  margin: 0;
  padding: 0;
}

li {
  list-style: none;
}

a {
  color: inherit;
  text-decoration: none;
}

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

.band--dark :focus-visible {
  outline-color: var(--yellow);
}

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

.skip-link {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 200;
  padding: 0.75rem 1.25rem;
  background: var(--white);
  color: var(--ink);
  font-weight: 600;
  transform: translateY(-120%);
  transition: transform var(--dur-fast) var(--ease-out);
}

.skip-link:focus-visible {
  transform: translateY(0);
}

/* ==========================================================================
   Layout shell
   ========================================================================== */

.shell {
  width: min(100% - var(--gutter) * 2, var(--shell));
  margin-inline: auto;
}

.shell--wide {
  width: min(100% - var(--gutter) * 2, var(--shell-wide));
}

.section {
  position: relative;
  padding-block: var(--section-y);
  overflow: clip;
}

/* ==========================================================================
   Shared atoms — carried over from the brochure's visual language
   ========================================================================== */

.eyebrow {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.85rem;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--step--1);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent, var(--green));
}

.eyebrow::before {
  content: "";
  width: 1.75rem;
  height: 3px;
  border-radius: 2px;
  background: currentColor;
  transform-origin: left;
  transition: scale var(--dur-mid) var(--ease-out) 120ms;
}

.js .eyebrow::before {
  scale: 0 1;
}

.is-in .eyebrow::before,
.eyebrow.is-in::before {
  scale: 1 1;
}

.section-title {
  font-size: var(--step-6);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -0.015em;
}

.section-title--sm {
  font-size: var(--step-4);
}

.lead {
  max-width: 62ch;
  margin-top: 1.25rem;
  font-size: var(--step-1);
  line-height: 1.62;
  color: var(--fg-dim, var(--slate));
}

/* The four-colour ribbon rule that separates blocks in the brochure. */
.brandbar {
  width: 132px;
  height: 6px;
  margin-block: 1.5rem;
  border-radius: 3px;
  background: linear-gradient(
    90deg,
    var(--green) 0 25%,
    var(--blue) 25% 50%,
    var(--orange) 50% 75%,
    var(--red) 75% 100%
  );
  transform-origin: left;
  transition: scale var(--dur-slow) var(--ease-out) 200ms;
}

.js .brandbar {
  scale: 0 1;
}

.js .is-in .brandbar {
  scale: 1 1;
}

.brandbar--full {
  width: 100%;
  height: 6px;
  margin-block: 0;
}

/* ==========================================================================
   Buttons
   ========================================================================== */

.btn {
  --btn-bg: var(--ink);
  --btn-fg: var(--white);
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  padding: 1rem 1.9rem;
  border: 1px solid transparent;
  border-radius: 999px;
  overflow: hidden;
  font-family: var(--font-display);
  font-size: var(--step-0);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--btn-fg);
  background: var(--btn-bg);
  cursor: pointer;
  isolation: isolate;
  transition:
    transform var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast) var(--ease-out);
}

.btn:hover,
.btn:focus-visible {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

.btn:active {
  transform: translateY(0);
}

.btn__arrow {
  transition: translate var(--dur-fast) var(--ease-out);
}

.btn:hover .btn__arrow {
  translate: 4px 0;
}

.btn--ghost {
  --btn-bg: transparent;
  --btn-fg: currentColor;
  border-color: var(--hairline, rgb(255 255 255 / 0.28));
}

.btn--light {
  --btn-bg: var(--white);
  --btn-fg: var(--ink);
}

/* ==========================================================================
   Brand edge
   --------------------------------------------------------------------------
   The four ribbon colours drawn as a border rather than a fill. A gradient
   can't be a border-color, so it's painted as a filled pseudo-element and
   masked down to a ring: two stacked masks, one inset to the content box, and
   `exclude` composites away everything except the padding ring.

   --edge-w  ring thickness
   --edge-on final opacity (0 by default, so it lights up on hover/focus)
   ========================================================================== */

/* .floor is omitted deliberately — it is position: sticky, which already
   establishes a containing block, and relative would break the stack. */
.edge,
.btn,
.card,
.frame,
.legend__item,
.marker,
.numbers,
.contact__card,
.bowl__diagram {
  position: relative;
}

/* Small chips read better with a hairline ring than the default weight. */
.legend__item,
.marker {
  --edge-w: 1px;
}

.edge::after,
.btn::after,
.card::after,
.frame::after,
.floor::after,
.legend__item::after,
.marker::after,
.numbers::after,
.contact__card::after,
.bowl__diagram::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  padding: var(--edge-w, 1.5px);
  background: var(--brand-gradient);
  -webkit-mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  mask-composite: exclude;
  opacity: var(--edge-on, 0);
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease-out);
}

/* Interactive surfaces light their edge on hover / keyboard focus. */
.edge:hover,
.edge:focus-within,
.card:hover,
.frame:hover,
.floor:hover,
.legend__item:hover,
.marker:hover {
  --edge-on: 1;
}

/* Feature panels carry the edge permanently. */
.edge--always,
.numbers,
.contact__card,
.bowl__diagram {
  --edge-on: 1;
}

/* Buttons carry the edge permanently; the ring is the brand cue, and hover
   just thickens it rather than flooding the button with colour. */
.btn {
  --edge-w: 1.5px;
  --edge-on: 1;
}

.btn:hover,
.btn:focus-visible {
  --edge-w: 3px;
}

/* ==========================================================================
   Motion primitives
   --------------------------------------------------------------------------
   Everything animated on scroll uses one contract: an element carries
   [data-reveal], the observer in js/reveal.js adds .is-in, and CSS handles the
   rest. --i is the stagger index written by the observer.
   ========================================================================== */

/* Hidden states are scoped to .js (set by an inline script in <head>) so that
   if scripting is unavailable or a script throws, the page still renders in
   full rather than staying blank. */
[data-reveal] {
  --i: 0;
  transition:
    opacity var(--dur-mid) var(--ease-out),
    translate var(--dur-mid) var(--ease-out),
    scale var(--dur-mid) var(--ease-out);
  transition-delay: calc(var(--i) * var(--stagger));
}

.js [data-reveal] {
  opacity: 0;
  translate: 0 var(--rise);
  will-change: opacity, translate;
}

.js [data-reveal="left"] {
  translate: calc(var(--rise) * -1.4) 0;
}

.js [data-reveal="right"] {
  translate: calc(var(--rise) * 1.4) 0;
}

.js [data-reveal="scale"] {
  translate: 0 0;
  scale: 0.94;
}

/* Landed state, declared last so it beats every directional variant above. */
.js [data-reveal].is-in {
  opacity: 1;
  translate: 0 0;
  scale: 1;
  will-change: auto;
}

/* Headings split into words by js/reveal.js. Each word gets its own clip so
   multi-line headings mask correctly line by line; the padding/margin pair
   keeps ascenders and descenders from being shaved off. */
.split__word {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding-block: 0.12em;
  margin-block: -0.12em;
}

.js .split__word > span {
  translate: 0 115%;
  rotate: 3deg;
}

.split__word > span {
  display: inline-block;
  transition:
    translate var(--dur-slow) var(--ease-soft),
    rotate var(--dur-slow) var(--ease-soft);
  transition-delay: calc(var(--w, 0) * 55ms);
}

.is-in .split__word > span {
  translate: 0 0;
  rotate: 0deg;
}

/* Image frames: the picture scales down into a clip that wipes open.
   The clip lives on the <img>, never on .frame itself — .frame is the element
   the IntersectionObserver watches, and Chrome applies a target's own
   clip-path when computing its intersection rect. Clipping the observed
   element to zero height makes it permanently invisible to the observer, so
   the reveal that would remove the clip can never fire. */
.frame {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-md);
  background: var(--mist-2);
}

.frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition:
    clip-path var(--dur-slow) var(--ease-soft),
    scale calc(var(--dur-slow) * 1.5) var(--ease-soft);
}

.js .frame img {
  clip-path: inset(0 0 100% 0);
  scale: 1.12;
}

.js .frame.is-in img {
  clip-path: inset(0 0 0 0);
  scale: 1;
}

.frame__caption {
  transition: opacity var(--dur-mid) var(--ease-out) var(--dur-mid);
}

.js .frame__caption {
  opacity: 0;
}

.js .frame.is-in .frame__caption {
  opacity: 1;
}

.frame__caption {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  padding: 2.6rem 1.1rem 0.9rem;
  font-family: var(--font-display);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--white);
  background: linear-gradient(to top, rgb(11 14 17 / 0.82), transparent);
}

.frame--ratio-wide {
  aspect-ratio: 16 / 9;
}

.frame--ratio-tall {
  aspect-ratio: 4 / 5;
}

.frame--ratio-square {
  aspect-ratio: 1;
}

/* Cards that lift on hover, with a brand hairline that grows across the top. */
.card {
  --accent: var(--green);
  position: relative;
  padding: 1.6rem 1.5rem;
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  background: var(--surface);
  transition:
    transform var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out);
}

.card::before {
  content: "";
  position: absolute;
  inset: -1px -1px auto;
  height: 3px;
  border-radius: var(--r-md) var(--r-md) 0 0;
  background: var(--accent);
  transform-origin: left;
  transition: scale var(--dur-mid) var(--ease-out);
  transition-delay: calc(var(--i) * var(--stagger) + 200ms);
}

.js .card::before {
  scale: 0 1;
}

.js .card.is-in::before,
.js .is-in .card::before {
  scale: 1 1;
}

.card:hover {
  transform: translateY(-4px);
  background: var(--surface-hover);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--hairline));
  box-shadow: var(--shadow-md);
}

.band--dark .card:hover {
  box-shadow: none;
}

.card__label {
  display: block;
  margin-bottom: 0.55rem;
  font-family: var(--font-display);
  font-size: var(--step-0);
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--accent);
}

.card__body {
  font-size: var(--step--1);
  line-height: 1.62;
  color: var(--fg-dim);
}

/* Accent rotation so a group of cards walks the brand ribbon. */
.accent-1 { --accent: var(--green); }
.accent-2 { --accent: var(--blue); }
.accent-3 { --accent: var(--orange); }
.accent-4 { --accent: var(--red); }
.accent-5 { --accent: var(--yellow); }

/* ==========================================================================
   Reduced motion — one switch, everything lands in its final state.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

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

  [data-reveal],
  [data-reveal].is-in {
    opacity: 1;
    translate: 0 0;
    scale: 1;
  }

  .frame,
  .frame.is-in {
    clip-path: none;
  }

  .frame img,
  .frame.is-in img {
    scale: 1;
  }

  .split__word > span {
    translate: 0 0;
    rotate: 0deg;
  }

  .ribbon__line {
    stroke-dasharray: none !important;
    stroke-dashoffset: 0 !important;
  }

  .bowl__diagram [data-draw] {
    stroke-dasharray: none;
    stroke-dashoffset: 0;
  }

  .bowl__diagram [data-fade] {
    opacity: 1;
  }

  .timeline__rail {
    scale: 1 1;
  }

  .timeline__item::before,
  .legend__pin,
  .is-in .legend__pin {
    scale: 1;
  }

  .numbers__item::after {
    scale: 1 1;
  }

  .record__track {
    animation: none;
  }

  .hero__media img,
  .contact::before {
    animation: none;
  }

  .hero__cue i {
    animation: none;
    transform: scaleY(1);
  }

  .brandbar,
  .is-in .brandbar,
  .eyebrow::before {
    scale: 1 1;
  }

  .card::before {
    scale: 1 1;
  }
}
