/* Stagecraft press front.
 *
 * Dark, purple standing in for blue. One horizontal fold across the middle:
 * drapery above, floor below, both lit from the fold. Everything here is
 * gradients and one SVG turbulence — no images, no fonts beyond the brand face,
 * no script. */

/* Registered so it INTERPOLATES. An unregistered custom property animates
   discretely — the stroke would snap between values instead of bouncing, which
   reads as a strobe rather than a heartbeat.
   It is a NUMBER, not a length: @property's initial-value must be
   computationally independent, so `0.055em` is invalid and silently drops the
   whole rule — which is exactly how the first version of this ended up
   snapping. A multiplier is font-size independent, and it makes the keyframes
   below readable as percentages of the base stroke. */
@property --sw {
  syntax: "<number>";
  inherits: false;
  initial-value: 1;
}

@font-face {
  font-family: BrandFont;
  src: url('../fonts/Angelic Serif.ttf') format('truetype');
  font-display: swap;
}

:root {
  /* Purple carrying the weight blue usually carries. */
  --ink:        #0c0818;   /* deepest, the wings */
  --ink-2:      #150e2b;
  --violet:     #241a4d;
  --violet-lit: #3b2a78;   /* where the light reaches */
  --glow:       #7d5cff;   /* the fold's own colour */
  --cream:      #efe8ff;
  --cream-dim:  #a99ec8;

  /* The stroke copy of the wordmark. Translucent on purpose: it is meant to
     interact with the drapery behind it, not to sit on top of it. */
  --stroke:     #b79cff66;

  --fold:       52vh;      /* where the scene folds */
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
  padding: 0;
  background: var(--ink);
  color: var(--cream);
  font-family: Verdana, Geneva, sans-serif;
  overflow: hidden;
}

/* ---------------------------------------------------------------- drape --
 * Vertical folds: every gradient varies along x and is constant along y, so
 * the bands read as hanging cloth rather than as a pattern. Three periods that
 * do not divide into each other, which is what keeps it from looking tiled. */
#drape {
  position: fixed;
  inset: 0 0 calc(100% - var(--fold)) 0;
  background:
    repeating-linear-gradient(90deg,
      #ffffff00 0px, #ffffff0a 37px, #00000026 74px, #ffffff00 111px),
    repeating-linear-gradient(90deg,
      #00000000 0px, #0000002e 53px, #ffffff08 106px, #00000000 159px),
    repeating-linear-gradient(90deg,
      #ffffff00 0px, #ffffff06 23px, #00000018 46px, #ffffff00 69px),
    linear-gradient(180deg, var(--ink) 0%, var(--ink-2) 46%, var(--violet) 88%, var(--violet-lit) 100%);
}

/* ----------------------------------------------------------------- floor --
 * The same light laid flat. A repeating gradient in y, tipped away under
 * perspective so the bands converge at the fold — the floor arriving at us. */
#floor {
  position: fixed;
  inset: var(--fold) 0 0 0;
  perspective: 34vh;
  perspective-origin: 50% 0%;
  overflow: hidden;
  background: linear-gradient(180deg, var(--violet-lit) 0%, var(--violet) 22%, var(--ink-2) 58%, var(--ink) 100%);
}
#floor::before {
  content: "";
  position: absolute;
  /* Hinged AT the fold and lying down toward the viewer. The earlier version
     hinged 120% above the floor, which put the pivot off-screen and swung the
     whole plane out of view — the bands were being drawn, just nowhere visible. */
  inset: 0 -70% -90% -70%;
  transform: rotateX(76deg);
  transform-origin: 50% 0%;
  /* The converging lines are what say "coming at us"; the rungs are what turn
     it into a grid. So the rungs stay very faint and far apart, and the whole
     thing is a suggestion of a plane rather than a floor you could walk on. */
  background:
    repeating-linear-gradient(0deg,
      #ffffff00 0px, #cbb9ff1c 3px, #ffffff00 6px, #ffffff00 104px),
    repeating-linear-gradient(90deg,
      #ffffff00 0px, #c9b6ff17 2px, #ffffff00 4px, #ffffff00 168px);
  opacity: 0.85;
  /* Strongest a little way in front of the fold — right at the fold the bands
     converge past the point of being readable, and behind the viewer there is
     nothing to light. */
  mask-image: linear-gradient(180deg, #0000 0%, #000c 18%, #0007 52%, #0000 88%);
}

/* ------------------------------------------------------------------ fold --
 * The line the scene folds on. Light pools here and spills a little both ways,
 * which is what sells the two planes as one room. */
#fold {
  position: fixed;
  left: 0;
  right: 0;
  top: var(--fold);
  height: 0;
  border-top: 1px solid #c9b6ff5c;
  box-shadow:
    0 0 2px 0 #b79cff40,
    0 -14vh 26vh -10vh #4a2fa03d,
    0  10vh 20vh -10vh #4a2fa04d;
}

/* ----------------------------------------------------------------- grain --
 * Overlay rather than normal, so it lifts the lit areas and leaves the wings
 * alone instead of fogging the whole frame evenly. */
#grain {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0.42;
  mix-blend-mode: overlay;
  pointer-events: none;
}

/* -------------------------------------------------------------- wordmark --
 * Two copies. The stroke sits above the fill and blends with the drapery, so
 * it keeps blending at every viewport size rather than being right at one. */
main {
  position: relative;
  z-index: 2;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.9rem;
  padding: 0 1rem;
}

#title-stack {
  position: relative;
  width: 100%;
  text-align: center;
  line-height: 1;
  /* Sits a little above the fold, so the descenders cross it. */
  margin-top: -6vh;
}

#title, #title-stroke {
  margin: 0;
  width: 100%;
  font-family: BrandFont, Georgia, serif;
  font-weight: 400;
  font-size: clamp(3.2rem, 17vw, 15rem);
  letter-spacing: 0.02em;
  white-space: nowrap;
  text-shadow: none;
}

#title {
  color: var(--cream);
}

#title-stroke {
  position: absolute;
  inset: 0;
  color: transparent;
  -webkit-text-stroke: calc(0.055em * var(--sw)) var(--stroke);
  mix-blend-mode: screen;
  z-index: 2;
  pointer-events: none;
  /* Ease-out on every segment: the attack leaves fast and the recovery
     settles, which is what makes it taut rather than a soft swell. The rest
     stretches are flat, so it costs them nothing. */
  animation: beat 7.2s cubic-bezier(.22, 1, .36, 1) infinite;
}

/* ---------------------------------------------------------------- beat --
 * A heartbeat in the stroke width, and nothing else on the page moves.
 *
 * Cheap on purpose: one text element repainting a stroke. No layout, no
 * compositing of the drape or the floor, no script, no renderer process doing
 * work on the whole view — which is the constraint this was asked under.
 *
 * Timing is regular; only the bounce DISTANCE is noisy. Four heartbeats per
 * cycle, each with a different peak, so it never lands in the same place twice
 * within a turn of the loop and does not read as a two-frame blink. The noise
 * is composed rather than random, so it costs nothing and is identical on every
 * machine — which also means it can be a golden frame later.
 *
 *   ceiling   150% of base, never exceeded
 *   floor      88% — the undershoot, which is what makes it taut rather than
 *              soft; a bounce that only ever goes up reads as a swell
 *   major      bounces most; the minor beat is the answering one
 */
@keyframes beat {
  /* --- 1 --- the big one: 150 / 114, undershoot 88 */
    0%    { --sw: 1.00; }
    1.4%  { --sw: 1.50; }   /* 150% — attack */
    4.6%  { --sw: 0.88; }   /*  88% — undershoot */
    6.8%  { --sw: 1.14; }   /* 114% — the answering beat */
    9.4%  { --sw: 0.96; }   /*  96% */
   12.0%  { --sw: 1.00; }
   25.0%  { --sw: 1.00; }   /* rest */

  /* --- 2 --- softer: 132 / 108, undershoot 93 */
   26.4%  { --sw: 1.32; }
   29.6%  { --sw: 0.93; }
   31.8%  { --sw: 1.08; }
   34.4%  { --sw: 0.97; }
   37.0%  { --sw: 1.00; }
   50.0%  { --sw: 1.00; }

  /* --- 3 --- nearly the big one: 144 / 118, undershoot 90 */
   51.4%  { --sw: 1.44; }
   54.6%  { --sw: 0.90; }
   56.8%  { --sw: 1.18; }
   59.4%  { --sw: 0.95; }
   62.0%  { --sw: 1.00; }
   75.0%  { --sw: 1.00; }

  /* --- 4 --- the quiet one: 126 / 105, undershoot 95 */
   76.4%  { --sw: 1.26; }
   79.6%  { --sw: 0.95; }
   81.8%  { --sw: 1.05; }
   84.4%  { --sw: 0.98; }
   87.0%  { --sw: 1.00; }
  100.0%  { --sw: 1.00; }
}

#line {
  margin: 0;
  font-size: clamp(0.8rem, 1.6vw, 1rem);
  letter-spacing: 0.34em;
  text-transform: lowercase;
  color: var(--cream-dim);
  text-align: center;
}

footer {
  position: fixed;
  z-index: 2;
  bottom: 1.4rem;
  left: 0;
  right: 0;
  text-align: center;
  font-size: 0.68rem;
  letter-spacing: 0.3em;
  text-transform: lowercase;
  &, * { color: #8d84ab; }
}

/* A short viewport has no room for the fold to be interesting; pull it up and
   let the wordmark keep the middle. */
/* The tagline's tracking is what makes it a nameplate rather than a sentence,
   but tracking is the first thing to overflow a narrow phone. */
@media (max-width: 420px) {
  #line { letter-spacing: 0.22em; font-size: 0.72rem; }
}

@media (max-height: 520px) {
  :root { --fold: 46vh; }
  #title-stack { margin-top: -3vh; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}
