/* ==========================================================================
   SURREX — teaser page
   Single committed visual world (photographic, warm, dark-grounded).
   Brand values are taken from the source SVGs, not from the palette JPEG:
     wordmark ink  #0B130B
     mark ember    #A44819   (gradient runs to #8C370C)
   ========================================================================== */

/* Inter, self-hosted, under the SIL Open Font License (the full text
   travels with the build, in assets/). Subset to Latin plus the few marks
   this page uses, with the optical-size axis pinned at 32 — this is
   display type and wants the display cut — and the weight kept as a range
   so it can still be tuned without rebuilding the font. 22KB in total.

   It stands in for SF Pro, which is Apple's and whose licence covers
   designing for Apple platforms, not embedding in a public web page.
   Inter is the same lineage and is set here to match what SF Pro was
   doing: weight 350 carries the same stem-to-cap-height ratio (0.105)
   that SF Pro 300 at 112% width did, and tracking is opened from 0.08 to
   0.09em because Inter's caps are wider relative to their height. */
@font-face {
  font-family: "Inter";
  src: url("assets/inter.woff2") format("woff2-variations"),
       url("assets/inter.woff2") format("woff2");
  font-weight: 200 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --ink:         #0B130B;
  --ink-2:       #11170F;
  --ember:       #A44819;
  --ember-deep:  #8C370C;
  --bone:        #DFCDA8;

  /* neutrals are bone mixed toward ink, never a flat grey */
  --bone-70:     rgba(223, 205, 168, 0.70);
  --bone-46:     rgba(223, 205, 168, 0.46);
  --bone-22:     rgba(223, 205, 168, 0.22);
  --bone-12:     rgba(223, 205, 168, 0.12);

  --font-display: "Inter", -apple-system, BlinkMacSystemFont, "Helvetica Neue",
                  Helvetica, Arial, sans-serif;
  /* Nothing is fetched from a third party any more, so the one small
     instrument label falls back to whatever mono the system has. */
  --font-mono:    ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  height: 100dvh;
  margin: 0;
  background: var(--ink);
  color: var(--bone);
  font-family: var(--font-display);
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}

/* --- WebGL frame -------------------------------------------------------- */

/* The plate's video element is never looked at directly — the canvas
   samples it — but it has to stay in the document to keep decoding. */
.plate-source {
  position: fixed;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
  z-index: -1;
}

#scene {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
}

/* Covers the canvas until the first textured frame is ready, then lifts.
   Only armed when JS is running, so a dead script never leaves a black page. */
#curtain {
  position: fixed;
  inset: 0;
  background: var(--ink);
  z-index: 5;
  opacity: 0;
  transition: opacity 900ms var(--ease);
  pointer-events: none;
}
.js #curtain { opacity: 1; }
.js #curtain.is-lifted { opacity: 0; }

/* No scrims, veils or text shadows anywhere: the copy is set in the
   wordmark's own dark green, which carries itself against the sky the
   same way the mark does, so nothing has to be laid over the plate. */

/* --- Content ------------------------------------------------------------
   Everything below is placeholder copy and a provisional stack — the real
   layout lands once the content sketch is ready.
   ----------------------------------------------------------------------- */

/* --- Flickering squares -------------------------------------------------
   Each one is a flat brand-coloured swatch that blows out to white and
   drags a soft halo with it, then goes. The halo is a separate element
   rather than a box-shadow so it can be scaled and faded on the
   compositor — these spawn several times a second and the page is already
   running a shader. Every square's timing is driven by the Web Animations
   API on spawn, so the render loop only ever decides when to make one.
   ----------------------------------------------------------------------- */

.sparks {
  position: fixed;
  inset: 0;
  z-index: 2;
  overflow: hidden;
  pointer-events: none;
}

.spark {
  position: absolute;
  will-change: transform, opacity;
}

.spark__core {
  position: absolute;
  inset: 0;
}

.spark__halo {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 700%;
  height: 700%;
  transform: translate(-50%, -50%) scale(0.3);
  opacity: 0;
  background: radial-gradient(
    circle closest-side,
    rgba(255, 249, 234, 0.92) 0%,
    rgba(255, 244, 222, 0.42) 26%,
    rgba(255, 240, 214, 0.12) 48%,
    rgba(255, 238, 210, 0) 70%
  );
}

@media (prefers-reduced-motion: reduce) {
  .sparks { display: none; }
}

/* The two corners of the frame. Holding them in one flex row rather than
   pinning each separately is what keeps them on a shared centre line at
   every width, despite being different heights. */
.chrome {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 4;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: calc(24px + env(safe-area-inset-top, 0px)) clamp(20px, 3.2vw, 46px) 0;
  pointer-events: none;
}
.chrome > * { pointer-events: auto; }

.mark {
  display: block;
  width: clamp(38px, 3.4vw, 54px);
  height: auto;
  pointer-events: none;
  transition: opacity 900ms var(--ease) 200ms;
}
.js .mark { opacity: 0; }
.js .mark.is-in { opacity: 1; }

/* Centred, with only a few pixels of parallax drift written from JS — far
   less than the button's own size, so it never becomes a target that runs
   away from the hand reaching for it. */
/* Centred on the dynamic viewport, not the layout one. On a phone the
   layout viewport is the tall version that includes the strip behind the
   browser's own bars, so a plain 50% puts the copy below the middle of
   what is actually on screen — and it is the dynamic height that the
   canvas and the button's measured position already use. */
.content {
  position: fixed;
  left: 50%;
  top: 50%;
  top: 50dvh;
  transform: translate(-50%, -50%);
  z-index: 3;
  width: min(100%, 820px);
  padding-inline: 24px;
  pointer-events: none;
}

.content__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  transition: opacity 900ms var(--ease) 320ms, transform 900ms var(--ease) 320ms;
}
.js .content__inner {
  opacity: 0;
  transform: translateY(10px);
}
.js .content__inner.is-in {
  opacity: 1;
  transform: translateY(0);
}

/* --- Cursor-zone copy ---------------------------------------------------
   Three fragments of one line, each owning a third of the screen. Set in
   the wordmark's ink so they read as the same typographic family as the
   mark, and so they hold against bright sky without any shadow.
   Opacity is driven per frame from the cursor, not by CSS transitions —
   the damping already carries the easing.
   ----------------------------------------------------------------------- */

/* Every phrase is a single line now, so the stage holds exactly one and
   nothing below it can shift as they swap. */
.stage {
  position: relative;
  width: 100%;
  min-height: 1.15em;
  font-size: clamp(25px, 4.2vw, 60px);
  margin-bottom: 40px;
}

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

.phrase {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: inherit;
  font-weight: 350;
  line-height: 1.05;
  /* Caps this light need air between them or they read as a grey bar. */
  letter-spacing: 0.09em;
  text-transform: uppercase;
  white-space: nowrap;
  color: #000000;
  pointer-events: none;
}

/* Letter-spacing hangs one trailing space off the end of the line, so a
   centred line sits half a tracking unit left of true centre. Half of it
   back again, and the wordmark that replaces the middle line rides along
   with it. */
.line {
  position: relative;
  display: block;
  line-height: 1.05;
  transform: translateX(0.04em);
}

/* Characters are never faded — they roll through glyphs in place. The
   transition here only ever applies to the wordmark handover below. */
.glyph {
  display: inline-block;
  transition: opacity 130ms linear;
}

/* The wordmark stands in for the first line of the middle state once its
   glyphs have settled. It is set to the cap height of the type beneath it
   and centred over the line it replaces, so "Surrex / Builds" carries the
   same rhythm as "Community / backs" — and because "SURREX" in this face
   is within a few pixels of the wordmark's own width, the swap lands
   without the line changing size. */
.line__mark {
  position: absolute;
  /* JS writes `left` in pixels once the glyphs it covers have settled —
     50% is only what it sits at before the first handover, while it is
     still invisible. */
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  /* Sized so the mark comes out the same width as the six letters it
     replaces — a width mismatch shows as the outer letters shifting under
     the crossfade, where a hair of height difference does not. Inter's
     caps are proportionally wider than SF Pro's, so this is lower than
     the 0.72em that matched there. */
  height: 0.703em;
  width: auto;
  opacity: 0;
  transition: opacity 130ms linear;
  /* The wordmark SVG is a fixed dark green. Driving it to black in CSS
     keeps the middle state matching the two phrases either side of it,
     and the ticker beside it, without a second copy of the asset. */
  filter: brightness(0);
}

/* The Telegram call to action gets a line of its own; the X link sits
   beneath it as the quieter, secondary destination. */
.actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  pointer-events: auto;
}

/* Frosted glass: the button sits inside the page's own optical world,
   taking a real blur off whatever the canvas is rendering behind it.
   The pill radius echoes the half-discs the mark is built from. */
.cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 15px 34px;
  -webkit-backdrop-filter: blur(16px) saturate(115%);
  backdrop-filter: blur(16px) saturate(115%);
  /* Smoked rather than milky: a light tint would leave cream type sitting
     on bright cloud with almost no contrast. */
  background: rgba(11, 19, 11, 0.40);
  border: 1px solid rgba(223, 205, 168, 0.34);
  border-radius: 999px;
  color: var(--bone);
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  transition: background 280ms var(--ease), border-color 280ms var(--ease),
              transform 280ms var(--ease);
}

.cta:hover {
  background: rgba(11, 19, 11, 0.58);
  border-color: rgba(223, 205, 168, 0.58);
  transform: translateY(-1px);
}

.cta:focus-visible {
  outline: 2px solid var(--bone);
  outline-offset: 3px;
}

/* Where the blur is unsupported, fall back to a readable tinted plate. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .cta { background: rgba(11, 19, 11, 0.46); }
  .cta:hover { background: rgba(11, 19, 11, 0.60); }
}

/* Bare glyph in the opposite corner — no plate, no border. Ink green to
   match the copy, warming to the mark's ember on hover. */
.x-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 8px;
  margin-right: -8px; /* pull the optical edge back to the gutter */
  color: #000000;
  text-decoration: none;
  opacity: 0.85;
  transition: color 240ms var(--ease), opacity 240ms var(--ease),
              transform 240ms var(--ease);
}

.x-link:hover {
  color: var(--ember);
  opacity: 1;
  transform: translateY(-2px);
}

.x-link:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 4px;
}

/* --- Focus rail ---------------------------------------------------------
   A 1:1 read-out of the scrub value along the screen's x-axis: it encodes
   the interaction rather than decorating it, and doubles as the affordance.
   ----------------------------------------------------------------------- */

.rail {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 4;
  height: 1px;
  padding-bottom: env(safe-area-inset-bottom, 0px);
  pointer-events: none;
  transition: opacity 900ms var(--ease) 520ms;
}
.js .rail { opacity: 0; }
.js .rail.is-in { opacity: 1; }

.rail__track,
.rail__fill {
  position: absolute;
  left: 0;
  bottom: env(safe-area-inset-bottom, 0px);
  height: 1px;
}
.rail__track { right: 0; background: var(--bone-12); }
.rail__fill  { width: 0; background: var(--ember); }

/* Marks the focal point the tick is travelling toward. */
.rail__notch {
  position: absolute;
  bottom: env(safe-area-inset-bottom, 0px);
  left: 50%;
  width: 1px;
  height: 5px;
  background: var(--bone-46);
  transform: translateX(-0.5px);
}

.rail__tick {
  position: absolute;
  bottom: env(safe-area-inset-bottom, 0px);
  left: 0;
  width: 1px;
  height: 9px;
  background: var(--ember);
  transform: translateX(-0.5px);
}

.rail__hint {
  position: absolute;
  left: 22px;
  bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 300;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--bone-46);
  transition: opacity 700ms var(--ease);
}
.rail__hint.is-done { opacity: 0; }

/* The hint stays left-aligned at every width now that the X glyph owns
   the bottom centre. */

@media (prefers-reduced-motion: reduce) {
  .content, .rail, #curtain, .cta, .x-link, .rail__hint {
    transition-duration: 1ms !important;
    transition-delay: 0ms !important;
  }
}
