/* ============================================================================
   thevendingmachine.cc — placeholder landing page
   Deliberately contentless: a mark, a wordmark, and light behind glass.

   Everything that animates continuously runs on `transform` / `opacity` only,
   so it stays on the compositor. All motion is opt-in via
   `prefers-reduced-motion: no-preference` — the page's resting state IS the
   final state, so nothing is left invisible when motion is off.
   ========================================================================= */

@font-face {
  font-family: "Bricolage Grotesque";
  src: url("assets/fonts/bricolage-grotesque-latin.woff2") format("woff2");
  font-weight: 200 800;
  font-stretch: 75% 100%;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Martian Mono";
  src: url("assets/fonts/martian-mono-latin.woff2") format("woff2");
  font-weight: 100 800;
  font-stretch: 75% 112.5%;
  font-style: normal;
  font-display: swap;
}

/* --------------------------------------------------------------- tokens -- */

:root {
  color-scheme: dark light;

  /* dark is the primary reading of this page: a lit machine in a dark room */
  --ground:      #06070A;
  --ink:         #EDEEF2;
  /* --ink-dim is measured, not eyeballed: the aurora drifts underneath it, so
     it has to clear 4.5:1 against the brightest plate the animation reaches. */
  --ink-dim:     #C3C9D8;
  --scrim:       rgba(4, 5, 8, 0.55);

  --glass:       rgba(255, 255, 255, 0.032);
  --glass-edge:  rgba(255, 255, 255, 0.115);
  --glass-spec:  rgba(255, 255, 255, 0.20);
  --glass-blur:  20px;
  --glass-sat:   135%;

  --au-teal:     rgba(14, 139, 150, 0.62);
  --au-petrol:   rgba(27, 58, 160, 0.58);
  --au-violet:   rgba(88, 33, 148, 0.46);
  --au-amber:    rgba(242, 163, 60, 0.40);
  --au-opacity:  1;

  --lamp-color:  rgba(242, 163, 60, 0.85);
  --bloom:       rgba(120, 220, 225, 0.30);
  --sweep-color: rgba(255, 255, 255, 0.11);
  --sweep-blend: plus-lighter;

  --grain-opacity: 0.055;
  --grain-blend:   overlay;

  --vignette: radial-gradient(120% 95% at 50% 42%,
                transparent 38%,
                rgba(0, 0, 0, 0.42) 78%,
                rgba(0, 0, 0, 0.70) 100%);

  /* the pointer-tracked highlight; JS writes these, CSS survives without it */
  --px: 50vw;
  --py: 42vh;

  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
}

@media (prefers-color-scheme: light) {
  :root {
    /* daylight: the same apparatus, seen through a bright window */
    --ground:      #F2EEE7;
    --ink:         #14161E;
    --ink-dim:     #3F4553;
    --scrim:       rgba(250, 247, 242, 0.62);

    /* far less white in the glass than the dark theme has black — otherwise the
       aurora turns to milk and the page loses every trace of colour */
    --glass:       rgba(255, 252, 246, 0.20);
    --glass-edge:  rgba(30, 26, 20, 0.14);
    --glass-spec:  rgba(255, 255, 255, 0.90);
    --glass-blur:  26px;
    --glass-sat:   150%;

    --au-teal:     rgba(0, 150, 152, 0.50);
    --au-petrol:   rgba(46, 74, 190, 0.38);
    --au-violet:   rgba(126, 62, 178, 0.30);
    --au-amber:    rgba(236, 138, 20, 0.52);
    --au-opacity:  1;

    --lamp-color:  rgba(198, 104, 12, 0.70);
    --bloom:       rgba(0, 140, 150, 0.26);
    --sweep-color: rgba(255, 255, 255, 0.72);
    --sweep-blend: overlay;

    --grain-opacity: 0.055;
    --grain-blend:   multiply;

    --vignette: radial-gradient(125% 100% at 50% 42%,
                  transparent 40%,
                  rgba(58, 40, 22, 0.07) 80%,
                  rgba(58, 40, 22, 0.16) 100%);
  }
}

/* ---------------------------------------------------------------- reset -- */

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

html, body { height: 100%; }

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100svh;
  background: var(--ground);
  color: var(--ink);
  font-family: "Bricolage Grotesque", ui-sans-serif, sans-serif;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow: hidden;
}

img { display: block; max-width: 100%; height: auto; }

/* --------------------------------------------------------------- aurora -- */

.aurora {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  opacity: var(--au-opacity);
  /* layout+paint, not `strict` — `strict` adds size containment, and a
     size-contained box is one stylesheet edit away from collapsing to nothing. */
  contain: layout paint style;
}

/* Soft radial stops rather than a blur filter: same look, far cheaper, and it
   leaves every blob on the compositor. The grain layer dithers the banding. */
.blob {
  position: absolute;
  border-radius: 50%;
  will-change: transform;
}

.blob--teal {
  width: 78vmax; height: 78vmax;
  top: -22vmax; left: -18vmax;
  background: radial-gradient(circle at 50% 50%, var(--au-teal) 0%, transparent 64%);
}

.blob--petrol {
  width: 86vmax; height: 86vmax;
  top: -12vmax; right: -26vmax;
  background: radial-gradient(circle at 50% 50%, var(--au-petrol) 0%, transparent 62%);
}

.blob--violet {
  width: 72vmax; height: 72vmax;
  bottom: -30vmax; left: 6vmax;
  background: radial-gradient(circle at 50% 50%, var(--au-violet) 0%, transparent 66%);
}

/* the warm one — the lamp inside the cabinet. Small, so it stays an accent. */
.blob--amber {
  width: 52vmax; height: 52vmax;
  bottom: -20vmax; left: 32%;
  background: radial-gradient(circle at 50% 50%, var(--au-amber) 0%, transparent 60%);
}

.pointer-light {
  position: absolute;
  top: 0; left: 0;
  width: 46vmax; height: 46vmax;
  margin: -23vmax 0 0 -23vmax;
  background: radial-gradient(circle at 50% 50%, var(--bloom) 0%, transparent 62%);
  transform: translate3d(var(--px), var(--py), 0);
  opacity: 0;
  transition: opacity 900ms ease-out;
  will-change: transform;
}

body.pointer-fine .pointer-light { opacity: 0.75; }

.vignette {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: var(--vignette);
}

/* ----------------------------------------------------------------- door -- */

/* The whole viewport is the machine's glass door. The few pixels of margin let
   the aurora show through unrefracted, which is what sells the glass. */
.door {
  position: fixed;
  /* the page is viewport-fit=cover, so the frame has to clear the notch */
  top:    calc(clamp(10px, 2.2vmin, 22px) + env(safe-area-inset-top));
  right:  calc(clamp(10px, 2.2vmin, 22px) + env(safe-area-inset-right));
  bottom: calc(clamp(10px, 2.2vmin, 22px) + env(safe-area-inset-bottom));
  left:   calc(clamp(10px, 2.2vmin, 22px) + env(safe-area-inset-left));
  z-index: 2;
  overflow: hidden;

  border: 1px solid var(--glass-edge);
  border-radius: clamp(16px, 2.4vmin, 28px);
  background: var(--glass);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
  box-shadow:
    inset 0 1px 0 var(--glass-spec),
    inset 0 -1px 0 rgba(0, 0, 0, 0.22),
    0 30px 90px rgba(0, 0, 0, 0.35);

  display: grid;
  grid-template-rows: 1fr auto;
  justify-items: center;
  align-items: center;
  padding: clamp(24px, 5vmin, 56px);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .door { background: color-mix(in srgb, var(--ground) 72%, transparent); }
}

/* Base scrim. The aurora is in motion, so the contrast of the footer line
   cannot be argued from a single screenshot — this pins the plate beneath it
   to a known value at every phase of the animation. It also gives the
   composition a floor, which the drifting amber was otherwise stealing. */
.door::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 34%;
  background: linear-gradient(to bottom, transparent, var(--scrim));
  pointer-events: none;
}

/* the shelf lamp: a hairline of light crossing behind the lockup */
.lamp {
  position: absolute;
  left: -5%;
  right: -5%;
  top: 44%;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--lamp-color) 48%, transparent);
  opacity: 0.5;
  pointer-events: none;
}

.lamp::after {
  content: "";
  position: absolute;
  inset: -110px 0;
  background: radial-gradient(38% 50% at 50% 50%, var(--lamp-color) 0%, transparent 72%);
  opacity: 0.26;
}

/* raking specular highlight */
.sweep {
  position: absolute;
  top: -30%;
  bottom: -30%;
  width: 42%;
  left: 0;
  pointer-events: none;
  transform: translate3d(-160%, 0, 0) skewX(-14deg);
  background: linear-gradient(90deg, transparent, var(--sweep-color) 46%, transparent);
  mix-blend-mode: var(--sweep-blend);
  opacity: 0;
  will-change: transform;
}

/* registration marks, like the corners of a machine's fascia */
.tick {
  position: absolute;
  width: 13px;
  height: 13px;
  border: 0 solid var(--glass-edge);
  pointer-events: none;
  transition: transform 700ms var(--ease-out), border-color 700ms ease;
}

.tick--tl { top: 14px;    left: 14px;  border-top-width: 1px; border-left-width: 1px; }
.tick--tr { top: 14px;    right: 14px; border-top-width: 1px; border-right-width: 1px; }
.tick--bl { bottom: 14px; left: 14px;  border-bottom-width: 1px; border-left-width: 1px; }
.tick--br { bottom: 14px; right: 14px; border-bottom-width: 1px; border-right-width: 1px; }

.door:hover .tick { border-color: color-mix(in srgb, var(--ink) 34%, transparent); }
.door:hover .tick--tl { transform: translate3d(-4px, -4px, 0); }
.door:hover .tick--tr { transform: translate3d( 4px, -4px, 0); }
.door:hover .tick--bl { transform: translate3d(-4px,  4px, 0); }
.door:hover .tick--br { transform: translate3d( 4px,  4px, 0); }

/* --------------------------------------------------------------- lockup -- */

.lockup {
  grid-row: 1;
  position: relative;
  z-index: 1; /* above .door::after */
  display: grid;
  justify-items: center;
  gap: clamp(18px, 3.4vmin, 30px);
  text-align: center;
}

.mark {
  position: relative;
  margin: 0;
  line-height: 0;
  transition: transform 800ms var(--ease-out);
}

/* <picture> is a wrapper, not a box — take it out of the layout entirely so the
   img stays the direct flow child of .mark. */
.mark picture { display: contents; }

/* Bounded on both axes and driven by the file's own aspect ratio, so the
   supplied art is never distorted or letterboxed. The asset is the full lockup
   (cabinet over two lines of type, 932 x 960, aspect 0.970), so it carries the
   name on its own and gets a larger slot than a bare mark would. */
.mark img {
  width: auto;
  height: auto;
  max-height: clamp(168px, 36vmin, 368px);
  max-width: min(80vw, 430px);
  position: relative;
  z-index: 1;
}

/* bloom behind the mark */
.mark::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 260%;
  aspect-ratio: 1;
  translate: -50% -50%;
  background: radial-gradient(circle at 50% 50%, var(--bloom) 0%, transparent 62%);
  opacity: 0;
  transform: scale(0.78);
  transition: opacity 800ms var(--ease-out), transform 800ms var(--ease-out);
  pointer-events: none;
}

.door:hover .mark { transform: translate3d(0, -6px, 0) scale(1.018); }
.door:hover .mark::after { opacity: 1; transform: scale(1); }

.meta {
  grid-row: 2;
  position: relative;
  z-index: 1; /* above .door::after */
  margin: 0;
  font-family: "Martian Mono", ui-monospace, monospace;
  font-size: 0.625rem;
  font-variation-settings: "wdth" 88, "wght" 400;
  letter-spacing: 0.24em;
  text-indent: 0.24em;
  color: var(--ink-dim);
}

/* ---------------------------------------------------------------- grain -- */

.grain {
  position: fixed;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: var(--grain-blend);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.86' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* --------------------------------------------------------------- motion -- */

@media (prefers-reduced-motion: no-preference) {

  /* --- drift: four periods that never line back up --- */

  @keyframes drift-a {
    0%   { transform: translate3d(0, 0, 0) scale(1); }
    50%  { transform: translate3d(7vmax, 5vmax, 0) scale(1.14); }
    100% { transform: translate3d(0, 0, 0) scale(1); }
  }
  @keyframes drift-b {
    0%   { transform: translate3d(0, 0, 0) scale(1.06); }
    50%  { transform: translate3d(-6vmax, 8vmax, 0) scale(0.92); }
    100% { transform: translate3d(0, 0, 0) scale(1.06); }
  }
  @keyframes drift-c {
    0%   { transform: translate3d(0, 0, 0) scale(0.96); }
    50%  { transform: translate3d(9vmax, -6vmax, 0) scale(1.18); }
    100% { transform: translate3d(0, 0, 0) scale(0.96); }
  }
  @keyframes drift-d {
    0%   { transform: translate3d(0, 0, 0) scale(1); }
    50%  { transform: translate3d(-8vmax, -7vmax, 0) scale(1.26); }
    100% { transform: translate3d(0, 0, 0) scale(1); }
  }

  .blob--teal   { animation: drift-a 41s ease-in-out infinite; }
  .blob--petrol { animation: drift-b 53s ease-in-out -9s infinite; }
  .blob--violet { animation: drift-c 47s ease-in-out -21s infinite; }
  .blob--amber  { animation: drift-d 34s ease-in-out -5s infinite; }

  @keyframes breathe {
    0%, 100% { opacity: 0.34; }
    50%      { opacity: 0.62; }
  }
  .lamp { animation: breathe 11s ease-in-out infinite; }

  /* --- entrance --- */

  @keyframes fade-in { from { opacity: 0; } }

  @keyframes door-in {
    from { opacity: 0; transform: scale(0.985); }
    to   { opacity: 1; transform: scale(1); }
  }

  @keyframes rise {
    from { opacity: 0; transform: translate3d(0, 14px, 0); }
    to   { opacity: 1; transform: translate3d(0, 0, 0); }
  }

  /* `backwards`, never `both`.
     A `both`-filled animation keeps owning the properties it animated after it
     ends, and an animation always outranks a plain declaration — so the hover
     lift on .mark could not win against a finished `rise`. Cancelling the
     animation on :hover to get around that is the trap: mouse-out restores the
     animation-name, which RESTARTS it, and with a 480ms delay the mark spends
     half a second at `opacity: 0` every time the pointer leaves.
     `backwards` fills the delay and then lets go. Every one of these ends on
     its element's own resting values, so there is nothing to hold. */
  .aurora   { animation: fade-in 1600ms ease-out backwards; }
  .door     { animation: door-in 1000ms var(--ease-out) 120ms backwards; }
  .mark     { animation: rise 900ms var(--ease-out) 480ms backwards; }
  .meta     { animation: rise 900ms var(--ease-out) 720ms backwards; }

  /* --- sweep: once on arrival, again on hover --- */

  @keyframes sweep-intro {
    0%   { opacity: 0; transform: translate3d(-160%, 0, 0) skewX(-14deg); }
    12%  { opacity: 1; }
    88%  { opacity: 1; }
    100% { opacity: 0; transform: translate3d(360%, 0, 0) skewX(-14deg); }
  }
  @keyframes sweep-hover {
    0%   { opacity: 0; transform: translate3d(-160%, 0, 0) skewX(-14deg); }
    14%  { opacity: 1; }
    86%  { opacity: 1; }
    100% { opacity: 0; transform: translate3d(360%, 0, 0) skewX(-14deg); }
  }

  /* Two elements, not one animation-name swapped on :hover. Swapping the name
     means mouse-out swaps it BACK, which replays the arrival sweep 1.25s after
     every time the pointer leaves. The intro's name never changes, so it fires
     exactly once; the hover bar has no animation at rest, so :hover is what
     starts it and re-entering restarts it cleanly. */
  .sweep--intro { animation: sweep-intro 2000ms var(--ease-out) 1250ms backwards; }
  .door:hover .sweep--hover { animation: sweep-hover 1150ms var(--ease-out); }

  /* --- the mark's power-on flicker --- */

  @keyframes charge {
    0%   { filter: none; }
    35%  { filter: drop-shadow(-3px 0 0 rgba(20, 200, 210, 0.55))
                   drop-shadow( 3px 0 0 rgba(242, 163, 60, 0.50)); }
    100% { filter: none; }
  }
  .door:hover .mark img { animation: charge 820ms ease-out; }
}

/* Motion off: no keyframes are ever declared, the resting state above stands,
   and the pointer light is never armed by app.js. */
@media (prefers-reduced-motion: reduce) {
  .sweep { display: none; }
  .door:hover .mark,
  .door:hover .tick { transform: none; }

  /* the bloom may still fade in — it must not travel while doing so */
  .mark::after { transition: opacity 400ms ease; transform: scale(1); }
  .door:hover .mark::after { transform: scale(1); }
}

/* ------------------------------------------------------------- narrow -- */

@media (max-width: 480px) {
  .tick { width: 10px; height: 10px; }
  .tick--tl, .tick--tr { top: 10px; }
  .tick--bl, .tick--br { bottom: 10px; }
  .tick--tl, .tick--bl { left: 10px; }
  .tick--tr, .tick--br { right: 10px; }
  .meta { letter-spacing: 0.16em; text-indent: 0.16em; }
}

@media (max-height: 460px) {
  .mark img { max-height: clamp(96px, 26vmin, 150px); }
}
