/* ---------------------------------------------------------------------------
   Mako Capital
   Type is deliberately small and widely tracked. The wordmark carries the
   scale; everything under it whispers. Confidence is space, not size.
   --------------------------------------------------------------------------- */

/* Variable Inter, subset to the ~70 glyphs this page can render. One 21 KB
   file covers both the 200 and 300 weights used below. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/assets/fonts/inter-subset.woff2') format('woff2');
  unicode-range: U+20, U+26-27, U+2c-2e, U+30-39, U+40-5a, U+61-7a;
}

:root {
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --sans: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
}

body {
  font-family: var(--sans);
  /* dvh so iOS Safari's collapsing toolbar doesn't clip or jump the layout */
  min-height: 100dvh;
}

/* --- the ocean ----------------------------------------------------------- */

#scene,
#fallback {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
}

#scene {
  display: block;
  opacity: 0;
  transition: opacity 1400ms var(--ease-out);
}
body.gl-ready #scene { opacity: 1; }

/* Static frame for browsers without WebGL2. Hidden unless promoted. */
#fallback {
  display: none;
  background: url('/assets/fallback.jpg') center / cover no-repeat;
}
body.gl-failed #fallback { display: block; }

/* --- content ------------------------------------------------------------- */

.stage {
  /* Sits above the canvas by paint order — it follows it in the DOM — rather
     than by z-index. Either would work now, but the asymmetric-padding lift
     below is kept in preference to a transform: transforms can soften text
     rendering on some GPUs, and there is nothing to gain from one here. */
  position: relative;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;

  /* Content sits just above optical centre. Asymmetric padding rather than a
     transform, for the reason above; the block lifts by half the difference. */
  --lift: 6dvh;
  padding: max(6vh, 2rem) 1.5rem;
  padding-bottom: calc(max(6vh, 2rem) + var(--lift) * 2);
}

/* --- wordmark ------------------------------------------------------------ */

.wordmark {
  position: relative;
  width: clamp(232px, 46vw, 604px);
  line-height: 0;
}

/* The mark is drawn by the shader so light can cross it. This element still
   does three jobs: it drives the responsive sizing below, it is the box app.js
   measures to tell the shader where to draw, and it carries the alt text.
   opacity:0 rather than display:none — it must keep its layout box, and it
   stays in the accessibility tree this way. */
.wordmark__mark {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 0 26px rgba(120, 190, 255, 0.18));
  transition: opacity 420ms var(--ease-out);
}

/* Handed over only once the texture has actually landed, so a slow load never
   leaves the page briefly nameless. */
body.word-in-gl .wordmark__mark { opacity: 0; }

/* ...and handed straight back if the canvas is not there to draw it. Without
   this the brand name vanishes entirely on browsers without WebGL2. */
body.gl-failed .wordmark__mark {
  opacity: 1;
  transition: none;
  /* The raw artwork is near-white, which is the brightness the shader version
     deliberately avoids. Bring the fallback into the same range so it reads as
     the same mark rather than the old pasted-on one. */
  filter: brightness(0.7) saturate(0.85)
          drop-shadow(0 0 26px rgba(120, 190, 255, 0.22));
}

/* --- the statement ------------------------------------------------------- */

/* The clamp minimums hold every line at or above 12px on a phone. Below that
   the type stops being restrained and starts being unreadable — and the email
   address is the one thing on this page that has a job to do. The maximums are
   unchanged, so the desktop composition is unaffected.

   These lines are semi-transparent on purpose: the water shows through, so a
   light shaft passing behind genuinely lifts them. Measured across the
   wordmark's width, that gives ~6.7 luminance of modulation between the
   darkest and brightest backdrop behind the glyphs.

   This previously also carried `mix-blend-mode: screen` for the same effect.
   Measurement retired it: of that 6.7, the blend contributed 0.2 and the alpha
   contributed 6.5. It was doing nothing while forcing .stage to avoid
   transforms and z-index (a stacking context silently confines a blend), so it
   was removed rather than kept as decoration. The soft text-shadow stands in
   for light scattering, which stops glyph edges sitting hard on the water. */

.triad {
  margin-top: clamp(2.2rem, 5vh, 3.6rem);
  display: flex;
  justify-content: center;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0 clamp(1.1rem, 2.6vw, 2.2rem);
  font-weight: 200;
  font-size: clamp(0.85rem, 1.45vw, 1.02rem);
}

/* Buttons, so the words are focusable and tappable — but they must not look
   like controls. Everything a button brings visually is stripped back out. */
.triad__word {
  appearance: none;
  background: none;
  border: 0;
  font: inherit;
  position: relative;          /* for the hairline below */
  color: rgba(233, 243, 255, 0.58);
  text-transform: uppercase;
  /* Settled tracking lives in a custom property so the entrance keyframe can
     land on it without hard-coding a value the breakpoints then override. */
  --track: 0.34em;
  --padx: 0rem;
  letter-spacing: var(--track);
  text-indent: var(--track);   /* trailing tracking pushes each word off-centre */
  text-shadow: 0 0 20px rgba(150, 200, 255, 0.30);
  /* generous vertical padding gives a real tap target despite the small type */
  padding: 0.55rem var(--padx);
  cursor: pointer;
  /* Asymmetric on purpose: the shorter duration lives on the hover state, so
     it arrives quickly and recedes slowly. Snapping away the instant the
     cursor leaves is the single thing that makes a hover feel cheap.
     No transition-DELAY here, ever — see the entrance section for why. */
  transition:
    color 560ms var(--ease-out),
    text-shadow 560ms var(--ease-out);
}

/* A hairline that draws outward from the centre. It earns its place twice: it
   is the only thing signalling these words are interactive at all, and it is
   the part of the gesture that reads as considered rather than a colour swap.
   scaleX so it costs no layout, and inset by the tracking so it spans the
   glyphs rather than the button's box. */
.triad__word::after {
  content: '';
  position: absolute;
  left: calc(var(--padx) + var(--track));
  right: calc(var(--padx) + var(--track));
  bottom: 0.3rem;
  height: 1px;
  background: linear-gradient(
    90deg,
    transparent,
    rgba(198, 226, 255, 0.62) 22%,
    rgba(198, 226, 255, 0.62) 78%,
    transparent
  );
  opacity: 0;
  transform: scaleX(0.2);
  transform-origin: 50% 50%;
  transition:
    transform 620ms var(--ease-out),
    opacity 620ms var(--ease-out);
}

.triad__word:hover,
.triad__word:focus-visible,
.triad__word[aria-expanded='true'] {
  color: rgba(243, 250, 255, 0.94);
  text-shadow: 0 0 26px rgba(170, 214, 255, 0.55);
  transition:
    color 300ms var(--ease-out),
    text-shadow 300ms var(--ease-out);
}

.triad__word:hover::after,
.triad__word:focus-visible::after,
.triad__word[aria-expanded='true']::after {
  opacity: 1;
  transform: scaleX(1);
  transition:
    transform 420ms var(--ease-out),
    opacity 300ms var(--ease-out);
}

/* The hairline is the focus affordance, so the default ring would be redundant
   noise on top of it. Keep a ring only where the hairline cannot be seen. */
.triad__word:focus-visible {
  outline: none;
}
@media (forced-colors: active) {
  .triad__word:focus-visible { outline: 2px solid; outline-offset: 4px; }
}

/* --- pillar definitions --------------------------------------------------- */

/* All three occupy ONE grid cell. The row sizes itself to the tallest, so
   revealing a definition shifts nothing — at any width, with no measurement
   and no hard-coded height to fall out of date when the copy changes. */
.pillars {
  display: grid;
  margin-top: clamp(0.9rem, 2vh, 1.4rem);
  max-width: 34rem;
  pointer-events: none;
}

.pillars__def {
  grid-area: 1 / 1;
  margin: 0;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 620ms var(--ease-out), transform 620ms var(--ease-out);
  text-shadow: 0 0 16px rgba(150, 200, 255, 0.22);
  font-size: clamp(0.75rem, 0.98vw, 0.8rem);
  font-weight: 300;
  line-height: 1.65;
  letter-spacing: 0.045em;
  color: rgba(198, 222, 246, 0.52);
}

.pillars[data-active='vision']     .pillars__def[data-pillar='vision'],
.pillars[data-active='precision']  .pillars__def[data-pillar='precision'],
.pillars[data-active='innovation'] .pillars__def[data-pillar='innovation'] {
  opacity: 1;
  transform: none;
}

/* --- entrance ------------------------------------------------------------
   Everything starts hidden and is released by .intro on the body, so the
   choreography can't fire before the ocean is actually on screen.           */

.wordmark {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity 1500ms var(--ease-out),
    transform 1500ms var(--ease-out);
}

body.intro .wordmark { opacity: 1; transform: none; transition-delay: 260ms; }

/* The three words arrive in sequence, so the statement reads as three
   deliberate terms rather than one long line.

   This is an ANIMATION, not a transition, and that is the whole point. The
   stagger needs a per-word delay; `transition-delay` takes one value that
   applies to EVERY property in the transition list, and `body.intro` never
   comes off. So a transition-based stagger permanently delayed the colour
   change too — clicking a word took ~1s to respond and felt broken. An
   animation carries its own delay and leaves `transition` free for interaction.

   Do not reintroduce transition-delay on .triad__word. */
.triad__word { opacity: 0; }

body.intro .triad__word {
  animation: word-in 1500ms var(--ease-out) both;
}
body.intro .triad__word:nth-child(1) { animation-delay: 900ms; }
body.intro .triad__word:nth-child(2) { animation-delay: 1080ms; }
body.intro .triad__word:nth-child(3) { animation-delay: 1260ms; }

@keyframes word-in {
  from {
    opacity: 0;
    transform: translateY(14px);
    letter-spacing: 0.58em;   /* tracking opens outward as the word arrives */
  }
  to {
    opacity: 1;
    transform: none;
    letter-spacing: var(--track);
  }
}

/* The specular sweep across the letterforms now happens in the shader, where it
   can ride the real light rather than approximate it with a CSS gradient. */

/* --- small screens ------------------------------------------------------- */

@media (max-width: 640px) {
  .stage { --lift: 4dvh; }
  .wordmark { width: min(78vw, 400px); }
  /* One value drives the settled tracking, the text-indent that recentres it,
     and where the entrance keyframe lands. */
  .triad__word { --track: 0.28em; }
}

/* One word per line reads better than a 2 + 1 wrap. */
@media (max-width: 520px) {
  .triad {
    flex-direction: column;
    align-items: center;
    gap: 0;
  }
  /* Stacked words are the only tap targets on the page, so give them real
     height and width — comfortably past the 24px WCAG 2.2 minimum. --padx
     rather than a padding shorthand, so the hairline stays aligned to the
     glyphs instead of the widened box. */
  .triad__word { --padx: 0.9rem; padding-block: 0.62rem; }
  .pillars { margin-top: clamp(1.1rem, 2.4vh, 1.6rem); }
}

/* Short landscape phones: reclaim vertical room. There is no space here for a
   reserved definition slot, so it collapses and the definitions are simply
   unavailable — this viewport is a glance, not a read. */
@media (max-height: 460px) {
  .stage {
    --lift: 0dvh;
    padding-block: 1.25rem;
  }
  .triad { margin-top: 1.4rem; }
  .triad__word { padding-block: 0.3rem; }
  .pillars { display: none; }
}

/* ?bare=1 — water only, used to shoot the static fallback plate. */
body.bare .stage { display: none; }

/* Still-capture mode (?still=<seconds>): no transitions, final state only. */
body.still #scene { transition: none; }
body.still .wordmark,
body.still .triad__word {
  opacity: 1;
  transform: none;
  transition: none;
  animation: none;   /* the entrance is an animation now, not a transition */
}
body.still .wordmark__mark { transition: none; }

/* --- reduced motion ------------------------------------------------------
   No entrance, no sweep, no animation anywhere. app.js additionally renders a
   single static frame and never starts the loop.                            */

@media (prefers-reduced-motion: reduce) {
  #scene { transition: none; }
  .wordmark,
  .triad__word {
    opacity: 1;
    transform: none;
    transition: none;
    animation: none;
  }
  /* Must match the specificity of `body.intro .triad__word`, which carries the
     entrance animation. A bare `.triad__word { animation: none }` loses the
     cascade to it and the words animate in regardless — which `transition:
     none` used to prevent only because the entrance was a transition. */
  body.intro .wordmark,
  body.intro .triad__word {
    animation: none;
    transition: none;
    opacity: 1;
    transform: none;
  }
  .wordmark__mark { transition: none; }
  /* Interaction still works — that is response, not decoration — but it
     arrives without the easing. */
  .pillars__def { transition: none; transform: none; }
  .triad__word::after { transition: none; }
}
